Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0+ |
05a1f28e TH |
2 | /* |
3 | * Copyright (C) 2003-2008 Takahiro Hirofuchi | |
bb7871ad | 4 | * Copyright (C) 2015 Nobuo Iwata |
05a1f28e TH |
5 | */ |
6 | ||
b8868e45 | 7 | #include <linux/kthread.h> |
8e336a72 | 8 | #include <linux/export.h> |
bb7871ad NI |
9 | #include <linux/slab.h> |
10 | #include <linux/workqueue.h> | |
7aaacb43 | 11 | |
0f79847c | 12 | #include "usbip_common.h" |
05a1f28e | 13 | |
bb7871ad NI |
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) | |
05a1f28e | 23 | { |
bb7871ad | 24 | unsigned long flags; |
05a1f28e | 25 | |
bb7871ad NI |
26 | spin_lock_irqsave(&ud->lock, flags); |
27 | ud->event |= event; | |
28 | spin_unlock_irqrestore(&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(&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(&event_list)) { | |
48 | ue = list_first_entry(&event_list, struct usbip_event, node); | |
49 | list_del(&ue->node); | |
50 | } | |
51 | spin_unlock_irqrestore(&event_lock, flags); | |
52 | ||
53 | if (ue) { | |
54 | ud = ue->ud; | |
55 | kfree(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) { | |
b8868e45 | 71 | usbip_dbg_eh("pending event %lx\n", ud->event); |
05a1f28e TH |
72 | |
73 | /* | |
74 | * NOTE: shutdown must come first. | |
75 | * Shutdown the device. | |
76 | */ | |
77 | if (ud->event & USBIP_EH_SHUTDOWN) { | |
78 | ud->eh_ops.shutdown(ud); | |
bb7871ad | 79 | unset_event(ud, USBIP_EH_SHUTDOWN); |
05a1f28e TH |
80 | } |
81 | ||
05a1f28e TH |
82 | /* Reset the device. */ |
83 | if (ud->event & USBIP_EH_RESET) { | |
84 | ud->eh_ops.reset(ud); | |
bb7871ad | 85 | unset_event(ud, USBIP_EH_RESET); |
05a1f28e TH |
86 | } |
87 | ||
88 | /* Mark the device as unusable. */ | |
89 | if (ud->event & USBIP_EH_UNUSABLE) { | |
90 | ud->eh_ops.unusable(ud); | |
bb7871ad | 91 | unset_event(ud, USBIP_EH_UNUSABLE); |
05a1f28e TH |
92 | } |
93 | ||
bb7871ad | 94 | wake_up(&ud->eh_waitq); |
05a1f28e | 95 | } |
bb7871ad | 96 | } |
05a1f28e | 97 | |
bb7871ad NI |
98 | int usbip_start_eh(struct usbip_device *ud) |
99 | { | |
100 | init_waitqueue_head(&ud->eh_waitq); | |
101 | ud->event = 0; | |
05a1f28e TH |
102 | return 0; |
103 | } | |
bb7871ad | 104 | EXPORT_SYMBOL_GPL(usbip_start_eh); |
05a1f28e | 105 | |
bb7871ad | 106 | void usbip_stop_eh(struct usbip_device *ud) |
05a1f28e | 107 | { |
bb7871ad | 108 | unsigned long pending = ud->event & ~USBIP_EH_BYE; |
05a1f28e | 109 | |
bb7871ad NI |
110 | if (!(ud->event & USBIP_EH_BYE)) |
111 | usbip_dbg_eh("usbip_eh stopping but not removed\n"); | |
05a1f28e | 112 | |
bb7871ad NI |
113 | if (pending) |
114 | usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending); | |
0f79847c | 115 | |
bb7871ad NI |
116 | wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE)); |
117 | usbip_dbg_eh("usbip_eh has stopped\n"); | |
05a1f28e | 118 | } |
bb7871ad | 119 | EXPORT_SYMBOL_GPL(usbip_stop_eh); |
05a1f28e | 120 | |
bb7871ad | 121 | #define WORK_QUEUE_NAME "usbip_event" |
05a1f28e | 122 | |
bb7871ad NI |
123 | static struct workqueue_struct *usbip_queue; |
124 | static DECLARE_WORK(usbip_work, event_handler); | |
0f79847c | 125 | |
bb7871ad NI |
126 | int usbip_init_eh(void) |
127 | { | |
128 | usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME); | |
129 | if (usbip_queue == NULL) { | |
130 | pr_err("failed to create usbip_event\n"); | |
131 | return -ENOMEM; | |
132 | } | |
b8868e45 | 133 | return 0; |
05a1f28e | 134 | } |
05a1f28e | 135 | |
bb7871ad | 136 | void usbip_finish_eh(void) |
05a1f28e | 137 | { |
bb7871ad NI |
138 | flush_workqueue(usbip_queue); |
139 | destroy_workqueue(usbip_queue); | |
140 | usbip_queue = NULL; | |
05a1f28e | 141 | } |
05a1f28e TH |
142 | |
143 | void usbip_event_add(struct usbip_device *ud, unsigned long event) | |
144 | { | |
bb7871ad | 145 | struct usbip_event *ue; |
dcf14779 HY |
146 | unsigned long flags; |
147 | ||
bb7871ad NI |
148 | if (ud->event & USBIP_EH_BYE) |
149 | return; | |
150 | ||
151 | set_event(ud, event); | |
152 | ||
153 | spin_lock_irqsave(&event_lock, flags); | |
154 | ||
155 | list_for_each_entry_reverse(ue, &event_list, node) { | |
156 | if (ue->ud == ud) | |
157 | goto out; | |
158 | } | |
159 | ||
160 | ue = kmalloc(sizeof(struct usbip_event), GFP_ATOMIC); | |
161 | if (ue == NULL) | |
162 | goto out; | |
163 | ||
164 | ue->ud = ud; | |
165 | ||
166 | list_add_tail(&ue->node, &event_list); | |
167 | queue_work(usbip_queue, &usbip_work); | |
168 | ||
169 | out: | |
170 | spin_unlock_irqrestore(&event_lock, flags); | |
05a1f28e TH |
171 | } |
172 | EXPORT_SYMBOL_GPL(usbip_event_add); | |
173 | ||
b8868e45 | 174 | int usbip_event_happened(struct usbip_device *ud) |
05a1f28e | 175 | { |
b8868e45 | 176 | int happened = 0; |
21619792 | 177 | unsigned long flags; |
05a1f28e | 178 | |
21619792 | 179 | spin_lock_irqsave(&ud->lock, flags); |
05a1f28e | 180 | if (ud->event != 0) |
b8868e45 | 181 | happened = 1; |
21619792 | 182 | spin_unlock_irqrestore(&ud->lock, flags); |
05a1f28e | 183 | |
b8868e45 | 184 | return happened; |
05a1f28e | 185 | } |
b8868e45 | 186 | EXPORT_SYMBOL_GPL(usbip_event_happened); |
bb7871ad NI |
187 | |
188 | int usbip_in_eh(struct task_struct *task) | |
189 | { | |
190 | if (task == worker_context) | |
191 | return 1; | |
192 | ||
193 | return 0; | |
194 | } | |
195 | EXPORT_SYMBOL_GPL(usbip_in_eh); |