add code for getting the primary interface
[project/usbmode.git] / main.c
1 #include <stdio.h>
2 #include <getopt.h>
3 #include <stdbool.h>
4 #include <ctype.h>
5
6 #include <libubox/blobmsg_json.h>
7 #include <libubox/avl.h>
8 #include <libubox/avl-cmp.h>
9 #include "switch.h"
10
11 #define DEFAULT_CONFIG "/etc/usb-mode.json"
12
13 struct device {
14 struct avl_node avl;
15 struct blob_attr *data;
16 };
17
18 static int verbose = 0;
19 static const char *config_file = DEFAULT_CONFIG;
20 static struct blob_buf conf;
21
22 static struct blob_attr **messages;
23 static int n_messages;
24
25 static struct avl_tree devices;
26
27 static struct libusb_context *usb;
28 static struct libusb_device **usbdevs;
29 static int n_usbdevs;
30
31 static int hex2num(char c)
32 {
33 if (c >= '0' && c <= '9')
34 return c - '0';
35
36 c = toupper(c);
37 if (c >= 'A' && c <= 'F')
38 return c - 'A' + 10;
39
40 return -1;
41 }
42
43 static int hex2byte(const char *hex)
44 {
45 int a, b;
46
47 a = hex2num(*hex++);
48 if (a < 0)
49 return -1;
50
51 b = hex2num(*hex++);
52 if (b < 0)
53 return -1;
54
55 return (a << 4) | b;
56 }
57
58 static int hexstr2bin(const char *hex, char *buffer, int len)
59 {
60 const char *ipos = hex;
61 char *opos = buffer;
62 int i, a;
63
64 for (i = 0; i < len; i++) {
65 a = hex2byte(ipos);
66 if (a < 0)
67 return -1;
68
69 *opos++ = a;
70 ipos += 2;
71 }
72
73 return 0;
74 }
75
76 static bool convert_message(struct blob_attr *attr)
77 {
78 char *data;
79 int len;
80
81 if (!attr)
82 return true;
83
84 data = blobmsg_data(attr);
85 len = strlen(data);
86 if (len % 2)
87 return false;
88
89 return !hexstr2bin(data, data, len / 2);
90 }
91
92 static int parse_config(void)
93 {
94 enum {
95 CONF_MESSAGES,
96 CONF_DEVICES,
97 __CONF_MAX
98 };
99 static const struct blobmsg_policy policy[__CONF_MAX] = {
100 [CONF_MESSAGES] = { .name = "messages", .type = BLOBMSG_TYPE_ARRAY },
101 [CONF_DEVICES] = { .name = "devices", .type = BLOBMSG_TYPE_TABLE },
102 };
103 struct blob_attr *tb[__CONF_MAX];
104 struct blob_attr *cur;
105 struct device *dev;
106 int rem;
107
108 blobmsg_parse(policy, __CONF_MAX, tb, blob_data(conf.head), blob_len(conf.head));
109 if (!tb[CONF_MESSAGES] || !tb[CONF_DEVICES]) {
110 fprintf(stderr, "Configuration incomplete\n");
111 return -1;
112 }
113
114 blobmsg_for_each_attr(cur, tb[CONF_MESSAGES], rem)
115 n_messages++;
116
117 messages = calloc(n_messages, sizeof(*messages));
118 n_messages = 0;
119 blobmsg_for_each_attr(cur, tb[CONF_MESSAGES], rem) {
120 if (!convert_message(cur)) {
121 fprintf(stderr, "Invalid data in message %d\n", n_messages);
122 return -1;
123 }
124 messages[n_messages++] = cur;
125 }
126
127 blobmsg_for_each_attr(cur, tb[CONF_DEVICES], rem) {
128 dev = calloc(1, sizeof(*dev));
129 dev->avl.key = blobmsg_name(cur);
130 dev->data = cur;
131 avl_insert(&devices, &dev->avl);
132 }
133
134 return 0;
135 }
136
137 static int usage(const char *prog)
138 {
139 fprintf(stderr, "Usage: %s <command> <options>\n"
140 "Commands:\n"
141 " -l List matching devices\n"
142 " -s Modeswitch matching devices\n"
143 "\n"
144 "Options:\n"
145 " -v Verbose output\n"
146 " -c <file> Set configuration file to <file> (default: %s)\n"
147 "\n", prog, DEFAULT_CONFIG);
148 return 1;
149 }
150
151 typedef void (*cmd_cb_t)(struct usbdev_data *data);
152
153 static struct blob_attr *
154 find_dev_data(struct usbdev_data *data, struct device *dev)
155 {
156 struct blob_attr *cur;
157 int rem;
158
159 blobmsg_for_each_attr(cur, dev->data, rem) {
160 const char *name = blobmsg_name(cur);
161 const char *next;
162 char *val;
163
164 if (!strcmp(blobmsg_name(cur), "*"))
165 return cur;
166
167 next = strchr(name, '=');
168 if (!next)
169 continue;
170
171 next++;
172 if (!strncmp(name, "uMa", 3)) {
173 val = data->mfg;
174 } else if (!strncmp(name, "uPr", 3)) {
175 val = data->prod;
176 } else if (!strncmp(name, "uSe", 3)) {
177 val = data->serial;
178 } else {
179 /* ignore unsupported scsi attributes */
180 return cur;
181 }
182
183 if (!strcmp(val, next))
184 return cur;
185 }
186
187 return NULL;
188 }
189
190 static void
191 parse_interface_config(libusb_device *dev, struct usbdev_data *data)
192 {
193 struct libusb_config_descriptor *config;
194 const struct libusb_interface *iface;
195
196 data->interface = -1;
197 if (libusb_get_config_descriptor(dev, 0, &config))
198 return;
199
200 data->config = config;
201 if (!config->bNumInterfaces)
202 return;
203
204 iface = &config->interface[0];
205 if (!iface->num_altsetting)
206 return;
207
208 data->interface = iface->altsetting[0].bInterfaceNumber;
209 }
210
211 static void iterate_devs(cmd_cb_t cb)
212 {
213 struct usbdev_data data;
214 struct device *dev;
215 int i;
216
217 if (!cb)
218 return;
219
220 for (i = 0; i < n_usbdevs; i++) {
221 memset(&data, 0, sizeof(data));
222
223 if (libusb_get_device_descriptor(usbdevs[i], &data.desc))
224 continue;
225
226 sprintf(data.idstr, "%04x:%04x", data.desc.idVendor, data.desc.idProduct);
227
228 dev = avl_find_element(&devices, data.idstr, dev, avl);
229 if (!dev)
230 continue;
231
232 if (libusb_open(usbdevs[i], &data.devh))
233 continue;
234
235 libusb_get_string_descriptor_ascii(
236 data.devh, data.desc.iManufacturer,
237 (void *) data.mfg, sizeof(data.mfg));
238 libusb_get_string_descriptor_ascii(
239 data.devh, data.desc.iProduct,
240 (void *) data.prod, sizeof(data.prod));
241 libusb_get_string_descriptor_ascii(
242 data.devh, data.desc.iSerialNumber,
243 (void *) data.serial, sizeof(data.serial));
244
245 parse_interface_config(usbdevs[i], &data);
246
247 data.info = find_dev_data(&data, dev);
248 if (data.info)
249 cb(&data);
250
251 if (data.config)
252 libusb_free_config_descriptor(data.config);
253
254 libusb_close(data.devh);
255 }
256 }
257
258 static void handle_list(struct usbdev_data *data)
259 {
260 fprintf(stderr, "Found device: %s (Manufacturer: \"%s\", Product: \"%s\", Serial: \"%s\")\n",
261 data->idstr, data->mfg, data->prod, data->serial);
262 }
263
264 int main(int argc, char **argv)
265 {
266 cmd_cb_t cb = NULL;
267 int ret;
268 int ch;
269
270 avl_init(&devices, avl_strcmp, false, NULL);
271
272 while ((ch = getopt(argc, argv, "lsc:v")) != -1) {
273 switch (ch) {
274 case 'l':
275 cb = handle_list;
276 break;
277 case 's':
278 cb = handle_switch;
279 break;
280 case 'c':
281 config_file = optarg;
282 break;
283 case 'v':
284 verbose++;
285 break;
286 default:
287 return usage(argv[0]);
288 }
289 }
290
291 blob_buf_init(&conf, 0);
292 if (!blobmsg_add_json_from_file(&conf, config_file) ||
293 parse_config()) {
294 fprintf(stderr, "Failed to load config file\n");
295 return 1;
296 }
297
298 ret = libusb_init(&usb);
299 if (ret) {
300 fprintf(stderr, "Failed to initialize libusb: %s\n", libusb_error_name(ret));
301 return 1;
302 }
303
304 n_usbdevs = libusb_get_device_list(usb, &usbdevs);
305 iterate_devs(cb);
306 libusb_free_device_list(usbdevs, 1);
307 libusb_exit(usb);
308
309 return 0;
310 }