/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * * DSD code for keyspan usb2serial adapters * */ #include #include #include #include #include #include #include #include #include #define USBDRV_MAJOR_VER 2 #define USBDRV_MINOR_VER 0 #include #include #include #include #include #include /* * DSD operations which are filled in ds_ops structure. */ static int keyspan_attach(ds_attach_info_t *); static void keyspan_detach(ds_hdl_t); static int keyspan_register_cb(ds_hdl_t, uint_t, ds_cb_t *); static void keyspan_unregister_cb(ds_hdl_t, uint_t); static int keyspan_open_port(ds_hdl_t, uint_t); static int keyspan_close_port(ds_hdl_t, uint_t); /* power management */ static int keyspan_usb_power(ds_hdl_t, int, int, int *); static int keyspan_suspend(ds_hdl_t); static int keyspan_resume(ds_hdl_t); /* hotplug */ static int keyspan_disconnect(ds_hdl_t); static int keyspan_reconnect(ds_hdl_t); /* standard UART operations */ static int keyspan_set_port_params(ds_hdl_t, uint_t, ds_port_params_t *); static int keyspan_set_modem_ctl(ds_hdl_t, uint_t, int, int); static int keyspan_get_modem_ctl(ds_hdl_t, uint_t, int, int *); static int keyspan_break_ctl(ds_hdl_t, uint_t, int); static int keyspan_loopback(ds_hdl_t, uint_t, int); /* data xfer */ static int keyspan_tx(ds_hdl_t, uint_t, mblk_t *); static mblk_t *keyspan_rx(ds_hdl_t, uint_t); static void keyspan_stop(ds_hdl_t, uint_t, int); static void keyspan_start(ds_hdl_t, uint_t, int); static int keyspan_fifo_flush(ds_hdl_t, uint_t, int); static int keyspan_fifo_drain(ds_hdl_t, uint_t, int); /* * Sub-routines */ /* configuration routines */ static void keyspan_free_soft_state(keyspan_state_t *); static void keyspan_init_sync_objs(keyspan_state_t *); static void keyspan_fini_sync_objs(keyspan_state_t *); static int keyspan_usb_register(keyspan_state_t *); static void keyspan_usb_unregister(keyspan_state_t *); static int keyspan_attach_dev(keyspan_state_t *); static void keyspan_attach_ports(keyspan_state_t *); static void keyspan_detach_ports(keyspan_state_t *); static void keyspan_init_port_params(keyspan_state_t *); static void keyspan_free_descr_tree(keyspan_state_t *); static int keyspan_register_events(keyspan_state_t *); static void keyspan_unregister_events(keyspan_state_t *); static void keyspan_set_dev_state_online(keyspan_state_t *); /* hotplug */ static int keyspan_restore_device_state(keyspan_state_t *); static int keyspan_restore_ports_state(keyspan_state_t *); /* power management */ static int keyspan_create_pm_components(keyspan_state_t *); static void keyspan_destroy_pm_components(keyspan_state_t *); static int keyspan_pm_set_busy(keyspan_state_t *); static void keyspan_pm_set_idle(keyspan_state_t *); static int keyspan_pwrlvl0(keyspan_state_t *); static int keyspan_pwrlvl1(keyspan_state_t *); static int keyspan_pwrlvl2(keyspan_state_t *); static int keyspan_pwrlvl3(keyspan_state_t *); /* pipe operations */ static int keyspan_attach_pipes(keyspan_state_t *); static void keyspan_detach_pipes(keyspan_state_t *); static void keyspan_disconnect_pipes(keyspan_state_t *); static int keyspan_reconnect_pipes(keyspan_state_t *); /* data transfer routines */ static int keyspan_wait_tx_drain(keyspan_port_t *, int); /* misc */ static void keyspan_default_port_params(keyspan_port_t *); static void keyspan_build_cmd_msg(keyspan_port_t *, ds_port_params_t *); static void keyspan_save_port_params(keyspan_port_t *); /* * Model specific functions. */ /* usa19hs specific functions */ static void keyspan_build_cmd_msg_usa19hs(keyspan_port_t *, ds_port_params_t *); static void keyspan_default_port_params_usa19hs(keyspan_port_t *); static void keyspan_save_port_params_usa19hs(keyspan_port_t *); /* usa49 specific functions */ static void keyspan_build_cmd_msg_usa49(keyspan_port_t *, ds_port_params_t *); static void keyspan_default_port_params_usa49(keyspan_port_t *); static void keyspan_save_port_params_usa49(keyspan_port_t *); /* * DSD ops structure */ ds_ops_t keyspan_ds_ops = { DS_OPS_VERSION, keyspan_attach, keyspan_detach, keyspan_register_cb, keyspan_unregister_cb, keyspan_open_port, keyspan_close_port, keyspan_usb_power, keyspan_suspend, keyspan_resume, keyspan_disconnect, keyspan_reconnect, keyspan_set_port_params, keyspan_set_modem_ctl, keyspan_get_modem_ctl, keyspan_break_ctl, keyspan_loopback, keyspan_tx, keyspan_rx, keyspan_stop, keyspan_start, keyspan_fifo_flush, keyspan_fifo_drain }; /* * For USA19HS baud speed, precalculated using the following algorithm: * * speed = (uint16_t)(14769231L / baud); */ static uint16_t keyspan_speedtab_usa19hs[] = { 0x0, /* B0 */ 0x481d, /* B50 */ 0x3013, /* B75 */ 0x20c7, /* B110 */ 0x1ae8, /* B134 */ 0x1809, /* B150 */ 0x1207, /* B200 */ 0xc04, /* B300 */ 0x602, /* B600 */ 0x301, /* B1200 */ 0x200, /* B1800 */ 0x180, /* B2400 */ 0xc0, /* B4800 */ 0x60, /* B9600 */ 0x30, /* B19200 */ 0x18, /* B38400 */ 0x10, /* B57600 */ 0xc, /* B76800 */ 0x8, /* B115200 */ 0x6, /* B153600 */ 0x4, /* B230400 */ }; /* * For USA49WLC baud speed, precalculated. */ static uint16_t keyspan_speedtab_usa49[] = { 0x0, /* B0 */ 0x7530, /* B50 */ 0x4e20, /* B75 */ 0x3544, /* B110 */ 0x2bba, /* B134 */ 0x2710, /* B150 */ 0x1d4c, /* B200 */ 0x1388, /* B300 */ 0x9c4, /* B600 */ 0x4e2, /* B1200 */ 0x25e, /* B1800 */ 0x271, /* B2400 */ 0xfa, /* B4800 */ 0x7d, /* B9600 */ 0x19, /* B19200 */ 0x27, /* B38400 */ 0x1a, /* B57600 */ 0xd, /* B76800 */ 0xd, /* B115200 */ 0x6, /* B153600 */ 0x4, /* B230400 */ }; /* * For USA49WLC prescaler, precalculated. */ static uint8_t keyspan_prescaler_49wlc[] = { 0x0, /* B0 */ 0x8, /* B50 */ 0x8, /* B75 */ 0x8, /* B110 */ 0x8, /* B134 */ 0x8, /* B150 */ 0x8, /* B200 */ 0x8, /* B300 */ 0x8, /* B600 */ 0x8, /* B1200 */ 0xb, /* B1800 */ 0x8, /* B2400 */ 0xa, /* B4800 */ 0xa, /* B9600 */ 0x19, /* B19200 */ 0x8, /* B38400 */ 0x8, /* B57600 */ 0xc, /* B76800 */ 0x8, /* B115200 */ 0xd, /* B153600 */ 0xd, /* B230400 */ }; /* convert baud code into baud rate */ static int keyspan_speed2baud[] = { 0, /* B0 */ 50, /* B50 */ 75, /* B75 */ 110, /* B110 */ 134, /* B134 */ 150, /* B150 */ 200, /* B200 */ 300, /* B300 */ 600, /* B600 */ 1200, /* B1200 */ 1800, /* B1800 */ 2400, /* B2400 */ 4800, /* B4800 */ 9600, /* B9600 */ 19200, /* B19200 */ 38400, /* B38400 */ 57600, /* B57600 */ 76800, /* B76800 */ 115200, /* B115200 */ 153600, /* B153600 */ 230400, /* B230400 */ }; /* debug support */ static uint_t keyspan_errlevel = USB_LOG_L4; static uint_t keyspan_errmask = DPRINT_MASK_ALL; static uint_t keyspan_instance_debug = (uint_t)-1; static int keyspan_attach(ds_attach_info_t *aip) { keyspan_state_t *ksp; int rval = USB_SUCCESS; ksp = (keyspan_state_t *)kmem_zalloc(sizeof (keyspan_state_t), KM_SLEEP); ksp->ks_dip = aip->ai_dip; ksp->ks_usb_events = aip->ai_usb_events; *aip->ai_hdl = (ds_hdl_t)ksp; if (keyspan_usb_register(ksp) != USB_SUCCESS) { goto fail_register; } /* init mutex and semaphore */ keyspan_init_sync_objs(ksp); /* get device specific parameters */ if (keyspan_attach_dev(ksp) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "fail attach dev "); goto fail_attach_dev; } keyspan_attach_ports(ksp); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: case KEYSPAN_USA49WLC_PID: rval = keyspan_init_pipes(ksp); break; case KEYSPAN_USA49WG_PID: rval = keyspan_init_pipes_usa49wg(ksp); break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_attach:" "the device's product id can't be recognized"); return (USB_FAILURE); } if (rval != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_init_pipes: failed."); goto fail_init_pipes; } keyspan_init_port_params(ksp); keyspan_free_descr_tree(ksp); keyspan_set_dev_state_online(ksp); if (keyspan_create_pm_components(ksp) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_create_pm_components: failed."); goto fail_pm; } if (keyspan_register_events(ksp) != USB_SUCCESS) { goto fail_events; } /* open the global pipes */ if (keyspan_attach_pipes(ksp) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_attach_pipes: failed."); goto fail_attach_pipes; } *aip->ai_port_cnt = ksp->ks_dev_spec.port_cnt; return (USB_SUCCESS); fail_attach_pipes: keyspan_unregister_events(ksp); fail_events: keyspan_destroy_pm_components(ksp); fail_pm: keyspan_fini_pipes(ksp); fail_init_pipes: keyspan_detach_ports(ksp); fail_attach_dev: keyspan_fini_sync_objs(ksp); keyspan_usb_unregister(ksp); fail_register: keyspan_free_soft_state(ksp); return (USB_FAILURE); } /* * ds_detach */ static void keyspan_detach(ds_hdl_t hdl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_detach_pipes(ksp); keyspan_unregister_events(ksp); keyspan_destroy_pm_components(ksp); keyspan_fini_pipes(ksp); keyspan_detach_ports(ksp); keyspan_fini_sync_objs(ksp); keyspan_usb_unregister(ksp); keyspan_free_soft_state(ksp); } /* * ds_register_cb */ static int keyspan_register_cb(ds_hdl_t hdl, uint_t port_num, ds_cb_t *cb) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp; if (port_num >= ksp->ks_dev_spec.port_cnt) { return (USB_FAILURE); } kp = &ksp->ks_ports[port_num]; kp->kp_cb = *cb; return (USB_SUCCESS); } /* * ds_unregister_cb */ static void keyspan_unregister_cb(ds_hdl_t hdl, uint_t port_num) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp; if (port_num < ksp->ks_dev_spec.port_cnt) { kp = &ksp->ks_ports[port_num]; bzero(&kp->kp_cb, sizeof (kp->kp_cb)); } } /* * initialize hardware serial port * * 'open_pipes' specifies whether to open USB pipes or not */ int keyspan_open_hw_port(keyspan_port_t *kp, boolean_t open_pipes) { int rval; keyspan_state_t *ksp = kp->kp_ksp; USB_DPRINTF_L4(DPRINT_OPEN, kp->kp_lh, "keyspan_open_hw_port: [%d]", kp->kp_port_num); if (open_pipes) { /* open r/w pipes for this port */ if ((rval = keyspan_open_port_pipes(kp)) != USB_SUCCESS) { return (rval); } } mutex_enter(&kp->kp_mutex); kp->kp_state = KEYSPAN_PORT_OPEN; mutex_exit(&kp->kp_mutex); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: case KEYSPAN_USA49WLC_PID: if ((rval = keyspan_receive_data(&kp->kp_datain_pipe, kp->kp_read_len, kp)) != USB_SUCCESS) { goto fail; } break; case KEYSPAN_USA49WG_PID: mutex_enter(&ksp->ks_mutex); /* open data in pipe the first time, start receiving data */ if ((ksp->ks_datain_open_cnt == 1) && open_pipes) { mutex_exit(&ksp->ks_mutex); if ((rval = keyspan_receive_data(&kp->kp_datain_pipe, kp->kp_read_len, kp)) != USB_SUCCESS) { goto fail; } /* the device is reconnected to host, restart receiving data */ } else if ((ksp->ks_reconnect_flag) && (!open_pipes)) { mutex_exit(&ksp->ks_mutex); if ((rval = keyspan_receive_data(&kp->kp_datain_pipe, kp->kp_read_len, kp)) != USB_SUCCESS) { goto fail; } mutex_enter(&ksp->ks_mutex); ksp->ks_reconnect_flag = 0; mutex_exit(&ksp->ks_mutex); } else { mutex_exit(&ksp->ks_mutex); } break; default: USB_DPRINTF_L2(DPRINT_OPEN, ksp->ks_lh, "keyspan_open_hw_port:" "the device's product id can't be recognized"); return (USB_FAILURE); } /* set the default port parameters and send cmd msg to enable port */ mutex_enter(&kp->kp_mutex); keyspan_default_port_params(kp); mutex_exit(&kp->kp_mutex); (void) keyspan_send_cmd(kp); USB_DPRINTF_L4(DPRINT_OPEN, kp->kp_lh, "keyspan_open_hw_port: [%d] finished", kp->kp_port_num); return (rval); fail: mutex_enter(&kp->kp_mutex); kp->kp_state = KEYSPAN_PORT_CLOSED; mutex_exit(&kp->kp_mutex); if (open_pipes) { /* close all ports' data pipes */ keyspan_close_port_pipes(kp); } USB_DPRINTF_L2(DPRINT_OPEN, kp->kp_lh, "keyspan_open_hw_port: failed. This port can't be used."); return (rval); } /* * ds_open_port */ static int keyspan_open_port(ds_hdl_t hdl, uint_t port_num) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; int rval; if (port_num >= ksp->ks_dev_spec.port_cnt) { return (USB_FAILURE); } USB_DPRINTF_L4(DPRINT_OPEN, kp->kp_lh, "keyspan_open_port"); mutex_enter(&ksp->ks_mutex); if (ksp->ks_dev_state == USB_DEV_DISCONNECTED) { mutex_exit(&ksp->ks_mutex); return (USB_FAILURE); } mutex_exit(&ksp->ks_mutex); if (keyspan_pm_set_busy(ksp) != USB_SUCCESS) { return (USB_FAILURE); } /* * initialize state */ mutex_enter(&kp->kp_mutex); ASSERT(kp->kp_state == KEYSPAN_PORT_CLOSED); ASSERT((kp->kp_rx_mp == NULL) && (kp->kp_tx_mp == NULL)); kp->kp_state = KEYSPAN_PORT_OPENING; kp->kp_flags = 0; mutex_exit(&kp->kp_mutex); /* * initialize hardware serial port, B_TRUE means open pipes */ sema_p(&ksp->ks_pipes_sema); rval = keyspan_open_hw_port(kp, B_TRUE); if (rval != USB_SUCCESS) { keyspan_pm_set_idle(ksp); } sema_v(&ksp->ks_pipes_sema); return (rval); } /* * close hardware serial port */ void keyspan_close_hw_port(keyspan_port_t *kp) { keyspan_state_t *ksp = kp->kp_ksp; ASSERT(!mutex_owned(&kp->kp_mutex)); USB_DPRINTF_L4(DPRINT_CLOSE, kp->kp_lh, "keyspan_close_hw_port"); /* * The bulk IN/OUT pipes might have got closed due to * a device disconnect event. So its required to check the * pipe handle and proceed if it is not NULL */ mutex_enter(&kp->kp_mutex); if ((kp->kp_datain_pipe.pipe_handle == NULL) && (kp->kp_dataout_pipe.pipe_handle == NULL)) { mutex_exit(&kp->kp_mutex); return; } switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: keyspan_build_cmd_msg_usa19hs(kp, NULL); kp->kp_ctrl_msg.usa19hs.portEnabled = 0; kp->kp_ctrl_msg.usa19hs.rxFlush = 0; kp->kp_ctrl_msg.usa19hs.txFlush = 0; kp->kp_ctrl_msg.usa19hs.returnStatus = 0; kp->kp_ctrl_msg.usa19hs.setRts = 1; kp->kp_ctrl_msg.usa19hs.rts = 0; kp->kp_ctrl_msg.usa19hs.setDtr = 1; kp->kp_ctrl_msg.usa19hs.dtr = 0; kp->kp_ctrl_msg.usa19hs.setTxFlowControl = 1; kp->kp_ctrl_msg.usa19hs.txFlowControl = 0; kp->kp_ctrl_msg.usa19hs.setRxFlowControl = 1; kp->kp_ctrl_msg.usa19hs.rxFlowControl = 0; kp->kp_ctrl_msg.usa19hs.rxForwardingTimeout = 0; kp->kp_ctrl_msg.usa19hs.rxForwardingLength = 0; break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: keyspan_build_cmd_msg_usa49(kp, NULL); kp->kp_ctrl_msg.usa49._txOn = 0; kp->kp_ctrl_msg.usa49._txOff = 1; kp->kp_ctrl_msg.usa49.txFlush = 0; kp->kp_ctrl_msg.usa49.txBreak = 0; kp->kp_ctrl_msg.usa49.rxOn = 0; kp->kp_ctrl_msg.usa49.rxOff = 1; kp->kp_ctrl_msg.usa49.rxFlush = 0; kp->kp_ctrl_msg.usa49.rxForward = 0; kp->kp_ctrl_msg.usa49.returnStatus = 0; kp->kp_ctrl_msg.usa49.resetDataToggle = 0; kp->kp_ctrl_msg.usa49.enablePort = 0; kp->kp_ctrl_msg.usa49.disablePort = 1; break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_close_hw_port:" "the device's product id can't be recognized"); mutex_exit(&kp->kp_mutex); return; } mutex_exit(&kp->kp_mutex); /* send close port cmd to this port */ if (keyspan_send_cmd(kp) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_close_hw_port: closing hw port, send cmd FAILED"); } /* blow away bulkin requests or pipe close will wait until timeout */ switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: case KEYSPAN_USA49WLC_PID: usb_pipe_reset(ksp->ks_dip, kp->kp_datain_pipe.pipe_handle, USB_FLAGS_SLEEP, NULL, NULL); break; case KEYSPAN_USA49WG_PID: mutex_enter(&ksp->ks_mutex); /* * if only this port is opened, shared data in pipe * can be reset. */ if (ksp->ks_datain_open_cnt == 1) { mutex_exit(&ksp->ks_mutex); usb_pipe_reset(ksp->ks_dip, kp->kp_datain_pipe.pipe_handle, USB_FLAGS_SLEEP, NULL, NULL); } else { mutex_exit(&ksp->ks_mutex); } break; default: USB_DPRINTF_L2(DPRINT_CLOSE, kp->kp_lh, "keyspan_close_hw_port: the device's" " product id can't be recognized"); } (void) keyspan_close_port_pipes(kp); } /* * ds_close_port */ static int keyspan_close_port(ds_hdl_t hdl, uint_t port_num) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; if (port_num >= ksp->ks_dev_spec.port_cnt) { return (USB_FAILURE); } USB_DPRINTF_L4(DPRINT_CLOSE, kp->kp_lh, "keyspan_close_port"); sema_p(&ksp->ks_pipes_sema); mutex_enter(&kp->kp_mutex); kp->kp_no_more_reads = B_TRUE; /* close hardware serial port */ mutex_exit(&kp->kp_mutex); keyspan_close_hw_port(kp); mutex_enter(&kp->kp_mutex); /* * free resources and finalize state */ if (kp->kp_rx_mp) { freemsg(kp->kp_rx_mp); kp->kp_rx_mp = NULL; } if (kp->kp_tx_mp) { freemsg(kp->kp_tx_mp); kp->kp_tx_mp = NULL; } kp->kp_no_more_reads = B_FALSE; kp->kp_state = KEYSPAN_PORT_CLOSED; mutex_exit(&kp->kp_mutex); keyspan_pm_set_idle(ksp); sema_v(&ksp->ks_pipes_sema); return (USB_SUCCESS); } /* * power management * * ds_usb_power */ /*ARGSUSED*/ static int keyspan_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_pm_t *pm = ksp->ks_pm; int rval; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_usb_power"); mutex_enter(&ksp->ks_mutex); /* * check if we are transitioning to a legal power level */ if (USB_DEV_PWRSTATE_OK(pm->pm_pwr_states, level)) { USB_DPRINTF_L2(DPRINT_PM, ksp->ks_lh, "keyspan_usb_power:" "illegal power level %d, pwr_states=%x", level, pm->pm_pwr_states); mutex_exit(&ksp->ks_mutex); return (USB_FAILURE); } /* * if we are about to raise power and asked to lower power, fail */ if (pm->pm_raise_power && (level < (int)pm->pm_cur_power)) { mutex_exit(&ksp->ks_mutex); return (USB_FAILURE); } switch (level) { case USB_DEV_OS_PWR_OFF: rval = keyspan_pwrlvl0(ksp); break; case USB_DEV_OS_PWR_1: rval = keyspan_pwrlvl1(ksp); break; case USB_DEV_OS_PWR_2: rval = keyspan_pwrlvl2(ksp); break; case USB_DEV_OS_FULL_PWR: rval = keyspan_pwrlvl3(ksp); /* * If usbser dev_state is DISCONNECTED or SUSPENDED, it shows * that the usb serial device is disconnected/suspended while it * is under power down state, now the device is powered up * before it is reconnected/resumed. xxx_pwrlvl3() will set dev * state to ONLINE, we need to set the dev state back to * DISCONNECTED/SUSPENDED. */ if ((rval == USB_SUCCESS) && ((*new_state == USB_DEV_DISCONNECTED) || (*new_state == USB_DEV_SUSPENDED))) { ksp->ks_dev_state = *new_state; } break; default: ASSERT(0); /* cannot happen */ } *new_state = ksp->ks_dev_state; mutex_exit(&ksp->ks_mutex); return (rval); } /* * ds_suspend */ static int keyspan_suspend(ds_hdl_t hdl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; int state = USB_DEV_SUSPENDED; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_suspend"); /* * If the device is suspended while it is under PWRED_DOWN state, we * need to keep the PWRED_DOWN state so that it could be powered up * later. In the mean while, usbser dev state will be changed to * SUSPENDED state. */ mutex_enter(&ksp->ks_mutex); if (ksp->ks_dev_state != USB_DEV_PWRED_DOWN) { ksp->ks_dev_state = USB_DEV_SUSPENDED; } mutex_exit(&ksp->ks_mutex); keyspan_disconnect_pipes(ksp); return (state); } /* * ds_resume */ static int keyspan_resume(ds_hdl_t hdl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; int current_state; int rval; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_resume"); mutex_enter(&ksp->ks_mutex); current_state = ksp->ks_dev_state; mutex_exit(&ksp->ks_mutex); if (current_state != USB_DEV_ONLINE) { rval = keyspan_restore_device_state(ksp); } else { rval = USB_SUCCESS; } return (rval); } /* * ds_disconnect */ static int keyspan_disconnect(ds_hdl_t hdl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; int state = USB_DEV_DISCONNECTED; USB_DPRINTF_L4(DPRINT_HOTPLUG, ksp->ks_lh, "keyspan_disconnect"); /* * If the device is disconnected while it is under PWRED_DOWN state, we * need to keep the PWRED_DOWN state so that it could be powered up * later. In the mean while, usbser dev state will be changed to * DISCONNECTED state. */ mutex_enter(&ksp->ks_mutex); if (ksp->ks_dev_state != USB_DEV_PWRED_DOWN) { ksp->ks_dev_state = USB_DEV_DISCONNECTED; } mutex_exit(&ksp->ks_mutex); keyspan_disconnect_pipes(ksp); return (state); } /* * ds_reconnect */ static int keyspan_reconnect(ds_hdl_t hdl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; USB_DPRINTF_L4(DPRINT_HOTPLUG, ksp->ks_lh, "keyspan_reconnect"); return (keyspan_restore_device_state(ksp)); } /* * ds_set_port_params */ static int keyspan_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t *tp) { int cnt = tp->tp_cnt; keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_set_port_params: port: %d params", cnt); if (cnt <= 0) { return (USB_SUCCESS); } mutex_enter(&kp->kp_mutex); ASSERT((kp->kp_state == KEYSPAN_PORT_OPENING) || (kp->kp_state == KEYSPAN_PORT_OPEN)); keyspan_build_cmd_msg(kp, tp); mutex_exit(&kp->kp_mutex); if (keyspan_send_cmd(kp) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd() FAILED"); return (USB_FAILURE); } return (USB_SUCCESS); } /* * ds_set_modem_ctl */ static int keyspan_set_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int val) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); mutex_enter(&kp->kp_mutex); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_set_modem_ctl: " "mask=%x, val=%x", mask, val); keyspan_build_cmd_msg(kp, NULL); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: if (mask & TIOCM_RTS) { kp->kp_ctrl_msg.usa19hs.setRts = 0x01; if (val & TIOCM_RTS) { kp->kp_ctrl_msg.usa19hs.rts = 0x1; } else { kp->kp_ctrl_msg.usa19hs.rts = 0x0; } } else { kp->kp_ctrl_msg.usa19hs.setRts = 0x0; } if (mask & TIOCM_DTR) { kp->kp_ctrl_msg.usa19hs.setDtr = 0x01; if (val & TIOCM_DTR) { kp->kp_ctrl_msg.usa19hs.dtr = 0x1; } else { kp->kp_ctrl_msg.usa19hs.dtr = 0x0; } } else { kp->kp_ctrl_msg.usa19hs.setDtr = 0x0; } break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: if (mask & TIOCM_RTS) { kp->kp_ctrl_msg.usa49.setRts = 0x1; if (val & TIOCM_RTS) { kp->kp_ctrl_msg.usa49.rts = 0x1; } else { kp->kp_ctrl_msg.usa49.rts = 0x0; } } else { kp->kp_ctrl_msg.usa49.setRts = 0x0; } if (mask & TIOCM_DTR) { kp->kp_ctrl_msg.usa49.setDtr = 0x1; if (val & TIOCM_DTR) { kp->kp_ctrl_msg.usa49.dtr = 0x1; } else { kp->kp_ctrl_msg.usa49.dtr = 0x0; } } else { kp->kp_ctrl_msg.usa49.setDtr = 0x0; } break; default: USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_get_modem_ctl:" "the device's product id can't be recognized"); mutex_exit(&kp->kp_mutex); return (USB_FAILURE); } mutex_exit(&kp->kp_mutex); if (keyspan_send_cmd(kp) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd() FAILED"); return (USB_FAILURE); } return (USB_SUCCESS); } /* * ds_get_modem_ctl */ static int keyspan_get_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int *valp) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; int val = 0; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); mutex_enter(&kp->kp_mutex); /* * rts and dtr are not in status_msg, but we can get it from * status_flag since it represents what we set the device last time. */ if (kp->kp_status_flag & KEYSPAN_PORT_RTS) { val |= TIOCM_RTS; } if (kp->kp_status_flag & KEYSPAN_PORT_DTR) { val |= TIOCM_DTR; } /* usbser don't deal with TIOCM_RI status */ switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: if (kp->kp_status_msg.usa19hs.dcd) { val |= TIOCM_CD; } if (kp->kp_status_msg.usa19hs.cts) { val |= TIOCM_CTS; } if (kp->kp_status_msg.usa19hs.dsr) { val |= TIOCM_DSR; } break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: if (kp->kp_status_msg.usa49.dcd) { val |= TIOCM_CD; } if (kp->kp_status_msg.usa49.cts) { val |= TIOCM_CTS; } if (kp->kp_status_msg.usa49.dsr) { val |= TIOCM_DSR; } break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_get_modem_ctl:" "the device's product id can't be recognized"); mutex_exit(&kp->kp_mutex); return (USB_FAILURE); } *valp = val & mask; USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_get_modem_ctl:" "success. status_flag = %x, val=0%o", kp->kp_status_flag, *valp); mutex_exit(&kp->kp_mutex); return (USB_SUCCESS); } /* * ds_break_ctl */ static int keyspan_break_ctl(ds_hdl_t hdl, uint_t port_num, int ctl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; int is_break; int rval = USB_SUCCESS; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_break_ctl: ctl = %s", (ctl == DS_ON) ? "on" : "off"); mutex_enter(&kp->kp_mutex); ASSERT(kp->kp_state == KEYSPAN_PORT_OPEN); ASSERT(ctl == DS_ON || ctl == DS_OFF); is_break = kp->kp_status_flag & KEYSPAN_PORT_TXBREAK; if ((ctl == DS_ON) && !is_break) { keyspan_build_cmd_msg(kp, NULL); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: kp->kp_ctrl_msg.usa19hs.txBreak = 1; break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: kp->kp_ctrl_msg.usa49.txBreak = 1; break; default: mutex_exit(&kp->kp_mutex); USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_break_ctl:" "the device's product id can't be recognized"); return (USB_FAILURE); } mutex_exit(&kp->kp_mutex); rval = keyspan_send_cmd(kp); return (rval); } if ((ctl == DS_OFF) && is_break) { keyspan_build_cmd_msg(kp, NULL); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: kp->kp_ctrl_msg.usa19hs.txBreak = 0; break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: kp->kp_ctrl_msg.usa49._txOn = 1; kp->kp_ctrl_msg.usa49.txBreak = 0; break; default: mutex_exit(&kp->kp_mutex); USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_break_ctl:" "the device's product id can't be recognized"); return (USB_FAILURE); } mutex_exit(&kp->kp_mutex); rval = keyspan_send_cmd(kp); if (rval == USB_SUCCESS) { mutex_enter(&kp->kp_mutex); /* resume transmit */ keyspan_tx_start(kp, NULL); mutex_exit(&kp->kp_mutex); } return (rval); } mutex_exit(&kp->kp_mutex); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_break_ctl: not necessary to set break, is_break = %d", is_break); return (rval); } /* * ds_loopback */ static int keyspan_loopback(ds_hdl_t hdl, uint_t port_num, int ctl) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; int is_loop; int rval = USB_SUCCESS; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_loopback: %s", (ctl == DS_ON) ? "on" : "off"); mutex_enter(&kp->kp_mutex); ASSERT(kp->kp_state == KEYSPAN_PORT_OPEN); ASSERT(ctl == DS_ON || ctl == DS_OFF); /* check bit indicating internal loopback state */ is_loop = kp->kp_status_flag & KEYSPAN_PORT_LOOPBACK; if ((ctl == DS_ON) && !is_loop) { keyspan_build_cmd_msg(kp, NULL); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: kp->kp_ctrl_msg.usa19hs.loopbackMode = 0; break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: kp->kp_ctrl_msg.usa49.loopbackMode = 0; break; default: mutex_exit(&kp->kp_mutex); USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_loopback:" "the device's product id can't be recognized"); return (USB_FAILURE); } mutex_exit(&kp->kp_mutex); rval = keyspan_send_cmd(kp); } else if ((ctl == DS_OFF) && is_loop) { keyspan_build_cmd_msg(kp, NULL); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: kp->kp_ctrl_msg.usa19hs.loopbackMode = 1; break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: kp->kp_ctrl_msg.usa49.loopbackMode = 1; break; default: mutex_exit(&kp->kp_mutex); USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_loopback:" "the device's product id can't be recognized"); return (USB_FAILURE); } mutex_exit(&kp->kp_mutex); rval = keyspan_send_cmd(kp); } else { mutex_exit(&kp->kp_mutex); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_loopback: not necessary to set loopback," "is_loop = %d", is_loop); } return (rval); } /* * ds_tx */ static int keyspan_tx(ds_hdl_t hdl, uint_t port_num, mblk_t *mp) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; int xferd; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_tx"); /* * sanity checks */ if (mp == NULL) { USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_tx: mp=NULL"); return (USB_SUCCESS); } kp = &ksp->ks_ports[port_num]; mutex_enter(&kp->kp_mutex); keyspan_put_tail(&kp->kp_tx_mp, mp); /* add to the chain */ keyspan_tx_start(kp, &xferd); /* go! */ mutex_exit(&kp->kp_mutex); return (USB_SUCCESS); } /* * ds_rx. the real data receiving is in keyspan_open_hw_port */ static mblk_t * keyspan_rx(ds_hdl_t hdl, uint_t port_num) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; mblk_t *mp; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_rx"); mutex_enter(&kp->kp_mutex); mp = kp->kp_rx_mp; kp->kp_rx_mp = NULL; mutex_exit(&kp->kp_mutex); return (mp); } /* * ds_stop */ static void keyspan_stop(ds_hdl_t hdl, uint_t port_num, int dir) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_stop"); if (dir & DS_TX) { mutex_enter(&kp->kp_mutex); kp->kp_flags |= KEYSPAN_PORT_TX_STOPPED; mutex_exit(&kp->kp_mutex); } } /* * ds_start */ static void keyspan_start(ds_hdl_t hdl, uint_t port_num, int dir) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_start"); if (dir & DS_TX) { mutex_enter(&kp->kp_mutex); if (kp->kp_flags & KEYSPAN_PORT_TX_STOPPED) { kp->kp_flags &= ~KEYSPAN_PORT_TX_STOPPED; keyspan_tx_start(kp, NULL); } mutex_exit(&kp->kp_mutex); } } /* * ds_fifo_flush * send flush cmd and wait for completion, then turn off the flush. */ static int keyspan_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_fifo_flush: dir=%x", dir); mutex_enter(&kp->kp_mutex); ASSERT(kp->kp_state == KEYSPAN_PORT_OPEN); /* discard the data in DSD buffers */ if ((dir & DS_TX) && kp->kp_tx_mp) { freemsg(kp->kp_tx_mp); kp->kp_tx_mp = NULL; } if ((dir & DS_RX) && kp->kp_rx_mp) { freemsg(kp->kp_rx_mp); kp->kp_rx_mp = NULL; } mutex_exit(&kp->kp_mutex); return (USB_SUCCESS); } /* * ds_fifo_drain * * it is the caller's responsibility to cease submitting new tx data * while this function executes */ static int keyspan_fifo_drain(ds_hdl_t hdl, uint_t port_num, int timeout) { keyspan_state_t *ksp = (keyspan_state_t *)hdl; keyspan_port_t *kp = &ksp->ks_ports[port_num]; int rval = USB_SUCCESS; ASSERT(port_num < ksp->ks_dev_spec.port_cnt); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_fifo_drain, timeout = %d", timeout); mutex_enter(&kp->kp_mutex); ASSERT(kp->kp_state == KEYSPAN_PORT_OPEN); /* wait until local data drains */ if (keyspan_wait_tx_drain(kp, 0) != USB_SUCCESS) { mutex_exit(&kp->kp_mutex); return (USB_FAILURE); } mutex_exit(&kp->kp_mutex); /* wait until hw fifo drains */ delay(drv_usectohz(500*1000)); return (rval); } /* * configuration routines * ---------------------- * */ /* * free state structure */ static void keyspan_free_soft_state(keyspan_state_t *ksp) { kmem_free(ksp, sizeof (keyspan_state_t)); } /* * register/unregister USBA client */ static int keyspan_usb_register(keyspan_state_t *ksp) { int rval; rval = usb_client_attach(ksp->ks_dip, USBDRV_VERSION, 0); if (rval == USB_SUCCESS) { rval = usb_get_dev_data(ksp->ks_dip, &ksp->ks_dev_data, USB_PARSE_LVL_IF, 0); if (rval == USB_SUCCESS) { ksp->ks_lh = usb_alloc_log_hdl(ksp->ks_dip, "keyspan[*].", &keyspan_errlevel, &keyspan_errmask, &keyspan_instance_debug, 0); ksp->ks_def_pipe.pipe_handle = ksp->ks_dev_data->dev_default_ph; ksp->ks_def_pipe.pipe_ksp = ksp; ksp->ks_def_pipe.pipe_lh = ksp->ks_lh; } } return (rval); } static void keyspan_usb_unregister(keyspan_state_t *ksp) { usb_free_log_hdl(ksp->ks_lh); ksp->ks_lh = NULL; usb_client_detach(ksp->ks_dip, ksp->ks_dev_data); ksp->ks_def_pipe.pipe_handle = NULL; ksp->ks_dev_data = NULL; } /* * init/fini soft state during attach */ static void keyspan_init_sync_objs(keyspan_state_t *ksp) { mutex_init(&ksp->ks_mutex, NULL, MUTEX_DRIVER, ksp->ks_dev_data->dev_iblock_cookie); sema_init(&ksp->ks_pipes_sema, 1, NULL, SEMA_DRIVER, NULL); } static void keyspan_fini_sync_objs(keyspan_state_t *ksp) { mutex_destroy(&ksp->ks_mutex); sema_destroy(&ksp->ks_pipes_sema); } /* * device specific attributes */ static int keyspan_attach_dev(keyspan_state_t *ksp) { mutex_enter(&ksp->ks_mutex); switch (ksp->ks_dev_data->dev_descr->idProduct) { case KEYSPAN_USA19HS_PID: ksp->ks_dev_spec.id_product = KEYSPAN_USA19HS_PID; ksp->ks_dev_spec.port_cnt = 1; ksp->ks_dev_spec.ctrl_ep_addr = 0x02; ksp->ks_dev_spec.stat_ep_addr = 0x82; ksp->ks_dev_spec.dataout_ep_addr[0] = 0x01; ksp->ks_dev_spec.datain_ep_addr[0] = 0x81; break; case KEYSPAN_USA49WLC_PID: ksp->ks_dev_spec.id_product = KEYSPAN_USA49WLC_PID; ksp->ks_dev_spec.port_cnt = 4; ksp->ks_dev_spec.ctrl_ep_addr = 0x07; ksp->ks_dev_spec.stat_ep_addr = 0x87; ksp->ks_dev_spec.dataout_ep_addr[0] = 0x01; ksp->ks_dev_spec.dataout_ep_addr[1] = 0x02; ksp->ks_dev_spec.dataout_ep_addr[2] = 0x03; ksp->ks_dev_spec.dataout_ep_addr[3] = 0x04; ksp->ks_dev_spec.datain_ep_addr[0] = 0x81; ksp->ks_dev_spec.datain_ep_addr[1] = 0x82; ksp->ks_dev_spec.datain_ep_addr[2] = 0x83; ksp->ks_dev_spec.datain_ep_addr[3] = 0x84; break; case KEYSPAN_USA49WG_PID: ksp->ks_dev_spec.id_product = KEYSPAN_USA49WG_PID; ksp->ks_dev_spec.port_cnt = 4; ksp->ks_dev_spec.stat_ep_addr = 0x81; ksp->ks_dev_spec.dataout_ep_addr[0] = 0x01; ksp->ks_dev_spec.dataout_ep_addr[1] = 0x02; ksp->ks_dev_spec.dataout_ep_addr[2] = 0x04; ksp->ks_dev_spec.dataout_ep_addr[3] = 0x06; ksp->ks_dev_spec.datain_ep_addr[0] = 0x88; ksp->ks_dev_spec.datain_ep_addr[1] = 0x88; ksp->ks_dev_spec.datain_ep_addr[2] = 0x88; ksp->ks_dev_spec.datain_ep_addr[3] = 0x88; break; default: mutex_exit(&ksp->ks_mutex); USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_attach_dev:" "the device's product id can't be recognized"); return (USB_FAILURE); } mutex_exit(&ksp->ks_mutex); return (USB_SUCCESS); } /* * allocate and initialize per port resources. */ static void keyspan_attach_ports(keyspan_state_t *ksp) { int i; keyspan_port_t *kp; ksp->ks_ports = kmem_zalloc(ksp->ks_dev_spec.port_cnt * sizeof (keyspan_port_t), KM_SLEEP); for (i = 0; i < ksp->ks_dev_spec.port_cnt; i++) { kp = &ksp->ks_ports[i]; kp->kp_port_num = i; kp->kp_ksp = ksp; (void) sprintf(kp->kp_lh_name, "keyspan[%d].", i); kp->kp_lh = usb_alloc_log_hdl(ksp->ks_dip, kp->kp_lh_name, &keyspan_errlevel, &keyspan_errmask, &keyspan_instance_debug, 0); kp->kp_state = KEYSPAN_PORT_CLOSED; mutex_init(&kp->kp_mutex, NULL, MUTEX_DRIVER, ksp->ks_dev_data->dev_iblock_cookie); cv_init(&kp->kp_tx_cv, NULL, CV_DRIVER, NULL); } } /* * free per port resources */ static void keyspan_detach_ports(keyspan_state_t *ksp) { int i; keyspan_port_t *kp; for (i = 0; i < ksp->ks_dev_spec.port_cnt; i++) { kp = &ksp->ks_ports[i]; if (kp->kp_state != KEYSPAN_PORT_NOT_INIT) { ASSERT(kp->kp_state == KEYSPAN_PORT_CLOSED); mutex_destroy(&kp->kp_mutex); cv_destroy(&kp->kp_tx_cv); usb_free_log_hdl(kp->kp_lh); } } kmem_free(ksp->ks_ports, ksp->ks_dev_spec.port_cnt * sizeof (keyspan_port_t)); } static void keyspan_init_port_params(keyspan_state_t *ksp) { int i; size_t sz; uint_t read_len; uint_t write_len; /* the max data len of every bulk in req. */ if (usb_pipe_get_max_bulk_transfer_size(ksp->ks_dip, &sz) == USB_SUCCESS) { if (ksp->ks_dev_spec.id_product == KEYSPAN_USA49WG_PID) { read_len = (uint_t)min(sz, KEYSPAN_BULKIN_MAX_LEN_49WG); } else { read_len = (uint_t)min(sz, KEYSPAN_BULKIN_MAX_LEN); } } else { if (ksp->ks_dev_spec.id_product == KEYSPAN_USA49WG_PID) { read_len = KEYSPAN_BULKIN_MAX_LEN_49WG; } else { read_len = KEYSPAN_BULKIN_MAX_LEN; } } for (i = 0; i < ksp->ks_dev_spec.port_cnt; i++) { ksp->ks_ports[i].kp_read_len = read_len; /* the max data len of every bulk out req. */ switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: ksp->ks_ports[i].kp_write_len = KEYSPAN_BULKOUT_MAX_LEN_19HS; break; case KEYSPAN_USA49WLC_PID: ksp->ks_ports[i].kp_write_len = KEYSPAN_BULKOUT_MAX_LEN_49WLC; break; case KEYSPAN_USA49WG_PID: /* * USA49WG port0 uses intr out pipe send data while * other ports use bulk out pipes, so port0's max * packet length for "bulk out" is different from other * ports' while the same as USA49WLC. */ write_len = ((i == 0) ? KEYSPAN_BULKOUT_MAX_LEN_49WLC : KEYSPAN_BULKOUT_MAX_LEN_49WG); ksp->ks_ports[i].kp_write_len = write_len; break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_init_port_params:" "the device's product id can't be recognized"); return; } } } /* * free descriptor tree */ static void keyspan_free_descr_tree(keyspan_state_t *ksp) { usb_free_descr_tree(ksp->ks_dip, ksp->ks_dev_data); } /* * register/unregister USB event callbacks */ static int keyspan_register_events(keyspan_state_t *ksp) { return (usb_register_event_cbs(ksp->ks_dip, ksp->ks_usb_events, 0)); } static void keyspan_unregister_events(keyspan_state_t *ksp) { usb_unregister_event_cbs(ksp->ks_dip, ksp->ks_usb_events); } static void keyspan_set_dev_state_online(keyspan_state_t *ksp) { ksp->ks_dev_state = USB_DEV_ONLINE; } /* * send command to the port and save the params after its completion for * USA19HS and USA49WLC */ int keyspan_send_cmd_usa49(keyspan_port_t *kp) { keyspan_state_t *ksp = kp->kp_ksp; mblk_t *mp; int rval = USB_SUCCESS; int size; usb_bulk_req_t *br; ASSERT(!mutex_owned(&kp->kp_mutex)); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd_usa49"); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: size = sizeof (keyspan_usa19hs_port_ctrl_msg_t); break; case KEYSPAN_USA49WLC_PID: size = sizeof (keyspan_usa49_port_ctrl_msg_t); break; default: USB_DPRINTF_L2(DPRINT_CTLOP, ksp->ks_lh, "keyspan_send_cmd_usa49:" "the device's product id can't be recognized"); return (USB_FAILURE); } if ((mp = allocb(size, BPRI_LO)) == NULL) { return (USB_FAILURE); } bcopy(&kp->kp_ctrl_msg, mp->b_rptr, size); br = usb_alloc_bulk_req(ksp->ks_dip, 0, USB_FLAGS_SLEEP); br->bulk_len = size; br->bulk_data = mp; br->bulk_timeout = KEYSPAN_BULK_TIMEOUT; br->bulk_client_private = (void *)kp; br->bulk_attributes = USB_ATTRS_AUTOCLEARING; rval = usb_pipe_bulk_xfer(ksp->ks_ctrlout_pipe.pipe_handle, br, USB_FLAGS_SLEEP); if (rval == USB_SUCCESS) { mutex_enter(&kp->kp_mutex); keyspan_save_port_params(kp); mutex_exit(&kp->kp_mutex); } else { USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd_usa49" ": failure, rval=%d", rval); } usb_free_bulk_req(br); return (rval); } /* * send command to the port and save the params after its completion for * USA_49WG only */ int keyspan_send_cmd_usa49wg(keyspan_port_t *kp) { keyspan_state_t *ksp = kp->kp_ksp; mblk_t *mp; int rval = USB_SUCCESS; uint16_t size = sizeof (keyspan_usa49_port_ctrl_msg_t); usb_cb_flags_t cb_flags; usb_cr_t cr; usb_ctrl_setup_t setup; ASSERT(!mutex_owned(&kp->kp_mutex)); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd_usa49wg"); if ((mp = allocb(size, BPRI_LO)) == NULL) { return (USB_FAILURE); } bcopy(&kp->kp_ctrl_msg, mp->b_rptr, size); setup.bmRequestType = USB_DEV_REQ_TYPE_VENDOR; setup.bRequest = KEYSPAN_SET_CONTROL_REQUEST; setup.wValue = 0; setup.wIndex = 0; setup.wLength = size; setup.attrs = 0; rval = usb_pipe_ctrl_xfer_wait(ksp->ks_def_pipe.pipe_handle, &setup, &mp, &cr, &cb_flags, 0); if (rval == USB_SUCCESS) { mutex_enter(&kp->kp_mutex); keyspan_save_port_params(kp); mutex_exit(&kp->kp_mutex); } else { USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd_usa49wg: failure, rval=%d", rval); } if (mp) { freemsg(mp); } return (rval); } /* * send command to the port and save the params after its completion */ int keyspan_send_cmd(keyspan_port_t *kp) { keyspan_state_t *ksp = kp->kp_ksp; int rval = USB_FAILURE; switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: case KEYSPAN_USA49WLC_PID: rval = keyspan_send_cmd_usa49(kp); break; case KEYSPAN_USA49WG_PID: rval = keyspan_send_cmd_usa49wg(kp); break; default: USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd: " "the device's product id can't be recognized"); } if (rval != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_send_cmd() FAILED"); return (rval); } return (USB_SUCCESS); } /* * hotplug * ------- * * restore device state after CPR resume or reconnect */ static int keyspan_restore_device_state(keyspan_state_t *ksp) { int state; mutex_enter(&ksp->ks_mutex); state = ksp->ks_dev_state; mutex_exit(&ksp->ks_mutex); if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) { return (state); } if (usb_check_same_device(ksp->ks_dip, ksp->ks_lh, USB_LOG_L0, DPRINT_MASK_ALL, USB_CHK_ALL, NULL) != USB_SUCCESS) { mutex_enter(&ksp->ks_mutex); state = ksp->ks_dev_state = USB_DEV_DISCONNECTED; mutex_exit(&ksp->ks_mutex); return (state); } if (state == USB_DEV_DISCONNECTED) { USB_DPRINTF_L0(DPRINT_HOTPLUG, ksp->ks_lh, "device has been reconnected but data may have been lost"); } if (keyspan_reconnect_pipes(ksp) != USB_SUCCESS) { return (state); } /* * init device state */ mutex_enter(&ksp->ks_mutex); state = ksp->ks_dev_state = USB_DEV_ONLINE; ksp->ks_reconnect_flag = 1; mutex_exit(&ksp->ks_mutex); /* * now restore each open port */ (void) keyspan_restore_ports_state(ksp); return (state); } /* * restore ports state after CPR resume or reconnect */ static int keyspan_restore_ports_state(keyspan_state_t *ksp) { keyspan_port_t *kp; int rval = USB_SUCCESS; int err; int i; for (i = 0; i < ksp->ks_dev_spec.port_cnt; i++) { kp = &ksp->ks_ports[i]; /* * only care about open ports */ mutex_enter(&kp->kp_mutex); if (kp->kp_state != KEYSPAN_PORT_OPEN) { mutex_exit(&kp->kp_mutex); continue; } mutex_exit(&kp->kp_mutex); sema_p(&ksp->ks_pipes_sema); /* open hardware serial port */ err = keyspan_open_hw_port(kp, B_FALSE); sema_v(&ksp->ks_pipes_sema); if (err != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_HOTPLUG, kp->kp_lh, "keyspan_restore_ports_state: failed"); rval = err; } } return (rval); } /* * power management * ---------------- * * * create PM components */ static int keyspan_create_pm_components(keyspan_state_t *ksp) { dev_info_t *dip = ksp->ks_dip; keyspan_pm_t *pm; uint_t pwr_states; pm = ksp->ks_pm = kmem_zalloc(sizeof (keyspan_pm_t), KM_SLEEP); pm->pm_cur_power = USB_DEV_OS_FULL_PWR; if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_PM, ksp->ks_lh, "keyspan_create_pm_components: failed"); return (USB_SUCCESS); } pm->pm_wakeup_enabled = (usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS); pm->pm_pwr_states = (uint8_t)pwr_states; (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); return (USB_SUCCESS); } /* * destroy PM components */ static void keyspan_destroy_pm_components(keyspan_state_t *ksp) { keyspan_pm_t *pm = ksp->ks_pm; dev_info_t *dip = ksp->ks_dip; int rval; if (ksp->ks_dev_state != USB_DEV_DISCONNECTED) { if (pm->pm_wakeup_enabled) { (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); rval = usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_DISABLE); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_PM, ksp->ks_lh, "keyspan_destroy_pm_components: disable " "remote wakeup failed, rval=%d", rval); } } (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF); } kmem_free(pm, sizeof (keyspan_pm_t)); ksp->ks_pm = NULL; } /* * mark device busy and raise power */ static int keyspan_pm_set_busy(keyspan_state_t *ksp) { keyspan_pm_t *pm = ksp->ks_pm; dev_info_t *dip = ksp->ks_dip; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pm_set_busy"); mutex_enter(&ksp->ks_mutex); /* if already marked busy, just increment the counter */ if (pm->pm_busy_cnt++ > 0) { USB_DPRINTF_L3(DPRINT_PM, ksp->ks_lh, "keyspan_pm_set_busy:" "already busy, busy_cnt = %d", pm->pm_busy_cnt); mutex_exit(&ksp->ks_mutex); return (USB_SUCCESS); } (void) pm_busy_component(dip, 0); if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) { mutex_exit(&ksp->ks_mutex); return (USB_SUCCESS); } /* need to raise power */ pm->pm_raise_power = B_TRUE; mutex_exit(&ksp->ks_mutex); USB_DPRINTF_L3(DPRINT_PM, ksp->ks_lh, "keyspan_pm_set_busy: raise power"); (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); mutex_enter(&ksp->ks_mutex); pm->pm_raise_power = B_FALSE; mutex_exit(&ksp->ks_mutex); return (USB_SUCCESS); } /* * mark device idle */ static void keyspan_pm_set_idle(keyspan_state_t *ksp) { keyspan_pm_t *pm = ksp->ks_pm; dev_info_t *dip = ksp->ks_dip; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pm_set_idle"); /* * if more ports use the device, do not mark as yet */ mutex_enter(&ksp->ks_mutex); if (--pm->pm_busy_cnt > 0) { mutex_exit(&ksp->ks_mutex); return; } USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pm_set_idle: set idle"); (void) pm_idle_component(dip, 0); mutex_exit(&ksp->ks_mutex); } /* * Functions to handle power transition for OS levels 0 -> 3 */ static int keyspan_pwrlvl0(keyspan_state_t *ksp) { int rval; keyspan_pipe_t *statin = &ksp->ks_statin_pipe; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pwrlvl0"); switch (ksp->ks_dev_state) { case USB_DEV_ONLINE: /* issue USB D3 command to the device */ rval = usb_set_device_pwrlvl3(ksp->ks_dip); ASSERT(rval == USB_SUCCESS); if (ksp->ks_dev_spec.id_product == KEYSPAN_USA49WG_PID) { mutex_exit(&ksp->ks_mutex); usb_pipe_stop_intr_polling(statin->pipe_handle, USB_FLAGS_SLEEP); mutex_enter(&ksp->ks_mutex); mutex_enter(&statin->pipe_mutex); statin->pipe_state = KEYSPAN_PIPE_CLOSED; mutex_exit(&statin->pipe_mutex); } ksp->ks_dev_state = USB_DEV_PWRED_DOWN; ksp->ks_pm->pm_cur_power = USB_DEV_OS_PWR_OFF; /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: /* allow a disconnect/cpr'ed device to go to lower power */ return (USB_SUCCESS); case USB_DEV_PWRED_DOWN: default: USB_DPRINTF_L2(DPRINT_PM, ksp->ks_lh, "keyspan_pwrlvl0: illegal device state"); return (USB_FAILURE); } } static int keyspan_pwrlvl1(keyspan_state_t *ksp) { USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pwrlvl1"); /* issue USB D2 command to the device */ (void) usb_set_device_pwrlvl2(ksp->ks_dip); return (USB_FAILURE); } static int keyspan_pwrlvl2(keyspan_state_t *ksp) { USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pwrlvl2"); /* issue USB D1 command to the device */ (void) usb_set_device_pwrlvl1(ksp->ks_dip); return (USB_FAILURE); } static int keyspan_pwrlvl3(keyspan_state_t *ksp) { int rval; USB_DPRINTF_L4(DPRINT_PM, ksp->ks_lh, "keyspan_pwrlvl3"); switch (ksp->ks_dev_state) { case USB_DEV_PWRED_DOWN: /* Issue USB D0 command to the device here */ rval = usb_set_device_pwrlvl0(ksp->ks_dip); ASSERT(rval == USB_SUCCESS); if (ksp->ks_dev_spec.id_product == KEYSPAN_USA49WG_PID) { mutex_exit(&ksp->ks_mutex); keyspan_pipe_start_polling(&ksp->ks_statin_pipe); mutex_enter(&ksp->ks_mutex); } ksp->ks_dev_state = USB_DEV_ONLINE; ksp->ks_pm->pm_cur_power = USB_DEV_OS_FULL_PWR; /* FALLTHRU */ case USB_DEV_ONLINE: /* we are already in full power */ /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: return (USB_SUCCESS); default: USB_DPRINTF_L2(DPRINT_PM, ksp->ks_lh, "keyspan_pwrlvl3: illegal device state"); return (USB_FAILURE); } } /* * pipe operations * --------------- * * XXX keyspan seem to malfunction after the pipes are closed * and reopened again (does not respond to OPEN_PORT command). * so we open them once in attach */ static int keyspan_attach_pipes(keyspan_state_t *ksp) { return (keyspan_open_dev_pipes(ksp)); } void keyspan_detach_pipes(keyspan_state_t *ksp) { /* * Blow away status bulk in requests or * pipe close will wait until timeout. */ if (ksp->ks_statin_pipe.pipe_handle) { usb_pipe_stop_intr_polling(ksp->ks_statin_pipe.pipe_handle, USB_FLAGS_SLEEP); } /* Close the globle pipes */ keyspan_close_dev_pipes(ksp); } /* * during device disconnect/suspend, close pipes if they are open. */ static void keyspan_disconnect_pipes(keyspan_state_t *ksp) { sema_p(&ksp->ks_pipes_sema); keyspan_close_pipes(ksp); sema_v(&ksp->ks_pipes_sema); } /* * during device reconnect/resume, reopen pipes if they were open. */ static int keyspan_reconnect_pipes(keyspan_state_t *ksp) { int rval = USB_SUCCESS; sema_p(&ksp->ks_pipes_sema); rval = keyspan_reopen_pipes(ksp); sema_v(&ksp->ks_pipes_sema); return (rval); } /* * data transfer routines * ---------------------- * * * start data transmit */ void keyspan_tx_start(keyspan_port_t *kp, int *xferd) { keyspan_state_t *ksp = kp->kp_ksp; int len; /* # of bytes we can transmit */ mblk_t *data; /* data to be transmitted */ int data_len = 0; /* # of bytes in 'data' */ int tran_len; int rval; int status_len = 0; ASSERT(!mutex_owned(&ksp->ks_mutex)); ASSERT(mutex_owned(&kp->kp_mutex)); ASSERT(kp->kp_state != KEYSPAN_PORT_CLOSED); USB_DPRINTF_L4(DPRINT_OUT_PIPE, kp->kp_lh, "keyspan_tx_start"); if (xferd) { *xferd = 0; } if ((kp->kp_flags & KEYSPAN_PORT_TX_STOPPED) || (kp->kp_tx_mp == NULL)) { return; } len = min(msgdsize(kp->kp_tx_mp), kp->kp_write_len); USB_DPRINTF_L4(DPRINT_OUT_PIPE, kp->kp_lh, "keyspan_tx_start:" "len = %d, tx_mp_len = %d", len, (int)msgdsize(kp->kp_tx_mp)); mutex_exit(&kp->kp_mutex); /* * Some keyspan adapters, such as usa49wlc, * need use the first byte as flag. */ switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: if ((data = allocb(len, BPRI_LO)) == NULL) { mutex_enter(&kp->kp_mutex); return; } mutex_enter(&kp->kp_mutex); /* copy at most 'len' bytes from mblk chain for transmission */ data_len = keyspan_tx_copy_data(kp, data, len); if (data_len <= 0) { USB_DPRINTF_L3(DPRINT_OUT_PIPE, kp->kp_lh, "keyspan_tx_start:keyspan_tx_copy_data copied" " zero bytes"); } break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: status_len = len / 64 + 1; if ((data = allocb(len + status_len, BPRI_LO)) == NULL) { mutex_enter(&kp->kp_mutex); return; } mutex_enter(&kp->kp_mutex); /* * the data format is [status byte][63 data bytes][...][status] * byte][up to 63 bytes] according to keyspan spec */ while (data_len < len) { /* Add status byte per 63 data bytes */ *(data->b_wptr++) = 0; /* copy at most 63 bytes from mblk chain for trans */ tran_len = keyspan_tx_copy_data(kp, data, 63); if (tran_len <= 0) { USB_DPRINTF_L3(DPRINT_OUT_PIPE, kp->kp_lh, "keyspan_tx_start:keyspan_tx_copy_data" " copied zero bytes"); break; } data_len += tran_len; } break; default: mutex_enter(&kp->kp_mutex); USB_DPRINTF_L2(DPRINT_OUT_PIPE, ksp->ks_lh, "keyspan_tx_start:" "the device's product id can't be recognized"); return; } mutex_exit(&kp->kp_mutex); /* * For USA-49WG, the port0 uses intr out pipe as data out pipe, while * other ports use bulk out pipe. */ if ((kp->kp_port_num == 0) && (ksp->ks_dev_spec.id_product == KEYSPAN_USA49WG_PID)) { rval = keyspan_send_data_port0(&kp->kp_dataout_pipe, &data, kp); } else { rval = keyspan_send_data(&kp->kp_dataout_pipe, &data, kp); } mutex_enter(&kp->kp_mutex); /* * if send failed, put data back */ if (rval != USB_SUCCESS) { ASSERT(data); keyspan_put_head(&kp->kp_tx_mp, data, kp); } else if (xferd) { *xferd = data_len; } USB_DPRINTF_L4(DPRINT_OUT_PIPE, kp->kp_lh, "keyspan_tx_start[%d]: over" "(%d) rval=%d", kp->kp_port_num, data_len, rval); } /* * copy no more than 'len' bytes from mblk chain to transmit mblk 'data'. * return number of bytes copied */ int keyspan_tx_copy_data(keyspan_port_t *kp, mblk_t *data, int len) { mblk_t *mp; /* current msgblk */ int copylen; /* # of bytes to copy from 'mp' to 'data' */ int data_len = 0; ASSERT(mutex_owned(&kp->kp_mutex)); if (msgdsize(kp->kp_tx_mp) == 0) { data->b_wptr = data->b_rptr; freeb(kp->kp_tx_mp); kp->kp_tx_mp = NULL; return (data_len); } while ((data_len < len) && kp->kp_tx_mp) { mp = kp->kp_tx_mp; copylen = min(MBLKL(mp), len - data_len); bcopy(mp->b_rptr, data->b_wptr, copylen); mp->b_rptr += copylen; data->b_wptr += copylen; data_len += copylen; if (MBLKL(mp) < 1) { kp->kp_tx_mp = unlinkb(mp); freeb(mp); } else { ASSERT(data_len == len); } } USB_DPRINTF_L3(DPRINT_OUT_DATA, kp->kp_lh, "keyspan_tx_copy_data:" "copied data_len = %d", data_len); return (data_len); } /* * wait until local tx buffer drains. * 'timeout' is in seconds, zero means wait forever */ static int keyspan_wait_tx_drain(keyspan_port_t *kp, int timeout) { clock_t until; int over = 0; USB_DPRINTF_L4(DPRINT_OUT_DATA, kp->kp_lh, "keyspan_wait_tx_drain:" "timeout = %d", timeout); until = ddi_get_lbolt() + drv_usectohz(1000000 * timeout); while (kp->kp_tx_mp && !over) { if (timeout > 0) { over = (cv_timedwait_sig(&kp->kp_tx_cv, &kp->kp_mutex, until) <= 0); } else { over = (cv_wait_sig(&kp->kp_tx_cv, &kp->kp_mutex) == 0); } } return ((kp->kp_tx_mp == NULL) ? USB_SUCCESS : USB_FAILURE); } /* * returns 0 if device is not online, != 0 otherwise */ int keyspan_dev_is_online(keyspan_state_t *ksp) { int rval; mutex_enter(&ksp->ks_mutex); rval = (ksp->ks_dev_state == USB_DEV_ONLINE); mutex_exit(&ksp->ks_mutex); return (rval); } /* * link a message block to tail of message * account for the case when message is null */ void keyspan_put_tail(mblk_t **mpp, mblk_t *bp) { if (*mpp) { linkb(*mpp, bp); } else { *mpp = bp; } } /* * put a message block at the head of the message * account for the case when message is null */ void keyspan_put_head(mblk_t **mpp, mblk_t *bp, keyspan_port_t *kp) { switch (kp->kp_ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: if (*mpp) { linkb(bp, *mpp); } *mpp = bp; break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: /* get rid of the first byte of the msg data which is a flag */ if (*mpp) { linkb(bp, *mpp); } bp->b_rptr = bp->b_datap->db_base + 1; *mpp = bp; break; default: USB_DPRINTF_L2(DPRINT_OUT_DATA, kp->kp_lh, "keyspan_put_head:" "the device's product id can't be recognized"); return; } } /* * Set the port parameters to default values */ static void keyspan_default_port_params(keyspan_port_t *kp) { keyspan_state_t *ksp = kp->kp_ksp; ASSERT(mutex_owned(&kp->kp_mutex)); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: keyspan_default_port_params_usa19hs(kp); break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: keyspan_default_port_params_usa49(kp); break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_default_port_params:" "the device's product id can't be recognized"); } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_default_port_params: setted."); } /* * Build the command message according to the params from usbser. * The message will then be sent to deivce by keyspan_send_cmd. */ static void keyspan_build_cmd_msg(keyspan_port_t *kp, ds_port_params_t *tp) { keyspan_state_t *ksp = kp->kp_ksp; switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: keyspan_build_cmd_msg_usa19hs(kp, tp); break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: keyspan_build_cmd_msg_usa49(kp, tp); break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_build_cmd_msg:" "the device's product id can't be recognized"); } } /* save the port params after send cmd successfully */ static void keyspan_save_port_params(keyspan_port_t *kp) { keyspan_state_t *ksp = kp->kp_ksp; ASSERT(mutex_owned(&kp->kp_mutex)); switch (ksp->ks_dev_spec.id_product) { case KEYSPAN_USA19HS_PID: keyspan_save_port_params_usa19hs(kp); break; case KEYSPAN_USA49WLC_PID: case KEYSPAN_USA49WG_PID: keyspan_save_port_params_usa49(kp); break; default: USB_DPRINTF_L2(DPRINT_ATTACH, ksp->ks_lh, "keyspan_save_port_params:" "the device's product id can't be recognized"); } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_save_port_params: baud = %x, lcr = %x," "status_flag = %x", kp->kp_baud, kp->kp_lcr, kp->kp_status_flag); } /* save the port params after send cmd successfully */ static void keyspan_save_port_params_usa19hs(keyspan_port_t *kp) { keyspan_usa19hs_port_ctrl_msg_t *ctrl_msg = &(kp->kp_ctrl_msg.usa19hs); ASSERT(mutex_owned(&kp->kp_mutex)); if (ctrl_msg->setClocking) { kp->kp_baud = ctrl_msg->baudHi; kp->kp_baud = (kp->kp_baud << 8); kp->kp_baud |= ctrl_msg->baudLo; } if (ctrl_msg->setLcr) { kp->kp_lcr = ctrl_msg->lcr; } if (ctrl_msg->setRts) { if (ctrl_msg->rts) { kp->kp_status_flag |= KEYSPAN_PORT_RTS; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_RTS; } } if (ctrl_msg->setDtr) { if (ctrl_msg->dtr) { kp->kp_status_flag |= KEYSPAN_PORT_DTR; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_DTR; } } if (ctrl_msg->portEnabled) { kp->kp_status_flag |= KEYSPAN_PORT_ENABLE; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_ENABLE; } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_save_port_params: baud = %x, lcr = %x," "status_flag = %x", kp->kp_baud, kp->kp_lcr, kp->kp_status_flag); } /* * Set the port parameters to default values */ static void keyspan_default_port_params_usa19hs(keyspan_port_t *kp) { keyspan_usa19hs_port_ctrl_msg_t *ctrl_msg = &(kp->kp_ctrl_msg.usa19hs); ASSERT(mutex_owned(&kp->kp_mutex)); keyspan_build_cmd_msg(kp, NULL); ctrl_msg->setRts = 0x01; ctrl_msg->rts = 0x1; ctrl_msg->setDtr = 0x01; ctrl_msg->dtr = 0x1; ctrl_msg->setClocking = 1; ctrl_msg->setRxMode = 1; ctrl_msg->setTxMode = 1; /* set baud rate to 9600 */ ctrl_msg->baudLo = keyspan_speedtab_usa19hs[13] & 0xff; ctrl_msg->baudHi = (keyspan_speedtab_usa19hs[13] >> 8) & 0xff; ctrl_msg->rxMode = RXMODE_BYHAND; ctrl_msg->txMode = TXMODE_BYHAND; ctrl_msg->lcr = 0x3; ctrl_msg->setLcr = 0x1; ctrl_msg->xonChar = CSTART; ctrl_msg->xoffChar = CSTOP; ctrl_msg->setTxFlowControl = 1; ctrl_msg->txFlowControl = TXFLOW_CTS; ctrl_msg->setRxFlowControl = 1; ctrl_msg->rxFlowControl = RXFLOW_RTS; ctrl_msg->rxFlush = 0; } /* * Build the command message according to the params from usbser. * The message will then be sent to deivce by keyspan_send_cmd. */ static void keyspan_build_cmd_msg_usa19hs(keyspan_port_t *kp, ds_port_params_t *tp) { int cnt, i; uint_t ui; ds_port_param_entry_t *pe; keyspan_usa19hs_port_ctrl_msg_t *ctrl_msg = &(kp->kp_ctrl_msg.usa19hs); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: tp = %p", (void *)tp); ASSERT(mutex_owned(&kp->kp_mutex)); ASSERT(kp->kp_state == KEYSPAN_PORT_OPEN || kp->kp_state == KEYSPAN_PORT_OPENING); /* bzero all elements */ bzero(ctrl_msg, sizeof (keyspan_usa19hs_port_ctrl_msg_t)); /* it is usaually 16, according to Keyspan spec */ ctrl_msg->rxForwardingLength = 16; /* from 1ms to 31ms, according to Keyspan spec. */ ctrl_msg->rxForwardingTimeout = 16; ctrl_msg->portEnabled = 1; ctrl_msg->returnStatus = 1; if (tp == NULL) { return; } cnt = tp->tp_cnt; pe = tp->tp_entries; /* translate tp parameters into cmd_msg elements */ for (i = 0; i < cnt; i++, pe++) { switch (pe->param) { case DS_PARAM_BAUD: ui = pe->val.ui; /* * if we don't support this speed, * then return failure. */ if ((ui >= NELEM(keyspan_speedtab_usa19hs)) || ((ui > 0) && (keyspan_speedtab_usa19hs[ui] == 0))) { USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs:" " bad baud %d", ui); break; } /* if the same as the old rate, need not set the rate */ if (kp->kp_baud == keyspan_speedtab_usa19hs[ui]) { USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs:" " same as old baud setting, baud = %d", keyspan_speed2baud[ui]); break; } ctrl_msg->setClocking = 1; /* enable the setting */ ctrl_msg->setRxMode = 1; ctrl_msg->setTxMode = 1; ctrl_msg->baudLo = keyspan_speedtab_usa19hs[ui] & 0xff; ctrl_msg->baudHi = (keyspan_speedtab_usa19hs[ui] >> 8) & 0xff; ctrl_msg->rxMode = RXMODE_BYHAND; ctrl_msg->txMode = TXMODE_BYHAND; USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: baud=%d", keyspan_speed2baud[ui]); break; case DS_PARAM_PARITY: if (pe->val.ui & PARENB) { /* * Since USA_PARITY_NONE == 0, it's not * necessary to or it in here. */ if (pe->val.ui & PARODD) { ctrl_msg->lcr |= USA_PARITY_ODD; } else { ctrl_msg->lcr |= USA_PARITY_EVEN; } } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: parity=%x,lcr = %x", pe->val.ui, ctrl_msg->lcr); break; case DS_PARAM_STOPB: if (pe->val.ui & CSTOPB) { ctrl_msg->lcr |= STOPBITS_678_2; } else { /* * STOPBITS_5678_1 equals zero, * so it's not necessary to or it in. */ USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs:" " STOPBITS_5678_1"); } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: stopb=%x, lcr = %x", pe->val.ui, ctrl_msg->lcr); break; case DS_PARAM_CHARSZ: switch (pe->val.ui) { case CS5: /* * USA_DATABITS_5 equals zero, * not necessary to or it in. */ USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs:" " USA_DATABITS_5"); break; case CS6: ctrl_msg->lcr |= USA_DATABITS_6; break; case CS7: ctrl_msg->lcr |= USA_DATABITS_7; break; case CS8: default: /* * The default value is USA_DATABITS_8. It is * safe to set to the default one here. */ ctrl_msg->lcr |= USA_DATABITS_8; break; } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: cs=%x, lcr = %x", pe->val.ui, ctrl_msg->lcr); break; case DS_PARAM_XON_XOFF: ctrl_msg->xonChar = pe->val.uc[0]; /* init to CSTART */ ctrl_msg->xoffChar = pe->val.uc[1]; /* init to CSTOP */ USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: xonChar=%x, " "xoffChar = %x", ctrl_msg->xonChar, ctrl_msg->xoffChar); break; case DS_PARAM_FLOW_CTL: if (pe->val.ui & CTSXON) { ctrl_msg->txFlowControl = TXFLOW_CTS; ctrl_msg->setTxFlowControl = 1; } else { /* Clear the tx flow control setting */ ctrl_msg->txFlowControl = 0; ctrl_msg->setTxFlowControl = 1; } if (pe->val.ui & RTSXOFF) { ctrl_msg->rxFlowControl = RXFLOW_RTS; ctrl_msg->setRxFlowControl = 1; } else { /* Clear the rx flow control setting */ ctrl_msg->rxFlowControl = 0; ctrl_msg->setRxFlowControl = 1; } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: txFlowControl = %x," "rxFlowControl = %x", ctrl_msg->txFlowControl, ctrl_msg->rxFlowControl); break; default: USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa19hs: bad param %d", pe->param); break; } } /* * Enable the lcr settings only if they are different * with the existing settings. */ ctrl_msg->setLcr = (ctrl_msg->lcr == kp->kp_lcr) ? 0 : 1; } /* * Build the command message according to the params from usbser. * The message will then be sent to deivce by keyspan_send_cmd. */ static void keyspan_build_cmd_msg_usa49(keyspan_port_t *kp, ds_port_params_t *tp) { int cnt, i; uint_t ui; ds_port_param_entry_t *pe; keyspan_usa49_port_ctrl_msg_t *ctrl_msg = &(kp->kp_ctrl_msg.usa49); USB_DPRINTF_L4(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: tp = %p", (void *)tp); ASSERT(mutex_owned(&kp->kp_mutex)); ASSERT(kp->kp_state == KEYSPAN_PORT_OPEN || kp->kp_state == KEYSPAN_PORT_OPENING); /* bzero all elements */ bzero(ctrl_msg, sizeof (keyspan_usa49_port_ctrl_msg_t)); ctrl_msg->portNumber = kp->kp_port_num; /* it is usaually 16, according to Keyspan spec */ ctrl_msg->forwardingLength = 16; ctrl_msg->enablePort = 1; ctrl_msg->returnStatus = 1; if (tp == NULL) { return; } cnt = tp->tp_cnt; pe = tp->tp_entries; /* translate tp parameters into cmd_msg elements */ for (i = 0; i < cnt; i++, pe++) { switch (pe->param) { case DS_PARAM_BAUD: ui = pe->val.ui; /* * If we don't support this speed, * then return failure. */ if ((ui >= NELEM(keyspan_speedtab_usa49)) || ((ui > 0) && (keyspan_speedtab_usa49[ui] == 0))) { USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49:" " bad baud %d", ui); break; } /* if the same as the old rate, need not set the rate */ if (kp->kp_baud == keyspan_speedtab_usa49[ui]) { USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: " "same as old baud setting, baud = %d", keyspan_speed2baud[ui]); break; } ctrl_msg->setClocking = 0xff; /* enable the setting */ ctrl_msg->baudLo = keyspan_speedtab_usa49[ui] & 0xff; ctrl_msg->baudHi = (keyspan_speedtab_usa49[ui] >> 8) & 0xff; ctrl_msg->prescaler = keyspan_prescaler_49wlc[ui]; USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: baud=%d", keyspan_speed2baud[ui]); break; case DS_PARAM_PARITY: if (pe->val.ui & PARENB) { /* * Since USA_PARITY_NONE == 0, * it's not necessary to or it in here. */ if (pe->val.ui & PARODD) { ctrl_msg->lcr |= USA_PARITY_ODD; } else { ctrl_msg->lcr |= USA_PARITY_EVEN; } } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: parity=%x, lcr = %x", pe->val.ui, ctrl_msg->lcr); break; case DS_PARAM_STOPB: if (pe->val.ui & CSTOPB) { ctrl_msg->lcr |= STOPBITS_678_2; } else { /* * STOPBITS_5678_1 equals zero, * not necessary to or it in. */ USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: " "STOPBITS_5678_1"); } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: stopb=%x, lcr = %x", pe->val.ui, ctrl_msg->lcr); break; case DS_PARAM_CHARSZ: switch (pe->val.ui) { case CS5: /* * USA_DATABITS_5 equals zero, * not necessary to or it in. */ USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49:" " USA_DATABITS_5"); break; case CS6: ctrl_msg->lcr |= USA_DATABITS_6; break; case CS7: ctrl_msg->lcr |= USA_DATABITS_7; break; case CS8: default: ctrl_msg->lcr |= USA_DATABITS_8; break; } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: cs=%x, lcr = %x", pe->val.ui, ctrl_msg->lcr); break; case DS_PARAM_XON_XOFF: ctrl_msg->xonChar = pe->val.uc[0]; /* init to CSTART */ ctrl_msg->xoffChar = pe->val.uc[1]; /* init to CSTOP */ USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: xonChar=%x, " "xoffChar = %x", ctrl_msg->xonChar, ctrl_msg->xoffChar); break; case DS_PARAM_FLOW_CTL: if (pe->val.ui & CTSXON) { ctrl_msg->ctsFlowControl = 1; ctrl_msg->setFlowControl = 1; } else { ctrl_msg->ctsFlowControl = 0; ctrl_msg->setFlowControl = 1; } if (pe->val.ui & RTSXOFF) { USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: " "pe->val.ui = %x, flow_ctl: RTSXOFF, " "no hardware support", pe->val.ui); } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: ctsFlowControl = %x," "dsrFlowControl = %x", ctrl_msg->ctsFlowControl, ctrl_msg->dsrFlowControl); break; default: USB_DPRINTF_L2(DPRINT_CTLOP, kp->kp_lh, "keyspan_build_cmd_msg_usa49: bad param %d", pe->param); break; } } /* * enable the lcr settings only if they are different * with the existing settings. */ ctrl_msg->setLcr = (ctrl_msg->lcr == kp->kp_lcr) ? 0 : 1; } /* * Set the port parameters to default values */ static void keyspan_default_port_params_usa49(keyspan_port_t *kp) { keyspan_usa49_port_ctrl_msg_t *ctrl_msg = &(kp->kp_ctrl_msg.usa49); ASSERT(mutex_owned(&kp->kp_mutex)); keyspan_build_cmd_msg(kp, NULL); ctrl_msg->setRts = 1; ctrl_msg->rts = 1; ctrl_msg->setDtr = 1; ctrl_msg->dtr = 1; ctrl_msg->_txOn = 1; ctrl_msg->_txOff = 0; ctrl_msg->txFlush = 0; ctrl_msg->txBreak = 0; ctrl_msg->rxOn = 1; ctrl_msg->rxOff = 0; ctrl_msg->rxFlush = 0; ctrl_msg->rxForward = 0; ctrl_msg->returnStatus = 1; ctrl_msg->resetDataToggle = 0; ctrl_msg->enablePort = 1; ctrl_msg->disablePort = 0; /* set baud rate to 9600 */ ctrl_msg->setClocking = 1; ctrl_msg->baudLo = keyspan_speedtab_usa49[13] & 0xff; ctrl_msg->baudHi = (keyspan_speedtab_usa49[13] >> 8) & 0xff; ctrl_msg->prescaler = keyspan_prescaler_49wlc[13]; ctrl_msg->lcr = 0x3; ctrl_msg->setLcr = 1; ctrl_msg->xonChar = CSTART; ctrl_msg->xoffChar = CSTOP; ctrl_msg->ctsFlowControl = 1; ctrl_msg->setFlowControl = 1; } /* save the port params after send cmd successfully */ static void keyspan_save_port_params_usa49(keyspan_port_t *kp) { keyspan_usa49_port_ctrl_msg_t *ctrl_msg = &(kp->kp_ctrl_msg.usa49); ASSERT(mutex_owned(&kp->kp_mutex)); if (ctrl_msg->setClocking) { kp->kp_baud = ctrl_msg->baudHi; kp->kp_baud = (kp->kp_baud << 8); kp->kp_baud |= ctrl_msg->baudLo; } if (ctrl_msg->setLcr) { kp->kp_lcr = ctrl_msg->lcr; } if (ctrl_msg->setRts) { if (ctrl_msg->rts) { kp->kp_status_flag |= KEYSPAN_PORT_RTS; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_RTS; } } if (ctrl_msg->setDtr) { if (ctrl_msg->dtr) { kp->kp_status_flag |= KEYSPAN_PORT_DTR; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_DTR; } } if (ctrl_msg->enablePort) { kp->kp_status_flag |= KEYSPAN_PORT_ENABLE; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_ENABLE; } /* * There are no flags in status msg (49wlc) can indicate the * break status, so we make use of ctrl_msg->txBreak here. */ if (ctrl_msg->txBreak) { kp->kp_status_flag |= KEYSPAN_PORT_TXBREAK; } else { kp->kp_status_flag &= ~KEYSPAN_PORT_TXBREAK; } USB_DPRINTF_L3(DPRINT_CTLOP, kp->kp_lh, "keyspan_save_port_params: baud = %x, lcr = %x," "status_flag = %x", kp->kp_baud, kp->kp_lcr, kp->kp_status_flag); }