devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Kishon Vijay Abraham I <kishon@ti.com>
To: Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
Cc: balbi@ti.com, gregkh@linuxfoundation.org, arnd@arndb.de,
	akpm@linux-foundation.org, swarren@wwwdotorg.org,
	rob@landley.net, netdev@vger.kernel.org, davem@davemloft.net,
	cesarb@cesarb.net, linux-usb@vger.kernel.org,
	linux-omap@vger.kernel.org, linux-kernel@vger.kernel.org,
	tony@atomide.com, grant.likely@secretlab.ca,
	rob.herring@calxeda.com, b-cousson@ti.com,
	linux@arm.linux.org.uk, eballetbo@gmail.com, javier@dowhile0.org,
	mchehab@redhat.com, santosh.shilimkar@ti.com,
	broonie@opensource.wolfsonmicro.com, swarren@nvidia.com,
	linux-doc@vger.kernel.org, devicetree-discuss@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org
Subject: Re: [PATCH v5 1/6] drivers: phy: add generic PHY framework
Date: Thu, 4 Apr 2013 14:51:51 +0530	[thread overview]
Message-ID: <515D462F.9050109@ti.com> (raw)
In-Reply-To: <515CA337.1010504@gmail.com>

Hi,

On Thursday 04 April 2013 03:16 AM, Sylwester Nawrocki wrote:
> On 04/03/2013 02:53 PM, Kishon Vijay Abraham I wrote:
.
.
<snip>
.
.
>> diff --git a/Documentation/phy.txt b/Documentation/phy.txt
>> new file mode 100644
>> index 0000000..7785ec0
>> --- /dev/null
>> +++ b/Documentation/phy.txt
>> @@ -0,0 +1,113 @@
>> +                PHY SUBSYSTEM
>> +          Kishon Vijay Abraham I<kishon@ti.com>
>> +
>> +This document explains the Generic PHY Framework along with the APIs
>> provided,
>> +and how-to-use.
>> +
>> +1. Introduction
>> +
>> +*PHY* is the abbreviation for physical layer. It is used to connect a
>> device
>> +to the physical medium e.g., the USB controller has a PHY to provide
>> functions
>
> Shouldn't it be "...medium, e.g. the USB..." ?
>
>> +such as serialization, de-serialization, encoding, decoding and is
>> responsible
>> +for obtaining the required data transmission rate. Note that some USB
>> +controller has PHY functionality embedded into it and others use an
>> external
>
> "controllers have PHY functionality embedded into them" ?
>
>> +PHY. Other peripherals that uses a PHY include Wireless LAN, Ethernet,
>
> s/uses/use ?
>
>> +SATA etc.
>> +
>> +The intention of creating this framework is to bring the phy drivers
>> spread
>> +all over the Linux kernel to drivers/phy to increase code re-use and to
>> +increase code maintainability.
>> +
>> +This framework will be of use only to devices that uses external PHY
>> (PHY
>
> s/uses/use ?
>
>> +functionality is not embedded within the controller).
>> +
>> +2. Creating the PHY
>> +
>> +The PHY driver should create the PHY in order for other peripheral
>> controllers
>> +to make use of it. The PHY framework provides 2 APIs to create the PHY.
>> +
>> +struct phy *phy_create(struct device *dev, const char *label,
>> +    struct device_node *of_node, int type, struct phy_ops *ops,
>> +    void *priv);
>> +struct phy *devm_phy_create(struct device *dev, const char *label,
>> +    struct device_node *of_node, int type, struct phy_ops *ops,
>> +    void *priv);
>> +
>> +The PHY drivers can use one of the above 2 APIs to create the PHY by
>> passing
>> +the device pointer, label, device node, type, phy ops and a driver data.
>> +phy_ops is a set of function pointers for performing PHY operations
>> such as
>> +init, exit, suspend, resume, power_on and power_off.
>> +
>> +3. Binding the PHY to the controller
>> +
>> +The framework provides an API for binding the controller to the PHY
>> in the
>> +case of non dt boot.
>> +
>> +struct phy_bind *phy_bind(const char *dev_name, int index,
>> +                const char *phy_dev_name);
>> +
>> +The API fills the phy_bind structure with the dev_name (device name
>> of the
>> +controller), index and phy_dev_name (device name of the PHY). This will
>> +be used when the controller requests this phy. This API should be
>> used by
>> +platform specific initialization code (board file).
>> +
>> +In the case of dt boot, the binding information should be added in
>> the dt
>> +data of the controller.
>
> s/in the dt data of/in the device tree node of ?
>
>> +4. Getting a reference to the PHY
>> +
>> +Before the controller can make use of the PHY, it has to get a
>> reference to
>> +it. This framework provides 6 APIs to get a reference to the PHY.
>> +
>> +struct phy *phy_get(struct device *dev, int index);
>> +struct phy *devm_phy_get(struct device *dev, int index);
>> +struct phy *of_phy_get(struct device *dev, const char *phandle, int
>> index);
>> +struct phy *devm_of_phy_get(struct device *dev, const char *phandle,
>> int index);
>
> Why do we need separate functions for dt and non-dt ? Couldn't it be
> handled
> internally by the framework ? So the PHY users can use same calls for dt
> and non-dt, like in case of e.g. the regulators API ?

