xref: /openbmc/linux/drivers/usb/serial/safe_serial.c (revision 664b0bae0b87f69bc9deb098f5e0158b9cf18e04)
1*5fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  * Safe Encapsulated USB Serial Driver
41da177e4SLinus Torvalds  *
5241c80eaSJohan Hovold  *      Copyright (C) 2010 Johan Hovold <jhovold@gmail.com>
61da177e4SLinus Torvalds  *      Copyright (C) 2001 Lineo
71da177e4SLinus Torvalds  *      Copyright (C) 2001 Hewlett-Packard
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  * By:
101da177e4SLinus Torvalds  *      Stuart Lynne <sl@lineo.com>, Tom Rushworth <tbr@lineo.com>
111da177e4SLinus Torvalds  */
121da177e4SLinus Torvalds 
131da177e4SLinus Torvalds /*
1443c8f435SAlan Cox  * The encapsultaion is designed to overcome difficulties with some USB
1543c8f435SAlan Cox  * hardware.
161da177e4SLinus Torvalds  *
171da177e4SLinus Torvalds  * While the USB protocol has a CRC over the data while in transit, i.e. while
1843c8f435SAlan Cox  * being carried over the bus, there is no end to end protection. If the
1943c8f435SAlan Cox  * hardware has any problems getting the data into or out of the USB transmit
2043c8f435SAlan Cox  * and receive FIFO's then data can be lost.
211da177e4SLinus Torvalds  *
2243c8f435SAlan Cox  * This protocol adds a two byte trailer to each USB packet to specify the
2343c8f435SAlan Cox  * number of bytes of valid data and a 10 bit CRC that will allow the receiver
2443c8f435SAlan Cox  * to verify that the entire USB packet was received without error.
251da177e4SLinus Torvalds  *
2643c8f435SAlan Cox  * Because in this case the sender and receiver are the class and function
2743c8f435SAlan Cox  * drivers there is now end to end protection.
281da177e4SLinus Torvalds  *
2943c8f435SAlan Cox  * There is an additional option that can be used to force all transmitted
3043c8f435SAlan Cox  * packets to be padded to the maximum packet size. This provides a work
3143c8f435SAlan Cox  * around for some devices which have problems with small USB packets.
321da177e4SLinus Torvalds  *
331da177e4SLinus Torvalds  * Assuming a packetsize of N:
341da177e4SLinus Torvalds  *
351da177e4SLinus Torvalds  *      0..N-2  data and optional padding
361da177e4SLinus Torvalds  *
371da177e4SLinus Torvalds  *      N-2     bits 7-2 - number of bytes of valid data
381da177e4SLinus Torvalds  *              bits 1-0 top two bits of 10 bit CRC
391da177e4SLinus Torvalds  *      N-1     bottom 8 bits of 10 bit CRC
401da177e4SLinus Torvalds  *
411da177e4SLinus Torvalds  *
421da177e4SLinus Torvalds  *      | Data Length       | 10 bit CRC                                |
431da177e4SLinus Torvalds  *      + 7 . 6 . 5 . 4 . 3 . 2 . 1 . 0 | 7 . 6 . 5 . 4 . 3 . 2 . 1 . 0 +
441da177e4SLinus Torvalds  *
4543c8f435SAlan Cox  * The 10 bit CRC is computed across the sent data, followed by the trailer
4643c8f435SAlan Cox  * with the length set and the CRC set to zero. The CRC is then OR'd into
4743c8f435SAlan Cox  * the trailer.
481da177e4SLinus Torvalds  *
4943c8f435SAlan Cox  * When received a 10 bit CRC is computed over the entire frame including
5043c8f435SAlan Cox  * the trailer and should be equal to zero.
511da177e4SLinus Torvalds  *
521da177e4SLinus Torvalds  * Two module parameters are used to control the encapsulation, if both are
531da177e4SLinus Torvalds  * turned of the module works as a simple serial device with NO
541da177e4SLinus Torvalds  * encapsulation.
551da177e4SLinus Torvalds  *
561da177e4SLinus Torvalds  * See linux/drivers/usbd/serial_fd for a device function driver
571da177e4SLinus Torvalds  * implementation of this.
581da177e4SLinus Torvalds  *
591da177e4SLinus Torvalds  */
601da177e4SLinus Torvalds 
6193ba0f03SGreg Kroah-Hartman #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
621da177e4SLinus Torvalds 
631da177e4SLinus Torvalds #include <linux/kernel.h>
641da177e4SLinus Torvalds #include <linux/errno.h>
655a0e3ad6STejun Heo #include <linux/gfp.h>
661da177e4SLinus Torvalds #include <linux/tty.h>
671da177e4SLinus Torvalds #include <linux/tty_driver.h>
681da177e4SLinus Torvalds #include <linux/tty_flip.h>
691da177e4SLinus Torvalds #include <linux/module.h>
701da177e4SLinus Torvalds #include <linux/spinlock.h>
7143c8f435SAlan Cox #include <linux/uaccess.h>
721da177e4SLinus Torvalds #include <linux/usb.h>
73a969888cSGreg Kroah-Hartman #include <linux/usb/serial.h>
741da177e4SLinus Torvalds 
75ce9d8562SMathieu OTHACEHE static bool safe = true;
76ce9d8562SMathieu OTHACEHE static bool padded = IS_ENABLED(CONFIG_USB_SERIAL_SAFE_PADDED);
771da177e4SLinus Torvalds 
78241c80eaSJohan Hovold #define DRIVER_AUTHOR "sl@lineo.com, tbr@lineo.com, Johan Hovold <jhovold@gmail.com>"
791da177e4SLinus Torvalds #define DRIVER_DESC "USB Safe Encapsulated Serial"
801da177e4SLinus Torvalds 
811da177e4SLinus Torvalds MODULE_AUTHOR(DRIVER_AUTHOR);
821da177e4SLinus Torvalds MODULE_DESCRIPTION(DRIVER_DESC);
831da177e4SLinus Torvalds MODULE_LICENSE("GPL");
841da177e4SLinus Torvalds 
851da177e4SLinus Torvalds module_param(safe, bool, 0);
861da177e4SLinus Torvalds MODULE_PARM_DESC(safe, "Turn Safe Encapsulation On/Off");
871da177e4SLinus Torvalds 
881da177e4SLinus Torvalds module_param(padded, bool, 0);
891da177e4SLinus Torvalds MODULE_PARM_DESC(padded, "Pad to full wMaxPacketSize On/Off");
901da177e4SLinus Torvalds 
911da177e4SLinus Torvalds #define CDC_DEVICE_CLASS                        0x02
921da177e4SLinus Torvalds 
931da177e4SLinus Torvalds #define CDC_INTERFACE_CLASS                     0x02
941da177e4SLinus Torvalds #define CDC_INTERFACE_SUBCLASS                  0x06
951da177e4SLinus Torvalds 
961da177e4SLinus Torvalds #define LINEO_INTERFACE_CLASS                   0xff
971da177e4SLinus Torvalds 
981da177e4SLinus Torvalds #define LINEO_INTERFACE_SUBCLASS_SAFENET        0x01
991da177e4SLinus Torvalds #define LINEO_SAFENET_CRC                       0x01
1001da177e4SLinus Torvalds #define LINEO_SAFENET_CRC_PADDED                0x02
1011da177e4SLinus Torvalds 
1021da177e4SLinus Torvalds #define LINEO_INTERFACE_SUBCLASS_SAFESERIAL     0x02
1031da177e4SLinus Torvalds #define LINEO_SAFESERIAL_CRC                    0x01
1041da177e4SLinus Torvalds #define LINEO_SAFESERIAL_CRC_PADDED             0x02
1051da177e4SLinus Torvalds 
1061da177e4SLinus Torvalds 
1071da177e4SLinus Torvalds #define MY_USB_DEVICE(vend, prod, dc, ic, isc) \
10843c8f435SAlan Cox 	.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
10943c8f435SAlan Cox 		       USB_DEVICE_ID_MATCH_DEV_CLASS | \
11043c8f435SAlan Cox 		       USB_DEVICE_ID_MATCH_INT_CLASS | \
11143c8f435SAlan Cox 		       USB_DEVICE_ID_MATCH_INT_SUBCLASS, \
1121da177e4SLinus Torvalds 	.idVendor = (vend), \
1131da177e4SLinus Torvalds 	.idProduct = (prod),\
1141da177e4SLinus Torvalds 	.bDeviceClass = (dc),\
1151da177e4SLinus Torvalds 	.bInterfaceClass = (ic), \
1161da177e4SLinus Torvalds 	.bInterfaceSubClass = (isc),
1171da177e4SLinus Torvalds 
1185c6b98ddSJohan Hovold static const struct usb_device_id id_table[] = {
11943c8f435SAlan Cox 	{MY_USB_DEVICE(0x49f, 0xffff, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Itsy */
12043c8f435SAlan Cox 	{MY_USB_DEVICE(0x3f0, 0x2101, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Calypso */
12143c8f435SAlan Cox 	{MY_USB_DEVICE(0x4dd, 0x8001, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Iris */
12243c8f435SAlan Cox 	{MY_USB_DEVICE(0x4dd, 0x8002, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Collie */
12343c8f435SAlan Cox 	{MY_USB_DEVICE(0x4dd, 0x8003, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Collie */
12443c8f435SAlan Cox 	{MY_USB_DEVICE(0x4dd, 0x8004, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Collie */
12543c8f435SAlan Cox 	{MY_USB_DEVICE(0x5f9, 0xffff, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)},	/* Sharp tmp */
12643c8f435SAlan Cox 	{}			/* terminating entry  */
1271da177e4SLinus Torvalds };
1281da177e4SLinus Torvalds 
1291da177e4SLinus Torvalds MODULE_DEVICE_TABLE(usb, id_table);
1301da177e4SLinus Torvalds 
1314c4c9432SArjan van de Ven static const __u16 crc10_table[256] = {
13243c8f435SAlan Cox 	0x000, 0x233, 0x255, 0x066, 0x299, 0x0aa, 0x0cc, 0x2ff,
13343c8f435SAlan Cox 	0x301, 0x132, 0x154, 0x367, 0x198, 0x3ab, 0x3cd, 0x1fe,
13443c8f435SAlan Cox 	0x031, 0x202, 0x264, 0x057, 0x2a8, 0x09b, 0x0fd, 0x2ce,
13543c8f435SAlan Cox 	0x330, 0x103, 0x165, 0x356, 0x1a9, 0x39a, 0x3fc, 0x1cf,
13643c8f435SAlan Cox 	0x062, 0x251, 0x237, 0x004, 0x2fb, 0x0c8, 0x0ae, 0x29d,
13743c8f435SAlan Cox 	0x363, 0x150, 0x136, 0x305, 0x1fa, 0x3c9, 0x3af, 0x19c,
13843c8f435SAlan Cox 	0x053, 0x260, 0x206, 0x035, 0x2ca, 0x0f9, 0x09f, 0x2ac,
13943c8f435SAlan Cox 	0x352, 0x161, 0x107, 0x334, 0x1cb, 0x3f8, 0x39e, 0x1ad,
14043c8f435SAlan Cox 	0x0c4, 0x2f7, 0x291, 0x0a2, 0x25d, 0x06e, 0x008, 0x23b,
14143c8f435SAlan Cox 	0x3c5, 0x1f6, 0x190, 0x3a3, 0x15c, 0x36f, 0x309, 0x13a,
14243c8f435SAlan Cox 	0x0f5, 0x2c6, 0x2a0, 0x093, 0x26c, 0x05f, 0x039, 0x20a,
14343c8f435SAlan Cox 	0x3f4, 0x1c7, 0x1a1, 0x392, 0x16d, 0x35e, 0x338, 0x10b,
14443c8f435SAlan Cox 	0x0a6, 0x295, 0x2f3, 0x0c0, 0x23f, 0x00c, 0x06a, 0x259,
14543c8f435SAlan Cox 	0x3a7, 0x194, 0x1f2, 0x3c1, 0x13e, 0x30d, 0x36b, 0x158,
14643c8f435SAlan Cox 	0x097, 0x2a4, 0x2c2, 0x0f1, 0x20e, 0x03d, 0x05b, 0x268,
14743c8f435SAlan Cox 	0x396, 0x1a5, 0x1c3, 0x3f0, 0x10f, 0x33c, 0x35a, 0x169,
14843c8f435SAlan Cox 	0x188, 0x3bb, 0x3dd, 0x1ee, 0x311, 0x122, 0x144, 0x377,
14943c8f435SAlan Cox 	0x289, 0x0ba, 0x0dc, 0x2ef, 0x010, 0x223, 0x245, 0x076,
15043c8f435SAlan Cox 	0x1b9, 0x38a, 0x3ec, 0x1df, 0x320, 0x113, 0x175, 0x346,
15143c8f435SAlan Cox 	0x2b8, 0x08b, 0x0ed, 0x2de, 0x021, 0x212, 0x274, 0x047,
15243c8f435SAlan Cox 	0x1ea, 0x3d9, 0x3bf, 0x18c, 0x373, 0x140, 0x126, 0x315,
15343c8f435SAlan Cox 	0x2eb, 0x0d8, 0x0be, 0x28d, 0x072, 0x241, 0x227, 0x014,
15443c8f435SAlan Cox 	0x1db, 0x3e8, 0x38e, 0x1bd, 0x342, 0x171, 0x117, 0x324,
15543c8f435SAlan Cox 	0x2da, 0x0e9, 0x08f, 0x2bc, 0x043, 0x270, 0x216, 0x025,
15643c8f435SAlan Cox 	0x14c, 0x37f, 0x319, 0x12a, 0x3d5, 0x1e6, 0x180, 0x3b3,
15743c8f435SAlan Cox 	0x24d, 0x07e, 0x018, 0x22b, 0x0d4, 0x2e7, 0x281, 0x0b2,
15843c8f435SAlan Cox 	0x17d, 0x34e, 0x328, 0x11b, 0x3e4, 0x1d7, 0x1b1, 0x382,
15943c8f435SAlan Cox 	0x27c, 0x04f, 0x029, 0x21a, 0x0e5, 0x2d6, 0x2b0, 0x083,
16043c8f435SAlan Cox 	0x12e, 0x31d, 0x37b, 0x148, 0x3b7, 0x184, 0x1e2, 0x3d1,
16143c8f435SAlan Cox 	0x22f, 0x01c, 0x07a, 0x249, 0x0b6, 0x285, 0x2e3, 0x0d0,
16243c8f435SAlan Cox 	0x11f, 0x32c, 0x34a, 0x179, 0x386, 0x1b5, 0x1d3, 0x3e0,
16343c8f435SAlan Cox 	0x21e, 0x02d, 0x04b, 0x278, 0x087, 0x2b4, 0x2d2, 0x0e1,
1641da177e4SLinus Torvalds };
1651da177e4SLinus Torvalds 
16643c8f435SAlan Cox #define CRC10_INITFCS     0x000	/* Initial FCS value */
16743c8f435SAlan Cox #define CRC10_GOODFCS     0x000	/* Good final FCS value */
1681da177e4SLinus Torvalds #define CRC10_FCS(fcs, c) ((((fcs) << 8) & 0x3ff) ^ crc10_table[((fcs) >> 2) & 0xff] ^ (c))
1691da177e4SLinus Torvalds 
1701da177e4SLinus Torvalds /**
1711da177e4SLinus Torvalds  * fcs_compute10 - memcpy and calculate 10 bit CRC across buffer
1721da177e4SLinus Torvalds  * @sp: pointer to buffer
1731da177e4SLinus Torvalds  * @len: number of bytes
1741da177e4SLinus Torvalds  * @fcs: starting FCS
1751da177e4SLinus Torvalds  *
1761da177e4SLinus Torvalds  * Perform a memcpy and calculate fcs using ppp 10bit CRC algorithm. Return
1771da177e4SLinus Torvalds  * new 10 bit FCS.
1781da177e4SLinus Torvalds  */
fcs_compute10(unsigned char * sp,int len,__u16 fcs)1794abf87f4SJoe Perches static inline __u16 fcs_compute10(unsigned char *sp, int len, __u16 fcs)
1801da177e4SLinus Torvalds {
1811da177e4SLinus Torvalds 	for (; len-- > 0; fcs = CRC10_FCS(fcs, *sp++));
1821da177e4SLinus Torvalds 	return fcs;
1831da177e4SLinus Torvalds }
1841da177e4SLinus Torvalds 
safe_process_read_urb(struct urb * urb)18512e2e52cSJohan Hovold static void safe_process_read_urb(struct urb *urb)
1861da177e4SLinus Torvalds {
187cdc97792SMing Lei 	struct usb_serial_port *port = urb->context;
1881da177e4SLinus Torvalds 	unsigned char *data = urb->transfer_buffer;
1891da177e4SLinus Torvalds 	unsigned char length = urb->actual_length;
1906d1bf48eSJohan Hovold 	int actual_length;
1916d1bf48eSJohan Hovold 	__u16 fcs;
1921da177e4SLinus Torvalds 
19312e2e52cSJohan Hovold 	if (!length)
1941da177e4SLinus Torvalds 		return;
1951da177e4SLinus Torvalds 
1966d1bf48eSJohan Hovold 	if (!safe)
1976d1bf48eSJohan Hovold 		goto out;
1986d1bf48eSJohan Hovold 
1998c76d7cdSJohan Hovold 	if (length < 2) {
2008c76d7cdSJohan Hovold 		dev_err(&port->dev, "malformed packet\n");
2018c76d7cdSJohan Hovold 		return;
2028c76d7cdSJohan Hovold 	}
2038c76d7cdSJohan Hovold 
20443c8f435SAlan Cox 	fcs = fcs_compute10(data, length, CRC10_INITFCS);
2056d1bf48eSJohan Hovold 	if (fcs) {
206194343d9SGreg Kroah-Hartman 		dev_err(&port->dev, "%s - bad CRC %x\n", __func__, fcs);
2072e124b4aSJiri Slaby 		return;
2081da177e4SLinus Torvalds 	}
20912e2e52cSJohan Hovold 
2106d1bf48eSJohan Hovold 	actual_length = data[length - 2] >> 2;
2116d1bf48eSJohan Hovold 	if (actual_length > (length - 2)) {
2126d1bf48eSJohan Hovold 		dev_err(&port->dev, "%s - inconsistent lengths %d:%d\n",
2136d1bf48eSJohan Hovold 				__func__, actual_length, length);
2142e124b4aSJiri Slaby 		return;
2156d1bf48eSJohan Hovold 	}
2166d1bf48eSJohan Hovold 	dev_info(&urb->dev->dev, "%s - actual: %d\n", __func__, actual_length);
2176d1bf48eSJohan Hovold 	length = actual_length;
2186d1bf48eSJohan Hovold out:
21905c7cd39SJiri Slaby 	tty_insert_flip_string(&port->port, data, length);
2202e124b4aSJiri Slaby 	tty_flip_buffer_push(&port->port);
2211da177e4SLinus Torvalds }
2221da177e4SLinus Torvalds 
safe_prepare_write_buffer(struct usb_serial_port * port,void * dest,size_t size)223241c80eaSJohan Hovold static int safe_prepare_write_buffer(struct usb_serial_port *port,
224241c80eaSJohan Hovold 						void *dest, size_t size)
2251da177e4SLinus Torvalds {
226241c80eaSJohan Hovold 	unsigned char *buf = dest;
227241c80eaSJohan Hovold 	int count;
228241c80eaSJohan Hovold 	int trailer_len;
229241c80eaSJohan Hovold 	int pkt_len;
2301da177e4SLinus Torvalds 	__u16 fcs;
2311da177e4SLinus Torvalds 
232241c80eaSJohan Hovold 	trailer_len = safe ? 2 : 0;
233241c80eaSJohan Hovold 
234241c80eaSJohan Hovold 	count = kfifo_out_locked(&port->write_fifo, buf, size - trailer_len,
235241c80eaSJohan Hovold 								&port->lock);
236241c80eaSJohan Hovold 	if (!safe)
237241c80eaSJohan Hovold 		return count;
238241c80eaSJohan Hovold 
23943c8f435SAlan Cox 	/* pad if necessary */
240241c80eaSJohan Hovold 	if (padded) {
241241c80eaSJohan Hovold 		pkt_len = size;
242241c80eaSJohan Hovold 		memset(buf + count, '0', pkt_len - count - trailer_len);
243241c80eaSJohan Hovold 	} else {
244241c80eaSJohan Hovold 		pkt_len = count + trailer_len;
245241c80eaSJohan Hovold 	}
246241c80eaSJohan Hovold 
24743c8f435SAlan Cox 	/* set count */
248241c80eaSJohan Hovold 	buf[pkt_len - 2] = count << 2;
249241c80eaSJohan Hovold 	buf[pkt_len - 1] = 0;
2501da177e4SLinus Torvalds 
25143c8f435SAlan Cox 	/* compute fcs and insert into trailer */
252241c80eaSJohan Hovold 	fcs = fcs_compute10(buf, pkt_len, CRC10_INITFCS);
253241c80eaSJohan Hovold 	buf[pkt_len - 2] |= fcs >> 8;
254241c80eaSJohan Hovold 	buf[pkt_len - 1] |= fcs & 0xff;
2551da177e4SLinus Torvalds 
256241c80eaSJohan Hovold 	return pkt_len;
2571da177e4SLinus Torvalds }
2581da177e4SLinus Torvalds 
safe_startup(struct usb_serial * serial)2591da177e4SLinus Torvalds static int safe_startup(struct usb_serial *serial)
2601da177e4SLinus Torvalds {
2618b61d927SJohan Hovold 	struct usb_interface_descriptor	*desc;
2628b61d927SJohan Hovold 
2638b61d927SJohan Hovold 	if (serial->dev->descriptor.bDeviceClass != CDC_DEVICE_CLASS)
2648b61d927SJohan Hovold 		return -ENODEV;
2658b61d927SJohan Hovold 
2668b61d927SJohan Hovold 	desc = &serial->interface->cur_altsetting->desc;
2678b61d927SJohan Hovold 
2688b61d927SJohan Hovold 	if (desc->bInterfaceClass != LINEO_INTERFACE_CLASS)
2698b61d927SJohan Hovold 		return -ENODEV;
2708b61d927SJohan Hovold 	if (desc->bInterfaceSubClass != LINEO_INTERFACE_SUBCLASS_SAFESERIAL)
2718b61d927SJohan Hovold 		return -ENODEV;
2728b61d927SJohan Hovold 
2738b61d927SJohan Hovold 	switch (desc->bInterfaceProtocol) {
2741da177e4SLinus Torvalds 	case LINEO_SAFESERIAL_CRC:
2751da177e4SLinus Torvalds 		break;
2761da177e4SLinus Torvalds 	case LINEO_SAFESERIAL_CRC_PADDED:
277ce9d8562SMathieu OTHACEHE 		padded = true;
2781da177e4SLinus Torvalds 		break;
2791da177e4SLinus Torvalds 	default:
2801da177e4SLinus Torvalds 		return -EINVAL;
2811da177e4SLinus Torvalds 	}
2821da177e4SLinus Torvalds 	return 0;
2831da177e4SLinus Torvalds }
2841da177e4SLinus Torvalds 
285ea65370dSGreg Kroah-Hartman static struct usb_serial_driver safe_device = {
28618fcac35SGreg Kroah-Hartman 	.driver = {
2871da177e4SLinus Torvalds 		.owner =	THIS_MODULE,
288269bda1cSGreg Kroah-Hartman 		.name =		"safe_serial",
28918fcac35SGreg Kroah-Hartman 	},
2901da177e4SLinus Torvalds 	.id_table =		id_table,
2911da177e4SLinus Torvalds 	.num_ports =		1,
29212e2e52cSJohan Hovold 	.process_read_urb =	safe_process_read_urb,
293241c80eaSJohan Hovold 	.prepare_write_buffer =	safe_prepare_write_buffer,
2941da177e4SLinus Torvalds 	.attach =		safe_startup,
2951da177e4SLinus Torvalds };
2961da177e4SLinus Torvalds 
297d860322fSAlan Stern static struct usb_serial_driver * const serial_drivers[] = {
298d860322fSAlan Stern 	&safe_device, NULL
299d860322fSAlan Stern };
300d860322fSAlan Stern 
3018b61d927SJohan Hovold module_usb_serial_driver(serial_drivers, id_table);
302