]>
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 CK |
58 | { |
59 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
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 | { | |
95 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
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 | { | |
118 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
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 | { | |
143 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
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 CK |
168 | { |
169 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
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; | |
216 | rc = oz_usb_set_interface_req(hpd, req_id, | |
217 | if_num, alt); | |
218 | } | |
219 | break; | |
220 | case USB_REQ_SET_FEATURE: | |
221 | rc = oz_usb_set_clear_feature_req(hpd, req_id, | |
222 | OZ_SET_FEATURE_REQ, | |
223 | setup->bRequestType & 0xf, (u8)windex, | |
224 | setup->wValue); | |
225 | break; | |
226 | case USB_REQ_CLEAR_FEATURE: | |
227 | rc = oz_usb_set_clear_feature_req(hpd, req_id, | |
228 | OZ_CLEAR_FEATURE_REQ, | |
229 | setup->bRequestType & 0xf, | |
230 | (u8)windex, setup->wValue); | |
231 | break; | |
232 | } | |
233 | } else { | |
234 | rc = oz_usb_vendor_class_req(hpd, req_id, setup->bRequestType, | |
235 | setup->bRequest, setup->wValue, setup->wIndex, | |
236 | data, data_len); | |
237 | } | |
238 | return rc; | |
239 | } | |
6e244a83 | 240 | |
4e7fb829 | 241 | /* |
b3147863 CK |
242 | * Context: softirq |
243 | */ | |
244 | int oz_usb_send_isoc(void *hpd, u8 ep_num, struct urb *urb) | |
245 | { | |
246 | struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd; | |
247 | struct oz_pd *pd = usb_ctx->pd; | |
248 | struct oz_elt_buf *eb; | |
249 | int i; | |
250 | int hdr_size; | |
251 | u8 *data; | |
252 | struct usb_iso_packet_descriptor *desc; | |
253 | ||
254 | if (pd->mode & OZ_F_ISOC_NO_ELTS) { | |
255 | for (i = 0; i < urb->number_of_packets; i++) { | |
256 | u8 *data; | |
257 | desc = &urb->iso_frame_desc[i]; | |
258 | data = ((u8 *)urb->transfer_buffer)+desc->offset; | |
259 | oz_send_isoc_unit(pd, ep_num, data, desc->length); | |
260 | } | |
261 | return 0; | |
262 | } | |
263 | ||
264 | hdr_size = sizeof(struct oz_isoc_fixed) - 1; | |
265 | eb = &pd->elt_buff; | |
266 | i = 0; | |
267 | while (i < urb->number_of_packets) { | |
268 | struct oz_elt_info *ei = oz_elt_info_alloc(eb); | |
269 | struct oz_elt *elt; | |
270 | struct oz_isoc_fixed *body; | |
271 | int unit_count; | |
272 | int unit_size; | |
273 | int rem; | |
4d1b2fbb | 274 | if (ei == NULL) |
b3147863 CK |
275 | return -1; |
276 | rem = MAX_ISOC_FIXED_DATA; | |
277 | elt = (struct oz_elt *)ei->data; | |
278 | body = (struct oz_isoc_fixed *)(elt + 1); | |
279 | body->type = OZ_USB_ENDPOINT_DATA; | |
280 | body->endpoint = ep_num; | |
281 | body->format = OZ_DATA_F_ISOC_FIXED; | |
282 | unit_size = urb->iso_frame_desc[i].length; | |
283 | body->unit_size = (u8)unit_size; | |
284 | data = ((u8 *)(elt+1)) + hdr_size; | |
285 | unit_count = 0; | |
286 | while (i < urb->number_of_packets) { | |
287 | desc = &urb->iso_frame_desc[i]; | |
288 | if ((unit_size == desc->length) && | |
289 | (desc->length <= rem)) { | |
290 | memcpy(data, ((u8 *)urb->transfer_buffer) + | |
291 | desc->offset, unit_size); | |
292 | data += unit_size; | |
293 | rem -= unit_size; | |
294 | unit_count++; | |
295 | desc->status = 0; | |
296 | desc->actual_length = desc->length; | |
297 | i++; | |
298 | } else { | |
299 | break; | |
300 | } | |
301 | } | |
302 | elt->length = hdr_size + MAX_ISOC_FIXED_DATA - rem; | |
303 | /* Store the number of units in body->frame_number for the | |
304 | * moment. This field will be correctly determined before | |
305 | * the element is sent. */ | |
306 | body->frame_number = (u8)unit_count; | |
307 | oz_usb_submit_elt(eb, ei, usb_ctx, ep_num, | |
308 | pd->mode & OZ_F_ISOC_ANYTIME); | |
309 | } | |
310 | return 0; | |
311 | } | |
6e244a83 | 312 | |
4e7fb829 | 313 | /* |
b3147863 CK |
314 | * Context: softirq-serialized |
315 | */ | |
a7f74c30 | 316 | static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx, |
b3147863 CK |
317 | struct oz_usb_hdr *usb_hdr, int len) |
318 | { | |
319 | struct oz_data *data_hdr = (struct oz_data *)usb_hdr; | |
18f8191e | 320 | |
b3147863 CK |
321 | switch (data_hdr->format) { |
322 | case OZ_DATA_F_MULTIPLE_FIXED: { | |
323 | struct oz_multiple_fixed *body = | |
324 | (struct oz_multiple_fixed *)data_hdr; | |
325 | u8 *data = body->data; | |
326 | int n = (len - sizeof(struct oz_multiple_fixed)+1) | |
327 | / body->unit_size; | |
328 | while (n--) { | |
329 | oz_hcd_data_ind(usb_ctx->hport, body->endpoint, | |
330 | data, body->unit_size); | |
331 | data += body->unit_size; | |
332 | } | |
333 | } | |
334 | break; | |
335 | case OZ_DATA_F_ISOC_FIXED: { | |
336 | struct oz_isoc_fixed *body = | |
337 | (struct oz_isoc_fixed *)data_hdr; | |
338 | int data_len = len-sizeof(struct oz_isoc_fixed)+1; | |
339 | int unit_size = body->unit_size; | |
340 | u8 *data = body->data; | |
341 | int count; | |
342 | int i; | |
343 | if (!unit_size) | |
344 | break; | |
345 | count = data_len/unit_size; | |
346 | for (i = 0; i < count; i++) { | |
347 | oz_hcd_data_ind(usb_ctx->hport, | |
348 | body->endpoint, data, unit_size); | |
349 | data += unit_size; | |
350 | } | |
351 | } | |
352 | break; | |
353 | } | |
354 | ||
355 | } | |
6e244a83 | 356 | |
4e7fb829 | 357 | /* |
b3147863 CK |
358 | * This is called when the PD has received a USB element. The type of element |
359 | * is determined and is then passed to an appropriate handler function. | |
360 | * Context: softirq-serialized | |
361 | */ | |
362 | void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) | |
363 | { | |
364 | struct oz_usb_hdr *usb_hdr = (struct oz_usb_hdr *)(elt + 1); | |
365 | struct oz_usb_ctx *usb_ctx; | |
366 | ||
367 | spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
368 | usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1]; | |
369 | if (usb_ctx) | |
370 | oz_usb_get(usb_ctx); | |
371 | spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
4d1b2fbb | 372 | if (usb_ctx == NULL) |
b3147863 CK |
373 | return; /* Context has gone so nothing to do. */ |
374 | if (usb_ctx->stopped) | |
375 | goto done; | |
376 | /* If sequence number is non-zero then check it is not a duplicate. | |
377 | * Zero sequence numbers are always accepted. | |
378 | */ | |
379 | if (usb_hdr->elt_seq_num != 0) { | |
380 | if (((usb_ctx->rx_seq_num - usb_hdr->elt_seq_num) & 0x80) == 0) | |
381 | /* Reject duplicate element. */ | |
382 | goto done; | |
383 | } | |
384 | usb_ctx->rx_seq_num = usb_hdr->elt_seq_num; | |
385 | switch (usb_hdr->type) { | |
386 | case OZ_GET_DESC_RSP: { | |
387 | struct oz_get_desc_rsp *body = | |
388 | (struct oz_get_desc_rsp *)usb_hdr; | |
389 | int data_len = elt->length - | |
390 | sizeof(struct oz_get_desc_rsp) + 1; | |
391 | u16 offs = le16_to_cpu(get_unaligned(&body->offset)); | |
392 | u16 total_size = | |
393 | le16_to_cpu(get_unaligned(&body->total_size)); | |
f724b584 | 394 | oz_dbg(ON, "USB_REQ_GET_DESCRIPTOR - cnf\n"); |
b3147863 CK |
395 | oz_hcd_get_desc_cnf(usb_ctx->hport, body->req_id, |
396 | body->rcode, body->data, | |
397 | data_len, offs, total_size); | |
398 | } | |
399 | break; | |
400 | case OZ_SET_CONFIG_RSP: { | |
401 | struct oz_set_config_rsp *body = | |
402 | (struct oz_set_config_rsp *)usb_hdr; | |
403 | oz_hcd_control_cnf(usb_ctx->hport, body->req_id, | |
4d1b2fbb | 404 | body->rcode, NULL, 0); |
b3147863 CK |
405 | } |
406 | break; | |
407 | case OZ_SET_INTERFACE_RSP: { | |
408 | struct oz_set_interface_rsp *body = | |
409 | (struct oz_set_interface_rsp *)usb_hdr; | |
410 | oz_hcd_control_cnf(usb_ctx->hport, | |
4d1b2fbb | 411 | body->req_id, body->rcode, NULL, 0); |
b3147863 CK |
412 | } |
413 | break; | |
414 | case OZ_VENDOR_CLASS_RSP: { | |
415 | struct oz_vendor_class_rsp *body = | |
416 | (struct oz_vendor_class_rsp *)usb_hdr; | |
417 | oz_hcd_control_cnf(usb_ctx->hport, body->req_id, | |
418 | body->rcode, body->data, elt->length- | |
419 | sizeof(struct oz_vendor_class_rsp)+1); | |
420 | } | |
421 | break; | |
422 | case OZ_USB_ENDPOINT_DATA: | |
423 | oz_usb_handle_ep_data(usb_ctx, usb_hdr, elt->length); | |
424 | break; | |
425 | } | |
426 | done: | |
427 | oz_usb_put(usb_ctx); | |
428 | } | |
6e244a83 | 429 | |
4e7fb829 | 430 | /* |
b3147863 CK |
431 | * Context: softirq, process |
432 | */ | |
433 | void oz_usb_farewell(struct oz_pd *pd, u8 ep_num, u8 *data, u8 len) | |
434 | { | |
435 | struct oz_usb_ctx *usb_ctx; | |
18f8191e | 436 | |
b3147863 CK |
437 | spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]); |
438 | usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1]; | |
439 | if (usb_ctx) | |
440 | oz_usb_get(usb_ctx); | |
441 | spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]); | |
4d1b2fbb | 442 | if (usb_ctx == NULL) |
b3147863 CK |
443 | return; /* Context has gone so nothing to do. */ |
444 | if (!usb_ctx->stopped) { | |
f724b584 | 445 | oz_dbg(ON, "Farewell indicated ep = 0x%x\n", ep_num); |
b3147863 CK |
446 | oz_hcd_data_ind(usb_ctx->hport, ep_num, data, len); |
447 | } | |
448 | oz_usb_put(usb_ctx); | |
449 | } |