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