2014-10-08 09:26:42

by Johan Hovold

[permalink] [raw]
Subject: Re: [PATCH v6 1/4] mfd: add support for Diolan DLN-2 devices

On Thu, Sep 25, 2014 at 07:07:31PM +0300, Octavian Purdila wrote:

> +static void dln2_rx_transfer(struct dln2_dev *dln2, struct urb *urb,
> + u16 handle, u16 rx_slot)
> +{
> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
> + struct dln2_rx_context *rxc;
> + struct device *dev = &dln2->interface->dev;
> +
> + spin_lock(&rxs->lock);

You must use spin_lock_irqsave here as you call it from the completion
handler.

> +
> + rxc = &rxs->slots[rx_slot];
> +
> + if (rxc->in_use && !rxc->urb) {
> + rxc->urb = urb;
> + complete(&rxc->done);
> + /* URB will be resubmitted in free_rx_slot */
> + } else {
> + dev_warn(dev, "bad/late response %d/%d\n", handle, rx_slot);
> + dln2_submit_urb(dln2, urb, GFP_ATOMIC);
> + }
> +
> + spin_unlock(&rxs->lock);
> +}

> +static int _dln2_transfer(struct dln2_dev *dln2, u16 handle, u16 cmd,
> + const void *obuf, unsigned obuf_len,
> + void *ibuf, unsigned *ibuf_len)
> +{
> + int ret = 0;
> + u16 result;
> + int rx_slot;
> + struct dln2_response *rsp;
> + struct dln2_rx_context *rxc;
> + struct device *dev;
> + const int timeout = DLN2_USB_TIMEOUT * HZ / 1000;
> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
> +
> + spin_lock(&dln2->disconnect_lock);

How did you test this version? You never initialise disconnect_lock so
lockdep complains loudly when calling _dln2_transfer during probe.

> + if (!dln2->disconnect)
> + dln2->active_transfers++;
> + else
> + ret = -ENODEV;
> + spin_unlock(&dln2->disconnect_lock);
> +
> + if (ret)
> + return ret;
> +
> + rx_slot = alloc_rx_slot(dln2, handle);
> + if (rx_slot < 0) {
> + ret = rx_slot;
> + goto out_decr;
> + }
> +
> + dev = &dln2->interface->dev;
> +
> + ret = dln2_send_wait(dln2, handle, cmd, rx_slot, obuf, obuf_len);
> + if (ret < 0) {
> + dev_err(dev, "USB write failed: %d\n", ret);
> + goto out_free_rx_slot;
> + }
> +
> + rxc = &rxs->slots[rx_slot];
> +
> + ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout);
> + if (ret <= 0) {
> + if (!ret)
> + ret = -ETIMEDOUT;
> + goto out_free_rx_slot;
> + }
> +
> + if (!rxc->urb) {
> + ret = -ENODEV;
> + goto out_free_rx_slot;
> + }
> +
> + /* if we got here we know that the response header has been checked */
> + rsp = rxc->urb->transfer_buffer;
> +
> + if (rsp->hdr.size < sizeof(*rsp)) {
> + ret = -EPROTO;
> + goto out_free_rx_slot;
> + }
> +
> + result = le16_to_cpu(rsp->result);
> + if (result) {
> + dev_dbg(dev, "%d received response with error %d\n",
> + handle, result);
> + ret = -EREMOTEIO;
> + goto out_free_rx_slot;
> + }
> +
> + if (!ibuf) {
> + ret = 0;
> + goto out_free_rx_slot;
> + }
> +
> + if (*ibuf_len > rsp->hdr.size - sizeof(*rsp))
> + *ibuf_len = rsp->hdr.size - sizeof(*rsp);
> +
> + memcpy(ibuf, rsp + 1, *ibuf_len);
> +
> +out_free_rx_slot:
> + free_rx_slot(dln2, handle, rx_slot);
> +out_decr:
> + spin_lock(&dln2->disconnect_lock);
> + dln2->active_transfers--;
> + spin_unlock(&dln2->disconnect_lock);
> + if (dln2->disconnect)
> + wake_up(&dln2->disconnect_wq);
> +
> + return ret;
> +}

> +static void dln2_disconnect(struct usb_interface *interface)
> +{
> + struct dln2_dev *dln2 = usb_get_intfdata(interface);
> + int i, j;
> +
> + /* don't allow starting new transfers */
> + spin_lock(&dln2->disconnect_lock);
> + dln2->disconnect = true;
> + spin_unlock(&dln2->disconnect_lock);
> +
> + /* cancel in progress transfers */
> + for (i = 0; i < DLN2_HANDLES; i++) {
> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[i];
> + unsigned long flags;
> +
> + spin_lock_irqsave(&rxs->lock, flags);

Just stick to spin_lock in this function.

> +
> + /* cancel all response waiters */
> + for (j = 0; j < DLN2_MAX_RX_SLOTS; j++) {
> + struct dln2_rx_context *rxc = &rxs->slots[j];
> +
> + if (rxc->in_use)
> + complete(&rxc->done);
> + }
> +
> + spin_unlock_irqrestore(&rxs->lock, flags);
> + }
> +
> + /* wait for transfers to end */
> + wait_event(dln2->disconnect_wq, !dln2->active_transfers);
> +
> + mfd_remove_devices(&interface->dev);
> +
> + dln2_free(dln2);
> +}
> +
> +static int dln2_probe(struct usb_interface *interface,
> + const struct usb_device_id *usb_id)
> +{
> + struct usb_host_interface *hostif = interface->cur_altsetting;
> + struct device *dev = &interface->dev;
> + struct dln2_dev *dln2;
> + int ret;
> + int i, j;
> + int id;
> +
> + if (hostif->desc.bInterfaceNumber != 0 ||
> + hostif->desc.bNumEndpoints < 2)
> + return -ENODEV;
> +
> + dln2 = kzalloc(sizeof(*dln2), GFP_KERNEL);
> + if (!dln2)
> + return -ENOMEM;
> +
> + dln2->ep_out = hostif->endpoint[0].desc.bEndpointAddress;
> + dln2->ep_in = hostif->endpoint[1].desc.bEndpointAddress;
> + dln2->usb_dev = usb_get_dev(interface_to_usbdev(interface));
> + dln2->interface = interface;
> + usb_set_intfdata(interface, dln2);
> + init_waitqueue_head(&dln2->disconnect_wq);
> +
> + for (i = 0; i < DLN2_HANDLES; i++) {
> + init_waitqueue_head(&dln2->mod_rx_slots[i].wq);
> + spin_lock_init(&dln2->mod_rx_slots[i].lock);
> + for (j = 0; j < DLN2_MAX_RX_SLOTS; j++)
> + init_completion(&dln2->mod_rx_slots[i].slots[j].done);
> + }
> +
> + spin_lock_init(&dln2->event_cb_lock);
> + INIT_LIST_HEAD(&dln2->event_cb_list);

The disconnect_lock is never initialised, as mentioned above.

> +
> + ret = dln2_setup_rx_urbs(dln2, hostif);
> + if (ret) {
> + dln2_free(dln2);
> + return ret;
> + }
> +
> + ret = dln2_hw_init(dln2);
> + if (ret < 0) {
> + dev_err(dev, "failed to initialize hardware\n");
> + goto out_cleanup;
> + }
> +
> + id = dln2->usb_dev->bus->busnum << 8 | dln2->usb_dev->devnum;
> + ret = mfd_add_devices(dev, id, dln2_devs, ARRAY_SIZE(dln2_devs),
> + NULL, 0, NULL);
> + if (ret != 0) {
> + dev_err(dev, "failed to add mfd devices to core\n");
> + goto out_cleanup;
> + }
> +
> + return 0;
> +
> +out_cleanup:
> + dln2_free(dln2);
> +
> + return ret;
> +}
> +
> +static const struct usb_device_id dln2_table[] = {
> + { USB_DEVICE(0xa257, 0x2013) },
> + { }
> +};
> +
> +MODULE_DEVICE_TABLE(usb, dln2_table);
> +
> +static struct usb_driver dln2_driver = {
> + .name = "usb-dln2",

Just call the usb driver "dln2".

> + .probe = dln2_probe,
> + .disconnect = dln2_disconnect,
> + .id_table = dln2_table,
> +};

Johan


2014-10-08 10:54:12

by Octavian Purdila

[permalink] [raw]
Subject: Re: [PATCH v6 1/4] mfd: add support for Diolan DLN-2 devices

On Wed, Oct 8, 2014 at 12:23 PM, Johan Hovold <[email protected]> wrote:
> On Thu, Sep 25, 2014 at 07:07:31PM +0300, Octavian Purdila wrote:
>
>> +static void dln2_rx_transfer(struct dln2_dev *dln2, struct urb *urb,
>> + u16 handle, u16 rx_slot)
>> +{
>> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
>> + struct dln2_rx_context *rxc;
>> + struct device *dev = &dln2->interface->dev;
>> +
>> + spin_lock(&rxs->lock);
>
> You must use spin_lock_irqsave here as you call it from the completion
> handler.

Why? AFAICS the completion handler gets called from the HCD irq handler:

[ 2.503945] Call Trace:
[ 2.503945] <IRQ> [<ffffffff81a73d14>] dump_stack+0x4e/0x7a
[ 2.503945] [<ffffffff81639fcb>] dln2_rx+0x1eb/0x2d0
[ 2.503945] [<ffffffff81742202>] __usb_hcd_giveback_urb+0x72/0x120
[ 2.503945] [<ffffffff817423cf>] usb_hcd_giveback_urb+0x3f/0x120
[ 2.503945] [<ffffffff81786ffa>] uhci_giveback_urb+0xaa/0x290
[ 2.503945] [<ffffffff811d36d7>] ? dma_pool_free+0xa7/0xd0
[ 2.503945] [<ffffffff81788fe3>] uhci_scan_schedule+0x493/0xb20
[ 2.503945] [<ffffffff81789c9e>] uhci_irq+0x9e/0x180
[ 2.503945] [<ffffffff81741546>] usb_hcd_irq+0x26/0x40
[ 2.503945] [<ffffffff8110e46e>] handle_irq_event_percpu+0x3e/0x1e0
[ 2.503945] [<ffffffff8110e64d>] handle_irq_event+0x3d/0x60
[ 2.503945] [<ffffffff811117f9>] handle_fasteoi_irq+0x99/0x160
[ 2.503945] [<ffffffff810492c2>] handle_irq+0x102/0x190
[ 2.503945] [<ffffffff810e493a>] ? atomic_notifier_call_chain+0x3a/0x50
[ 2.503945] [<ffffffff81a7fbcb>] do_IRQ+0x5b/0x100
[ 2.503945] [<ffffffff81a7dd6f>] common_interrupt+0x6f/0x6f
[ 2.503945] <EOI> [<ffffffff810512ed>] ? default_idle+0x1d/0x100

>
>> +
>> + rxc = &rxs->slots[rx_slot];
>> +
>> + if (rxc->in_use && !rxc->urb) {
>> + rxc->urb = urb;
>> + complete(&rxc->done);
>> + /* URB will be resubmitted in free_rx_slot */
>> + } else {
>> + dev_warn(dev, "bad/late response %d/%d\n", handle, rx_slot);
>> + dln2_submit_urb(dln2, urb, GFP_ATOMIC);
>> + }
>> +
>> + spin_unlock(&rxs->lock);
>> +}
>
>> +static int _dln2_transfer(struct dln2_dev *dln2, u16 handle, u16 cmd,
>> + const void *obuf, unsigned obuf_len,
>> + void *ibuf, unsigned *ibuf_len)
>> +{
>> + int ret = 0;
>> + u16 result;
>> + int rx_slot;
>> + struct dln2_response *rsp;
>> + struct dln2_rx_context *rxc;
>> + struct device *dev;
>> + const int timeout = DLN2_USB_TIMEOUT * HZ / 1000;
>> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
>> +
>> + spin_lock(&dln2->disconnect_lock);
>
> How did you test this version? You never initialise disconnect_lock so
> lockdep complains loudly when calling _dln2_transfer during probe.
>

Oops, missed that as lockdep was not enable in the lastest test setup.

>> + if (!dln2->disconnect)
>> + dln2->active_transfers++;
>> + else
>> + ret = -ENODEV;
>> + spin_unlock(&dln2->disconnect_lock);
>> +
>> + if (ret)
>> + return ret;
>> +
>> + rx_slot = alloc_rx_slot(dln2, handle);
>> + if (rx_slot < 0) {
>> + ret = rx_slot;
>> + goto out_decr;
>> + }
>> +
>> + dev = &dln2->interface->dev;
>> +
>> + ret = dln2_send_wait(dln2, handle, cmd, rx_slot, obuf, obuf_len);
>> + if (ret < 0) {
>> + dev_err(dev, "USB write failed: %d\n", ret);
>> + goto out_free_rx_slot;
>> + }
>> +
>> + rxc = &rxs->slots[rx_slot];
>> +
>> + ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout);
>> + if (ret <= 0) {
>> + if (!ret)
>> + ret = -ETIMEDOUT;
>> + goto out_free_rx_slot;
>> + }
>> +
>> + if (!rxc->urb) {
>> + ret = -ENODEV;
>> + goto out_free_rx_slot;
>> + }
>> +
>> + /* if we got here we know that the response header has been checked */
>> + rsp = rxc->urb->transfer_buffer;
>> +
>> + if (rsp->hdr.size < sizeof(*rsp)) {
>> + ret = -EPROTO;
>> + goto out_free_rx_slot;
>> + }
>> +
>> + result = le16_to_cpu(rsp->result);
>> + if (result) {
>> + dev_dbg(dev, "%d received response with error %d\n",
>> + handle, result);
>> + ret = -EREMOTEIO;
>> + goto out_free_rx_slot;
>> + }
>> +
>> + if (!ibuf) {
>> + ret = 0;
>> + goto out_free_rx_slot;
>> + }
>> +
>> + if (*ibuf_len > rsp->hdr.size - sizeof(*rsp))
>> + *ibuf_len = rsp->hdr.size - sizeof(*rsp);
>> +
>> + memcpy(ibuf, rsp + 1, *ibuf_len);
>> +
>> +out_free_rx_slot:
>> + free_rx_slot(dln2, handle, rx_slot);
>> +out_decr:
>> + spin_lock(&dln2->disconnect_lock);
>> + dln2->active_transfers--;
>> + spin_unlock(&dln2->disconnect_lock);
>> + if (dln2->disconnect)
>> + wake_up(&dln2->disconnect_wq);
>> +
>> + return ret;
>> +}
>
>> +static void dln2_disconnect(struct usb_interface *interface)
>> +{
>> + struct dln2_dev *dln2 = usb_get_intfdata(interface);
>> + int i, j;
>> +
>> + /* don't allow starting new transfers */
>> + spin_lock(&dln2->disconnect_lock);
>> + dln2->disconnect = true;
>> + spin_unlock(&dln2->disconnect_lock);
>> +
>> + /* cancel in progress transfers */
>> + for (i = 0; i < DLN2_HANDLES; i++) {
>> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[i];
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&rxs->lock, flags);
>
> Just stick to spin_lock in this function.
>

AFAICS disconnect is called from a kernel thread. Are there guarantees
that we can't get a call to the completion routine while we are
running it?

2014-10-08 12:07:05

by Johan Hovold

[permalink] [raw]
Subject: Re: [PATCH v6 1/4] mfd: add support for Diolan DLN-2 devices

On Wed, Oct 08, 2014 at 01:54:07PM +0300, Octavian Purdila wrote:
> On Wed, Oct 8, 2014 at 12:23 PM, Johan Hovold <[email protected]> wrote:
> > On Thu, Sep 25, 2014 at 07:07:31PM +0300, Octavian Purdila wrote:
> >
> >> +static void dln2_rx_transfer(struct dln2_dev *dln2, struct urb *urb,
> >> + u16 handle, u16 rx_slot)
> >> +{
> >> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
> >> + struct dln2_rx_context *rxc;
> >> + struct device *dev = &dln2->interface->dev;
> >> +
> >> + spin_lock(&rxs->lock);
> >
> > You must use spin_lock_irqsave here as you call it from the completion
> > handler.
>
> Why? AFAICS the completion handler gets called from the HCD irq handler:

The completion handler is currently called with local interrupts
disabled but that is about to change once all drivers have been updated:

http://marc.info/?l=linux-usb&m=137353360511003&w=2

In this case you could probably get away with not disabling interrupts
anyway, but using the irqsave versions would make it obvious.

> >> +static void dln2_disconnect(struct usb_interface *interface)
> >> +{
> >> + struct dln2_dev *dln2 = usb_get_intfdata(interface);
> >> + int i, j;
> >> +
> >> + /* don't allow starting new transfers */
> >> + spin_lock(&dln2->disconnect_lock);
> >> + dln2->disconnect = true;
> >> + spin_unlock(&dln2->disconnect_lock);
> >> +
> >> + /* cancel in progress transfers */
> >> + for (i = 0; i < DLN2_HANDLES; i++) {
> >> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[i];
> >> + unsigned long flags;
> >> +
> >> + spin_lock_irqsave(&rxs->lock, flags);
> >
> > Just stick to spin_lock in this function.
>
> AFAICS disconnect is called from a kernel thread. Are there guarantees
> that we can't get a call to the completion routine while we are
> running it?

Brain fart, nevermind.

Johan

2014-10-08 12:33:31

by Octavian Purdila

[permalink] [raw]
Subject: Re: [PATCH v6 1/4] mfd: add support for Diolan DLN-2 devices

On Wed, Oct 8, 2014 at 3:04 PM, Johan Hovold <[email protected]> wrote:
> On Wed, Oct 08, 2014 at 01:54:07PM +0300, Octavian Purdila wrote:
>> On Wed, Oct 8, 2014 at 12:23 PM, Johan Hovold <[email protected]> wrote:
>> > On Thu, Sep 25, 2014 at 07:07:31PM +0300, Octavian Purdila wrote:
>> >
>> >> +static void dln2_rx_transfer(struct dln2_dev *dln2, struct urb *urb,
>> >> + u16 handle, u16 rx_slot)
>> >> +{
>> >> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
>> >> + struct dln2_rx_context *rxc;
>> >> + struct device *dev = &dln2->interface->dev;
>> >> +
>> >> + spin_lock(&rxs->lock);
>> >
>> > You must use spin_lock_irqsave here as you call it from the completion
>> > handler.
>>
>> Why? AFAICS the completion handler gets called from the HCD irq handler:
>
> The completion handler is currently called with local interrupts
> disabled but that is about to change once all drivers have been updated:
>
> http://marc.info/?l=linux-usb&m=137353360511003&w=2
>
> In this case you could probably get away with not disabling interrupts
> anyway, but using the irqsave versions would make it obvious.
>

I was not assuming that interrupts are disabled while running the
completion handler. Since that spinlock is not touched by any other
interrupt context code I don't think irqsave is necessary.

2014-10-09 13:19:37

by Johan Hovold

[permalink] [raw]
Subject: Re: [PATCH v6 1/4] mfd: add support for Diolan DLN-2 devices

On Wed, Oct 08, 2014 at 03:33:28PM +0300, Octavian Purdila wrote:
> On Wed, Oct 8, 2014 at 3:04 PM, Johan Hovold <[email protected]> wrote:
> > On Wed, Oct 08, 2014 at 01:54:07PM +0300, Octavian Purdila wrote:
> >> On Wed, Oct 8, 2014 at 12:23 PM, Johan Hovold <[email protected]> wrote:
> >> > On Thu, Sep 25, 2014 at 07:07:31PM +0300, Octavian Purdila wrote:
> >> >
> >> >> +static void dln2_rx_transfer(struct dln2_dev *dln2, struct urb *urb,
> >> >> + u16 handle, u16 rx_slot)
> >> >> +{
> >> >> + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
> >> >> + struct dln2_rx_context *rxc;
> >> >> + struct device *dev = &dln2->interface->dev;
> >> >> +
> >> >> + spin_lock(&rxs->lock);
> >> >
> >> > You must use spin_lock_irqsave here as you call it from the completion
> >> > handler.
> >>
> >> Why? AFAICS the completion handler gets called from the HCD irq handler:
> >
> > The completion handler is currently called with local interrupts
> > disabled but that is about to change once all drivers have been updated:
> >
> > http://marc.info/?l=linux-usb&m=137353360511003&w=2
> >
> > In this case you could probably get away with not disabling interrupts
> > anyway, but using the irqsave versions would make it obvious.
> >
>
> I was not assuming that interrupts are disabled while running the
> completion handler. Since that spinlock is not touched by any other
> interrupt context code I don't think irqsave is necessary.

No, it isn't necessary so leave it as it is.

But you are exporting interfaces to other drivers and it may appear to
someone that those could possibly end up indirectly calling a function
taking that lock in IRQ context. We know that isn't the case now, but I
bet someone will post conversion patch for that spinlock at some point.
;)

Johan