USB serial drivers, Part 4

[Parts 1, 2, 3]

Here are a few DSD coding tips (all code herein is under CDDL).

Naming convention

Module names for STREAMS drivers are limited to 8 characters. By convention a USB serial driver name should start with "usbs", which leaves only 4 characters to identify device vendor. For example, our Keyspan driver will be called "usbsksp". (The Edgeport driver is called "usbser_edge", but that's a bug).

I will be using "usbsxx" for driver name in this blog.

Common driver code

Most Solaris drivers share the same structure: define cb_ops, dev_ops, modldrv and modlinkage structures; STREAMS drivers additionally define module_info and streamtab; then define the soft state opaque pointer; and finally the entry point functions. So do USB serial drivers except they also need to define ds_ops. STREAMS structures look like this:

struct module_info usbsxx_modinfo = {
	0,			/\* module id \*/
	"usbsxx",		/\* module name \*/
	USBSER_MIN_PKTSZ,	/\* min pkt size \*/
	USBSER_MAX_PKTSZ,	/\* max pkt size \*/
	USBSER_HIWAT,		/\* hi watermark \*/
	USBSER_LOWAT		/\* low watermark \*/
};

static struct qinit usbsxx_rinit = {
	putq,
	usbser_rsrv,
	usbsxx_open,
	usbser_close,
	NULL,
	&usbsxx_modinfo,
	NULL
};

static struct qinit usbsxx_winit = {
	usbser_wput,
	usbser_wsrv,
	NULL,
	NULL,
	NULL,
	&usbsxx_modinfo,
	NULL
};

struct streamtab usbsxx_str_info = {
	&usbsxx_rinit, &usbsxx_winit, NULL, NULL
};

Other driver structures are nothing different from any other driver. Don't forget to put streamtab address in cb_ops.

_init(9E) is special in that it should use usbser_soft_state_size() for soft state allocation:

static void	\*usbsxx_statep;	/\* opaque state pointer \*/

int
_init(void)
{
	int    error;

	if ((error = mod_install(&modlinkage)) == 0) {
		error = ddi_soft_state_init(&usbsxx_statep,
		    usbser_soft_state_size(), 1);
	}

	return (error);
}

GSD provides standard implementations of driver entry points, see for instance the qinit structures above. Functions that require the opaque state pointer need to be called explicitly:

int
usbsxx_getinfo(dev_info_t \*dip, ddi_info_cmd_t infocmd, void \*arg,
		void \*\*result)
{
	return (usbser_getinfo(dip, infocmd, arg, result,
	    usbsxx_statep));
}

static int
usbsxx_attach(dev_info_t \*dip, ddi_attach_cmd_t cmd)
{
	return (usbser_attach(dip, cmd, usbsxx_statep, &ds_ops));
}

static int
usbsxx_detach(dev_info_t \*dip, ddi_detach_cmd_t cmd)
{
	return (usbser_detach(dip, cmd, usbsxx_statep));
}

static int
usbsxx_open(queue_t \*rq, dev_t \*dev, int flag, int sflag, cred_t \*cr)
{
	return (usbser_open(rq, dev, flag, sflag, cr, usbsxx_statep));
}

Attach

ds_attach() should allocate and initialize the soft state, configure the device, figure out number of ports and register itself with the USB framework.

static int
usbsxx_ds_attach(ds_attach_info_t \*aip)
{
	usbsxx_state_t	\*xxp;

	xxp = (usbsxx_state_t \*)kmem_zalloc(sizeof (usbsxx_state_t), KM_SLEEP);
	xxp->xx_dip = aip->ai_dip;
	xxp->xx_usb_events = aip->ai_usb_events;
	\*aip->ai_hdl = (ds_hdl_t)xxp;

	if (usb_client_attach(xxp->xx_dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
		usbsxx_cleanup(xxp, 1);
		return (USB_FAILURE);
	}

	if (usb_get_dev_data(xxp->xx_dip, &xxp->xx_dev_data, USB_PARSE_LVL_IF,
	    0) != USB_SUCCESS) {
		usbsxx_cleanup(xxp, 2);
		return (USB_FAILURE);
	}

	xxp->xx_def_pipe_handle = xxp->xx_dev_data->dev_default_ph;
	mutex_init(&xxp->xx_mutex, NULL, MUTEX_DRIVER,
	    xxp->xx_dev_data->dev_iblock_cookie);

	/\* ... device specific code ... \*/

	xxp->xx_dev_state = USB_DEV_ONLINE;

	/\* ... register USB events .. \*/

	\*aip->ai_port_cnt = 1;

	return (USB_SUCCESS);
}

Open and close

Typically ds_open_port() notifies the device of a port open with a special command, does per-port state initialization, opens USB pipes and kicks off data receipt (by submitting a Bulk In request or starting interrupt endpoint polling).

ds_close_port() should dismiss any leftover data characters (GSD is expected to drain and flush before closing, but we want to be on the safe side) and in general reverse what ds_open_port() has done.

static int
usbsxx_open_port(ds_hdl_t hdl, uint_t port_num)
{
	usbsxx_state_t	\*xxp = (usbsxx_state_t \*)hdl;
	usbsxx_port_t	\*pp = &xxp->xx_ports[port_num];

	if (usbsxx_open_pipes(pp) != USB_SUCCESS) {
		return (USB_FAILURE);
	}
	if (usbsxx_send_cmd(pp, USBSXX_HW_OPEN) != USB_SUCCESS) {
		return (USB_FAILURE);
	}
	pp->port_state = USBSXX_PORT_OPEN;
	usbsxx_rx_start(pp); /\* start data receipt \*/

	return (USB_SUCCESS);
}

static int
usbsxx_close_port(ds_hdl_t hdl, uint_t port_num)
{
	usbsxx_state_t	\*xxp = (usbsxx_state_t \*)hdl;
	usbsxx_port_t	\*pp = &xxp->xx_ports[port_num];

	usbsxx_fifo_flush(hdl, port_num, DS_TX | DS_RX);
	usbsxx_rx_stop(pp);
	(void) usbsxx_send_cmd(pp, USBSXX_HW_CLOSE);
	usbsxx_close_pipes(pp);
	pp->port_state = USBSXX_PORT_CLOSED;

	return (USB_SUCCESS);
}

Here is an example of a synchronous command request using Control pipe:

static int
usbsxx_send_cmd(usbsxx_port_t \*pp, uint16_t value, int16_t index)
{
	usb_ctrl_setup_t setup = { USBSXX_HW_WRITE_REQ_TYPE,
			USBSXX_HW_WRITE_REQ, 0, 0,
			USBSXX_HW_WRITE_LENGTH, 0 };
	usb_cb_flags_t	cb_flags;
	usb_cr_t	cr;

	setup.wValue = value;
	setup.wIndex = index;

	return (usb_pipe_ctrl_xfer_wait(pp->ctrl_ph, &setup, NULL,
	    &cr, &cb_flags, 0));
}

Port parameters

Port parameters need to be parsed and turned into device-specific actions. Baud rate may require additional conversion of the baud constants (B9600, etc) into device-specific values, like absolute rate values or UART divisors.

/\* zero means unsupported rate \*/
static int usbsxx_speedtab[] = {
	0,	/\* B0 \*/
	0,	/\* B50 \*/
	75,	/\* B75 \*/
	0,	/\* B110 \*/
	0,	/\* B134 \*/
	150,	/\* B150 \*/
	0,	/\* B200 \*/
	300,	/\* B300 \*/
	600,	/\* B600 \*/
	1200,	/\* B1200 \*/
	1800,	/\* B1800 \*/
	2400,	/\* B2400 \*/
	4800,	/\* B4800 \*/
	9600,	/\* B9600 \*/
	19200,	/\* B19200 \*/
	38400,	/\* B38400 \*/
	57600,	/\* B57600 \*/
	0,	/\* B76800 \*/
	115200,	/\* B115200 \*/
	0,	/\* B153600 \*/
	230400	/\* B230400 \*/
};

#define	NELEM(a)	(sizeof (a) / sizeof (\*(a)))

static int
usbsxx_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t \*tp)
{
	usbsxx_state_t	\*xxp = (usbsxx_state_t \*)hdl;
	usbsxx_port_t	\*pp = &xxp->xx_ports[port_num];
	int		i;
	uint_t		ui;
	ds_port_param_entry_t \*pe;

	pe = tp->tp_entries;
	for (i = 0; i < tp->tp_cnt; i++, pe++) {
		switch (pe->param) {
		case DS_PARAM_BAUD:
			ui = pe->val.ui;

			/\* for unsupported speeds return failure \*/
			if ((ui >= NELEM(usbsxx_speedtab)) ||
			    ((ui > 0) && (usbsxx_speedtab[ui] == 0))) {
				return (USB_FAILURE);
			}

			/\* set baud rate \*/

			break;
		case DS_PARAM_PARITY:
			if (pe->val.ui & PARENB) {
				if (pe->val.ui & PARODD) {
					/\* set odd parity \*/
				} else {
					/\* set even parity \*/
				}
			} else {
				/\* disable parity \*/
			}

			break;
		case DS_PARAM_STOPB:
			if (pe->val.ui & CSTOPB) {
				/\* set stop bits \*/
			} else {
				/\* set stop bits \*/
			}

			break;
		case DS_PARAM_CHARSZ:
			switch (pe->val.ui) {
			case CS5:
				/\* set 5 bits \*/
				break;
			case CS6:
				/\* set 6 bits \*/
				break;
			case CS7:
				/\* set 7 bits \*/
				break;
			case CS8:
			default:
				/\* set 8 bits \*/
				break;
			}

			break;
		case DS_PARAM_XON_XOFF:
			if (pe->val.ui & IXON || pe->val.ui & IXOFF) {
				uint8_t	xon_char, xoff_char;

				xon_char = pe->val.uc[0];
				xoff_char = pe->val.uc[1];

				/\* set XON/XOFF chars \*/
			}

			break;
		case DS_PARAM_FLOW_CTL:
			if (pe->val.ui & CTSXON) {
				/\* enable hardware flow control \*/
			}

			break;
		default:

			break;
		}
	}

	return (USB_SUCCESS);
}

Data transmission

Typical ds_tx() would queue up the data block and kick off the transmission, if not already.

static void
usbsxx_put_tail(mblk_t \*\*mpp, mblk_t \*bp)
{
	if (\*mpp) {
		linkb(\*mpp, bp);
	} else {
		\*mpp = bp;
	}
}

static int
usbsxx_tx(ds_hdl_t hdl, uint_t port_num, mblk_t \*mp)
{
	usbsxx_state_t	\*xxp = (usbsxx_state_t \*)hdl;
	usbsxx_port_t	\*pp = &xxp->xx_ports[port_num];
	int		xferd;

	/\* sanity checks \*/
	if (mp == NULL) {
		return (USB_SUCCESS);
	}
	if (MBLKL(mp) <= 0) {
		freemsg(mp);

		return (USB_SUCCESS);
	}

	mutex_enter(&pp->mutex);
	usbsxx_put_tail(&pp->tx_mp, mp);
	usbsxx_tx_start(pp, &xferd);
	mutex_exit(&pp->mutex);

	return (USB_SUCCESS);
}

usbsxx_tx_start() is device specific, but typically the alrogithm is to take as much data off the queue as the device and the controller (see usb_pipe_get_max_bulk_transfer_size()) allow, submit a bulk request, and when the request completion callback - repeat until no data is left on the queue. Transmission ends by calling GSD's tx_cb() callback. It might also be necessary to wake up the data draining code by signalling the respective conditional variable.

Here's an example of a Bulk Out request:

static int
usbsxx_send_data(usbsxx_port_t \*pp, mblk_t \*data)
{
	usb_bulk_req_t	\*br;
	int		rval;

	br = usb_alloc_bulk_req(pp->dip, 0, USB_FLAGS_SLEEP);
	br->bulk_data = data;
	br->bulk_len = MBLKL(data);
	br->bulk_timeout = USBSXX_BULKOUT_TIMEOUT;
	br->bulk_cb = usbsxx_bulkout_cb;
	br->bulk_exc_cb = usbsxx_bulkout_cb;
	br->bulk_client_private = (usb_opaque_t)pp;
	br->bulk_attributes = USB_ATTRS_AUTOCLEARING;

	rval = usb_pipe_bulk_xfer(pp->bulkout_ph, br, 0);

	if (rval != USB_SUCCESS) {
		usb_free_bulk_req(br);
	}

	return (rval);
}

Data receipt

The driver is usually notified of the received data by a Bulk In or an Interrupt callback. The data is added to the list of received data, the GSD callback is invoked and the next request for receive is submitted.

static void
usbsxx_put_head(mblk_t \*\*mpp, mblk_t \*bp)
{
	if (\*mpp) {
		linkb(bp, \*mpp);
	}
	\*mpp = bp;
}

void
usbsxx_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t \*req)
{
	usbsxx_port_t	\*pp = (usbsxx_state_t \*)req->bulk_client_private;
	usbsxx_state_t	\*xxp = pp->soft_state;
	mblk_t		\*data;
	int		data_len;

	data = req->bulk_data;
	data_len = (data) ? MBLKL(data) : 0;

	if ((pp->port_state == USBSXX_PORT_OPEN) && (data_len) &&
	    (req->bulk_completion_reason == USB_CR_OK)) {
		/\* prevent USBA from freeing data along with the request \*/
		req->bulk_data = NULL;	

		/\* save data on the receive list \*/
		usbsxx_put_tail(&pp->rx_mp, data);

		/\* invoke GSD receive callback \*/
		if (pp->cb.cb_rx) {
			pp->cb.cb_rx(pp->cb.cb_arg);
		}
	}

	usb_free_bulk_req(req);

	usbsxx_rx_start(pp); /\* receive more \*/
}

