linux/drivers/usb/usbip/usbip_event.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2003-2008 Takahiro Hirofuchi
   3 * Copyright (C) 2015 Nobuo Iwata
   4 *
   5 * This is free software; you can redistribute it and/or modify
   6 * it under the terms of the GNU General Public License as published by
   7 * the Free Software Foundation; either version 2 of the License, or
   8 * (at your option) any later version.
   9 *
  10 * This is distributed in the hope that it will be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 *
  15 * You should have received a copy of the GNU General Public License
  16 * along with this program; if not, write to the Free Software
  17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  18 * USA.
  19 */
  20
  21#include <linux/kthread.h>
  22#include <linux/export.h>
  23#include <linux/slab.h>
  24#include <linux/workqueue.h>
  25
  26#include "usbip_common.h"
  27
  28struct usbip_event {
  29        struct list_head node;
  30        struct usbip_device *ud;
  31};
  32
  33static DEFINE_SPINLOCK(event_lock);
  34static LIST_HEAD(event_list);
  35
  36static void set_event(struct usbip_device *ud, unsigned long event)
  37{
  38        unsigned long flags;
  39
  40        spin_lock_irqsave(&ud->lock, flags);
  41        ud->event |= event;
  42        spin_unlock_irqrestore(&ud->lock, flags);
  43}
  44
  45static void unset_event(struct usbip_device *ud, unsigned long event)
  46{
  47        unsigned long flags;
  48
  49        spin_lock_irqsave(&ud->lock, flags);
  50        ud->event &= ~event;
  51        spin_unlock_irqrestore(&ud->lock, flags);
  52}
  53
  54static struct usbip_device *get_event(void)
  55{
  56        struct usbip_event *ue = NULL;
  57        struct usbip_device *ud = NULL;
  58        unsigned long flags;
  59
  60        spin_lock_irqsave(&event_lock, flags);
  61        if (!list_empty(&event_list)) {
  62                ue = list_first_entry(&event_list, struct usbip_event, node);
  63                list_del(&ue->node);
  64        }
  65        spin_unlock_irqrestore(&event_lock, flags);
  66
  67        if (ue) {
  68                ud = ue->ud;
  69                kfree(ue);
  70        }
  71        return ud;
  72}
  73
  74static struct task_struct *worker_context;
  75
  76static void event_handler(struct work_struct *work)
  77{
  78        struct usbip_device *ud;
  79
  80        if (worker_context == NULL) {
  81                worker_context = current;
  82        }
  83
  84        while ((ud = get_event()) != NULL) {
  85                usbip_dbg_eh("pending event %lx\n", ud->event);
  86
  87                /*
  88                 * NOTE: shutdown must come first.
  89                 * Shutdown the device.
  90                 */
  91                if (ud->event & USBIP_EH_SHUTDOWN) {
  92                        ud->eh_ops.shutdown(ud);
  93                        unset_event(ud, USBIP_EH_SHUTDOWN);
  94                }
  95
  96                /* Reset the device. */
  97                if (ud->event & USBIP_EH_RESET) {
  98                        ud->eh_ops.reset(ud);
  99                        unset_event(ud, USBIP_EH_RESET);
 100                }
 101
 102                /* Mark the device as unusable. */
 103                if (ud->event & USBIP_EH_UNUSABLE) {
 104                        ud->eh_ops.unusable(ud);
 105                        unset_event(ud, USBIP_EH_UNUSABLE);
 106                }
 107
 108                /* Stop the error handler. */
 109                if (ud->event & USBIP_EH_BYE)
 110                        usbip_dbg_eh("removed %p\n", ud);
 111
 112                wake_up(&ud->eh_waitq);
 113        }
 114}
 115
 116int usbip_start_eh(struct usbip_device *ud)
 117{
 118        init_waitqueue_head(&ud->eh_waitq);
 119        ud->event = 0;
 120        return 0;
 121}
 122EXPORT_SYMBOL_GPL(usbip_start_eh);
 123
 124void usbip_stop_eh(struct usbip_device *ud)
 125{
 126        unsigned long pending = ud->event & ~USBIP_EH_BYE;
 127
 128        if (!(ud->event & USBIP_EH_BYE))
 129                usbip_dbg_eh("usbip_eh stopping but not removed\n");
 130
 131        if (pending)
 132                usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending);
 133
 134        wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE));
 135        usbip_dbg_eh("usbip_eh has stopped\n");
 136}
 137EXPORT_SYMBOL_GPL(usbip_stop_eh);
 138
 139#define WORK_QUEUE_NAME "usbip_event"
 140
 141static struct workqueue_struct *usbip_queue;
 142static DECLARE_WORK(usbip_work, event_handler);
 143
 144int usbip_init_eh(void)
 145{
 146        usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME);
 147        if (usbip_queue == NULL) {
 148                pr_err("failed to create usbip_event\n");
 149                return -ENOMEM;
 150        }
 151        return 0;
 152}
 153
 154void usbip_finish_eh(void)
 155{
 156        flush_workqueue(usbip_queue);
 157        destroy_workqueue(usbip_queue);
 158        usbip_queue = NULL;
 159}
 160
 161void usbip_event_add(struct usbip_device *ud, unsigned long event)
 162{
 163        struct usbip_event *ue;
 164        unsigned long flags;
 165
 166        if (ud->event & USBIP_EH_BYE)
 167                return;
 168
 169        set_event(ud, event);
 170
 171        spin_lock_irqsave(&event_lock, flags);
 172
 173        list_for_each_entry_reverse(ue, &event_list, node) {
 174                if (ue->ud == ud)
 175                        goto out;
 176        }
 177
 178        ue = kmalloc(sizeof(struct usbip_event), GFP_ATOMIC);
 179        if (ue == NULL)
 180                goto out;
 181
 182        ue->ud = ud;
 183
 184        list_add_tail(&ue->node, &event_list);
 185        queue_work(usbip_queue, &usbip_work);
 186
 187out:
 188        spin_unlock_irqrestore(&event_lock, flags);
 189}
 190EXPORT_SYMBOL_GPL(usbip_event_add);
 191
 192int usbip_event_happened(struct usbip_device *ud)
 193{
 194        int happened = 0;
 195        unsigned long flags;
 196
 197        spin_lock_irqsave(&ud->lock, flags);
 198        if (ud->event != 0)
 199                happened = 1;
 200        spin_unlock_irqrestore(&ud->lock, flags);
 201
 202        return happened;
 203}
 204EXPORT_SYMBOL_GPL(usbip_event_happened);
 205
 206int usbip_in_eh(struct task_struct *task)
 207{
 208        if (task == worker_context)
 209                return 1;
 210
 211        return 0;
 212}
 213EXPORT_SYMBOL_GPL(usbip_in_eh);
 214