On Thu, Apr 13, 2017 at 04:06:47AM -0400, A Sun wrote: > > fix previous v1 patch error; incorrect location of "ir->pipe_in = pipe;" > caused null pointer dereference > > Bug: > > Once IR blasting or mceusb device commands fail with mce_async_callback() TX -EPIPE error, all subsequent TX to device then fail with the same error. > ... > [ 249.986174] mceusb 1-1.2:1.0: requesting 38000 HZ carrier > [ 249.986210] mceusb 1-1.2:1.0: send request called (size=0x4) > [ 249.986256] mceusb 1-1.2:1.0: send request complete (res=0) > [ 249.986403] mceusb 1-1.2:1.0: Error: request urb status = -32 (TX HALT) > [ 249.999885] mceusb 1-1.2:1.0: send request called (size=0x3) > [ 249.999929] mceusb 1-1.2:1.0: send request complete (res=0) > [ 250.000013] mceusb 1-1.2:1.0: Error: request urb status = -32 (TX HALT) > [ 250.019830] mceusb 1-1.2:1.0: send request called (size=0x21) > [ 250.019868] mceusb 1-1.2:1.0: send request complete (res=0) > [ 250.020007] mceusb 1-1.2:1.0: Error: request urb status = -32 (TX HALT) > ... > > Fix: > > Message pertains to TX usb halt (stall) condition requiring usb_clear_halt() call in non-interrupt context to recover. > Add USB TX halt error handling similar to the RX halt handling case from an earlier patch proposal. > Reorder some mceusb code to accommodate TX halt error handling. > > This patch depends on the earlier proposed patch set: > [PATCH 1/3] [media] mceusb: RX -EPIPE (urb status = -32) lockup failure fix > [PATCH 2/3] [media] mceusb: sporadic RX truncation corruption fix > [PATCH 3/3] [media] mceusb: fix inaccurate debug buffer dumps and misleading > > Tested with: > > Linux raspberrypi 4.4.50-v7+ #970 SMP Mon Feb 20 19:18:29 GMT 2017 armv7l GNU/Linux > mceusb 1-1.2:1.0: Registered SMK eHome Infrared Transceiver with mce emulator interface version 1 > mceusb 1-1.2:1.0: 2 tx ports (0x1 cabled) and 2 rx sensors (0x1 active) > > Fault simulation/injection is by executing the following USB operation in a mceusb instrumented driver, prior to TX I/O. > retval = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0), > USB_REQ_SET_FEATURE, USB_RECIP_ENDPOINT, > USB_ENDPOINT_HALT, usb_pipeendpoint(ir->pipe_out), > NULL, 0, USB_CTRL_SET_TIMEOUT); > dev_dbg(ir->dev, "set halt retval, %d", retval); > > After setting halt state for the TX endpoint, perform an lirc "irsend" to generate TX traffic to device. > After the TX HALT, the patch restores subsequent TX to working state. > ... > [ 508.009638] mceusb 1-1.2:1.0: send request called (size=0x3) > [ 508.009697] mceusb 1-1.2:1.0: send request complete (res=0) > [ 508.009847] mce_async_callback() > [ 508.009864] mceusb 1-1.2:1.0: Error: request urb status = -32 (TX HALT) > [ 508.009890] mceusb 1-1.2:1.0: kevent 0 scheduled > [ 508.021552] mceusb 1-1.2:1.0: send request called (size=0x21) > [ 508.021598] mceusb 1-1.2:1.0: send request complete (res=0) > [ 508.021963] mce_async_callback() > [ 508.021981] mceusb 1-1.2:1.0: tx data: 84 b0 0c 8c 0c 84 8c 0c 8c 0c 84 8c 0c 8c 0c 84 98 0c 98 0c 84 98 0c 8c 0c 84 8c 0c 8c 0c 81 8c 80 (length=33) > [ 508.021997] mceusb 1-1.2:1.0: Raw IR data, 0 pulse/space samples > [ 508.066627] mceusb 1-1.2:1.0: send request called (size=0x3) > [ 508.066669] mceusb 1-1.2:1.0: send request complete (res=0) > [ 508.066841] mce_async_callback() > [ 508.066858] mceusb 1-1.2:1.0: tx data: 9f 08 03 (length=3) > ... > > Open issue(s): > > Testing with Pinnacle mceusb device reveals device specific (non USB 2.0 standard) misbehavior with respect to USB TX halt. > > Linux raspberrypi 4.4.50-v7+ #970 SMP Mon Feb 20 19:18:29 GMT 2017 armv7l GNU/Linux > mceusb 1-1.2:1.0: Registered Pinnacle Systems PCTV Remote USB with mce emulator interface version 1 > mceusb 1-1.2:1.0: 2 tx ports (0x1 cabled) and 2 rx sensors (0x1 active) > > The Pinnacle device failed Linux usbtest module (modded to bind to the Pinnacle) test 13 with bogus halt status and -110 (-ETIMEDOUT) errors. > > [ 4558.114664] usbcore: deregistering interface driver mceusb > [14956.572207] usbtest 1-1.2:1.0: mce ir device > [14956.572234] usbtest 1-1.2:1.0: full-speed {control bulk-out} tests > [14956.572363] usbcore: registered new interface driver usbtest > [15241.341143] usbtest 1-1.2:1.0: TEST 1: write 512 bytes 1000 times > [15456.690845] usbtest 1-1.2:1.0: TEST 13: set/clear 1000 halts > [15456.691362] usbtest 1-1.2:1.0: ep 02 bogus status: 0001 != 0 > [15456.691381] usbtest 1-1.2:1.0: halts failed, iterations left 999 > > [37432.646344] usbcore: deregistering interface driver mceusb > [37468.447929] usbtest 1-1.2:1.0: mce ir device > [37468.447956] usbtest 1-1.2:1.0: full-speed {control bulk-out} tests > [37468.448079] usbcore: registered new interface driver usbtest > [37519.150810] usbtest 1-1.2:1.0: TEST 1: write 512 bytes 1000 times > [37537.853493] usbtest 1-1.2:1.0: TEST 13: set/clear 1000 halts > [37547.866871] usb 1-1.2: verify_not_halted failed, iterations left 0, status -110 (not 0) > [37547.866901] usbtest 1-1.2:1.0: halts failed, iterations left 999 > > With mceusb, upon executing usb_clear_halt() on this Pinnacle mceusb USB TX end-point, regardless of its halt/stall state, TX functionality silently ceases. > mce_async_callback() invocations cease, and there are no other error indications from usb_submit_urb() or anywhere else. > An escalating USB reset was necessary to restore this device to working state. > Certain mceusb devices may require device specific recovery procedure for TX halt conditions, which this patch does not address. > > Signed-off-by: A Sun <as1033x@xxxxxxxxxxx> > --- > drivers/media/rc/mceusb.c | 89 +++++++++++++++++++++++++++++------------------ > 1 file changed, 56 insertions(+), 33 deletions(-) > > diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c > index a9a9a85..af46860 100644 > --- a/drivers/media/rc/mceusb.c > +++ b/drivers/media/rc/mceusb.c > @@ -419,6 +419,7 @@ struct mceusb_dev { > struct urb *urb_in; > unsigned int pipe_in; > struct usb_endpoint_descriptor *usb_ep_out; > + unsigned int pipe_out; > > /* buffers and dma */ > unsigned char *buf_in; > @@ -456,7 +457,8 @@ struct mceusb_dev { > u8 txports_cabled; /* bitmask of transmitters with cable */ > u8 rxports_active; /* bitmask of active receive sensors */ > > - /* kevent support */ > + /* async error handler mceusb_deferred_kevent() support > + * via workqueue kworker (previously keventd) threads */ Multi-line comments should be like: /* * async error handler mceusb_deferred_kevent() support * via workqueue kworker (previously keventd) threads */ Then again the comment says no more than what you can read in the source code, so I would rephrase it /* clear halt should be done in process context */ > struct work_struct kevent; > unsigned long kevent_flags; > # define EVENT_TX_HALT 0 > @@ -694,6 +696,22 @@ static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, > #endif > } > > +/* > + * Schedule work that can't be done in interrupt handlers > + * (mceusb_dev_recv() and mce_async_callback()) nor tasklets. > + * Invokes mceusb_deferred_kevent() for recovering from > + * error events specified by the kevent bit field. > + */ > +static void mceusb_defer_kevent(struct mceusb_dev *ir, int kevent) > +{ > + set_bit(kevent, &ir->kevent_flags); > + if (!schedule_work(&ir->kevent)) { > + dev_err(ir->dev, "kevent %d may have been dropped", kevent); > + } else { > + dev_dbg(ir->dev, "kevent %d scheduled", kevent); > + } You don't need curly braces when there is only one statement on an if/else branch. > +} > + > static void mce_async_callback(struct urb *urb) > { > struct mceusb_dev *ir; > @@ -720,6 +738,11 @@ static void mce_async_callback(struct urb *urb) > break; > > case -EPIPE: > + dev_err(ir->dev, "Error: request urb status = %d (TX HALT)", > + urb->status); > + mceusb_defer_kevent(ir, EVENT_TX_HALT); > + break; > + > default: > dev_err(ir->dev, "Error: request urb status = %d", urb->status); > break; > @@ -734,7 +757,7 @@ static void mce_async_callback(struct urb *urb) > static void mce_request_packet(struct mceusb_dev *ir, unsigned char *data, > int size) > { > - int res, pipe; > + int res; > struct urb *async_urb; > struct device *dev = ir->dev; > unsigned char *async_buf; > @@ -753,17 +776,12 @@ static void mce_request_packet(struct mceusb_dev *ir, unsigned char *data, > > /* outbound data */ > if (usb_endpoint_xfer_int(ir->usb_ep_out)) { > - pipe = usb_sndintpipe(ir->usbdev, > - ir->usb_ep_out->bEndpointAddress); > - usb_fill_int_urb(async_urb, ir->usbdev, pipe, async_buf, > - size, mce_async_callback, ir, > + usb_fill_int_urb(async_urb, ir->usbdev, ir->pipe_out, > + async_buf, size, mce_async_callback, ir, > ir->usb_ep_out->bInterval); > } else { > - pipe = usb_sndbulkpipe(ir->usbdev, > - ir->usb_ep_out->bEndpointAddress); > - usb_fill_bulk_urb(async_urb, ir->usbdev, pipe, > - async_buf, size, mce_async_callback, > - ir); > + usb_fill_bulk_urb(async_urb, ir->usbdev, ir->pipe_out, > + async_buf, size, mce_async_callback, ir); > } No curly braces needed now that there is one statement left in each branch. > memcpy(async_buf, data, size); > > @@ -1034,23 +1052,6 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) > } > } > > -/* > - * Workqueue task dispatcher > - * for work that can't be done in interrupt handlers > - * (mceusb_dev_recv() and mce_async_callback()) nor tasklets. > - * Invokes mceusb_deferred_kevent() for recovering from > - * error events specified by the kevent bit field. > - */ > -static void mceusb_defer_kevent(struct mceusb_dev *ir, int kevent) > -{ > - set_bit(kevent, &ir->kevent_flags); > - if (!schedule_work(&ir->kevent)) { > - dev_err(ir->dev, "kevent %d may have been dropped", kevent); > - } else { > - dev_dbg(ir->dev, "kevent %d scheduled", kevent); > - } > -} > - > static void mceusb_dev_recv(struct urb *urb) > { > struct mceusb_dev *ir; > @@ -1220,16 +1221,28 @@ static void mceusb_deferred_kevent(struct work_struct *work) > if (status < 0) { > dev_err(ir->dev, "rx clear halt error %d", > status); > - return; > + goto done_rx_halt; This function can easily be re-written without gotos and it will be much more readible. > } > clear_bit(EVENT_RX_HALT, &ir->kevent_flags); > status = usb_submit_urb(ir->urb_in, GFP_KERNEL); > if (status < 0) { > dev_err(ir->dev, "rx unhalt submit urb error %d", > status); > - return; > + goto done_rx_halt; > } > } > +done_rx_halt: > + if (test_bit(EVENT_TX_HALT, &ir->kevent_flags)) { > + status = usb_clear_halt(ir->usbdev, ir->pipe_out); > + if (status < 0) { > + dev_err(ir->dev, "tx clear halt error %d", > + status); > + goto done_tx_halt; > + } > + clear_bit(EVENT_TX_HALT, &ir->kevent_flags); > + } > +done_tx_halt: > + return; > } > > static struct rc_dev *mceusb_init_rc_dev(struct mceusb_dev *ir) > @@ -1373,6 +1386,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, > if (!ir->urb_in) > goto urb_in_alloc_fail; > > + ir->pipe_in = pipe; > ir->usbdev = usb_get_dev(dev); > ir->dev = &intf->dev; > ir->len_in = maxp; > @@ -1383,6 +1397,13 @@ static int mceusb_dev_probe(struct usb_interface *intf, > > /* Saving usb interface data for use by the transmitter routine */ > ir->usb_ep_out = ep_out; > + if (usb_endpoint_xfer_int(ir->usb_ep_out)) { > + ir->pipe_out = usb_sndintpipe(ir->usbdev, > + ir->usb_ep_out->bEndpointAddress); > + } else { > + ir->pipe_out = usb_sndbulkpipe(ir->usbdev, > + ir->usb_ep_out->bEndpointAddress); > + } Unnecessary braces. > if (dev->descriptor.iManufacturer > && usb_string(dev, dev->descriptor.iManufacturer, > @@ -1394,13 +1415,14 @@ static int mceusb_dev_probe(struct usb_interface *intf, > snprintf(name + strlen(name), sizeof(name) - strlen(name), > " %s", buf); > > + /* initialize async USB error handler before registering > + * or activating any mceusb RX and TX functions */ Bad multiline comment. > + INIT_WORK(&ir->kevent, mceusb_deferred_kevent); > + > ir->rc = mceusb_init_rc_dev(ir); > if (!ir->rc) > goto rc_dev_fail; > > - ir->pipe_in = pipe; > - INIT_WORK(&ir->kevent, mceusb_deferred_kevent); > - > /* wire up inbound data handler */ > if (usb_endpoint_xfer_int(ep_in)) { > usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, > @@ -1450,6 +1472,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, > > /* Error-handling path */ > rc_dev_fail: > + cancel_work_sync(&ir->kevent); > usb_put_dev(ir->usbdev); > usb_kill_urb(ir->urb_in); > usb_free_urb(ir->urb_in); > -- > 2.1.4