Skip to content

Instantly share code, notes, and snippets.

@sjorge
Created June 6, 2020 13:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjorge/975bd61ffb8061c53dac8c465aca0485 to your computer and use it in GitHub Desktop.
Save sjorge/975bd61ffb8061c53dac8c465aca0485 to your computer and use it in GitHub Desktop.

Currently I am stuck trying to retrieve the version of the chip, which seems to be needed to configure it slightly different later on. FreeBSD function https://github.com/freebsd/freebsd/blob/f6abce8e79ecc1992d2d0b2e53f8e92ab3102671/sys/dev/usb/serial/uchcom.c#L479, which calls a different function to read data into a buffer from the device.

A similar function is also present for writing, based on ftdi and pl2303 I managed to get the write function to work (I think, it returns USB_SUCCESS). However I cannot get the read function to work, It returns USB_FAILURE with CR set to Endpoint returned stall PID

static int
uchcom_cmd_vendor_write0(uchcom_state_t *uch,
    uint16_t reqno, uint16_t val, uint16_t idx)
{
	usb_ctrl_setup_t req;
	usb_cb_flags_t cb_flags;
	usb_cr_t cr;
	int rval;

	ASSERT(!mutex_owned(&uch->uch_lock));

	bzero(&req, sizeof (req));
	req.bmRequestType =
	    USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_HOST_TO_DEV;
	req.bRequest = (uchar_t)reqno;
	req.wValue = val;
	req.wIndex = idx;
	req.wLength = 0;
	req.attrs = USB_ATTRS_NONE;

	if ((rval = usb_pipe_ctrl_xfer_wait(uch->uch_def_ph,
	    &req, NULL, &cr, &cb_flags, 0)) != USB_SUCCESS) {
		USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
		    "uchcom_cmd_vendor_write0: 0x%x 0x%x 0x%x failed %d %d 0x%x",
		    reqno, val, idx, rval, cr, cb_flags);
	}

	return (rval);
}

static int
uchcom_cmd_vendor_read0(uchcom_state_t *uch,
    uint16_t reqno, uint16_t val, uint16_t idx, void *buf, uint16_t buflen)
{
	usb_ctrl_setup_t req;
	usb_cb_flags_t cb_flags;
	usb_cr_t cr;
	mblk_t *mp = NULL;
	int rval;

	bzero(&req, sizeof (req));
	req.bmRequestType =
	    USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_DEV_TO_HOST;
	req.bRequest = (uchar_t)reqno;
	req.wValue = val;
	req.wIndex = idx;
	req.wLength = buflen;
	req.attrs = USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING;

	if ((rval = usb_pipe_ctrl_xfer_wait(uch->uch_def_ph,
	    &req, &mp, &cr, &cb_flags, 0)) != USB_SUCCESS) {
		USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
		    "uchcom_cmd_vendor_read0: get regs req failed :"
		    " cr:%s(%d), cb_flags:(%x)\n",
		    usb_str_cr(cr), cr, cb_flags);
		cmn_err(CE_WARN,
		    "uchcom_cmd_vendor_read0: get regs req failed :"
		    " cr:%s(%d), cb_flags:(%x)\n",
		    usb_str_cr(cr), cr, cb_flags);
	} else {
		bcopy(mp->b_rptr, buf, buflen);
	}

	freemsg(mp);
	return (rval);
}

I've also included a full patch with all I have so far, uchcom_open_hw_port is the first function called that is not yet implemented so it has a lot of ugly debug and testing stuff. The idea is to save the version to uch->uch_chip_version in that function. Then to move on to uchcom_set_port_params.

But I'm hitting the issue with the read failing.

