lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Mon, 14 Jul 2014 15:36:53 +0530
From:	pramod gurav <pramod.gurav.etc@...il.com>
To:	Stephen Boyd <sboyd@...eaurora.org>
Cc:	linux-arm-msm@...r.kernel.org,
	linux-arm-kernel@...ts.infradead.org, linux-kernel@...r.kernel.org,
	Mike Turquette <mturquette@...aro.org>,
	Saravana Kannan <skannan@...eaurora.org>
Subject: Re: [RFC/PATCH 03/12] clk: qcom: Add support for muxes, dividers, and
 mux dividers

Hi Stephen,

On Wed, Jun 25, 2014 at 5:36 AM, Stephen Boyd <sboyd@...eaurora.org> wrote:
> The Krait CPU clocks are made up of muxes and dividers with a
> handful of sources. Add a set of clk_ops that allow us to
> configure these clocks so we can support CPU frequency scaling on
> Krait CPUs.
>
> Based on code originally written by Saravana Kannan.
>
> Cc: Saravana Kannan <skannan@...eaurora.org>
> Signed-off-by: Stephen Boyd <sboyd@...eaurora.org>
> ---
>  drivers/clk/qcom/Makefile           |   1 +
>  drivers/clk/qcom/clk-generic.c      | 405 ++++++++++++++++++++++++++++++++++++
>  include/linux/clk/msm-clk-generic.h | 208 ++++++++++++++++++
>  3 files changed, 614 insertions(+)
>  create mode 100644 drivers/clk/qcom/clk-generic.c
>  create mode 100644 include/linux/clk/msm-clk-generic.h
>
> diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
> index 689e05bf4f95..8684ff004be6 100644
> --- a/drivers/clk/qcom/Makefile
> +++ b/drivers/clk/qcom/Makefile
> @@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o
>  clk-qcom-y += clk-rcg.o
>  clk-qcom-y += clk-rcg2.o
>  clk-qcom-y += clk-branch.o
> +clk-qcom-y += clk-generic.o
>  clk-qcom-y += reset.o
>
>  obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o
> diff --git a/drivers/clk/qcom/clk-generic.c b/drivers/clk/qcom/clk-generic.c
> new file mode 100644
> index 000000000000..a0d778ba7394
> --- /dev/null
> +++ b/drivers/clk/qcom/clk-generic.c
> @@ -0,0 +1,405 @@
> +/*
> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/export.h>
> +#include <linux/bug.h>
> +#include <linux/err.h>
> +#include <linux/clk-provider.h>
> +#include <linux/clk/msm-clk-generic.h>
> +
> +
> +/* ==================== Mux clock ==================== */
> +
> +static int mux_set_parent(struct clk_hw *hw, u8 sel)
> +{
> +       struct mux_clk *mux = to_mux_clk(hw);
> +
> +       if (mux->parent_map)
> +               sel = mux->parent_map[sel];
> +
> +       return mux->ops->set_mux_sel(mux, sel);
> +}
> +
> +static u8 mux_get_parent(struct clk_hw *hw)
> +{
> +       struct mux_clk *mux = to_mux_clk(hw);
> +       int num_parents = __clk_get_num_parents(hw->clk);
> +       int i;
> +       u8 sel;
> +
> +       sel = mux->ops->get_mux_sel(mux);
> +       if (mux->parent_map) {
> +               for (i = 0; i < num_parents; i++)
> +                       if (sel == mux->parent_map[i])
> +                               return i;
> +               WARN(1, "Can't find parent\n");
> +               return -EINVAL;
> +       }
> +
> +       return sel;
> +}
> +
> +static int mux_enable(struct clk_hw *hw)
> +{
> +       struct mux_clk *mux = to_mux_clk(hw);

Missing a blank line after declarations.

> +       if (mux->ops->enable)
> +               return mux->ops->enable(mux);
> +       return 0;
> +}
> +
> +static void mux_disable(struct clk_hw *hw)
> +{
> +       struct mux_clk *mux = to_mux_clk(hw);

Same here.

> +       if (mux->ops->disable)
> +               return mux->ops->disable(mux);
> +}
> +
> +static struct clk *mux_get_safe_parent(struct clk_hw *hw)
> +{
> +       int i;
> +       struct mux_clk *mux = to_mux_clk(hw);
> +       int num_parents = __clk_get_num_parents(hw->clk);
> +
> +       if (!mux->has_safe_parent)
> +               return NULL;
> +
> +       i = mux->safe_sel;
> +       if (mux->parent_map)
> +               for (i = 0; i < num_parents; i++)
> +                       if (mux->safe_sel == mux->parent_map[i])
> +                               break;
> +
> +       return clk_get_parent_by_index(hw->clk, i);
> +}
> +
> +const struct clk_ops clk_ops_gen_mux = {
> +       .enable = mux_enable,
> +       .disable = mux_disable,
> +       .set_parent = mux_set_parent,
> +       .get_parent = mux_get_parent,
> +       .determine_rate = __clk_mux_determine_rate,
> +       .get_safe_parent = mux_get_safe_parent,
> +};
> +EXPORT_SYMBOL_GPL(clk_ops_gen_mux);
> +
> +/* ==================== Divider clock ==================== */
> +
> +static long __div_round_rate(struct div_data *data, unsigned long rate,
> +       struct clk *parent, unsigned int *best_div, unsigned long *best_prate,
> +       bool set_parent)
> +{
> +       unsigned int div, min_div, max_div, _best_div = 1;
> +       unsigned long prate, _best_prate = 0, rrate = 0, req_prate, actual_rate;
> +       unsigned int numer;
> +
> +       rate = max(rate, 1UL);
> +
> +       min_div = max(data->min_div, 1U);
> +       max_div = min(data->max_div, (unsigned int) (ULONG_MAX / rate));
> +
> +       /*
> +        * div values are doubled for half dividers.
> +        * Adjust for that by picking a numer of 2.
> +        */
> +       numer = data->is_half_divider ? 2 : 1;
> +
> +       if (!set_parent) {
> +               prate = *best_prate * numer;
> +               div = DIV_ROUND_UP(prate, rate);
> +               div = clamp(1U, div, max_div);
> +               if (best_div)
> +                       *best_div = div;
> +               return mult_frac(*best_prate, numer, div);
> +       }
> +
> +       for (div = min_div; div <= max_div; div++) {
> +               req_prate = mult_frac(rate, div, numer);
> +               prate = __clk_round_rate(parent, req_prate);
> +               if (IS_ERR_VALUE(prate))
> +                       break;
> +
> +               actual_rate = mult_frac(prate, numer, div);
> +               if (is_better_rate(rate, rrate, actual_rate)) {
> +                       rrate = actual_rate;
> +                       _best_div = div;
> +                       _best_prate = prate;
> +               }
> +
> +               /*
> +                * Trying higher dividers is only going to ask the parent for
> +                * a higher rate. If it can't even output a rate higher than
> +                * the one we request for this divider, the parent is not
> +                * going to be able to output an even higher rate required
> +                * for a higher divider. So, stop trying higher dividers.
> +                */
> +               if (actual_rate < rate)
> +                       break;
> +
> +               if (rrate <= rate)
> +                       break;
> +       }
> +
> +       if (!rrate)
> +               return -EINVAL;
> +       if (best_div)
> +               *best_div = _best_div;
> +       if (best_prate)
> +               *best_prate = _best_prate;
> +
> +       return rrate;
> +}
> +
> +static long div_round_rate(struct clk_hw *hw, unsigned long rate,
> +                          unsigned long *parent_rate)
> +{
> +       struct div_clk *d = to_div_clk(hw);
> +       bool set_parent = __clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT;
> +
> +       return __div_round_rate(&d->data, rate, __clk_get_parent(hw->clk),
> +                               NULL, parent_rate, set_parent);
> +}
> +
> +static int div_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long
> +                       parent_rate)
> +{
> +       struct div_clk *d = to_div_clk(hw);
> +       int div, rc = 0;
> +       struct div_data *data = &d->data;
> +
> +       div = parent_rate / rate;
> +       if (div != data->div)
> +               rc = d->ops->set_div(d, div);
> +       data->div = div;
> +
> +       return rc;
> +}
> +
> +static int div_enable(struct clk_hw *hw)
> +{
> +       struct div_clk *d = to_div_clk(hw);

Missing a black line here.

> +       if (d->ops && d->ops->enable)
> +               return d->ops->enable(d);
> +       return 0;
> +}
> +
> +static void div_disable(struct clk_hw *hw)
> +{
> +       struct div_clk *d = to_div_clk(hw);

Here as well.

> +       if (d->ops && d->ops->disable)
> +               return d->ops->disable(d);
> +}
> +
> +static unsigned long div_recalc_rate(struct clk_hw *hw, unsigned long prate)
> +{
> +       struct div_clk *d = to_div_clk(hw);
> +       unsigned int div = d->data.div;
> +
> +       if (d->ops && d->ops->get_div)
> +               div = max(d->ops->get_div(d), 1);
> +       div = max(div, 1U);
> +
> +       if (!d->ops || !d->ops->set_div)
> +               d->data.min_div = d->data.max_div = div;
> +       d->data.div = div;
> +
> +       return prate / div;
> +}
> +
> +const struct clk_ops clk_ops_div = {
> +       .enable = div_enable,
> +       .disable = div_disable,
> +       .round_rate = div_round_rate,
> +       .set_rate = div_set_rate,
> +       .recalc_rate = div_recalc_rate,
> +};
> +EXPORT_SYMBOL_GPL(clk_ops_div);
> +
> +/* ==================== Mux_div clock ==================== */
> +
> +static int mux_div_clk_enable(struct clk_hw *hw)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +
> +       if (md->ops->enable)
> +               return md->ops->enable(md);
> +       return 0;
> +}
> +
> +static void mux_div_clk_disable(struct clk_hw *hw)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +
> +       if (md->ops->disable)
> +               return md->ops->disable(md);
> +}
> +
> +static long __mux_div_round_rate(struct clk_hw *hw, unsigned long rate,
> +       struct clk **best_parent, int *best_div, unsigned long *best_prate)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +       unsigned int i;
> +       unsigned long rrate, best = 0, _best_div = 0, _best_prate = 0;
> +       struct clk *_best_parent = 0;
> +       int num_parents = __clk_get_num_parents(hw->clk);
> +       bool set_parent = __clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT;
> +
> +       for (i = 0; i < num_parents; i++) {
> +               int div;
> +               unsigned long prate;
> +               struct clk *p = clk_get_parent_by_index(hw->clk, i);
> +
> +               rrate = __div_round_rate(&md->data, rate, p, &div, &prate,
> +                               set_parent);
> +
> +               if (is_better_rate(rate, best, rrate)) {
> +                       best = rrate;
> +                       _best_div = div;
> +                       _best_prate = prate;
> +                       _best_parent = p;
> +               }
> +
> +               if (rate <= rrate)
> +                       break;
> +       }
> +
> +       if (best_div)
> +               *best_div = _best_div;
> +       if (best_prate)
> +               *best_prate = _best_prate;
> +       if (best_parent)
> +               *best_parent = _best_parent;
> +
> +       if (best)
> +               return best;
> +       return -EINVAL;
> +}
> +
> +static long mux_div_clk_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                  unsigned long *parent_rate)
> +{
> +       return __mux_div_round_rate(hw, rate, NULL, NULL, parent_rate);
> +}
> +
> +/* requires enable lock to be held */
> +static int __set_src_div(struct mux_div_clk *md, u8 src_sel, u32 div)
> +{
> +       int rc;
> +
> +       rc = md->ops->set_src_div(md, src_sel, div);
> +       if (!rc) {
> +               md->data.div = div;
> +               md->src_sel = src_sel;
> +       }
> +
> +       return rc;
> +}
> +
> +/* Must be called after handoff to ensure parent clock rates are initialized */
> +static int safe_parent_init_once(struct clk_hw *hw)
> +{
> +       unsigned long rrate;
> +       u32 best_div;
> +       struct clk *best_parent;
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +
> +       if (IS_ERR(md->safe_parent))
> +               return -EINVAL;
> +       if (!md->safe_freq || md->safe_parent)
> +               return 0;
> +
> +       rrate = __mux_div_round_rate(hw, md->safe_freq, &best_parent,
> +                       &best_div, NULL);
> +
> +       if (rrate == md->safe_freq) {
> +               md->safe_div = best_div;
> +               md->safe_parent = best_parent;
> +       } else {
> +               md->safe_parent = ERR_PTR(-EINVAL);
> +               return -EINVAL;
> +       }
> +       return 0;
> +}
> +
> +static int
> +__mux_div_clk_set_rate_and_parent(struct clk_hw *hw, u8 index, u32 div)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +       int rc;
> +
> +       rc = safe_parent_init_once(hw);
> +       if (rc)
> +               return rc;
> +
> +       return __set_src_div(md, index, div);
> +}
> +
> +static int mux_div_clk_set_rate_and_parent(struct clk_hw *hw,
> +               unsigned long rate, unsigned long parent_rate, u8 index)
> +{
> +       return __mux_div_clk_set_rate_and_parent(hw, index, parent_rate / rate);
> +}
> +
> +static int mux_div_clk_set_rate(struct clk_hw *hw,
> +               unsigned long rate, unsigned long parent_rate)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);

Missing a blank line.

> +       return __mux_div_clk_set_rate_and_parent(hw, md->src_sel,
> +                       parent_rate / rate);
> +}
> +
> +static int mux_div_clk_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);

Same here.

> +       return __mux_div_clk_set_rate_and_parent(hw, md->parent_map[index],
> +                       md->data.div);
> +}
> +
> +static u8 mux_div_clk_get_parent(struct clk_hw *hw)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +       int num_parents = __clk_get_num_parents(hw->clk);
> +       u32 i, div, sel;
> +
> +       md->ops->get_src_div(md, &sel, &div);
> +       md->src_sel = sel;
> +
> +       for (i = 0; i < num_parents; i++)
> +               if (sel == md->parent_map[i])
> +                       return i;
> +       WARN(1, "Can't find parent\n");
> +       return -EINVAL;
> +}
> +
> +static unsigned long
> +mux_div_clk_recalc_rate(struct clk_hw *hw, unsigned long prate)
> +{
> +       struct mux_div_clk *md = to_mux_div_clk(hw);
> +       u32 div, sel;
> +
> +       md->ops->get_src_div(md, &sel, &div);
> +
> +       return prate / div;
> +}
> +
> +const struct clk_ops clk_ops_mux_div_clk = {
> +       .enable = mux_div_clk_enable,
> +       .disable = mux_div_clk_disable,
> +       .set_rate_and_parent = mux_div_clk_set_rate_and_parent,
> +       .set_rate = mux_div_clk_set_rate,
> +       .set_parent = mux_div_clk_set_parent,
> +       .round_rate = mux_div_clk_round_rate,
> +       .get_parent = mux_div_clk_get_parent,
> +       .recalc_rate = mux_div_clk_recalc_rate,
> +};
> +EXPORT_SYMBOL_GPL(clk_ops_mux_div_clk);
> diff --git a/include/linux/clk/msm-clk-generic.h b/include/linux/clk/msm-clk-generic.h
> new file mode 100644
> index 000000000000..cee38636fe0f
> --- /dev/null
> +++ b/include/linux/clk/msm-clk-generic.h
> @@ -0,0 +1,208 @@
> +/*
> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef __QCOM_CLK_GENERIC_H__
> +#define __QCOM_CLK_GENERIC_H__
> +
> +#include <linux/err.h>
> +#include <linux/clk-provider.h>
> +
> +static inline bool is_better_rate(unsigned long req, unsigned long best,
> +                                 unsigned long new)
> +{
> +       if (IS_ERR_VALUE(new))
> +               return false;
> +
> +       return (req <= new && new < best) || (best < req && best < new);
> +}
> +
> +/* ==================== Mux clock ==================== */
> +
> +struct mux_clk;
> +
> +struct clk_mux_ops {
> +       int (*set_mux_sel)(struct mux_clk *clk, int sel);
> +       int (*get_mux_sel)(struct mux_clk *clk);
> +
> +       /* Optional */
> +       bool (*is_enabled)(struct mux_clk *clk);
> +       int (*enable)(struct mux_clk *clk);
> +       void (*disable)(struct mux_clk *clk);
> +};
> +
> +struct mux_clk {
> +       /* Parents in decreasing order of preference for obtaining rates. */
> +       u8              *parent_map;

Checkpatch fails here with "no space before tabs".

> +       bool            has_safe_parent;
> +       u8              safe_sel;
> +       const struct clk_mux_ops *ops;
> +
> +       /* Fields not used by helper function. */
> +       void __iomem    *base;
Same here.

> +       u32             offset;
> +       u32             en_offset;
> +       int             en_reg;
> +       u32             mask;
> +       u32             shift;
> +       u32             en_mask;
> +       void            *priv;
> +
> +       struct clk_hw   hw;
> +};
> +
> +static inline struct mux_clk *to_mux_clk(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct mux_clk, hw);
> +}
> +
> +extern const struct clk_ops clk_ops_gen_mux;
> +
> +/* ==================== Divider clock ==================== */
> +
> +struct div_clk;
> +
> +struct clk_div_ops {
> +       int (*set_div)(struct div_clk *clk, int div);
> +       int (*get_div)(struct div_clk *clk);
> +       bool (*is_enabled)(struct div_clk *clk);
> +       int (*enable)(struct div_clk *clk);
> +       void (*disable)(struct div_clk *clk);
> +};
> +
> +struct div_data {
> +       unsigned int div;
> +       unsigned int min_div;
> +       unsigned int max_div;
> +       /*
> +        * Indicate whether this divider clock supports half-interger divider.
> +        * If it is, all the min_div and max_div have been doubled. It means
> +        * they are 2*N.
> +        */
> +       bool is_half_divider;
> +};
> +
> +struct div_clk {
> +       struct div_data data;
> +
> +       /* Optional */
> +       const struct clk_div_ops *ops;
> +
> +       /* Fields not used by helper function. */
> +       void __iomem    *base;
Same here.
> +       u32             offset;
> +       u32             mask;
> +       u32             shift;
> +       u32             en_mask;
> +       void            *priv;
> +       struct clk_hw   hw;
> +};
> +
> +static inline struct div_clk *to_div_clk(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct div_clk, hw);
> +}
> +
> +extern const struct clk_ops clk_ops_div;
> +
> +#define DEFINE_FIXED_DIV_CLK(clk_name, _div, _parent) \
> +static struct div_clk clk_name = {     \
> +       .data = {                               \
> +               .max_div = _div,                \
> +               .min_div = _div,                \
> +               .div = _div,                    \
> +       },                                      \
> +       .hw.init = &(struct clk_init_data){ \
> +               .parent_names = (const char *[]){ _parent }, \
> +               .num_parents = 1,               \
> +               .name = #clk_name,              \
> +               .ops = &clk_ops_div,            \
> +               .flags = CLK_SET_RATE_PARENT,   \
> +       }                                       \
> +}
> +
> +/* ==================== Mux Div clock ==================== */
> +
> +struct mux_div_clk;
> +
> +/*
> + * struct mux_div_ops
> + * the enable and disable ops are optional.
> + */
> +
> +struct mux_div_ops {
> +       int (*set_src_div)(struct mux_div_clk *, u32 src_sel, u32 div);
> +       void (*get_src_div)(struct mux_div_clk *, u32 *src_sel, u32 *div);
> +       int (*enable)(struct mux_div_clk *);
> +       void (*disable)(struct mux_div_clk *);
> +       bool (*is_enabled)(struct mux_div_clk *);
> +};
> +
> +/*
> + * struct mux_div_clk - combined mux/divider clock
> + * @priv
> +               parameters needed by ops
> + * @safe_freq
> +               when switching rates from A to B, the mux div clock will
> +               instead switch from A -> safe_freq -> B. This allows the
> +               mux_div clock to change rates while enabled, even if this
> +               behavior is not supported by the parent clocks.
> +
> +               If changing the rate of parent A also causes the rate of
> +               parent B to change, then safe_freq must be defined.
> +
> +               safe_freq is expected to have a source clock which is always
> +               on and runs at only one rate.
> + * @parents
> +               list of parents and mux indicies
> + * @ops
> +               function pointers for hw specific operations
> + * @src_sel
> +               the mux index which will be used if the clock is enabled.
> + */
> +
> +struct mux_div_clk {
> +       /* Required parameters */
> +       const struct mux_div_ops        *ops;
> +       struct div_data                 data;
> +       u8                              *parent_map;
> +
> +       struct clk_hw                   hw;
> +
> +       /* Internal */
> +       u32                             src_sel;
> +
> +       /* Optional parameters */
> +       void                            *priv;
> +       void __iomem                    *base;
> +       u32                             div_mask;
> +       u32                             div_offset;
> +       u32                             div_shift;
> +       u32                             src_mask;
> +       u32                             src_offset;
> +       u32                             src_shift;
> +       u32                             en_mask;
> +       u32                             en_offset;
> +
> +       u32                             safe_div;
> +       struct clk                      *safe_parent;
> +       unsigned long                   safe_freq;
> +};
> +
> +static inline struct mux_div_clk *to_mux_div_clk(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct mux_div_clk, hw);
> +}
> +
> +extern const struct clk_ops clk_ops_mux_div_clk;
> +
> +#endif
> --
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> hosted by The Linux Foundation
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@...r.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/



-- 
Thanks and Regards
Pramod
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists