[PATCH v3] USB: Convert from tasklet to BH workqueue

From: Allen Pais
Date: Wed Apr 03 2024 - 12:07:14 EST


The only generic interface to execute asynchronously in the BH context is
tasklet; however, it's marked deprecated and has some design flaws. To
replace tasklets, BH workqueue support was recently added. A BH workqueue
behaves similarly to regular workqueues except that the queued work items
are executed in the BH context.

This patch converts drivers/usb/* from tasklet to BH workqueue.

Based on the work done by Tejun Heo <tj@xxxxxxxxxx>
Branch: https://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-6.10

Reviewed-by: Duncan Sands <duncan.sands@xxxxxxx>
Signed-off-by: Allen Pais <allen.lkml@xxxxxxxxx>

---
Changes in v3:
- Fixed broken link

Changes in v2:
- Update code comments and commit message.
Suggested by Greg K h
- Retain local variable name(hcd.c).
Suggested by Alan Stern

- Link to v1:https://lore.kernel.org/all/20240327160314.9982-1-apais@xxxxxxxxxxxxxxxxxxx/T/#m2ec7868fbf102f1551ebe4c92d200848b94af3b1
---
drivers/usb/atm/usbatm.c | 51 +++++++++++++++--------------
drivers/usb/atm/usbatm.h | 3 +-
drivers/usb/core/hcd.c | 4 +--
drivers/usb/gadget/udc/fsl_qe_udc.c | 21 ++++++------
drivers/usb/gadget/udc/fsl_qe_udc.h | 4 +--
drivers/usb/host/ehci-sched.c | 2 +-
drivers/usb/host/fhci-hcd.c | 3 +-
drivers/usb/host/fhci-sched.c | 10 +++---
drivers/usb/host/fhci.h | 5 +--
drivers/usb/host/xhci-dbgcap.h | 3 +-
drivers/usb/host/xhci-dbgtty.c | 15 +++++----
include/linux/usb/cdc_ncm.h | 2 +-
include/linux/usb/usbnet.h | 2 +-
13 files changed, 65 insertions(+), 60 deletions(-)

diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c
index 2da6615fbb6f..1467d09a98c0 100644
--- a/drivers/usb/atm/usbatm.c
+++ b/drivers/usb/atm/usbatm.c
@@ -68,6 +68,7 @@
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/ratelimit.h>
+#include <linux/workqueue.h>

#ifdef VERBOSE_DEBUG
static int usbatm_print_packet(struct usbatm_data *instance, const unsigned char *data, int len);
@@ -269,7 +270,7 @@ static void usbatm_complete(struct urb *urb)
/* throttle processing in case of an error */
mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS));
} else
- tasklet_schedule(&channel->tasklet);
+ queue_work(system_bh_wq, &channel->work);
}


