Re: [PATCH 1/2] gadget: Introduce the usb charger framework

From: Peter Chen
Date: Fri Aug 07 2015 - 02:52:35 EST


On Thu, Aug 06, 2015 at 03:03:48PM +0800, Baolin Wang wrote:
> This patch introduces the usb charger driver based on usb gadget that
> makes an enhancement to a power driver. It works well in practice but
> that requires a system with suitable hardware.
>
> The basic conception of the usb charger is that, when one usb charger
> is added or removed by reporting from the usb gadget state change or
> the extcon device state change, the usb charger will report to power
> user to set the current limitation.
>
> The usb charger will register notifiees on the usb gadget or the extcon
> device to get notified the usb charger state.
>
> Power user will register a notifiee on the usb charger to get notified
> by status changes from the usb charger. It will report to power user
> to set the current limitation when detecting the usb charger is added
> or removed from extcon device state or usb gadget state.
>
> Signed-off-by: Baolin Wang <baolin.wang@xxxxxxxxxx>
> ---
> drivers/usb/gadget/charger.c | 547 +++++++++++++++++++++++++++++++++++++++
> include/linux/usb/usb_charger.h | 101 ++++++++
> 2 files changed, 648 insertions(+)
> create mode 100644 drivers/usb/gadget/charger.c
> create mode 100644 include/linux/usb/usb_charger.h
>
> diff --git a/drivers/usb/gadget/charger.c b/drivers/usb/gadget/charger.c
> new file mode 100644
> index 0000000..3ca0180
> --- /dev/null
> +++ b/drivers/usb/gadget/charger.c
> @@ -0,0 +1,547 @@
> +/*
> + * usb charger.c -- USB charger driver
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/extcon.h>
> +#include <linux/export.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +#include <linux/usb/ch9.h>
> +#include <linux/usb/gadget.h>
> +#include <linux/usb/usb_charger.h>
> +
> +#define DEFAULT_CUR_PROTECT (50)
> +#define DEFAULT_SDP_CUR_LIMIT (500 - DEFAULT_CUR_PROTECT)
> +#define DEFAULT_DCP_CUR_LIMIT (1500 - DEFAULT_CUR_PROTECT)
> +#define DEFAULT_CDP_CUR_LIMIT (1500 - DEFAULT_CUR_PROTECT)
> +#define DEFAULT_ACA_CUR_LIMIT (1500 - DEFAULT_CUR_PROTECT)
> +
> +static LIST_HEAD(usb_charger_list);
> +static DEFINE_MUTEX(usb_charger_list_lock);
> +
> +/*
> + * usb_charger_find_by_name - Get the usb charger device by name.
> + * @name - usb charger device name.
> + *
> + * notes: when this function walks the list and returns a charger
> + * it's dropped the lock which means that something else could come
> + * along and delete the charger before we dereference the pointer.
> + * It's very unlikely but it's a possibility so you should take care
> + * of it.
> + * Thus when you get the usb charger by name, you should call
> + * put_usb_charger() to derease the reference count of the usb charger.
> + *
> + * return the instance of usb charger device.
> + */
> +struct usb_charger *usb_charger_find_by_name(char *name)
> +{
> + struct usb_charger *uchger;
> +
> + if (!name)
> + return ERR_PTR(-EINVAL);
> +
> + mutex_lock(&usb_charger_list_lock);
> + list_for_each_entry(uchger, &usb_charger_list, entry) {
> + if (!strcmp(uchger->name, name)) {
> + get_usb_charger(uchger);
> + mutex_unlock(&usb_charger_list_lock);
> + return uchger;
> + }
> + }
> + mutex_unlock(&usb_charger_list_lock);
> +
> + return NULL;
> +}
> +
> +/*
> + * usb_charger_register_notify() - Register a notifiee to get notified by
> + * any attach status changes from the usb charger type detection.
> + * @uchger - the usb charger device which is monitored.
> + * @nb - a notifier block to be registered.
> + */
> +void usb_charger_register_notify(struct usb_charger *uchger,
> + struct notifier_block *nb)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&uchger->lock, flags);
> + raw_notifier_chain_register(&uchger->uchger_nh, nb);
> + spin_unlock_irqrestore(&uchger->lock, flags);
> +}
> +
> +/*
> + * usb_charger_unregister_notify() - Unregister a notifiee from the usb charger.
> + * @uchger - the usb charger device which is monitored.
> + * @nb - a notifier block to be unregistered.
> + */
> +void usb_charger_unregister_notify(struct usb_charger *uchger,
> + struct notifier_block *nb)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&uchger->lock, flags);
> + raw_notifier_chain_unregister(&uchger->uchger_nh, nb);
> + spin_unlock_irqrestore(&uchger->lock, flags);
> +}
> +
> +/*
> + * usb_charger_register_extcon_notifier() - Register a notifiee of the usb
> + * charger to get notified by any attach status changes from
> + * the extcon device.
> + * @uchger - the usb charger device.
> + * @edev - the extcon device.
> + * @extcon_id - extcon id.
> + */
> +int usb_charger_register_extcon_notifier(struct usb_charger *uchger,
> + struct extcon_dev *edev,
> + unsigned int extcon_id)
> +{
> + if (!uchger || !edev)
> + return -EINVAL;
> +
> + return extcon_register_notifier(edev, extcon_id, &uchger->extcon_nb.nb);
> +}
> +
> +/*
> + * usb_charger_unregister_extcon_notifier() - Unregister a notifiee of the
> + * usb charger from the extcon device.
> + * @uchger - the usb charger device.
> + * @edev - the extcon device.
> + * @extcon_id - extcon id.
> + */
> +int usb_charger_unregister_extcon_notifier(struct usb_charger *uchger,
> + struct extcon_dev *edev,
> + unsigned int extcon_id)
> +{
> + if (!uchger || !edev)
> + return -EINVAL;
> +
> + return extcon_unregister_notifier(edev, extcon_id, &uchger->extcon_nb.nb);
> +}
> +
> +/*
> + * usb_charger_register_gadget_notifier() - Register a notifiee of the usb
> + * charger to get notified by any attach status changes from
> + * the usb gadget device.
> + * @uchger - the usb charger device.
> + */
> +int usb_charger_register_gadget_notifier(struct usb_charger *uchger)
> +{
> + struct usb_gadget *ugadget = uchger->gadget;
> +
> + return usb_gadget_register_notify(ugadget, &uchger->gadget_nb);
> +}

