1/*
2 * Copyright (c) 2007-2008 by Michael Lotz
3 * Heavily based on the original usb_serial driver which is:
4 *
5 * Copyright (c) 2003 by Siarzhuk Zharski <imker@gmx.li>
6 * Distributed under the terms of the MIT License.
7 */
8#include "ACM.h"
9#include "Driver.h"
10
11
12ACMDevice::ACMDevice(usb_device device, uint16 vendorID, uint16 productID,
13	const char *description)
14	:	SerialDevice(device, vendorID, productID, description)
15{
16}
17
18
19status_t
20ACMDevice::AddDevice(const usb_configuration_info *config)
21{
22	TRACE_FUNCALLS("> ACMDevice::AddDevice(0x%08x, 0x%08x)\n", this, config);
23
24	status_t status = ENODEV;
25	uint8 masterIndex = 0;
26	uint8 slaveIndex = 0;
27	usb_cdc_cm_functional_descriptor* cmDesc = NULL;
28	usb_cdc_union_functional_descriptor* unionDesc = NULL;
29
30	// Search ACM Communication Interface
31	for (size_t i = 0; i < config->interface_count && status < B_OK; i++) {
32		usb_interface_info *interface = config->interface[i].active;
33		if (interface == NULL)
34			continue;
35		usb_interface_descriptor *descriptor = interface->descr;
36		if (descriptor == NULL)
37			continue;
38		if (descriptor->interface_class != USB_CDC_COMMUNICATION_INTERFACE_CLASS
39			|| descriptor->interface_subclass != USB_CDC_COMMUNICATION_INTERFACE_ACM_SUBCLASS)
40			continue;
41
42		// Found ACM Communication Interface!
43		// Get functional descriptors of some interest, if any
44		for (size_t j = 0; j < interface->generic_count; j++) {
45			usb_generic_descriptor *generic = &interface->generic[j]->generic;
46			switch (generic->data[0]) {
47				case USB_CDC_CM_FUNCTIONAL_DESCRIPTOR:
48					cmDesc = (usb_cdc_cm_functional_descriptor*)generic;
49					break;
50
51				case USB_CDC_ACM_FUNCTIONAL_DESCRIPTOR:
52					break;
53
54				case USB_CDC_UNION_FUNCTIONAL_DESCRIPTOR:
55					unionDesc = (usb_cdc_union_functional_descriptor*)generic;
56					break;
57			}
58		}
59
60		masterIndex = unionDesc ? unionDesc->master_interface : i;
61		slaveIndex = cmDesc ? cmDesc->data_interface
62			: unionDesc ? unionDesc->slave_interfaces[0] : 0;
63
64		TRACE("ACM device found on configuration #%d: master itf: %d, "
65				"slave/data itf: %d\n", config->descr->configuration,
66				masterIndex, slaveIndex);
67
68		// Some ACM USB devices report the wrong unions which rightfully
69		// breaks probing. Some drivers keep a list of these devices,
70		// for now we just assume identical indexes are wrong.
71		if (masterIndex == slaveIndex) {
72			TRACE_ALWAYS("Command interface matches data interface, "
73				"assuming broken union quirk!\n");
74			masterIndex = 0;
75			slaveIndex = 1;
76		}
77
78		status = B_OK;
79		break;
80	}
81
82	if (status == B_OK && masterIndex < config->interface_count) {
83		// check that the indicated master interface fits our need
84		usb_interface_info *interface = config->interface[masterIndex].active;
85		usb_interface_descriptor *descriptor = interface->descr;
86		if ((descriptor->interface_class == USB_CDC_COMMUNICATION_INTERFACE_CLASS
87			|| descriptor->interface_class == USB_CDC_DATA_INTERFACE_CLASS)
88			&& interface->endpoint_count >= 1) {
89			SetControlPipe(interface->endpoint[0].handle);
90			SetInterruptBufferSize(interface->endpoint[0].descr->max_packet_size);
91		} else {
92			TRACE("Indicated command interface doesn't fit our needs!\n");
93			status = ENODEV;
94		}
95	}
96
97	if (status == B_OK && slaveIndex < config->interface_count) {
98		// check that the indicated slave interface fits our need
99		usb_interface_info *interface = config->interface[slaveIndex].active;
100		usb_interface_descriptor *descriptor = interface->descr;
101		if (descriptor->interface_class == USB_CDC_DATA_INTERFACE_CLASS
102			&& interface->endpoint_count >= 2) {
103			if (!(interface->endpoint[0].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)) {
104				SetWriteBufferSize(ROUNDUP(interface->endpoint[0].descr->max_packet_size, 16));
105				SetWritePipe(interface->endpoint[0].handle);
106			} else {
107				SetReadBufferSize(ROUNDUP(interface->endpoint[0].descr->max_packet_size, 16));
108				SetReadPipe(interface->endpoint[0].handle);
109			}
110
111			if (interface->endpoint[1].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN) {
112				SetReadBufferSize(ROUNDUP(interface->endpoint[1].descr->max_packet_size, 16));
113				SetReadPipe(interface->endpoint[1].handle);
114			} else {
115				SetWriteBufferSize(ROUNDUP(interface->endpoint[1].descr->max_packet_size, 16));
116				SetWritePipe(interface->endpoint[1].handle);
117			}
118		} else {
119			TRACE("Indicated data interface doesn't fit our needs!\n");
120			status = ENODEV;
121		}
122	}
123
124	TRACE_FUNCRET("< ACMDevice::AddDevice() returns: 0x%08x\n", status);
125	return status;
126}
127
128
129status_t
130ACMDevice::SetLineCoding(usb_cdc_line_coding *lineCoding)
131{
132	TRACE_FUNCALLS("> ACMDevice::SetLineCoding(0x%08x, {%d, 0x%02x, 0x%02x, 0x%02x})\n",
133		this, lineCoding->speed, lineCoding->stopbits, lineCoding->parity,
134		lineCoding->databits);
135
136	size_t length = 0;
137	status_t status = gUSBModule->send_request(Device(),
138		USB_REQTYPE_CLASS | USB_REQTYPE_INTERFACE_OUT,
139		USB_CDC_SET_LINE_CODING, 0, 0,
140		sizeof(usb_cdc_line_coding),
141		lineCoding, &length);
142
143	TRACE_FUNCRET("< ACMDevice::SetLineCoding() returns: 0x%08x\n", status);
144	return status;
145}
146
147
148status_t
149ACMDevice::SetControlLineState(uint16 state)
150{
151	TRACE_FUNCALLS("> ACMDevice::SetControlLineState(0x%08x, 0x%04x)\n", this, state);
152
153	size_t length = 0;
154	status_t status = gUSBModule->send_request(Device(),
155		USB_REQTYPE_CLASS | USB_REQTYPE_INTERFACE_OUT,
156		USB_CDC_SET_CONTROL_LINE_STATE, state, 0, 0, NULL, &length);
157
158	TRACE_FUNCRET("< ACMDevice::SetControlLineState() returns: 0x%08x\n", status);
159	return status;
160}
161