diff --git a/usr/src/pkg/manifests/driver-serial-uchcom.mf b/usr/src/pkg/manifests/driver-serial-uchcom.mf
new file mode 100644
index 0000000000..e06c57bd0e
--- /dev/null
+++ b/usr/src/pkg/manifests/driver-serial-uchcom.mf
@@ -0,0 +1,50 @@
+#
+# 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+
+#
+# The default for payload-bearing actions in this package is to appear in the
+# global zone only. See the include file for greater detail, as well as
+# information about overriding the defaults.
+#
+<include global_zone_only_component>
+set name=pkg.fmri value=pkg:/driver/serial/uchcom@$(PKGVERS)
+set name=pkg.description value="CH340/CH341 serial driver"
+set name=pkg.summary value="UCHCOM"
+set name=info.classification value=org.opensolaris.category.2008:Drivers/Ports
+set name=variant.arch value=$(ARCH)
+dir path=kernel group=sys
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
+dir path=usr/share/man
+dir path=usr/share/man/man7d
+driver name=usbftdi perms="* 0666 root sys" \
+ alias=usb1a86,5523 \
+ alias=usb1a86,7523
+file path=kernel/drv/$(ARCH64)/uchcom group=sys
+file path=usr/share/man/man7d/uchcom.7d
+legacy pkg=SUNWuftdi desc="CH340/CH341 serial driver" \
+ name="UCHCOM"
+license cr_Sun license=cr_Sun
+license lic_CDDL license=lic_CDDL
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 6cc78d6fb3..1f3b51f2e2 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -829,6 +829,8 @@ USBSPRL_OBJS += usbser_pl2303.o pl2303_dsd.o
USBFTDI_OBJS += usbser_uftdi.o uftdi_dsd.o
+UCHCOM_OBJS += usbser_uchcom.o uchcom_dsd.o
+
USBECM_OBJS += usbecm.o
WC_OBJS += wscons.o vcons.o
diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules
index c2502f9ebb..6a65927840 100644
--- a/usr/src/uts/common/Makefile.rules
+++ b/usr/src/uts/common/Makefile.rules
@@ -1248,6 +1248,10 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/usbser/usbsprl/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/usbser/uchcom/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/usbecm/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -2541,6 +2545,9 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/usb/clients/usbser/usbser_keyspan/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/usb/clients/usbser/usbsprl/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/usb/clients/usbser/uchcom/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/usb/clients/usbecm/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
diff --git a/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c b/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c
new file mode 100644
index 0000000000..08dd34b49c
--- /dev/null
+++ b/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c
@@ -0,0 +1,1738 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Copyright 2020 Jorge Schrauwen <sjorge@blackdot.be>
+ */
+
+/*
+ * DSD code for WinChipHead CH341/340 adapter
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/stream.h>
+#include <sys/strsun.h>
+#include <sys/termio.h>
+#include <sys/termiox.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+#define USBDRV_MAJOR_VER 2
+#define USBDRV_MINOR_VER 0
+
+#include <sys/usb/usba.h>
+#include <sys/usb/usba/usba_types.h>
+#include <sys/usb/usba/usba_impl.h>
+
+#include <sys/usb/clients/usbser/usbser_dsdi.h>
+#include <sys/usb/clients/usbser/uchcom/uchcom_var.h>
+#include <sys/usb/clients/usbser/uchcom/uchcom_reg.h>
+
+#include <sys/usb/usbdevs.h>
+
+#include <sys/cmn_err.h>
+
+/*
+ * DSD operations
+ */
+static int uchcom_attach(ds_attach_info_t *);
+static void uchcom_detach(ds_hdl_t);
+static int uchcom_register_cb(ds_hdl_t, uint_t, ds_cb_t *);
+static void uchcom_unregister_cb(ds_hdl_t, uint_t);
+static int uchcom_open_port(ds_hdl_t, uint_t);
+static int uchcom_close_port(ds_hdl_t, uint_t);
+
+/* power management */
+static int uchcom_usb_power(ds_hdl_t, int, int, int *);
+static int uchcom_suspend(ds_hdl_t);
+static int uchcom_resume(ds_hdl_t);
+static int uchcom_disconnect(ds_hdl_t);
+static int uchcom_reconnect(ds_hdl_t);
+
+/* standard UART operations */
+static int uchcom_set_port_params(ds_hdl_t, uint_t, ds_port_params_t *);
+static int uchcom_set_modem_ctl(ds_hdl_t, uint_t, int, int);
+static int uchcom_get_modem_ctl(ds_hdl_t, uint_t, int, int *);
+static int uchcom_break_ctl(ds_hdl_t, uint_t, int);
+
+/* data xfer */
+static int uchcom_tx(ds_hdl_t, uint_t, mblk_t *);
+static mblk_t *uchcom_rx(ds_hdl_t, uint_t);
+static void uchcom_stop(ds_hdl_t, uint_t, int);
+static void uchcom_start(ds_hdl_t, uint_t, int);
+static int uchcom_fifo_flush(ds_hdl_t, uint_t, int);
+static int uchcom_fifo_drain(ds_hdl_t, uint_t, int);
+
+/* polled I/O support */
+static usb_pipe_handle_t uchcom_out_pipe(ds_hdl_t, uint_t);
+static usb_pipe_handle_t uchcom_in_pipe(ds_hdl_t, uint_t);
+
+/*
+ * Sub-routines
+ */
+
+/* configuration routines */
+static void uchcom_cleanup(uchcom_state_t *, int);
+static int uchcom_dev_attach(uchcom_state_t *);
+static int uchcom_open_hw_port(uchcom_state_t *);
+
+/* hotplug */
+static int uchcom_restore_device_state(uchcom_state_t *);
+static int uchcom_restore_port_state(uchcom_state_t *);
+
+/* power management */
+static int uchcom_create_pm_components(uchcom_state_t *);
+static void uchcom_destroy_pm_components(uchcom_state_t *);
+static int uchcom_pm_set_busy(uchcom_state_t *);
+static void uchcom_pm_set_idle(uchcom_state_t *);
+static int uchcom_pwrlvl0(uchcom_state_t *);
+static int uchcom_pwrlvl1(uchcom_state_t *);
+static int uchcom_pwrlvl2(uchcom_state_t *);
+static int uchcom_pwrlvl3(uchcom_state_t *);
+
+/* pipe operations */
+static int uchcom_open_pipes(uchcom_state_t *);
+static void uchcom_close_pipes(uchcom_state_t *);
+static void uchcom_disconnect_pipes(uchcom_state_t *);
+static int uchcom_reconnect_pipes(uchcom_state_t *);
+
+/* pipe callbacks */
+static void uchcom_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *);
+static void uchcom_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *);
+
+/* data transfer routines */
+static int uchcom_rx_start(uchcom_state_t *);
+static void uchcom_tx_start(uchcom_state_t *, int *);
+static int uchcom_send_data(uchcom_state_t *, mblk_t *);
+static int uchcom_wait_tx_drain(uchcom_state_t *, int);
+
+/* vendor-specific commands */
+static int uchcom_cmd_vendor_write0(uchcom_state_t *,
+ uint16_t, uint16_t, uint16_t);
+static int uchcom_cmd_vendor_read0(uchcom_state_t *,
+ uint16_t, uint16_t, uint16_t, void *, uint16_t);
+
+/* misc */
+static void uchcom_put_tail(mblk_t **, mblk_t *);
+static void uchcom_put_head(mblk_t **, mblk_t *);
+
+
+/*
+ * DSD ops structure
+ */
+ds_ops_t uchcom_ds_ops = {
+ DS_OPS_VERSION,
+ uchcom_attach,
+ uchcom_detach,
+ uchcom_register_cb,
+ uchcom_unregister_cb,
+ uchcom_open_port,
+ uchcom_close_port,
+ uchcom_usb_power,
+ uchcom_suspend,
+ uchcom_resume,
+ uchcom_disconnect,
+ uchcom_reconnect,
+ uchcom_set_port_params,
+ uchcom_set_modem_ctl,
+ uchcom_get_modem_ctl,
+ uchcom_break_ctl,
+ NULL, /* no loopback support */
+ uchcom_tx,
+ uchcom_rx,
+ uchcom_stop,
+ uchcom_start,
+ uchcom_fifo_flush,
+ uchcom_fifo_drain,
+ uchcom_out_pipe,
+ uchcom_in_pipe
+};
+
+/* debug support */
+static uint_t uchcom_errlevel = USB_LOG_L4;
+static uint_t uchcom_errmask = DPRINT_MASK_ALL;
+static uint_t uchcom_instance_debug = (uint_t)-1;
+
+/*
+ * ds_attach
+ */
+static int
+uchcom_attach(ds_attach_info_t *aip)
+{
+ uchcom_state_t *uch;
+ usb_dev_descr_t *dd;
+ boolean_t recognized;
+
+ uch = kmem_zalloc(sizeof (*uch), KM_SLEEP);
+ uch->uch_dip = aip->ai_dip;
+ uch->uch_usb_events = aip->ai_usb_events;
+ *aip->ai_hdl = (ds_hdl_t)uch;
+
+ /* only one port */
+ *aip->ai_port_cnt = 1;
+
+ if (usb_client_attach(uch->uch_dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
+ uchcom_cleanup(uch, 1);
+ return (USB_FAILURE);
+ }
+
+ if (usb_get_dev_data(uch->uch_dip,
+ &uch->uch_dev_data, USB_PARSE_LVL_IF, 0) != USB_SUCCESS) {
+ uchcom_cleanup(uch, 2);
+ return (USB_FAILURE);
+ }
+
+ mutex_init(&uch->uch_lock, NULL, MUTEX_DRIVER,
+ uch->uch_dev_data->dev_iblock_cookie);
+
+ cv_init(&uch->uch_tx_cv, NULL, CV_DRIVER, NULL);
+
+ uch->uch_lh = usb_alloc_log_hdl(uch->uch_dip, "uchcom",
+ &uchcom_errlevel, &uchcom_errmask, &uchcom_instance_debug, 0);
+
+
+ recognized = B_FALSE;
+ dd = uch->uch_dev_data->dev_descr;
+ if (dd->idVendor == UCHCOM_VENDOR) {
+ switch (dd->idProduct) {
+ case UCHCOM_CHIP_CH340:
+ USB_DPRINTF_L3(DPRINT_ATTACH, uch->uch_lh,
+ "Chip Type: CH340");
+ recognized = B_TRUE;
+ break;
+ case UCHCOM_CHIP_CH341:
+ USB_DPRINTF_L3(DPRINT_ATTACH, uch->uch_lh,
+ "Chip Type: CH341");
+ recognized = B_TRUE;
+ break;
+ }
+ }
+
+ if (!recognized) {
+ uchcom_cleanup(uch, 3);
+ return (USB_FAILURE);
+ }
+
+ uch->uch_def_ph = uch->uch_dev_data->dev_default_ph;
+
+ mutex_enter(&uch->uch_lock);
+ uch->uch_dev_state = USB_DEV_ONLINE;
+ uch->uch_port_state = UCHCOM_PORT_CLOSED;
+ mutex_exit(&uch->uch_lock);
+
+ if (uchcom_create_pm_components(uch) != USB_SUCCESS) {
+ cmn_err(CE_WARN, "uchcom: fail pmc");
+ uchcom_cleanup(uch, 3);
+ return (USB_FAILURE);
+ }
+
+ if (usb_register_event_cbs(uch->uch_dip,
+ uch->uch_usb_events, 0) != USB_SUCCESS) {
+ uchcom_cleanup(uch, 4);
+ return (USB_FAILURE);
+ }
+
+ if (uchcom_dev_attach(uch) != USB_SUCCESS) {
+ uchcom_cleanup(uch, 5);
+ return (USB_FAILURE);
+ }
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_detach
+ */
+static void
+uchcom_detach(ds_hdl_t hdl)
+{
+ uchcom_cleanup((uchcom_state_t *)hdl, UCHCOM_CLEANUP_LEVEL_MAX);
+}
+
+/*
+ * ds_register_cb
+ */
+/*ARGSUSED*/
+static int
+uchcom_register_cb(ds_hdl_t hdl, uint_t portno, ds_cb_t *cb)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ ASSERT(portno == 0);
+
+ uch->uch_cb = *cb;
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_unregister_cb
+ */
+/*ARGSUSED*/
+static void
+uchcom_unregister_cb(ds_hdl_t hdl, uint_t portno)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ ASSERT(portno == 0);
+
+ bzero(&uch->uch_cb, sizeof (uch->uch_cb));
+}
+
+/*
+ * ds_open_port
+ */
+/*ARGSUSED*/
+static int
+uchcom_open_port(ds_hdl_t hdl, uint_t portno)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_OPEN, uch->uch_lh, "uchcom_open_port %d", portno);
+
+ mutex_enter(&uch->uch_lock);
+ if (uch->uch_dev_state == USB_DEV_DISCONNECTED ||
+ uch->uch_port_state != UCHCOM_PORT_CLOSED) {
+ mutex_exit(&uch->uch_lock);
+ return (USB_FAILURE);
+ }
+ mutex_exit(&uch->uch_lock);
+
+ if ((rval = uchcom_pm_set_busy(uch)) != USB_SUCCESS)
+ return (rval);
+
+ /* initialize hardware serial port */
+ rval = uchcom_open_hw_port(uch);
+
+ if (rval == USB_SUCCESS) {
+ mutex_enter(&uch->uch_lock);
+
+ /* start to receive data */
+ if (uchcom_rx_start(uch) != USB_SUCCESS) {
+ mutex_exit(&uch->uch_lock);
+ return (USB_FAILURE);
+ }
+ uch->uch_port_state = UCHCOM_PORT_OPEN;
+ mutex_exit(&uch->uch_lock);
+ } else
+ uchcom_pm_set_idle(uch);
+
+ return (rval);
+}
+
+/*
+ * ds_close_port
+ */
+/*ARGSUSED*/
+static int
+uchcom_close_port(ds_hdl_t hdl, uint_t portno)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CLOSE, uch->uch_lh, "uchcom_close_port %d", portno);
+
+ mutex_enter(&uch->uch_lock);
+
+ /* free resources and finalize state */
+ freemsg(uch->uch_rx_mp);
+ uch->uch_rx_mp = NULL;
+
+ freemsg(uch->uch_tx_mp);
+ uch->uch_tx_mp = NULL;
+
+ uch->uch_port_state = UCHCOM_PORT_CLOSED;
+ mutex_exit(&uch->uch_lock);
+
+ uchcom_pm_set_idle(uch);
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_usb_power
+ */
+/*ARGSUSED*/
+static int
+uchcom_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ uchcom_pm_t *pm = uch->uch_pm;
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_usb_power");
+
+ if (!pm)
+ return (USB_FAILURE);
+
+ mutex_enter(&uch->uch_lock);
+
+ /*
+ * 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, uch->uch_lh, "uchcom_usb_power: "
+ "illegal power level %d, pwr_states=0x%x",
+ level, pm->pm_pwr_states);
+ mutex_exit(&uch->uch_lock);
+ 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(&uch->uch_lock);
+ return (USB_FAILURE);
+ }
+
+ switch (level) {
+ case USB_DEV_OS_PWR_OFF:
+ rval = uchcom_pwrlvl0(uch);
+ break;
+ case USB_DEV_OS_PWR_1:
+ rval = uchcom_pwrlvl1(uch);
+ break;
+ case USB_DEV_OS_PWR_2:
+ rval = uchcom_pwrlvl2(uch);
+ break;
+ case USB_DEV_OS_FULL_PWR:
+ rval = uchcom_pwrlvl3(uch);
+ /*
+ * 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))
+ uch->uch_dev_state = *new_state;
+ break;
+ default:
+ ASSERT(0); /* cannot happen */
+ }
+
+ *new_state = uch->uch_dev_state;
+ mutex_exit(&uch->uch_lock);
+
+ return (rval);
+}
+
+/*
+ * ds_suspend
+ */
+static int
+uchcom_suspend(ds_hdl_t hdl)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ int state = USB_DEV_SUSPENDED;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_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(&uch->uch_lock);
+ if (uch->uch_dev_state != USB_DEV_PWRED_DOWN)
+ uch->uch_dev_state = USB_DEV_SUSPENDED;
+ mutex_exit(&uch->uch_lock);
+
+ uchcom_disconnect_pipes(uch);
+ return (state);
+}
+
+/*
+ * ds_resume
+ */
+static int
+uchcom_resume(ds_hdl_t hdl)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ int current_state;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_resume");
+
+ mutex_enter(&uch->uch_lock);
+ current_state = uch->uch_dev_state;
+ mutex_exit(&uch->uch_lock);
+
+ if (current_state == USB_DEV_ONLINE)
+ return (USB_SUCCESS);
+
+ return (uchcom_restore_device_state(uch));
+}
+
+/*
+ * ds_disconnect
+ */
+static int
+uchcom_disconnect(ds_hdl_t hdl)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ int state = USB_DEV_DISCONNECTED;
+
+ USB_DPRINTF_L4(DPRINT_HOTPLUG, uch->uch_lh, "uchcom_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(&uch->uch_lock);
+ if (uch->uch_dev_state != USB_DEV_PWRED_DOWN)
+ uch->uch_dev_state = USB_DEV_DISCONNECTED;
+ mutex_exit(&uch->uch_lock);
+
+ uchcom_disconnect_pipes(uch);
+
+ return (state);
+}
+
+/*
+ * ds_reconnect
+ */
+static int
+uchcom_reconnect(ds_hdl_t hdl)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_HOTPLUG, uch->uch_lh, "uchcom_reconnect");
+
+ return (uchcom_restore_device_state(uch));
+}
+
+/*
+ * ds_set_port_params
+ */
+static int
+uchcom_set_port_params(ds_hdl_t hdl, uint_t portno, ds_port_params_t *tp)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ int rval;
+ int i;
+ ds_port_param_entry_t *pe;
+
+ if (tp == NULL)
+ return (USB_FAILURE);
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_set_port_params");
+
+ rval = uchcom_cmd_vendor_write0(uch, UCHCOM_REQ_RESET, 0, 0);
+ if (rval != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
+ "uchcom_set_port_params: failed to reset!");
+ return (rval);
+ }
+
+ for (i = 0, pe = tp->tp_entries; i < tp->tp_cnt; i++, pe++) {
+ switch (pe->param) {
+ case DS_PARAM_BAUD:
+ switch (pe->val.ui) {
+ case B300:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 300");
+ break;
+ case B600:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 600");
+ break;
+ case B1200:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 1200");
+ break;
+ case B2400:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 2400");
+ break;
+ case B4800:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 4800");
+ break;
+ case B9600:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 9400");
+ break;
+ case B19200:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 19200");
+ break;
+ case B38400:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 38400");
+ break;
+ case B57600:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 57600");
+ break;
+ case B115200:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 115200");
+ break;
+ case B230400:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 230400");
+ break;
+ case B460800:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 460800");
+ break;
+ case B921600:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 921600");
+ break;
+ default:
+ USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh,
+ "uchcom_set_port_params: bad baud %d",
+ pe->val.ui);
+ return (USB_FAILURE);
+ }
+ break;
+
+ case DS_PARAM_PARITY:
+ if (pe->val.ui & PARENB) {
+ if (pe->val.ui & PARODD)
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params par odd");
+ else
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params par even");
+ } else {
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params par none");
+ }
+ break;
+
+ case DS_PARAM_STOPB:
+ if (pe->val.ui & CSTOPB)
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params stop bits2");
+ else {
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params stop bits1");
+ }
+ break;
+
+ case DS_PARAM_CHARSZ:
+ switch (pe->val.ui) {
+ case CS8:
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params csz cs8");
+ break;
+ default:
+ USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh,
+ "uchcom_set_port_params: bad charsz");
+ return (USB_FAILURE);
+ }
+ break;
+
+ case DS_PARAM_XON_XOFF: /* Software flow control */
+ if ((pe->val.ui & IXON) || (pe->val.ui & IXOFF)) {
+ uint8_t xonc = pe->val.uc[0];
+ uint8_t xoffc = pe->val.uc[1];
+
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params soft flow ???");
+ }
+ break;
+
+ case DS_PARAM_FLOW_CTL: /* Hardware flow control */
+ if (pe->val.ui & (RTSXOFF | CTSXON)) {
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params hard flow RTS_CTS_HS");
+ }
+ if (pe->val.ui & DTRXOFF) {
+ cmn_err(CE_WARN, "XXX: uchcom_set_port_params hard flow DTR_DSR_HS");
+ }
+ break;
+ default:
+ USB_DPRINTF_L2(DPRINT_CTLOP, uch->uch_lh,
+ "uchcom_set_port_params: bad param %d", pe->param);
+ break;
+ }
+ }
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_set_modem_ctl
+ */
+static int
+uchcom_set_modem_ctl(ds_hdl_t hdl, uint_t portno, int mask, int val)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_set_modem_ctl");
+
+ // XXX: chip is weird and picky, figure out later
+ cmn_err(CE_WARN, "XXX: uchcom_set_modem_ctl");
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_get_modem_ctl
+ */
+static int
+uchcom_get_modem_ctl(ds_hdl_t hdl, uint_t portno, int mask, int *valp)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_get_modem_ctl");
+
+ // XXX: chip is weird and picky, figure out later
+ cmn_err(CE_WARN, "XXX: uchcom_get_modem_ctl");
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_break_ctl
+ */
+static int
+uchcom_break_ctl(ds_hdl_t hdl, uint_t portno, int ctl)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_break_ctl");
+
+ // XXX: chip is weird and picky, figure out later
+ cmn_err(CE_WARN, "XXX: uchcom_break_ctl");
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_tx
+ */
+/*ARGSUSED*/
+static int
+uchcom_tx(ds_hdl_t hdl, uint_t portno, mblk_t *mp)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_tx");
+
+ ASSERT(mp != NULL && MBLKL(mp) >= 1);
+
+ mutex_enter(&uch->uch_lock);
+ uchcom_put_tail(&uch->uch_tx_mp, mp); /* add to the chain */
+ uchcom_tx_start(uch, NULL);
+ mutex_exit(&uch->uch_lock);
+
+ return (USB_SUCCESS);
+}
+
+
+/*
+ * ds_rx
+ */
+/*ARGSUSED*/
+static mblk_t *
+uchcom_rx(ds_hdl_t hdl, uint_t portno)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+ mblk_t *mp;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_rx");
+
+ mutex_enter(&uch->uch_lock);
+ mp = uch->uch_rx_mp;
+ uch->uch_rx_mp = NULL;
+ mutex_exit(&uch->uch_lock);
+
+ return (mp);
+}
+
+/*
+ * ds_stop
+ */
+/*ARGSUSED*/
+static void
+uchcom_stop(ds_hdl_t hdl, uint_t portno, int dir)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_stop");
+
+ if (dir & DS_TX) {
+ mutex_enter(&uch->uch_lock);
+ uch->uch_port_flags |= UCHCOM_PORT_TX_STOPPED;
+ mutex_exit(&uch->uch_lock);
+ }
+}
+
+
+/*
+ * ds_start
+ */
+/*ARGSUSED*/
+static void
+uchcom_start(ds_hdl_t hdl, uint_t portno, int dir)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_start");
+
+ if (dir & DS_TX) {
+ mutex_enter(&uch->uch_lock);
+ if (uch->uch_port_flags & UCHCOM_PORT_TX_STOPPED) {
+ uch->uch_port_flags &= ~UCHCOM_PORT_TX_STOPPED;
+ uchcom_tx_start(uch, NULL);
+ }
+ mutex_exit(&uch->uch_lock);
+ }
+}
+
+/*
+ * ds_fifo_flush
+ */
+/*ARGSUSED*/
+static int
+uchcom_fifo_flush(ds_hdl_t hdl, uint_t portno, int dir)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh,
+ "uchcom_fifo_flush: dir=0x%x", dir);
+
+ mutex_enter(&uch->uch_lock);
+ ASSERT(uch->uch_port_state == UCHCOM_PORT_OPEN);
+
+ if (dir & DS_TX) {
+ freemsg(uch->uch_tx_mp);
+ uch->uch_tx_mp = NULL;
+ }
+
+ if (dir & DS_RX) {
+ freemsg(uch->uch_rx_mp);
+ uch->uch_rx_mp = NULL;
+ }
+ mutex_exit(&uch->uch_lock);
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * ds_fifo_drain
+ */
+/*ARGSUSED*/
+static int
+uchcom_fifo_drain(ds_hdl_t hdl, uint_t portno, int timeout)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_fifo_drain");
+
+ mutex_enter(&uch->uch_lock);
+ ASSERT(uch->uch_port_state == UCHCOM_PORT_OPEN);
+
+ if (uchcom_wait_tx_drain(uch, 0) != USB_SUCCESS) {
+ mutex_exit(&uch->uch_lock);
+ return (USB_FAILURE);
+ }
+
+ mutex_exit(&uch->uch_lock);
+
+ /* wait 500 ms until hw fifo drains */
+ delay(drv_usectohz(500*1000));
+
+ return (USB_SUCCESS);
+}
+
+/*ARGSUSED*/
+static usb_pipe_handle_t
+uchcom_out_pipe(ds_hdl_t hdl, uint_t port_num)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ return (uch->uch_bulkout_ph);
+}
+
+/*ARGSUSED*/
+static usb_pipe_handle_t
+uchcom_in_pipe(ds_hdl_t hdl, uint_t port_num)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)hdl;
+
+ return (uch->uch_bulkin_ph);
+}
+
+/*
+ * configuration clean up
+ */
+static void
+uchcom_cleanup(uchcom_state_t *uch, int level)
+{
+ ASSERT(level > 0 && level <= UCHCOM_CLEANUP_LEVEL_MAX);
+
+ switch (level) {
+ default:
+ case 6:
+ uchcom_close_pipes(uch);
+ /*FALLTHROUGH*/
+ case 5:
+ usb_unregister_event_cbs(uch->uch_dip, uch->uch_usb_events);
+ /*FALLTHROUGH*/
+ case 4:
+ uchcom_destroy_pm_components(uch);
+ /*FALLTHROUGH*/
+ case 3:
+ mutex_destroy(&uch->uch_lock);
+ cv_destroy(&uch->uch_tx_cv);
+
+ usb_free_log_hdl(uch->uch_lh);
+ uch->uch_lh = NULL;
+
+ usb_free_descr_tree(uch->uch_dip, uch->uch_dev_data);
+ uch->uch_def_ph = NULL;
+ /*FALLTHROUGH*/
+ case 2:
+ usb_client_detach(uch->uch_dip, uch->uch_dev_data);
+ /*FALLTHROUGH*/
+ case 1:
+ kmem_free(uch, sizeof (*uch));
+ break;
+ }
+}
+
+/*
+ * device specific attach
+ */
+static int
+uchcom_dev_attach(uchcom_state_t *uch)
+{
+ return (uchcom_open_pipes(uch));
+}
+
+static int
+uchcom_open_hw_port(uchcom_state_t *uch)
+{
+ int rval;
+ uint8_t chip_ver[UCHCOM_INPUT_BUF_SIZE];
+
+ // initialize device with defaults
+ rval = uchcom_cmd_vendor_write0(uch, UCHCOM_REQ_RESET,
+ UCHCOM_RESET_VALUE, UCHCOM_RESET_INDEX);
+ if (rval != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
+ "uchcom_open_hw_port: failed to reset!");
+ return (rval);
+ }
+
+ // get chip version
+ rval = uchcom_cmd_vendor_read0(uch, UCHCOM_REQ_GET_VERSION, 0, 0,
+ &chip_ver, sizeof(chip_ver));
+ if (rval != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
+ "uchcom_open_hw_port: failed to read chip version!");
+ if (rval == USB_FAILURE) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_FAILURE");
+ if (rval == USB_NO_RESOURCES) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_NO_RESOURCES");
+ if (rval == USB_NO_BANDWIDTH) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_NO_BANDWIDTH");
+ if (rval == USB_NOT_SUPPORTED) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_NOT_SUPPORTED");
+ if (rval == USB_PIPE_ERROR) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_PIPE_ERROR");
+ if (rval == USB_INVALID_PIPE) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_PIPE");
+ if (rval == USB_NO_FRAME_NUMBER) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_NO_FRAME_NUMBER");
+ if (rval == USB_INVALID_START_FRAME) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_START_FRAME");
+ if (rval == USB_HC_HARDWARE_ERROR) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_HC_HARDWARE_ERROR");
+ if (rval == USB_INVALID_REQUEST) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_REQUEST");
+ if (rval == USB_INVALID_CONTEXT) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_CONTEXT");
+ if (rval == USB_INVALID_VERSION) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_VERSION");
+ if (rval == USB_INVALID_ARGS) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_ARGS");
+ if (rval == USB_INVALID_PERM) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_INVALID_PERM");
+ if (rval == USB_BUSY) cmn_err(CE_WARN, "XXX: uchcom_open_hw_port USB_BUSY");
+
+ return (rval);
+ }
+
+ //mutex_enter(&uch->uch_lock);
+ //uch->uch_chip_version = chip_ver;
+ //mutex_exit(&uch->uch_lock);
+
+ /*
+ if (chip_ver == 0x20) {
+ cmn_err(CE_WARN, "XXX: chip_ver 0x20");
+ } else if (chip_ver == 0x30) {
+ cmn_err(CE_WARN, "XXX: chip_ver 0x30");
+ } else {
+ cmn_err(CE_WARN, "XXX: chip_ver ???");
+ }
+ */
+
+ cmn_err(CE_WARN, "XXX: uchcom_open_hw_port END");
+ return (USB_SUCCESS);
+}
+
+/*
+ * restore device state after CPR resume or reconnect
+ */
+static int
+uchcom_restore_device_state(uchcom_state_t *uch)
+{
+ int state;
+
+ mutex_enter(&uch->uch_lock);
+ state = uch->uch_dev_state;
+ mutex_exit(&uch->uch_lock);
+
+ if (state != USB_DEV_DISCONNECTED && state != USB_DEV_SUSPENDED)
+ return (state);
+
+ if (usb_check_same_device(uch->uch_dip, uch->uch_lh, USB_LOG_L0,
+ DPRINT_MASK_ALL, USB_CHK_ALL, NULL) != USB_SUCCESS) {
+ mutex_enter(&uch->uch_lock);
+ state = uch->uch_dev_state = USB_DEV_DISCONNECTED;
+ mutex_exit(&uch->uch_lock);
+ return (state);
+ }
+
+ if (state == USB_DEV_DISCONNECTED) {
+ USB_DPRINTF_L0(DPRINT_HOTPLUG, uch->uch_lh,
+ "Device has been reconnected but data may have been lost");
+ }
+
+ if (uchcom_reconnect_pipes(uch) != USB_SUCCESS)
+ return (state);
+
+ /*
+ * init device state
+ */
+ mutex_enter(&uch->uch_lock);
+ state = uch->uch_dev_state = USB_DEV_ONLINE;
+ mutex_exit(&uch->uch_lock);
+
+ if ((uchcom_restore_port_state(uch) != USB_SUCCESS)) {
+ USB_DPRINTF_L2(DPRINT_HOTPLUG, uch->uch_lh,
+ "uchcom_restore_device_state: failed");
+ }
+
+ return (state);
+}
+
+/*
+ * restore ports state after CPR resume or reconnect
+ */
+static int
+uchcom_restore_port_state(uchcom_state_t *uch)
+{
+ int rval;
+
+ mutex_enter(&uch->uch_lock);
+ if (uch->uch_port_state != UCHCOM_PORT_OPEN) {
+ mutex_exit(&uch->uch_lock);
+ return (USB_SUCCESS);
+ }
+ mutex_exit(&uch->uch_lock);
+
+ /* open hardware serial port, restoring old settings */
+ if ((rval = uchcom_open_hw_port(uch)) != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_HOTPLUG, uch->uch_lh,
+ "uchcom_restore_port_state: failed");
+ }
+
+ return (rval);
+}
+
+/*
+ * create PM components
+ */
+static int
+uchcom_create_pm_components(uchcom_state_t *uch)
+{
+ dev_info_t *dip = uch->uch_dip;
+ uchcom_pm_t *pm;
+ uint_t pwr_states;
+
+ if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_PM, uch->uch_lh,
+ "uchcom_create_pm_components: failed");
+ return (USB_SUCCESS);
+ }
+
+ pm = uch->uch_pm = kmem_zalloc(sizeof (*pm), KM_SLEEP);
+
+ pm->pm_pwr_states = (uint8_t)pwr_states;
+ pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
+ pm->pm_wakeup_enabled = usb_handle_remote_wakeup(dip,
+ USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS;
+
+ (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * destroy PM components
+ */
+static void
+uchcom_destroy_pm_components(uchcom_state_t *uch)
+{
+ uchcom_pm_t *pm = uch->uch_pm;
+ dev_info_t *dip = uch->uch_dip;
+ int rval;
+
+ if (!pm)
+ return;
+
+ if (uch->uch_dev_state != USB_DEV_DISCONNECTED) {
+ if (pm->pm_wakeup_enabled) {
+ rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
+ if (rval != DDI_SUCCESS)
+ USB_DPRINTF_L2(DPRINT_PM, uch->uch_lh,
+ "uchcom_destroy_pm_components: "
+ "raising power failed, rval=%d", rval);
+
+ rval = usb_handle_remote_wakeup(dip,
+ USB_REMOTE_WAKEUP_DISABLE);
+
+ if (rval != USB_SUCCESS)
+ USB_DPRINTF_L2(DPRINT_PM, uch->uch_lh,
+ "uchcom_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 (*pm));
+ uch->uch_pm = NULL;
+}
+
+/*
+ * mark device busy and raise power
+ */
+static int
+uchcom_pm_set_busy(uchcom_state_t *uch)
+{
+ uchcom_pm_t *pm = uch->uch_pm;
+ dev_info_t *dip = uch->uch_dip;
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_pm_set_busy");
+
+ if (!pm)
+ return (USB_SUCCESS);
+
+ mutex_enter(&uch->uch_lock);
+ /* if already marked busy, just increment the counter */
+ if (pm->pm_busy_cnt++ > 0) {
+ mutex_exit(&uch->uch_lock);
+ return (USB_SUCCESS);
+ }
+
+ rval = pm_busy_component(dip, 0);
+ ASSERT(rval == DDI_SUCCESS);
+
+ if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) {
+ mutex_exit(&uch->uch_lock);
+ return (USB_SUCCESS);
+ }
+
+ /* need to raise power */
+ pm->pm_raise_power = B_TRUE;
+ mutex_exit(&uch->uch_lock);
+
+ rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
+ if (rval != DDI_SUCCESS)
+ USB_DPRINTF_L2(DPRINT_PM, uch->uch_lh, "raising power failed");
+
+ mutex_enter(&uch->uch_lock);
+ pm->pm_raise_power = B_FALSE;
+ mutex_exit(&uch->uch_lock);
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * mark device idle
+ */
+static void
+uchcom_pm_set_idle(uchcom_state_t *uch)
+{
+ uchcom_pm_t *pm = uch->uch_pm;
+ dev_info_t *dip = uch->uch_dip;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_pm_set_idle");
+
+ if (!pm)
+ return;
+
+ /*
+ * if more ports use the device, do not mark as yet
+ */
+ mutex_enter(&uch->uch_lock);
+ if (--pm->pm_busy_cnt > 0) {
+ mutex_exit(&uch->uch_lock);
+ return;
+ }
+
+ if (pm)
+ (void) pm_idle_component(dip, 0);
+
+ mutex_exit(&uch->uch_lock);
+}
+
+/*
+ * Functions to handle power transition for OS levels 0 -> 3
+ * The same level as OS state, different from USB state
+ */
+static int
+uchcom_pwrlvl0(uchcom_state_t *uch)
+{
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_pwrlvl0");
+
+ switch (uch->uch_dev_state) {
+ case USB_DEV_ONLINE:
+ /* issue USB D3 command to the device */
+ rval = usb_set_device_pwrlvl3(uch->uch_dip);
+ ASSERT(rval == USB_SUCCESS);
+
+ uch->uch_dev_state = USB_DEV_PWRED_DOWN;
+ uch->uch_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, uch->uch_lh,
+ "uchcom_pwrlvl0: illegal device state");
+
+ return (USB_FAILURE);
+ }
+}
+
+static int
+uchcom_pwrlvl1(uchcom_state_t *uch)
+{
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_pwrlvl1");
+
+ /* issue USB D2 command to the device */
+ (void) usb_set_device_pwrlvl2(uch->uch_dip);
+
+ return (USB_FAILURE);
+}
+
+
+static int
+uchcom_pwrlvl2(uchcom_state_t *uch)
+{
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_pwrlvl2");
+
+ /* issue USB D1 command to the device */
+ (void) usb_set_device_pwrlvl1(uch->uch_dip);
+
+ return (USB_FAILURE);
+}
+
+
+static int
+uchcom_pwrlvl3(uchcom_state_t *uch)
+{
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_PM, uch->uch_lh, "uchcom_pwrlvl3");
+
+ switch (uch->uch_dev_state) {
+ case USB_DEV_PWRED_DOWN:
+ /* Issue USB D0 command to the device here */
+ rval = usb_set_device_pwrlvl0(uch->uch_dip);
+ ASSERT(rval == USB_SUCCESS);
+
+ uch->uch_dev_state = USB_DEV_ONLINE;
+ uch->uch_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, uch->uch_lh,
+ "uchcom_pwrlvl3: illegal device state");
+
+ return (USB_FAILURE);
+ }
+}
+
+/*
+ * pipe operations
+ */
+static int
+uchcom_open_pipes(uchcom_state_t *uch)
+{
+ int ifc, alt;
+ usb_pipe_policy_t policy;
+ usb_ep_data_t *in_data, *out_data;
+ size_t max_xfer_sz;
+
+ /* get ep data */
+ ifc = uch->uch_dev_data->dev_curr_if;
+ alt = 0;
+
+ in_data = usb_lookup_ep_data(uch->uch_dip, uch->uch_dev_data, ifc, alt,
+ 0, USB_EP_ATTR_BULK, USB_EP_DIR_IN);
+
+ out_data = usb_lookup_ep_data(uch->uch_dip, uch->uch_dev_data, ifc, alt,
+ 0, USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
+
+ if ((in_data == NULL) || (out_data == NULL)) {
+ USB_DPRINTF_L2(DPRINT_ATTACH, uch->uch_lh,
+ "uchcom_open_pipes: can't get ep data");
+
+ return (USB_FAILURE);
+ }
+
+ /*
+ * Set buffer sizes. Default to UCHCOM_XFER_SZ_MAX.
+ * Use wMaxPacketSize from endpoint descriptor if it is nonzero.
+ * Cap at a max transfer size of host controller.
+ */
+ uch->uch_ibuf_sz = uch->uch_obuf_sz = UCHCOM_XFER_SZ_MAX;
+
+ if (in_data->ep_descr.wMaxPacketSize)
+ uch->uch_ibuf_sz = in_data->ep_descr.wMaxPacketSize;
+ uch->uch_ibuf_sz = min(uch->uch_ibuf_sz, max_xfer_sz);
+
+ if (out_data->ep_descr.wMaxPacketSize)
+ uch->uch_obuf_sz = out_data->ep_descr.wMaxPacketSize;
+ uch->uch_obuf_sz = min(uch->uch_obuf_sz, max_xfer_sz);
+
+ /* open pipes */
+ policy.pp_max_async_reqs = 2;
+
+ if (usb_pipe_open(uch->uch_dip, &in_data->ep_descr, &policy,
+ USB_FLAGS_SLEEP, &uch->uch_bulkin_ph) != USB_SUCCESS) {
+
+ return (USB_FAILURE);
+ }
+
+ if (usb_pipe_open(uch->uch_dip, &out_data->ep_descr, &policy,
+ USB_FLAGS_SLEEP, &uch->uch_bulkout_ph) != USB_SUCCESS) {
+ usb_pipe_close(uch->uch_dip, uch->uch_bulkin_ph, USB_FLAGS_SLEEP,
+ NULL, NULL);
+
+ return (USB_FAILURE);
+ }
+
+ mutex_enter(&uch->uch_lock);
+ uch->uch_bulkin_state = UCHCOM_PIPE_IDLE;
+ uch->uch_bulkout_state = UCHCOM_PIPE_IDLE;
+ mutex_exit(&uch->uch_lock);
+
+ return (USB_SUCCESS);
+}
+
+static void
+uchcom_close_pipes(uchcom_state_t *uch)
+{
+ if (uch->uch_bulkin_ph)
+ usb_pipe_close(uch->uch_dip, uch->uch_bulkin_ph,
+ USB_FLAGS_SLEEP, 0, 0);
+ if (uch->uch_bulkout_ph)
+ usb_pipe_close(uch->uch_dip, uch->uch_bulkout_ph,
+ USB_FLAGS_SLEEP, 0, 0);
+
+ mutex_enter(&uch->uch_lock);
+ uch->uch_bulkin_state = UCHCOM_PIPE_CLOSED;
+ uch->uch_bulkout_state = UCHCOM_PIPE_CLOSED;
+ mutex_exit(&uch->uch_lock);
+}
+
+static void
+uchcom_disconnect_pipes(uchcom_state_t *uch)
+{
+ uchcom_close_pipes(uch);
+}
+
+
+static int
+uchcom_reconnect_pipes(uchcom_state_t *uch)
+{
+ return (uchcom_open_pipes(uch));
+}
+
+/*
+ * pipe callbacks bulk in common and exeception callback
+ */
+/*ARGSUSED*/
+void
+uchcom_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)req->bulk_client_private;
+ mblk_t *data;
+ int data_len;
+
+ data = req->bulk_data;
+ data_len = (data) ? MBLKL(data) : 0;
+
+ USB_DPRINTF_L4(DPRINT_IN_PIPE, uch->uch_lh, "uchcom_bulkin_cb: "
+ "cr=%d len=%d",
+ req->bulk_completion_reason,
+ data_len);
+
+ /* save data and notify GSD */
+ if ((uch->uch_port_state == UCHCOM_PORT_OPEN) && (data_len) &&
+ (req->bulk_completion_reason == USB_CR_OK)) {
+ req->bulk_data = NULL;
+ uchcom_put_tail(&uch->uch_rx_mp, data);
+ if (uch->uch_cb.cb_rx) {
+ uch->uch_cb.cb_rx(uch->uch_cb.cb_arg);
+ }
+ }
+
+ usb_free_bulk_req(req);
+
+ /* receive more */
+ mutex_enter(&uch->uch_lock);
+ uch->uch_bulkin_state = UCHCOM_PIPE_IDLE;
+ if ((uch->uch_port_state == UCHCOM_PORT_OPEN) &&
+ (uch->uch_dev_state == USB_DEV_ONLINE)) {
+ if (uchcom_rx_start(uch) != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_IN_PIPE, uch->uch_lh,
+ "uchcom_bulkin_cb: restart rx fail");
+ }
+ }
+ mutex_exit(&uch->uch_lock);
+}
+
+/*
+ * pipe callbacks bulk out common and exeception callback
+ */
+/*ARGSUSED*/
+void
+uchcom_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
+{
+ uchcom_state_t *uch = (uchcom_state_t *)req->bulk_client_private;
+ int data_len;
+ mblk_t *data = req->bulk_data;
+
+ data_len = (req->bulk_data) ? MBLKL(req->bulk_data) : 0;
+
+ USB_DPRINTF_L4(DPRINT_OUT_PIPE, uch->uch_lh,
+ "uchcom_bulkout_cb: cr=%d len=%d",
+ req->bulk_completion_reason,
+ data_len);
+
+ /* Re-send data only when port is open */
+ if ((uch->uch_port_state == UCHCOM_PORT_OPEN) &&
+ req->bulk_completion_reason && (data_len > 0)) {
+ uchcom_put_head(&uch->uch_tx_mp, data);
+ req->bulk_data = NULL;
+ }
+
+ usb_free_bulk_req(req);
+
+ /* notify GSD */
+ if (uch->uch_cb.cb_tx) {
+ uch->uch_cb.cb_tx(uch->uch_cb.cb_arg);
+ }
+
+ /* send more */
+ mutex_enter(&uch->uch_lock);
+ uch->uch_bulkout_state = UCHCOM_PIPE_IDLE;
+ if (uch->uch_tx_mp == NULL) {
+ cv_broadcast(&uch->uch_tx_cv);
+ } else {
+ uchcom_tx_start(uch, NULL);
+ }
+ mutex_exit(&uch->uch_lock);
+}
+
+/*
+ * data transfer routines start data receipt
+ */
+static int
+uchcom_rx_start(uchcom_state_t *uch)
+{
+ usb_bulk_req_t *br;
+ int rval = USB_FAILURE;
+
+ USB_DPRINTF_L4(DPRINT_OUT_PIPE, uch->uch_lh, "uchcom_rx_start");
+
+ ASSERT(mutex_owned(&uch->uch_lock));
+
+ uch->uch_bulkin_state = UCHCOM_PIPE_BUSY;
+ mutex_exit(&uch->uch_lock);
+
+ br = usb_alloc_bulk_req(uch->uch_dip, uch->uch_ibuf_sz, USB_FLAGS_SLEEP);
+ br->bulk_len = uch->uch_ibuf_sz;
+ br->bulk_timeout = UCHCOM_BULKIN_TIMEOUT;
+ br->bulk_cb = uchcom_bulkin_cb;
+ br->bulk_exc_cb = uchcom_bulkin_cb;
+ br->bulk_client_private = (usb_opaque_t)uch;
+ br->bulk_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_SHORT_XFER_OK;
+
+ rval = usb_pipe_bulk_xfer(uch->uch_bulkin_ph, br, 0);
+
+ if (rval != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_IN_PIPE, uch->uch_lh,
+ "uchcom_rx_start: xfer failed %d", rval);
+ usb_free_bulk_req(br);
+ }
+
+ mutex_enter(&uch->uch_lock);
+ if (rval != USB_SUCCESS) {
+ uch->uch_bulkin_state = UCHCOM_PIPE_IDLE;
+ }
+
+ return (rval);
+}
+
+/*
+ * data transfer routines start data transmit
+ */
+static void
+uchcom_tx_start(uchcom_state_t *uch, int *xferd)
+{
+ int len; /* bytes we can transmit */
+ mblk_t *data; /* data to be transmitted */
+ int data_len; /* bytes in 'data' */
+ mblk_t *mp; /* current msgblk */
+ int copylen; /* bytes copy from 'mp' to 'data' */
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_OUT_PIPE, uch->uch_lh, "uchcom_tx_start");
+ ASSERT(mutex_owned(&uch->uch_lock));
+ ASSERT(uch->uch_port_state != UCHCOM_PORT_CLOSED);
+
+ if (xferd) {
+ *xferd = 0;
+ }
+ if ((uch->uch_port_flags & UCHCOM_PORT_TX_STOPPED) ||
+ (uch->uch_tx_mp == NULL)) {
+ return;
+ }
+ if (uch->uch_bulkout_state != UCHCOM_PIPE_IDLE) {
+ USB_DPRINTF_L4(DPRINT_OUT_PIPE, uch->uch_lh,
+ "uchcom_tx_start: pipe busy");
+ return;
+ }
+ ASSERT(MBLKL(uch->uch_tx_mp) > 0);
+
+ /* send as much data as port can receive */
+ len = min(msgdsize(uch->uch_tx_mp), uch->uch_obuf_sz);
+
+ if (len == 0) {
+ return;
+ }
+
+ if ((data = allocb(len, BPRI_LO)) == NULL) {
+ return;
+ }
+
+ /*
+ * copy no more than 'len' bytes from mblk chain to transmit mblk 'data'
+ */
+ data_len = 0;
+
+ while ((data_len < len) && uch->uch_tx_mp) {
+ mp = uch->uch_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) {
+ uch->uch_tx_mp = unlinkb(mp);
+ freeb(mp);
+ } else {
+ ASSERT(data_len == len);
+ }
+ }
+ if (data_len <= 0) {
+ USB_DPRINTF_L3(DPRINT_OUT_PIPE, uch->uch_lh,
+ "uchcom_tx_start: copied zero bytes");
+ freeb(data);
+ return;
+ }
+
+ uch->uch_bulkout_state = UCHCOM_PIPE_BUSY;
+ mutex_exit(&uch->uch_lock);
+
+ rval = uchcom_send_data(uch, data);
+ mutex_enter(&uch->uch_lock);
+
+ if (rval != USB_SUCCESS) {
+ uch->uch_bulkout_state = UCHCOM_PIPE_IDLE;
+ uchcom_put_head(&uch->uch_tx_mp, data);
+ } else {
+ if (xferd) {
+ *xferd = data_len;
+ }
+ }
+}
+
+static int
+uchcom_send_data(uchcom_state_t *uch, mblk_t *data)
+{
+ usb_bulk_req_t *br;
+ int len = MBLKL(data);
+ int rval;
+
+ USB_DPRINTF_L4(DPRINT_OUT_PIPE, uch->uch_lh, "uchcom_send_data: %d "
+ "%x %x %x", len, data->b_rptr[0],
+ (len > 1) ? data->b_rptr[1] : 0,
+ (len > 2) ? data->b_rptr[2] : 0);
+ ASSERT(!mutex_owned(&uch->uch_lock));
+
+ br = usb_alloc_bulk_req(uch->uch_dip, 0, USB_FLAGS_SLEEP);
+ br->bulk_data = data;
+ br->bulk_len = len;
+ br->bulk_timeout = UCHCOM_BULKOUT_TIMEOUT;
+ br->bulk_cb = uchcom_bulkout_cb;
+ br->bulk_exc_cb = uchcom_bulkout_cb;
+ br->bulk_client_private = (usb_opaque_t)uch;
+ br->bulk_attributes = USB_ATTRS_AUTOCLEARING;
+
+ rval = usb_pipe_bulk_xfer(uch->uch_bulkout_ph, br, 0);
+
+ if (rval != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_OUT_PIPE, uch->uch_lh,
+ "uchcom_send_data: xfer failed %d", rval);
+
+ br->bulk_data = NULL;
+ usb_free_bulk_req(br);
+ }
+
+ return (rval);
+}
+
+/*
+ * wait until local tx buffer drains.
+ * 'timeout' is in seconds, zero means wait forever
+ */
+static int
+uchcom_wait_tx_drain(uchcom_state_t *uch, int timeout)
+{
+ clock_t until;
+ int over = 0;
+
+ until = ddi_get_lbolt() + drv_usectohz(1000 * 1000 * timeout);
+
+ while (uch->uch_tx_mp && !over) {
+ if (timeout > 0) {
+ /* whether timedout or signal pending */
+ over = (cv_timedwait_sig(&uch->uch_tx_cv,
+ &uch->uch_lock, until) <= 0);
+ } else {
+ /* whether a signal is pending */
+ over = (cv_wait_sig(&uch->uch_tx_cv,
+ &uch->uch_lock) == 0);
+ }
+ }
+
+ return ((uch->uch_tx_mp == NULL) ? USB_SUCCESS : USB_FAILURE);
+}
+
+/*
+ * Vendor routines
+ */
+static int
+uchcom_cmd_vendor_write0(uchcom_state_t *uch,
+ uint16_t reqno, uint16_t val, uint16_t idx)
+{
+ usb_ctrl_setup_t req;
+ usb_cb_flags_t cb_flags;
+ usb_cr_t cr;
+ int rval;
+
+ ASSERT(!mutex_owned(&uch->uch_lock));
+
+ bzero(&req, sizeof (req));
+ req.bmRequestType =
+ USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_HOST_TO_DEV;
+ req.bRequest = (uchar_t)reqno;
+ req.wValue = val;
+ req.wIndex = idx;
+ req.wLength = 0;
+ req.attrs = USB_ATTRS_NONE;
+
+ if ((rval = usb_pipe_ctrl_xfer_wait(uch->uch_def_ph,
+ &req, NULL, &cr, &cb_flags, 0)) != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
+ "uchcom_cmd_vendor_write0: 0x%x 0x%x 0x%x failed %d %d 0x%x",
+ reqno, val, idx, rval, cr, cb_flags);
+ }
+
+ return (rval);
+}
+
+static int
+uchcom_cmd_vendor_read0(uchcom_state_t *uch,
+ uint16_t reqno, uint16_t val, uint16_t idx, void *buf, uint16_t buflen)
+{
+ usb_ctrl_setup_t req;
+ usb_cb_flags_t cb_flags;
+ usb_cr_t cr;
+ mblk_t *mp = NULL;
+ int rval;
+
+ bzero(&req, sizeof (req));
+ req.bmRequestType =
+ USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_DEV_TO_HOST;
+ req.bRequest = (uchar_t)reqno;
+ req.wValue = val;
+ req.wIndex = idx;
+ req.wLength = buflen;
+ req.attrs = USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING;
+
+ if ((rval = usb_pipe_ctrl_xfer_wait(uch->uch_def_ph,
+ &req, &mp, &cr, &cb_flags, 0)) != USB_SUCCESS) {
+ USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh,
+ "uchcom_cmd_vendor_read0: get regs req failed :"
+ " cr:%s(%d), cb_flags:(%x)\n",
+ usb_str_cr(cr), cr, cb_flags);
+ cmn_err(CE_WARN,
+ "uchcom_cmd_vendor_read0: get regs req failed :"
+ " cr:%s(%d), cb_flags:(%x)\n",
+ usb_str_cr(cr), cr, cb_flags);
+ } else {
+ bcopy(mp->b_rptr, buf, buflen);
+ }
+
+ freemsg(mp);
+ return (rval);
+}
+
+/*
+ * misc routines
+ */
+static void
+uchcom_put_tail(mblk_t **mpp, mblk_t *bp)
+{
+ if (*mpp) {
+ linkb(*mpp, bp);
+ } else {
+ *mpp = bp;
+ }
+}
+
+
+static void
+uchcom_put_head(mblk_t **mpp, mblk_t *bp)
+{
+ if (*mpp) {
+ linkb(bp, *mpp);
+ }
+ *mpp = bp;
+}
+
diff --git a/usr/src/uts/common/io/usb/clients/usbser/uchcom/usbser_uchcom.c b/usr/src/uts/common/io/usb/clients/usbser/uchcom/usbser_uchcom.c
new file mode 100644
index 0000000000..4e482c9225
--- /dev/null
+++ b/usr/src/uts/common/io/usb/clients/usbser/uchcom/usbser_uchcom.c
@@ -0,0 +1,189 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+/*
+ * Copyright 2013 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
+ * This driver supports WinChipHead CH341/340 adapters. It is a
+ * device-specific driver (DSD) working with the USB generic serial
+ * driver (GSD) usbser.
+ *
+ * It implements the USB-to-serial device-specific driver interface (DSDI)
+ * which is exported by GSD. The DSDI is defined by ds_ops_t structure.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stream.h>
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+#include <sys/usb/clients/usbser/usbser.h>
+#include <sys/usb/clients/usbser/uchcom/uchcom_var.h>
+
+static void *usbser_uchcom_statep; /* soft state handle for usbser */
+
+extern ds_ops_t uchcom_ds_ops; /* DSD operations */
+
+static int
+usbser_uchcom_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
+ void **result)
+{
+ return (usbser_getinfo(dip, infocmd, arg, result, usbser_uchcom_statep));
+}
+
+
+static int
+usbser_uchcom_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ return (usbser_attach(dip, cmd, usbser_uchcom_statep, &uchcom_ds_ops));
+}
+
+
+static int
+usbser_uchcom_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ return (usbser_detach(dip, cmd, usbser_uchcom_statep));
+}
+
+
+static int
+usbser_uchcom_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr)
+{
+ return (usbser_open(rq, dev, flag, sflag, cr, usbser_uchcom_statep));
+}
+
+/*
+ * STREAMS structures
+ */
+struct module_info uchcom_modinfo = {
+ 0, /* module id */
+ "uchcom", /* module name */
+ USBSER_MIN_PKTSZ, /* min pkt size */
+ USBSER_MAX_PKTSZ, /* max pkt size */
+ USBSER_HIWAT, /* hi watermark */
+ USBSER_LOWAT /* low watermark */
+};
+
+static struct qinit uchcom_rinit = {
+ putq,
+ usbser_rsrv,
+ usbser_uchcom_open,
+ usbser_close,
+ NULL,
+ &uchcom_modinfo,
+};
+
+static struct qinit uchcom_winit = {
+ usbser_wput,
+ usbser_wsrv,
+ NULL,
+ NULL,
+ NULL,
+ &uchcom_modinfo,
+};
+
+static struct streamtab uchcom_str_info = {
+ &uchcom_rinit,
+ &uchcom_winit,
+};
+
+static struct cb_ops uchcom_cb_ops = {
+ nodev, /* cb_open */
+ nodev, /* cb_close */
+ nodev, /* cb_strategy */
+ nodev, /* cb_print */
+ nodev, /* cb_dump */
+ nodev, /* cb_read */
+ nodev, /* cb_write */
+ nodev, /* cb_ioctl */
+ nodev, /* cb_devmap */
+ nodev, /* cb_mmap */
+ nodev, /* cb_segmap */
+ nochpoll, /* cb_chpoll */
+ ddi_prop_op, /* cb_prop_op */
+ &uchcom_str_info, /* cb_stream */
+ (int)(D_64BIT | D_NEW | D_MP | D_HOTPLUG) /* cb_flag */
+};
+
+struct dev_ops uchcom_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* devo_refcnt */
+ usbser_uchcom_getinfo, /* devo_getinfo */
+ nulldev, /* devo_identify */
+ nulldev, /* devo_probe */
+ usbser_uchcom_attach, /* devo_attach */
+ usbser_uchcom_detach, /* devo_detach */
+ nodev, /* devo_reset */
+ &uchcom_cb_ops, /* devo_cb_ops */
+ (struct bus_ops *)NULL, /* devo_bus_ops */
+ usbser_power, /* devo_power */
+ ddi_quiesce_not_needed, /* devo_quiesce */
+};
+
+static struct modldrv modldrv = {
+ &mod_driverops,
+ "WCH CH341/340 USB UART driver",
+ &uchcom_ops,
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ &modldrv
+};
+
+int
+_init(void)
+{
+ int error;
+
+ if ((error = mod_install(&modlinkage)) != 0)
+ return (error);
+ if ((error = ddi_soft_state_init(&usbser_uchcom_statep,
+ usbser_soft_state_size(), 1)) != 0)
+ (void) mod_remove(&modlinkage);
+ return (error);
+}
+
+
+int
+_fini(void)
+{
+ int error;
+
+ if ((error = mod_remove(&modlinkage)) == 0)
+ ddi_soft_state_fini(&usbser_uchcom_statep);
+ return (error);
+}
+
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
diff --git a/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h
new file mode 100644
index 0000000000..0e7544d5f0
--- /dev/null
+++ b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h
@@ -0,0 +1,56 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _USBSER_UCHCOM_REG_H
+#define _USBSER_UCHCOM_REG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UCHCOM_VENDOR 0x1a86
+#define UCHCOM_CHIP_CH340 0x5523
+#define UCHCOM_CHIP_CH341 0x7523
+
+#define UCHCOM_INPUT_BUF_SIZE 8
+
+
+#define UCHCOM_REQ_GET_VERSION 0x5
+#define UCHCOM_REQ_RESET 0xA1
+
+/*
+ * XXX - these magic numbers come from Linux (drivers/usb/serial/ch341.c).
+ * The manufacturer was unresponsive when asked for documentation.
+ */
+#define UCHCOM_RESET_VALUE 0x501F /* line mode? */
+#define UCHCOM_RESET_INDEX 0xD90A /* baud rate? */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _USBSER_UCHCOM_REG_H */
diff --git a/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h
new file mode 100644
index 0000000000..6537c84421
--- /dev/null
+++ b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+/*
+ * Copyright 2020 Jorge Schrauwen <sjorge@blackdot.be>
+ */
+
+#ifndef _SYS_USB_UCHCOM_VAR_H
+#define _SYS_USB_UCHCOM_VAR_H
+
+
+#include <sys/types.h>
+#include <sys/dditypes.h>
+#include <sys/note.h>
+
+#include <sys/usb/clients/usbser/usbser_dsdi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * PM support
+ */
+typedef struct uchcom_pm {
+ uint8_t pm_wakeup_enabled; /* remote wakeup enabled */
+ uint8_t pm_pwr_states; /* bit mask of power states */
+ boolean_t pm_raise_power; /* driver is about to raise power */
+ uint8_t pm_cur_power; /* current power level */
+ uint_t pm_busy_cnt; /* number of set_busy requests */
+} uchcom_pm_t;
+
+/*
+ * per device state structure
+ */
+typedef struct uchcom_state {
+ kmutex_t uch_lock; /* structure lock */
+ dev_info_t *uch_dip; /* device info */
+ int uch_dev_flags; /* device flags */
+ int uch_port_state; /* port state */
+ int uch_port_flags; /* port flags */
+ ds_cb_t uch_cb; /* DSD callbacks */
+
+ /*
+ * USBA
+ */
+ usb_client_dev_data_t *uch_dev_data; /* registration data */
+ usb_event_t *uch_usb_events; /* usb events */
+ usb_pipe_handle_t uch_def_ph; /* default pipe hdl */
+ usb_pipe_handle_t uch_bulkin_ph; /* in pipe hdl */
+ int uch_bulkin_state; /* in pipe state */
+ usb_pipe_handle_t uch_bulkout_ph; /* in pipe hdl */
+ int uch_bulkout_state; /* out pipe state */
+ usb_log_handle_t uch_lh; /* USBA log handle */
+ int uch_dev_state; /* USB device state */
+ size_t uch_ibuf_sz; /* input buffer size */
+ size_t uch_obuf_sz; /* output buffer size */
+
+ uchcom_pm_t *uch_pm; /* PM support */
+
+ /*
+ * data receive and transmit
+ */
+ mblk_t *uch_rx_mp; /* rx data */
+ mblk_t *uch_tx_mp; /* tx data */
+ kcondvar_t uch_tx_cv; /* tx completion */
+
+ /*
+ * other
+ */
+ uint8_t uch_chip_version; /* chip version */
+
+} uchcom_state_t;
+
+_NOTE(MUTEX_PROTECTS_DATA(uchcom_state::uch_lock, uchcom_state))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(uchcom_state::{
+ uch_dip
+ uch_dev_data
+ uch_usb_events
+ uch_def_ph
+ uch_lh
+ uch_ibuch_sz
+ uch_obuch_sz
+ uch_pm
+ uch_port_state
+ uch_cb
+ uch_bulkin_ph
+ uch_bulkout_ph
+}))
+
+/* port state */
+enum {
+ UCHCOM_PORT_CLOSED, /* port is closed */
+ UCHCOM_PORT_OPEN, /* port is open */
+ UCHCOM_PORT_CLOSING
+};
+
+/* port flags */
+enum {
+ UCHCOM_PORT_TX_STOPPED = 0x0001 /* transmit not allowed */
+};
+
+/* pipe state */
+enum {
+ UCHCOM_PIPE_CLOSED, /* pipe is closed */
+ UCHCOM_PIPE_IDLE, /* open but no requests */
+ UCHCOM_PIPE_BUSY /* servicing request */
+};
+
+/* various numbers */
+enum {
+ UCHCOM_BULKOUT_TIMEOUT = 15, /* bulkout timeout */
+ UCHCOM_BULKIN_TIMEOUT = 15, /* bulkin timeout */
+ UCHCOM_XFER_SZ_MAX = 64, /* max xfer size */
+ UCHCOM_CLEANUP_LEVEL_MAX = 6 /* cleanup level */
+};
+
+/*
+ * debug printing masks
+ */
+#define DPRINT_ATTACH 0x00000001
+#define DPRINT_OPEN 0x00000002
+#define DPRINT_CLOSE 0x00000004
+#define DPRINT_DEF_PIPE 0x00000010
+#define DPRINT_IN_PIPE 0x00000020
+#define DPRINT_OUT_PIPE 0x00000040
+#define DPRINT_IN_DATA 0x00000400
+#define DPRINT_OUT_DATA 0x00000800
+#define DPRINT_CTLOP 0x00001000
+#define DPRINT_HOTPLUG 0x00002000
+#define DPRINT_PM 0x00004000
+#define DPRINT_MASK_ALL 0xFFFFFFFF
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_USB_UCHCOM_VAR_H */
diff --git a/usr/src/uts/intel/Makefile.intel b/usr/src/uts/intel/Makefile.intel
index 94e2cfb415..c1e30efb92 100644
--- a/usr/src/uts/intel/Makefile.intel
+++ b/usr/src/uts/intel/Makefile.intel
@@ -526,6 +526,7 @@ DRV_KMODS += usbser
DRV_KMODS += usbsacm
DRV_KMODS += usbsksp
DRV_KMODS += usbsprl
+DRV_KMODS += uchcom
DRV_KMODS += usb_ac
DRV_KMODS += usb_as
DRV_KMODS += usbskel
diff --git a/usr/src/uts/intel/uchcom/Makefile b/usr/src/uts/intel/uchcom/Makefile
new file mode 100644
index 0000000000..791d0ecfe8
--- /dev/null
+++ b/usr/src/uts/intel/uchcom/Makefile
@@ -0,0 +1,79 @@
+#
+# 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
+#
+#
+# uts/intel/uchcom/Makefile
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE = uchcom
+OBJECTS = $(UCHCOM_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(UCHCOM_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/intel/Makefile.intel
+
+LDFLAGS += -dy -Nmisc/usba -Nmisc/usbser
+
+CERRWARN += $(CNOWARN_UNINIT)
+
+# needs work
+SMOFF += deref_check
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE)
+
+.KEEP_STATE:
+
+all: $(ALL_DEPS)
+
+def: $(DEF_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+#
+# Include common targets.
+#
+include $(UTSBASE)/intel/Makefile.targ
diff --git a/usr/src/uts/sparc/uchcom/Makefile b/usr/src/uts/sparc/uchcom/Makefile
new file mode 100644
index 0000000000..3fb0735492
--- /dev/null
+++ b/usr/src/uts/sparc/uchcom/Makefile
@@ -0,0 +1,85 @@
+#
+# 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
+#
+#
+# uts/sparc/uchcom/Makefile
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This makefile drives the production of Abstract Control Model of
+# USB Communication Devices Class dirver.
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE = uchcom
+OBJECTS = $(UCHCOM_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(UCHCOM_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/sparc/Makefile.sparc
+
+#
+# lint pass one enforcement
+#
+CFLAGS += $(CCVERBOSE)
+
+LDFLAGS += -dy -Nmisc/usba -Nmisc/usbser
+
+CERRWARN += $(CNOWARN_UNINIT)
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE)
+
+.KEEP_STATE:
+
+all: $(ALL_DEPS)
+
+def: $(DEF_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+#
+# Include common targets.
+#
+include $(UTSBASE)/sparc/Makefile.targ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment