blob: 1776c94fb2101a5fe0e7a5ea5a2a7d822a8e40a8 [file] [log] [blame]
Harald Weltea67be5f2020-09-03 10:04:36 +02001/* USB Gadget simulating a USB hub over FunctionFS
2 * - this was used to play with limitations of the Linux kernel hub driver
3 *
4 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
19 */
Harald Weltea7c1eb02019-09-12 15:16:43 +020020
21#include <errno.h>
22#include <stdint.h>
23#include <endian.h>
24#include <signal.h>
25#include <sys/types.h>
26#include <linux/usb/functionfs.h>
27#include <linux/usb/ch11.h>
28
29#if __BYTE_ORDER == __LITTLE_ENDIAN
30#define cpu_to_le16(x) (x)
31#define cpu_to_le32(x) (x)
32#else
33#define cpu_to_le16(x) ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8))
34#define cpu_to_le32(x) \
35 ((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \
36 (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24))
37#endif
38
39#define le32_to_cpu(x) le32toh(x)
40#define le16_to_cpu(x) le16toh(x)
41
42enum {
43 DUSB,
44};
45
46/***********************************************************************
47 * Actual USB Descriptors
48 ***********************************************************************/
49
50static const struct {
51 struct usb_functionfs_descs_head_v2 header;
52 __le32 fs_count;
53 struct {
54 struct usb_interface_descriptor intf;
55 struct usb_endpoint_descriptor_no_audio ep_int;
56 } __attribute__ ((packed)) fs_descs;
57} __attribute__ ((packed)) descriptors = {
58 .header = {
59 .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),
60 .flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_ALL_CTRL_RECIP),
61 .length = cpu_to_le32(sizeof(descriptors)),
62 },
63 .fs_count = cpu_to_le32(2),
64 .fs_descs = {
65 .intf = {
66 .bLength = sizeof(descriptors.fs_descs.intf),
67 .bDescriptorType = USB_DT_INTERFACE,
68 .bNumEndpoints = 1,
69 .bInterfaceClass = USB_CLASS_HUB,
70 .iInterface = 1,
71 },
72 .ep_int = {
73 .bLength = sizeof(descriptors.fs_descs.ep_int),
74 .bDescriptorType = USB_DT_ENDPOINT,
75 .bEndpointAddress = 1 | USB_DIR_IN,
76 .bmAttributes = USB_ENDPOINT_XFER_INT,
77 .wMaxPacketSize = 64,
78 .bInterval = 12,
79 },
80 },
81};
82
83#define STR_INTERFACE_ "Osmocom USB Hub"
84
85static const struct {
86 struct usb_functionfs_strings_head header;
87 struct {
88 __le16 code;
89 const char str1[sizeof(STR_INTERFACE_)];
90 } __attribute__((packed)) lang0;
91} __attribute__((packed)) strings = {
92 .header = {
93 .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
94 .length = cpu_to_le32(sizeof(strings)),
95 .str_count = cpu_to_le32(1),
96 .lang_count = cpu_to_le32(1),
97 },
98 .lang0 = {
99 cpu_to_le16(0x0409), /* en-us */
100 STR_INTERFACE_,
101 },
102};
103
104
105struct usb2_hub_desc_header {
106 __u8 bDescLength;
107 __u8 bDescriptorType;
108 __u8 bNbrPorts;
109 __le16 wHubCharacteristics;
110 __u8 bPwrOn2PwrGood;
111 __u8 bHubContrCurrent;
112 __u8 data[0];
113};
114
115#define NUM_PORTS 31
116#define HDESC_ARR_BYTES ((NUM_PORTS + 1 + 7) / 8)
117static const struct {
118 struct usb2_hub_desc_header hdr;
119 uint8_t DeviceRemovable[HDESC_ARR_BYTES];
120 uint8_t PortPwrCtrlMask[HDESC_ARR_BYTES];
121} __attribute__ ((packed)) hub_desc = {
122 .hdr = {
123 .bDescLength = sizeof(hub_desc),
124 .bDescriptorType = USB_DT_HUB,
125 .bNbrPorts = NUM_PORTS,
126 .wHubCharacteristics = cpu_to_le16(0x0001),
127 .bPwrOn2PwrGood = 100,
128 .bHubContrCurrent = 0,
129 },
130};
131
132
133/***********************************************************************
134 * USB FunctionFS interface
135 ***********************************************************************/
136
137#include <stdlib.h>
138#include <stdio.h>
139#include <unistd.h>
140#include <string.h>
141#include <assert.h>
142#include <fcntl.h>
143#include <sys/stat.h>
144#include <osmocom/core/select.h>
145#include <osmocom/core/utils.h>
146#include <osmocom/core/msgb.h>
147#include <osmocom/core/utils.h>
148#include <osmocom/core/application.h>
149#include <osmocom/core/logging.h>
150
151#ifndef FUNCTIONFS_SUPPORTS_POLL
152#include <libaio.h>
153struct aio_help {
154 struct msgb *msg;
155 struct iocb *iocb;
156};
157#endif
158
159/* usb function handle */
160struct ufunc_handle {
161 struct osmo_fd ep0;
162 struct osmo_fd ep_int;
163 struct llist_head ep_int_queue;
164#ifndef FUNCTIONFS_SUPPORTS_POLL
165 struct osmo_fd aio_evfd;
166 io_context_t aio_ctx;
167 struct aio_help aio_int;
168#endif
169};
170
171static int ep_int_cb(struct osmo_fd *ofd, unsigned int what)
172{
173 LOGP(DUSB, LOGL_DEBUG, "%s\n", __func__);
174 return 0;
175}
176
177const struct value_string ffs_evt_type_names[] = {
178 { FUNCTIONFS_BIND, "BIND" },
179 { FUNCTIONFS_UNBIND, "UNBIND" },
180 { FUNCTIONFS_ENABLE, "ENABLE" },
181 { FUNCTIONFS_DISABLE, "DISABLE" },
182 { FUNCTIONFS_SETUP, "SETUP" },
183 { FUNCTIONFS_SUSPEND, "SUSPEND" },
184 { FUNCTIONFS_RESUME, "RESUME" },
185 { 0, NULL }
186};
187
188/* local, stand-alone definition of a USB control request */
189struct _usb_ctrl_req {
190 uint8_t bRequestType;
191 uint8_t bRequest;
192 uint16_t wValue;
193 uint16_t wIndex;
194 uint16_t wLength;
195} __attribute__ ((packed));;
196
197/* class requests from the USB 2.0 hub spec, table 11-15 */
198#define HUB_CLASS_REQ(dir, type, request) ((((dir) | (type)) << 8) | (request))
199/* GetBusState and SetHubDescriptor are optional, omitted */
200#define ClearHubFeature HUB_CLASS_REQ(USB_DIR_OUT, USB_RT_HUB, USB_REQ_CLEAR_FEATURE)
201#define ClearPortFeature HUB_CLASS_REQ(USB_DIR_OUT, USB_RT_PORT, USB_REQ_CLEAR_FEATURE)
202#define GetHubDescriptor HUB_CLASS_REQ(USB_DIR_IN, USB_RT_HUB, USB_REQ_GET_DESCRIPTOR)
203#define GetHubStatus HUB_CLASS_REQ(USB_DIR_IN, USB_RT_HUB, USB_REQ_GET_STATUS)
204#define GetPortStatus HUB_CLASS_REQ(USB_DIR_IN, USB_RT_PORT, USB_REQ_GET_STATUS)
205#define SetHubFeature HUB_CLASS_REQ(USB_DIR_OUT, USB_RT_HUB, USB_REQ_SET_FEATURE)
206#define SetPortFeature HUB_CLASS_REQ(USB_DIR_OUT, USB_RT_PORT, USB_REQ_SET_FEATURE)
207
208static const struct value_string hub_class_spec_req_vals[] = {
209 OSMO_VALUE_STRING(USB_REQ_CLEAR_FEATURE),
210 OSMO_VALUE_STRING(USB_REQ_GET_DESCRIPTOR),
211 OSMO_VALUE_STRING(USB_REQ_GET_STATUS),
212 OSMO_VALUE_STRING(USB_REQ_SET_FEATURE),
213 { 0, NULL }
214};
215
216#define CCID_CTRL_RET_INVALID -1
217#define CCID_CTRL_RET_UNKNOWN -2
218#define CCID_CTRL_RET_OK 0
219
220/*! Handle [class specific] CTRL request. We assume the caller has already verified that the
221 * request was made to the correct interface as well as it is a class-specific request.
222 * \param[in] ci CCID Instance for which CTRL request was received
223 * \param[in] ctrl_req buffer holding the 8 bytes CTRL transfer header
224 * \param[out] data_in data to be returned to the host in the IN transaction (if any)
225 * \returns CCID_CTRL_RET_OK, CCID_CTRL_RET_INVALID or CCID_CTRL_RET_UNKNOWN
226 */
227int hub_handle_ctrl(void *ci, const uint8_t *ctrl_req, const uint8_t **data_in)
228{
229 const struct _usb_ctrl_req *req = (const struct _usb_ctrl_req *) ctrl_req;
230 static uint16_t status[2];
231 int rc;
232
233 LOGP(DUSB, LOGL_NOTICE, "CTRL bmReqT=0x%02X bRequest=%s, wValue=0x%04X, wIndex=0x%04X, wLength=%d\n",
234 req->bRequestType, get_value_string(hub_class_spec_req_vals, req->bRequest),
235 req->wValue, req->wIndex, req->wLength);
236
237 switch (req->bRequestType & 0x7f) {
238 case USB_RT_HUB:
239 switch (req->bRequest) {
240 case USB_REQ_GET_DESCRIPTOR:
241 if (req->wIndex != 0) {
242 LOGP(DUSB, LOGL_ERROR, "GET_DESC wIndex invalid\n");
243 return CCID_CTRL_RET_INVALID;
244 }
245 if (0) // ctrl_req->wValue != FIXME
246 return CCID_CTRL_RET_INVALID;
247 *data_in = (const uint8_t *) &hub_desc;
248 return sizeof(hub_desc);
249 case USB_REQ_CLEAR_FEATURE:
250 switch (req->wValue) {
251 case C_HUB_LOCAL_POWER:
252 case C_HUB_OVER_CURRENT:
253 return CCID_CTRL_RET_OK;
254 }
255 break;
256 case USB_REQ_GET_STATUS:
257 status[0] = cpu_to_le16(HUB_STATUS_LOCAL_POWER);
258 status[1] = cpu_to_le16(0);
259 *data_in = (const uint8_t *) status;
260 return sizeof(status);
261 case USB_REQ_SET_FEATURE:
262 if (req->wValue > 1)
263 return CCID_CTRL_RET_INVALID;
264 return CCID_CTRL_RET_OK;
265 }
266 break;
267 case USB_RT_PORT:
268 switch (req->bRequest) {
269 case USB_REQ_CLEAR_FEATURE:
270 switch (req->wValue) {
271 case USB_PORT_FEAT_CONNECTION:
272 case USB_PORT_FEAT_ENABLE:
273 case USB_PORT_FEAT_SUSPEND:
274 case USB_PORT_FEAT_OVER_CURRENT:
275 case USB_PORT_FEAT_RESET:
276 case USB_PORT_FEAT_L1:
277 case USB_PORT_FEAT_POWER:
278 case USB_PORT_FEAT_LOWSPEED:
279 case USB_PORT_FEAT_C_CONNECTION:
280 case USB_PORT_FEAT_C_ENABLE:
281 case USB_PORT_FEAT_C_SUSPEND:
282 case USB_PORT_FEAT_C_OVER_CURRENT:
283 case USB_PORT_FEAT_C_RESET:
284 case USB_PORT_FEAT_TEST:
285 case USB_PORT_FEAT_C_PORT_L1:
286 return CCID_CTRL_RET_OK;
287 }
288 break;
289 case USB_REQ_GET_STATUS:
290 status[0] = cpu_to_le16(0);
291 status[1] = cpu_to_le16(0);
292 *data_in = (const uint8_t *) status;
293 return sizeof(status);
294 case USB_REQ_SET_FEATURE:
295 //selector = wIndex >> 8
296 //port = wIndex & 0xff
297 return CCID_CTRL_RET_OK;
298 }
299 }
300 return CCID_CTRL_RET_UNKNOWN;
301}
302
303static void handle_setup(int fd, const struct usb_ctrlrequest *setup)
304{
305 const uint8_t *data_in = NULL;
306 int rc;
307
308 LOGP(DUSB, LOGL_NOTICE, "EP0 SETUP bRequestType=0x%02x, bRequest=0x%02x wValue=0x%04x, "
309 "wIndex=0x%04x, wLength=%u\n", setup->bRequestType, setup->bRequest,
310 le16_to_cpu(setup->wValue), le16_to_cpu(setup->wIndex), le16_to_cpu(setup->wLength));
311
312 rc = hub_handle_ctrl(NULL, (const uint8_t *) setup, &data_in);
313 switch (rc) {
314 case CCID_CTRL_RET_INVALID:
315 if (setup->bRequestType & USB_DIR_IN)
316 read(fd, NULL, 0); /* cause stall */
317 else
318 write(fd, NULL, 0); /* cause stall */
319 break;
320 case CCID_CTRL_RET_UNKNOWN:
321 /* FIXME: is this correct behavior? */
322 if (setup->bRequestType & USB_DIR_IN)
323 write(fd, NULL, 0); /* send ZLP */
324 else
325 read(fd, NULL, 0);
326 break;
327 default:
328 if (setup->bRequestType & USB_DIR_IN) {
329 uint16_t len = rc;
330 if (setup->wLength < len)
331 len = setup->wLength;
332 LOGP(DUSB, LOGL_NOTICE, "Writing %u: %s\n", len, osmo_hexdump_nospc(data_in, len));
333 write(fd, data_in, le16_to_cpu(len));
334 } else
335 read(fd, NULL, 0); /* FIXME: control OUT? */
336 break;
337 }
338}
339
340static int ep_0_cb(struct osmo_fd *ofd, unsigned int what)
341{
342 struct ufunc_handle *uh = (struct ufunc_handle *) ofd->data;
343 int rc;
344
345 if (what & BSC_FD_READ) {
346 struct usb_functionfs_event evt;
347 rc = read(ofd->fd, (uint8_t *)&evt, sizeof(evt));
348 if (rc < sizeof(evt))
349 return -23;
350 LOGP(DUSB, LOGL_NOTICE, "EP0 %s\n", get_value_string(ffs_evt_type_names, evt.type));
351 switch (evt.type) {
352 case FUNCTIONFS_ENABLE:
353 //aio_refill_out(uh);
354 break;
355 case FUNCTIONFS_SETUP:
356 handle_setup(ofd->fd, &evt.u.setup);
357 break;
358 }
359
360 }
361 return 0;
362}
363
364#ifndef FUNCTIONFS_SUPPORTS_POLL
365
366/* dequeue the next msgb from ep_int_queue and set up AIO for it */
367static void dequeue_aio_write_int(struct ufunc_handle *uh)
368{
369 struct aio_help *ah = &uh->aio_int;
370 struct msgb *d;
371 int rc;
372
373 if (ah->msg)
374 return;
375
376 d = msgb_dequeue(&uh->ep_int_queue);
377 if (!d)
378 return;
379
380 OSMO_ASSERT(ah->iocb);
381 ah->msg = d;
382 io_prep_pwrite(ah->iocb, uh->ep_int.fd, msgb_data(d), msgb_length(d), 0);
383 io_set_eventfd(ah->iocb, uh->aio_evfd.fd);
384 rc = io_submit(uh->aio_ctx, 1, &ah->iocb);
385 OSMO_ASSERT(rc >= 0);
386}
387
388static int evfd_cb(struct osmo_fd *ofd, unsigned int what)
389{
390 struct ufunc_handle *uh = (struct ufunc_handle *) ofd->data;
391 struct io_event evt[1];
392 struct msgb *msg;
393 uint64_t ev_cnt;
394 int i, rc;
395
396 rc = read(ofd->fd, &ev_cnt, sizeof(ev_cnt));
397 assert(rc == sizeof(ev_cnt));
398
399 rc = io_getevents(uh->aio_ctx, 1, 1, evt, NULL);
400 if (rc <= 0) {
401 LOGP(DUSB, LOGL_ERROR, "error in io_getevents(): %d\n", rc);
402 return rc;
403 }
404
405 for (i = 0; i < rc; i++) {
406 int fd = evt[i].obj->aio_fildes;
407 if (fd == uh->ep_int.fd) {
408 /* interrupt endpoint AIO has completed. This means the IRQ transfer
409 * which we generated has reached the host */
410 LOGP(DUSB, LOGL_DEBUG, "IRQ AIO completed, free()ing msgb\n");
411 msgb_free(uh->aio_int.msg);
412 uh->aio_int.msg = NULL;
413 dequeue_aio_write_int(uh);
414 }
415 }
416 return 0;
417}
418#endif
419
420
421static int ep0_init(struct ufunc_handle *uh)
422{
423 int rc;
424
425 /* open control endpoint and write descriptors to it */
426 rc = open("ep0", O_RDWR);
427 assert(rc >= 0);
428 osmo_fd_setup(&uh->ep0, rc, BSC_FD_READ, &ep_0_cb, uh, 0);
429 osmo_fd_register(&uh->ep0);
430 rc = write(uh->ep0.fd, &descriptors, sizeof(descriptors));
431 if (rc != sizeof(descriptors)) {
432 LOGP(DUSB, LOGL_ERROR, "Cannot write descriptors: %s\n", strerror(errno));
433 return -1;
434 }
435 rc = write(uh->ep0.fd, &strings, sizeof(strings));
436 if (rc != sizeof(strings)) {
437 LOGP(DUSB, LOGL_ERROR, "Cannot write strings: %s\n", strerror(errno));
438 return -1;
439 }
440
441 /* open other endpoint file descriptors */
442 INIT_LLIST_HEAD(&uh->ep_int_queue);
443 rc = open("ep1", O_RDWR);
444 assert(rc >= 0);
445 osmo_fd_setup(&uh->ep_int, rc, 0, &ep_int_cb, uh, 1);
446#ifdef FUNCTIONFS_SUPPORTS_POLL
447 osmo_fd_register(&uh->ep_int);
448#endif
449
450#ifndef FUNCTIONFS_SUPPORTS_POLL
451#include <sys/eventfd.h>
452 /* for some absolutely weird reason, gadgetfs+functionfs don't support
453 * the standard methods of non-blocking I/o (select/poll). We need to
454 * work around using Linux AIO, which is not to be confused with POSIX AIO! */
455
456 memset(&uh->aio_ctx, 0, sizeof(uh->aio_ctx));
457 rc = io_setup(1, &uh->aio_ctx);
458 OSMO_ASSERT(rc >= 0);
459
460 /* create an eventfd, which will be marked readable once some AIO completes */
461 rc = eventfd(0, 0);
462 OSMO_ASSERT(rc >= 0);
463 osmo_fd_setup(&uh->aio_evfd, rc, BSC_FD_READ, &evfd_cb, uh, 0);
464 osmo_fd_register(&uh->aio_evfd);
465
466 uh->aio_int.iocb = malloc(sizeof(struct iocb));
467#endif
468
469 return 0;
470}
471
472static const struct log_info_cat log_info_cat[] = {
473 [DUSB] = {
474 .name = "USB",
475 .description = "USB Transport",
476 .enabled = 1,
477 .loglevel = LOGL_NOTICE,
478 },
479};
480
481static const struct log_info log_info = {
482 .cat = log_info_cat,
483 .num_cat = ARRAY_SIZE(log_info_cat),
484};
485
Eric Wilddd2e82e2019-12-02 14:11:23 +0100486static void *g_tall_ctx;
Harald Weltea7c1eb02019-09-12 15:16:43 +0200487
488static void signal_handler(int signal)
489{
490 switch (signal) {
491 case SIGUSR1:
Eric Wilddd2e82e2019-12-02 14:11:23 +0100492 talloc_report_full(g_tall_ctx, stderr);
Harald Weltea7c1eb02019-09-12 15:16:43 +0200493 break;
494 }
495}
496
497
498int main(int argc, char **argv)
499{
500 struct ufunc_handle ufh = (struct ufunc_handle) { 0, };
501 int rc;
502
Eric Wilddd2e82e2019-12-02 14:11:23 +0100503 g_tall_ctx = talloc_named_const(NULL, 0, "hub_main_functionfs");
504 msgb_talloc_ctx_init(g_tall_ctx, 0);
505 osmo_init_logging2(g_tall_ctx, &log_info);
Harald Weltea7c1eb02019-09-12 15:16:43 +0200506
507 signal(SIGUSR1, &signal_handler);
508
509 if (argc < 2) {
510 fprintf(stderr, "You have to specify the mount-path of the functionfs\n");
511 exit(2);
512 }
513
514 chdir(argv[1]);
515 rc = ep0_init(&ufh);
516 if (rc < 0) {
517 fprintf(stderr, "Error %d\n", rc);
518 exit(1);
519 }
520
521 while (1) {
522 osmo_select_main(0);
523 }
524}