1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (C) 2003-2008 Takahiro Hirofuchi
4*4882a593Smuzhiyun * Copyright (C) 2015 Nobuo Iwata
5*4882a593Smuzhiyun */
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun #include <linux/kthread.h>
8*4882a593Smuzhiyun #include <linux/export.h>
9*4882a593Smuzhiyun #include <linux/slab.h>
10*4882a593Smuzhiyun #include <linux/workqueue.h>
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun #include "usbip_common.h"
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun struct usbip_event {
15*4882a593Smuzhiyun struct list_head node;
16*4882a593Smuzhiyun struct usbip_device *ud;
17*4882a593Smuzhiyun };
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun static DEFINE_SPINLOCK(event_lock);
20*4882a593Smuzhiyun static LIST_HEAD(event_list);
21*4882a593Smuzhiyun
set_event(struct usbip_device * ud,unsigned long event)22*4882a593Smuzhiyun static void set_event(struct usbip_device *ud, unsigned long event)
23*4882a593Smuzhiyun {
24*4882a593Smuzhiyun unsigned long flags;
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun spin_lock_irqsave(&ud->lock, flags);
27*4882a593Smuzhiyun ud->event |= event;
28*4882a593Smuzhiyun spin_unlock_irqrestore(&ud->lock, flags);
29*4882a593Smuzhiyun }
30*4882a593Smuzhiyun
unset_event(struct usbip_device * ud,unsigned long event)31*4882a593Smuzhiyun static void unset_event(struct usbip_device *ud, unsigned long event)
32*4882a593Smuzhiyun {
33*4882a593Smuzhiyun unsigned long flags;
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun spin_lock_irqsave(&ud->lock, flags);
36*4882a593Smuzhiyun ud->event &= ~event;
37*4882a593Smuzhiyun spin_unlock_irqrestore(&ud->lock, flags);
38*4882a593Smuzhiyun }
39*4882a593Smuzhiyun
get_event(void)40*4882a593Smuzhiyun static struct usbip_device *get_event(void)
41*4882a593Smuzhiyun {
42*4882a593Smuzhiyun struct usbip_event *ue = NULL;
43*4882a593Smuzhiyun struct usbip_device *ud = NULL;
44*4882a593Smuzhiyun unsigned long flags;
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun spin_lock_irqsave(&event_lock, flags);
47*4882a593Smuzhiyun if (!list_empty(&event_list)) {
48*4882a593Smuzhiyun ue = list_first_entry(&event_list, struct usbip_event, node);
49*4882a593Smuzhiyun list_del(&ue->node);
50*4882a593Smuzhiyun }
51*4882a593Smuzhiyun spin_unlock_irqrestore(&event_lock, flags);
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun if (ue) {
54*4882a593Smuzhiyun ud = ue->ud;
55*4882a593Smuzhiyun kfree(ue);
56*4882a593Smuzhiyun }
57*4882a593Smuzhiyun return ud;
58*4882a593Smuzhiyun }
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun static struct task_struct *worker_context;
61*4882a593Smuzhiyun
event_handler(struct work_struct * work)62*4882a593Smuzhiyun static void event_handler(struct work_struct *work)
63*4882a593Smuzhiyun {
64*4882a593Smuzhiyun struct usbip_device *ud;
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun if (worker_context == NULL) {
67*4882a593Smuzhiyun worker_context = current;
68*4882a593Smuzhiyun }
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun while ((ud = get_event()) != NULL) {
71*4882a593Smuzhiyun usbip_dbg_eh("pending event %lx\n", ud->event);
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun mutex_lock(&ud->sysfs_lock);
74*4882a593Smuzhiyun /*
75*4882a593Smuzhiyun * NOTE: shutdown must come first.
76*4882a593Smuzhiyun * Shutdown the device.
77*4882a593Smuzhiyun */
78*4882a593Smuzhiyun if (ud->event & USBIP_EH_SHUTDOWN) {
79*4882a593Smuzhiyun ud->eh_ops.shutdown(ud);
80*4882a593Smuzhiyun unset_event(ud, USBIP_EH_SHUTDOWN);
81*4882a593Smuzhiyun }
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun /* Reset the device. */
84*4882a593Smuzhiyun if (ud->event & USBIP_EH_RESET) {
85*4882a593Smuzhiyun ud->eh_ops.reset(ud);
86*4882a593Smuzhiyun unset_event(ud, USBIP_EH_RESET);
87*4882a593Smuzhiyun }
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun /* Mark the device as unusable. */
90*4882a593Smuzhiyun if (ud->event & USBIP_EH_UNUSABLE) {
91*4882a593Smuzhiyun ud->eh_ops.unusable(ud);
92*4882a593Smuzhiyun unset_event(ud, USBIP_EH_UNUSABLE);
93*4882a593Smuzhiyun }
94*4882a593Smuzhiyun mutex_unlock(&ud->sysfs_lock);
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun wake_up(&ud->eh_waitq);
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun }
99*4882a593Smuzhiyun
usbip_start_eh(struct usbip_device * ud)100*4882a593Smuzhiyun int usbip_start_eh(struct usbip_device *ud)
101*4882a593Smuzhiyun {
102*4882a593Smuzhiyun init_waitqueue_head(&ud->eh_waitq);
103*4882a593Smuzhiyun ud->event = 0;
104*4882a593Smuzhiyun return 0;
105*4882a593Smuzhiyun }
106*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(usbip_start_eh);
107*4882a593Smuzhiyun
usbip_stop_eh(struct usbip_device * ud)108*4882a593Smuzhiyun void usbip_stop_eh(struct usbip_device *ud)
109*4882a593Smuzhiyun {
110*4882a593Smuzhiyun unsigned long pending = ud->event & ~USBIP_EH_BYE;
111*4882a593Smuzhiyun
112*4882a593Smuzhiyun if (!(ud->event & USBIP_EH_BYE))
113*4882a593Smuzhiyun usbip_dbg_eh("usbip_eh stopping but not removed\n");
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun if (pending)
116*4882a593Smuzhiyun usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending);
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE));
119*4882a593Smuzhiyun usbip_dbg_eh("usbip_eh has stopped\n");
120*4882a593Smuzhiyun }
121*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(usbip_stop_eh);
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun #define WORK_QUEUE_NAME "usbip_event"
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun static struct workqueue_struct *usbip_queue;
126*4882a593Smuzhiyun static DECLARE_WORK(usbip_work, event_handler);
127*4882a593Smuzhiyun
usbip_init_eh(void)128*4882a593Smuzhiyun int usbip_init_eh(void)
129*4882a593Smuzhiyun {
130*4882a593Smuzhiyun usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME);
131*4882a593Smuzhiyun if (usbip_queue == NULL) {
132*4882a593Smuzhiyun pr_err("failed to create usbip_event\n");
133*4882a593Smuzhiyun return -ENOMEM;
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun return 0;
136*4882a593Smuzhiyun }
137*4882a593Smuzhiyun
usbip_finish_eh(void)138*4882a593Smuzhiyun void usbip_finish_eh(void)
139*4882a593Smuzhiyun {
140*4882a593Smuzhiyun flush_workqueue(usbip_queue);
141*4882a593Smuzhiyun destroy_workqueue(usbip_queue);
142*4882a593Smuzhiyun usbip_queue = NULL;
143*4882a593Smuzhiyun }
144*4882a593Smuzhiyun
usbip_event_add(struct usbip_device * ud,unsigned long event)145*4882a593Smuzhiyun void usbip_event_add(struct usbip_device *ud, unsigned long event)
146*4882a593Smuzhiyun {
147*4882a593Smuzhiyun struct usbip_event *ue;
148*4882a593Smuzhiyun unsigned long flags;
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun if (ud->event & USBIP_EH_BYE)
151*4882a593Smuzhiyun return;
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun set_event(ud, event);
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun spin_lock_irqsave(&event_lock, flags);
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun list_for_each_entry_reverse(ue, &event_list, node) {
158*4882a593Smuzhiyun if (ue->ud == ud)
159*4882a593Smuzhiyun goto out;
160*4882a593Smuzhiyun }
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun ue = kmalloc(sizeof(struct usbip_event), GFP_ATOMIC);
163*4882a593Smuzhiyun if (ue == NULL)
164*4882a593Smuzhiyun goto out;
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun ue->ud = ud;
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun list_add_tail(&ue->node, &event_list);
169*4882a593Smuzhiyun queue_work(usbip_queue, &usbip_work);
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun out:
172*4882a593Smuzhiyun spin_unlock_irqrestore(&event_lock, flags);
173*4882a593Smuzhiyun }
174*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(usbip_event_add);
175*4882a593Smuzhiyun
usbip_event_happened(struct usbip_device * ud)176*4882a593Smuzhiyun int usbip_event_happened(struct usbip_device *ud)
177*4882a593Smuzhiyun {
178*4882a593Smuzhiyun int happened = 0;
179*4882a593Smuzhiyun unsigned long flags;
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun spin_lock_irqsave(&ud->lock, flags);
182*4882a593Smuzhiyun if (ud->event != 0)
183*4882a593Smuzhiyun happened = 1;
184*4882a593Smuzhiyun spin_unlock_irqrestore(&ud->lock, flags);
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun return happened;
187*4882a593Smuzhiyun }
188*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(usbip_event_happened);
189*4882a593Smuzhiyun
usbip_in_eh(struct task_struct * task)190*4882a593Smuzhiyun int usbip_in_eh(struct task_struct *task)
191*4882a593Smuzhiyun {
192*4882a593Smuzhiyun if (task == worker_context)
193*4882a593Smuzhiyun return 1;
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun return 0;
196*4882a593Smuzhiyun }
197*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(usbip_in_eh);
198