usb_gadget_register_notify is defined at your 2nd patch, it will
cause 'git bisect' problem.

> +
> +/*
> + * usb_charger_unregister_gadget_notifier() - Unregister a notifiee of the usb
> + * charger from the usb gadget device.
> + * @uchger - the usb charger device.
> + */
> +int usb_charger_unregister_gadget_notifier(struct usb_charger *uchger)
> +{
> + struct usb_gadget *ugadget = uchger->gadget;
> +
> + return usb_gadget_unregister_notify(ugadget, &uchger->gadget_nb);
> +}
> +
> +/*
> + * usb_charger_set_cur_limit() - Set the current limitation.
> + * @uchger - the usb charger device.
> + * @xxx_cur - the current limit by different charger type.
> + *
> + */
> +int usb_charger_set_cur_limit(struct usb_charger *uchger,
> + struct usb_charger_cur_limit *cur_limit_set)
> +{
> + if (!uchger || !cur_limit_set)
> + return -EINVAL;
> +
> + uchger->cur_limit.sdp_cur_limit = cur_limit_set->sdp_cur_limit;
> + uchger->cur_limit.dcp_cur_limit = cur_limit_set->dcp_cur_limit;
> + uchger->cur_limit.cdp_cur_limit = cur_limit_set->cdp_cur_limit;
> + uchger->cur_limit.aca_cur_limit = cur_limit_set->aca_cur_limit;
> + return 0;
> +}
> +
> +/*
> + * usb_charger_get_cur_limit() - Get the current limitation by different usb
> + * charger type.
> + * @uchger - the usb charger device.
> + * @type - the usb charger type.
> + *
> + * return the current limitation to set.
> + */
> +static unsigned int
> +usb_charger_get_cur_limit(struct usb_charger *uchger)
> +{
> + enum usb_charger_type uchger_type = uchger->type;
> + unsigned int cur_limit;
> +
> + switch (uchger_type) {
> + case SDP_TYPE:
> + cur_limit = uchger->cur_limit.sdp_cur_limit;
> + break;
> + case DCP_TYPE:
> + cur_limit = uchger->cur_limit.dcp_cur_limit;
> + break;
> + case CDP_TYPE:
> + cur_limit = uchger->cur_limit.cdp_cur_limit;
> + break;
> + case ACA_TYPE:
> + cur_limit = uchger->cur_limit.aca_cur_limit;
> + break;
> + default:
> + return 0;
> + }
> +
> + return cur_limit;
> +}
> +
> +/*
> + * usb_charger_detect_type() - Get the usb charger type by the callback which is
> + * implemented by user.
> + * @uchger - the usb charger device.
> + *
> + * return the usb charger type.
> + */
> +enum usb_charger_type
> +usb_charger_detect_type(struct usb_charger *uchger)
> +{
> + if (uchger->gadget && uchger->gadget->ops
> + && uchger->gadget->ops->get_charger_type)
> + uchger->type =
> + uchger->gadget->ops->get_charger_type(uchger->gadget);
> + else
> + uchger->type = UNKNOWN_TYPE;
> +
> + return uchger->type;
> +}
> +
> +/*
> + * usb_charger_notifier_others() - It will notify other device registered on
> + * usb charger.
> + * @uchger - the usb charger device.
> + *
> + */
> +static void
> +usb_charger_notify_others(struct usb_charger *uchger,
> + enum usb_charger_state state)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&uchger->lock, flags);

