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