yeah. Indeed it makes sense to do it that way.
>
> Also signatures of some functions are different now:
>
> extern struct phy *phy_get(struct device *dev, int index);
> extern struct phy *devm_phy_get(struct device *dev, int index);
> extern struct phy *of_phy_get(struct device *dev, int index);
> extern struct phy *devm_of_phy_get(struct device *dev, int index);

My bad :-(

>
> BTW, I think "extern" could be dropped, does it have any significance in
> function declarations in header files ?

it shouldn't have any effect I guess. It's harmless nevertheless.

>
>> +struct phy *of_phy_get_byname(struct device *dev, const char *string);
>> +struct phy *devm_of_phy_get_byname(struct device *dev, const char
>> *string);
>> +
>> +phy_get and devm_phy_get can be used to get the PHY in non-dt boot.
>> This API
>> +uses the binding information added using the phy_bind API to find and
>> return
>> +the appropriate PHY. The only difference between the two APIs is that
>> +devm_phy_get associates the device with the PHY using devres on
>> successful PHY
>> +get. On driver detach, release function is invoked on the the devres
>> data and
>
> s/the the/the
>
>> +devres data is freed.
>> +
>> +of_phy_get and devm_of_phy_get can be used to get the PHY in dt boot.
>> These
>> +APIs take the phandle and index to get a reference to the PHY. The only
>> +difference between the two APIs is that devm_of_phy_get associates
>> the device
>> +with the PHY using devres on successful phy get. On driver detach,
>> release
>> +function is invoked on the devres data and it is freed.
>> +
>> +of_phy_get_byname and devm_of_phy_get_byname can also be used to get
>> the PHY
>> +in dt boot. It is same as the above API except that the user has to
>> pass the
>> +phy name as filled in "phy-names" phandle in dt data and the
>> framework will
>
> s/phandle/property ?
>
>> +find the index and get the PHY.
>> +
>> +5. Releasing a reference to the PHY
>> +
>> +When the controller no longer needs the PHY, it has to release the
>> reference
>> +to the PHY it has obtained using the APIs mentioned in the above
>> section. The
>> +PHY framework provides 2 APIS to release a reference to the PHY.
>
> s/APIS/APIs ?
>
>> +
>> +void phy_put(struct phy *phy);
>> +void devm_phy_put(struct device *dev, struct phy *phy);
>> +
>> +Both these APIs are used to release a reference to the PHY and
>> devm_phy_put
>> +destroys the devres associated with this PHY.
>> +
>> +6. Destroying the PHY
>> +
>> +When the driver that created the PHY is unloaded, it should destroy
>> the PHY it
>> +created using one of the following 2 APIs.
>> +
>> +void phy_destroy(struct phy *phy);
>> +void devm_phy_destroy(struct device *dev, struct phy *phy);
>> +
>> +Both these APIs destroys the PHY and devm_phy_destroy destroys the
>> devres
>
> s/APIs destroys/APIs destroy ?
>
>> +associated with this PHY.
>> +
>> +7. DeviceTree Binding
>> +
>> +The documentation for PHY dt binding can be found @
>> +Documentation/devicetree/bindings/phy/phy-bindings.txt
>
>> --- /dev/null
>> +++ b/drivers/phy/phy-core.c
>> @@ -0,0 +1,616 @@
>
>> +static struct phy *of_phy_lookup(struct device_node *node)
>> +{
>> +    struct phy *phy;
>> +    struct device *dev;
>> +    struct class_dev_iter iter;
>> +
>> +    class_dev_iter_init(&iter, phy_class, NULL, NULL);
>
> There is currently nothing preventing a call to this function before
> phy_class is initialized. It happened during my tests, and the nice
> stack dump showed that it was the PHY user attempting to get the PHY
> before the framework got initialized. So either phy_core_init should
> be a subsys_initcall or we need a better protection against phy_class
> being NULL or ERR_PTR in more places.

Whats the initcall used in your PHY user? Looks like more people prefer 
having module_init and any issue because of it should be fixed in PHY 
providers and PHY users.
>
>> +    while ((dev = class_dev_iter_next(&iter))) {
>> +        phy = container_of(dev, struct phy, dev);
>> +        if (node != phy->of_node)
>> +            continue;
>> +
>> +        class_dev_iter_exit(&iter);
>> +        return phy;
>> +    }
>> +
>> +    class_dev_iter_exit(&iter);
>> +    return ERR_PTR(-EPROBE_DEFER);
>> +}
>
>> +/**
>> + * of_phy_get() - lookup and obtain a reference to a phy by phandle
>> + * @dev: device that requests this phy
>> + * @index: the index of the phy
>> + *
>> + * Returns the phy associated with the given phandle value,
>> + * after getting a refcount to it or -ENODEV if there is no such phy or
>> + * -EPROBE_DEFER if there is a phandle to the phy, but the device is
>> + * not yet loaded.
>> + */
>> +struct phy *of_phy_get(struct device *dev, int index)
>> +{
>> +    int ret;
>> +    struct phy *phy = NULL;
>> +    struct phy_bind *phy_map = NULL;
>> +    struct of_phandle_args args;
>> +    struct device_node *node;
>> +
>> +    if (!dev->of_node) {
>> +        dev_dbg(dev, "device does not have a device node entry\n");
>> +        return ERR_PTR(-EINVAL);
>> +    }
>> +
>> +    ret = of_parse_phandle_with_args(dev->of_node, "phys", "#phy-cells",
>> +        index,&args);
>> +    if (ret) {
>> +        dev_dbg(dev, "failed to get phy in %s node\n",
>> +            dev->of_node->full_name);
>> +        return ERR_PTR(-ENODEV);
>> +    }
>> +
>> +    phy = of_phy_lookup(args.np);
>> +    if (IS_ERR(phy) || !try_module_get(phy->ops->owner)) {
>> +        phy = ERR_PTR(-EPROBE_DEFER);
>> +        goto err0;
>> +    }
>> +
>> +    phy = phy->ops->of_xlate(phy,&args);
>> +    if (IS_ERR(phy))
>> +        goto err1;
>> +
>> +    phy_map = phy_bind(dev_name(dev), index, dev_name(&phy->dev));
>> +    if (!IS_ERR(phy_map)) {
>> +        phy_map->phy = phy;
>> +        phy_map->auto_bind = true;
>
> Shouldn't it be done with the phy_bind_mutex held ? I guess an unlocked
> version of the phy_bind functions is needed, so it can be used internally.

The locking is done inside phy_bind function. I'm not sure but I vaguely 
remember getting a dump stack (incorrect mutex ordering or something) 
when trying to have phy_bind_mutex here. I can check it again.
>
>> +    }
>> +
>> +    get_device(&phy->dev);
>> +
>> +err1:
>> +    module_put(phy->ops->owner);
>> +
>> +err0:
>> +    of_node_put(node);
>> +
>> +    return phy;
>> +}
>> +EXPORT_SYMBOL_GPL(of_phy_get);
>
>> +/**
>> + * phy_create() - create a new phy
>> + * @dev: device that is creating the new phy
>> + * @label: label given to phy
>> + * @of_node: device node of the phy
>> + * @type: specifies the phy type
>> + * @ops: function pointers for performing phy operations
>> + * @priv: private data from phy driver
>> + *
>> + * Called to create a phy using phy framework.
>> + */
>> +struct phy *phy_create(struct device *dev, const char *label,
>> +    struct device_node *of_node, int type, struct phy_ops *ops,
>> +    void *priv)
>> +{
>> +    int ret;
>> +    struct phy *phy;
>> +    struct phy_bind *phy_bind;
>> +    const char *devname = NULL;
>> +
>> +    if (!dev) {
>> +        dev_err(dev, "no device provided for PHY\n");
>> +        ret = -EINVAL;
>> +        goto err0;
>> +    }
>> +
>> +    if (!ops || !ops->of_xlate || !priv) {
>> +        dev_err(dev, "no PHY ops/PHY data provided\n");
>> +        ret = -EINVAL;
>> +        goto err0;
>> +    }
>> +
>> +    if (!phy_class)
>> +        phy_core_init();
>> +
>> +    phy = kzalloc(sizeof(*phy), GFP_KERNEL);
>> +    if (!phy) {
>> +        ret = -ENOMEM;
>> +        goto err0;
>> +    }
>> +
>> +    devname = dev_name(dev);
>
> OK, a sort of serious issue here is that you can't call phy_create()
> multiple times for same PHY provider device.

Ah.. right.
>
> device_add() further will fail as sysfs won't let you create multiple
> objects with same name. So I would assume we need to add a new argument
> to phy_create() or rename @type to e.g. @phy_id, which could be
> concatenated with dev_name(dev) to create a unique name of the phy
> device.

hmm.. ok
>
> And how is the PHY provider supposed to identify a PHY in its common PHY
> ops, now when the struct phy port field is removed ? I have temporarily
> (ab)used the type field in order to select proper registers within the
> phy ops.

Can't it be handled in the PHY provider driver without using struct phy 
*? Moreover the PHY ops passes on the *phy to phy provider right? Not 
sure I understand you here.
>
>> +    device_initialize(&phy->dev);
>> +
>> +    phy->dev.class = phy_class;
>> +    phy->dev.parent = dev;
>> +    phy->label = label;
>
> What about duplicating the string here ? That would make the API a bit
> more convenient and the callers wouldn't be required to keep all the
> labels around.

you mean use dev_name? The device names are sometime more cryptic so 
preferred to have it like this.
>
>> +    phy->of_node = of_node;
>> +    phy->type = type;
>> +    phy->ops = ops;
>> +
>> +    dev_set_drvdata(&phy->dev, priv);
>> +
>> +    ret = dev_set_name(&phy->dev, "%s", devname);
>> +    if (ret)
>> +        goto err1;
>> +
>> +    mutex_lock(&phy_bind_mutex);
>> +    list_for_each_entry(phy_bind,&phy_bind_list, list)
>> +        if (!(strcmp(phy_bind->phy_dev_name, devname)))
>> +            phy_bind->phy = phy;
>> +    mutex_unlock(&phy_bind_mutex);
>> +
>> +    ret = device_add(&phy->dev);
>> +    if (ret)
>> +        goto err2;
>> +
>> +    return phy;
>> +
>> +err2:
>> +    phy_bind->phy = NULL;
>> +
>> +err1:
>> +    put_device(&phy->dev);
>> +    kfree(phy);
>> +
>> +err0:
>> +    return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL_GPL(phy_create);
>
>> +/**
>> + * devm_phy_create() - create a new phy
>> + * @dev: device that is creating the new phy
>> + * @dev: device that is creating the new phy
>
> Duplicated line.
>
>> + * @label: label given to phy
>> + * @of_node: device node of the phy
>> + * @type: specifies the phy type
>> + * @ops: function pointers for performing phy operations
>> + * @priv: private data from phy driver
>> + *
>> + * Creates a new PHY device adding it to the PHY class.
>> + * While at that, it also associates the device with the phy using
>> devres.
>> + * On driver detach, release function is invoked on the devres data,
>> + * then, devres data is freed.
>> + */
>
>> +/**
>> + * phy_bind() - bind the phy and the controller that uses the phy
>> + * @dev_name: the device name of the device that will bind to the phy
>> + * @index: index to specify the port number
>> + * @phy_dev_name: the device name of the phy
>> + *
>> + * Fills the phy_bind structure with the dev_name and phy_dev_name.
>> This will
>> + * be used when the phy driver registers the phy and when the controller
>> + * requests this phy.
>> + *
>> + * To be used by platform specific initialization code.
>> + */
>> +struct phy_bind *phy_bind(const char *dev_name, int index,
>> +                const char *phy_dev_name)
>> +{
>> +    struct phy_bind *phy_bind;
>> +
>> +    mutex_lock(&phy_bind_mutex);
>> +    list_for_each_entry(phy_bind,&phy_bind_list, list) {
>> +        if (!strcmp(phy_bind->dev_name, dev_name)&&  phy_bind->index ==
>> +            index) {
>> +            phy_bind->phy_dev_name = phy_dev_name;
>> +            goto ret0;
>> +        }
>> +    }
>> +
>> +    phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL);
>> +    if (!phy_bind) {
>> +        phy_bind = ERR_PTR(-ENOMEM);
>> +        goto ret0;
>> +    }
>> +
>> +    phy_bind->dev_name = dev_name;
>> +    phy_bind->phy_dev_name = phy_dev_name;
>> +    phy_bind->index = index;
>> +    phy_bind->auto_bind = false;
>> +
>> +    list_add_tail(&phy_bind->list,&phy_bind_list);
>> +
>> +ret0:
>> +    mutex_unlock(&phy_bind_mutex);
>> +    return phy_bind;
>> +}
>> +EXPORT_SYMBOL_GPL(phy_bind);
>
>> +static ssize_t phy_bind_show(struct device *dev,
>> +                 struct device_attribute *attr, char *buf)
>> +{
>> +    struct phy_bind *phy_bind;
>> +    struct phy *phy;
>> +    char *p = buf;
>> +    int len;
>> +
>> +    phy = container_of(dev, struct phy, dev);
>> +
>
> Shouldn't this iteration also be protected with the phy_bind_mutex ?

Indeed.
>
>> +    list_for_each_entry(phy_bind,&phy_bind_list, list)
>> +        if (phy_bind->phy == phy)
>> +            p += sprintf(p, "%s %d %s\n", phy_bind->dev_name,
>> +                phy_bind->index, phy_bind->phy_dev_name);
>> +    len = (p - buf);
>> +
>> +    return len;
>> +}
>
>> +static int phy_core_init(void)
>> +{
>> +    if (phy_class)
>> +        return 0;
>> +
>> +    phy_class = class_create(THIS_MODULE, "phy");
>> +    if (IS_ERR(phy_class)) {
>> +        pr_err("failed to create phy class -->  %ld\n",
>> +            PTR_ERR(phy_class));
>> +        return PTR_ERR(phy_class);
>> +    }
>> +
>> +    phy_class->dev_release = phy_release;
>> +    phy_class->dev_attrs = phy_dev_attrs;
>> +
>> +    return 0;
>> +}
>> +module_init(phy_core_init);
>
> Having this framework initialized before the PHY provider and consumer
> drivers could save some unnecessary probe deferrals. I was wondering,
> what is really an advantage of having it as module_init(), rather than
> subsys_initcall() ?

I'm not sure of the exact reason but after the advent of EPROBE_DEFER, 
everyone recommends to use module_init only.

Thanks for the detailed review.

Regards
Kishon

  reply	other threads:[~2013-04-04  9:21 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-04-03 12:53 [PATCH v5 0/6] Generic PHY Framework Kishon Vijay Abraham I
2013-04-03 12:53 ` [PATCH v5 4/6] ARM: OMAP: USB: Add phy binding information Kishon Vijay Abraham I
2013-04-03 12:53 ` [PATCH v5 5/6] ARM: dts: omap: update usb_otg_hs data Kishon Vijay Abraham I
     [not found] ` <1364993634-6378-1-git-send-email-kishon-l0cyMroinI0@public.gmane.org>
2013-04-03 12:53   ` [PATCH v5 1/6] drivers: phy: add generic PHY framework Kishon Vijay Abraham I
2013-04-03 21:46     ` Sylwester Nawrocki
2013-04-04  9:21       ` Kishon Vijay Abraham I [this message]
     [not found]         ` <515D462F.9050109-l0cyMroinI0@public.gmane.org>
2013-04-04 10:41           ` Sylwester Nawrocki
2013-04-04 11:11             ` Kishon Vijay Abraham I
     [not found]     ` <1364993634-6378-2-git-send-email-kishon-l0cyMroinI0@public.gmane.org>
2013-04-03 13:42       ` Felipe Balbi
     [not found]         ` <20130403134102.GC14680-S8G//mZuvNWo5Im9Ml3/Zg@public.gmane.org>
2013-04-03 14:18           ` Kishon Vijay Abraham I
     [not found]             ` <515C3A42.4020404-l0cyMroinI0@public.gmane.org>
2013-04-03 14:27               ` Felipe Balbi
2013-04-03 14:32                 ` Kishon Vijay Abraham I
     [not found]                   ` <515C3D94.1060000-l0cyMroinI0@public.gmane.org>
2013-04-03 15:47                     ` Felipe Balbi
     [not found]                       ` <20130403154704.GD19093-S8G//mZuvNWo5Im9Ml3/Zg@public.gmane.org>
2013-04-04  8:56                         ` Kishon Vijay Abraham I
2013-04-03 23:54       ` Stephen Warren
     [not found]         ` <515CC123.4060402-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>
2013-04-04  7:15           ` Felipe Balbi
2013-04-08 13:21         ` Kishon Vijay Abraham I
2013-04-03 12:53   ` [PATCH v5 2/6] usb: phy: omap-usb2: use the new " Kishon Vijay Abraham I
     [not found]     ` <1364993634-6378-3-git-send-email-kishon-l0cyMroinI0@public.gmane.org>
2013-04-03 13:48       ` Felipe Balbi
2013-04-03 14:55         ` Arnd Bergmann
2013-04-03 15:48           ` Felipe Balbi
2013-04-03 12:53   ` [PATCH v5 3/6] usb: otg: twl4030: " Kishon Vijay Abraham I
2013-04-03 12:53   ` [PATCH v5 6/6] usb: musb: omap2430: " Kishon Vijay Abraham I
2013-04-03 23:42   ` [PATCH v5 0/6] Generic PHY Framework Stephen Warren

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=515D462F.9050109@ti.com \
    --to=kishon@ti.com \
    --cc=akpm@linux-foundation.org \
    --cc=arnd@arndb.de \
    --cc=b-cousson@ti.com \
    --cc=balbi@ti.com \
    --cc=broonie@opensource.wolfsonmicro.com \
    --cc=cesarb@cesarb.net \
    --cc=davem@davemloft.net \
    --cc=devicetree-discuss@lists.ozlabs.org \
    --cc=eballetbo@gmail.com \
    --cc=grant.likely@secretlab.ca \
    --cc=gregkh@linuxfoundation.org \
    --cc=javier@dowhile0.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-omap@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=linux@arm.linux.org.uk \
    --cc=mchehab@redhat.com \
    --cc=netdev@vger.kernel.org \
    --cc=rob.herring@calxeda.com \
    --cc=rob@landley.net \
    --cc=santosh.shilimkar@ti.com \
    --cc=swarren@nvidia.com \
    --cc=swarren@wwwdotorg.org \
    --cc=sylvester.nawrocki@gmail.com \
    --cc=tony@atomide.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).