1/*-
2 * Copyright (c) 2007-2022 Hans Petter Selasky
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26#include <stdio.h>
27#include <stdint.h>
28#include <stdlib.h>
29#include <err.h>
30#include <string.h>
31#include <errno.h>
32#include <unistd.h>
33
34#include <sys/sysctl.h>
35#include <sys/time.h>
36
37#include <libusb20.h>
38#include <libusb20_desc.h>
39
40#include <dev/usb/usb_endian.h>
41#include <dev/usb/usb.h>
42#include <dev/usb/usb_cdc.h>
43
44#include "usbtest.h"
45
46static struct modem {
47	struct libusb20_transfer *xfer_in;
48	struct libusb20_transfer *xfer_out;
49	struct libusb20_device *usb_dev;
50
51	struct bps rx_bytes;
52	struct bps tx_bytes;
53	uint32_t c0;
54	uint32_t c1;
55	uint32_t out_state;
56	uint32_t in_last;
57	uint32_t in_synced;
58	uint32_t duration;
59	uint32_t errors;
60
61	uint8_t use_vendor_specific;
62	uint8_t	loop_data;
63	uint8_t	modem_at_mode;
64	uint8_t	data_stress_test;
65	uint8_t	control_ep_test;
66	uint8_t	usb_iface;
67	uint8_t	random_tx_length;
68	uint8_t	random_tx_delay;
69
70}	modem;
71
72static void
73set_defaults(struct modem *p)
74{
75	memset(p, 0, sizeof(*p));
76
77	p->data_stress_test = 1;
78	p->control_ep_test = 1;
79	p->duration = 60;		/* seconds */
80}
81
82void
83do_bps(const char *desc, struct bps *bps, uint32_t len)
84{
85	bps->bytes += len;
86}
87
88static void
89modem_out_state(uint8_t *buf)
90{
91	if (modem.modem_at_mode) {
92		switch (modem.out_state & 3) {
93		case 0:
94			*buf = 'A';
95			break;
96		case 1:
97			*buf = 'T';
98			break;
99		case 2:
100			*buf = '\r';
101			break;
102		default:
103			*buf = '\n';
104			modem.c0++;
105			break;
106		}
107		modem.out_state++;
108	} else {
109		*buf = modem.out_state;
110		modem.out_state++;
111		modem.out_state %= 255;
112	}
113}
114
115static void
116modem_in_state(uint8_t buf, uint32_t counter)
117{
118	if ((modem.in_last == 'O') && (buf == 'K')) {
119		modem.c1++;
120		modem.in_last = buf;
121	} else if (buf == modem.in_last) {
122		modem.c1++;
123		modem.in_last++;
124		modem.in_last %= 255;
125		if (modem.in_synced == 0) {
126			if (modem.errors < 64) {
127				printf("Got sync\n");
128			}
129			modem.in_synced = 1;
130		}
131	} else {
132		if (modem.in_synced) {
133			if (modem.errors < 64) {
134				printf("Lost sync @ %d, 0x%02x != 0x%02x\n",
135				    counter % 512, buf, modem.in_last);
136			}
137			modem.in_synced = 0;
138			modem.errors++;
139		}
140		modem.in_last = buf;
141		modem.in_last++;
142		modem.in_last %= 255;
143	}
144}
145
146static void
147modem_write(uint8_t *buf, uint32_t len)
148{
149	uint32_t n;
150
151	for (n = 0; n != len; n++) {
152		modem_out_state(buf + n);
153	}
154
155	do_bps("transmitted", &modem.tx_bytes, len);
156}
157
158static void
159modem_read(uint8_t *buf, uint32_t len)
160{
161	uint32_t n;
162
163	for (n = 0; n != len; n++) {
164		modem_in_state(buf[n], n);
165	}
166
167	do_bps("received", &modem.rx_bytes, len);
168}
169
170static void
171usb_modem_control_ep_test(struct modem *p, uint32_t duration, uint8_t flag)
172{
173	struct timeval sub_tv;
174	struct timeval ref_tv;
175	struct timeval res_tv;
176	struct LIBUSB20_CONTROL_SETUP_DECODED setup;
177	struct usb_cdc_abstract_state ast;
178	struct usb_cdc_line_state ls;
179	uint16_t feature = UCDC_ABSTRACT_STATE;
180	uint16_t state = UCDC_DATA_MULTIPLEXED;
181	uint8_t iface_no;
182	uint8_t buf[4];
183	int id = 0;
184	int iter = 0;
185
186	time_t last_sec;
187
188	iface_no = p->usb_iface - 1;
189
190	gettimeofday(&ref_tv, 0);
191
192	last_sec = ref_tv.tv_sec;
193
194	printf("\nTest=%d\n", (int)flag);
195
196	while (1) {
197
198		gettimeofday(&sub_tv, 0);
199
200		if (last_sec != sub_tv.tv_sec) {
201
202			printf("STATUS: ID=%u, COUNT=%u tests/sec ERR=%u\n",
203			    (int)id,
204			    (int)iter,
205			    (int)p->errors);
206
207			fflush(stdout);
208
209			last_sec = sub_tv.tv_sec;
210
211			id++;
212
213			iter = 0;
214		}
215		timersub(&sub_tv, &ref_tv, &res_tv);
216
217		if ((res_tv.tv_sec < 0) || (res_tv.tv_sec >= (int)duration))
218			break;
219
220		LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &setup);
221
222		if (flag & 1) {
223			setup.bmRequestType = UT_READ_CLASS_INTERFACE;
224			setup.bRequest = 0x03;
225			setup.wValue = 0x0001;
226			setup.wIndex = iface_no;
227			setup.wLength = 0x0002;
228
229			if (libusb20_dev_request_sync(p->usb_dev, &setup, buf, NULL, 250, 0)) {
230				p->errors++;
231			}
232		}
233		if (flag & 2) {
234			setup.bmRequestType = UT_WRITE_CLASS_INTERFACE;
235			setup.bRequest = UCDC_SET_COMM_FEATURE;
236			setup.wValue = feature;
237			setup.wIndex = iface_no;
238			setup.wLength = UCDC_ABSTRACT_STATE_LENGTH;
239			USETW(ast.wState, state);
240
241			if (libusb20_dev_request_sync(p->usb_dev, &setup, &ast, NULL, 250, 0)) {
242				p->errors++;
243			}
244		}
245		if (flag & 4) {
246			USETDW(ls.dwDTERate, 115200);
247			ls.bCharFormat = UCDC_STOP_BIT_1;
248			ls.bParityType = UCDC_PARITY_NONE;
249			ls.bDataBits = 8;
250
251			setup.bmRequestType = UT_WRITE_CLASS_INTERFACE;
252			setup.bRequest = UCDC_SET_LINE_CODING;
253			setup.wValue = 0;
254			setup.wIndex = iface_no;
255			setup.wLength = sizeof(ls);
256
257			if (libusb20_dev_request_sync(p->usb_dev, &setup, &ls, NULL, 250, 0)) {
258				p->errors++;
259			}
260		}
261		iter++;
262	}
263
264	printf("\nModem control endpoint test done!\n");
265}
266
267static void
268usb_modem_data_stress_test(struct modem *p, uint32_t duration)
269{
270	struct timeval sub_tv;
271	struct timeval ref_tv;
272	struct timeval res_tv;
273
274	time_t last_sec;
275
276	uint8_t in_pending = 0;
277	uint8_t in_ready = 0;
278	uint8_t out_pending = 0;
279
280	uint32_t id = 0;
281
282	uint32_t in_max;
283	uint32_t out_max;
284	uint32_t io_max;
285
286	uint8_t *in_buffer = 0;
287	uint8_t *out_buffer = 0;
288
289	gettimeofday(&ref_tv, 0);
290
291	last_sec = ref_tv.tv_sec;
292
293	printf("\n");
294
295	in_max = libusb20_tr_get_max_total_length(p->xfer_in);
296	out_max = libusb20_tr_get_max_total_length(p->xfer_out);
297
298	/* get the smallest buffer size and use that */
299	io_max = (in_max < out_max) ? in_max : out_max;
300
301	if (in_max != out_max)
302		printf("WARNING: Buffer sizes are un-equal: %u vs %u\n", in_max, out_max);
303
304	in_buffer = malloc(io_max);
305	if (in_buffer == NULL)
306		goto fail;
307
308	out_buffer = malloc(io_max);
309	if (out_buffer == NULL)
310		goto fail;
311
312	while (1) {
313
314		gettimeofday(&sub_tv, 0);
315
316		if (last_sec != sub_tv.tv_sec) {
317
318			printf("STATUS: ID=%u, RX=%u bytes/sec, TX=%u bytes/sec, ERR=%d\n",
319			    (int)id,
320			    (int)p->rx_bytes.bytes,
321			    (int)p->tx_bytes.bytes,
322			    (int)p->errors);
323
324			p->rx_bytes.bytes = 0;
325			p->tx_bytes.bytes = 0;
326
327			fflush(stdout);
328
329			last_sec = sub_tv.tv_sec;
330
331			id++;
332		}
333		timersub(&sub_tv, &ref_tv, &res_tv);
334
335		if ((res_tv.tv_sec < 0) || (res_tv.tv_sec >= (int)duration))
336			break;
337
338		libusb20_dev_process(p->usb_dev);
339
340		if (!libusb20_tr_pending(p->xfer_in)) {
341			if (in_pending) {
342				if (libusb20_tr_get_status(p->xfer_in) == 0) {
343					modem_read(in_buffer, libusb20_tr_get_length(p->xfer_in, 0));
344				} else {
345					p->errors++;
346					usleep(10000);
347				}
348				in_pending = 0;
349				in_ready = 1;
350			}
351			if (p->loop_data == 0) {
352				libusb20_tr_setup_bulk(p->xfer_in, in_buffer, io_max, 0);
353				libusb20_tr_start(p->xfer_in);
354				in_pending = 1;
355				in_ready = 0;
356			}
357		}
358		if (!libusb20_tr_pending(p->xfer_out)) {
359
360			uint32_t len;
361			uint32_t dly;
362
363			if (out_pending) {
364				if (libusb20_tr_get_status(p->xfer_out) != 0) {
365					p->errors++;
366					usleep(10000);
367				}
368			}
369			if (p->random_tx_length) {
370				len = ((uint32_t)usb_ts_rand_noise()) % ((uint32_t)io_max);
371			} else {
372				len = io_max;
373			}
374
375			if (p->random_tx_delay) {
376				dly = ((uint32_t)usb_ts_rand_noise()) % 16000U;
377			} else {
378				dly = 0;
379			}
380
381			if (p->loop_data != 0) {
382				if (in_ready != 0) {
383					len = libusb20_tr_get_length(p->xfer_in, 0);
384					memcpy(out_buffer, in_buffer, len);
385					in_ready = 0;
386				} else {
387					len = io_max + 1;
388				}
389				if (!libusb20_tr_pending(p->xfer_in)) {
390					libusb20_tr_setup_bulk(p->xfer_in, in_buffer, io_max, 0);
391					libusb20_tr_start(p->xfer_in);
392					in_pending = 1;
393				}
394			} else {
395				modem_write(out_buffer, len);
396			}
397
398			if (len <= io_max) {
399				libusb20_tr_setup_bulk(p->xfer_out, out_buffer, len, 0);
400
401				if (dly != 0)
402					usleep(dly);
403
404				libusb20_tr_start(p->xfer_out);
405
406				out_pending = 1;
407			}
408		}
409		libusb20_dev_wait_process(p->usb_dev, 500);
410
411		if (libusb20_dev_check_connected(p->usb_dev) != 0) {
412			printf("Device disconnected\n");
413			break;
414		}
415	}
416
417	libusb20_tr_stop(p->xfer_in);
418	libusb20_tr_stop(p->xfer_out);
419
420	printf("\nData stress test done!\n");
421
422fail:
423	if (in_buffer)
424		free(in_buffer);
425	if (out_buffer)
426		free(out_buffer);
427}
428
429static void
430exec_host_modem_test(struct modem *p, struct uaddr uaddr)
431{
432	struct libusb20_device *pdev;
433
434	uint8_t ntest = 0;
435	uint8_t x;
436	uint8_t in_ep;
437	uint8_t out_ep;
438	uint8_t iface;
439
440	int error;
441
442	pdev = find_usb_device(uaddr);
443	if (pdev == NULL) {
444		printf("USB device not found\n");
445		return;
446	}
447
448	if (p->use_vendor_specific)
449		find_usb_endpoints(pdev, 255, 255, 255, 0, &iface, &in_ep, &out_ep, 0);
450	else
451		find_usb_endpoints(pdev, 2, 2, 1, 0, &iface, &in_ep, &out_ep, 1);
452
453	if ((in_ep == 0) || (out_ep == 0)) {
454		printf("Could not find USB endpoints\n");
455		libusb20_dev_free(pdev);
456		return;
457	}
458	printf("Attaching to: %s @ iface %d\n",
459	    libusb20_dev_get_desc(pdev), iface);
460
461	if (libusb20_dev_open(pdev, 2)) {
462		printf("Could not open USB device\n");
463		libusb20_dev_free(pdev);
464		return;
465	}
466	if (libusb20_dev_detach_kernel_driver(pdev, iface)) {
467		printf("WARNING: Could not detach kernel driver\n");
468	}
469	p->xfer_in = libusb20_tr_get_pointer(pdev, 0);
470	error = libusb20_tr_open(p->xfer_in, 65536 / 4, 1, in_ep);
471	if (error) {
472		printf("Could not open USB endpoint %d\n", in_ep);
473		libusb20_dev_free(pdev);
474		return;
475	}
476	p->xfer_out = libusb20_tr_get_pointer(pdev, 1);
477	error = libusb20_tr_open(p->xfer_out, 65536 / 4, 1, out_ep);
478	if (error) {
479		printf("Could not open USB endpoint %d\n", out_ep);
480		libusb20_dev_free(pdev);
481		return;
482	}
483	p->usb_dev = pdev;
484	p->usb_iface = iface;
485	p->errors = 0;
486
487	if (p->control_ep_test)
488		ntest += 7;
489
490	if (p->data_stress_test)
491		ntest += 1;
492
493	if (ntest == 0) {
494		printf("No tests selected\n");
495	} else {
496
497		if (p->control_ep_test) {
498			for (x = 1; x != 8; x++) {
499				usb_modem_control_ep_test(p,
500				    (p->duration + ntest - 1) / ntest, x);
501			}
502		}
503		if (p->data_stress_test) {
504			usb_modem_data_stress_test(p,
505			    (p->duration + ntest - 1) / ntest);
506		}
507	}
508
509	printf("\nDone\n");
510
511	libusb20_dev_free(pdev);
512}
513
514void
515show_host_modem_test(uint8_t level, struct uaddr uaddr, uint32_t duration)
516{
517	uint8_t retval;
518
519	set_defaults(&modem);
520
521	modem.duration = duration;
522
523	while (1) {
524
525		retval = usb_ts_show_menu(level, "Modem Test Parameters",
526		    " 1) Execute Data Stress Test: <%s>\n"
527		    " 2) Execute Modem Control Endpoint Test: <%s>\n"
528		    " 3) Use random transmit length: <%s>\n"
529		    " 4) Use random transmit delay: <%s> ms\n"
530		    " 5) Use vendor specific interface: <%s>\n"
531		    "10) Loop data: <%s>\n"
532		    "13) Set test duration: <%d> seconds\n"
533		    "20) Reset parameters\n"
534		    "30) Start test (VID=0x%04x, PID=0x%04x)\n"
535		    "40) Select another device\n"
536		    " x) Return to previous menu \n",
537		    (modem.data_stress_test ? "YES" : "NO"),
538		    (modem.control_ep_test ? "YES" : "NO"),
539		    (modem.random_tx_length ? "YES" : "NO"),
540		    (modem.random_tx_delay ? "16" : "0"),
541		    (modem.use_vendor_specific ? "YES" : "NO"),
542		    (modem.loop_data ? "YES" : "NO"),
543		    (int)(modem.duration),
544		    (int)uaddr.vid, (int)uaddr.pid);
545
546		switch (retval) {
547		case 0:
548			break;
549		case 1:
550			modem.data_stress_test ^= 1;
551			break;
552		case 2:
553			modem.control_ep_test ^= 1;
554			break;
555		case 3:
556			modem.random_tx_length ^= 1;
557			break;
558		case 4:
559			modem.random_tx_delay ^= 1;
560			break;
561		case 5:
562			modem.use_vendor_specific ^= 1;
563			modem.control_ep_test = 0;
564			break;
565		case 10:
566			modem.loop_data ^= 1;
567			break;
568		case 13:
569			modem.duration = get_integer();
570			break;
571		case 20:
572			set_defaults(&modem);
573			break;
574		case 30:
575			exec_host_modem_test(&modem, uaddr);
576			break;
577		case 40:
578			show_host_device_selection(level + 1, &uaddr);
579			break;
580		default:
581			return;
582		}
583	}
584}
585