]>
Commit | Line | Data |
---|---|---|
f587027e GKH |
1 | /* |
2 | * Greybus "AP" USB driver for "ES2" controller chips | |
3 | * | |
142f8ddf AE |
4 | * Copyright 2014-2015 Google Inc. |
5 | * Copyright 2014-2015 Linaro Ltd. | |
f587027e GKH |
6 | * |
7 | * Released under the GPLv2 only. | |
8 | */ | |
ca3ec299 | 9 | #include <linux/kthread.h> |
f587027e GKH |
10 | #include <linux/sizes.h> |
11 | #include <linux/usb.h> | |
ca3ec299 GKH |
12 | #include <linux/kfifo.h> |
13 | #include <linux/debugfs.h> | |
491e60d6 | 14 | #include <asm/unaligned.h> |
f587027e GKH |
15 | |
16 | #include "greybus.h" | |
3eac885d | 17 | #include "svc_msg.h" |
f587027e GKH |
18 | #include "kernel_ver.h" |
19 | ||
f587027e | 20 | /* Memory sizes for the buffers sent to/from the ES1 controller */ |
3eac885d | 21 | #define ES1_SVC_MSG_SIZE (sizeof(struct svc_msg) + SZ_64K) |
cce31036 | 22 | #define ES1_GBUF_MSG_SIZE_MAX 2048 |
f587027e GKH |
23 | |
24 | static const struct usb_device_id id_table[] = { | |
2bf4c876 GKH |
25 | /* Made up numbers for the SVC USB Bridge in ES2 */ |
26 | { USB_DEVICE(0xffff, 0x0002) }, | |
f587027e GKH |
27 | { }, |
28 | }; | |
29 | MODULE_DEVICE_TABLE(usb, id_table); | |
30 | ||
ca3ec299 GKH |
31 | #define APB1_LOG_SIZE SZ_16K |
32 | static struct dentry *apb1_log_dentry; | |
33 | static struct dentry *apb1_log_enable_dentry; | |
34 | static struct task_struct *apb1_log_task; | |
35 | static DEFINE_KFIFO(apb1_log_fifo, char, APB1_LOG_SIZE); | |
36 | ||
fc1a536e AB |
37 | /* Number of cport present on USB bridge */ |
38 | #define CPORT_MAX 44 | |
39 | ||
606addd2 AB |
40 | /* Number of bulk in and bulk out couple */ |
41 | #define NUM_BULKS 7 | |
42 | ||
f587027e GKH |
43 | /* |
44 | * Number of CPort IN urbs in flight at any point in time. | |
45 | * Adjust if we are having stalls in the USB buffer due to not enough urbs in | |
46 | * flight. | |
47 | */ | |
48 | #define NUM_CPORT_IN_URB 4 | |
49 | ||
50 | /* Number of CPort OUT urbs in flight at any point in time. | |
51 | * Adjust if we get messages saying we are out of urbs in the system log. | |
52 | */ | |
606addd2 | 53 | #define NUM_CPORT_OUT_URB (8 * NUM_BULKS) |
f587027e | 54 | |
611c1739 AB |
55 | /* vendor request AP message */ |
56 | #define REQUEST_SVC 0x01 | |
57 | ||
58 | /* vendor request APB1 log */ | |
59 | #define REQUEST_LOG 0x02 | |
60 | ||
fc1a536e AB |
61 | /* vendor request to map a cport to bulk in and bulk out endpoints */ |
62 | #define REQUEST_EP_MAPPING 0x03 | |
63 | ||
ddc09acd AB |
64 | /* |
65 | * @endpoint: bulk in endpoint for CPort data | |
66 | * @urb: array of urbs for the CPort in messages | |
67 | * @buffer: array of buffers for the @cport_in_urb urbs | |
68 | */ | |
69 | struct es1_cport_in { | |
70 | __u8 endpoint; | |
71 | struct urb *urb[NUM_CPORT_IN_URB]; | |
72 | u8 *buffer[NUM_CPORT_IN_URB]; | |
73 | }; | |
74 | ||
75 | /* | |
76 | * @endpoint: bulk out endpoint for CPort data | |
77 | */ | |
78 | struct es1_cport_out { | |
79 | __u8 endpoint; | |
80 | }; | |
81 | ||
f587027e GKH |
82 | /** |
83 | * es1_ap_dev - ES1 USB Bridge to AP structure | |
84 | * @usb_dev: pointer to the USB device we are. | |
85 | * @usb_intf: pointer to the USB interface we are bound to. | |
86 | * @hd: pointer to our greybus_host_device structure | |
87 | * @control_endpoint: endpoint to send data to SVC | |
3eac885d | 88 | * @svc_endpoint: endpoint for SVC data in |
ddc09acd | 89 | |
3eac885d GKH |
90 | * @svc_buffer: buffer for SVC messages coming in on @svc_endpoint |
91 | * @svc_urb: urb for SVC messages coming in on @svc_endpoint | |
ddc09acd AB |
92 | * @cport_in: endpoint, urbs and buffer for cport in messages |
93 | * @cport_out: endpoint for for cport out messages | |
f587027e GKH |
94 | * @cport_out_urb: array of urbs for the CPort out messages |
95 | * @cport_out_urb_busy: array of flags to see if the @cport_out_urb is busy or | |
96 | * not. | |
3e136cc9 JH |
97 | * @cport_out_urb_cancelled: array of flags indicating whether the |
98 | * corresponding @cport_out_urb is being cancelled | |
f587027e GKH |
99 | * @cport_out_urb_lock: locks the @cport_out_urb_busy "list" |
100 | */ | |
101 | struct es1_ap_dev { | |
102 | struct usb_device *usb_dev; | |
103 | struct usb_interface *usb_intf; | |
104 | struct greybus_host_device *hd; | |
105 | ||
106 | __u8 control_endpoint; | |
3eac885d GKH |
107 | __u8 svc_endpoint; |
108 | ||
109 | u8 *svc_buffer; | |
110 | struct urb *svc_urb; | |
f587027e | 111 | |
606addd2 AB |
112 | struct es1_cport_in cport_in[NUM_BULKS]; |
113 | struct es1_cport_out cport_out[NUM_BULKS]; | |
f587027e GKH |
114 | struct urb *cport_out_urb[NUM_CPORT_OUT_URB]; |
115 | bool cport_out_urb_busy[NUM_CPORT_OUT_URB]; | |
3e136cc9 | 116 | bool cport_out_urb_cancelled[NUM_CPORT_OUT_URB]; |
f587027e | 117 | spinlock_t cport_out_urb_lock; |
fc1a536e AB |
118 | |
119 | int cport_to_ep[CPORT_MAX]; | |
120 | }; | |
121 | ||
122 | struct cport_to_ep { | |
123 | __le16 cport_id; | |
124 | __u8 endpoint_in; | |
125 | __u8 endpoint_out; | |
f587027e GKH |
126 | }; |
127 | ||
128 | static inline struct es1_ap_dev *hd_to_es1(struct greybus_host_device *hd) | |
129 | { | |
130 | return (struct es1_ap_dev *)&hd->hd_priv; | |
131 | } | |
132 | ||
133 | static void cport_out_callback(struct urb *urb); | |
ca3ec299 GKH |
134 | static void usb_log_enable(struct es1_ap_dev *es1); |
135 | static void usb_log_disable(struct es1_ap_dev *es1); | |
f587027e | 136 | |
fc1a536e AB |
137 | static int cport_to_ep(struct es1_ap_dev *es1, u16 cport_id) |
138 | { | |
139 | if (cport_id >= CPORT_MAX) | |
140 | return 0; | |
141 | return es1->cport_to_ep[cport_id]; | |
142 | } | |
143 | ||
f587027e | 144 | #define ES1_TIMEOUT 500 /* 500 ms for the SVC to do something */ |
3eac885d GKH |
145 | static int submit_svc(struct svc_msg *svc_msg, struct greybus_host_device *hd) |
146 | { | |
147 | struct es1_ap_dev *es1 = hd_to_es1(hd); | |
148 | int retval; | |
149 | ||
150 | /* SVC messages go down our control pipe */ | |
151 | retval = usb_control_msg(es1->usb_dev, | |
152 | usb_sndctrlpipe(es1->usb_dev, | |
153 | es1->control_endpoint), | |
154 | REQUEST_SVC, | |
155 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, | |
156 | 0x00, 0x00, | |
157 | (char *)svc_msg, | |
158 | sizeof(*svc_msg), | |
159 | ES1_TIMEOUT); | |
160 | if (retval != sizeof(*svc_msg)) | |
161 | return retval; | |
162 | ||
163 | return 0; | |
164 | } | |
f587027e | 165 | |
fc1a536e AB |
166 | static int ep_in_use(struct es1_ap_dev *es1, int bulk_ep_set) |
167 | { | |
168 | int i; | |
169 | ||
170 | for (i = 0; i < CPORT_MAX; i++) { | |
171 | if (es1->cport_to_ep[i] == bulk_ep_set) | |
172 | return 1; | |
173 | } | |
174 | return 0; | |
175 | } | |
176 | ||
177 | int map_cport_to_ep(struct es1_ap_dev *es1, | |
178 | u16 cport_id, int bulk_ep_set) | |
179 | { | |
180 | int retval; | |
181 | struct cport_to_ep *cport_to_ep; | |
182 | ||
183 | if (bulk_ep_set == 0 || bulk_ep_set >= NUM_BULKS) | |
184 | return -EINVAL; | |
185 | if (cport_id >= CPORT_MAX) | |
186 | return -EINVAL; | |
187 | if (bulk_ep_set && ep_in_use(es1, bulk_ep_set)) | |
188 | return -EINVAL; | |
189 | ||
190 | cport_to_ep = kmalloc(sizeof(*cport_to_ep), GFP_KERNEL); | |
191 | if (!cport_to_ep) | |
192 | return -ENOMEM; | |
193 | ||
194 | es1->cport_to_ep[cport_id] = bulk_ep_set; | |
195 | cport_to_ep->cport_id = cpu_to_le16(cport_id); | |
196 | cport_to_ep->endpoint_in = es1->cport_in[bulk_ep_set].endpoint; | |
197 | cport_to_ep->endpoint_out = es1->cport_out[bulk_ep_set].endpoint; | |
198 | ||
199 | retval = usb_control_msg(es1->usb_dev, | |
200 | usb_sndctrlpipe(es1->usb_dev, | |
201 | es1->control_endpoint), | |
202 | REQUEST_EP_MAPPING, | |
203 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, | |
204 | 0x00, 0x00, | |
205 | (char *)cport_to_ep, | |
206 | sizeof(*cport_to_ep), | |
207 | ES1_TIMEOUT); | |
208 | if (retval == sizeof(*cport_to_ep)) | |
209 | retval = 0; | |
210 | kfree(cport_to_ep); | |
211 | ||
212 | return retval; | |
213 | } | |
214 | ||
215 | int unmap_cport(struct es1_ap_dev *es1, u16 cport_id) | |
216 | { | |
217 | return map_cport_to_ep(es1, cport_id, 0); | |
218 | } | |
219 | ||
f587027e GKH |
220 | static struct urb *next_free_urb(struct es1_ap_dev *es1, gfp_t gfp_mask) |
221 | { | |
222 | struct urb *urb = NULL; | |
223 | unsigned long flags; | |
224 | int i; | |
225 | ||
226 | spin_lock_irqsave(&es1->cport_out_urb_lock, flags); | |
227 | ||
228 | /* Look in our pool of allocated urbs first, as that's the "fastest" */ | |
229 | for (i = 0; i < NUM_CPORT_OUT_URB; ++i) { | |
3e136cc9 JH |
230 | if (es1->cport_out_urb_busy[i] == false && |
231 | es1->cport_out_urb_cancelled[i] == false) { | |
f587027e GKH |
232 | es1->cport_out_urb_busy[i] = true; |
233 | urb = es1->cport_out_urb[i]; | |
234 | break; | |
235 | } | |
236 | } | |
237 | spin_unlock_irqrestore(&es1->cport_out_urb_lock, flags); | |
238 | if (urb) | |
239 | return urb; | |
240 | ||
241 | /* | |
242 | * Crap, pool is empty, complain to the syslog and go allocate one | |
243 | * dynamically as we have to succeed. | |
244 | */ | |
245 | dev_err(&es1->usb_dev->dev, | |
246 | "No free CPort OUT urbs, having to dynamically allocate one!\n"); | |
247 | return usb_alloc_urb(0, gfp_mask); | |
248 | } | |
249 | ||
250 | static void free_urb(struct es1_ap_dev *es1, struct urb *urb) | |
251 | { | |
252 | unsigned long flags; | |
253 | int i; | |
254 | /* | |
255 | * See if this was an urb in our pool, if so mark it "free", otherwise | |
256 | * we need to free it ourselves. | |
257 | */ | |
258 | spin_lock_irqsave(&es1->cport_out_urb_lock, flags); | |
259 | for (i = 0; i < NUM_CPORT_OUT_URB; ++i) { | |
260 | if (urb == es1->cport_out_urb[i]) { | |
261 | es1->cport_out_urb_busy[i] = false; | |
262 | urb = NULL; | |
263 | break; | |
264 | } | |
265 | } | |
266 | spin_unlock_irqrestore(&es1->cport_out_urb_lock, flags); | |
267 | ||
268 | /* If urb is not NULL, then we need to free this urb */ | |
269 | usb_free_urb(urb); | |
270 | } | |
271 | ||
d29b3d63 AE |
272 | /* |
273 | * We (ab)use the operation-message header pad bytes to transfer the | |
274 | * cport id in order to minimise overhead. | |
275 | */ | |
276 | static void | |
277 | gb_message_cport_pack(struct gb_operation_msg_hdr *header, u16 cport_id) | |
278 | { | |
4bc1389d | 279 | header->pad[0] = cport_id; |
d29b3d63 AE |
280 | } |
281 | ||
282 | /* Clear the pad bytes used for the CPort id */ | |
283 | static void gb_message_cport_clear(struct gb_operation_msg_hdr *header) | |
284 | { | |
4bc1389d | 285 | header->pad[0] = 0; |
d29b3d63 AE |
286 | } |
287 | ||
288 | /* Extract the CPort id packed into the header, and clear it */ | |
289 | static u16 gb_message_cport_unpack(struct gb_operation_msg_hdr *header) | |
290 | { | |
4bc1389d | 291 | u16 cport_id = header->pad[0]; |
d29b3d63 AE |
292 | |
293 | gb_message_cport_clear(header); | |
294 | ||
295 | return cport_id; | |
296 | } | |
297 | ||
f587027e | 298 | /* |
3e136cc9 JH |
299 | * Returns zero if the message was successfully queued, or a negative errno |
300 | * otherwise. | |
f587027e | 301 | */ |
3e136cc9 | 302 | static int message_send(struct greybus_host_device *hd, u16 cport_id, |
7cf7bca9 | 303 | struct gb_message *message, gfp_t gfp_mask) |
f587027e GKH |
304 | { |
305 | struct es1_ap_dev *es1 = hd_to_es1(hd); | |
306 | struct usb_device *udev = es1->usb_dev; | |
7cf7bca9 | 307 | size_t buffer_size; |
f587027e GKH |
308 | int retval; |
309 | struct urb *urb; | |
fc1a536e | 310 | int bulk_ep_set; |
3e136cc9 | 311 | unsigned long flags; |
f587027e | 312 | |
f587027e GKH |
313 | /* |
314 | * The data actually transferred will include an indication | |
315 | * of where the data should be sent. Do one last check of | |
316 | * the target CPort id before filling it in. | |
317 | */ | |
821c620a AE |
318 | if (!cport_id_valid(cport_id)) { |
319 | pr_err("invalid destination cport 0x%02x\n", cport_id); | |
3e136cc9 | 320 | return -EINVAL; |
f587027e | 321 | } |
f587027e GKH |
322 | |
323 | /* Find a free urb */ | |
324 | urb = next_free_urb(es1, gfp_mask); | |
325 | if (!urb) | |
3e136cc9 JH |
326 | return -ENOMEM; |
327 | ||
328 | spin_lock_irqsave(&es1->cport_out_urb_lock, flags); | |
329 | message->hcpriv = urb; | |
330 | spin_unlock_irqrestore(&es1->cport_out_urb_lock, flags); | |
f587027e | 331 | |
d29b3d63 AE |
332 | /* Pack the cport id into the message header */ |
333 | gb_message_cport_pack(message->header, cport_id); | |
491e60d6 | 334 | |
821c620a | 335 | buffer_size = sizeof(*message->header) + message->payload_size; |
491e60d6 | 336 | |
fc1a536e | 337 | bulk_ep_set = cport_to_ep(es1, cport_id); |
f587027e | 338 | usb_fill_bulk_urb(urb, udev, |
606addd2 AB |
339 | usb_sndbulkpipe(udev, |
340 | es1->cport_out[bulk_ep_set].endpoint), | |
821c620a | 341 | message->buffer, buffer_size, |
7cf7bca9 | 342 | cport_out_callback, message); |
f587027e GKH |
343 | retval = usb_submit_urb(urb, gfp_mask); |
344 | if (retval) { | |
345 | pr_err("error %d submitting URB\n", retval); | |
3e136cc9 JH |
346 | |
347 | spin_lock_irqsave(&es1->cport_out_urb_lock, flags); | |
348 | message->hcpriv = NULL; | |
349 | spin_unlock_irqrestore(&es1->cport_out_urb_lock, flags); | |
350 | ||
f587027e | 351 | free_urb(es1, urb); |
d29b3d63 | 352 | gb_message_cport_clear(message->header); |
3e136cc9 JH |
353 | |
354 | return retval; | |
f587027e GKH |
355 | } |
356 | ||
3e136cc9 | 357 | return 0; |
f587027e GKH |
358 | } |
359 | ||
360 | /* | |
3e136cc9 | 361 | * Can not be called in atomic context. |
f587027e | 362 | */ |
3e136cc9 | 363 | static void message_cancel(struct gb_message *message) |
f587027e | 364 | { |
3e136cc9 JH |
365 | struct greybus_host_device *hd = message->operation->connection->hd; |
366 | struct es1_ap_dev *es1 = hd_to_es1(hd); | |
367 | struct urb *urb; | |
368 | int i; | |
f587027e | 369 | |
3e136cc9 JH |
370 | might_sleep(); |
371 | ||
372 | spin_lock_irq(&es1->cport_out_urb_lock); | |
373 | urb = message->hcpriv; | |
374 | ||
375 | /* Prevent dynamically allocated urb from being deallocated. */ | |
376 | usb_get_urb(urb); | |
377 | ||
378 | /* Prevent pre-allocated urb from being reused. */ | |
379 | for (i = 0; i < NUM_CPORT_OUT_URB; ++i) { | |
380 | if (urb == es1->cport_out_urb[i]) { | |
381 | es1->cport_out_urb_cancelled[i] = true; | |
382 | break; | |
383 | } | |
384 | } | |
385 | spin_unlock_irq(&es1->cport_out_urb_lock); | |
386 | ||
387 | usb_kill_urb(urb); | |
388 | ||
389 | if (i < NUM_CPORT_OUT_URB) { | |
390 | spin_lock_irq(&es1->cport_out_urb_lock); | |
391 | es1->cport_out_urb_cancelled[i] = false; | |
392 | spin_unlock_irq(&es1->cport_out_urb_lock); | |
393 | } | |
394 | ||
395 | usb_free_urb(urb); | |
f587027e GKH |
396 | } |
397 | ||
398 | static struct greybus_host_driver es1_driver = { | |
399 | .hd_priv_size = sizeof(struct es1_ap_dev), | |
7cf7bca9 JH |
400 | .message_send = message_send, |
401 | .message_cancel = message_cancel, | |
3eac885d | 402 | .submit_svc = submit_svc, |
f587027e GKH |
403 | }; |
404 | ||
405 | /* Common function to report consistent warnings based on URB status */ | |
406 | static int check_urb_status(struct urb *urb) | |
407 | { | |
408 | struct device *dev = &urb->dev->dev; | |
409 | int status = urb->status; | |
410 | ||
411 | switch (status) { | |
412 | case 0: | |
413 | return 0; | |
414 | ||
415 | case -EOVERFLOW: | |
416 | dev_err(dev, "%s: overflow actual length is %d\n", | |
417 | __func__, urb->actual_length); | |
418 | case -ECONNRESET: | |
419 | case -ENOENT: | |
420 | case -ESHUTDOWN: | |
421 | case -EILSEQ: | |
422 | case -EPROTO: | |
423 | /* device is gone, stop sending */ | |
424 | return status; | |
425 | } | |
426 | dev_err(dev, "%s: unknown status %d\n", __func__, status); | |
427 | ||
428 | return -EAGAIN; | |
429 | } | |
430 | ||
431 | static void ap_disconnect(struct usb_interface *interface) | |
432 | { | |
433 | struct es1_ap_dev *es1; | |
434 | struct usb_device *udev; | |
606addd2 | 435 | int bulk_in; |
f587027e GKH |
436 | int i; |
437 | ||
438 | es1 = usb_get_intfdata(interface); | |
439 | if (!es1) | |
440 | return; | |
441 | ||
ca3ec299 GKH |
442 | usb_log_disable(es1); |
443 | ||
f587027e GKH |
444 | /* Tear down everything! */ |
445 | for (i = 0; i < NUM_CPORT_OUT_URB; ++i) { | |
446 | struct urb *urb = es1->cport_out_urb[i]; | |
447 | ||
448 | if (!urb) | |
449 | break; | |
450 | usb_kill_urb(urb); | |
451 | usb_free_urb(urb); | |
452 | es1->cport_out_urb[i] = NULL; | |
453 | es1->cport_out_urb_busy[i] = false; /* just to be anal */ | |
454 | } | |
455 | ||
606addd2 AB |
456 | for (bulk_in = 0; bulk_in < NUM_BULKS; bulk_in++) { |
457 | struct es1_cport_in *cport_in = &es1->cport_in[bulk_in]; | |
458 | for (i = 0; i < NUM_CPORT_IN_URB; ++i) { | |
459 | struct urb *urb = cport_in->urb[i]; | |
460 | ||
461 | if (!urb) | |
462 | break; | |
463 | usb_kill_urb(urb); | |
464 | usb_free_urb(urb); | |
465 | kfree(cport_in->buffer[i]); | |
466 | cport_in->buffer[i] = NULL; | |
467 | } | |
f587027e GKH |
468 | } |
469 | ||
3eac885d GKH |
470 | usb_kill_urb(es1->svc_urb); |
471 | usb_free_urb(es1->svc_urb); | |
472 | es1->svc_urb = NULL; | |
473 | kfree(es1->svc_buffer); | |
474 | es1->svc_buffer = NULL; | |
475 | ||
f587027e GKH |
476 | usb_set_intfdata(interface, NULL); |
477 | udev = es1->usb_dev; | |
478 | greybus_remove_hd(es1->hd); | |
479 | ||
480 | usb_put_dev(udev); | |
481 | } | |
482 | ||
3eac885d GKH |
483 | /* Callback for when we get a SVC message */ |
484 | static void svc_in_callback(struct urb *urb) | |
485 | { | |
486 | struct greybus_host_device *hd = urb->context; | |
487 | struct device *dev = &urb->dev->dev; | |
488 | int status = check_urb_status(urb); | |
489 | int retval; | |
490 | ||
491 | if (status) { | |
492 | if ((status == -EAGAIN) || (status == -EPROTO)) | |
493 | goto exit; | |
494 | dev_err(dev, "urb svc in error %d (dropped)\n", status); | |
495 | return; | |
496 | } | |
497 | ||
498 | /* We have a message, create a new message structure, add it to the | |
499 | * list, and wake up our thread that will process the messages. | |
500 | */ | |
501 | greybus_svc_in(hd, urb->transfer_buffer, urb->actual_length); | |
502 | ||
503 | exit: | |
504 | /* resubmit the urb to get more messages */ | |
505 | retval = usb_submit_urb(urb, GFP_ATOMIC); | |
506 | if (retval) | |
507 | dev_err(dev, "Can not submit urb for AP data: %d\n", retval); | |
508 | } | |
509 | ||
f587027e GKH |
510 | static void cport_in_callback(struct urb *urb) |
511 | { | |
512 | struct greybus_host_device *hd = urb->context; | |
513 | struct device *dev = &urb->dev->dev; | |
491e60d6 | 514 | struct gb_operation_msg_hdr *header; |
f587027e GKH |
515 | int status = check_urb_status(urb); |
516 | int retval; | |
517 | u16 cport_id; | |
f587027e GKH |
518 | |
519 | if (status) { | |
520 | if ((status == -EAGAIN) || (status == -EPROTO)) | |
521 | goto exit; | |
522 | dev_err(dev, "urb cport in error %d (dropped)\n", status); | |
523 | return; | |
524 | } | |
525 | ||
491e60d6 JH |
526 | if (urb->actual_length < sizeof(*header)) { |
527 | dev_err(dev, "%s: short message received\n", __func__); | |
f587027e GKH |
528 | goto exit; |
529 | } | |
530 | ||
d29b3d63 | 531 | /* Extract the CPort id, which is packed in the message header */ |
491e60d6 | 532 | header = urb->transfer_buffer; |
d29b3d63 | 533 | cport_id = gb_message_cport_unpack(header); |
f587027e | 534 | |
821c620a AE |
535 | if (cport_id_valid(cport_id)) |
536 | greybus_data_rcvd(hd, cport_id, urb->transfer_buffer, | |
491e60d6 | 537 | urb->actual_length); |
821c620a AE |
538 | else |
539 | dev_err(dev, "%s: invalid cport id 0x%02x received\n", | |
540 | __func__, cport_id); | |
f587027e GKH |
541 | exit: |
542 | /* put our urb back in the request pool */ | |
543 | retval = usb_submit_urb(urb, GFP_ATOMIC); | |
544 | if (retval) | |
545 | dev_err(dev, "%s: error %d in submitting urb.\n", | |
546 | __func__, retval); | |
547 | } | |
548 | ||
549 | static void cport_out_callback(struct urb *urb) | |
550 | { | |
7cf7bca9 JH |
551 | struct gb_message *message = urb->context; |
552 | struct greybus_host_device *hd = message->operation->connection->hd; | |
f587027e GKH |
553 | struct es1_ap_dev *es1 = hd_to_es1(hd); |
554 | int status = check_urb_status(urb); | |
3e136cc9 | 555 | unsigned long flags; |
f587027e | 556 | |
d29b3d63 | 557 | gb_message_cport_clear(message->header); |
491e60d6 | 558 | |
f587027e | 559 | /* |
7cf7bca9 JH |
560 | * Tell the submitter that the message send (attempt) is |
561 | * complete, and report the status. | |
f587027e | 562 | */ |
7cf7bca9 JH |
563 | greybus_message_sent(hd, message, status); |
564 | ||
3e136cc9 JH |
565 | spin_lock_irqsave(&es1->cport_out_urb_lock, flags); |
566 | message->hcpriv = NULL; | |
567 | spin_unlock_irqrestore(&es1->cport_out_urb_lock, flags); | |
568 | ||
f587027e | 569 | free_urb(es1, urb); |
f587027e GKH |
570 | } |
571 | ||
c15ccabe JH |
572 | #define APB1_LOG_MSG_SIZE 64 |
573 | static void apb1_log_get(struct es1_ap_dev *es1, char *buf) | |
ca3ec299 | 574 | { |
ca3ec299 GKH |
575 | int retval; |
576 | ||
577 | /* SVC messages go down our control pipe */ | |
578 | do { | |
ca3ec299 GKH |
579 | retval = usb_control_msg(es1->usb_dev, |
580 | usb_rcvctrlpipe(es1->usb_dev, | |
581 | es1->control_endpoint), | |
611c1739 | 582 | REQUEST_LOG, |
ca3ec299 GKH |
583 | USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, |
584 | 0x00, 0x00, | |
585 | buf, | |
c15ccabe | 586 | APB1_LOG_MSG_SIZE, |
ca3ec299 GKH |
587 | ES1_TIMEOUT); |
588 | if (retval > 0) | |
589 | kfifo_in(&apb1_log_fifo, buf, retval); | |
590 | } while (retval > 0); | |
591 | } | |
592 | ||
593 | static int apb1_log_poll(void *data) | |
594 | { | |
c15ccabe JH |
595 | struct es1_ap_dev *es1 = data; |
596 | char *buf; | |
597 | ||
598 | buf = kmalloc(APB1_LOG_MSG_SIZE, GFP_KERNEL); | |
599 | if (!buf) | |
600 | return -ENOMEM; | |
601 | ||
ca3ec299 GKH |
602 | while (!kthread_should_stop()) { |
603 | msleep(1000); | |
c15ccabe | 604 | apb1_log_get(es1, buf); |
ca3ec299 | 605 | } |
c15ccabe JH |
606 | |
607 | kfree(buf); | |
608 | ||
ca3ec299 GKH |
609 | return 0; |
610 | } | |
611 | ||
612 | static ssize_t apb1_log_read(struct file *f, char __user *buf, | |
613 | size_t count, loff_t *ppos) | |
614 | { | |
615 | ssize_t ret; | |
616 | size_t copied; | |
617 | char *tmp_buf; | |
618 | ||
619 | if (count > APB1_LOG_SIZE) | |
620 | count = APB1_LOG_SIZE; | |
621 | ||
622 | tmp_buf = kmalloc(count, GFP_KERNEL); | |
623 | if (!tmp_buf) | |
624 | return -ENOMEM; | |
625 | ||
626 | copied = kfifo_out(&apb1_log_fifo, tmp_buf, count); | |
627 | ret = simple_read_from_buffer(buf, count, ppos, tmp_buf, copied); | |
628 | ||
629 | kfree(tmp_buf); | |
630 | ||
631 | return ret; | |
632 | } | |
633 | ||
634 | static const struct file_operations apb1_log_fops = { | |
635 | .read = apb1_log_read, | |
636 | }; | |
637 | ||
638 | static void usb_log_enable(struct es1_ap_dev *es1) | |
639 | { | |
e0feaf14 | 640 | if (!IS_ERR_OR_NULL(apb1_log_task)) |
ca3ec299 GKH |
641 | return; |
642 | ||
643 | /* get log from APB1 */ | |
644 | apb1_log_task = kthread_run(apb1_log_poll, es1, "apb1_log"); | |
e0feaf14 | 645 | if (IS_ERR(apb1_log_task)) |
ca3ec299 GKH |
646 | return; |
647 | apb1_log_dentry = debugfs_create_file("apb1_log", S_IRUGO, | |
648 | gb_debugfs_get(), NULL, | |
649 | &apb1_log_fops); | |
650 | } | |
651 | ||
652 | static void usb_log_disable(struct es1_ap_dev *es1) | |
653 | { | |
e0feaf14 | 654 | if (IS_ERR_OR_NULL(apb1_log_task)) |
ca3ec299 GKH |
655 | return; |
656 | ||
657 | debugfs_remove(apb1_log_dentry); | |
658 | apb1_log_dentry = NULL; | |
659 | ||
660 | kthread_stop(apb1_log_task); | |
661 | apb1_log_task = NULL; | |
662 | } | |
663 | ||
664 | static ssize_t apb1_log_enable_read(struct file *f, char __user *buf, | |
665 | size_t count, loff_t *ppos) | |
666 | { | |
667 | char tmp_buf[3]; | |
e0feaf14 | 668 | int enable = !IS_ERR_OR_NULL(apb1_log_task); |
ca3ec299 GKH |
669 | |
670 | sprintf(tmp_buf, "%d\n", enable); | |
671 | return simple_read_from_buffer(buf, count, ppos, tmp_buf, 3); | |
672 | } | |
673 | ||
674 | static ssize_t apb1_log_enable_write(struct file *f, const char __user *buf, | |
675 | size_t count, loff_t *ppos) | |
676 | { | |
677 | int enable; | |
678 | ssize_t retval; | |
679 | struct es1_ap_dev *es1 = (struct es1_ap_dev *)f->f_inode->i_private; | |
680 | ||
681 | retval = kstrtoint_from_user(buf, count, 10, &enable); | |
682 | if (retval) | |
683 | return retval; | |
684 | ||
685 | if (enable) | |
686 | usb_log_enable(es1); | |
687 | else | |
688 | usb_log_disable(es1); | |
689 | ||
690 | return count; | |
691 | } | |
692 | ||
693 | static const struct file_operations apb1_log_enable_fops = { | |
694 | .read = apb1_log_enable_read, | |
695 | .write = apb1_log_enable_write, | |
696 | }; | |
697 | ||
f587027e GKH |
698 | /* |
699 | * The ES1 USB Bridge device contains 4 endpoints | |
700 | * 1 Control - usual USB stuff + AP -> SVC messages | |
701 | * 1 Interrupt IN - SVC -> AP messages | |
702 | * 1 Bulk IN - CPort data in | |
703 | * 1 Bulk OUT - CPort data out | |
704 | */ | |
705 | static int ap_probe(struct usb_interface *interface, | |
706 | const struct usb_device_id *id) | |
707 | { | |
708 | struct es1_ap_dev *es1; | |
709 | struct greybus_host_device *hd; | |
710 | struct usb_device *udev; | |
711 | struct usb_host_interface *iface_desc; | |
712 | struct usb_endpoint_descriptor *endpoint; | |
3eac885d | 713 | bool int_in_found = false; |
606addd2 AB |
714 | int bulk_in = 0; |
715 | int bulk_out = 0; | |
f587027e GKH |
716 | int retval = -ENOMEM; |
717 | int i; | |
3eac885d | 718 | u8 svc_interval = 0; |
f587027e | 719 | |
4bc1389d AE |
720 | /* We need to fit a CPort ID in one byte of a message header */ |
721 | BUILD_BUG_ON(CPORT_ID_MAX > U8_MAX); | |
722 | ||
f587027e GKH |
723 | udev = usb_get_dev(interface_to_usbdev(interface)); |
724 | ||
d933667a | 725 | hd = greybus_create_hd(&es1_driver, &udev->dev, ES1_GBUF_MSG_SIZE_MAX); |
8ea70fe0 | 726 | if (IS_ERR(hd)) { |
f587027e | 727 | usb_put_dev(udev); |
8ea70fe0 | 728 | return PTR_ERR(hd); |
f587027e GKH |
729 | } |
730 | ||
f587027e GKH |
731 | es1 = hd_to_es1(hd); |
732 | es1->hd = hd; | |
733 | es1->usb_intf = interface; | |
734 | es1->usb_dev = udev; | |
735 | spin_lock_init(&es1->cport_out_urb_lock); | |
736 | usb_set_intfdata(interface, es1); | |
737 | ||
738 | /* Control endpoint is the pipe to talk to this AP, so save it off */ | |
739 | endpoint = &udev->ep0.desc; | |
740 | es1->control_endpoint = endpoint->bEndpointAddress; | |
741 | ||
742 | /* find all 3 of our endpoints */ | |
743 | iface_desc = interface->cur_altsetting; | |
744 | for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { | |
745 | endpoint = &iface_desc->endpoint[i].desc; | |
746 | ||
3eac885d GKH |
747 | if (usb_endpoint_is_int_in(endpoint)) { |
748 | es1->svc_endpoint = endpoint->bEndpointAddress; | |
749 | svc_interval = endpoint->bInterval; | |
750 | int_in_found = true; | |
751 | } else if (usb_endpoint_is_bulk_in(endpoint)) { | |
606addd2 AB |
752 | es1->cport_in[bulk_in++].endpoint = |
753 | endpoint->bEndpointAddress; | |
f587027e | 754 | } else if (usb_endpoint_is_bulk_out(endpoint)) { |
606addd2 AB |
755 | es1->cport_out[bulk_out++].endpoint = |
756 | endpoint->bEndpointAddress; | |
f587027e GKH |
757 | } else { |
758 | dev_err(&udev->dev, | |
759 | "Unknown endpoint type found, address %x\n", | |
760 | endpoint->bEndpointAddress); | |
761 | } | |
762 | } | |
3eac885d GKH |
763 | if ((int_in_found == false) || |
764 | (bulk_in == 0) || | |
606addd2 | 765 | (bulk_out == 0)) { |
f587027e GKH |
766 | dev_err(&udev->dev, "Not enough endpoints found in device, aborting!\n"); |
767 | goto error; | |
768 | } | |
769 | ||
3eac885d GKH |
770 | /* Create our buffer and URB to get SVC messages, and start it up */ |
771 | es1->svc_buffer = kmalloc(ES1_SVC_MSG_SIZE, GFP_KERNEL); | |
772 | if (!es1->svc_buffer) | |
773 | goto error; | |
774 | ||
775 | es1->svc_urb = usb_alloc_urb(0, GFP_KERNEL); | |
776 | if (!es1->svc_urb) | |
777 | goto error; | |
778 | ||
779 | usb_fill_int_urb(es1->svc_urb, udev, | |
780 | usb_rcvintpipe(udev, es1->svc_endpoint), | |
781 | es1->svc_buffer, ES1_SVC_MSG_SIZE, svc_in_callback, | |
782 | hd, svc_interval); | |
783 | ||
f587027e | 784 | /* Allocate buffers for our cport in messages and start them up */ |
606addd2 AB |
785 | for (bulk_in = 0; bulk_in < NUM_BULKS; bulk_in++) { |
786 | struct es1_cport_in *cport_in = &es1->cport_in[bulk_in]; | |
787 | for (i = 0; i < NUM_CPORT_IN_URB; ++i) { | |
788 | struct urb *urb; | |
789 | u8 *buffer; | |
790 | ||
791 | urb = usb_alloc_urb(0, GFP_KERNEL); | |
792 | if (!urb) | |
793 | goto error; | |
794 | buffer = kmalloc(ES1_GBUF_MSG_SIZE_MAX, GFP_KERNEL); | |
795 | if (!buffer) | |
796 | goto error; | |
797 | ||
798 | usb_fill_bulk_urb(urb, udev, | |
799 | usb_rcvbulkpipe(udev, | |
800 | cport_in->endpoint), | |
801 | buffer, ES1_GBUF_MSG_SIZE_MAX, | |
802 | cport_in_callback, hd); | |
803 | cport_in->urb[i] = urb; | |
804 | cport_in->buffer[i] = buffer; | |
805 | retval = usb_submit_urb(urb, GFP_KERNEL); | |
806 | if (retval) | |
807 | goto error; | |
808 | } | |
f587027e GKH |
809 | } |
810 | ||
811 | /* Allocate urbs for our CPort OUT messages */ | |
812 | for (i = 0; i < NUM_CPORT_OUT_URB; ++i) { | |
813 | struct urb *urb; | |
814 | ||
815 | urb = usb_alloc_urb(0, GFP_KERNEL); | |
816 | if (!urb) | |
817 | goto error; | |
818 | ||
819 | es1->cport_out_urb[i] = urb; | |
820 | es1->cport_out_urb_busy[i] = false; /* just to be anal */ | |
821 | } | |
822 | ||
619dccd2 VK |
823 | /* Initialize AP's greybus interface */ |
824 | if (!gb_ap_svc_connection_create(hd)) { | |
825 | retval = -EINVAL; | |
3eac885d | 826 | goto error; |
619dccd2 | 827 | } |
3eac885d GKH |
828 | |
829 | /* Start up our svc urb, which allows events to start flowing */ | |
830 | retval = usb_submit_urb(es1->svc_urb, GFP_KERNEL); | |
831 | if (retval) | |
832 | goto error; | |
833 | ||
86f918ee JH |
834 | apb1_log_enable_dentry = debugfs_create_file("apb1_log_enable", |
835 | (S_IWUSR | S_IRUGO), | |
836 | gb_debugfs_get(), es1, | |
837 | &apb1_log_enable_fops); | |
f587027e GKH |
838 | return 0; |
839 | error: | |
840 | ap_disconnect(interface); | |
841 | ||
842 | return retval; | |
843 | } | |
844 | ||
845 | static struct usb_driver es1_ap_driver = { | |
c13c8bf0 | 846 | .name = "es2_ap_driver", |
f587027e GKH |
847 | .probe = ap_probe, |
848 | .disconnect = ap_disconnect, | |
849 | .id_table = id_table, | |
850 | }; | |
851 | ||
852 | module_usb_driver(es1_ap_driver); | |
853 | ||
6cf42a44 | 854 | MODULE_LICENSE("GPL v2"); |
f587027e | 855 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>"); |