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