@@ -511,10 +512,10 @@ static unsigned int usbatm_write_cells(struct usbatm_data *instance,
** receive **
**************/

-static void usbatm_rx_process(struct tasklet_struct *t)
+static void usbatm_rx_process(struct work_struct *t)
{
- struct usbatm_data *instance = from_tasklet(instance, t,
- rx_channel.tasklet);
+ struct usbatm_data *instance = from_work(instance, t,
+ rx_channel.work);
struct urb *urb;

while ((urb = usbatm_pop_urb(&instance->rx_channel))) {
@@ -565,10 +566,10 @@ static void usbatm_rx_process(struct tasklet_struct *t)
** send **
***********/

-static void usbatm_tx_process(struct tasklet_struct *t)
+static void usbatm_tx_process(struct work_struct *t)
{
- struct usbatm_data *instance = from_tasklet(instance, t,
- tx_channel.tasklet);
+ struct usbatm_data *instance = from_work(instance, t,
+ tx_channel.work);
struct sk_buff *skb = instance->current_skb;
struct urb *urb = NULL;
const unsigned int buf_size = instance->tx_channel.buf_size;
@@ -632,13 +633,13 @@ static void usbatm_cancel_send(struct usbatm_data *instance,
}
spin_unlock_irq(&instance->sndqueue.lock);

- tasklet_disable(&instance->tx_channel.tasklet);
+ disable_work_sync(&instance->tx_channel.work);
if ((skb = instance->current_skb) && (UDSL_SKB(skb)->atm.vcc == vcc)) {
atm_dbg(instance, "%s: popping current skb (0x%p)\n", __func__, skb);
instance->current_skb = NULL;
usbatm_pop(vcc, skb);
}
- tasklet_enable(&instance->tx_channel.tasklet);
+ enable_and_queue_work(system_bh_wq, &instance->tx_channel.work);
}

static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
@@ -677,7 +678,7 @@ static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
ctrl->crc = crc32_be(~0, skb->data, skb->len);

skb_queue_tail(&instance->sndqueue, skb);
- tasklet_schedule(&instance->tx_channel.tasklet);
+ queue_work(system_bh_wq, &instance->tx_channel.work);

return 0;

@@ -695,8 +696,8 @@ static void usbatm_destroy_instance(struct kref *kref)
{
struct usbatm_data *instance = container_of(kref, struct usbatm_data, refcount);

- tasklet_kill(&instance->rx_channel.tasklet);
- tasklet_kill(&instance->tx_channel.tasklet);
+ cancel_work_sync(&instance->rx_channel.work);
+ cancel_work_sync(&instance->tx_channel.work);
usb_put_dev(instance->usb_dev);
kfree(instance);
}
@@ -823,12 +824,12 @@ static int usbatm_atm_open(struct atm_vcc *vcc)

vcc->dev_data = new;

- tasklet_disable(&instance->rx_channel.tasklet);
+ disable_work_sync(&instance->rx_channel.work);
instance->cached_vcc = new;
instance->cached_vpi = vpi;
instance->cached_vci = vci;
list_add(&new->list, &instance->vcc_list);
- tasklet_enable(&instance->rx_channel.tasklet);
+ enable_and_queue_work(system_bh_wq, &instance->rx_channel.work);

set_bit(ATM_VF_ADDR, &vcc->flags);
set_bit(ATM_VF_PARTIAL, &vcc->flags);
@@ -858,14 +859,14 @@ static void usbatm_atm_close(struct atm_vcc *vcc)

mutex_lock(&instance->serialize); /* vs self, usbatm_atm_open, usbatm_usb_disconnect */

- tasklet_disable(&instance->rx_channel.tasklet);
+ disable_work_sync(&instance->rx_channel.work);
if (instance->cached_vcc == vcc_data) {
instance->cached_vcc = NULL;
instance->cached_vpi = ATM_VPI_UNSPEC;
instance->cached_vci = ATM_VCI_UNSPEC;
}
list_del(&vcc_data->list);
- tasklet_enable(&instance->rx_channel.tasklet);
+ enable_and_queue_work(system_bh_wq, &instance->rx_channel.work);

kfree_skb(vcc_data->sarb);
vcc_data->sarb = NULL;
@@ -991,18 +992,18 @@ static int usbatm_heavy_init(struct usbatm_data *instance)
return 0;
}

-static void usbatm_tasklet_schedule(struct timer_list *t)
+static void usbatm_queue_work(system_bh_wq, struct timer_list *t)
{
struct usbatm_channel *channel = from_timer(channel, t, delay);

- tasklet_schedule(&channel->tasklet);
+ queue_work(system_bh_wq, &channel->work);
}

static void usbatm_init_channel(struct usbatm_channel *channel)
{
spin_lock_init(&channel->lock);
INIT_LIST_HEAD(&channel->list);
- timer_setup(&channel->delay, usbatm_tasklet_schedule, 0);
+ timer_setup(&channel->delay, usbatm_queue_work, 0);
}

int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,
@@ -1074,8 +1075,8 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,

usbatm_init_channel(&instance->rx_channel);
usbatm_init_channel(&instance->tx_channel);
- tasklet_setup(&instance->rx_channel.tasklet, usbatm_rx_process);
- tasklet_setup(&instance->tx_channel.tasklet, usbatm_tx_process);
+ INIT_WORK(&instance->rx_channel.work, usbatm_rx_process);
+ INIT_WORK(&instance->tx_channel.work, usbatm_tx_process);
instance->rx_channel.stride = ATM_CELL_SIZE + driver->rx_padding;
instance->tx_channel.stride = ATM_CELL_SIZE + driver->tx_padding;
instance->rx_channel.usbatm = instance->tx_channel.usbatm = instance;
@@ -1231,8 +1232,8 @@ void usbatm_usb_disconnect(struct usb_interface *intf)
vcc_release_async(vcc_data->vcc, -EPIPE);
mutex_unlock(&instance->serialize);

- tasklet_disable(&instance->rx_channel.tasklet);
- tasklet_disable(&instance->tx_channel.tasklet);
+ disable_work_sync(&instance->rx_channel.work);
+ disable_work_sync(&instance->tx_channel.work);

for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++)
usb_kill_urb(instance->urbs[i]);
@@ -1245,8 +1246,8 @@ void usbatm_usb_disconnect(struct usb_interface *intf)
INIT_LIST_HEAD(&instance->rx_channel.list);
INIT_LIST_HEAD(&instance->tx_channel.list);

- tasklet_enable(&instance->rx_channel.tasklet);
- tasklet_enable(&instance->tx_channel.tasklet);
+ enable_and_queue_work(system_bh_wq, &instance->rx_channel.work);
+ enable_and_queue_work(system_bh_wq, &instance->tx_channel.work);

if (instance->atm_dev && instance->driver->atm_stop)
instance->driver->atm_stop(instance, instance->atm_dev);
diff --git a/drivers/usb/atm/usbatm.h b/drivers/usb/atm/usbatm.h
index d96658e2e209..3452f8c2e1e5 100644
--- a/drivers/usb/atm/usbatm.h
+++ b/drivers/usb/atm/usbatm.h
@@ -21,6 +21,7 @@
#include <linux/usb.h>
#include <linux/mutex.h>
#include <linux/ratelimit.h>
+#include <linux/workqueue.h>

/*
#define VERBOSE_DEBUG
@@ -109,7 +110,7 @@ struct usbatm_channel {
unsigned int packet_size; /* endpoint maxpacket */
spinlock_t lock;
struct list_head list;
- struct tasklet_struct tasklet;
+ struct work_struct work;
struct timer_list delay;
struct usbatm_data *usbatm;
};
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index c0e005670d67..b31d84b02055 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -37,6 +37,7 @@
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/otg.h>
+#include <linux/workqueue.h>

