1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <dirent.h>
6 #include <fcntl.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <lib/fdio/util.h>
12 #include <ddk/protocol/usb/modeswitch.h>
13 #include <zircon/device/usb-peripheral.h>
14 #include <zircon/hw/usb.h>
15 #include <zircon/hw/usb/cdc.h>
16 #include <zircon/usb/peripheral/c/fidl.h>
17 #include <zircon/syscalls.h>
18 #include <zircon/types.h>
19 
20 
21 #include <zircon/types.h>
22 
23 #define DEV_USB_PERIPHERAL_DIR  "/dev/class/usb-peripheral"
24 
25 #define MANUFACTURER_STRING "Zircon"
26 #define CDC_PRODUCT_STRING  "CDC Ethernet"
27 #define UMS_PRODUCT_STRING  "USB Mass Storage"
28 #define TEST_PRODUCT_STRING  "USB Function Test"
29 #define SERIAL_STRING       "12345678"
30 
31 typedef zircon_usb_peripheral_FunctionDescriptor usb_function_descriptor_t;
32 
33 const usb_function_descriptor_t cdc_function_desc = {
34     .interface_class = USB_CLASS_COMM,
35     .interface_subclass = USB_CDC_SUBCLASS_ETHERNET,
36     .interface_protocol = 0,
37 };
38 
39 const usb_function_descriptor_t ums_function_desc = {
40     .interface_class = USB_CLASS_MSC,
41     .interface_subclass = USB_SUBCLASS_MSC_SCSI,
42     .interface_protocol = USB_PROTOCOL_MSC_BULK_ONLY,
43 };
44 
45 const usb_function_descriptor_t test_function_desc = {
46     .interface_class = USB_CLASS_VENDOR,
47     .interface_subclass = 0,
48     .interface_protocol = 0,
49 };
50 
51 
52 typedef struct {
53     const usb_function_descriptor_t* desc;
54     const char* product_string;
55     uint16_t vid;
56     uint16_t pid;
57 } usb_function_t;
58 
59 static const usb_function_t cdc_function = {
60     .desc = &cdc_function_desc,
61     .product_string = CDC_PRODUCT_STRING,
62     .vid = GOOGLE_USB_VID,
63     .pid = GOOGLE_USB_CDC_PID,
64 };
65 
66 static const usb_function_t ums_function = {
67     .desc = &ums_function_desc,
68     .product_string = UMS_PRODUCT_STRING,
69     .vid = GOOGLE_USB_VID,
70     .pid = GOOGLE_USB_UMS_PID,
71 };
72 
73 static const usb_function_t test_function = {
74     .desc = &test_function_desc,
75     .product_string = TEST_PRODUCT_STRING,
76     .vid = GOOGLE_USB_VID,
77     .pid = GOOGLE_USB_PERIPHERAL_TEST_PID,
78 };
79 
80 static zircon_usb_peripheral_DeviceDescriptor device_desc = {
81     .bcdUSB = htole16(0x0200),
82     .bDeviceClass = 0,
83     .bDeviceSubClass = 0,
84     .bDeviceProtocol = 0,
85     .bMaxPacketSize0 = 64,
86 //   idVendor and idProduct are filled in later
87     .bcdDevice = htole16(0x0100),
88 //    iManufacturer, iProduct and iSerialNumber are filled in later
89     .bNumConfigurations = 1,
90 };
91 
open_usb_device(void)92 static int open_usb_device(void) {
93     struct dirent* de;
94     DIR* dir = opendir(DEV_USB_PERIPHERAL_DIR);
95     if (!dir) {
96         printf("Error opening %s\n", DEV_USB_PERIPHERAL_DIR);
97         return -1;
98     }
99 
100     while ((de = readdir(dir)) != NULL) {
101        char devname[128];
102 
103         snprintf(devname, sizeof(devname), "%s/%s", DEV_USB_PERIPHERAL_DIR, de->d_name);
104         int fd = open(devname, O_RDWR);
105         if (fd < 0) {
106             printf("Error opening %s\n", devname);
107             continue;
108         }
109 
110         closedir(dir);
111         return fd;
112     }
113 
114     closedir(dir);
115     return -1;
116 }
117 
device_init(zx_handle_t svc,const usb_function_t * function)118 static zx_status_t device_init(zx_handle_t svc, const usb_function_t* function) {
119     zx_status_t status2;
120 
121     device_desc.idVendor = htole16(function->vid);
122     device_desc.idProduct = htole16(function->pid);
123 
124     // allocate string descriptors
125     zx_status_t status = zircon_usb_peripheral_DeviceAllocStringDesc(svc, MANUFACTURER_STRING,
126                                                                      strlen(MANUFACTURER_STRING) + 1,
127                                                                      &status2, &device_desc.iManufacturer);
128     if (status == ZX_OK) status = status2;
129     if (status != ZX_OK) {
130         fprintf(stderr, "zircon_usb_peripheral_DeviceAllocStringDesc failed: %d\n", status);
131         return status;
132     }
133     status = zircon_usb_peripheral_DeviceAllocStringDesc(svc, function->product_string,
134                                                          strlen(function->product_string) + 1,
135                                                          &status2, &device_desc.iProduct);
136     if (status == ZX_OK) status = status2;
137     if (status != ZX_OK) {
138         fprintf(stderr, "zircon_usb_peripheral_DeviceAllocStringDesc failed: %d\n", status);
139         return status;
140     }
141     status = zircon_usb_peripheral_DeviceAllocStringDesc(svc, SERIAL_STRING, strlen(SERIAL_STRING) + 1,
142                                                          &status2, &device_desc.iSerialNumber);
143     if (status == ZX_OK) status = status2;
144     if (status != ZX_OK) {
145         fprintf(stderr, "zircon_usb_peripheral_DeviceAllocStringDesc failed: %d\n", status);
146         return status;
147     }
148 
149     // set device descriptor
150     status = zircon_usb_peripheral_DeviceSetDeviceDescriptor(svc, &device_desc, &status2);
151     if (status == ZX_OK) status = status2;
152     if (status != ZX_OK) {
153         fprintf(stderr, "zircon_usb_peripheral_DeviceSetDeviceDescriptor failed: %d\n", status);
154         return status;
155     }
156 
157     status = zircon_usb_peripheral_DeviceAddFunction(svc, function->desc, &status2);
158     if (status == ZX_OK) status = status2;
159     if (status != ZX_OK) {
160         fprintf(stderr, "zircon_usb_peripheral_DeviceAddFunction failed: %d\n", status);
161         return status;
162     }
163 
164     status = zircon_usb_peripheral_DeviceBindFunctions(svc, &status2);
165     if (status == ZX_OK) status = status2;
166     if (status != ZX_OK) {
167         fprintf(stderr, "zircon_usb_peripheral_DeviceBindFunctions failed: %d\n", status);
168     }
169 
170     return status;
171 }
172 
ums_command(zx_handle_t svc,int argc,const char * argv[])173 static int ums_command(zx_handle_t svc, int argc, const char* argv[]) {
174     zx_status_t status, status2;
175 
176     status = zircon_usb_peripheral_DeviceClearFunctions(svc, &status2);
177     if (status == ZX_OK) status = status2;
178     if (status == ZX_OK) {
179         status = device_init(svc, &ums_function);
180     }
181 
182     return status == ZX_OK ? 0 : -1;
183 }
184 
cdc_command(zx_handle_t svc,int argc,const char * argv[])185 static int cdc_command(zx_handle_t svc, int argc, const char* argv[]) {
186     zx_status_t status, status2;
187 
188     status = zircon_usb_peripheral_DeviceClearFunctions(svc, &status2);
189     if (status == ZX_OK) status = status2;
190     if (status == ZX_OK) {
191         status = device_init(svc, &cdc_function);
192     }
193 
194     return status == ZX_OK ? 0 : -1;
195 }
196 
test_command(zx_handle_t svc,int argc,const char * argv[])197 static int test_command(zx_handle_t svc, int argc, const char* argv[]) {
198     zx_status_t status, status2;
199 
200     status = zircon_usb_peripheral_DeviceClearFunctions(svc, &status2);
201     if (status == ZX_OK) status = status2;
202     if (status == ZX_OK) {
203         status = device_init(svc, &test_function);
204     }
205 
206     return status == ZX_OK ? 0 : -1;
207 }
208 
mode_command(zx_handle_t svc,int argc,const char * argv[])209 static int mode_command(zx_handle_t svc, int argc, const char* argv[]) {
210     zx_status_t status = ZX_OK;
211     zx_status_t status2;
212 
213     if (argc == 1) {
214         // print current mode
215         usb_mode_t mode;
216         status = zircon_usb_peripheral_DeviceGetMode(svc, &status2, &mode);
217         if (status == ZX_OK) status = status2;
218         if (status != ZX_OK) {
219             fprintf(stderr, "zircon_usb_peripheral_DeviceGetMode failed: %d\n", status);
220         } else {
221             switch (mode) {
222             case USB_MODE_NONE:
223                 printf("NONE\n");
224                 break;
225             case USB_MODE_HOST:
226                 printf("HOST\n");
227                 break;
228             case USB_MODE_PERIPHERAL:
229                 printf("PERIPHERAL\n");
230                 break;
231             case USB_MODE_OTG:
232                 printf("OTG\n");
233                 break;
234             default:
235                 printf("unknown mode %d\n", mode);
236                 break;
237             }
238          }
239     } else {
240         usb_mode_t mode;
241         if (strcasecmp(argv[1], "none") == 0) {
242             mode = USB_MODE_NONE;
243         } else if (strcasecmp(argv[1], "host") == 0) {
244             mode = USB_MODE_HOST;
245         } else if (strcasecmp(argv[1], "peripheral") == 0) {
246             mode = USB_MODE_PERIPHERAL;
247         } else if (strcasecmp(argv[1], "otg") == 0) {
248             mode = USB_MODE_OTG;
249         } else {
250             fprintf(stderr, "unknown USB mode %s\n", argv[1]);
251             status = ZX_ERR_INVALID_ARGS;
252         }
253 
254         if (status == ZX_OK) {
255             status = zircon_usb_peripheral_DeviceSetMode(svc, mode, &status2);
256             if (status == ZX_OK) status = status2;
257             if (status != ZX_OK) {
258                 fprintf(stderr, "zircon_usb_peripheral_DeviceSetMode failed: %d\n", status);
259             }
260         }
261     }
262 
263     return status;
264 }
265 
266 typedef struct {
267     const char* name;
268     int (*command)(zx_handle_t svc, int argc, const char* argv[]);
269     const char* description;
270 } usbctl_command_t;
271 
272 static usbctl_command_t commands[] = {
273     {
274         "init-ums",
275         ums_command,
276         "init-ums - initializes the USB Mass Storage function"
277     },
278     {
279         "init-cdc",
280         cdc_command,
281         "init-cdc - initializes the CDC Ethernet function"
282     },
283     {
284         "init-test",
285         test_command,
286         "init-test - initializes the USB Peripheral Test function"
287     },
288     {
289         "mode",
290         mode_command,
291         "mode [none|host|peripheral|otg] - sets the current USB mode. "
292         "Returns the current mode if no additional arugment is provided."
293     },
294     { NULL, NULL, NULL },
295 };
296 
usage(void)297 static void usage(void) {
298     fprintf(stderr, "usage: \"usbctl <command>\", where command is one of:\n");
299 
300     usbctl_command_t* command = commands;
301     while (command->name) {
302         fprintf(stderr, "    %s\n", command->description);
303         command++;
304     }
305 }
306 
main(int argc,const char ** argv)307 int main(int argc, const char** argv) {
308     if (argc < 2) {
309         usage();
310         return -1;
311     }
312 
313     int fd = open_usb_device();
314     if (fd < 0) {
315         fprintf(stderr, "could not find a device in %s\n", DEV_USB_PERIPHERAL_DIR);
316         return fd;
317     }
318 
319     zx_handle_t svc;
320     zx_status_t status = fdio_get_service_handle(fd, &svc);
321     if (status != ZX_OK) {
322         close(fd);
323         return status;
324     }
325 
326     const char* command_name = argv[1];
327     usbctl_command_t* command = commands;
328     while (command->name) {
329         if (!strcmp(command_name, command->name)) {
330             status = command->command(svc, argc - 1, argv + 1);
331             goto done;
332         }
333         command++;
334     }
335     // if we fall through, print usage
336     usage();
337     status = ZX_ERR_INVALID_ARGS;
338 
339 done:
340     zx_handle_close(svc);
341     close(fd);
342     return status;
343 }
344