Linux-libre 4.7-rc7-gnu
[librecmc/linux-libre.git] / drivers / usb / usbip / usbip_event.c
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
28 struct usbip_event {
29         struct list_head node;
30         struct usbip_device *ud;
31 };
32
33 static DEFINE_SPINLOCK(event_lock);
34 static LIST_HEAD(event_list);
35
36 static 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
45 static 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
54 static 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
74 static struct task_struct *worker_context;
75
76 static 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
116 int usbip_start_eh(struct usbip_device *ud)
117 {
118         init_waitqueue_head(&ud->eh_waitq);
119         ud->event = 0;
120         return 0;
121 }
122 EXPORT_SYMBOL_GPL(usbip_start_eh);
123
124 void 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 }
137 EXPORT_SYMBOL_GPL(usbip_stop_eh);
138
139 #define WORK_QUEUE_NAME "usbip_event"
140
141 static struct workqueue_struct *usbip_queue;
142 static DECLARE_WORK(usbip_work, event_handler);
143
144 int 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
154 void usbip_finish_eh(void)
155 {
156         flush_workqueue(usbip_queue);
157         destroy_workqueue(usbip_queue);
158         usbip_queue = NULL;
159 }
160
161 void 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
187 out:
188         spin_unlock_irqrestore(&event_lock, flags);
189 }
190 EXPORT_SYMBOL_GPL(usbip_event_add);
191
192 int 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 }
204 EXPORT_SYMBOL_GPL(usbip_event_happened);
205
206 int usbip_in_eh(struct task_struct *task)
207 {
208         if (task == worker_context)
209                 return 1;
210
211         return 0;
212 }
213 EXPORT_SYMBOL_GPL(usbip_in_eh);