it is better to use mutex_lock instead of disabling interrupt since
checking charger type may cause sleeping.

> + uchger->state = state;
> +
> + switch (state) {
> + case USB_CHARGER_PRESENT:
> + usb_charger_detect_type(uchger);
> + raw_notifier_call_chain(&uchger->uchger_nh,
> + usb_charger_get_cur_limit(uchger),
> + uchger);
> + break;
> + case USB_CHARGER_REMOVE:
> + uchger->type = UNKNOWN_TYPE;
> + raw_notifier_call_chain(&uchger->uchger_nh, 0, uchger);
> + break;
> + default:
> + dev_warn(&uchger->dev, "Unknown USB charger state: %d\n",
> + state);
> + break;
> + }
> + spin_unlock_irqrestore(&uchger->lock, flags);
> +}
> +
> +/*
> + * usb_charger_plug_by_extcon() - The notifier call function which is registered
> + * on the extcon device.
> + * @nb - thr notifier block that notified by extcon device.
> + * @state - the extcon device state changed.
> + * @data - here specify a extcon device.
> + *
> + * return the notify flag.
> + */
> +static int
> +usb_charger_plug_by_extcon(struct notifier_block *nb,
> + unsigned long state, void *data)
> +{
> + struct usb_charger_nb *extcon_nb =
> + container_of(nb, struct usb_charger_nb, nb);
> + struct usb_charger *uchger = extcon_nb->uchger;
> + enum usb_charger_state uchger_state;
> +
> + if (!uchger)
> + return NOTIFY_BAD;
> +
> + /* Report event to power to setting the current limitation
> + * for this usb charger when one usb charger is added or removed
> + * with detecting by extcon device.
> + */
> + if (state)
> + uchger_state = USB_CHARGER_PRESENT;
> + else
> + uchger_state = USB_CHARGER_REMOVE;
> +
> + usb_charger_notify_others(uchger, uchger_state);
> +
> + return NOTIFY_OK;
> +}
> +
> +/*
> + * usb_charger_plug_by_gadget() - Set the usb charger current limitation
> + * according to the usb gadget device state.
> + * @data - here specify a usb charger device.
> + *
> + */
> +static int
> +usb_charger_plug_by_gadget(struct notifier_block *nb,
> + unsigned long state, void *data)
> +{
> + struct usb_gadget *gadget = (struct usb_gadget *)data;
> + struct usb_charger *uchger = gadget->uchger;
> + enum usb_charger_state uchger_state;
> +
> + if (!uchger)
> + return NOTIFY_BAD;
> +
> + /* Report event to power to setting the current limitation
> + * for this usb charger when one usb charger state is changed
> + * with detecting by usb gadget state.
> + */
> + if (uchger->old_gadget_state != state) {
> + uchger->old_gadget_state = state;
> +
> + if (state >= USB_STATE_ATTACHED)
> + uchger_state = USB_CHARGER_PRESENT;
> + else if (state == USB_STATE_NOTATTACHED)
> + uchger_state = USB_CHARGER_REMOVE;
> + else
> + uchger_state = USB_CHARGER_DEFAULT;
> +
> + usb_charger_notify_others(uchger, uchger_state);
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int devm_uchger_dev_match(struct device *dev, void *res, void *data)
> +{
> + struct usb_charger **r = res;
> +
> + if (WARN_ON(!r || !*r))
> + return 0;
> +
> + return *r == data;
> +}
> +
> +static void usb_charger_release(struct device *dev)
> +{
> + struct usb_charger *uchger = dev_get_drvdata(dev);
> +
> + if (!atomic_dec_and_test(&uchger->count)) {
> + dev_err(dev, "The usb charger is still in use\n");
> + return;
> + }
> +
> + kfree(uchger->name);
> + kfree(uchger);
> +}
> +
> +/*
> + * usb_charger_unregister() - Unregister a usb charger device.
> + * @uchger - the usb charger device.
> + *
> + */
> +int usb_charger_unregister(struct usb_charger *uchger)
> +{
> + if (!uchger)
> + return -EINVAL;
> +
> + mutex_lock(&usb_charger_list_lock);
> + list_del(&uchger->entry);
> + mutex_unlock(&usb_charger_list_lock);
> +
> + device_unregister(&uchger->dev);
> + return 0;
> +}
> +
> +static void devm_uchger_dev_unreg(struct device *dev, void *res)
> +{
> + usb_charger_unregister(*(struct usb_charger **)res);
> +}
> +
> +void devm_usb_charger_unregister(struct device *dev,
> + struct usb_charger *uchger)
> +{
> + devres_release(dev, devm_uchger_dev_unreg,
> + devm_uchger_dev_match, uchger);
> +}
> +
> +/*
> + * usb_charger_register() - Register a new usb charger device.
> + * @uchger - the new usb charger device.
> + *
> + */
> +int usb_charger_register(struct device *dev, struct usb_charger *uchger)
> +{
> + static atomic_t uchger_no = ATOMIC_INIT(-1);
> + struct usb_charger *tmp;
> + int ret;
> +
> + if (!uchger) {
> + dev_err(dev, "no device provided for charger\n");
> + return -EINVAL;
> + }
> +
> + uchger->dev.parent = dev;
> + uchger->dev.release = usb_charger_release;
> + dev_set_name(&uchger->dev, "usb-chger%lu",
> + (unsigned long)atomic_inc_return(&uchger_no));

For the name of usb-charger, which not using "usb-charger.x" directly?
> +
> + ret = device_register(&uchger->dev);
> + if (ret) {
> + put_device(&uchger->dev);
> + return ret;
> + }
> +
> + dev_set_drvdata(&uchger->dev, uchger);
> +
> + mutex_lock(&usb_charger_list_lock);
> + list_for_each_entry(tmp, &usb_charger_list, entry) {
> + if (!(strcmp(tmp->name, uchger->name))) {
> + mutex_unlock(&usb_charger_list_lock);
> + ret = -EEXIST;
> + goto out;
> + }
> + }
> + list_add_tail(&uchger->entry, &usb_charger_list);
> + mutex_unlock(&usb_charger_list_lock);
> +
> + return 0;
> +
> +out:
> + dev_err(dev, "Failed to register usb charger (%s)\n",
> + uchger->name);
> + device_unregister(&uchger->dev);
> + put_device(&uchger->dev);
> + return ret;
> +}
> +
> +int devm_usb_charger_register(struct device *dev,
> + struct usb_charger *uchger)
> +{
> + struct usb_charger **ptr;
> + int ret;
> +
> + ptr = devres_alloc(devm_uchger_dev_unreg, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return -ENOMEM;
> +
> + ret = usb_charger_register(dev, uchger);
> + if (ret) {
> + devres_free(ptr);
> + return ret;
> + }
> +
> + *ptr = uchger;
> + devres_add(dev, ptr);
> +
> + return 0;
> +}
> +
> +int usb_charger_init(struct usb_gadget *ugadget)
> +{
> + struct usb_charger *uchger;
> + struct extcon_dev *edev;
> + char buf[100];
> + char *str;
> + int ret;
> +
> + if (!ugadget)
> + return -EINVAL;
> +
> + uchger = devm_kzalloc(&ugadget->dev, sizeof(struct usb_charger),
> + GFP_KERNEL);
> + if (!uchger)
> + return -ENOMEM;
> +
> + sprintf(buf, "usb-charger.%s", ugadget->name);
> + str = devm_kzalloc(&ugadget->dev, sizeof(char) * (strlen(buf) + 1),
> + GFP_KERNEL);
> + if (!str)
> + return -ENOMEM;
> +
> + strcpy(str, buf);
> + uchger->name = str;
> + uchger->type = UNKNOWN_TYPE;
> + uchger->state = USB_CHARGER_DEFAULT;
> + uchger->cur_limit.sdp_cur_limit = DEFAULT_SDP_CUR_LIMIT;
> + uchger->cur_limit.dcp_cur_limit = DEFAULT_DCP_CUR_LIMIT;
> + uchger->cur_limit.cdp_cur_limit = DEFAULT_CDP_CUR_LIMIT;
> + uchger->cur_limit.aca_cur_limit = DEFAULT_ACA_CUR_LIMIT;
> +
> + atomic_set(&uchger->count, 1);
> + spin_lock_init(&uchger->lock);
> + INIT_LIST_HEAD(&uchger->entry);
> + RAW_INIT_NOTIFIER_HEAD(&uchger->uchger_nh);
> +
> + /* register a notifier on a extcon device if it is exsited */
> + edev = extcon_get_edev_by_phandle(ugadget->dev.parent, 0);
> + if (!IS_ERR_OR_NULL(edev)) {
> + uchger->extcon_dev = edev;
> + uchger->extcon_nb.nb.notifier_call = usb_charger_plug_by_extcon;
> + uchger->extcon_nb.uchger = uchger;
> + usb_charger_register_extcon_notifier(uchger, edev, EXTCON_USB);
> + }
> +
> + /* register a notifier on a usb gadget device */
> + uchger->gadget = ugadget;
> + ugadget->uchger = uchger;
> + uchger->old_gadget_state = ugadget->state;
> + uchger->gadget_nb.notifier_call = usb_charger_plug_by_gadget;
> + usb_charger_register_gadget_notifier(uchger);
> +
> + ret = usb_charger_register(&ugadget->dev, uchger);
> + if (ret)
> + goto reg_fail;
> +
> + return 0;
> +
> +reg_fail:
> + if (uchger->extcon_dev)
> + usb_charger_unregister_extcon_notifier(uchger,
> + uchger->extcon_dev, EXTCON_USB);
> +
> + usb_charger_unregister_gadget_notifier(uchger);
> + return ret;
> +}
> +
> +int usb_charger_exit(struct usb_gadget *ugadget)
> +{
> + struct usb_charger *uchger = ugadget->uchger;
> +
> + if (!uchger)
> + return -EINVAL;
> +
> + if (uchger->extcon_dev)
> + usb_charger_unregister_extcon_notifier(uchger,
> + uchger->extcon_dev, EXTCON_USB);
> +
> + usb_charger_unregister_gadget_notifier(uchger);
> + return usb_charger_unregister(uchger);
> +}
> +
> +MODULE_AUTHOR("Baolin Wang <baolin.wang@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("USB charger driver");
> diff --git a/include/linux/usb/usb_charger.h b/include/linux/usb/usb_charger.h
> new file mode 100644
> index 0000000..da4d3c9
> --- /dev/null
> +++ b/include/linux/usb/usb_charger.h
> @@ -0,0 +1,101 @@
> +#ifndef __LINUX_USB_CHARGER_H__
> +#define __LINUX_USB_CHARGER_H__
> +
> +#include <linux/device.h>
> +#include <linux/notifier.h>
> +#include <linux/sysfs.h>
> +#include <linux/usb/ch9.h>
> +
> +/* USB charger type:
> + * SDP (Standard Downstream Port)
> + * DCP (Dedicated Charging Port)
> + * CDP (Charging Downstream Port)
> + * ACA (Accessory Charger Adapters)
> + */
> +enum usb_charger_type {
> + UNKNOWN_TYPE,
> + SDP_TYPE,
> + DCP_TYPE,
> + CDP_TYPE,
> + ACA_TYPE,
> +};
> +
> +/* USB charger state */
> +enum usb_charger_state {
> + USB_CHARGER_DEFAULT,
> + USB_CHARGER_PRESENT,
> + USB_CHARGER_REMOVE,
> +};
> +
> +/* Current limitation by charger type */
> +struct usb_charger_cur_limit {
> + unsigned int sdp_cur_limit;
> + unsigned int dcp_cur_limit;
> + unsigned int cdp_cur_limit;
> + unsigned int aca_cur_limit;
> +};
> +
> +struct usb_charger_nb {
> + struct notifier_block nb;
> + struct usb_charger *uchger;
> +};
> +
> +struct usb_charger {
> + /* Internal data. Please do not set. */
> + const char *name;
> + struct device dev;
> + struct raw_notifier_head uchger_nh;
> + struct list_head entry;
> + spinlock_t lock;
> + enum usb_charger_type type;
> + enum usb_charger_state state;
> + atomic_t count;
> +
> + /* For supporting extcon usb gpio */
> + struct extcon_dev *extcon_dev;
> + struct usb_charger_nb extcon_nb;
> +
> + /* For supporting usb gadget */
> + struct usb_gadget *gadget;
> + enum usb_device_state old_gadget_state;
> + struct notifier_block gadget_nb;
> +
> + /* Current limitation */
> + struct usb_charger_cur_limit cur_limit;
> +};
> +
> +extern struct usb_charger *usb_charger_find_by_name(char *name);
> +
> +extern void usb_charger_register_notify(struct usb_charger *uchger,
> + struct notifier_block *nb);
> +extern void usb_charger_unregister_notify(struct usb_charger *uchger,
> + struct notifier_block *nb);
> +
> +extern int usb_charger_register_extcon_notifier(struct usb_charger *uchger,
> + struct extcon_dev *edev,
> + unsigned int extcon_id);
> +extern int usb_charger_unregister_extcon_notifier(struct usb_charger *uchger,
> + struct extcon_dev *edev,
> + unsigned int extcon_id);
> +
> +extern int usb_charger_register_gadget_notifier(struct usb_charger *uchger);
> +extern int usb_charger_unregister_gadget_notifier(struct usb_charger *uchger);
> +
> +extern int usb_charger_set_cur_limit(struct usb_charger *uchger,
> + struct usb_charger_cur_limit *cur_limit_set);
> +extern enum usb_charger_type usb_charger_detect_type(struct usb_charger *uchger);
> +
> +extern int usb_charger_init(struct usb_gadget *ugadget);
> +extern int usb_charger_exit(struct usb_gadget *ugadget);
> +
> +static inline void get_usb_charger(struct usb_charger *uchger)
> +{
> + atomic_inc(&uchger->count);
> +}
> +
> +static inline void put_usb_charger(struct usb_charger *uchger)
> +{
> + atomic_dec(&uchger->count);
> +}
> +
> +#endif /* __LINUX_USB_CHARGER_H__ */
> --
> 1.7.9.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at http://vger.kernel.org/majordomo-info.html

--

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