#include "usb.h"
#include "phy.h"
@@ -1664,8 +1665,7 @@ static void __usb_hcd_giveback_urb(struct urb *urb)

static void usb_giveback_urb_bh(struct work_struct *work)
{
- struct giveback_urb_bh *bh =
- container_of(work, struct giveback_urb_bh, bh);
+ struct giveback_urb_bh *bh = from_work(bh, work, bh);
struct list_head local_list;

spin_lock_irq(&bh->lock);
diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c
index 4e88681a79b6..ae29d946a972 100644
--- a/drivers/usb/gadget/udc/fsl_qe_udc.c
+++ b/drivers/usb/gadget/udc/fsl_qe_udc.c
@@ -35,6 +35,7 @@
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
+#include <linux/workqueue.h>
#include <soc/fsl/qe/qe.h>
#include <asm/cpm.h>
#include <asm/dma.h>
@@ -930,9 +931,9 @@ static int qe_ep_rxframe_handle(struct qe_ep *ep)
return 0;
}

-static void ep_rx_tasklet(struct tasklet_struct *t)
+static void ep_rx_work(struct work_struct *t)
{
- struct qe_udc *udc = from_tasklet(udc, t, rx_tasklet);
+ struct qe_udc *udc = from_work(udc, t, rx_work);
struct qe_ep *ep;
struct qe_frame *pframe;
struct qe_bd __iomem *bd;
@@ -945,9 +946,9 @@ static void ep_rx_tasklet(struct tasklet_struct *t)
for (i = 1; i < USB_MAX_ENDPOINTS; i++) {
ep = &udc->eps[i];

- if (ep->dir == USB_DIR_IN || ep->enable_tasklet == 0) {
+ if (ep->dir == USB_DIR_IN || ep->enable_work == 0) {
dev_dbg(udc->dev,
- "This is a transmit ep or disable tasklet!\n");
+ "This is a transmit ep or disable work!\n");
continue;
}

@@ -1012,7 +1013,7 @@ static void ep_rx_tasklet(struct tasklet_struct *t)
if (ep->localnack)
ep_recycle_rxbds(ep);

- ep->enable_tasklet = 0;
+ ep->enable_work = 0;
} /* for i=1 */

spin_unlock_irqrestore(&udc->lock, flags);
@@ -1057,8 +1058,8 @@ static int qe_ep_rx(struct qe_ep *ep)
return 0;
}

