mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 13:55:32 +00:00
staging: most: hdm-usb: fix clear halt processing
This patch is needed to ensure that submitted URBs get unlinked before the driver calls usb_clear_halt(). Since the halt condition of an USB endpoint is channel related, the work_struct is moved from a buffer basis to a channel basis. Signed-off-by: Christian Gromm <christian.gromm@microchip.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
9ed745ff8e
commit
cc28983c32
1 changed files with 29 additions and 33 deletions
|
@ -65,19 +65,15 @@
|
||||||
/**
|
/**
|
||||||
* struct buf_anchor - used to create a list of pending URBs
|
* struct buf_anchor - used to create a list of pending URBs
|
||||||
* @urb: pointer to USB request block
|
* @urb: pointer to USB request block
|
||||||
* @clear_work_obj:
|
|
||||||
* @list: linked list
|
* @list: linked list
|
||||||
* @urb_completion:
|
* @urb_completion:
|
||||||
*/
|
*/
|
||||||
struct buf_anchor {
|
struct buf_anchor {
|
||||||
struct urb *urb;
|
struct urb *urb;
|
||||||
struct work_struct clear_work_obj;
|
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
struct completion urb_compl;
|
struct completion urb_compl;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define to_buf_anchor(w) container_of(w, struct buf_anchor, clear_work_obj)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct most_dci_obj - Direct Communication Interface
|
* struct most_dci_obj - Direct Communication Interface
|
||||||
* @kobj:position in sysfs
|
* @kobj:position in sysfs
|
||||||
|
@ -90,6 +86,17 @@ struct most_dci_obj {
|
||||||
|
|
||||||
#define to_dci_obj(p) container_of(p, struct most_dci_obj, kobj)
|
#define to_dci_obj(p) container_of(p, struct most_dci_obj, kobj)
|
||||||
|
|
||||||
|
struct most_dev;
|
||||||
|
|
||||||
|
struct clear_hold_work {
|
||||||
|
struct work_struct ws;
|
||||||
|
struct most_dev *mdev;
|
||||||
|
unsigned int channel;
|
||||||
|
int pipe;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_clear_hold_work(w) container_of(w, struct clear_hold_work, ws)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct most_dev - holds all usb interface specific stuff
|
* struct most_dev - holds all usb interface specific stuff
|
||||||
* @parent: parent object in sysfs
|
* @parent: parent object in sysfs
|
||||||
|
@ -126,6 +133,7 @@ struct most_dev {
|
||||||
spinlock_t anchor_list_lock[MAX_NUM_ENDPOINTS];
|
spinlock_t anchor_list_lock[MAX_NUM_ENDPOINTS];
|
||||||
bool padding_active[MAX_NUM_ENDPOINTS];
|
bool padding_active[MAX_NUM_ENDPOINTS];
|
||||||
bool is_channel_healthy[MAX_NUM_ENDPOINTS];
|
bool is_channel_healthy[MAX_NUM_ENDPOINTS];
|
||||||
|
struct clear_hold_work clear_work[MAX_NUM_ENDPOINTS];
|
||||||
struct list_head *anchor_list;
|
struct list_head *anchor_list;
|
||||||
struct mutex io_mutex;
|
struct mutex io_mutex;
|
||||||
struct timer_list link_stat_timer;
|
struct timer_list link_stat_timer;
|
||||||
|
@ -222,7 +230,6 @@ static void free_anchored_buffers(struct most_dev *mdev, unsigned int channel,
|
||||||
}
|
}
|
||||||
spin_lock_irqsave(&mdev->anchor_list_lock[channel], flags);
|
spin_lock_irqsave(&mdev->anchor_list_lock[channel], flags);
|
||||||
list_del(&anchor->list);
|
list_del(&anchor->list);
|
||||||
cancel_work_sync(&anchor->clear_work_obj);
|
|
||||||
kfree(anchor);
|
kfree(anchor);
|
||||||
}
|
}
|
||||||
spin_unlock_irqrestore(&mdev->anchor_list_lock[channel], flags);
|
spin_unlock_irqrestore(&mdev->anchor_list_lock[channel], flags);
|
||||||
|
@ -289,6 +296,8 @@ static int hdm_poison_channel(struct most_interface *iface, int channel)
|
||||||
|
|
||||||
mdev->is_channel_healthy[channel] = false;
|
mdev->is_channel_healthy[channel] = false;
|
||||||
|
|
||||||
|
cancel_work_sync(&mdev->clear_work[channel].ws);
|
||||||
|
|
||||||
mutex_lock(&mdev->io_mutex);
|
mutex_lock(&mdev->io_mutex);
|
||||||
free_anchored_buffers(mdev, channel, MBO_E_CLOSE);
|
free_anchored_buffers(mdev, channel, MBO_E_CLOSE);
|
||||||
if (mdev->padding_active[channel])
|
if (mdev->padding_active[channel])
|
||||||
|
@ -409,9 +418,8 @@ static void hdm_write_completion(struct urb *urb)
|
||||||
dev_warn(dev, "Broken OUT pipe detected\n");
|
dev_warn(dev, "Broken OUT pipe detected\n");
|
||||||
most_stop_enqueue(&mdev->iface, channel);
|
most_stop_enqueue(&mdev->iface, channel);
|
||||||
mbo->status = MBO_E_INVAL;
|
mbo->status = MBO_E_INVAL;
|
||||||
usb_unlink_urb(urb);
|
mdev->clear_work[channel].pipe = urb->pipe;
|
||||||
INIT_WORK(&anchor->clear_work_obj, wq_clear_halt);
|
schedule_work(&mdev->clear_work[channel].ws);
|
||||||
schedule_work(&anchor->clear_work_obj);
|
|
||||||
return;
|
return;
|
||||||
case -ENODEV:
|
case -ENODEV:
|
||||||
case -EPROTO:
|
case -EPROTO:
|
||||||
|
@ -573,9 +581,8 @@ static void hdm_read_completion(struct urb *urb)
|
||||||
case -EPIPE:
|
case -EPIPE:
|
||||||
dev_warn(dev, "Broken IN pipe detected\n");
|
dev_warn(dev, "Broken IN pipe detected\n");
|
||||||
mbo->status = MBO_E_INVAL;
|
mbo->status = MBO_E_INVAL;
|
||||||
usb_unlink_urb(urb);
|
mdev->clear_work[channel].pipe = urb->pipe;
|
||||||
INIT_WORK(&anchor->clear_work_obj, wq_clear_halt);
|
schedule_work(&mdev->clear_work[channel].ws);
|
||||||
schedule_work(&anchor->clear_work_obj);
|
|
||||||
return;
|
return;
|
||||||
case -ENODEV:
|
case -ENODEV:
|
||||||
case -EPROTO:
|
case -EPROTO:
|
||||||
|
@ -735,6 +742,9 @@ static int hdm_configure_channel(struct most_interface *iface, int channel,
|
||||||
|
|
||||||
mdev = to_mdev(iface);
|
mdev = to_mdev(iface);
|
||||||
mdev->is_channel_healthy[channel] = true;
|
mdev->is_channel_healthy[channel] = true;
|
||||||
|
mdev->clear_work[channel].channel = channel;
|
||||||
|
mdev->clear_work[channel].mdev = mdev;
|
||||||
|
INIT_WORK(&mdev->clear_work[channel].ws, wq_clear_halt);
|
||||||
dev = &mdev->usb_device->dev;
|
dev = &mdev->usb_device->dev;
|
||||||
|
|
||||||
if (unlikely(!iface || !conf)) {
|
if (unlikely(!iface || !conf)) {
|
||||||
|
@ -916,33 +926,19 @@ static void wq_netinfo(struct work_struct *wq_obj)
|
||||||
*/
|
*/
|
||||||
static void wq_clear_halt(struct work_struct *wq_obj)
|
static void wq_clear_halt(struct work_struct *wq_obj)
|
||||||
{
|
{
|
||||||
struct buf_anchor *anchor;
|
struct clear_hold_work *clear_work = to_clear_hold_work(wq_obj);
|
||||||
struct most_dev *mdev;
|
struct most_dev *mdev = clear_work->mdev;
|
||||||
struct mbo *mbo;
|
unsigned int channel = clear_work->channel;
|
||||||
struct urb *urb;
|
int pipe = clear_work->pipe;
|
||||||
unsigned int channel;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
anchor = to_buf_anchor(wq_obj);
|
mutex_lock(&mdev->io_mutex);
|
||||||
urb = anchor->urb;
|
free_anchored_buffers(mdev, channel, MBO_E_INVAL);
|
||||||
mbo = urb->context;
|
if (usb_clear_halt(mdev->usb_device, pipe))
|
||||||
mdev = to_mdev(mbo->ifp);
|
|
||||||
channel = mbo->hdm_channel_id;
|
|
||||||
|
|
||||||
if (usb_clear_halt(urb->dev, urb->pipe))
|
|
||||||
dev_warn(&mdev->usb_device->dev, "Failed to reset endpoint.\n");
|
dev_warn(&mdev->usb_device->dev, "Failed to reset endpoint.\n");
|
||||||
|
|
||||||
usb_free_urb(urb);
|
|
||||||
spin_lock_irqsave(&mdev->anchor_list_lock[channel], flags);
|
|
||||||
list_del(&anchor->list);
|
|
||||||
spin_unlock_irqrestore(&mdev->anchor_list_lock[channel], flags);
|
|
||||||
|
|
||||||
if (likely(mbo->complete))
|
|
||||||
mbo->complete(mbo);
|
|
||||||
if (mdev->conf[channel].direction & MOST_CH_TX)
|
if (mdev->conf[channel].direction & MOST_CH_TX)
|
||||||
most_resume_enqueue(&mdev->iface, channel);
|
most_resume_enqueue(&mdev->iface, channel);
|
||||||
|
mutex_unlock(&mdev->io_mutex);
|
||||||
kfree(anchor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue