Commit | Line | Data |
---|---|---|
b3147863 CK |
1 | /* ----------------------------------------------------------------------------- |
2 | * Copyright (c) 2011 Ozmo Inc | |
3 | * Released under the GNU General Public License Version 2 (GPLv2). | |
4 | * | |
5 | * This file implements the protocol specific parts of the USB service for a PD. | |
6 | * ----------------------------------------------------------------------------- | |
7 | */ | |
8 | #include <linux/init.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/timer.h> | |
11 | #include <linux/sched.h> | |
12 | #include <linux/netdevice.h> | |
13 | #include <linux/errno.h> | |
14 | #include <linux/input.h> | |
15 | #include <asm/unaligned.h> | |
16 | #include "ozconfig.h" | |
17 | #include "ozprotocol.h" | |
18 | #include "ozeltbuf.h" | |
19 | #include "ozpd.h" | |
20 | #include "ozproto.h" | |
21 | #include "ozusbif.h" | |
22 | #include "ozhcd.h" | |
23 | #include "oztrace.h" | |
b3147863 CK |
24 | #include "ozusbsvc.h" |
25 | #include "ozevent.h" | |
26 | /*------------------------------------------------------------------------------ | |
27 | */ | |
28 | #define MAX_ISOC_FIXED_DATA (253-sizeof(struct oz_isoc_fixed)) | |
29 | /*------------------------------------------------------------------------------ | |
30 | * Context: softirq | |
31 | */ | |
32 | static int oz_usb_submit_elt(struct oz_elt_buf *eb, struct oz_elt_info *ei, | |
33 | struct oz_usb_ctx *usb_ctx, u8 strid, u8 isoc) | |
34 | { | |
35 | int ret; | |
36 | struct oz_elt *elt = (struct oz_elt *)ei->data; | |
37 | struct oz_app_hdr *app_hdr = (struct oz_app_hdr *)(elt+1); | |
38 | elt->type = OZ_ELT_APP_DATA; | |
39 | ei->app_id = OZ_APPID_USB; | |
40 | ei->length = elt->length + sizeof(struct oz_elt); | |
41 | app_hdr->app_id = OZ_APPID_USB; | |
42 | spin_lock_bh(&eb->lock); | |
43 | if (isoc == 0) { | |
44 | app_hdr->elt_seq_num = usb_ctx->tx_seq_num++; | |
45 | if (usb_ctx->tx_seq_num == 0) | |
46 | usb_ctx->tx_seq_num = 1; | |
47 | } | |
48 | ret = oz_queue_elt_info(eb, isoc, strid, ei); | |
49 | if (ret) | |
50 | oz_elt_info_free(eb, ei); | |
51 | spin_unlock_bh(&eb->lock); | |
52 | return ret; | |
53 | } | |
54 | /*------------------------------------------------------------------------------ | |
55 | * Context: softirq | |
56 | */ | |
57 | int oz_usb_get_desc_req(void *hpd, u8 req_id, u8 req_type, u8 desc_type, | |
58 | u8 index, u16 windex, int offset, int len) | |
59 | { | |
60 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
61 | struct oz_pd *pd = usb_ctx->pd; | |
62 | struct oz_elt *elt; | |
63 | struct oz_get_desc_req *body; | |
64 | struct oz_elt_buf *eb = &pd->elt_buff; | |
65 | struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff); | |
66 | oz_trace(" req_type = 0x%x\n", req_type); | |
67 | oz_trace(" desc_type = 0x%x\n", desc_type); | |
68 | oz_trace(" index = 0x%x\n", index); | |
69 | oz_trace(" windex = 0x%x\n", windex); | |
70 | oz_trace(" offset = 0x%x\n", offset); | |
71 | oz_trace(" len = 0x%x\n", len); | |
72 | if (len > 200) | |
73 | len = 200; | |
4d1b2fbb | 74 | if (ei == NULL) |
b3147863 CK |
75 | return -1; |
76 | elt = (struct oz_elt *)ei->data; | |
77 | elt->length = sizeof(struct oz_get_desc_req); | |
78 | body = (struct oz_get_desc_req *)(elt+1); | |
79 | body->type = OZ_GET_DESC_REQ; | |
80 | body->req_id = req_id; | |
81 | put_unaligned(cpu_to_le16(offset), &body->offset); | |
82 | put_unaligned(cpu_to_le16(len), &body->size); | |
83 | body->req_type = req_type; | |
84 | body->desc_type = desc_type; | |
85 | body->w_index = windex; | |
86 | body->index = index; | |
87 | return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0); | |
88 | } | |
89 | /*------------------------------------------------------------------------------ | |
90 | * Context: tasklet | |
91 | */ | |
92 | static int oz_usb_set_config_req(void *hpd, u8 req_id, u8 index) | |
93 | { | |
94 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
95 | struct oz_pd *pd = usb_ctx->pd; | |
96 | struct oz_elt *elt; | |
97 | struct oz_elt_buf *eb = &pd->elt_buff; | |
98 | struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff); | |
99 | struct oz_set_config_req *body; | |
4d1b2fbb | 100 | if (ei == NULL) |
b3147863 CK |
101 | return -1; |
102 | elt = (struct oz_elt *)ei->data; | |
103 | elt->length = sizeof(struct oz_set_config_req); | |
104 | body = (struct oz_set_config_req *)(elt+1); | |
105 | body->type = OZ_SET_CONFIG_REQ; | |
106 | body->req_id = req_id; | |
107 | body->index = index; | |
108 | return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0); | |
109 | } | |
110 | /*------------------------------------------------------------------------------ | |
111 | * Context: tasklet | |
112 | */ | |
113 | static int oz_usb_set_interface_req(void *hpd, u8 req_id, u8 index, u8 alt) | |
114 | { | |
115 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
116 | struct oz_pd *pd = usb_ctx->pd; | |
117 | struct oz_elt *elt; | |
118 | struct oz_elt_buf *eb = &pd->elt_buff; | |
119 | struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff); | |
120 | struct oz_set_interface_req *body; | |
4d1b2fbb | 121 | if (ei == NULL) |
b3147863 CK |
122 | return -1; |
123 | elt = (struct oz_elt *)ei->data; | |
124 | elt->length = sizeof(struct oz_set_interface_req); | |
125 | body = (struct oz_set_interface_req *)(elt+1); | |
126 | body->type = OZ_SET_INTERFACE_REQ; | |
127 | body->req_id = req_id; | |
128 | body->index = index; | |
129 | body->alternative = alt; | |
130 | return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0); | |
131 | } | |
132 | /*------------------------------------------------------------------------------ | |
133 | * Context: tasklet | |
134 | */ | |
135 | static int oz_usb_set_clear_feature_req(void *hpd, u8 req_id, u8 type, | |
136 | u8 recipient, u8 index, __le16 feature) | |
137 | { | |
138 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
139 | struct oz_pd *pd = usb_ctx->pd; | |
140 | struct oz_elt *elt; | |
141 | struct oz_elt_buf *eb = &pd->elt_buff; | |
142 | struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff); | |
143 | struct oz_feature_req *body; | |
4d1b2fbb | 144 | if (ei == NULL) |
b3147863 CK |
145 | return -1; |
146 | elt = (struct oz_elt *)ei->data; | |
147 | elt->length = sizeof(struct oz_feature_req); | |
148 | body = (struct oz_feature_req *)(elt+1); | |
149 | body->type = type; | |
150 | body->req_id = req_id; | |
151 | body->recipient = recipient; | |
152 | body->index = index; | |
153 | put_unaligned(feature, &body->feature); | |
154 | return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0); | |
155 | } | |
156 | /*------------------------------------------------------------------------------ | |
157 | * Context: tasklet | |
158 | */ | |
159 | static int oz_usb_vendor_class_req(void *hpd, u8 req_id, u8 req_type, | |
dc7f5b35 | 160 | u8 request, __le16 value, __le16 index, const u8 *data, int data_len) |
b3147863 CK |
161 | { |
162 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
163 | struct oz_pd *pd = usb_ctx->pd; | |
164 | struct oz_elt *elt; | |
165 | struct oz_elt_buf *eb = &pd->elt_buff; | |
166 | struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff); | |
167 | struct oz_vendor_class_req *body; | |
4d1b2fbb | 168 | if (ei == NULL) |
b3147863 CK |
169 | return -1; |
170 | elt = (struct oz_elt *)ei->data; | |
171 | elt->length = sizeof(struct oz_vendor_class_req) - 1 + data_len; | |
172 | body = (struct oz_vendor_class_req *)(elt+1); | |
173 | body->type = OZ_VENDOR_CLASS_REQ; | |
174 | body->req_id = req_id; | |
175 | body->req_type = req_type; | |
176 | body->request = request; | |
177 | put_unaligned(value, &body->value); | |
178 | put_unaligned(index, &body->index); | |
179 | if (data_len) | |
180 | memcpy(body->data, data, data_len); | |
181 | return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0); | |
182 | } | |
183 | /*------------------------------------------------------------------------------ | |
184 | * Context: tasklet | |
185 | */ | |
186 | int oz_usb_control_req(void *hpd, u8 req_id, struct usb_ctrlrequest *setup, | |
dc7f5b35 | 187 | const u8 *data, int data_len) |
b3147863 CK |
188 | { |
189 | unsigned wvalue = le16_to_cpu(setup->wValue); | |
190 | unsigned windex = le16_to_cpu(setup->wIndex); | |
191 | unsigned wlength = le16_to_cpu(setup->wLength); | |
192 | int rc = 0; | |
193 | oz_event_log(OZ_EVT_CTRL_REQ, setup->bRequest, req_id, | |
194 | (void *)(((unsigned long)(setup->wValue))<<16 | | |
195 | ((unsigned long)setup->wIndex)), | |
196 | setup->bRequestType); | |
197 | if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { | |
198 | switch (setup->bRequest) { | |
199 | case USB_REQ_GET_DESCRIPTOR: | |
200 | rc = oz_usb_get_desc_req(hpd, req_id, | |
201 | setup->bRequestType, (u8)(wvalue>>8), | |
202 | (u8)wvalue, setup->wIndex, 0, wlength); | |
203 | break; | |
204 | case USB_REQ_SET_CONFIGURATION: | |
205 | rc = oz_usb_set_config_req(hpd, req_id, (u8)wvalue); | |
206 | break; | |
207 | case USB_REQ_SET_INTERFACE: { | |
208 | u8 if_num = (u8)windex; | |
209 | u8 alt = (u8)wvalue; | |
210 | rc = oz_usb_set_interface_req(hpd, req_id, | |
211 | if_num, alt); | |
212 | } | |
213 | break; | |
214 | case USB_REQ_SET_FEATURE: | |
215 | rc = oz_usb_set_clear_feature_req(hpd, req_id, | |
216 | OZ_SET_FEATURE_REQ, | |
217 | setup->bRequestType & 0xf, (u8)windex, | |
218 | setup->wValue); | |
219 | break; | |
220 | case USB_REQ_CLEAR_FEATURE: | |
221 | rc = oz_usb_set_clear_feature_req(hpd, req_id, | |
222 | OZ_CLEAR_FEATURE_REQ, | |
223 | setup->bRequestType & 0xf, | |
224 | (u8)windex, setup->wValue); | |
225 | break; | |
226 | } | |
227 | } else { | |
228 | rc = oz_usb_vendor_class_req(hpd, req_id, setup->bRequestType, | |
229 | setup->bRequest, setup->wValue, setup->wIndex, | |
230 | data, data_len); | |
231 | } | |
232 | return rc; | |
233 | } | |
234 | /*------------------------------------------------------------------------------ | |
235 | * Context: softirq | |
236 | */ | |
237 | int oz_usb_send_isoc(void *hpd, u8 ep_num, struct urb *urb) | |
238 | { | |
239 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
240 | struct oz_pd *pd = usb_ctx->pd; | |
241 | struct oz_elt_buf *eb; | |
242 | int i; | |
243 | int hdr_size; | |
244 | u8 *data; | |
245 | struct usb_iso_packet_descriptor *desc; | |
246 | ||
247 | if (pd->mode & OZ_F_ISOC_NO_ELTS) { | |
248 | for (i = 0; i < urb->number_of_packets; i++) { | |
249 | u8 *data; | |
250 | desc = &urb->iso_frame_desc[i]; | |
251 | data = ((u8 *)urb->transfer_buffer)+desc->offset; | |
252 | oz_send_isoc_unit(pd, ep_num, data, desc->length); | |
253 | } | |
254 | return 0; | |
255 | } | |
256 | ||
257 | hdr_size = sizeof(struct oz_isoc_fixed) - 1; | |
258 | eb = &pd->elt_buff; | |
259 | i = 0; | |
260 | while (i < urb->number_of_packets) { | |
261 | struct oz_elt_info *ei = oz_elt_info_alloc(eb); | |
262 | struct oz_elt *elt; | |
263 | struct oz_isoc_fixed *body; | |
264 | int unit_count; | |
265 | int unit_size; | |
266 | int rem; | |
4d1b2fbb | 267 | if (ei == NULL) |
b3147863 CK |
268 | return -1; |
269 | rem = MAX_ISOC_FIXED_DATA; | |
270 | elt = (struct oz_elt *)ei->data; | |
271 | body = (struct oz_isoc_fixed *)(elt + 1); | |
272 | body->type = OZ_USB_ENDPOINT_DATA; | |
273 | body->endpoint = ep_num; | |
274 | body->format = OZ_DATA_F_ISOC_FIXED; | |
275 | unit_size = urb->iso_frame_desc[i].length; | |
276 | body->unit_size = (u8)unit_size; | |
277 | data = ((u8 *)(elt+1)) + hdr_size; | |
278 | unit_count = 0; | |
279 | while (i < urb->number_of_packets) { | |
280 | desc = &urb->iso_frame_desc[i]; | |
281 | if ((unit_size == desc->length) && | |
282 | (desc->length <= rem)) { | |
283 | memcpy(data, ((u8 *)urb->transfer_buffer) + | |
284 | desc->offset, unit_size); | |
285 | data += unit_size; | |
286 | rem -= unit_size; | |
287 | unit_count++; | |
288 | desc->status = 0; | |
289 | desc->actual_length = desc->length; | |
290 | i++; | |
291 | } else { | |
292 | break; | |
293 | } | |
294 | } | |
295 | elt->length = hdr_size + MAX_ISOC_FIXED_DATA - rem; | |
296 | /* Store the number of units in body->frame_number for the | |
297 | * moment. This field will be correctly determined before | |
298 | * the element is sent. */ | |
299 | body->frame_number = (u8)unit_count; | |
300 | oz_usb_submit_elt(eb, ei, usb_ctx, ep_num, | |
301 | pd->mode & OZ_F_ISOC_ANYTIME); | |
302 | } | |
303 | return 0; | |
304 | } | |
305 | /*------------------------------------------------------------------------------ | |
306 | * Context: softirq-serialized | |
307 | */ | |
a7f74c30 | 308 | static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx, |
b3147863 CK |
309 | struct oz_usb_hdr *usb_hdr, int len) |
310 | { | |
311 | struct oz_data *data_hdr = (struct oz_data *)usb_hdr; | |
312 | switch (data_hdr->format) { | |
313 | case OZ_DATA_F_MULTIPLE_FIXED: { | |
314 | struct oz_multiple_fixed *body = | |
315 | (struct oz_multiple_fixed *)data_hdr; | |
316 | u8 *data = body->data; | |
1c9daa06 JD |
317 | unsigned int n; |
318 | if (!body->unit_size || | |
319 | len < sizeof(struct oz_multiple_fixed) - 1) | |
8ca9ab66 | 320 | break; |
1c9daa06 | 321 | n = (len - (sizeof(struct oz_multiple_fixed) - 1)) |
b3147863 CK |
322 | / body->unit_size; |
323 | while (n--) { | |
324 | oz_hcd_data_ind(usb_ctx->hport, body->endpoint, | |
325 | data, body->unit_size); | |
326 | data += body->unit_size; | |
327 | } | |
328 | } | |
329 | break; | |
330 | case OZ_DATA_F_ISOC_FIXED: { | |
331 | struct oz_isoc_fixed *body = | |
332 | (struct oz_isoc_fixed *)data_hdr; | |
333 | int data_len = len-sizeof(struct oz_isoc_fixed)+1; | |
334 | int unit_size = body->unit_size; | |
335 | u8 *data = body->data; | |
336 | int count; | |
337 | int i; | |
338 | if (!unit_size) | |
339 | break; | |
340 | count = data_len/unit_size; | |
341 | for (i = 0; i < count; i++) { | |
342 | oz_hcd_data_ind(usb_ctx->hport, | |
343 | body->endpoint, data, unit_size); | |
344 | data += unit_size; | |
345 | } | |
346 | } | |
347 | break; | |
348 | } | |
349 | ||
350 | } | |
351 | /*------------------------------------------------------------------------------ | |
352 | * This is called when the PD has received a USB element. The type of element | |
353 | * is determined and is then passed to an appropriate handler function. | |
354 | * Context: softirq-serialized | |
355 | */ | |
356 | void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) | |
357 | { | |
358 | struct oz_usb_hdr *usb_hdr = (struct oz_usb_hdr *)(elt + 1); | |
359 | struct oz_usb_ctx *usb_ctx; | |
360 | ||
361 | spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
362 | usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1]; | |
363 | if (usb_ctx) | |
364 | oz_usb_get(usb_ctx); | |
365 | spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
4d1b2fbb | 366 | if (usb_ctx == NULL) |
b3147863 CK |
367 | return; /* Context has gone so nothing to do. */ |
368 | if (usb_ctx->stopped) | |
369 | goto done; | |
370 | /* If sequence number is non-zero then check it is not a duplicate. | |
371 | * Zero sequence numbers are always accepted. | |
372 | */ | |
373 | if (usb_hdr->elt_seq_num != 0) { | |
374 | if (((usb_ctx->rx_seq_num - usb_hdr->elt_seq_num) & 0x80) == 0) | |
375 | /* Reject duplicate element. */ | |
376 | goto done; | |
377 | } | |
378 | usb_ctx->rx_seq_num = usb_hdr->elt_seq_num; | |
379 | switch (usb_hdr->type) { | |
380 | case OZ_GET_DESC_RSP: { | |
381 | struct oz_get_desc_rsp *body = | |
382 | (struct oz_get_desc_rsp *)usb_hdr; | |
1804b143 JD |
383 | u16 offs, total_size; |
384 | u8 data_len; | |
385 | ||
386 | if (elt->length < sizeof(struct oz_get_desc_rsp) - 1) | |
387 | break; | |
388 | data_len = elt->length - | |
389 | (sizeof(struct oz_get_desc_rsp) - 1); | |
390 | offs = le16_to_cpu(get_unaligned(&body->offset)); | |
391 | total_size = | |
b3147863 CK |
392 | le16_to_cpu(get_unaligned(&body->total_size)); |
393 | oz_trace("USB_REQ_GET_DESCRIPTOR - cnf\n"); | |
394 | oz_hcd_get_desc_cnf(usb_ctx->hport, body->req_id, | |
395 | body->rcode, body->data, | |
396 | data_len, offs, total_size); | |
397 | } | |
398 | break; | |
399 | case OZ_SET_CONFIG_RSP: { | |
400 | struct oz_set_config_rsp *body = | |
401 | (struct oz_set_config_rsp *)usb_hdr; | |
402 | oz_hcd_control_cnf(usb_ctx->hport, body->req_id, | |
4d1b2fbb | 403 | body->rcode, NULL, 0); |
b3147863 CK |
404 | } |
405 | break; | |
406 | case OZ_SET_INTERFACE_RSP: { | |
407 | struct oz_set_interface_rsp *body = | |
408 | (struct oz_set_interface_rsp *)usb_hdr; | |
409 | oz_hcd_control_cnf(usb_ctx->hport, | |
4d1b2fbb | 410 | body->req_id, body->rcode, NULL, 0); |
b3147863 CK |
411 | } |
412 | break; | |
413 | case OZ_VENDOR_CLASS_RSP: { | |
414 | struct oz_vendor_class_rsp *body = | |
415 | (struct oz_vendor_class_rsp *)usb_hdr; | |
416 | oz_hcd_control_cnf(usb_ctx->hport, body->req_id, | |
417 | body->rcode, body->data, elt->length- | |
418 | sizeof(struct oz_vendor_class_rsp)+1); | |
419 | } | |
420 | break; | |
421 | case OZ_USB_ENDPOINT_DATA: | |
422 | oz_usb_handle_ep_data(usb_ctx, usb_hdr, elt->length); | |
423 | break; | |
424 | } | |
425 | done: | |
426 | oz_usb_put(usb_ctx); | |
427 | } | |
428 | /*------------------------------------------------------------------------------ | |
429 | * Context: softirq, process | |
430 | */ | |
431 | void oz_usb_farewell(struct oz_pd *pd, u8 ep_num, u8 *data, u8 len) | |
432 | { | |
433 | struct oz_usb_ctx *usb_ctx; | |
434 | spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
435 | usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1]; | |
436 | if (usb_ctx) | |
437 | oz_usb_get(usb_ctx); | |
438 | spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
4d1b2fbb | 439 | if (usb_ctx == NULL) |
b3147863 CK |
440 | return; /* Context has gone so nothing to do. */ |
441 | if (!usb_ctx->stopped) { | |
442 | oz_trace("Farewell indicated ep = 0x%x\n", ep_num); | |
443 | oz_hcd_data_ind(usb_ctx->hport, ep_num, data, len); | |
444 | } | |
445 | oz_usb_put(usb_ctx); | |
446 | } |