- tasklet_schedule(&udc->rx_tasklet);
- ep->enable_tasklet = 1;
+ queue_work(system_bh_wq, &udc->rx_work);
+ ep->enable_work = 1;

return 0;
}
@@ -2559,7 +2560,7 @@ static int qe_udc_probe(struct platform_device *ofdev)
DMA_TO_DEVICE);
}

- tasklet_setup(&udc->rx_tasklet, ep_rx_tasklet);
+ INIT_WORK(&udc->rx_work, ep_rx_work);
/* request irq and disable DR */
udc->usb_irq = irq_of_parse_and_map(np, 0);
if (!udc->usb_irq) {
@@ -2636,7 +2637,7 @@ static void qe_udc_remove(struct platform_device *ofdev)
usb_del_gadget_udc(&udc->gadget);

udc->done = &done;
- tasklet_disable(&udc->rx_tasklet);
+ disable_work_sync(&udc->rx_work);

if (udc->nullmap) {
dma_unmap_single(udc->gadget.dev.parent,
@@ -2671,7 +2672,7 @@ static void qe_udc_remove(struct platform_device *ofdev)
free_irq(udc->usb_irq, udc);
irq_dispose_mapping(udc->usb_irq);

- tasklet_kill(&udc->rx_tasklet);
+ cancel_work_sync(&udc->rx_work);

iounmap(udc->usb_regs);

diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.h b/drivers/usb/gadget/udc/fsl_qe_udc.h
index 53ca0ff7c2cb..1de87c318460 100644
--- a/drivers/usb/gadget/udc/fsl_qe_udc.h
+++ b/drivers/usb/gadget/udc/fsl_qe_udc.h
@@ -293,7 +293,7 @@ struct qe_ep {
u8 init;

u8 already_seen;
- u8 enable_tasklet;
+ u8 enable_work;
u8 setup_stage;
u32 last_io; /* timestamp */

@@ -353,7 +353,7 @@ struct qe_udc {
unsigned int usb_irq;
struct usb_ctlr __iomem *usb_regs;

- struct tasklet_struct rx_tasklet;
+ struct work_struct rx_work;

struct completion *done; /* to make sure release() is done */
};
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index 7e834587e7de..54f3bd6298e6 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -682,7 +682,7 @@ static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)

/*
* It is common only one intr URB is scheduled on one qh, and
- * given complete() is run in tasklet context, introduce a bit
+ * given complete() is run in task context, introduce a bit
* delay to avoid unlink qh too early.
*/
static void start_unlink_intr_wait(struct ehci_hcd *ehci,
diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c
index 9a1b5224f239..5358bb688acb 100644
--- a/drivers/usb/host/fhci-hcd.c
+++ b/drivers/usb/host/fhci-hcd.c
@@ -211,8 +211,7 @@ static int fhci_mem_init(struct fhci_hcd *fhci)
INIT_LIST_HEAD(&fhci->empty_tds);

/* initialize work queue to handle done list */
- fhci_tasklet.data = (unsigned long)fhci;
- fhci->process_done_task = &fhci_tasklet;
+ INIT_WORK(&fhci->process_done_task, process_done_list);

for (i = 0; i < MAX_TDS; i++) {
struct td *td;
diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c
index a45ede80edfc..9033cce28014 100644
--- a/drivers/usb/host/fhci-sched.c
+++ b/drivers/usb/host/fhci-sched.c
@@ -628,13 +628,13 @@ irqreturn_t fhci_irq(struct usb_hcd *hcd)
* is process_del_list(),which unlinks URBs by scanning EDs,instead of scanning
* the (re-reversed) done list as this does.
*/
-static void process_done_list(unsigned long data)
+static void process_done_list(struct work_struct *t)
{
struct urb *urb;
struct ed *ed;
struct td *td;
struct urb_priv *urb_priv;
- struct fhci_hcd *fhci = (struct fhci_hcd *)data;
+ struct fhci_hcd *fhci = from_work(fhci, t, process_done_task);

disable_irq(fhci->timer->irq);
disable_irq(fhci_to_hcd(fhci)->irq);
@@ -677,13 +677,13 @@ static void process_done_list(unsigned long data)
enable_irq(fhci_to_hcd(fhci)->irq);
}

-DECLARE_TASKLET_OLD(fhci_tasklet, process_done_list);
+DECLARE_WORK(fhci_work, process_done_list);

/* transfer complted callback */
u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci)
{
- if (!fhci->process_done_task->state)
- tasklet_schedule(fhci->process_done_task);
+ if (!fhci->process_done_task)
+ queue_work(system_bh_wq, fhci->process_done_task);
return 0;
}

diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h
index 1f57b0989485..7cd613762249 100644
--- a/drivers/usb/host/fhci.h
+++ b/drivers/usb/host/fhci.h
@@ -24,6 +24,7 @@
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/gpio/consumer.h>
+#include <linux/workqueue.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/immap_qe.h>

@@ -254,7 +255,7 @@ struct fhci_hcd {
struct virtual_root_hub *vroot_hub; /* the virtual root hub */
int active_urbs;
struct fhci_controller_list *hc_list;
- struct tasklet_struct *process_done_task; /* tasklet for done list */
+ struct work_struct *process_done_task; /* work for done list */

struct list_head empty_eds;
struct list_head empty_tds;
@@ -549,7 +550,7 @@ void fhci_init_ep_registers(struct fhci_usb *usb,
void fhci_ep0_free(struct fhci_usb *usb);

/* fhci-sched.c */
-extern struct tasklet_struct fhci_tasklet;
+extern struct work_struct fhci_work;
void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt);
void fhci_flush_all_transmissions(struct fhci_usb *usb);
void fhci_schedule_transactions(struct fhci_usb *usb);
diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h
index 92661b555c2a..0cd57c479fd7 100644
--- a/drivers/usb/host/xhci-dbgcap.h
+++ b/drivers/usb/host/xhci-dbgcap.h
@@ -11,6 +11,7 @@

#include <linux/tty.h>
#include <linux/kfifo.h>
+#include <linux/workqueue.h>

struct dbc_regs {
__le32 capability;
@@ -107,7 +108,7 @@ struct dbc_port {
struct list_head read_pool;
struct list_head read_queue;
unsigned int n_read;
- struct tasklet_struct push;
+ struct work_struct push;

struct list_head write_pool;
struct kfifo write_fifo;
diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c
index b74e98e94393..dec2280b4ae9 100644
--- a/drivers/usb/host/xhci-dbgtty.c
+++ b/drivers/usb/host/xhci-dbgtty.c
@@ -11,6 +11,7 @@
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/idr.h>
+#include <linux/workqueue.h>

#include "xhci.h"
#include "xhci-dbgcap.h"
@@ -108,7 +109,7 @@ dbc_read_complete(struct xhci_dbc *dbc, struct dbc_request *req)

spin_lock_irqsave(&port->port_lock, flags);
list_add_tail(&req->list_pool, &port->read_queue);
- tasklet_schedule(&port->push);
+ queue_work(system_bh_wq, &port->push);
spin_unlock_irqrestore(&port->port_lock, flags);
}

@@ -278,7 +279,7 @@ static void dbc_tty_unthrottle(struct tty_struct *tty)
unsigned long flags;

spin_lock_irqsave(&port->port_lock, flags);
- tasklet_schedule(&port->push);
+ queue_work(system_bh_wq, &port->push);
spin_unlock_irqrestore(&port->port_lock, flags);
}

@@ -294,14 +295,14 @@ static const struct tty_operations dbc_tty_ops = {
.unthrottle = dbc_tty_unthrottle,
};

-static void dbc_rx_push(struct tasklet_struct *t)
+static void dbc_rx_push(struct work_struct *t)
{
struct dbc_request *req;
struct tty_struct *tty;
unsigned long flags;
bool do_push = false;
bool disconnect = false;
- struct dbc_port *port = from_tasklet(port, t, push);
+ struct dbc_port *port = from_work(port, t, push);
struct list_head *queue = &port->read_queue;

spin_lock_irqsave(&port->port_lock, flags);
@@ -355,7 +356,7 @@ static void dbc_rx_push(struct tasklet_struct *t)
if (!list_empty(queue) && tty) {
if (!tty_throttled(tty)) {
if (do_push)
- tasklet_schedule(&port->push);
+ queue_work(system_bh_wq, &port->push);
else
pr_warn("ttyDBC0: RX not scheduled?\n");
}
@@ -388,7 +389,7 @@ xhci_dbc_tty_init_port(struct xhci_dbc *dbc, struct dbc_port *port)
{
tty_port_init(&port->port);
spin_lock_init(&port->port_lock);
- tasklet_setup(&port->push, dbc_rx_push);
+ INIT_WORK(&port->push, dbc_rx_push);
INIT_LIST_HEAD(&port->read_pool);
INIT_LIST_HEAD(&port->read_queue);
INIT_LIST_HEAD(&port->write_pool);
@@ -400,7 +401,7 @@ xhci_dbc_tty_init_port(struct xhci_dbc *dbc, struct dbc_port *port)
static void
xhci_dbc_tty_exit_port(struct dbc_port *port)
{
- tasklet_kill(&port->push);
+ cancel_work_sync(&port->push);
tty_port_destroy(&port->port);
}

diff --git a/include/linux/usb/cdc_ncm.h b/include/linux/usb/cdc_ncm.h
index 2d207cb4837d..8775580852f9 100644
--- a/include/linux/usb/cdc_ncm.h
+++ b/include/linux/usb/cdc_ncm.h
@@ -96,7 +96,7 @@
struct cdc_ncm_ctx {
struct usb_cdc_ncm_ntb_parameters ncm_parm;
struct hrtimer tx_timer;
- struct tasklet_struct bh;
+ struct work_struct bh;

struct usbnet *dev;

diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h
index 9f08a584d707..522c533a966b 100644
--- a/include/linux/usb/usbnet.h
+++ b/include/linux/usb/usbnet.h
@@ -58,7 +58,7 @@ struct usbnet {
unsigned interrupt_count;
struct mutex interrupt_mutex;
struct usb_anchor deferred;
- struct tasklet_struct bh;
+ struct work_struct bh;

struct work_struct kevent;
unsigned long flags;
--
2.17.1