1 /* $NetBSD: umodem_common.c,v 1.16 2009/09/23 19:07:19 plunky Exp $ */
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Lennart Augustsson (lennart@augustsson.net) at
9 * Carlstedt Research & Technology.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
34 * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf
35 * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf
40 * - Add error recovery in various places; the big problem is what
41 * to do in a callback if there is an error.
42 * - Implement a Call Device for modems without multiplexed commands.
46 #include <sys/cdefs.h>
47 __KERNEL_RCSID(0, "$NetBSD: umodem_common.c,v 1.16 2009/09/23 19:07:19 plunky Exp $");
49 #include <sys/param.h>
50 #include <sys/systm.h>
51 #include <sys/kernel.h>
52 #include <sys/ioctl.h>
56 #include <sys/select.h>
58 #include <sys/vnode.h>
59 #include <sys/device.h>
62 #include <dev/usb/usb.h>
63 #include <dev/usb/usbcdc.h>
65 #include <dev/usb/usbdi.h>
66 #include <dev/usb/usbdi_util.h>
67 #include <dev/usb/usbdevs.h>
68 #include <dev/usb/usb_quirks.h>
70 #include <dev/usb/ucomvar.h>
71 #include <dev/usb/umodemvar.h>
74 #define DPRINTFN(n, x) if (umodemdebug > (n)) logprintf x
77 #define DPRINTFN(n, x)
79 #define DPRINTF(x) DPRINTFN(0, x)
82 * These are the maximum number of bytes transferred per frame.
83 * If some really high speed devices should use this driver they
84 * may need to be increased, but this is good enough for normal modems.
86 * Note: increased from 64/256, to better support EVDO wireless PPP.
87 * The sizes should not be increased further, or there
88 * will be problems with contiguous storage allocation.
90 #define UMODEMIBUFSIZE 4096
91 #define UMODEMOBUFSIZE 4096
93 Static usbd_status
umodem_set_comm_feature(struct umodem_softc
*sc
,
94 int feature
, int state
);
95 Static usbd_status
umodem_set_line_coding(struct umodem_softc
*sc
,
96 usb_cdc_line_state_t
*state
);
98 Static
void umodem_dtr(struct umodem_softc
*, int);
99 Static
void umodem_rts(struct umodem_softc
*, int);
100 Static
void umodem_break(struct umodem_softc
*, int);
101 Static
void umodem_set_line_state(struct umodem_softc
*);
102 Static
void umodem_intr(usbd_xfer_handle
, usbd_private_handle
, usbd_status
);
105 umodem_common_attach(device_ptr_t self
, struct umodem_softc
*sc
,
106 struct usbif_attach_arg
*uaa
, struct ucom_attach_args
*uca
)
108 usbd_device_handle dev
= uaa
->device
;
109 usb_interface_descriptor_t
*id
;
110 usb_endpoint_descriptor_t
*ed
;
118 sc
->sc_ctl_iface
= uaa
->iface
;
123 id
= usbd_get_interface_descriptor(sc
->sc_ctl_iface
);
124 devinfop
= usbd_devinfo_alloc(uaa
->device
, 0);
125 aprint_normal_dev(self
, "%s, iclass %d/%d\n",
126 devinfop
, id
->bInterfaceClass
, id
->bInterfaceSubClass
);
127 usbd_devinfo_free(devinfop
);
129 sc
->sc_ctl_iface_no
= id
->bInterfaceNumber
;
131 /* Get the data interface no. */
132 sc
->sc_data_iface_no
= data_ifcno
=
133 umodem_get_caps(dev
, &sc
->sc_cm_cap
, &sc
->sc_acm_cap
, id
);
135 if (data_ifcno
== -1) {
136 aprint_error_dev(self
, "no pointer to data interface\n");
140 aprint_normal_dev(self
,
141 "data interface %d, has %sCM over data, has %sbreak\n",
142 data_ifcno
, sc
->sc_cm_cap
& USB_CDC_CM_OVER_DATA
? "" : "no ",
143 sc
->sc_acm_cap
& USB_CDC_ACM_HAS_BREAK
? "" : "no ");
145 /* Get the data interface too. */
146 for (i
= 0; i
< uaa
->nifaces
; i
++) {
147 if (uaa
->ifaces
[i
] != NULL
) {
148 id
= usbd_get_interface_descriptor(uaa
->ifaces
[i
]);
149 if (id
!= NULL
&& id
->bInterfaceNumber
== data_ifcno
) {
150 sc
->sc_data_iface
= uaa
->ifaces
[i
];
151 uaa
->ifaces
[i
] = NULL
;
155 if (sc
->sc_data_iface
== NULL
) {
156 aprint_error_dev(self
, "no data interface\n");
161 * Find the bulk endpoints.
162 * Iterate over all endpoints in the data interface and take note.
164 uca
->bulkin
= uca
->bulkout
= -1;
166 id
= usbd_get_interface_descriptor(sc
->sc_data_iface
);
167 for (i
= 0; i
< id
->bNumEndpoints
; i
++) {
168 ed
= usbd_interface2endpoint_descriptor(sc
->sc_data_iface
, i
);
170 aprint_error_dev(self
,
171 "no endpoint descriptor for %d\n)", i
);
174 if (UE_GET_DIR(ed
->bEndpointAddress
) == UE_DIR_IN
&&
175 (ed
->bmAttributes
& UE_XFERTYPE
) == UE_BULK
) {
176 uca
->bulkin
= ed
->bEndpointAddress
;
177 } else if (UE_GET_DIR(ed
->bEndpointAddress
) == UE_DIR_OUT
&&
178 (ed
->bmAttributes
& UE_XFERTYPE
) == UE_BULK
) {
179 uca
->bulkout
= ed
->bEndpointAddress
;
183 if (uca
->bulkin
== -1) {
184 aprint_error_dev(self
, "Could not find data bulk in\n");
187 if (uca
->bulkout
== -1) {
188 aprint_error_dev(self
, "Could not find data bulk out\n");
192 if (usbd_get_quirks(sc
->sc_udev
)->uq_flags
& UQ_ASSUME_CM_OVER_DATA
) {
193 sc
->sc_cm_over_data
= 1;
195 if (sc
->sc_cm_cap
& USB_CDC_CM_OVER_DATA
) {
196 if (sc
->sc_acm_cap
& USB_CDC_ACM_HAS_FEATURE
)
197 err
= umodem_set_comm_feature(sc
,
198 UCDC_ABSTRACT_STATE
, UCDC_DATA_MULTIPLEXED
);
202 aprint_error_dev(self
,
203 "could not set data multiplex mode\n");
206 sc
->sc_cm_over_data
= 1;
211 * The standard allows for notification messages (to indicate things
212 * like a modem hangup) to come in via an interrupt endpoint
213 * off of the control interface. Iterate over the endpoints on
214 * the control interface and see if there are any interrupt
215 * endpoints; if there are, then register it.
218 sc
->sc_ctl_notify
= -1;
219 sc
->sc_notify_pipe
= NULL
;
221 for (i
= 0; i
< id
->bNumEndpoints
; i
++) {
222 ed
= usbd_interface2endpoint_descriptor(sc
->sc_ctl_iface
, i
);
226 if (UE_GET_DIR(ed
->bEndpointAddress
) == UE_DIR_IN
&&
227 (ed
->bmAttributes
& UE_XFERTYPE
) == UE_INTERRUPT
) {
228 aprint_error_dev(self
,
229 "status change notification available\n");
230 sc
->sc_ctl_notify
= ed
->bEndpointAddress
;
236 /* bulkin, bulkout set above */
237 uca
->ibufsize
= UMODEMIBUFSIZE
;
238 uca
->obufsize
= UMODEMOBUFSIZE
;
239 uca
->ibufsizepad
= UMODEMIBUFSIZE
;
241 uca
->device
= sc
->sc_udev
;
242 uca
->iface
= sc
->sc_data_iface
;
245 usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH
, sc
->sc_udev
,
248 DPRINTF(("umodem_common_attach: sc=%p\n", sc
));
249 sc
->sc_subdev
= config_found_sm_loc(self
, "ucombus", NULL
, uca
,
250 ucomprint
, ucomsubmatch
);
260 umodem_open(void *addr
, int portno
)
262 struct umodem_softc
*sc
= addr
;
265 DPRINTF(("umodem_open: sc=%p\n", sc
));
267 if (sc
->sc_ctl_notify
!= -1 && sc
->sc_notify_pipe
== NULL
) {
268 err
= usbd_open_pipe_intr(sc
->sc_ctl_iface
, sc
->sc_ctl_notify
,
269 USBD_SHORT_XFER_OK
, &sc
->sc_notify_pipe
, sc
,
270 &sc
->sc_notify_buf
, sizeof(sc
->sc_notify_buf
),
271 umodem_intr
, USBD_DEFAULT_INTERVAL
);
274 DPRINTF(("Failed to establish notify pipe: %s\n",
284 umodem_close(void *addr
, int portno
)
286 struct umodem_softc
*sc
= addr
;
289 DPRINTF(("umodem_close: sc=%p\n", sc
));
291 if (sc
->sc_notify_pipe
!= NULL
) {
292 err
= usbd_abort_pipe(sc
->sc_notify_pipe
);
294 printf("%s: abort notify pipe failed: %s\n",
295 USBDEVNAME(sc
->sc_dev
), usbd_errstr(err
));
296 err
= usbd_close_pipe(sc
->sc_notify_pipe
);
298 printf("%s: close notify pipe failed: %s\n",
299 USBDEVNAME(sc
->sc_dev
), usbd_errstr(err
));
300 sc
->sc_notify_pipe
= NULL
;
305 umodem_intr(usbd_xfer_handle xfer
, usbd_private_handle priv
,
308 struct umodem_softc
*sc
= priv
;
314 if (status
!= USBD_NORMAL_COMPLETION
) {
315 if (status
== USBD_NOT_STARTED
|| status
== USBD_CANCELLED
)
317 printf("%s: abnormal status: %s\n", USBDEVNAME(sc
->sc_dev
),
318 usbd_errstr(status
));
322 if (sc
->sc_notify_buf
.bmRequestType
!= UCDC_NOTIFICATION
) {
323 DPRINTF(("%s: unknown message type (%02x) on notify pipe\n",
324 USBDEVNAME(sc
->sc_dev
),
325 sc
->sc_notify_buf
.bmRequestType
));
329 switch (sc
->sc_notify_buf
.bNotification
) {
330 case UCDC_N_SERIAL_STATE
:
332 * Set the serial state in ucom driver based on
333 * the bits from the notify message
335 if (UGETW(sc
->sc_notify_buf
.wLength
) != 2) {
336 printf("%s: Invalid notification length! (%d)\n",
337 USBDEVNAME(sc
->sc_dev
),
338 UGETW(sc
->sc_notify_buf
.wLength
));
341 DPRINTF(("%s: notify bytes = %02x%02x\n",
342 USBDEVNAME(sc
->sc_dev
),
343 sc
->sc_notify_buf
.data
[0],
344 sc
->sc_notify_buf
.data
[1]));
345 /* Currently, lsr is always zero. */
346 sc
->sc_lsr
= sc
->sc_msr
= 0;
347 mstatus
= sc
->sc_notify_buf
.data
[0];
349 if (ISSET(mstatus
, UCDC_N_SERIAL_RI
))
350 sc
->sc_msr
|= UMSR_RI
;
351 if (ISSET(mstatus
, UCDC_N_SERIAL_DSR
))
352 sc
->sc_msr
|= UMSR_DSR
;
353 if (ISSET(mstatus
, UCDC_N_SERIAL_DCD
))
354 sc
->sc_msr
|= UMSR_DCD
;
355 ucom_status_change(device_private(sc
->sc_subdev
));
358 DPRINTF(("%s: unknown notify message: %02x\n",
359 USBDEVNAME(sc
->sc_dev
),
360 sc
->sc_notify_buf
.bNotification
));
366 umodem_get_caps(usbd_device_handle dev
, int *cm
, int *acm
,
367 usb_interface_descriptor_t
*id
)
369 const usb_cdc_cm_descriptor_t
*cmd
;
370 const usb_cdc_acm_descriptor_t
*cad
;
371 const usb_cdc_union_descriptor_t
*cud
;
375 cmd
= (const usb_cdc_cm_descriptor_t
*)usb_find_desc_if(dev
,
377 UDESCSUB_CDC_CM
, id
);
379 DPRINTF(("umodem_get_caps: no CM desc\n"));
381 *cm
= cmd
->bmCapabilities
;
384 cad
= (const usb_cdc_acm_descriptor_t
*)usb_find_desc_if(dev
,
389 DPRINTF(("umodem_get_caps: no ACM desc\n"));
391 *acm
= cad
->bmCapabilities
;
394 cud
= (const usb_cdc_union_descriptor_t
*)usb_find_desc_if(dev
,
399 DPRINTF(("umodem_get_caps: no UNION desc\n"));
402 return cmd
? cmd
->bDataInterface
: cud
? cud
->bSlaveInterface
[0] : -1;
406 umodem_get_status(void *addr
, int portno
, u_char
*lsr
, u_char
*msr
)
408 struct umodem_softc
*sc
= addr
;
410 DPRINTF(("umodem_get_status:\n"));
419 umodem_param(void *addr
, int portno
, struct termios
*t
)
421 struct umodem_softc
*sc
= addr
;
423 usb_cdc_line_state_t ls
;
425 DPRINTF(("umodem_param: sc=%p\n", sc
));
427 USETDW(ls
.dwDTERate
, t
->c_ospeed
);
428 if (ISSET(t
->c_cflag
, CSTOPB
))
429 ls
.bCharFormat
= UCDC_STOP_BIT_2
;
431 ls
.bCharFormat
= UCDC_STOP_BIT_1
;
432 if (ISSET(t
->c_cflag
, PARENB
)) {
433 if (ISSET(t
->c_cflag
, PARODD
))
434 ls
.bParityType
= UCDC_PARITY_ODD
;
436 ls
.bParityType
= UCDC_PARITY_EVEN
;
438 ls
.bParityType
= UCDC_PARITY_NONE
;
439 switch (ISSET(t
->c_cflag
, CSIZE
)) {
454 err
= umodem_set_line_coding(sc
, &ls
);
456 DPRINTF(("umodem_param: err=%s\n", usbd_errstr(err
)));
457 return (EPASSTHROUGH
);
463 umodem_ioctl(void *addr
, int portno
, u_long cmd
, void *data
,
464 int flag
, usb_proc_ptr p
)
466 struct umodem_softc
*sc
= addr
;
472 DPRINTF(("umodem_ioctl: cmd=0x%08lx\n", cmd
));
475 case USB_GET_CM_OVER_DATA
:
476 *(int *)data
= sc
->sc_cm_over_data
;
479 case USB_SET_CM_OVER_DATA
:
480 if (*(int *)data
!= sc
->sc_cm_over_data
) {
486 DPRINTF(("umodem_ioctl: unknown\n"));
487 error
= EPASSTHROUGH
;
495 umodem_dtr(struct umodem_softc
*sc
, int onoff
)
497 DPRINTF(("umodem_dtr: onoff=%d\n", onoff
));
499 if (sc
->sc_dtr
== onoff
)
503 umodem_set_line_state(sc
);
507 umodem_rts(struct umodem_softc
*sc
, int onoff
)
509 DPRINTF(("umodem_rts: onoff=%d\n", onoff
));
511 if (sc
->sc_rts
== onoff
)
515 umodem_set_line_state(sc
);
519 umodem_set_line_state(struct umodem_softc
*sc
)
521 usb_device_request_t req
;
524 ls
= (sc
->sc_dtr
? UCDC_LINE_DTR
: 0) |
525 (sc
->sc_rts
? UCDC_LINE_RTS
: 0);
526 req
.bmRequestType
= UT_WRITE_CLASS_INTERFACE
;
527 req
.bRequest
= UCDC_SET_CONTROL_LINE_STATE
;
528 USETW(req
.wValue
, ls
);
529 USETW(req
.wIndex
, sc
->sc_ctl_iface_no
);
530 USETW(req
.wLength
, 0);
532 (void)usbd_do_request(sc
->sc_udev
, &req
, 0);
537 umodem_break(struct umodem_softc
*sc
, int onoff
)
539 usb_device_request_t req
;
541 DPRINTF(("umodem_break: onoff=%d\n", onoff
));
543 if (!(sc
->sc_acm_cap
& USB_CDC_ACM_HAS_BREAK
))
546 req
.bmRequestType
= UT_WRITE_CLASS_INTERFACE
;
547 req
.bRequest
= UCDC_SEND_BREAK
;
548 USETW(req
.wValue
, onoff
? UCDC_BREAK_ON
: UCDC_BREAK_OFF
);
549 USETW(req
.wIndex
, sc
->sc_ctl_iface_no
);
550 USETW(req
.wLength
, 0);
552 (void)usbd_do_request(sc
->sc_udev
, &req
, 0);
556 umodem_set(void *addr
, int portno
, int reg
, int onoff
)
558 struct umodem_softc
*sc
= addr
;
562 umodem_dtr(sc
, onoff
);
565 umodem_rts(sc
, onoff
);
568 umodem_break(sc
, onoff
);
576 umodem_set_line_coding(struct umodem_softc
*sc
, usb_cdc_line_state_t
*state
)
578 usb_device_request_t req
;
581 DPRINTF(("umodem_set_line_coding: rate=%d fmt=%d parity=%d bits=%d\n",
582 UGETDW(state
->dwDTERate
), state
->bCharFormat
,
583 state
->bParityType
, state
->bDataBits
));
585 if (memcmp(state
, &sc
->sc_line_state
, UCDC_LINE_STATE_LENGTH
) == 0) {
586 DPRINTF(("umodem_set_line_coding: already set\n"));
587 return (USBD_NORMAL_COMPLETION
);
590 req
.bmRequestType
= UT_WRITE_CLASS_INTERFACE
;
591 req
.bRequest
= UCDC_SET_LINE_CODING
;
592 USETW(req
.wValue
, 0);
593 USETW(req
.wIndex
, sc
->sc_ctl_iface_no
);
594 USETW(req
.wLength
, UCDC_LINE_STATE_LENGTH
);
596 err
= usbd_do_request(sc
->sc_udev
, &req
, state
);
598 DPRINTF(("umodem_set_line_coding: failed, err=%s\n",
603 sc
->sc_line_state
= *state
;
605 return (USBD_NORMAL_COMPLETION
);
609 umodem_set_comm_feature(struct umodem_softc
*sc
, int feature
, int state
)
611 usb_device_request_t req
;
613 usb_cdc_abstract_state_t ast
;
615 DPRINTF(("umodem_set_comm_feature: feature=%d state=%d\n", feature
,
618 req
.bmRequestType
= UT_WRITE_CLASS_INTERFACE
;
619 req
.bRequest
= UCDC_SET_COMM_FEATURE
;
620 USETW(req
.wValue
, feature
);
621 USETW(req
.wIndex
, sc
->sc_ctl_iface_no
);
622 USETW(req
.wLength
, UCDC_ABSTRACT_STATE_LENGTH
);
623 USETW(ast
.wState
, state
);
625 err
= usbd_do_request(sc
->sc_udev
, &req
, &ast
);
627 DPRINTF(("umodem_set_comm_feature: feature=%d, err=%s\n",
628 feature
, usbd_errstr(err
)));
632 return (USBD_NORMAL_COMPLETION
);
636 umodem_common_activate(struct umodem_softc
*sc
, enum devact act
)
639 case DVACT_DEACTIVATE
:
648 umodem_common_childdet(struct umodem_softc
*sc
, device_t child
)
650 KASSERT(sc
->sc_subdev
== child
);
651 sc
->sc_subdev
= NULL
;
655 umodem_common_detach(struct umodem_softc
*sc
, int flags
)
659 DPRINTF(("umodem_common_detach: sc=%p flags=%d\n", sc
, flags
));
663 if (sc
->sc_subdev
!= NULL
)
664 rv
= config_detach(sc
->sc_subdev
, flags
);
666 usbd_add_drv_event(USB_EVENT_DRIVER_DETACH
, sc
->sc_udev
,