The only thing left for ds_rx() to do is simply return pp->rx_mp.

Flush and drain

static int
usbsxx_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir)
{
	usbsxx_state_t	\*xxp = (usbsxx_state_t \*)hdl;
	usbsxx_port_t	\*pp = &xxp->xx_ports[port_num];

	if ((dir & DS_TX) && pp->tx_mp) {
		freemsg(pp->tx_mp);
		pp->tx_mp = NULL;
	}
	if ((dir & DS_RX) && pp->rx_mp) {
		freemsg(pp->rx_mp);
		pp->rx_mp = NULL;
	}

	return (USB_SUCCESS);
}

Notice that freemsg() is used, but freeb(), because we want to free all b_cont-linked messages.

Data drain can occur at two levels: first draining DSD's internal buffer by waiting on a conditional variable and then draining device's buffer by sending a special command.

Compile and install

USB serial driver modules should be linked with the following parameters:

ld -r -dy -Nmisc/usba -Nmisc/usbser -o usbsxx usbsxx.o

This is to ensure that 'usba' (USB architecture) and 'usbser' (GSD) misc modules are loaded into the kernel memory before DSD is loaded.

Drivers should be installed using the standard add_drv(1M) command. In addition to that, an autopush entry should be added to /etc/iu.ap:

	usbsxx	-1	0	ldterm ttcompat

This is to ensure that ldterm(7M) and ttcompat(7M) are automatically pushed on top of the DSD. Verifying that the entry works is easy:

# strconf < /dev/cua/0
ttcompat
ldterm
usbsxx

That's it, folks. I hope this blog proves useful to someone either writing a new USB serial driver for Solaris or debugging an existing driver. As always, email your comments and questions to artem dot kachitchkin at sun dot com.


Tags:

Comments:

Post a Comment:
Comments are closed for this entry.
About

artem

Search

Top Tags
Archives
« September 2015
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today