diff options
Diffstat (limited to 'usr/src/uts/common/io')
76 files changed, 34490 insertions, 394 deletions
diff --git a/usr/src/uts/common/io/1394/targets/av1394/av1394_async.c b/usr/src/uts/common/io/1394/targets/av1394/av1394_async.c index 94323582d6..c2c16e848b 100644 --- a/usr/src/uts/common/io/1394/targets/av1394/av1394_async.c +++ b/usr/src/uts/common/io/1394/targets/av1394/av1394_async.c @@ -24,7 +24,9 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * Copyright (c) 2014, Joyent, Inc. All rights reserved. + */ /* * av1394 asynchronous module @@ -359,9 +361,10 @@ av1394_async_poll(av1394_inst_t *avp, short events, int anyyet, short *reventsp, AV1394_TNF_ENTER(av1394_async_poll); if (events & POLLIN) { - if (av1394_peekq(rq)) { + if (av1394_peekq(rq)) *reventsp |= POLLIN; - } else if (!anyyet) { + + if ((!*reventsp && !anyyet) || (events & POLLET)) { mutex_enter(&ap->a_mutex); ap->a_pollevents |= POLLIN; *phpp = &ap->a_pollhead; diff --git a/usr/src/uts/common/io/aggr/aggr_port.c b/usr/src/uts/common/io/aggr/aggr_port.c index 00545d2c03..a39110255a 100644 --- a/usr/src/uts/common/io/aggr/aggr_port.c +++ b/usr/src/uts/common/io/aggr/aggr_port.c @@ -21,6 +21,7 @@ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright 2012 OmniTI Computer Consulting, Inc All rights reserved. */ /* @@ -528,8 +529,13 @@ aggr_port_promisc(aggr_port_t *port, boolean_t on) if (on) { mac_rx_clear(port->lp_mch); + /* We use the promisc callback because without hardware + * rings, we deliver through flows that will cause duplicate + * delivery of packets when we've flipped into this mode + * to compensate for the lack of hardware MAC matching + */ rc = mac_promisc_add(port->lp_mch, MAC_CLIENT_PROMISC_ALL, - aggr_recv_cb, port, &port->lp_mphp, + aggr_recv_promisc_cb, port, &port->lp_mphp, MAC_PROMISC_FLAGS_NO_TX_LOOP); if (rc != 0) { mac_rx_set(port->lp_mch, aggr_recv_cb, port); diff --git a/usr/src/uts/common/io/aggr/aggr_recv.c b/usr/src/uts/common/io/aggr/aggr_recv.c index 2bdb7872e3..0dfe234b70 100644 --- a/usr/src/uts/common/io/aggr/aggr_recv.c +++ b/usr/src/uts/common/io/aggr/aggr_recv.c @@ -21,6 +21,7 @@ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright 2012 OmniTI Computer Consulting, Inc All rights reserved. */ /* @@ -68,16 +69,27 @@ aggr_recv_lacp(aggr_port_t *port, mac_resource_handle_t mrh, mblk_t *mp) /* * Callback function invoked by MAC service module when packets are - * made available by a MAC port. + * made available by a MAC port, both in promisc_on mode and not. */ /* ARGSUSED */ -void -aggr_recv_cb(void *arg, mac_resource_handle_t mrh, mblk_t *mp, - boolean_t loopback) +static void +aggr_recv_path_cb(void *arg, mac_resource_handle_t mrh, mblk_t *mp, + boolean_t loopback, boolean_t promisc_path) { aggr_port_t *port = (aggr_port_t *)arg; aggr_grp_t *grp = port->lp_grp; + /* In the case where lp_promisc_on has been turned on to + * compensate for insufficient hardware MAC matching and + * hardware rings are not in use we will fall back to + * using flows for delivery which can result in duplicates + * pushed up the stack. Only respect the chosen path. + */ + if (port->lp_promisc_on != promisc_path) { + freemsgchain(mp); + return; + } + if (grp->lg_lacp_mode == AGGR_LACP_OFF) { aggr_mac_rx(grp->lg_mh, mrh, mp); } else { @@ -161,3 +173,19 @@ aggr_recv_cb(void *arg, mac_resource_handle_t mrh, mblk_t *mp, } } } + +/* ARGSUSED */ +void +aggr_recv_cb(void *arg, mac_resource_handle_t mrh, mblk_t *mp, + boolean_t loopback) +{ + aggr_recv_path_cb(arg, mrh, mp, loopback, B_FALSE); +} + +/* ARGSUSED */ +void +aggr_recv_promisc_cb(void *arg, mac_resource_handle_t mrh, mblk_t *mp, + boolean_t loopback) +{ + aggr_recv_path_cb(arg, mrh, mp, loopback, B_TRUE); +} diff --git a/usr/src/uts/common/io/axf/ax88172reg.h b/usr/src/uts/common/io/axf/ax88172reg.h new file mode 100644 index 0000000000..8ca6ebc187 --- /dev/null +++ b/usr/src/uts/common/io/axf/ax88172reg.h @@ -0,0 +1,163 @@ +/* + * @(#)ax88172reg.h 1.1 09/06/15 + * Macro definitions for ASIX AX88172 USB to fast ethernet controler + * based on ASIX AX88172/88772 data sheet + * This file is public domain. Coded by M.Murayama (KHF04453@nifty.com) + */ + +#ifndef __AX88172_H__ +#define __AX88172_H__ + +/* + * Vendor command definitions + */ +#define VCMD_READ_SRAM 0x02 +#define VCMD_WRITE_RXSRAM 0x03 +#define VCMD_WRITE_TXSRAM 0x04 +#define VCMD_SOFTWARE_MII_OP 0x06 +#define VCMD_READ_MII_REG 0x07 +#define VCMD_WRITE_MII_REG 0x08 +#define VCMD_READ_MII_OPMODE 0x09 +#define VCMD_HARDWARE_MII_OP 0x0a +#define VCMD_READ_SROM 0x0b +#define VCMD_WRITE_SROM 0x0c +#define VCMD_WRITE_SROM_ENABLE 0x0d +#define VCMD_WRITE_SROM_DISABLE 0x0e +#define VCMD_READ_RXCTRL 0x0f +#define VCMD_WRITE_RXCTRL 0x10 +#define VCMD_READ_IPGS 0x11 +#define VCMD_WRITE_IPG 0x12 +#define VCMD_WRITE_IPG1 0x13 +#define VCMD_WRITE_IPG2 0x14 +#define VCMD_READ_MCAST_FILTER 0x15 +#define VCMD_WRITE_MCAST_FILTER 0x16 +#define VCMD_READ_NODE_ID 0x17 +#define VCMD_READ_PHY_IDS 0x19 +#define VCMD_READ_MEDIUM_STATUS 0x1a +#define VCMD_WRITE_MEDIUM_STATUS 0x1b +#define VCMD_SET_MONITOR_MODE 0x1c +#define VCMD_GET_MONITOR_MODE 0x1d +#define VCMD_READ_GPIO 0x1e +#define VCMD_WRITE_GPIO 0x1f + +/* ax88772 only, currently not supported */ +#define VCMD_WRITE_IPGS_88772 0x12 +#define VCMD_READ_NODE_ID_88772 0x13 +#define VCMD_WRITE_NODE_ID_88772 0x14 +#define VCMD_WRITE_TEST_REG_88772 0x17 +#define VCMD_SOFTWARE_RESET_88772 0x20 +#define VCMD_READ_PHY_SELECT_88772 0x21 +#define VCMD_WRITE_PHY_SELECT_88772 0x22 + + +/* + * Register definitions + */ + +/* Rx control register */ +#define RCR_SO 0x80 /* Start Operation */ +#define RCR_AP_88772 0x20 /* accept physical address from mcast filter */ +#define RCR_AM 0x10 /* accept multicast address */ +#define RCR_AB 0x08 /* accept broadcast address */ +#define RCR_SEP 0x04 /* save error packet */ +#define RCR_AMALL 0x02 /* accept all multicast address */ +#define RCR_PRO 0x01 /* promiscious, all frames received */ + +#define RCR_MFB 0x0300 +#define RCR_MFB_SHIFT 8 +#define RCR_MFB_2K (0U << RCR_MFB_SHIFT) +#define RCR_MFB_4K (1U << RCR_MFB_SHIFT) +#define RCR_MFB_8K (2U << RCR_MFB_SHIFT) +#define RCR_MFB_16K (3U << RCR_MFB_SHIFT) + +#define RCR_BITS \ + "\020" \ + "\010SO" \ + "\006AP" \ + "\005AM" \ + "\004AB" \ + "\003SEP" \ + "\002AMALL" \ + "\001PRO" + +/* Medium status register */ +#define MSR_SM 0x1000 /* super mac support */ +#define MSR_SBP 0x0800 /* stop backpressure */ +#define MSR_PS 0x0200 /* port speed in mii mode */ +#define MSR_RE 0x0100 /* rx enable */ +#define MSR_PF 0x0080 /* check only length/type for pause frame */ +#define MSR_JFE 0x0040 /* jumbo frame enable */ +#define MSR_TFC 0x0020 /* tx flow control enable */ +#define MSR_RFC 0x0010 /* rx flow control enable (178) */ +#define MSR_FCEN 0x0010 /* flow control enable (172/772) */ +#define MSR_ENCK 0x0008 /* Enable GTX_CLK and TXC clock output (178) */ +#define MSR_TXABT 0x0004 /* Tx abort allow, always set */ +#define MSR_FDPX 0x0002 /* full duplex */ +#define MSR_GM 0x0001 /* Gigabit mode (178) */ + +#define MSR_BITS \ + "\020" \ + "\015SM" \ + "\014SBP" \ + "\012PS" \ + "\011RE" \ + "\005FCEN" \ + "\004ENCK" \ + "\003TXABT" \ + "\002FDPX" \ + "\001GM" + +/* monitor mode register */ +#define MMR_RWMP 0x04 /* remote wakeup by magic pkt */ +#define MMR_RWLU 0x02 /* remote wakeup by linkup */ +#define MMR_MOM 0x01 /* monitor mode 1:en, 0:dis */ + +#define MMR_BITS \ + "\020" \ + "\003RWMP" \ + "\002RWLU" \ + "\001MOM" + +/* GPIO register */ +#define GPIO_RSE 0x80 /* reload serial eeprom (88772)*/ +#define GPIO_DATA2 0x20 +#define GPIO_EN2 0x10 +#define GPIO_DATA1 0x08 +#define GPIO_EN1 0x04 +#define GPIO_DATA0 0x02 +#define GPIO_EN0 0x01 + +#define GPIO_BITS \ + "\020" \ + "\010RSE" \ + "\006DATA2" \ + "\005EN2" \ + "\004DATA1" \ + "\003EN1" \ + "\002DATA0" \ + "\001EN0" + +/* Software reset register */ +#define SWRST_IPPD 0x40 /* internal phy power down control */ +#define SWRST_IPRL 0x20 /* internal phy reset control */ +#define SWRST_BZ 0x10 /* force Bulk In to return zero-length pkt */ +#define SWRST_PRL 0x08 /* external phy reset pin level */ +#define SWRST_PRTE 0x04 /* external phy tri-state enable */ +#define SWRST_RT 0x02 /* clear frame length error for Bulk-Out */ +#define SWRST_RR 0x01 /* clear frame length error for Bulk-In */ + +#define SWRST_BITS \ + "\020" \ + "\007IPPD" \ + "\006IPRL" \ + "\005BZ" \ + "\004PRL" \ + "\003PRTE" \ + "\002RT" \ + "\001RR" + +/* Software PHY Select Status register */ +#define SPSS_ASEL 0x02 /* 1:auto select 0:manual select */ +#define SPSS_PSEL 0x01 /* 1:intenal phy, 0:external (when ASEL=0) */ + +#endif /* __AX88172_H__ */ diff --git a/usr/src/uts/common/io/axf/axf_usbgem.c b/usr/src/uts/common/io/axf/axf_usbgem.c new file mode 100644 index 0000000000..28963f6849 --- /dev/null +++ b/usr/src/uts/common/io/axf/axf_usbgem.c @@ -0,0 +1,1539 @@ +/* + * axf_usbgem.c : ASIX AX88172/772 USB to Fast Ethernet Driver for Solaris + * + * Copyright (c) 2004-2012 Masayuki Murayama. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#pragma ident "@(#)axf_usbgem.c 1.3 12/02/09" + +/* + * Changelog: + */ + +/* + * TODO + * handle RXMODE_ENABLE in set_rx_filter() + */ +/* ======================================================= */ + +/* + * Solaris system header files and macros + */ + +/* minimum kernel headers for drivers */ +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/debug.h> +#include <sys/kmem.h> +#include <sys/modctl.h> +#include <sys/errno.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/byteorder.h> + +/* ethernet stuff */ +#include <sys/ethernet.h> + +/* interface card depend stuff */ +#include <sys/stropts.h> +#include <sys/stream.h> +#include <sys/strlog.h> +#include <sys/usb/usba.h> +#include "usbgem.h" + +/* hardware stuff */ +#include "usbgem_mii.h" +#include "ax88172reg.h" + +char ident[] = "ax88x72 usbnic driver v" VERSION; + +/* + * Useful macros + */ +#define CHECK_AND_JUMP(err, label) if (err != USB_SUCCESS) goto label +#define LE16P(p) ((((uint8_t *)(p))[1] << 8) | ((uint8_t *)(p))[0]) + +#define AX88172(dp) \ + (((struct axf_dev *)(dp)->private)->chip->type == CHIP_TYPE_AX88172) + +#define AX88772(dp) \ + (((struct axf_dev *)(dp)->private)->chip->type == CHIP_TYPE_AX88772) + +/* + * Debugging + */ +#ifdef DEBUG_LEVEL +static int axf_debug = DEBUG_LEVEL; +#define DPRINTF(n, args) if (axf_debug > (n)) cmn_err args +#else +#define DPRINTF(n, args) +#endif + +/* + * Our configration for ax88172 + */ +/* timeouts */ +#define ONESEC (drv_usectohz(1*1000000)) + +/* + * RX/TX buffer size + */ + +/* + * Local device definitions + */ +struct chip_info { + uint16_t vid; /* usb vendor id */ + uint16_t pid; /* usb product id */ + int type; + uint8_t gpio_reset[2]; + uint8_t gpio_speed[2]; + uint8_t gpio_duplex[2]; + char *name; +#define CHIP_TYPE_AX88172 0 +#define CHIP_TYPE_AX88772 1 +#define CHIP_TYPE_AX88178 2 +}; + +#define GPIO_DEFAULT {0x00, 0x15}, {0, 0}, {0, 0} +struct chip_info chiptbl_88x7x[] = { +/* AX88172 */ +{ + /* Planex UE2-100TX, Hawking UF200, TrendNet TU2-ET100 */ + 0x07b8, 0x420a, CHIP_TYPE_AX88172, + + /* + * the default setting covers below: + * gpio bit2 has to be 0 and gpio bit0 has to be 1 + */ + {0, 0}, + {GPIO_EN1, GPIO_DATA1 | GPIO_EN1}, + {0, 0}, + "Planex UE2-100TX", /* tested */ +}, +{ + 0x2001, 0x1a00, CHIP_TYPE_AX88172, + {0x9f, 0x9e}, {0, 0}, {0, 0}, + "D-Link dube100", /* XXX */ +}, +{ + 0x077b, 0x2226, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Linksys USB200M", +}, +{ + 0x0846, 0x1040, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Netgear FA120", +}, +{ + 0x0b95, 0x1720, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Intellinet, ST Lab USB Ethernet", +}, +{ + 0x08dd, 0x90ff, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Billionton Systems, USB2AR", +}, +{ + 0x0557, 0x2009, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "ATEN UC210T", +}, +{ + 0x0411, 0x003d, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Buffalo LUA-U2-KTX", +}, +{ + 0x6189, 0x182d, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Sitecom LN-029 USB 2.0 10/100 Ethernet adapter", +}, +{ + 0x07aa, 0x0017, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "corega FEther USB2-TX", +}, +{ + 0x1189, 0x0893, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "Surecom EP-1427X-2", +}, +{ + 0x1631, 0x6200, CHIP_TYPE_AX88172, + GPIO_DEFAULT, + "goodway corp usb gwusb2e", +}, +/* AX88772 and AX88178 */ +{ + 0x13b1, 0x0018, CHIP_TYPE_AX88772, + {0, 0}, {0, 0}, {0, 0}, + "Linksys USB200M rev.2", +}, +{ + 0x1557, 0x7720, CHIP_TYPE_AX88772, + {0, 0}, {0, 0}, {0, 0}, + "0Q0 cable ethernet", +}, +{ + 0x07d1, 0x3c05, CHIP_TYPE_AX88772, + {0, 0}, {0, 0}, {0, 0}, + "DLink DUB E100 ver B1", +}, +{ + 0x2001, 0x3c05, CHIP_TYPE_AX88772, + {0, 0}, {0, 0}, {0, 0}, + "DLink DUB E100 ver B1(2)", +}, +{ + 0x05ac, 0x1402, CHIP_TYPE_AX88772, + {0, 0}, {0, 0}, {0, 0}, + "Apple Ethernet USB Adapter", +}, +{ + 0x1737, 0x0039, CHIP_TYPE_AX88178, + {0, 0}, {0, 0}, {0, 0}, + "Linksys USB1000", +}, +{ + 0x0411, 0x006e, CHIP_TYPE_AX88178, + {0, 0}, {0, 0}, {0, 0}, + "Buffalo LUA-U2-KGT/LUA-U2-GT", +}, +{ + 0x04bb, 0x0930, CHIP_TYPE_AX88178, + {0, 0}, {0, 0}, {0, 0}, + "I/O DATA ETG-US2", +}, +{ + 0x050d, 0x5055, CHIP_TYPE_AX88178, + {0, 0}, {0, 0}, {0, 0}, + "Belkin F5D5055", +}, +{ + /* generic ax88772 must be the last entry */ + /* planex UE-200TX-G */ + 0x0b95, 0x7720, CHIP_TYPE_AX88772, + {0, 0}, {0, 0}, {0, 0}, + "ASIX AX88772/AX88178", /* tested */ +}, +}; + +#define CHIPTABLESIZE (sizeof (chiptbl_88x7x) / sizeof (struct chip_info)) + +struct axf_dev { + /* + * Misc HW information + */ + struct chip_info *chip; + uint8_t ipg[3]; + uint8_t gpio; + uint16_t rcr; + uint16_t msr; + uint8_t last_link_state; + boolean_t phy_has_reset; +}; + +/* + * private functions + */ + +/* mii operations */ +static uint16_t axf_mii_read(struct usbgem_dev *, uint_t, int *errp); +static void axf_mii_write(struct usbgem_dev *, uint_t, uint16_t, int *errp); + +/* nic operations */ +static int axf_reset_chip(struct usbgem_dev *); +static int axf_init_chip(struct usbgem_dev *); +static int axf_start_chip(struct usbgem_dev *); +static int axf_stop_chip(struct usbgem_dev *); +static int axf_set_media(struct usbgem_dev *); +static int axf_set_rx_filter(struct usbgem_dev *); +static int axf_get_stats(struct usbgem_dev *); +static void axf_interrupt(struct usbgem_dev *, mblk_t *); + +/* packet operations */ +static mblk_t *axf_tx_make_packet(struct usbgem_dev *, mblk_t *); +static mblk_t *axf_rx_make_packet(struct usbgem_dev *, mblk_t *); + +/* =============================================================== */ +/* + * I/O functions + */ +/* =============================================================== */ +#define OUT(dp, req, val, ix, len, buf, errp, label) \ + if ((*(errp) = usbgem_ctrl_out((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ (req), \ + /* wValue */ (val), \ + /* wIndex */ (ix), \ + /* wLength */ (len), \ + /* value */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +#define IN(dp, req, val, ix, len, buf, errp, label) \ + if ((*(errp) = usbgem_ctrl_in((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ (req), \ + /* wValue */ (val), \ + /* wIndex */ (ix), \ + /* wLength */ (len), \ + /* valuep */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +/* =============================================================== */ +/* + * Hardware manupilation + */ +/* =============================================================== */ +static int +axf_reset_phy(struct usbgem_dev *dp) +{ + uint8_t phys[2]; + uint8_t val8; + int err; + struct axf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + if (AX88172(dp)) { + delay(drv_usectohz(5000)); + IN(dp, VCMD_READ_GPIO, 0, 0, 1, &val8, &err, usberr); + + DPRINTF(0, (CE_CONT, "!%s: %s: gpio 0x%b", + dp->name, __func__, val8, GPIO_BITS)); + + /* reset MII PHY */ + val8 = lp->chip->gpio_reset[1] + | lp->chip->gpio_speed[dp->speed] + | lp->chip->gpio_duplex[dp->full_duplex]; + + OUT(dp, VCMD_WRITE_GPIO, + val8, 0, 0, NULL, &err, usberr); + delay(drv_usectohz(5000)); + + val8 = lp->chip->gpio_reset[0] + | lp->chip->gpio_speed[dp->speed] + | lp->chip->gpio_duplex[dp->full_duplex]; + + OUT(dp, VCMD_WRITE_GPIO, + val8, 0, 0, NULL, &err, usberr); + delay(drv_usectohz(5000)); + } else { + lp->gpio = GPIO_RSE | GPIO_DATA2 | GPIO_EN2; + OUT(dp, VCMD_WRITE_GPIO, lp->gpio, 0, + 0, NULL, &err, usberr); + drv_usecwait(1000); + + OUT(dp, VCMD_WRITE_PHY_SELECT_88772, + dp->mii_phy_addr == 16 ? 1 : 0, 0, 0, NULL, &err, usberr); + + OUT(dp, VCMD_SOFTWARE_RESET_88772, + SWRST_IPPD | SWRST_PRL, 0, 0, NULL, &err, usberr); + delay(drv_usectohz(150*1000)); + OUT(dp, VCMD_SOFTWARE_RESET_88772, + 0, 0, 0, NULL, &err, usberr); + + OUT(dp, VCMD_SOFTWARE_RESET_88772, + dp->mii_phy_addr == 16 ? SWRST_IPRL : SWRST_PRTE, + 0, 0, NULL, &err, usberr); + delay(drv_usectohz(150*1000)); + } + + + return (USB_SUCCESS); + +usberr: + return (USB_FAILURE); +} + +static int +axf_reset_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + + if (AX88172(dp)) { + /* there are no ways to reset nic */ + return (USB_SUCCESS); + } +#ifdef NEVER + OUT(dp, VCMD_SOFTWARE_RESET_88772, + SWRST_RR | SWRST_RT, 0, 0, NULL, &err, usberr); + OUT(dp, VCMD_SOFTWARE_RESET_88772, + 0, 0, 0, NULL, &err, usberr); +usberr: +#endif + return (err); +} + +/* + * Setup ax88172 + */ +static int +axf_init_chip(struct usbgem_dev *dp) +{ + int i; + uint32_t val; + int err = USB_SUCCESS; + uint16_t reg; + uint8_t buf[2]; + uint16_t tmp16; + struct axf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* rx conrol register: read default value */ + if (!AX88172(dp)) { + /* clear rx control */ + OUT(dp, VCMD_WRITE_RXCTRL, 0, 0, 0, NULL, &err, usberr); + } + + IN(dp, VCMD_READ_RXCTRL, 0, 0, 2, buf, &err, usberr); + lp->rcr = LE16P(buf); + DPRINTF(0, (CE_CONT, "!%s: %s: rcr(default):%b", + dp->name, __func__, lp->rcr, RCR_BITS)); + + lp->rcr &= ~RCR_SO; + + /* Media status register */ + if (AX88172(dp)) { +#ifdef notdef + lp->msr = MSR_TXABT; +#else + lp->msr = 0; +#endif + } else { + lp->msr = MSR_RE | MSR_TXABT; + } + DPRINTF(0, (CE_CONT, "!%s: %s: msr:%b", + dp->name, __func__, lp->msr, MSR_BITS)); + err = axf_set_media(dp); + CHECK_AND_JUMP(err, usberr); + + /* write IPG0-2 registers */ + if (AX88172(dp)) { + OUT(dp, VCMD_WRITE_IPG, lp->ipg[0], 0, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_IPG1, lp->ipg[1], 0, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_IPG2, lp->ipg[2], 0, 0, NULL, &err, usberr); + } else { + /* EMPTY */ + } +#ifdef ENABLE_RX_IN_INIT_CHIP + /* enable Rx */ + lp->rcr |= RCR_SO; + OUT(dp, VCMD_WRITE_RXCTRL, lp->rcr, 0, 0, NULL, &err, usberr); +#endif +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end (%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +static int +axf_start_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + struct axf_dev *lp = dp->private; +#ifndef ENABLE_RX_IN_INIT_CHIP + /* enable Rx */ + lp->rcr |= RCR_SO; + OUT(dp, VCMD_WRITE_RXCTRL, lp->rcr, 0, 0, NULL, &err, usberr); + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end (%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); +#endif + return (err); +} + +static int +axf_stop_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + struct axf_dev *lp = dp->private; + + /* Disable Rx */ + lp->rcr &= ~RCR_SO; + OUT(dp, VCMD_WRITE_RXCTRL, lp->rcr, 0, 0, NULL, &err, usberr); + + /* + * Restore factory mac address + * if we have changed current mac address + */ + if (!AX88172(dp) && + bcmp(dp->dev_addr.ether_addr_octet, + dp->cur_addr.ether_addr_octet, + ETHERADDRL) != 0) { + OUT(dp, VCMD_WRITE_NODE_ID_88772, 0, 0, + ETHERADDRL, dp->cur_addr.ether_addr_octet, &err, usberr); + } +usberr: + return (axf_reset_chip(dp)); +} + +static int +axf_get_stats(struct usbgem_dev *dp) +{ + /* EMPTY */ + return (USB_SUCCESS); +} + +static uint_t +axf_mcast_hash(struct usbgem_dev *dp, const uint8_t *addr) +{ + return (usbgem_ether_crc_be(addr) >> (32 - 6)); +} + +static int +axf_set_rx_filter(struct usbgem_dev *dp) +{ + int i; + uint8_t mode; + uint8_t mhash[8]; + uint8_t buf[2]; + uint_t h; + int err = USB_SUCCESS; + struct axf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called, rxmode:%x", + dp->name, __func__, dp->rxmode)); + + if (lp->rcr & RCR_SO) { + /* set promiscuous mode before changing it. */ + OUT(dp, VCMD_WRITE_RXCTRL, + lp->rcr | RCR_PRO, 0, 0, NULL, &err, usberr); + } + + lp->rcr &= ~(RCR_AP_88772 | RCR_AM | RCR_SEP | RCR_AMALL | RCR_PRO); + mode = RCR_AB; /* accept broadcast packets */ + + bzero(mhash, sizeof (mhash)); + + if (dp->rxmode & RXMODE_PROMISC) { + /* promiscious mode implies all multicast and all physical */ + mode |= RCR_PRO; + } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 32) { + /* accept all multicast packets */ + mode |= RCR_AMALL; + } else if (dp->mc_count > 0) { + /* + * make hash table to select interresting + * multicast address only. + */ + mode |= RCR_AM; + for (i = 0; i < dp->mc_count; i++) { + h = dp->mc_list[i].hash; + mhash[h / 8] |= 1 << (h % 8); + } + } + if (AX88172(dp)) { + if (bcmp(dp->dev_addr.ether_addr_octet, + dp->cur_addr.ether_addr_octet, ETHERADDRL) != 0) { + /* + * we use promiscious mode instead of changing the + * mac address in ax88172 + */ + mode |= RCR_PRO; + } + } else { + OUT(dp, VCMD_WRITE_NODE_ID_88772, 0, 0, + ETHERADDRL, dp->cur_addr.ether_addr_octet, &err, usberr); + } + lp->rcr |= mode; + + /* set multicast hash table */ + if (mode & RCR_AM) { + /* need to set up multicast hash table */ + OUT(dp, VCMD_WRITE_MCAST_FILTER, 0, 0, + sizeof (mhash), mhash, &err, usberr); + } + + /* update rcr */ + OUT(dp, VCMD_WRITE_RXCTRL, lp->rcr, 0, + 0, NULL, &err, usberr); + +#if DEBUG_LEVEL > 1 + /* verify rxctrl reg */ + IN(dp, VCMD_READ_RXCTRL, 0, 0, 2, buf, &err, usberr); + cmn_err(CE_CONT, "!%s: %s: rcr:%b returned", + dp->name, __func__, LE16P(buf), RCR_BITS); +#endif +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end (%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +static int +axf_set_media(struct usbgem_dev *dp) +{ + uint8_t val8; + uint8_t gpio; + uint8_t gpio_old; + int err = USB_SUCCESS; + uint16_t msr; + struct axf_dev *lp = dp->private; + + IN(dp, VCMD_READ_GPIO, 0, 0, 1, &gpio, &err, usberr); + + DPRINTF(0, (CE_CONT, "!%s: %s: called, gpio:%b", + dp->name, __func__, gpio, GPIO_BITS)); + + msr = lp->msr; + gpio_old = gpio; + gpio = lp->chip->gpio_reset[0]; + + /* setup speed */ + if (AX88172(dp)) { + /* EMPTY */ + } else { + msr &= ~(MSR_PS | MSR_GM | MSR_ENCK); + + switch (dp->speed) { + case USBGEM_SPD_1000: + msr |= MSR_GM | MSR_ENCK; + break; + + case USBGEM_SPD_100: + msr |= MSR_PS; + break; + + case USBGEM_SPD_10: + break; + } + } + gpio |= lp->chip->gpio_speed[dp->speed == USBGEM_SPD_100 ? 1 : 0]; + + /* select duplex */ + msr &= ~MSR_FDPX; + if (dp->full_duplex) { + msr |= MSR_FDPX; + + /* select flow control */ + if (AX88172(dp)) { + msr &= ~MSR_FCEN; + switch (dp->flow_control) { + case FLOW_CONTROL_TX_PAUSE: + case FLOW_CONTROL_SYMMETRIC: + case FLOW_CONTROL_RX_PAUSE: + msr |= MSR_FCEN; + break; + } + } else { + msr &= ~(MSR_RFC | MSR_TFC); + switch (dp->flow_control) { + case FLOW_CONTROL_TX_PAUSE: + msr |= MSR_TFC; + break; + + case FLOW_CONTROL_SYMMETRIC: + msr |= MSR_TFC | MSR_RFC; + break; + + case FLOW_CONTROL_RX_PAUSE: + msr |= MSR_RFC; + break; + } + } + } + gpio |= lp->chip->gpio_duplex[dp->full_duplex ? 1 : 0]; + + /* update medium status register */ + lp->msr = msr; + OUT(dp, VCMD_WRITE_MEDIUM_STATUS, lp->msr, 0, + 0, NULL, &err, usberr); + + if (gpio != gpio_old) { + /* LED control required for some products */ + OUT(dp, VCMD_WRITE_GPIO, + gpio, 0, 0, NULL, &err, usberr); + } + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end (%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +#define FILL_PKT_HEADER(bp, len) { \ + (bp)[0] = (uint8_t)(len); \ + (bp)[1] = (uint8_t)((len) >> 8); \ + (bp)[2] = (uint8_t)(~(len)); \ + (bp)[3] = (uint8_t)((~(len)) >> 8); \ +} + +#define PKT_HEADER_SIZE 4 + +/* + * send/receive packet check + */ +static mblk_t * +axf_tx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + int n; + size_t len; + size_t pkt_size; + mblk_t *new; + mblk_t *tp; + uint8_t *bp; + uint8_t *last_pos; + uint_t align_mask; + size_t header_size; + int pad_size; + + len = msgdsize(mp); + + if (AX88172(dp)) { +#ifdef notdef + align_mask = 63; +#else + align_mask = 511; +#endif + header_size = 0; + + if (len >= ETHERMIN && mp->b_cont == NULL && + (len & align_mask) != 0) { + /* use the mp "as is" */ + return (mp); + } + } else { + align_mask = 511; + header_size = PKT_HEADER_SIZE; + } + + /* + * re-allocate the mp + */ + /* minimum ethernet packet size of ETHERMIN */ + pkt_size = max(len, ETHERMIN); + + if (((pkt_size + header_size) & align_mask) == 0) { + /* padding is required in usb communication */ + pad_size = PKT_HEADER_SIZE; + } else { + pad_size = 0; + } + + if ((new = allocb(header_size + pkt_size + pad_size, 0)) == NULL) { + return (NULL); + } + + bp = new->b_rptr; + if (header_size) { + uint16_t tmp; + + /* add a header */ + tmp = (uint16_t)pkt_size; + FILL_PKT_HEADER(bp, tmp); + bp += header_size; + } + + /* copy contents of the buffer */ + for (tp = mp; tp; tp = tp->b_cont) { + n = tp->b_wptr - tp->b_rptr; + bcopy(tp->b_rptr, bp, n); + bp += n; + } + + /* add pads for ethernet packets */ + last_pos = new->b_rptr + header_size + pkt_size; + while (bp < last_pos) { + *bp++ = 0; + } + + /* add a zero-length pad segment for usb communications */ + if (pad_size) { + /* add a dummy header for zero-length packet */ + FILL_PKT_HEADER(bp, 0); + bp += pad_size; + } + + /* close the payload of the packet */ + new->b_wptr = bp; + + return (new); +} + +static void +axf_dump_packet(struct usbgem_dev *dp, uint8_t *bp, int n) +{ + int i; + + for (i = 0; i < n; i += 8, bp += 8) { + cmn_err(CE_CONT, "%02x %02x %02x %02x %02x %02x %02x %02x", + bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7]); + } +} + +static mblk_t * +axf_rx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + mblk_t *tp; + int rest; + + if (AX88172(dp)) { + return (mp); + } + + tp = mp; + rest = tp->b_wptr - tp->b_rptr; + + if (rest <= PKT_HEADER_SIZE) { + /* + * the usb bulk-in frame doesn't include any valid + * ethernet packets. + */ + return (NULL); + } + + for (; ; ) { + uint16_t len; + uint16_t cksum; + + /* analyse the header of the received usb frame */ + len = LE16P(tp->b_rptr + 0); + cksum = LE16P(tp->b_rptr + 2); + + /* test if the header is valid */ + if (len + cksum != 0xffff) { + /* discard whole the packet */ + cmn_err(CE_WARN, + "!%s: %s: corrupted header:%04x %04x", + dp->name, __func__, len, cksum); + return (NULL); + } +#if DEBUG_LEVEL > 0 + if (len < ETHERMIN || len > ETHERMAX) { + cmn_err(CE_NOTE, + "!%s: %s: incorrect pktsize:%d", + dp->name, __func__, len); + } +#endif + /* extract a ethernet packet from the bulk-in frame */ + tp->b_rptr += PKT_HEADER_SIZE; + tp->b_wptr = tp->b_rptr + len; + + if (len & 1) { + /* + * skip a tailing pad byte if the packet + * length is odd + */ + len++; + } + rest -= len + PKT_HEADER_SIZE; + + if (rest <= PKT_HEADER_SIZE) { + /* no more vaild ethernet packets */ + break; + } + +#if DEBUG_LEVEL > 10 + axf_dump_packet(dp, tp->b_wptr, 18); +#endif + /* allocate a mblk_t header for the next ethernet packet */ + tp->b_next = dupb(mp); + tp->b_next->b_rptr = tp->b_rptr + len; + tp = tp->b_next; + } + + return (mp); +} + +/* + * MII Interfaces + */ +static uint16_t +axf_mii_read(struct usbgem_dev *dp, uint_t index, int *errp) +{ + uint8_t buf[2]; + uint16_t val; + + DPRINTF(4, (CE_CONT, "!%s: %s: called, ix:%d", + dp->name, __func__, index)); + + /* switch to software MII operation mode */ + OUT(dp, VCMD_SOFTWARE_MII_OP, 0, 0, 0, NULL, errp, usberr); + + /* Read MII register */ + IN(dp, VCMD_READ_MII_REG, dp->mii_phy_addr, index, + 2, buf, errp, usberr); + + /* switch to hardware MII operation mode */ + OUT(dp, VCMD_HARDWARE_MII_OP, 0, 0, 0, NULL, errp, usberr); + + return (LE16P(buf)); + +usberr: + cmn_err(CE_CONT, + "!%s: %s: usberr(%d) detected", dp->name, __func__, *errp); + return (0); +} + +static void +axf_mii_write(struct usbgem_dev *dp, uint_t index, uint16_t val, int *errp) +{ + uint8_t buf[2]; + + DPRINTF(4, (CE_CONT, "!%s: %s called, reg:%x val:%x", + dp->name, __func__, index, val)); + + /* switch software MII operation mode */ + OUT(dp, VCMD_SOFTWARE_MII_OP, 0, 0, 0, NULL, errp, usberr); + + /* Write to the specified MII register */ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + OUT(dp, VCMD_WRITE_MII_REG, dp->mii_phy_addr, index, + 2, buf, errp, usberr); + + /* switch to hardware MII operation mode */ + OUT(dp, VCMD_HARDWARE_MII_OP, 0, 0, 0, NULL, errp, usberr); + +usberr: + ; +} + +static void +axf_interrupt(struct usbgem_dev *dp, mblk_t *mp) +{ + uint8_t *bp; + struct axf_dev *lp = dp->private; + + bp = mp->b_rptr; + + DPRINTF(2, (CE_CONT, + "!%s: %s: size:%d, %02x %02x %02x %02x %02x %02x %02x %02x", + dp->name, __func__, mp->b_wptr - mp->b_rptr, + bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7])); + + if (lp->last_link_state ^ bp[2]) { + usbgem_mii_update_link(dp); + } + + lp->last_link_state = bp[2]; +} + +/* ======================================================== */ +/* + * OS depend (device driver DKI) routine + */ +/* ======================================================== */ +#ifdef DEBUG_LEVEL +static void +axf_eeprom_dump(struct usbgem_dev *dp, int size) +{ + int i; + int err; + uint8_t w0[2], w1[2], w2[2], w3[2]; + + cmn_err(CE_CONT, "!%s: eeprom dump:", dp->name); + + err = USB_SUCCESS; + + for (i = 0; i < size; i += 4) { + IN(dp, VCMD_READ_SROM, i + 0, 0, 2, w0, &err, usberr); + IN(dp, VCMD_READ_SROM, i + 1, 0, 2, w1, &err, usberr); + IN(dp, VCMD_READ_SROM, i + 2, 0, 2, w2, &err, usberr); + IN(dp, VCMD_READ_SROM, i + 3, 0, 2, w3, &err, usberr); + cmn_err(CE_CONT, "!0x%02x: 0x%04x 0x%04x 0x%04x 0x%04x", + i, + (w0[1] << 8) | w0[0], + (w1[1] << 8) | w1[0], + (w2[1] << 8) | w2[0], + (w3[1] << 8) | w3[0]); + } +usberr: + ; +} +#endif + +static int +axf_attach_chip(struct usbgem_dev *dp) +{ + uint8_t phys[2]; + int err; + uint_t vcmd; + int ret; +#ifdef CONFIG_FULLSIZE_VLAN + uint8_t maxpktsize[2]; + uint16_t vlan_pktsize; +#endif +#ifdef DEBUG_LEVEL + uint8_t val8; +#endif + struct axf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s enter", dp->name, __func__)); + + ret = USB_SUCCESS; + /* + * mac address in EEPROM has loaded to ID registers. + */ + vcmd = AX88172(dp) ? VCMD_READ_NODE_ID : VCMD_READ_NODE_ID_88772; + IN(dp, vcmd, 0, 0, + ETHERADDRL, dp->dev_addr.ether_addr_octet, &err, usberr); + + /* + * setup IPG values + */ + lp->ipg[0] = 0x15; + lp->ipg[1] = 0x0c; + lp->ipg[2] = 0x12; + + /* + * We cannot scan phy because the nic returns undefined + * value, i.e. remained garbage, when MII phy is not at the + * specified index. + */ +#ifdef DEBUG_LEVELx + if (lp->chip->vid == 0x07b8 && lp->chip->pid == 0x420a) { + /* + * restore the original phy address of brain + * damaged Planex UE2-100TX + */ + OUT(dp, VCMD_WRITE_SROM_ENABLE, 0, 0, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_SROM, 0x11, 0xe004, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_SROM_DISABLE, 0, 0, 0, NULL, &err, usberr); + } +#endif + if (AX88172(dp)) { + IN(dp, VCMD_READ_PHY_IDS, 0, 0, 2, &phys, &err, usberr); + dp->mii_phy_addr = phys[1]; + DPRINTF(0, (CE_CONT, "!%s: %s: phys_addr:%d %d", + dp->name, __func__, phys[0], phys[1])); + } else { + /* use built-in phy */ + dp->mii_phy_addr = 0x10; + } + + dp->misc_flag |= USBGEM_VLAN; +#ifdef CONFIG_FULLSIZE_VLAN + if (AX88172(dp) || AX88772(dp)) { + /* check max packet size in srom */ + IN(dp, VCMD_READ_SROM, 0x10, 0, 2, maxpktsize, &err, usberr); + vlan_pktsize = ETHERMAX + ETHERFCSL + 4 /* VTAG_SIZE */; + + if (LE16P(maxpktsize) < vlan_pktsize) { + cmn_err(CE_CONT, + "!%s: %s: max packet size in srom is too small, " + "changing %d -> %d, do power cycle for the device", + dp->name, __func__, + LE16P(maxpktsize), vlan_pktsize); + OUT(dp, VCMD_WRITE_SROM_ENABLE, + 0, 0, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_SROM, 0x10, + vlan_pktsize, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_SROM_DISABLE, + 0, 0, 0, NULL, &err, usberr); + + /* need to power off the device */ + ret = USB_FAILURE; + } + } +#endif +#ifdef DEBUG_LEVEL + IN(dp, VCMD_READ_GPIO, 0, 0, 1, &val8, &err, usberr); + cmn_err(CE_CONT, + "!%s: %s: ipg 0x%02x 0x%02x 0x%02x, gpio 0x%b", + dp->name, __func__, lp->ipg[0], lp->ipg[1], lp->ipg[2], + val8, GPIO_BITS); +#endif + /* fix rx buffer size */ + if (!AX88172(dp)) { + dp->rx_buf_len = 2048; + } + +#if DEBUG_LEVEL > 0 + axf_eeprom_dump(dp, 0x20); +#endif + return (ret); + +usberr: + cmn_err(CE_WARN, "%s: %s: usb error detected (%d)", + dp->name, __func__, err); + return (USB_FAILURE); +} + +static boolean_t +axf_scan_phy(struct usbgem_dev *dp) +{ + int i; + int err; + uint16_t val; + int phy_addr_saved; + struct axf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + phy_addr_saved = dp->mii_phy_addr; + + /* special probe routine for unreliable MII addr */ +#define PROBE_PAT \ + (MII_ABILITY_100BASE_TX_FD | \ + MII_ABILITY_100BASE_TX | \ + MII_ABILITY_10BASE_T_FD | \ + MII_ABILITY_10BASE_T) + + for (i = 0; i < 32; i++) { + dp->mii_phy_addr = i; + axf_mii_write(dp, MII_AN_ADVERT, 0, &err); + if (err != USBGEM_SUCCESS) { + break; + } + val = axf_mii_read(dp, MII_AN_ADVERT, &err); + if (err != USBGEM_SUCCESS) { + break; + } + if (val != 0) { + DPRINTF(0, (CE_CONT, "!%s: %s: index:%d, val %b != 0", + dp->name, __func__, i, val, MII_ABILITY_BITS)); + continue; + } + + axf_mii_write(dp, MII_AN_ADVERT, PROBE_PAT, &err); + if (err != USBGEM_SUCCESS) { + break; + } + val = axf_mii_read(dp, MII_AN_ADVERT, &err); + if (err != USBGEM_SUCCESS) { + break; + } + if ((val & MII_ABILITY_TECH) != PROBE_PAT) { + DPRINTF(0, (CE_CONT, "!%s: %s: " + "index:%d, pat:%x != val:%b", + dp->name, __func__, i, + PROBE_PAT, val, MII_ABILITY_BITS)); + continue; + } + + /* found */ + dp->mii_phy_addr = phy_addr_saved; + return (i); + } +#undef PROBE_PAT + if (i == 32) { + cmn_err(CE_CONT, "!%s: %s: no mii phy found", + dp->name, __func__); + } else { + cmn_err(CE_CONT, "!%s: %s: i/o error while scanning phy", + dp->name, __func__); + } + dp->mii_phy_addr = phy_addr_saved; + return (-1); +} + +static int +axf_mii_probe(struct usbgem_dev *dp) +{ + int my_guess; + int err; + uint8_t old_11th[2]; + uint8_t new_11th[2]; + struct axf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + (void) axf_reset_phy(dp); + lp->phy_has_reset = B_TRUE; + + if (AX88172(dp)) { + my_guess = axf_scan_phy(dp); + if (my_guess >= 0 && my_guess < 32 && + my_guess != dp->mii_phy_addr) { + /* + * phy addr in srom is wrong, need to fix it + */ + IN(dp, VCMD_READ_SROM, + 0x11, 0, 2, old_11th, &err, usberr); + + new_11th[0] = my_guess; + new_11th[1] = old_11th[1]; + + OUT(dp, VCMD_WRITE_SROM_ENABLE, + 0, 0, 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_SROM, + 0x11, LE16P(new_11th), 0, NULL, &err, usberr); + OUT(dp, VCMD_WRITE_SROM_DISABLE, + 0, 0, 0, NULL, &err, usberr); +#if 1 + /* XXX - read back, but it doesn't work, why? */ + delay(drv_usectohz(1000*1000)); + IN(dp, VCMD_READ_SROM, + 0x11, 0, 2, new_11th, &err, usberr); +#endif + cmn_err(CE_NOTE, "!%s: %s: phy addr in srom fixed: " + "%04x -> %04x", + dp->name, __func__, + LE16P(old_11th), LE16P(new_11th)); + return (USBGEM_FAILURE); +usberr: + cmn_err(CE_NOTE, + "!%s: %s: failed to patch phy addr, " + "current: %04x", + dp->name, __func__, LE16P(old_11th)); + return (USBGEM_FAILURE); + } + } + return (usbgem_mii_probe_default(dp)); +} + +static int +axf_mii_init(struct usbgem_dev *dp) +{ + struct axf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + if (!lp->phy_has_reset) { + (void) axf_reset_phy(dp); + } + + /* prepare to reset phy on the next reconnect or resume */ + lp->phy_has_reset = B_FALSE; + + return (USB_SUCCESS); +} + +static int +axfattach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int i; + ddi_iblock_cookie_t c; + int ret; + int revid; + int unit; + int vid; + int pid; + struct chip_info *p; + int len; + const char *drv_name; + struct usbgem_dev *dp; + void *base; + struct usbgem_conf *ugcp; + struct axf_dev *lp; + + unit = ddi_get_instance(dip); + drv_name = ddi_driver_name(dip); + + DPRINTF(3, (CE_CONT, "!%s%d: %s: called, cmd:%d", + drv_name, unit, __func__, cmd)); + + if (cmd == DDI_ATTACH) { + /* + * Check if the chip is supported. + */ + vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "usb-vendor-id", -1); + pid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "usb-product-id", -1); + revid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "usb-revision-id", -1); + + for (i = 0, p = chiptbl_88x7x; i < CHIPTABLESIZE; i++, p++) { + if (p->vid == vid && p->pid == pid) { + /* found */ + cmn_err(CE_CONT, "!%s%d: %s " + "(vid: 0x%04x, did: 0x%04x, revid: 0x%02x)", + drv_name, unit, p->name, vid, pid, revid); + goto chip_found; + } + } + + /* Not found */ + cmn_err(CE_WARN, "!%s: %s: wrong usb venid/prodid (0x%x, 0x%x)", + drv_name, __func__, vid, pid); + + /* assume 88772 */ + p = &chiptbl_88x7x[CHIPTABLESIZE - 1]; +chip_found: + /* + * construct usbgem configration + */ + ugcp = kmem_zalloc(sizeof (*ugcp), KM_SLEEP); + + /* name */ + /* + * softmac requires that ppa is the instance number + * of the device, otherwise it hangs in seaching the device. + */ + sprintf(ugcp->usbgc_name, "%s%d", drv_name, unit); + ugcp->usbgc_ppa = unit; + + ugcp->usbgc_ifnum = 0; + ugcp->usbgc_alt = 0; + + ugcp->usbgc_tx_list_max = 64; + + ugcp->usbgc_rx_header_len = 0; + ugcp->usbgc_rx_list_max = 64; + + /* time out parameters */ + ugcp->usbgc_tx_timeout = USBGEM_TX_TIMEOUT; + ugcp->usbgc_tx_timeout_interval = USBGEM_TX_TIMEOUT_INTERVAL; + + /* flow control */ + /* + * XXX - flow control caused link down frequently under + * heavy traffic + */ + ugcp->usbgc_flow_control = FLOW_CONTROL_RX_PAUSE; + + /* MII timeout parameters */ + ugcp->usbgc_mii_link_watch_interval = ONESEC; + ugcp->usbgc_mii_an_watch_interval = ONESEC/5; + ugcp->usbgc_mii_reset_timeout = MII_RESET_TIMEOUT; /* 1 sec */ + ugcp->usbgc_mii_an_timeout = MII_AN_TIMEOUT; /* 5 sec */ + ugcp->usbgc_mii_an_wait = 0; + ugcp->usbgc_mii_linkdown_timeout = MII_LINKDOWN_TIMEOUT; + + ugcp->usbgc_mii_an_delay = ONESEC/10; + ugcp->usbgc_mii_linkdown_action = MII_ACTION_RSA; + ugcp->usbgc_mii_linkdown_timeout_action = MII_ACTION_RESET; + ugcp->usbgc_mii_dont_reset = B_FALSE; + ugcp->usbgc_mii_hw_link_detection = B_TRUE; + ugcp->usbgc_mii_stop_mac_on_linkdown = B_FALSE; + + /* I/O methods */ + + /* mac operation */ + ugcp->usbgc_attach_chip = &axf_attach_chip; + ugcp->usbgc_reset_chip = &axf_reset_chip; + ugcp->usbgc_init_chip = &axf_init_chip; + ugcp->usbgc_start_chip = &axf_start_chip; + ugcp->usbgc_stop_chip = &axf_stop_chip; + ugcp->usbgc_multicast_hash = &axf_mcast_hash; + + ugcp->usbgc_set_rx_filter = &axf_set_rx_filter; + ugcp->usbgc_set_media = &axf_set_media; + ugcp->usbgc_get_stats = &axf_get_stats; + ugcp->usbgc_interrupt = &axf_interrupt; + + /* packet operation */ + ugcp->usbgc_tx_make_packet = &axf_tx_make_packet; + ugcp->usbgc_rx_make_packet = &axf_rx_make_packet; + + /* mii operations */ + ugcp->usbgc_mii_probe = &axf_mii_probe; + ugcp->usbgc_mii_init = &axf_mii_init; + ugcp->usbgc_mii_config = &usbgem_mii_config_default; + ugcp->usbgc_mii_read = &axf_mii_read; + ugcp->usbgc_mii_write = &axf_mii_write; + + /* mtu */ + ugcp->usbgc_min_mtu = ETHERMTU; + ugcp->usbgc_max_mtu = ETHERMTU; + ugcp->usbgc_default_mtu = ETHERMTU; + + lp = kmem_zalloc(sizeof (struct axf_dev), KM_SLEEP); + lp->chip = p; + lp->last_link_state = 0; + lp->phy_has_reset = B_FALSE; + + dp = usbgem_do_attach(dip, ugcp, lp, sizeof (struct axf_dev)); + + kmem_free(ugcp, sizeof (*ugcp)); + + if (dp != NULL) { + return (DDI_SUCCESS); + } + +err_free_mem: + kmem_free(lp, sizeof (struct axf_dev)); +err_close_pipe: +err: + return (DDI_FAILURE); + } + + if (cmd == DDI_RESUME) { + return (usbgem_resume(dip)); + } + + return (DDI_FAILURE); +} + +static int +axfdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int ret; + + if (cmd == DDI_DETACH) { + ret = usbgem_do_detach(dip); + if (ret != DDI_SUCCESS) { + return (DDI_FAILURE); + } + return (DDI_SUCCESS); + } + if (cmd == DDI_SUSPEND) { + return (usbgem_suspend(dip)); + } + return (DDI_FAILURE); +} + +/* ======================================================== */ +/* + * OS depend (loadable streams driver) routine + */ +/* ======================================================== */ +#ifdef USBGEM_CONFIG_GLDv3 +USBGEM_STREAM_OPS(axf_ops, axfattach, axfdetach); +#else +static struct module_info axfminfo = { + 0, /* mi_idnum */ + "axf", /* mi_idname */ + 0, /* mi_minpsz */ + ETHERMTU, /* mi_maxpsz */ + ETHERMTU*128, /* mi_hiwat */ + 1, /* mi_lowat */ +}; + +static struct qinit axfrinit = { + (int (*)()) NULL, /* qi_putp */ + usbgem_rsrv, /* qi_srvp */ + usbgem_open, /* qi_qopen */ + usbgem_close, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &axfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct qinit axfwinit = { + usbgem_wput, /* qi_putp */ + usbgem_wsrv, /* qi_srvp */ + (int (*)()) NULL, /* qi_qopen */ + (int (*)()) NULL, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &axfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct streamtab axf_info = { + &axfrinit, /* st_rdinit */ + &axfwinit, /* st_wrinit */ + NULL, /* st_muxrinit */ + NULL /* st_muxwrinit */ +}; + +static struct cb_ops cb_axf_ops = { + nulldev, /* cb_open */ + nulldev, /* 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 */ + &axf_info, /* cb_stream */ + D_NEW|D_MP /* cb_flag */ +}; + +static struct dev_ops axf_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + usbgem_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + axfattach, /* devo_attach */ + axfdetach, /* devo_detach */ + nodev, /* devo_reset */ + &cb_axf_ops, /* devo_cb_ops */ + NULL, /* devo_bus_ops */ + usbgem_power, /* devo_power */ +#if DEVO_REV >= 4 + usbgem_quiesce, /* devo_quiesce */ +#endif +}; +#endif + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + ident, + &axf_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, &modldrv, NULL +}; + +/* ======================================================== */ +/* + * _init : done + */ +/* ======================================================== */ +int +_init(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!axf: _init: called")); + + status = usbgem_mod_init(&axf_ops, "axf"); + if (status != DDI_SUCCESS) { + return (status); + } + status = mod_install(&modlinkage); + if (status != DDI_SUCCESS) { + usbgem_mod_fini(&axf_ops); + } + return (status); +} + +/* + * _fini : done + */ +int +_fini(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!axf: _fini: called")); + status = mod_remove(&modlinkage); + if (status == DDI_SUCCESS) { + usbgem_mod_fini(&axf_ops); + } + return (status); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} diff --git a/usr/src/uts/common/io/bge/bge_chip2.c b/usr/src/uts/common/io/bge/bge_chip2.c index f687ce4892..a459f867f3 100644 --- a/usr/src/uts/common/io/bge/bge_chip2.c +++ b/usr/src/uts/common/io/bge/bge_chip2.c @@ -24,7 +24,7 @@ */ /* - * Copyright 2011 Nexenta Systems, Inc. All rights reserved. + * Copyright 2011, 2012 Nexenta Systems, Inc. All rights reserved. */ #include "bge_impl.h" @@ -363,7 +363,34 @@ bge_chip_cfg_init(bge_t *bgep, chip_id_t *cidp, boolean_t enable_dma) if (DEVICE_5717_SERIES_CHIPSETS(bgep)) pci_config_put32(handle, PCI_CONF_BGE_MHCR, 0); mhcr = pci_config_get32(handle, PCI_CONF_BGE_MHCR); - cidp->asic_rev = mhcr & MHCR_CHIP_REV_MASK; + cidp->asic_rev = (mhcr & MHCR_CHIP_REV_MASK) >> MHCR_CHIP_REV_SHIFT; + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_PRODID) { + uint32_t reg; + switch (cidp->device) { + case DEVICE_ID_5717: + case DEVICE_ID_5718: + case DEVICE_ID_5719: + case DEVICE_ID_5720: + reg = PCI_CONF_GEN2_PRODID_ASICREV; + break; + case DEVICE_ID_57781: + case DEVICE_ID_57785: + case DEVICE_ID_57761: + case DEVICE_ID_57765: + case DEVICE_ID_57791: + case DEVICE_ID_57795: + case DEVICE_ID_57762: + case DEVICE_ID_57766: + case DEVICE_ID_57782: + case DEVICE_ID_57786: + reg = PCI_CONF_GEN15_PRODID_ASICREV; + break; + default: + reg = PCI_CONF_PRODID_ASICREV; + break; + } + cidp->asic_rev = pci_config_get32(handle, reg); + } cidp->businfo = pci_config_get32(handle, PCI_CONF_BGE_PCISTATE); cidp->command = pci_config_get16(handle, PCI_CONF_COMM); @@ -386,6 +413,45 @@ bge_chip_cfg_init(bge_t *bgep, chip_id_t *cidp, boolean_t enable_dma) BGE_DEBUG(("bge_chip_cfg_init: clsize %d latency %d command 0x%x", cidp->clsize, cidp->latency, cidp->command)); + cidp->chip_type = 0; + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5717 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5719 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5720) + cidp->chip_type |= CHIP_TYPE_5717_PLUS; + + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_57765 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_57766) + cidp->chip_type |= CHIP_TYPE_57765_CLASS; + + if (cidp->chip_type & CHIP_TYPE_57765_CLASS || + cidp->chip_type & CHIP_TYPE_5717_PLUS) + cidp->chip_type |= CHIP_TYPE_57765_PLUS; + + /* Intentionally exclude ASIC_REV_5906 */ + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5755 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5787 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5784 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5761 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5785 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_57780 || + cidp->chip_type & CHIP_TYPE_57765_PLUS) + cidp->chip_type |= CHIP_TYPE_5755_PLUS; + + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5780 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5714) + cidp->chip_type |= CHIP_TYPE_5780_CLASS; + + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5750 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5752 || + MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5906 || + cidp->chip_type & CHIP_TYPE_5755_PLUS || + cidp->chip_type & CHIP_TYPE_5780_CLASS) + cidp->chip_type |= CHIP_TYPE_5750_PLUS; + + if (MHCR_CHIP_ASIC_REV(cidp->asic_rev) == MHCR_CHIP_ASIC_REV_5705 || + cidp->chip_type & CHIP_TYPE_5750_PLUS) + cidp->chip_type |= CHIP_TYPE_5705_PLUS; + /* * Step 2 (also step 6): disable and clear interrupts. * Steps 11-13: configure PIO endianness options, and enable @@ -445,8 +511,9 @@ bge_chip_cfg_init(bge_t *bgep, chip_id_t *cidp, boolean_t enable_dma) * see whether the host is truly up to date, and regenerate * its interrupt if not. */ - mhcr = MHCR_ENABLE_INDIRECT_ACCESS | + mhcr = MHCR_ENABLE_INDIRECT_ACCESS | MHCR_ENABLE_TAGGED_STATUS_MODE | + MHCR_ENABLE_PCI_STATE_WRITE | MHCR_MASK_INTERRUPT_MODE | MHCR_CLEAR_INTERRUPT_INTA; @@ -1896,10 +1963,16 @@ bge_nvmem_id(bge_t *bgep) case DEVICE_ID_5705_2: case DEVICE_ID_5717: case DEVICE_ID_5718: + case DEVICE_ID_5719: + case DEVICE_ID_5720: case DEVICE_ID_5724: + case DEVICE_ID_57760: case DEVICE_ID_57780: + case DEVICE_ID_57788: + case DEVICE_ID_57790: case DEVICE_ID_5780: case DEVICE_ID_5782: + case DEVICE_ID_5784M: case DEVICE_ID_5785: case DEVICE_ID_5787: case DEVICE_ID_5787M: @@ -1918,6 +1991,8 @@ bge_nvmem_id(bge_t *bgep) case DEVICE_ID_5723: case DEVICE_ID_5761: case DEVICE_ID_5761E: + case DEVICE_ID_5761S: + case DEVICE_ID_5761SE: case DEVICE_ID_5764: case DEVICE_ID_5714C: case DEVICE_ID_5714S: @@ -2023,14 +2098,35 @@ bge_chip_id_init(bge_t *bgep) cidp->msi_enabled = B_FALSE; + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) > + MHCR_CHIP_ASIC_REV_PRODID || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5906 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5700 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5701 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5750) + /* + * Just a plain reset; the "check" code breaks these chips + */ + cidp->flags |= CHIP_FLAG_NO_CHECK_RESET; + switch (cidp->device) { case DEVICE_ID_5717: case DEVICE_ID_5718: + case DEVICE_ID_5719: + case DEVICE_ID_5720: case DEVICE_ID_5724: if (cidp->device == DEVICE_ID_5717) cidp->chip_label = 5717; else if (cidp->device == DEVICE_ID_5718) cidp->chip_label = 5718; + else if (cidp->device == DEVICE_ID_5719) + cidp->chip_label = 5719; + else if (cidp->device == DEVICE_ID_5720) + cidp->chip_label = 5720; else cidp->chip_label = 5724; cidp->msi_enabled = bge_enable_msi; @@ -2044,7 +2140,7 @@ bge_chip_id_init(bge_t *bgep) cidp->mbuf_hi_water = MBUF_HIWAT_5717; cidp->mbuf_base = bge_mbuf_pool_base_5705; cidp->mbuf_length = bge_mbuf_pool_len_5705; - cidp->recv_slots = BGE_RECV_SLOTS_5705; + cidp->recv_slots = BGE_RECV_SLOTS_5717; cidp->bge_mlcr_default = MLCR_DEFAULT_5717; cidp->rx_rings = BGE_RECV_RINGS_MAX_5705; cidp->tx_rings = BGE_SEND_RINGS_MAX_5705; @@ -2220,7 +2316,13 @@ bge_chip_id_init(bge_t *bgep) case DEVICE_ID_5723: case DEVICE_ID_5761: case DEVICE_ID_5761E: + case DEVICE_ID_5761S: + case DEVICE_ID_5761SE: + case DEVICE_ID_5784M: + case DEVICE_ID_57760: case DEVICE_ID_57780: + case DEVICE_ID_57788: + case DEVICE_ID_57790: cidp->msi_enabled = bge_enable_msi; /* * We don't use MSI for BCM5764 and BCM5785, as the @@ -2234,10 +2336,18 @@ bge_chip_id_init(bge_t *bgep) cidp->chip_label = 5723; else if (cidp->device == DEVICE_ID_5764) cidp->chip_label = 5764; + else if (cidp->device == DEVICE_ID_5784M) + cidp->chip_label = 5784; else if (cidp->device == DEVICE_ID_5785) cidp->chip_label = 5785; + else if (cidp->device == DEVICE_ID_57760) + cidp->chip_label = 57760; else if (cidp->device == DEVICE_ID_57780) cidp->chip_label = 57780; + else if (cidp->device == DEVICE_ID_57788) + cidp->chip_label = 57788; + else if (cidp->device == DEVICE_ID_57790) + cidp->chip_label = 57790; else cidp->chip_label = 5761; cidp->bge_dma_rwctrl = bge_dma_rwctrl_5721; @@ -3401,18 +3511,27 @@ bge_chip_reset(bge_t *bgep, boolean_t enable_dma) mhcr = MHCR_ENABLE_INDIRECT_ACCESS | MHCR_ENABLE_TAGGED_STATUS_MODE | MHCR_MASK_INTERRUPT_MODE | - MHCR_MASK_PCI_INT_OUTPUT | MHCR_CLEAR_INTERRUPT_INTA | MHCR_ENABLE_ENDIAN_WORD_SWAP | MHCR_ENABLE_ENDIAN_BYTE_SWAP; + + if (bgep->intr_type == DDI_INTR_TYPE_FIXED) + mhcr |= MHCR_MASK_PCI_INT_OUTPUT; + if (DEVICE_5717_SERIES_CHIPSETS(bgep)) pci_config_put32(bgep->cfg_handle, PCI_CONF_BGE_MHCR, 0); +#else + mhcr = MHCR_ENABLE_INDIRECT_ACCESS | + MHCR_ENABLE_TAGGED_STATUS_MODE | + MHCR_MASK_INTERRUPT_MODE | + MHCR_MASK_PCI_INT_OUTPUT | + MHCR_CLEAR_INTERRUPT_INTA; +#endif pci_config_put32(bgep->cfg_handle, PCI_CONF_BGE_MHCR, mhcr); bge_reg_put32(bgep, MEMORY_ARBITER_MODE_REG, bge_reg_get32(bgep, MEMORY_ARBITER_MODE_REG) | MEMORY_ARBITER_ENABLE); -#endif if (asf_mode == ASF_MODE_INIT) { bge_asf_pre_reset_operations(bgep, BGE_INIT_RESET); } else if (asf_mode == ASF_MODE_SHUTDOWN) { @@ -3436,9 +3555,13 @@ bge_chip_reset(bge_t *bgep, boolean_t enable_dma) mhcr = MHCR_ENABLE_INDIRECT_ACCESS | MHCR_ENABLE_TAGGED_STATUS_MODE | + MHCR_ENABLE_PCI_STATE_WRITE | MHCR_MASK_INTERRUPT_MODE | - MHCR_MASK_PCI_INT_OUTPUT | MHCR_CLEAR_INTERRUPT_INTA; + + if (bgep->intr_type == DDI_INTR_TYPE_FIXED) + mhcr |= MHCR_MASK_PCI_INT_OUTPUT; + #ifdef _BIG_ENDIAN mhcr |= MHCR_ENABLE_ENDIAN_WORD_SWAP | MHCR_ENABLE_ENDIAN_BYTE_SWAP; #endif /* _BIG_ENDIAN */ @@ -3449,6 +3572,12 @@ bge_chip_reset(bge_t *bgep, boolean_t enable_dma) if (bgep->asf_enabled) bgep->asf_wordswapped = B_FALSE; #endif + + if (DEVICE_IS_5755_PLUS(bgep) || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5752) + bge_reg_put32(bgep, GRC_FASTBOOT_PC, 0); + /* * NVRAM Corruption Workaround */ @@ -3508,6 +3637,11 @@ bge_chip_reset(bge_t *bgep, boolean_t enable_dma) #else modeflags = MODE_WORD_SWAP_FRAME | MODE_BYTE_SWAP_FRAME; #endif /* _BIG_ENDIAN */ + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5720) + modeflags |= + MODE_BYTE_SWAP_B2HRX_DATA | MODE_WORD_SWAP_B2HRX_DATA | + MODE_B2HRX_ENABLE | MODE_HTX2B_ENABLE; #ifdef BGE_IPMI_ASF if (bgep->asf_enabled) modeflags |= MODE_HOST_STACK_UP; @@ -3592,6 +3726,13 @@ bge_chip_reset(bge_t *bgep, boolean_t enable_dma) */ bge_reg_put32(bgep, ETHERNET_MAC_MODE_REG, 0); + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5720) { + uint32_t regval = bge_reg_get32(bgep, CPMU_CLCK_ORIDE_REG); + bge_reg_put32(bgep, CPMU_CLCK_ORIDE_REG, + regval & ~CPMU_CLCK_ORIDE_MAC_ORIDE_EN); + } + /* * Step 21: restore cache-line-size, latency timer, and * subsystem ID registers to their original values (not @@ -3818,8 +3959,17 @@ bge_chip_start(bge_t *bgep, boolean_t reset_phys) /* * Steps 34-36: enable buffer manager & internal h/w queues */ - if (!bge_chip_enable_engine(bgep, BUFFER_MANAGER_MODE_REG, - STATE_MACHINE_ATTN_ENABLE_BIT)) + + regval = STATE_MACHINE_ATTN_ENABLE_BIT; + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5719) + regval |= BUFF_MGR_NO_TX_UNDERRUN; + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5717 || + bgep->chipid.asic_rev == MHCR_CHIP_REV_5719_A0 || + bgep->chipid.asic_rev == MHCR_CHIP_REV_5720_A0) + regval |= BUFF_MGR_MBUF_LOW_ATTN_ENABLE; + if (!bge_chip_enable_engine(bgep, BUFFER_MANAGER_MODE_REG, regval)) retval = DDI_FAILURE; if (!bge_chip_enable_engine(bgep, FTQ_RESET_REG, 0)) retval = DDI_FAILURE; @@ -3913,7 +4063,13 @@ bge_chip_start(bge_t *bgep, boolean_t reset_phys) /* * Step 50: configure the IPG et al */ - bge_reg_put32(bgep, MAC_TX_LENGTHS_REG, MAC_TX_LENGTHS_DEFAULT); + regval = MAC_TX_LENGTHS_DEFAULT; + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) + == MHCR_CHIP_ASIC_REV_5720) + regval |= bge_reg_get32(bgep, MAC_TX_LENGTHS_REG) & + (MAC_TX_LENGTHS_JMB_FRM_LEN_MSK | + MAC_TX_LENGTHS_CNT_DWN_VAL_MSK); + bge_reg_put32(bgep, MAC_TX_LENGTHS_REG, regval); /* * Step 51: configure the default Rx Return Ring @@ -4068,22 +4224,45 @@ bge_chip_start(bge_t *bgep, boolean_t reset_phys) retval = DDI_FAILURE; dma_wrprio = (bge_dma_wrprio << DMA_PRIORITY_SHIFT) | ALL_DMA_ATTN_BITS; - if ((MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == - MHCR_CHIP_ASIC_REV_5755) || - (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == - MHCR_CHIP_ASIC_REV_5723) || - (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == - MHCR_CHIP_ASIC_REV_5906)) { + if (DEVICE_IS_5755_PLUS(bgep)) dma_wrprio |= DMA_STATUS_TAG_FIX_CQ12384; - } if (!bge_chip_enable_engine(bgep, WRITE_DMA_MODE_REG, dma_wrprio)) retval = DDI_FAILURE; + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5761 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5784 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5785 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_57780 || + DEVICE_IS_57765_PLUS(bgep)) { + regval = bge_reg_get32(bgep, READ_DMA_RESERVED_CONTROL_REG); + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5719 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5720) { + regval &= ~(RDMA_RSRVCTRL_TXMRGN_MASK | + RDMA_RSRVCTRL_FIFO_LWM_MASK | + RDMA_RSRVCTRL_FIFO_HWM_MASK); + regval |= RDMA_RSRVCTRL_TXMRGN_320B | + RDMA_RSRVCTRL_FIFO_LWM_1_5K | + RDMA_RSRVCTRL_FIFO_HWM_1_5K; + } + bge_reg_put32(bgep, READ_DMA_RESERVED_CONTROL_REG, + regval | RDMA_RSRVCTRL_FIFO_OFLW_FIX); + } if (DEVICE_5723_SERIES_CHIPSETS(bgep) || DEVICE_5717_SERIES_CHIPSETS(bgep)) bge_dma_rdprio = 0; + regval = bge_dma_rdprio << DMA_PRIORITY_SHIFT; + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5720) + regval |= bge_reg_get32(bgep, READ_DMA_MODE_REG) & + DMA_H2BNC_VLAN_DET; if (!bge_chip_enable_engine(bgep, READ_DMA_MODE_REG, - (bge_dma_rdprio << DMA_PRIORITY_SHIFT) | ALL_DMA_ATTN_BITS)) + regval | ALL_DMA_ATTN_BITS)) retval = DDI_FAILURE; if (!bge_chip_enable_engine(bgep, RCV_DATA_COMPLETION_MODE_REG, STATE_MACHINE_ATTN_ENABLE_BIT)) @@ -4116,7 +4295,23 @@ bge_chip_start(bge_t *bgep, boolean_t reset_phys) * Step 88: download firmware -- doesn't apply * Steps 89-90: enable Transmit & Receive MAC Engines */ - if (!bge_chip_enable_engine(bgep, TRANSMIT_MAC_MODE_REG, 0)) + if (DEVICE_IS_5755_PLUS(bgep) || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5906) { + regval = bge_reg_get32(bgep, TRANSMIT_MAC_MODE_REG); + regval |= TRANSMIT_MODE_MBUF_LOCKUP_FIX; + } else { + regval = 0; + } + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5720) { + regval &= ~(TRANSMIT_MODE_HTX2B_JMB_FRM_LEN | + TRANSMIT_MODE_HTX2B_CNT_DN_MODE); + regval |= bge_reg_get32(bgep, TRANSMIT_MAC_MODE_REG) & + (TRANSMIT_MODE_HTX2B_JMB_FRM_LEN | + TRANSMIT_MODE_HTX2B_CNT_DN_MODE); + } + if (!bge_chip_enable_engine(bgep, TRANSMIT_MAC_MODE_REG, regval)) retval = DDI_FAILURE; #ifdef BGE_IPMI_ASF if (!bgep->asf_enabled) { @@ -4219,7 +4414,6 @@ bge_chip_start(bge_t *bgep, boolean_t reset_phys) if (bgep->intr_type == DDI_INTR_TYPE_FIXED) bge_cfg_clr32(bgep, PCI_CONF_BGE_MHCR, bgep->chipid.mask_pci_int); - /* * All done! */ diff --git a/usr/src/uts/common/io/bge/bge_hw.h b/usr/src/uts/common/io/bge/bge_hw.h index f8e6c4d09a..cfcae929dd 100644 --- a/usr/src/uts/common/io/bge/bge_hw.h +++ b/usr/src/uts/common/io/bge/bge_hw.h @@ -23,6 +23,10 @@ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. */ +/* + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. + */ + #ifndef _BGE_HW_H #define _BGE_HW_H @@ -68,9 +72,12 @@ extern "C" { #define DEVICE_ID_5724 0x165c #define DEVICE_ID_5705M 0x165d #define DEVICE_ID_5705MA3 0x165e +#define DEVICE_ID_5719 0x1657 +#define DEVICE_ID_5720 0x165f #define DEVICE_ID_5705F 0x166e #define DEVICE_ID_5780 0x166a #define DEVICE_ID_5782 0x1696 +#define DEVICE_ID_5784M 0x1698 #define DEVICE_ID_5785 0x1699 #define DEVICE_ID_5787 0x169b #define DEVICE_ID_5787M 0x1693 @@ -92,12 +99,27 @@ extern "C" { #define DEVICE_ID_5714S 0x1669 #define DEVICE_ID_5715C 0x1678 #define DEVICE_ID_5715S 0x1679 -#define DEVICE_ID_5761E 0x1680 #define DEVICE_ID_5761 0x1681 +#define DEVICE_ID_5761E 0x1680 +#define DEVICE_ID_5761S 0x1688 +#define DEVICE_ID_5761SE 0x1689 #define DEVICE_ID_5764 0x1684 #define DEVICE_ID_5906 0x1712 #define DEVICE_ID_5906M 0x1713 +#define DEVICE_ID_57760 0x1690 #define DEVICE_ID_57780 0x1692 +#define DEVICE_ID_57788 0x1691 +#define DEVICE_ID_57790 0x1694 +#define DEVICE_ID_57781 0x16b1 +#define DEVICE_ID_57785 0x16b5 +#define DEVICE_ID_57761 0x16b0 +#define DEVICE_ID_57765 0x16b4 +#define DEVICE_ID_57791 0x16b2 +#define DEVICE_ID_57795 0x16b6 +#define DEVICE_ID_57762 0x1682 +#define DEVICE_ID_57766 0x1686 +#define DEVICE_ID_57786 0x16b3 +#define DEVICE_ID_57782 0x16b7 #define REVISION_ID_5700_B0 0x10 #define REVISION_ID_5700_B2 0x12 @@ -189,15 +211,23 @@ extern "C" { #define DEVICE_5717_SERIES_CHIPSETS(bgep) \ (bgep->chipid.device == DEVICE_ID_5717) ||\ (bgep->chipid.device == DEVICE_ID_5718) ||\ + (bgep->chipid.device == DEVICE_ID_5719) ||\ + (bgep->chipid.device == DEVICE_ID_5720) ||\ (bgep->chipid.device == DEVICE_ID_5724) #define DEVICE_5723_SERIES_CHIPSETS(bgep) \ ((bgep->chipid.device == DEVICE_ID_5723) ||\ (bgep->chipid.device == DEVICE_ID_5761) ||\ (bgep->chipid.device == DEVICE_ID_5761E) ||\ + (bgep->chipid.device == DEVICE_ID_5761S) ||\ + (bgep->chipid.device == DEVICE_ID_5761SE) ||\ (bgep->chipid.device == DEVICE_ID_5764) ||\ + (bgep->chipid.device == DEVICE_ID_5784M) ||\ (bgep->chipid.device == DEVICE_ID_5785) ||\ - (bgep->chipid.device == DEVICE_ID_57780)) + (bgep->chipid.device == DEVICE_ID_57760) ||\ + (bgep->chipid.device == DEVICE_ID_57780) ||\ + (bgep->chipid.device == DEVICE_ID_57788) ||\ + (bgep->chipid.device == DEVICE_ID_57790)) #define DEVICE_5714_SERIES_CHIPSETS(bgep) \ ((bgep->chipid.device == DEVICE_ID_5714C) ||\ @@ -209,6 +239,20 @@ extern "C" { ((bgep->chipid.device == DEVICE_ID_5906) ||\ (bgep->chipid.device == DEVICE_ID_5906M)) + +#define CHIP_TYPE_5705_PLUS (1 << 0) +#define CHIP_TYPE_5750_PLUS (1 << 1) +#define CHIP_TYPE_5780_CLASS (1 << 2) +#define CHIP_TYPE_5755_PLUS (1 << 3) +#define CHIP_TYPE_57765_CLASS (1 << 4) +#define CHIP_TYPE_57765_PLUS (1 << 5) +#define CHIP_TYPE_5717_PLUS (1 << 6) + +#define DEVICE_IS_57765_PLUS(bgep) \ + (bgep->chipid.chip_type & CHIP_TYPE_57765_PLUS) +#define DEVICE_IS_5755_PLUS(bgep) \ + (bgep->chipid.chip_type & CHIP_TYPE_5755_PLUS) + /* * Second section: * Offsets of important registers & definitions for bits therein @@ -225,6 +269,7 @@ extern "C" { */ #define PCI_CONF_BGE_MHCR 0x68 #define MHCR_CHIP_REV_MASK 0xffff0000 +#define MHCR_CHIP_REV_SHIFT 16 #define MHCR_ENABLE_TAGGED_STATUS_MODE 0x00000200 #define MHCR_MASK_INTERRUPT_MODE 0x00000100 #define MHCR_ENABLE_INDIRECT_ACCESS 0x00000080 @@ -236,95 +281,38 @@ extern "C" { #define MHCR_MASK_PCI_INT_OUTPUT 0x00000002 #define MHCR_CLEAR_INTERRUPT_INTA 0x00000001 -#define MHCR_CHIP_REV_5700_B0 0x71000000 -#define MHCR_CHIP_REV_5700_B2 0x71020000 -#define MHCR_CHIP_REV_5700_B3 0x71030000 -#define MHCR_CHIP_REV_5700_C0 0x72000000 -#define MHCR_CHIP_REV_5700_C1 0x72010000 -#define MHCR_CHIP_REV_5700_C2 0x72020000 - -#define MHCR_CHIP_REV_5701_A0 0x00000000 -#define MHCR_CHIP_REV_5701_A2 0x00020000 -#define MHCR_CHIP_REV_5701_A3 0x00030000 -#define MHCR_CHIP_REV_5701_A5 0x01050000 - -#define MHCR_CHIP_REV_5702_A0 0x10000000 -#define MHCR_CHIP_REV_5702_A1 0x10010000 -#define MHCR_CHIP_REV_5702_A2 0x10020000 - -#define MHCR_CHIP_REV_5703_A0 0x10000000 -#define MHCR_CHIP_REV_5703_A1 0x10010000 -#define MHCR_CHIP_REV_5703_A2 0x10020000 -#define MHCR_CHIP_REV_5703_B0 0x11000000 -#define MHCR_CHIP_REV_5703_B1 0x11010000 - -#define MHCR_CHIP_REV_5704_A0 0x20000000 -#define MHCR_CHIP_REV_5704_A1 0x20010000 -#define MHCR_CHIP_REV_5704_A2 0x20020000 -#define MHCR_CHIP_REV_5704_A3 0x20030000 -#define MHCR_CHIP_REV_5704_B0 0x21000000 - -#define MHCR_CHIP_REV_5705_A0 0x30000000 -#define MHCR_CHIP_REV_5705_A1 0x30010000 -#define MHCR_CHIP_REV_5705_A2 0x30020000 -#define MHCR_CHIP_REV_5705_A3 0x30030000 -#define MHCR_CHIP_REV_5705_A5 0x30050000 - -#define MHCR_CHIP_REV_5782_A0 0x30030000 -#define MHCR_CHIP_REV_5782_A1 0x30030088 - -#define MHCR_CHIP_REV_5788_A1 0x30050000 - -#define MHCR_CHIP_REV_5751_A0 0x40000000 -#define MHCR_CHIP_REV_5751_A1 0x40010000 - -#define MHCR_CHIP_REV_5721_A0 0x41000000 -#define MHCR_CHIP_REV_5721_A1 0x41010000 - -#define MHCR_CHIP_REV_5714_A0 0x50000000 -#define MHCR_CHIP_REV_5714_A1 0x90010000 - -#define MHCR_CHIP_REV_5715_A0 0x50000000 -#define MHCR_CHIP_REV_5715_A1 0x90010000 - -#define MHCR_CHIP_REV_5715S_A0 0x50000000 -#define MHCR_CHIP_REV_5715S_A1 0x90010000 - -#define MHCR_CHIP_REV_5754_A0 0xb0000000 -#define MHCR_CHIP_REV_5754_A1 0xb0010000 - -#define MHCR_CHIP_REV_5787_A0 0xb0000000 -#define MHCR_CHIP_REV_5787_A1 0xb0010000 -#define MHCR_CHIP_REV_5787_A2 0xb0020000 - -#define MHCR_CHIP_REV_5755_A0 0xa0000000 -#define MHCR_CHIP_REV_5755_A1 0xa0010000 - -#define MHCR_CHIP_REV_5906_A0 0xc0000000 -#define MHCR_CHIP_REV_5906_A1 0xc0010000 -#define MHCR_CHIP_REV_5906_A2 0xc0020000 - -#define MHCR_CHIP_REV_5723_A0 0xf0000000 -#define MHCR_CHIP_REV_5723_A1 0xf0010000 -#define MHCR_CHIP_REV_5723_A2 0xf0020000 -#define MHCR_CHIP_REV_5723_B0 0xf1000000 - -#define MHCR_CHIP_ASIC_REV(ChipRevId) ((ChipRevId) & 0xf0000000) -#define MHCR_CHIP_ASIC_REV_5700 (0x7 << 28) -#define MHCR_CHIP_ASIC_REV_5701 (0x0 << 28) -#define MHCR_CHIP_ASIC_REV_5703 (0x1 << 28) -#define MHCR_CHIP_ASIC_REV_5704 (0x2 << 28) -#define MHCR_CHIP_ASIC_REV_5705 (0x3 << 28) -#define MHCR_CHIP_ASIC_REV_5721_5751 (0x4 << 28) -#define MHCR_CHIP_ASIC_REV_5714 (0x5 << 28) -#define MHCR_CHIP_ASIC_REV_5752 (0x6 << 28) -#define MHCR_CHIP_ASIC_REV_5754 (0xb << 28) -#define MHCR_CHIP_ASIC_REV_5787 ((uint32_t)0xb << 28) -#define MHCR_CHIP_ASIC_REV_5755 ((uint32_t)0xa << 28) -#define MHCR_CHIP_ASIC_REV_5715 ((uint32_t)0x9 << 28) -#define MHCR_CHIP_ASIC_REV_5906 ((uint32_t)0xc << 28) -#define MHCR_CHIP_ASIC_REV_5723 ((uint32_t)0xf << 28) - +#define MHCR_CHIP_REV_5703_A0 0x1000 +#define MHCR_CHIP_REV_5704_A0 0x2000 +#define MHCR_CHIP_REV_5751_A0 0x4000 +#define MHCR_CHIP_REV_5721_A0 0x4100 +#define MHCR_CHIP_REV_5755_A0 0xa000 +#define MHCR_CHIP_REV_5755_A1 0xa001 +#define MHCR_CHIP_REV_5719_A0 0x05719000 +#define MHCR_CHIP_REV_5720_A0 0x05720000 + +#define MHCR_CHIP_ASIC_REV(ChipRevId) ((ChipRevId) >> 12) +#define MHCR_CHIP_ASIC_REV_5700 0x07 +#define MHCR_CHIP_ASIC_REV_5701 0x00 +#define MHCR_CHIP_ASIC_REV_5703 0x01 +#define MHCR_CHIP_ASIC_REV_5704 0x02 +#define MHCR_CHIP_ASIC_REV_5705 0x03 +#define MHCR_CHIP_ASIC_REV_5750 0x04 +#define MHCR_CHIP_ASIC_REV_5752 0x06 +#define MHCR_CHIP_ASIC_REV_5780 0x08 +#define MHCR_CHIP_ASIC_REV_5714 0x09 +#define MHCR_CHIP_ASIC_REV_5755 0x0a +#define MHCR_CHIP_ASIC_REV_5787 0x0b +#define MHCR_CHIP_ASIC_REV_5906 0x0c +#define MHCR_CHIP_ASIC_REV_PRODID 0x0f +#define MHCR_CHIP_ASIC_REV_5784 0x5784 +#define MHCR_CHIP_ASIC_REV_5761 0x5761 +#define MHCR_CHIP_ASIC_REV_5785 0x5785 +#define MHCR_CHIP_ASIC_REV_5717 0x5717 +#define MHCR_CHIP_ASIC_REV_5719 0x5719 +#define MHCR_CHIP_ASIC_REV_5720 0x5720 +#define MHCR_CHIP_ASIC_REV_57780 0x57780 +#define MHCR_CHIP_ASIC_REV_57765 0x57785 +#define MHCR_CHIP_ASIC_REV_57766 0x57766 /* * PCI DMA read/write Control Register, in PCI config space @@ -466,6 +454,10 @@ extern "C" { #define PCI_CONF_DEV_STUS_5723 0xd6 #define DEVICE_ERROR_STUS 0xf +#define PCI_CONF_PRODID_ASICREV 0x000000bc +#define PCI_CONF_GEN2_PRODID_ASICREV 0x000000f4 +#define PCI_CONF_GEN15_PRODID_ASICREV 0x000000fc + #define NIC_MEM_WINDOW_OFFSET 0x00008000 /* 32k */ /* @@ -541,6 +533,7 @@ extern "C" { #define MEMORY_ARBITER_MODE_REG 0x4000 #define BUFFER_MANAGER_MODE_REG 0x4400 #define READ_DMA_MODE_REG 0x4800 +#define READ_DMA_RESERVED_CONTROL_REG 0x4900 #define WRITE_DMA_MODE_REG 0x4c00 #define DMA_COMPLETION_MODE_REG 0x6400 @@ -552,6 +545,9 @@ extern "C" { * Transmit MAC Mode Register * (TRANSMIT_MAC_MODE_REG, 0x045c) */ +#define TRANSMIT_MODE_HTX2B_CNT_DN_MODE 0x00800000 +#define TRANSMIT_MODE_HTX2B_JMB_FRM_LEN 0x00400000 +#define TRANSMIT_MODE_MBUF_LOCKUP_FIX 0x00000100 #define TRANSMIT_MODE_LONG_PAUSE 0x00000040 #define TRANSMIT_MODE_BIG_BACKOFF 0x00000020 #define TRANSMIT_MODE_FLOW_CONTROL 0x00000010 @@ -619,12 +615,14 @@ extern "C" { */ #define BUFF_MGR_TEST_MODE 0x00000008 #define BUFF_MGR_MBUF_LOW_ATTN_ENABLE 0x00000010 +#define BUFF_MGR_NO_TX_UNDERRUN 0x80000000 #define BUFF_MGR_ALL_ATTN_BITS 0x00000014 /* * Read and Write DMA Mode Registers (READ_DMA_MODE_REG, - * 0x4800 and WRITE_DMA_MODE_REG, 0x4c00) + * 0x4800, READ_DMA_RESERVED_CONTROL_REG, 0x4900, + * WRITE_DMA_MODE_REG, 0x4c00) * * These registers each contain a 2-bit priority field, which controls * the relative priority of that type of DMA (read vs. write vs. MSI), @@ -635,6 +633,15 @@ extern "C" { #define DMA_PRIORITY_SHIFT 30 #define ALL_DMA_ATTN_BITS 0x000003fc +#define RDMA_RSRVCTRL_FIFO_OFLW_FIX 0x00000004 +#define RDMA_RSRVCTRL_FIFO_LWM_1_5K 0x00000c00 +#define RDMA_RSRVCTRL_FIFO_LWM_MASK 0x00000ff0 +#define RDMA_RSRVCTRL_FIFO_HWM_1_5K 0x000c0000 +#define RDMA_RSRVCTRL_FIFO_HWM_MASK 0x000ff000 +#define RDMA_RSRVCTRL_TXMRGN_320B 0x28000000 +#define RDMA_RSRVCTRL_TXMRGN_MASK 0xffe00000 + + /* * BCM5755, 5755M, 5906, 5906M only * 1 - Enable Fix. Device will send out the status block before @@ -644,6 +651,10 @@ extern "C" { */ #define DMA_STATUS_TAG_FIX_CQ12384 0x20000000 +/* 5720 only */ +#define DMA_H2BNC_VLAN_DET 0x20000000 + + /* * End of state machine control register definitions */ @@ -781,6 +792,8 @@ extern "C" { #define MAC_RX_MTU_DEFAULT 0x000005f2 /* 1522 */ #define MAC_TX_LENGTHS_REG 0x0464 #define MAC_TX_LENGTHS_DEFAULT 0x00002620 +#define MAC_TX_LENGTHS_JMB_FRM_LEN_MSK 0x00ff0000 +#define MAC_TX_LENGTHS_CNT_DWN_VAL_MSK 0xff000000 /* * MII access registers @@ -1069,10 +1082,16 @@ extern "C" { #define JUMBO_RCV_BD_REPLENISH_DEFAULT 0x00000020 /* 32 */ /* - * CPMU registers (5717/5718 only) + * CPMU registers (5717/5718/5719/5720 only) */ -#define CPMU_STATUS_REG 0x362c -#define CPMU_STATUS_FUN_NUM 0x20000000 +#define CPMU_CLCK_ORIDE_REG 0x3624 +#define CPMU_CLCK_ORIDE_MAC_ORIDE_EN 0x80000000 + +#define CPMU_STATUS_REG 0x362c +#define CPMU_STATUS_FUN_NUM_5717 0x20000000 +#define CPMU_STATUS_FUN_NUM_5719 0xc0000000 +#define CPMU_STATUS_FUN_NUM_5719_SHIFT 30 + /* * Host Coalescing Engine Control Registers @@ -1191,6 +1210,8 @@ extern "C" { #define VCPU_EXT_CTL 0x6890 #define VCPU_EXT_CTL_HALF 0x00400000 +#define GRC_FASTBOOT_PC 0x6894 + #define FTQ_RESET_REG 0x5c00 #define MSI_MODE_REG 0x6000 @@ -1210,14 +1231,18 @@ extern "C" { #define MODE_INT_ON_TXRISC_ATTN 0x01000000 #define MODE_RECV_NO_PSEUDO_HDR_CSUM 0x00800000 #define MODE_SEND_NO_PSEUDO_HDR_CSUM 0x00100000 +#define MODE_HTX2B_ENABLE 0x00040000 #define MODE_HOST_SEND_BDS 0x00020000 #define MODE_HOST_STACK_UP 0x00010000 #define MODE_FORCE_32_BIT_PCI 0x00008000 +#define MODE_B2HRX_ENABLE 0x00008000 #define MODE_NO_INT_ON_RECV 0x00004000 #define MODE_NO_INT_ON_SEND 0x00002000 #define MODE_ALLOW_BAD_FRAMES 0x00000800 #define MODE_NO_CRC 0x00000400 #define MODE_NO_FRAME_CRACKING 0x00000200 +#define MODE_WORD_SWAP_B2HRX_DATA 0x00000080 +#define MODE_BYTE_SWAP_B2HRX_DATA 0x00000040 #define MODE_WORD_SWAP_FRAME 0x00000020 #define MODE_BYTE_SWAP_FRAME 0x00000010 #define MODE_WORD_SWAP_NONFRAME 0x00000004 @@ -1246,7 +1271,7 @@ extern "C" { */ #define CORE_CLOCK_MHZ 66 #define MISC_CONFIG_REG 0x6804 -#define MISC_CONFIG_GRC_RESET_DISABLE 0x20000000 +#define MISC_CONFIG_GRC_RESET_DISABLE 0x20000000 #define MISC_CONFIG_GPHY_POWERDOWN_OVERRIDE 0x04000000 #define MISC_CONFIG_POWERDOWN 0x00100000 #define MISC_CONFIG_POWER_STATE 0x00060000 @@ -1567,6 +1592,7 @@ extern "C" { #define BGE_MINI_SLOTS_MAX 1024 #define BGE_RECV_SLOTS_MAX 2048 #define BGE_RECV_SLOTS_5705 512 +#define BGE_RECV_SLOTS_5717 1024 #define BGE_RECV_SLOTS_5782 512 #define BGE_RECV_SLOTS_5721 512 diff --git a/usr/src/uts/common/io/bge/bge_impl.h b/usr/src/uts/common/io/bge/bge_impl.h index 772c989092..0c51c2bc8e 100644 --- a/usr/src/uts/common/io/bge/bge_impl.h +++ b/usr/src/uts/common/io/bge/bge_impl.h @@ -23,6 +23,10 @@ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. */ +/* + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. + */ + #ifndef _BGE_IMPL_H #define _BGE_IMPL_H @@ -605,6 +609,7 @@ typedef struct { uint8_t latency; /* latency-timer */ uint8_t flags; + uint32_t chip_type; /* see CHIP_TYPE_ in bge_hw.h */ uint16_t chip_label; /* numeric part only */ /* (e.g. 5703/5794/etc) */ uint32_t mbuf_base; /* Mbuf pool parameters */ @@ -640,10 +645,11 @@ typedef struct { uint32_t mask_pci_int; } chip_id_t; -#define CHIP_FLAG_SUPPORTED 0x80 -#define CHIP_FLAG_SERDES 0x40 -#define CHIP_FLAG_PARTIAL_CSUM 0x20 -#define CHIP_FLAG_NO_JUMBO 0x1 +#define CHIP_FLAG_SUPPORTED 0x80 +#define CHIP_FLAG_SERDES 0x40 +#define CHIP_FLAG_PARTIAL_CSUM 0x20 +#define CHIP_FLAG_NO_CHECK_RESET 0x2 +#define CHIP_FLAG_NO_JUMBO 0x1 /* * Collection of physical-layer functions to: diff --git a/usr/src/uts/common/io/bge/bge_main2.c b/usr/src/uts/common/io/bge/bge_main2.c index f191f313c0..d0f309730d 100644 --- a/usr/src/uts/common/io/bge/bge_main2.c +++ b/usr/src/uts/common/io/bge/bge_main2.c @@ -23,6 +23,10 @@ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. */ +/* + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. + */ + #include "bge_impl.h" #include <sys/sdt.h> #include <sys/mac_provider.h> @@ -3211,13 +3215,17 @@ bge_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd) */ if (DEVICE_5717_SERIES_CHIPSETS(bgep)) pci_config_put32(bgep->cfg_handle, PCI_CONF_BGE_MHCR, 0); +#else + mhcrValue = MHCR_ENABLE_INDIRECT_ACCESS | + MHCR_ENABLE_TAGGED_STATUS_MODE | + MHCR_MASK_INTERRUPT_MODE | + MHCR_MASK_PCI_INT_OUTPUT | + MHCR_CLEAR_INTERRUPT_INTA; +#endif pci_config_put32(bgep->cfg_handle, PCI_CONF_BGE_MHCR, mhcrValue); bge_ind_put32(bgep, MEMORY_ARBITER_MODE_REG, bge_ind_get32(bgep, MEMORY_ARBITER_MODE_REG) | MEMORY_ARBITER_ENABLE); -#else - mhcrValue = pci_config_get32(bgep->cfg_handle, PCI_CONF_BGE_MHCR); -#endif if (mhcrValue & MHCR_ENABLE_ENDIAN_WORD_SWAP) { bgep->asf_wordswapped = B_TRUE; } else { diff --git a/usr/src/uts/common/io/bge/bge_mii.c b/usr/src/uts/common/io/bge/bge_mii.c index f24b6a3f16..b47c043d8c 100644 --- a/usr/src/uts/common/io/bge/bge_mii.c +++ b/usr/src/uts/common/io/bge/bge_mii.c @@ -23,6 +23,10 @@ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. */ +/* + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. + */ + #include "bge_impl.h" /* @@ -207,6 +211,7 @@ bge_phy_reset(bge_t *bgep) { uint16_t control; uint_t count; + boolean_t ret = B_FALSE; BGE_TRACE(("bge_phy_reset($%p)", (void *)bgep)); @@ -221,22 +226,26 @@ bge_phy_reset(bge_t *bgep) } /* - * Set the PHY RESET bit, then wait up to 5 ms for it to self-clear + * Set the PHY RESET bit, then wait up to 50 ms for it to self-clear */ bge_mii_put16(bgep, MII_CONTROL, MII_CONTROL_RESET); - for (count = 0; ++count < 1000; ) { - drv_usecwait(5); + for (count = 0; ++count < 5000; ) { control = bge_mii_get16(bgep, MII_CONTROL); - if (BIC(control, MII_CONTROL_RESET)) - return (B_TRUE); + if (BIC(control, MII_CONTROL_RESET)) { + drv_usecwait(40); + ret = B_TRUE; + break; + } + drv_usecwait(10); } - if (DEVICE_5906_SERIES_CHIPSETS(bgep)) + if (ret == B_TRUE && DEVICE_5906_SERIES_CHIPSETS(bgep)) (void) bge_adj_volt_5906(bgep); - BGE_DEBUG(("bge_phy_reset: FAILED, control now 0x%x", control)); + if (ret == B_FALSE) + BGE_DEBUG(("bge_phy_reset: FAILED, control now 0x%x", control)); - return (B_FALSE); + return (ret); } /* @@ -541,34 +550,14 @@ bge_restart_copper(bge_t *bgep, boolean_t powerdown) ASSERT(mutex_owned(bgep->genlock)); - switch (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev)) { - default: - /* - * Shouldn't happen; it means we don't recognise this chip. - * It's probably a new one, so we'll try our best anyway ... - */ - case MHCR_CHIP_ASIC_REV_5703: - case MHCR_CHIP_ASIC_REV_5704: - case MHCR_CHIP_ASIC_REV_5705: - case MHCR_CHIP_ASIC_REV_5752: - case MHCR_CHIP_ASIC_REV_5714: - case MHCR_CHIP_ASIC_REV_5715: - reset_ok = bge_phy_reset_and_check(bgep); - break; - - case MHCR_CHIP_ASIC_REV_5906: - case MHCR_CHIP_ASIC_REV_5700: - case MHCR_CHIP_ASIC_REV_5701: - case MHCR_CHIP_ASIC_REV_5723: - case MHCR_CHIP_ASIC_REV_5721_5751: - /* - * Just a plain reset; the "check" code breaks these chips - */ + if (bgep->chipid.flags & CHIP_FLAG_NO_CHECK_RESET) { reset_ok = bge_phy_reset(bgep); if (!reset_ok) bge_fm_ereport(bgep, DDI_FM_DEVICE_NO_RESPONSE); - break; + } else { + reset_ok = bge_phy_reset_and_check(bgep); } + if (!reset_ok) { BGE_REPORT((bgep, "PHY failed to reset correctly")); return (DDI_FAILURE); @@ -590,7 +579,7 @@ bge_restart_copper(bge_t *bgep, boolean_t powerdown) switch (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev)) { case MHCR_CHIP_ASIC_REV_5705: - case MHCR_CHIP_ASIC_REV_5721_5751: + case MHCR_CHIP_ASIC_REV_5750: bge_phy_bit_err_fix(bgep); break; } @@ -1507,14 +1496,22 @@ bge_phys_init(bge_t *bgep) */ bgep->phy_mii_addr = 1; if (DEVICE_5717_SERIES_CHIPSETS(bgep)) { - int regval = bge_reg_get32(bgep, CPMU_STATUS_REG); - if (regval & CPMU_STATUS_FUN_NUM) - bgep->phy_mii_addr += 1; + uint32_t regval = bge_reg_get32(bgep, CPMU_STATUS_REG); + if (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5719 || + MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev) == + MHCR_CHIP_ASIC_REV_5720) { + bgep->phy_mii_addr += + (regval & CPMU_STATUS_FUN_NUM_5719) >> + CPMU_STATUS_FUN_NUM_5719_SHIFT; + } else { + bgep->phy_mii_addr += + (regval & CPMU_STATUS_FUN_NUM_5717) ? 1 : 0; + } regval = bge_reg_get32(bgep, SGMII_STATUS_REG); if (regval & MEDIA_SELECTION_MODE) bgep->phy_mii_addr += 7; } - if (bge_phy_probe(bgep)) { bgep->chipid.flags &= ~CHIP_FLAG_SERDES; bgep->physops = &copper_ops; diff --git a/usr/src/uts/common/io/devpoll.c b/usr/src/uts/common/io/devpoll.c index a3fcbbba03..5be9dc6166 100644 --- a/usr/src/uts/common/io/devpoll.c +++ b/usr/src/uts/common/io/devpoll.c @@ -25,6 +25,7 @@ /* * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2014, Joyent, Inc. All rights reserved. */ #include <sys/types.h> @@ -45,6 +46,8 @@ #include <sys/devpoll.h> #include <sys/rctl.h> #include <sys/resource.h> +#include <sys/schedctl.h> +#include <sys/epoll.h> #define RESERVED 1 @@ -237,7 +240,8 @@ dpinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) * stale entries! */ static int -dp_pcache_poll(pollfd_t *pfdp, pollcache_t *pcp, nfds_t nfds, int *fdcntp) +dp_pcache_poll(dp_entry_t *dpep, void *dpbuf, + pollcache_t *pcp, nfds_t nfds, int *fdcntp) { int start, ostart, end; int fdcnt, fd; @@ -247,7 +251,10 @@ dp_pcache_poll(pollfd_t *pfdp, pollcache_t *pcp, nfds_t nfds, int *fdcntp) boolean_t no_wrap; pollhead_t *php; polldat_t *pdp; + pollfd_t *pfdp; + epoll_event_t *epoll; int error = 0; + short mask = POLLRDHUP | POLLWRBAND; ASSERT(MUTEX_HELD(&pcp->pc_lock)); if (pcp->pc_bitmap == NULL) { @@ -257,6 +264,14 @@ dp_pcache_poll(pollfd_t *pfdp, pollcache_t *pcp, nfds_t nfds, int *fdcntp) */ return (error); } + + if (dpep->dpe_flag & DP_ISEPOLLCOMPAT) { + pfdp = NULL; + epoll = (epoll_event_t *)dpbuf; + } else { + pfdp = (pollfd_t *)dpbuf; + epoll = NULL; + } retry: start = ostart = pcp->pc_mapstart; end = pcp->pc_mapend; @@ -316,11 +331,32 @@ repoll: * polling a closed fd. Hope this will remind * user to do a POLLREMOVE. */ - pfdp[fdcnt].fd = fd; - pfdp[fdcnt].revents = POLLNVAL; - fdcnt++; + if (pfdp != NULL) { + pfdp[fdcnt].fd = fd; + pfdp[fdcnt].revents = POLLNVAL; + fdcnt++; + continue; + } + + /* + * In the epoll compatibility case, we actually + * perform the implicit removal to remain + * closer to the epoll semantics. + */ + ASSERT(epoll != NULL); + + pdp->pd_fp = NULL; + pdp->pd_events = 0; + + if (php != NULL) { + pollhead_delete(php, pdp); + pdp->pd_php = NULL; + } + + BT_CLEAR(pcp->pc_bitmap, fd); continue; } + if (fp != pdp->pd_fp) { /* * user is polling on a cached fd which was @@ -376,9 +412,69 @@ repoll: } if (revent != 0) { - pfdp[fdcnt].fd = fd; - pfdp[fdcnt].events = pdp->pd_events; - pfdp[fdcnt].revents = revent; + if (pfdp != NULL) { + pfdp[fdcnt].fd = fd; + pfdp[fdcnt].events = pdp->pd_events; + pfdp[fdcnt].revents = revent; + } else { + epoll_event_t *ep = &epoll[fdcnt]; + + ASSERT(epoll != NULL); + ep->data.u64 = pdp->pd_epolldata; + + /* + * If any of the event bits are set for + * which poll and epoll representations + * differ, swizzle in the native epoll + * values. + */ + if (revent & mask) { + ep->events = (revent & ~mask) | + ((revent & POLLRDHUP) ? + EPOLLRDHUP : 0) | + ((revent & POLLWRBAND) ? + EPOLLWRBAND : 0); + } else { + ep->events = revent; + } + + /* + * We define POLLWRNORM to be POLLOUT, + * but epoll has separate definitions + * for them; if POLLOUT is set and the + * user has asked for EPOLLWRNORM, set + * that as well. + */ + if ((revent & POLLOUT) && + (pdp->pd_events & EPOLLWRNORM)) { + ep->events |= EPOLLWRNORM; + } + } + + /* + * If POLLET is set, clear the bit in the + * bitmap -- which effectively latches the + * edge on a pollwakeup() from the driver. + */ + if (pdp->pd_events & POLLET) + BT_CLEAR(pcp->pc_bitmap, fd); + + /* + * If POLLONESHOT is set, perform the implicit + * POLLREMOVE. + */ + if (pdp->pd_events & POLLONESHOT) { + pdp->pd_fp = NULL; + pdp->pd_events = 0; + + if (php != NULL) { + pollhead_delete(php, pdp); + pdp->pd_php = NULL; + } + + BT_CLEAR(pcp->pc_bitmap, fd); + } + fdcnt++; } else if (php != NULL) { /* @@ -392,7 +488,7 @@ repoll: * in bitmap. */ if ((pdp->pd_php != NULL) && - ((pcp->pc_flag & T_POLLWAKE) == 0)) { + ((pcp->pc_flag & PC_POLLWAKE) == 0)) { BT_CLEAR(pcp->pc_bitmap, fd); } if (pdp->pd_php == NULL) { @@ -473,11 +569,15 @@ dpopen(dev_t *devp, int flag, int otyp, cred_t *credp) /* * allocate a pollcache skeleton here. Delay allocating bitmap * structures until dpwrite() time, since we don't know the - * optimal size yet. + * optimal size yet. We also delay setting the pid until either + * dpwrite() or attempt to poll on the instance, allowing parents + * to create instances of /dev/poll for their children. (In the + * epoll compatibility case, this check isn't performed to maintain + * semantic compatibility.) */ pcp = pcache_alloc(); dpep->dpe_pcache = pcp; - pcp->pc_pid = curproc->p_pid; + pcp->pc_pid = -1; *devp = makedevice(getmajor(*devp), minordev); /* clone the driver */ mutex_enter(&devpoll_lock); ASSERT(minordev < dptblsize); @@ -499,7 +599,9 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) dp_entry_t *dpep; pollcache_t *pcp; pollfd_t *pollfdp, *pfdp; - int error; + dvpoll_epollfd_t *epfdp; + uintptr_t limit; + int error, size; ssize_t uiosize; nfds_t pollfdnum; struct pollhead *php = NULL; @@ -515,11 +617,23 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) ASSERT(dpep != NULL); mutex_exit(&devpoll_lock); pcp = dpep->dpe_pcache; - if (curproc->p_pid != pcp->pc_pid) { - return (EACCES); + + if (!(dpep->dpe_flag & DP_ISEPOLLCOMPAT) && + curproc->p_pid != pcp->pc_pid) { + if (pcp->pc_pid != -1) + return (EACCES); + + pcp->pc_pid = curproc->p_pid; } + + if (dpep->dpe_flag & DP_ISEPOLLCOMPAT) { + size = sizeof (dvpoll_epollfd_t); + } else { + size = sizeof (pollfd_t); + } + uiosize = uiop->uio_resid; - pollfdnum = uiosize / sizeof (pollfd_t); + pollfdnum = uiosize / size; mutex_enter(&curproc->p_lock); if (pollfdnum > (uint_t)rctl_enforced_value( rctlproc_legacy[RLIMIT_NOFILE], curproc->p_rctls, curproc)) { @@ -534,6 +648,7 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) * each polled fd to the cached set. */ pollfdp = kmem_alloc(uiosize, KM_SLEEP); + limit = (uintptr_t)pollfdp + (pollfdnum * size); /* * Although /dev/poll uses the write(2) interface to cache fds, it's @@ -555,9 +670,27 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) mutex_enter(&dpep->dpe_lock); dpep->dpe_writerwait++; while (dpep->dpe_refcnt != 0) { + /* + * We need to do a bit of a dance here: we need to drop + * our dpe_lock and grab the pc_lock to broadcast the pc_cv to + * kick any DP_POLL/DP_PPOLL sleepers. + */ + mutex_exit(&dpep->dpe_lock); + mutex_enter(&pcp->pc_lock); + pcp->pc_flag |= PC_WRITEWANTED; + cv_broadcast(&pcp->pc_cv); + mutex_exit(&pcp->pc_lock); + mutex_enter(&dpep->dpe_lock); + + if (dpep->dpe_refcnt == 0) + break; + if (!cv_wait_sig_swap(&dpep->dpe_cv, &dpep->dpe_lock)) { dpep->dpe_writerwait--; mutex_exit(&dpep->dpe_lock); + mutex_enter(&pcp->pc_lock); + pcp->pc_flag &= ~PC_WRITEWANTED; + mutex_exit(&pcp->pc_lock); kmem_free(pollfdp, uiosize); return (set_errno(EINTR)); } @@ -565,13 +698,17 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) dpep->dpe_writerwait--; dpep->dpe_flag |= DP_WRITER_PRESENT; dpep->dpe_refcnt++; + mutex_exit(&dpep->dpe_lock); mutex_enter(&pcp->pc_lock); + pcp->pc_flag &= ~PC_WRITEWANTED; + if (pcp->pc_bitmap == NULL) { pcache_create(pcp, pollfdnum); } - for (pfdp = pollfdp; pfdp < pollfdp + pollfdnum; pfdp++) { + for (pfdp = pollfdp; (uintptr_t)pfdp < limit; + pfdp = (pollfd_t *)((uintptr_t)pfdp + size)) { fd = pfdp->fd; if ((uint_t)fd >= P_FINFO(curproc)->fi_nfiles) continue; @@ -582,7 +719,24 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) pdp->pd_fd = fd; pdp->pd_pcache = pcp; pcache_insert_fd(pcp, pdp, pollfdnum); + } else { + if ((dpep->dpe_flag & DP_ISEPOLLCOMPAT) && + pdp->pd_fp != NULL) { + /* + * epoll semantics demand that we error + * out in this case. + */ + error = EEXIST; + break; + } + } + + if (dpep->dpe_flag & DP_ISEPOLLCOMPAT) { + /* LINTED pointer alignment */ + epfdp = (dvpoll_epollfd_t *)pfdp; + pdp->pd_epolldata = epfdp->dpep_data; } + ASSERT(pdp->pd_fd == fd); ASSERT(pdp->pd_pcache == pcp); if (fd >= pcp->pc_mapsize) { @@ -665,7 +819,17 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) } releasef(fd); } else { - if (pdp == NULL) { + if (pdp == NULL || pdp->pd_fp == NULL) { + if (dpep->dpe_flag & DP_ISEPOLLCOMPAT) { + /* + * As with the add case (above), epoll + * semantics demand that we error out + * in this case. + */ + error = ENOENT; + break; + } + continue; } ASSERT(pdp->pd_fd == fd); @@ -690,6 +854,17 @@ dpwrite(dev_t dev, struct uio *uiop, cred_t *credp) return (error); } +#define DP_SIGMASK_RESTORE(ksetp) { \ + if (ksetp != NULL) { \ + mutex_enter(&p->p_lock); \ + if (lwp->lwp_cursig == 0) { \ + t->t_hold = lwp->lwp_sigoldmask; \ + t->t_flag &= ~T_TOMASK; \ + } \ + mutex_exit(&p->p_lock); \ + } \ +} + /*ARGSUSED*/ static int dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) @@ -701,7 +876,7 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) int error = 0; STRUCT_DECL(dvpoll, dvpoll); - if (cmd == DP_POLL) { + if (cmd == DP_POLL || cmd == DP_PPOLL) { /* do this now, before we sleep on DP_WRITER_PRESENT */ now = gethrtime(); } @@ -713,10 +888,37 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) mutex_exit(&devpoll_lock); ASSERT(dpep != NULL); pcp = dpep->dpe_pcache; - if (curproc->p_pid != pcp->pc_pid) - return (EACCES); mutex_enter(&dpep->dpe_lock); + + if (cmd == DP_EPOLLCOMPAT) { + if (dpep->dpe_refcnt != 0) { + /* + * We can't turn on epoll compatibility while there + * are outstanding operations. + */ + mutex_exit(&dpep->dpe_lock); + return (EBUSY); + } + + /* + * epoll compatibility is a one-way street: there's no way + * to turn it off for a particular open. + */ + dpep->dpe_flag |= DP_ISEPOLLCOMPAT; + mutex_exit(&dpep->dpe_lock); + + return (0); + } + + if (!(dpep->dpe_flag & DP_ISEPOLLCOMPAT) && + curproc->p_pid != pcp->pc_pid) { + if (pcp->pc_pid != -1) + return (EACCES); + + pcp->pc_pid = curproc->p_pid; + } + while ((dpep->dpe_flag & DP_WRITER_PRESENT) || (dpep->dpe_writerwait != 0)) { if (!cv_wait_sig_swap(&dpep->dpe_cv, &dpep->dpe_lock)) { @@ -729,15 +931,36 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) switch (cmd) { case DP_POLL: + case DP_PPOLL: { pollstate_t *ps; nfds_t nfds; int fdcnt = 0; + size_t size, fdsize, dpsize; hrtime_t deadline = 0; + k_sigset_t *ksetp = NULL; + k_sigset_t kset; + sigset_t set; + kthread_t *t = curthread; + klwp_t *lwp = ttolwp(t); + struct proc *p = ttoproc(curthread); STRUCT_INIT(dvpoll, mode); - error = copyin((caddr_t)arg, STRUCT_BUF(dvpoll), - STRUCT_SIZE(dvpoll)); + + /* + * The dp_setp member is only required/consumed for DP_PPOLL, + * which otherwise uses the same structure as DP_POLL. + */ + if (cmd == DP_POLL) { + dpsize = (uintptr_t)STRUCT_FADDR(dvpoll, dp_setp) - + (uintptr_t)STRUCT_FADDR(dvpoll, dp_fds); + } else { + ASSERT(cmd == DP_PPOLL); + dpsize = STRUCT_SIZE(dvpoll); + } + + error = copyin((caddr_t)arg, STRUCT_BUF(dvpoll), dpsize); + if (error) { DP_REFRELE(dpep); return (EFAULT); @@ -755,6 +978,52 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) deadline += now; } + if (cmd == DP_PPOLL) { + void *setp = STRUCT_FGETP(dvpoll, dp_setp); + + if (setp != NULL) { + if (copyin(setp, &set, sizeof (set))) { + DP_REFRELE(dpep); + return (EFAULT); + } + + sigutok(&set, &kset); + ksetp = &kset; + + mutex_enter(&p->p_lock); + schedctl_finish_sigblock(t); + lwp->lwp_sigoldmask = t->t_hold; + t->t_hold = *ksetp; + t->t_flag |= T_TOMASK; + + /* + * Like ppoll() with a non-NULL sigset, we'll + * call cv_reltimedwait_sig() just to check for + * signals. This call will return immediately + * with either 0 (signalled) or -1 (no signal). + * There are some conditions whereby we can + * get 0 from cv_reltimedwait_sig() without + * a true signal (e.g., a directed stop), so + * we restore our signal mask in the unlikely + * event that lwp_cursig is 0. + */ + if (!cv_reltimedwait_sig(&t->t_delay_cv, + &p->p_lock, 0, TR_CLOCK_TICK)) { + if (lwp->lwp_cursig == 0) { + t->t_hold = lwp->lwp_sigoldmask; + t->t_flag &= ~T_TOMASK; + } + + mutex_exit(&p->p_lock); + + DP_REFRELE(dpep); + return (EINTR); + } + + mutex_exit(&p->p_lock); + } + } + if ((nfds = STRUCT_FGET(dvpoll, dp_nfds)) == 0) { /* * We are just using DP_POLL to sleep, so @@ -762,17 +1031,29 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) * Do not check for signals if we have a zero timeout. */ DP_REFRELE(dpep); - if (deadline == 0) + if (deadline == 0) { + DP_SIGMASK_RESTORE(ksetp); return (0); + } + mutex_enter(&curthread->t_delay_lock); while ((error = cv_timedwait_sig_hrtime(&curthread->t_delay_cv, &curthread->t_delay_lock, deadline)) > 0) continue; mutex_exit(&curthread->t_delay_lock); + + DP_SIGMASK_RESTORE(ksetp); + return (error == 0 ? EINTR : 0); } + if (dpep->dpe_flag & DP_ISEPOLLCOMPAT) { + size = nfds * (fdsize = sizeof (epoll_event_t)); + } else { + size = nfds * (fdsize = sizeof (pollfd_t)); + } + /* * XXX It would be nice not to have to alloc each time, but it * requires another per thread structure hook. This can be @@ -782,37 +1063,45 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) curthread->t_pollstate = pollstate_create(); ps = curthread->t_pollstate; } - if (ps->ps_dpbufsize < nfds) { - struct proc *p = ttoproc(curthread); + + if (ps->ps_dpbufsize < size) { /* - * The maximum size should be no large than - * current maximum open file count. + * If nfds is larger than twice the current maximum + * open file count, we'll silently clamp it. This + * only limits our exposure to allocating an + * inordinate amount of kernel memory; it doesn't + * otherwise affect the semantics. (We have this + * check at twice the maximum instead of merely the + * maximum because some applications pass an nfds that + * is only slightly larger than their limit.) */ mutex_enter(&p->p_lock); - if (nfds > p->p_fno_ctl) { - mutex_exit(&p->p_lock); - DP_REFRELE(dpep); - return (EINVAL); + if ((nfds >> 1) > p->p_fno_ctl) { + nfds = p->p_fno_ctl; + size = nfds * fdsize; } mutex_exit(&p->p_lock); - kmem_free(ps->ps_dpbuf, sizeof (pollfd_t) * - ps->ps_dpbufsize); - ps->ps_dpbuf = kmem_zalloc(sizeof (pollfd_t) * - nfds, KM_SLEEP); - ps->ps_dpbufsize = nfds; + + if (ps->ps_dpbufsize < size) { + kmem_free(ps->ps_dpbuf, ps->ps_dpbufsize); + ps->ps_dpbuf = kmem_zalloc(size, KM_SLEEP); + ps->ps_dpbufsize = size; + } } mutex_enter(&pcp->pc_lock); for (;;) { - pcp->pc_flag = 0; - error = dp_pcache_poll(ps->ps_dpbuf, pcp, nfds, &fdcnt); + pcp->pc_flag &= ~PC_POLLWAKE; + + error = dp_pcache_poll(dpep, ps->ps_dpbuf, + pcp, nfds, &fdcnt); if (fdcnt > 0 || error != 0) break; /* * A pollwake has happened since we polled cache. */ - if (pcp->pc_flag & T_POLLWAKE) + if (pcp->pc_flag & PC_POLLWAKE) continue; /* @@ -822,8 +1111,40 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) /* immediate timeout; do not check signals */ break; } - error = cv_timedwait_sig_hrtime(&pcp->pc_cv, - &pcp->pc_lock, deadline); + + if (!(pcp->pc_flag & PC_WRITEWANTED)) { + error = cv_timedwait_sig_hrtime(&pcp->pc_cv, + &pcp->pc_lock, deadline); + } else { + error = 1; + } + + if (error > 0 && (pcp->pc_flag & PC_WRITEWANTED)) { + /* + * We've been kicked off of our cv because a + * writer wants in. We're going to drop our + * reference count and then wait until the + * writer is gone -- at which point we'll + * reacquire the pc_lock and call into + * dp_pcache_poll() to get the updated state. + */ + mutex_exit(&pcp->pc_lock); + + mutex_enter(&dpep->dpe_lock); + dpep->dpe_refcnt--; + cv_broadcast(&dpep->dpe_cv); + + while ((dpep->dpe_flag & DP_WRITER_PRESENT) || + (dpep->dpe_writerwait != 0)) { + error = cv_wait_sig_swap(&dpep->dpe_cv, + &dpep->dpe_lock); + } + + dpep->dpe_refcnt++; + mutex_exit(&dpep->dpe_lock); + mutex_enter(&pcp->pc_lock); + } + /* * If we were awakened by a signal or timeout * then break the loop, else poll again. @@ -837,9 +1158,11 @@ dpioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) } mutex_exit(&pcp->pc_lock); + DP_SIGMASK_RESTORE(ksetp); + if (error == 0 && fdcnt > 0) { - if (copyout(ps->ps_dpbuf, STRUCT_FGETP(dvpoll, - dp_fds), sizeof (pollfd_t) * fdcnt)) { + if (copyout(ps->ps_dpbuf, + STRUCT_FGETP(dvpoll, dp_fds), fdcnt * fdsize)) { DP_REFRELE(dpep); return (EFAULT); } @@ -901,10 +1224,25 @@ static int dppoll(dev_t dev, short events, int anyyet, short *reventsp, struct pollhead **phpp) { + minor_t minor; + dp_entry_t *dpep; + + minor = getminor(dev); + + mutex_enter(&devpoll_lock); + dpep = devpolltbl[minor]; + ASSERT(dpep != NULL); + mutex_exit(&devpoll_lock); + /* * Polling on a /dev/poll fd is not fully supported yet. */ - *reventsp = POLLERR; + if (dpep->dpe_flag & DP_ISEPOLLCOMPAT) { + /* no error in epoll compat. mode */ + *reventsp = 0; + } else { + *reventsp = POLLERR; + } return (0); } diff --git a/usr/src/uts/common/io/dld/dld_drv.c b/usr/src/uts/common/io/dld/dld_drv.c index 40cbe86170..2152ce0baa 100644 --- a/usr/src/uts/common/io/dld/dld_drv.c +++ b/usr/src/uts/common/io/dld/dld_drv.c @@ -20,6 +20,7 @@ */ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, Joyent Inc. All rights reserved. */ /* @@ -701,7 +702,8 @@ drv_ioc_prop_common(dld_ioc_macprop_t *prop, intptr_t arg, boolean_t set, err = EACCES; goto done; } - err = dls_devnet_setzid(dlh, dzp->diz_zid); + err = dls_devnet_setzid(dlh, dzp->diz_zid, + dzp->diz_transient); } else { kprop->pr_perm_flags = MAC_PROP_PERM_RW; (*(zoneid_t *)kprop->pr_val) = dls_devnet_getzid(dlh); @@ -865,7 +867,7 @@ drv_ioc_rename(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) return (err); if ((err = dls_devnet_rename(dir->dir_linkid1, dir->dir_linkid2, - dir->dir_link)) != 0) + dir->dir_link, dir->dir_zoneinit)) != 0) return (err); if (dir->dir_linkid2 == DATALINK_INVALID_LINKID) diff --git a/usr/src/uts/common/io/dld/dld_proto.c b/usr/src/uts/common/io/dld/dld_proto.c index a438e43d91..79c3d8260a 100644 --- a/usr/src/uts/common/io/dld/dld_proto.c +++ b/usr/src/uts/common/io/dld/dld_proto.c @@ -41,7 +41,7 @@ static proto_reqfunc_t proto_info_req, proto_attach_req, proto_detach_req, proto_bind_req, proto_unbind_req, proto_promiscon_req, proto_promiscoff_req, proto_enabmulti_req, proto_disabmulti_req, proto_physaddr_req, proto_setphysaddr_req, proto_udqos_req, proto_req, proto_capability_req, - proto_notify_req, proto_passive_req; + proto_notify_req, proto_passive_req, proto_exclusive_req; static void proto_capability_advertise(dld_str_t *, mblk_t *); static int dld_capab_poll_disable(dld_str_t *, dld_capab_poll_t *); @@ -121,6 +121,9 @@ dld_proto(dld_str_t *dsp, mblk_t *mp) case DL_PASSIVE_REQ: proto_passive_req(dsp, mp); break; + case DL_EXCLUSIVE_REQ: + proto_exclusive_req(dsp, mp); + break; default: proto_req(dsp, mp); break; @@ -605,6 +608,10 @@ proto_promiscon_req(dld_str_t *dsp, mblk_t *mp) new_flags |= DLS_PROMISC_PHYS; break; + case DL_PROMISC_RX_ONLY: + new_flags |= DLS_PROMISC_RX_ONLY; + break; + default: dl_err = DL_NOTSUPPORTED; goto failed2; @@ -692,12 +699,24 @@ proto_promiscoff_req(dld_str_t *dsp, mblk_t *mp) new_flags &= ~DLS_PROMISC_PHYS; break; + case DL_PROMISC_RX_ONLY: + if (!(dsp->ds_promisc & DLS_PROMISC_RX_ONLY)) { + dl_err = DL_NOTENAB; + goto failed; + } + new_flags &= ~DLS_PROMISC_RX_ONLY; + break; + default: dl_err = DL_NOTSUPPORTED; mac_perim_exit(mph); goto failed; } + /* DLS_PROMISC_RX_ONLY can't be a solo flag */ + if (new_flags == DLS_PROMISC_RX_ONLY) + new_flags = 0; + /* * Adjust channel promiscuity. */ @@ -1295,7 +1314,8 @@ proto_passive_req(dld_str_t *dsp, mblk_t *mp) * If we've already become active by issuing an active primitive, * then it's too late to try to become passive. */ - if (dsp->ds_passivestate == DLD_ACTIVE) { + if (dsp->ds_passivestate == DLD_ACTIVE || + dsp->ds_passivestate == DLD_EXCLUSIVE) { dl_err = DL_OUTSTATE; goto failed; } @@ -1354,7 +1374,12 @@ dld_capab_direct(dld_str_t *dsp, void *data, uint_t flags) dls_rx_set(dsp, (dls_rx_t)direct->di_rx_cf, direct->di_rx_ch); - direct->di_tx_df = (uintptr_t)str_mdata_fastpath_put; + if (direct->di_flags & DI_DIRECT_RAW) { + direct->di_tx_df = + (uintptr_t)str_mdata_raw_fastpath_put; + } else { + direct->di_tx_df = (uintptr_t)str_mdata_fastpath_put; + } direct->di_tx_dh = dsp; direct->di_tx_cb_df = (uintptr_t)mac_client_tx_notify; direct->di_tx_cb_dh = dsp->ds_mch; @@ -1516,8 +1541,9 @@ dld_capab(dld_str_t *dsp, uint_t type, void *data, uint_t flags) * completes. So we limit the check to DLD_ENABLE case. */ if ((flags == DLD_ENABLE && type != DLD_CAPAB_PERIM) && - (dsp->ds_sap != ETHERTYPE_IP || - !check_mod_above(dsp->ds_rq, "ip"))) { + ((dsp->ds_sap != ETHERTYPE_IP || + !check_mod_above(dsp->ds_rq, "ip")) && + !check_mod_above(dsp->ds_rq, "vnd"))) { return (ENOTSUP); } @@ -1599,9 +1625,15 @@ proto_capability_advertise(dld_str_t *dsp, mblk_t *mp) } /* - * Direct capability negotiation interface between IP and DLD + * Direct capability negotiation interface between IP/VND and DLD. Note + * that for vnd we only allow the case where the media type is the + * native media type so we know that there are no transformations that + * would have to happen to the mac header that it receives. */ - if (dsp->ds_sap == ETHERTYPE_IP && check_mod_above(dsp->ds_rq, "ip")) { + if ((dsp->ds_sap == ETHERTYPE_IP && + check_mod_above(dsp->ds_rq, "ip")) || + (check_mod_above(dsp->ds_rq, "vnd") && + dsp->ds_mip->mi_media == dsp->ds_mip->mi_nativemedia)) { dld_capable = B_TRUE; subsize += sizeof (dl_capability_sub_t) + sizeof (dl_capab_dld_t); @@ -1720,3 +1752,36 @@ dld_capabilities_disable(dld_str_t *dsp) if (dsp->ds_polling) (void) dld_capab_poll_disable(dsp, NULL); } + +static void +proto_exclusive_req(dld_str_t *dsp, mblk_t *mp) +{ + int ret = 0; + t_uscalar_t dl_err; + mac_perim_handle_t mph; + + if (dsp->ds_passivestate != DLD_UNINITIALIZED) { + dl_err = DL_OUTSTATE; + goto failed; + } + + if (MBLKL(mp) < DL_EXCLUSIVE_REQ_SIZE) { + dl_err = DL_BADPRIM; + goto failed; + } + + mac_perim_enter_by_mh(dsp->ds_mh, &mph); + ret = dls_exclusive_set(dsp, B_TRUE); + mac_perim_exit(mph); + + if (ret != 0) { + dl_err = DL_SYSERR; + goto failed; + } + + dsp->ds_passivestate = DLD_EXCLUSIVE; + dlokack(dsp->ds_wq, mp, DL_EXCLUSIVE_REQ); + return; +failed: + dlerrorack(dsp->ds_wq, mp, DL_EXCLUSIVE_REQ, dl_err, (t_uscalar_t)ret); +} diff --git a/usr/src/uts/common/io/dld/dld_str.c b/usr/src/uts/common/io/dld/dld_str.c index 6f0d0b9a6c..f5308e70ff 100644 --- a/usr/src/uts/common/io/dld/dld_str.c +++ b/usr/src/uts/common/io/dld/dld_str.c @@ -854,6 +854,77 @@ i_dld_ether_header_update_tag(mblk_t *mp, uint_t pri, uint16_t vid, return (mp); } +static boolean_t +i_dld_raw_ether_check(dld_str_t *dsp, mac_header_info_t *mhip, mblk_t **mpp) +{ + mblk_t *mp = *mpp; + mblk_t *newmp; + uint_t pri, vid, dvid; + + dvid = mac_client_vid(dsp->ds_mch); + + /* + * Discard the packet if this is a VLAN stream but the VID in + * the packet is not correct. + */ + vid = VLAN_ID(mhip->mhi_tci); + if ((dvid != VLAN_ID_NONE) && (vid != VLAN_ID_NONE)) + return (B_FALSE); + + /* + * Discard the packet if this packet is a tagged packet + * but both pri and VID are 0. + */ + pri = VLAN_PRI(mhip->mhi_tci); + if (mhip->mhi_istagged && !mhip->mhi_ispvid && pri == 0 && + vid == VLAN_ID_NONE) + return (B_FALSE); + + /* + * Update the priority bits to the per-stream priority if + * priority is not set in the packet. Update the VID for + * packets on a VLAN stream. + */ + pri = (pri == 0) ? dsp->ds_pri : 0; + if ((pri != 0) || (dvid != VLAN_ID_NONE)) { + if ((newmp = i_dld_ether_header_update_tag(mp, pri, + dvid, dsp->ds_dlp->dl_tagmode)) == NULL) { + return (B_FALSE); + } + *mpp = newmp; + } + + return (B_TRUE); +} + +mac_tx_cookie_t +str_mdata_raw_fastpath_put(dld_str_t *dsp, mblk_t *mp, uintptr_t f_hint, + uint16_t flag) +{ + boolean_t is_ethernet = (dsp->ds_mip->mi_media == DL_ETHER); + mac_header_info_t mhi; + mac_tx_cookie_t cookie; + + if (mac_vlan_header_info(dsp->ds_mh, mp, &mhi) != 0) + goto discard; + + if (is_ethernet) { + if (i_dld_raw_ether_check(dsp, &mhi, &mp) == B_FALSE) + goto discard; + } + + if ((cookie = DLD_TX(dsp, mp, f_hint, flag)) != NULL) { + DLD_SETQFULL(dsp); + } + return (cookie); +discard: + /* TODO: bump kstat? */ + freemsg(mp); + return (NULL); +} + + + /* * M_DATA put (IP fast-path mode) */ @@ -902,7 +973,6 @@ str_mdata_raw_put(dld_str_t *dsp, mblk_t *mp) mblk_t *bp, *newmp; size_t size; mac_header_info_t mhi; - uint_t pri, vid, dvid; uint_t max_sdu; /* @@ -948,38 +1018,8 @@ str_mdata_raw_put(dld_str_t *dsp, mblk_t *mp) goto discard; if (is_ethernet) { - dvid = mac_client_vid(dsp->ds_mch); - - /* - * Discard the packet if this is a VLAN stream but the VID in - * the packet is not correct. - */ - vid = VLAN_ID(mhi.mhi_tci); - if ((dvid != VLAN_ID_NONE) && (vid != VLAN_ID_NONE)) - goto discard; - - /* - * Discard the packet if this packet is a tagged packet - * but both pri and VID are 0. - */ - pri = VLAN_PRI(mhi.mhi_tci); - if (mhi.mhi_istagged && !mhi.mhi_ispvid && pri == 0 && - vid == VLAN_ID_NONE) + if (i_dld_raw_ether_check(dsp, &mhi, &mp) == B_FALSE) goto discard; - - /* - * Update the priority bits to the per-stream priority if - * priority is not set in the packet. Update the VID for - * packets on a VLAN stream. - */ - pri = (pri == 0) ? dsp->ds_pri : 0; - if ((pri != 0) || (dvid != VLAN_ID_NONE)) { - if ((newmp = i_dld_ether_header_update_tag(mp, pri, - dvid, dsp->ds_dlp->dl_tagmode)) == NULL) { - goto discard; - } - mp = newmp; - } } if (DLD_TX(dsp, mp, 0, 0) != NULL) { diff --git a/usr/src/uts/common/io/dls/dls.c b/usr/src/uts/common/io/dls/dls.c index 92993ada58..9fa649943c 100644 --- a/usr/src/uts/common/io/dls/dls.c +++ b/usr/src/uts/common/io/dls/dls.c @@ -25,7 +25,7 @@ */ /* - * Copyright (c) 2013 Joyent, Inc. All rights reserved. + * Copyright (c) 2014 Joyent, Inc. All rights reserved. */ /* @@ -248,19 +248,44 @@ dls_promisc(dld_str_t *dsp, uint32_t new_flags) { int err = 0; uint32_t old_flags = dsp->ds_promisc; + uint32_t new_type = new_flags & ~DLS_PROMISC_RX_ONLY; mac_client_promisc_type_t mptype = MAC_CLIENT_PROMISC_ALL; + uint16_t mac_flags = 0; ASSERT(MAC_PERIM_HELD(dsp->ds_mh)); ASSERT(!(new_flags & ~(DLS_PROMISC_SAP | DLS_PROMISC_MULTI | - DLS_PROMISC_PHYS))); + DLS_PROMISC_PHYS | DLS_PROMISC_RX_ONLY))); + + /* + * Asking us just to turn on DLS_PROMISC_RX_ONLY is not valid. + */ + if (new_flags == DLS_PROMISC_RX_ONLY) + return (EINVAL); /* * If the user has only requested DLS_PROMISC_MULTI then we need to make * sure that they don't see all packets. */ - if (new_flags == DLS_PROMISC_MULTI) + if (new_type == DLS_PROMISC_MULTI) mptype = MAC_CLIENT_PROMISC_MULTI; + /* + * Look at new flags and figure out the correct mac promisc flags. + * If we've only requested DLS_PROMISC_SAP and not _MULTI or _PHYS, + * don't turn on physical promisc mode. + */ + if (new_flags & DLS_PROMISC_RX_ONLY) + mac_flags |= MAC_PROMISC_FLAGS_NO_TX_LOOP; + if (new_type == DLS_PROMISC_SAP) + mac_flags |= MAC_PROMISC_FLAGS_NO_PHYS; + + /* + * There are three cases we care about here with respect to MAC. Going + * from nothing to something, something to nothing, something to + * something where we need to change how we're getting stuff from mac. + * In the last case, as long as they're not equal, we need to assume + * something has changed and do something about it. + */ if (dsp->ds_promisc == 0 && new_flags != 0) { /* * If only DLS_PROMISC_SAP, we don't turn on the @@ -268,9 +293,7 @@ dls_promisc(dld_str_t *dsp, uint32_t new_flags) */ dsp->ds_promisc = new_flags; err = mac_promisc_add(dsp->ds_mch, mptype, - dls_rx_promisc, dsp, &dsp->ds_mph, - (new_flags != DLS_PROMISC_SAP) ? 0 : - MAC_PROMISC_FLAGS_NO_PHYS); + dls_rx_promisc, dsp, &dsp->ds_mph, mac_flags); if (err != 0) { dsp->ds_promisc = old_flags; return (err); @@ -296,19 +319,13 @@ dls_promisc(dld_str_t *dsp, uint32_t new_flags) MAC_CLIENT_PROMISC_ALL, dls_rx_vlan_promisc, dsp, &dsp->ds_vlan_mph, MAC_PROMISC_FLAGS_NO_PHYS); } - } else if (dsp->ds_promisc == DLS_PROMISC_SAP && new_flags != 0 && - new_flags != dsp->ds_promisc) { - /* - * If the old flag is PROMISC_SAP, but the current flag has - * changed to some new non-zero value, we need to turn the - * physical promiscuous mode. - */ + } else if (new_flags != 0 && new_flags != old_flags) { ASSERT(dsp->ds_mph != NULL); mac_promisc_remove(dsp->ds_mph); /* Honors both after-remove and before-add semantics! */ dsp->ds_promisc = new_flags; err = mac_promisc_add(dsp->ds_mch, mptype, - dls_rx_promisc, dsp, &dsp->ds_mph, 0); + dls_rx_promisc, dsp, &dsp->ds_mph, mac_flags); if (err != 0) dsp->ds_promisc = old_flags; } else { @@ -629,6 +646,22 @@ boolean_t dls_accept_promisc(dld_str_t *dsp, mac_header_info_t *mhip, dls_rx_t *ds_rx, void **ds_rx_arg, boolean_t loopback) { + if (dsp->ds_promisc == 0) { + /* + * If there are active walkers of the mi_promisc_list when + * promiscuousness is disabled, ds_promisc will be cleared, + * but the DLS will remain on the mi_promisc_list until the + * walk is completed. If we do not recognize this case here, + * we won't properly execute the ds_promisc case in the common + * accept routine -- and we will potentially accept a packet + * that has originated with this DLS (which in turn can + * induce recursion and death by stack overflow). If + * ds_promisc is zero, we know that we are in this window -- + * and we refuse to accept the packet. + */ + return (B_FALSE); + } + return (dls_accept_common(dsp, mhip, ds_rx, ds_rx_arg, B_TRUE, loopback)); } @@ -659,7 +692,10 @@ dls_mac_active_set(dls_link_t *dlp) * Set the function to start receiving packets. */ mac_rx_set(dlp->dl_mch, i_dls_link_rx, dlp); + } else if (dlp->dl_exclusive == B_TRUE) { + return (EBUSY); } + dlp->dl_nactive++; return (0); } @@ -685,7 +721,11 @@ dls_active_set(dld_str_t *dsp) if (dsp->ds_passivestate == DLD_PASSIVE) return (0); - /* If we're already active, then there's nothing more to do. */ + if (dsp->ds_dlp->dl_exclusive == B_TRUE && + dsp->ds_passivestate != DLD_EXCLUSIVE) + return (EBUSY); + + /* If we're already active, we need to check the link's exclusivity */ if ((dsp->ds_nactive == 0) && ((err = dls_mac_active_set(dsp->ds_dlp)) != 0)) { /* except for ENXIO all other errors are mapped to EBUSY */ @@ -694,7 +734,8 @@ dls_active_set(dld_str_t *dsp) return (err); } - dsp->ds_passivestate = DLD_ACTIVE; + dsp->ds_passivestate = dsp->ds_dlp->dl_exclusive == B_TRUE ? + DLD_EXCLUSIVE : DLD_ACTIVE; dsp->ds_nactive++; return (0); } @@ -725,7 +766,32 @@ dls_active_clear(dld_str_t *dsp, boolean_t all) if (dsp->ds_nactive != 0) return; - ASSERT(dsp->ds_passivestate == DLD_ACTIVE); + ASSERT(dsp->ds_passivestate == DLD_ACTIVE || + dsp->ds_passivestate == DLD_EXCLUSIVE); dls_mac_active_clear(dsp->ds_dlp); + /* + * We verify below to ensure that no other part of DLS has mucked with + * our exclusive state. + */ + if (dsp->ds_passivestate == DLD_EXCLUSIVE) + VERIFY(dls_exclusive_set(dsp, B_FALSE) == 0); dsp->ds_passivestate = DLD_UNINITIALIZED; } + +int +dls_exclusive_set(dld_str_t *dsp, boolean_t enable) +{ + ASSERT(MAC_PERIM_HELD(dsp->ds_mh)); + + if (enable == B_FALSE) { + dsp->ds_dlp->dl_exclusive = B_FALSE; + return (0); + } + + if (dsp->ds_dlp->dl_nactive != 0) + return (EBUSY); + + dsp->ds_dlp->dl_exclusive = B_TRUE; + + return (0); +} diff --git a/usr/src/uts/common/io/dls/dls_link.c b/usr/src/uts/common/io/dls/dls_link.c index 6b92a81e77..c5363e8194 100644 --- a/usr/src/uts/common/io/dls/dls_link.c +++ b/usr/src/uts/common/io/dls/dls_link.c @@ -602,6 +602,7 @@ i_dls_link_destroy(dls_link_t *dlp) dlp->dl_mip = NULL; dlp->dl_unknowns = 0; dlp->dl_nonip_cnt = 0; + dlp->dl_exclusive = B_FALSE; kmem_cache_free(i_dls_link_cachep, dlp); } diff --git a/usr/src/uts/common/io/dls/dls_mgmt.c b/usr/src/uts/common/io/dls/dls_mgmt.c index 049c4bd757..6111d62475 100644 --- a/usr/src/uts/common/io/dls/dls_mgmt.c +++ b/usr/src/uts/common/io/dls/dls_mgmt.c @@ -21,6 +21,7 @@ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright (c) 2013 Joyent, Inc. All rights reserved. */ /* @@ -105,12 +106,13 @@ typedef struct dls_devnet_s { zoneid_t dd_zid; /* current zone */ boolean_t dd_prop_loaded; taskqid_t dd_prop_taskid; + boolean_t dd_transient; /* link goes away when zone does */ } dls_devnet_t; static int i_dls_devnet_create_iptun(const char *, const char *, datalink_id_t *); static int i_dls_devnet_destroy_iptun(datalink_id_t); -static int i_dls_devnet_setzid(dls_devnet_t *, zoneid_t, boolean_t); +static int i_dls_devnet_setzid(dls_devnet_t *, zoneid_t, boolean_t, boolean_t); static int dls_devnet_unset(const char *, datalink_id_t *, boolean_t); /*ARGSUSED*/ @@ -145,7 +147,12 @@ dls_zone_remove(datalink_id_t linkid, void *arg) dls_devnet_t *ddp; if (dls_devnet_hold_tmp(linkid, &ddp) == 0) { - (void) dls_devnet_setzid(ddp, GLOBAL_ZONEID); + /* + * Don't bother moving transient links back to the global zone + * since we will simply delete them in dls_devnet_unset. + */ + if (!ddp->dd_transient) + (void) dls_devnet_setzid(ddp, GLOBAL_ZONEID, B_FALSE); dls_devnet_rele_tmp(ddp); } return (0); @@ -526,6 +533,27 @@ dls_mgmt_get_linkid(const char *link, datalink_id_t *linkid) getlinkid.ld_cmd = DLMGMT_CMD_GETLINKID; (void) strlcpy(getlinkid.ld_link, link, MAXLINKNAMELEN); + getlinkid.ld_zoneid = getzoneid(); + + if ((err = i_dls_mgmt_upcall(&getlinkid, sizeof (getlinkid), &retval, + sizeof (retval))) == 0) { + *linkid = retval.lr_linkid; + } + return (err); +} + +int +dls_mgmt_get_linkid_in_zone(const char *link, datalink_id_t *linkid, + zoneid_t zid) +{ + dlmgmt_door_getlinkid_t getlinkid; + dlmgmt_getlinkid_retval_t retval; + int err; + + ASSERT(getzoneid() == GLOBAL_ZONEID || zid == getzoneid()); + getlinkid.ld_cmd = DLMGMT_CMD_GETLINKID; + (void) strlcpy(getlinkid.ld_link, link, MAXLINKNAMELEN); + getlinkid.ld_zoneid = zid; if ((err = i_dls_mgmt_upcall(&getlinkid, sizeof (getlinkid), &retval, sizeof (retval))) == 0) { @@ -534,6 +562,7 @@ dls_mgmt_get_linkid(const char *link, datalink_id_t *linkid) return (err); } + datalink_id_t dls_mgmt_get_next(datalink_id_t linkid, datalink_class_t class, datalink_media_t dmedia, uint32_t flags) @@ -740,12 +769,23 @@ dls_devnet_stat_update(kstat_t *ksp, int rw) * Create the "link" kstats. */ static void -dls_devnet_stat_create(dls_devnet_t *ddp, zoneid_t zoneid) +dls_devnet_stat_create(dls_devnet_t *ddp, zoneid_t zoneid, zoneid_t newzoneid) { kstat_t *ksp; + char *nm; + char kname[MAXLINKNAMELEN]; + + if (zoneid != newzoneid) { + ASSERT(zoneid == GLOBAL_ZONEID); + (void) snprintf(kname, sizeof (kname), "z%d_%s", newzoneid, + ddp->dd_linkname); + nm = kname; + } else { + nm = ddp->dd_linkname; + } - if (dls_stat_create("link", 0, ddp->dd_linkname, zoneid, - dls_devnet_stat_update, ddp, &ksp) == 0) { + if (dls_stat_create("link", 0, nm, zoneid, + dls_devnet_stat_update, ddp, &ksp, newzoneid) == 0) { ASSERT(ksp != NULL); if (zoneid == ddp->dd_owner_zid) { ASSERT(ddp->dd_ksp == NULL); @@ -765,12 +805,12 @@ dls_devnet_stat_destroy(dls_devnet_t *ddp, zoneid_t zoneid) { if (zoneid == ddp->dd_owner_zid) { if (ddp->dd_ksp != NULL) { - kstat_delete(ddp->dd_ksp); + dls_stat_delete(ddp->dd_ksp); ddp->dd_ksp = NULL; } } else { if (ddp->dd_zone_ksp != NULL) { - kstat_delete(ddp->dd_zone_ksp); + dls_stat_delete(ddp->dd_zone_ksp); ddp->dd_zone_ksp = NULL; } } @@ -781,15 +821,25 @@ dls_devnet_stat_destroy(dls_devnet_t *ddp, zoneid_t zoneid) * and create the new set using the new name. */ static void -dls_devnet_stat_rename(dls_devnet_t *ddp) +dls_devnet_stat_rename(dls_devnet_t *ddp, boolean_t zoneinit) { if (ddp->dd_ksp != NULL) { - kstat_delete(ddp->dd_ksp); + dls_stat_delete(ddp->dd_ksp); ddp->dd_ksp = NULL; } - /* We can't rename a link while it's assigned to a non-global zone. */ + if (zoneinit && ddp->dd_zone_ksp != NULL) { + dls_stat_delete(ddp->dd_zone_ksp); + ddp->dd_zone_ksp = NULL; + } + /* + * We can't rename a link while it's assigned to a non-global zone + * unless we're first initializing the zone while readying it. + */ ASSERT(ddp->dd_zone_ksp == NULL); - dls_devnet_stat_create(ddp, ddp->dd_owner_zid); + dls_devnet_stat_create(ddp, ddp->dd_owner_zid, + (zoneinit ? ddp->dd_zid : ddp->dd_owner_zid)); + if (zoneinit) + dls_devnet_stat_create(ddp, ddp->dd_zid, ddp->dd_zid); } /* @@ -878,7 +928,8 @@ done: rw_exit(&i_dls_devnet_lock); if (err == 0) { if (zoneid != GLOBAL_ZONEID && - (err = i_dls_devnet_setzid(ddp, zoneid, B_FALSE)) != 0) + (err = i_dls_devnet_setzid(ddp, zoneid, B_FALSE, + B_FALSE)) != 0) (void) dls_devnet_unset(macname, &linkid, B_TRUE); /* * The kstat subsystem holds its own locks (rather perimeter) @@ -887,7 +938,7 @@ done: * lock hierarchy is kstat locks -> i_dls_devnet_lock. */ if (stat_create) - dls_devnet_stat_create(ddp, zoneid); + dls_devnet_stat_create(ddp, zoneid, zoneid); if (ddpp != NULL) *ddpp = ddp; } @@ -924,17 +975,78 @@ dls_devnet_unset(const char *macname, datalink_id_t *id, boolean_t wait) ASSERT(ddp->dd_ref != 0); if ((ddp->dd_ref != 1) || (!wait && (ddp->dd_tref != 0 || ddp->dd_prop_taskid != NULL))) { - mutex_exit(&ddp->dd_mutex); - rw_exit(&i_dls_devnet_lock); - return (EBUSY); + int zstatus = 0; + + /* + * There are a couple of alternatives that might be going on + * here; a) the zone is shutting down and it has a transient + * link assigned, in which case we want to clean it up instead + * of moving it back to the global zone, or b) its possible + * that we're trying to clean up an orphaned vnic that was + * delegated to a zone and which wasn't cleaned up properly + * when the zone went away. Check for either of these cases + * before we simply return EBUSY. + * + * zstatus indicates which situation we are dealing with: + * 0 - means return EBUSY + * 1 - means case (a), cleanup transient link + * -1 - means case (b), orphained VNIC + */ + if (ddp->dd_ref > 1 && ddp->dd_zid != GLOBAL_ZONEID) { + zone_t *zp; + + if ((zp = zone_find_by_id(ddp->dd_zid)) == NULL) { + zstatus = -1; + } else { + if (ddp->dd_transient) { + zone_status_t s = zone_status_get(zp); + + if (s >= ZONE_IS_SHUTTING_DOWN) + zstatus = 1; + } + zone_rele(zp); + } + } + + if (zstatus == 0) { + mutex_exit(&ddp->dd_mutex); + rw_exit(&i_dls_devnet_lock); + return (EBUSY); + } + + /* + * We want to delete the link, reset ref to 1; + */ + if (zstatus == -1) + /* Log a warning, but continue in this case */ + cmn_err(CE_WARN, "clear orphaned datalink: %s\n", + ddp->dd_linkname); + ddp->dd_ref = 1; } ddp->dd_flags |= DD_CONDEMNED; ddp->dd_ref--; *id = ddp->dd_linkid; - if (ddp->dd_zid != GLOBAL_ZONEID) - (void) i_dls_devnet_setzid(ddp, GLOBAL_ZONEID, B_FALSE); + if (ddp->dd_zid != GLOBAL_ZONEID) { + /* + * We need to release the dd_mutex before we try and destroy the + * stat. When we destroy it, we'll need to grab the lock for the + * kstat but if there's a concurrent reader of the kstat, we'll + * be blocked on it. This will lead to deadlock because these + * kstats employ a ks_update function (dls_devnet_stat_update) + * which needs the dd_mutex that we currently hold. + * + * Because we've already flagged the dls_devnet_t as + * DD_CONDEMNED and we still have a write lock on + * i_dls_devnet_lock, we should be able to release the dd_mutex. + */ + mutex_exit(&ddp->dd_mutex); + dls_devnet_stat_destroy(ddp, ddp->dd_zid); + mutex_enter(&ddp->dd_mutex); + (void) i_dls_devnet_setzid(ddp, GLOBAL_ZONEID, B_FALSE, + B_FALSE); + } /* * Remove this dls_devnet_t from the hash table. @@ -960,8 +1072,15 @@ dls_devnet_unset(const char *macname, datalink_id_t *id, boolean_t wait) ASSERT(ddp->dd_tref == 0 && ddp->dd_prop_taskid == NULL); } - if (ddp->dd_linkid != DATALINK_INVALID_LINKID) + if (ddp->dd_linkid != DATALINK_INVALID_LINKID) { + /* + * See the earlier call in this function for an explanation. + */ + mutex_exit(&ddp->dd_mutex); dls_devnet_stat_destroy(ddp, ddp->dd_owner_zid); + mutex_enter(&ddp->dd_mutex); + } + ddp->dd_prop_loaded = B_FALSE; ddp->dd_linkid = DATALINK_INVALID_LINKID; @@ -1111,7 +1230,7 @@ dls_devnet_rele(dls_devnet_t *ddp) } static int -dls_devnet_hold_by_name(const char *link, dls_devnet_t **ddpp) +dls_devnet_hold_by_name(const char *link, dls_devnet_t **ddpp, zoneid_t zid) { char drv[MAXLINKNAMELEN]; uint_t ppa; @@ -1121,7 +1240,7 @@ dls_devnet_hold_by_name(const char *link, dls_devnet_t **ddpp) dls_dev_handle_t ddh; int err; - if ((err = dls_mgmt_get_linkid(link, &linkid)) == 0) + if ((err = dls_mgmt_get_linkid_in_zone(link, &linkid, zid)) == 0) return (dls_devnet_hold(linkid, ddpp)); /* @@ -1261,9 +1380,15 @@ dls_devnet_phydev(datalink_id_t vlanid, dev_t *devp) * * This case does not change the <link name, linkid> mapping, so the link's * kstats need to be updated with using name associated the given id2. + * + * The zonename parameter is used to allow us to create a VNIC in the global + * zone which is assigned to a non-global zone. Since there is a race condition + * in the create process if two VNICs have the same name, we need to rename it + * after it has been assigned to the zone. */ int -dls_devnet_rename(datalink_id_t id1, datalink_id_t id2, const char *link) +dls_devnet_rename(datalink_id_t id1, datalink_id_t id2, const char *link, + boolean_t zoneinit) { dls_dev_handle_t ddh = NULL; int err = 0; @@ -1313,13 +1438,16 @@ dls_devnet_rename(datalink_id_t id1, datalink_id_t id2, const char *link) * is currently accessing the link kstats, or if the link is on-loan * to a non-global zone. Then set the DD_KSTAT_CHANGING flag to * prevent any access to the kstats while we delete and recreate - * kstats below. + * kstats below. However, we skip this check if we're renaming the + * vnic as part of bringing it up for a zone. */ mutex_enter(&ddp->dd_mutex); - if (ddp->dd_ref > 1) { - mutex_exit(&ddp->dd_mutex); - err = EBUSY; - goto done; + if (!zoneinit) { + if (ddp->dd_ref > 1) { + mutex_exit(&ddp->dd_mutex); + err = EBUSY; + goto done; + } } ddp->dd_flags |= DD_KSTAT_CHANGING; @@ -1333,7 +1461,15 @@ dls_devnet_rename(datalink_id_t id1, datalink_id_t id2, const char *link) /* rename mac client name and its flow if exists */ if ((err = mac_open(ddp->dd_mac, &mh)) != 0) goto done; - (void) mac_rename_primary(mh, link); + if (zoneinit) { + char tname[MAXLINKNAMELEN]; + + (void) snprintf(tname, sizeof (tname), "z%d_%s", + ddp->dd_zid, link); + (void) mac_rename_primary(mh, tname); + } else { + (void) mac_rename_primary(mh, link); + } mac_close(mh); goto done; } @@ -1406,7 +1542,7 @@ done: */ rw_exit(&i_dls_devnet_lock); if (err == 0) - dls_devnet_stat_rename(ddp); + dls_devnet_stat_rename(ddp, zoneinit); if (clear_dd_flag) { mutex_enter(&ddp->dd_mutex); @@ -1421,7 +1557,8 @@ done: } static int -i_dls_devnet_setzid(dls_devnet_t *ddp, zoneid_t new_zoneid, boolean_t setprop) +i_dls_devnet_setzid(dls_devnet_t *ddp, zoneid_t new_zoneid, boolean_t setprop, + boolean_t transient) { int err; mac_perim_handle_t mph; @@ -1454,6 +1591,7 @@ i_dls_devnet_setzid(dls_devnet_t *ddp, zoneid_t new_zoneid, boolean_t setprop) } if ((err = dls_link_setzid(ddp->dd_mac, new_zoneid)) == 0) { ddp->dd_zid = new_zoneid; + ddp->dd_transient = transient; devnet_need_rebuild = B_TRUE; } @@ -1468,7 +1606,7 @@ done: } int -dls_devnet_setzid(dls_dl_handle_t ddh, zoneid_t new_zid) +dls_devnet_setzid(dls_dl_handle_t ddh, zoneid_t new_zid, boolean_t transient) { dls_devnet_t *ddp; int err; @@ -1490,7 +1628,7 @@ dls_devnet_setzid(dls_dl_handle_t ddh, zoneid_t new_zid) refheld = B_TRUE; } - if ((err = i_dls_devnet_setzid(ddh, new_zid, B_TRUE)) != 0) { + if ((err = i_dls_devnet_setzid(ddh, new_zid, B_TRUE, transient)) != 0) { if (refheld) dls_devnet_rele(ddp); return (err); @@ -1507,7 +1645,7 @@ dls_devnet_setzid(dls_dl_handle_t ddh, zoneid_t new_zid) if (old_zid != GLOBAL_ZONEID) dls_devnet_stat_destroy(ddh, old_zid); if (new_zid != GLOBAL_ZONEID) - dls_devnet_stat_create(ddh, new_zid); + dls_devnet_stat_create(ddh, new_zid, new_zid); return (0); } @@ -1545,15 +1683,19 @@ dls_devnet_islinkvisible(datalink_id_t linkid, zoneid_t zoneid) * Access a vanity naming node. */ int -dls_devnet_open(const char *link, dls_dl_handle_t *dhp, dev_t *devp) +dls_devnet_open_in_zone(const char *link, dls_dl_handle_t *dhp, dev_t *devp, + zoneid_t zid) { dls_devnet_t *ddp; dls_link_t *dlp; - zoneid_t zid = getzoneid(); + zoneid_t czid = getzoneid(); int err; mac_perim_handle_t mph; - if ((err = dls_devnet_hold_by_name(link, &ddp)) != 0) + if (czid != GLOBAL_ZONEID && czid != zid) + return (ENOENT); + + if ((err = dls_devnet_hold_by_name(link, &ddp, zid)) != 0) return (err); dls_devnet_prop_task_wait(ddp); @@ -1586,6 +1728,12 @@ dls_devnet_open(const char *link, dls_dl_handle_t *dhp, dev_t *devp) return (0); } +int +dls_devnet_open(const char *link, dls_dl_handle_t *dhp, dev_t *devp) +{ + return (dls_devnet_open_in_zone(link, dhp, devp, getzoneid())); +} + /* * Close access to a vanity naming node. */ diff --git a/usr/src/uts/common/io/dls/dls_stat.c b/usr/src/uts/common/io/dls/dls_stat.c index 51e4be7260..82dceff278 100644 --- a/usr/src/uts/common/io/dls/dls_stat.c +++ b/usr/src/uts/common/io/dls/dls_stat.c @@ -21,6 +21,7 @@ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright 2011 Joyent, Inc. All rights reserved. */ /* @@ -30,30 +31,33 @@ #include <sys/dld_impl.h> #include <sys/mac_ether.h> -static mac_stat_info_t i_dls_si[] = { - { MAC_STAT_IFSPEED, "ifspeed", KSTAT_DATA_UINT64, 0 }, - { MAC_STAT_MULTIRCV, "multircv", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_BRDCSTRCV, "brdcstrcv", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_MULTIXMT, "multixmt", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_BRDCSTXMT, "brdcstxmt", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_NORCVBUF, "norcvbuf", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_IERRORS, "ierrors", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_NOXMTBUF, "noxmtbuf", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_OERRORS, "oerrors", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_COLLISIONS, "collisions", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_RBYTES, "rbytes", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_IPACKETS, "ipackets", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_OBYTES, "obytes", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_OPACKETS, "opackets", KSTAT_DATA_UINT32, 0 }, - { MAC_STAT_RBYTES, "rbytes64", KSTAT_DATA_UINT64, 0 }, - { MAC_STAT_IPACKETS, "ipackets64", KSTAT_DATA_UINT64, 0 }, - { MAC_STAT_OBYTES, "obytes64", KSTAT_DATA_UINT64, 0 }, - { MAC_STAT_OPACKETS, "opackets64", KSTAT_DATA_UINT64, 0 }, - { MAC_STAT_LINK_STATE, "link_state", KSTAT_DATA_UINT32, - (uint64_t)LINK_STATE_UNKNOWN} -}; - -#define STAT_INFO_COUNT (sizeof (i_dls_si) / sizeof (i_dls_si[0])) +/* + * structure for link kstats + */ +typedef struct { + kstat_named_t dk_ifspeed; + kstat_named_t dk_multircv; + kstat_named_t dk_brdcstrcv; + kstat_named_t dk_multixmt; + kstat_named_t dk_brdcstxmt; + kstat_named_t dk_norcvbuf; + kstat_named_t dk_ierrors; + kstat_named_t dk_noxmtbuf; + kstat_named_t dk_oerrors; + kstat_named_t dk_collisions; + kstat_named_t dk_rbytes; + kstat_named_t dk_ipackets; + kstat_named_t dk_obytes; + kstat_named_t dk_opackets; + kstat_named_t dk_rbytes64; + kstat_named_t dk_ipackets64; + kstat_named_t dk_obytes64; + kstat_named_t dk_opackets64; + kstat_named_t dk_link_state; + kstat_named_t dk_link_duplex; + kstat_named_t dk_unknowns; + kstat_named_t dk_zonename; +} dls_kstat_t; /* * Exported functions. @@ -61,42 +65,54 @@ static mac_stat_info_t i_dls_si[] = { int dls_stat_update(kstat_t *ksp, dls_link_t *dlp, int rw) { - kstat_named_t *knp; - uint_t i; - uint64_t val; + dls_kstat_t *dkp = ksp->ks_data; if (rw != KSTAT_READ) return (EACCES); - knp = (kstat_named_t *)ksp->ks_data; - for (i = 0; i < STAT_INFO_COUNT; i++) { - val = mac_stat_get(dlp->dl_mh, i_dls_si[i].msi_stat); - - switch (i_dls_si[i].msi_type) { - case KSTAT_DATA_UINT64: - knp->value.ui64 = val; - break; - case KSTAT_DATA_UINT32: - knp->value.ui32 = (uint32_t)val; - break; - default: - ASSERT(B_FALSE); - } - - knp++; - } + dkp->dk_ifspeed.value.ui64 = mac_stat_get(dlp->dl_mh, MAC_STAT_IFSPEED); + dkp->dk_multircv.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_MULTIRCV); + dkp->dk_brdcstrcv.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_BRDCSTRCV); + dkp->dk_multixmt.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_MULTIXMT); + dkp->dk_brdcstxmt.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_BRDCSTXMT); + dkp->dk_norcvbuf.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_NORCVBUF); + dkp->dk_ierrors.value.ui32 = mac_stat_get(dlp->dl_mh, MAC_STAT_IERRORS); + dkp->dk_noxmtbuf.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_NOXMTBUF); + dkp->dk_oerrors.value.ui32 = mac_stat_get(dlp->dl_mh, MAC_STAT_OERRORS); + dkp->dk_collisions.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_COLLISIONS); + dkp->dk_rbytes.value.ui32 = mac_stat_get(dlp->dl_mh, MAC_STAT_RBYTES); + dkp->dk_ipackets.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_IPACKETS); + dkp->dk_obytes.value.ui32 = mac_stat_get(dlp->dl_mh, MAC_STAT_OBYTES); + dkp->dk_opackets.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_OPACKETS); + dkp->dk_rbytes64.value.ui64 = mac_stat_get(dlp->dl_mh, MAC_STAT_RBYTES); + dkp->dk_ipackets64.value.ui64 = mac_stat_get(dlp->dl_mh, + MAC_STAT_IPACKETS); + dkp->dk_obytes64.value.ui64 = mac_stat_get(dlp->dl_mh, MAC_STAT_OBYTES); + dkp->dk_opackets64.value.ui64 = mac_stat_get(dlp->dl_mh, + MAC_STAT_OPACKETS); + dkp->dk_link_state.value.ui32 = mac_stat_get(dlp->dl_mh, + MAC_STAT_LINK_STATE); /* * Ethernet specific kstat "link_duplex" */ if (dlp->dl_mip->mi_nativemedia != DL_ETHER) { - knp->value.ui32 = LINK_DUPLEX_UNKNOWN; + dkp->dk_link_duplex.value.ui32 = LINK_DUPLEX_UNKNOWN; } else { - val = mac_stat_get(dlp->dl_mh, ETHER_STAT_LINK_DUPLEX); - knp->value.ui32 = (uint32_t)val; + dkp->dk_link_duplex.value.ui32 = + (uint32_t)mac_stat_get(dlp->dl_mh, ETHER_STAT_LINK_DUPLEX); } - knp++; - knp->value.ui32 = dlp->dl_unknowns; + + dkp->dk_unknowns.value.ui32 = dlp->dl_unknowns; return (0); } @@ -104,30 +120,66 @@ dls_stat_update(kstat_t *ksp, dls_link_t *dlp, int rw) int dls_stat_create(const char *module, int instance, const char *name, zoneid_t zoneid, int (*update)(struct kstat *, int), void *private, - kstat_t **kspp) + kstat_t **kspp, zoneid_t newzoneid) { kstat_t *ksp; - kstat_named_t *knp; - uint_t i; + zone_t *zone; + dls_kstat_t *dkp; if ((ksp = kstat_create_zone(module, instance, name, "net", - KSTAT_TYPE_NAMED, STAT_INFO_COUNT + 2, 0, zoneid)) == NULL) { + KSTAT_TYPE_NAMED, sizeof (dls_kstat_t) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL, zoneid)) == NULL) { return (EINVAL); } ksp->ks_update = update; ksp->ks_private = private; + dkp = ksp->ks_data = kmem_zalloc(sizeof (dls_kstat_t), KM_SLEEP); + if ((zone = zone_find_by_id(newzoneid)) != NULL) { + ksp->ks_data_size += strlen(zone->zone_name) + 1; + } - knp = (kstat_named_t *)ksp->ks_data; - for (i = 0; i < STAT_INFO_COUNT; i++) { - kstat_named_init(knp, i_dls_si[i].msi_name, - i_dls_si[i].msi_type); - knp++; + kstat_named_init(&dkp->dk_ifspeed, "ifspeed", KSTAT_DATA_UINT64); + kstat_named_init(&dkp->dk_multircv, "multircv", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_brdcstrcv, "brdcstrcv", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_multixmt, "multixmt", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_brdcstxmt, "brdcstxmt", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_norcvbuf, "norcvbuf", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_ierrors, "ierrors", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_noxmtbuf, "noxmtbuf", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_oerrors, "oerrors", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_collisions, "collisions", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_rbytes, "rbytes", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_ipackets, "ipackets", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_obytes, "obytes", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_opackets, "opackets", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_rbytes64, "rbytes64", KSTAT_DATA_UINT64); + kstat_named_init(&dkp->dk_ipackets64, "ipackets64", KSTAT_DATA_UINT64); + kstat_named_init(&dkp->dk_obytes64, "obytes64", KSTAT_DATA_UINT64); + kstat_named_init(&dkp->dk_opackets64, "opackets64", KSTAT_DATA_UINT64); + kstat_named_init(&dkp->dk_link_state, "link_state", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_link_duplex, "link_duplex", + KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_unknowns, "unknowns", KSTAT_DATA_UINT32); + kstat_named_init(&dkp->dk_zonename, "zonename", KSTAT_DATA_STRING); + + if (zone != NULL) { + kstat_named_setstr(&dkp->dk_zonename, zone->zone_name); + zone_rele(zone); } - kstat_named_init(knp++, "link_duplex", KSTAT_DATA_UINT32); - kstat_named_init(knp, "unknowns", KSTAT_DATA_UINT32); kstat_install(ksp); *kspp = ksp; return (0); } + +void +dls_stat_delete(kstat_t *ksp) +{ + void *data; + if (ksp != NULL) { + data = ksp->ks_data; + kstat_delete(ksp); + kmem_free(data, sizeof (dls_kstat_t)); + } +} diff --git a/usr/src/uts/common/io/dr_sas/THIRDPARTYLICENSE b/usr/src/uts/common/io/dr_sas/THIRDPARTYLICENSE new file mode 100644 index 0000000000..00aefb6f51 --- /dev/null +++ b/usr/src/uts/common/io/dr_sas/THIRDPARTYLICENSE @@ -0,0 +1,32 @@ +/* + * MegaRAID device driver for SAS2.0 controllers + * Copyright (c) 2009, LSI Logic Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ diff --git a/usr/src/uts/common/io/dr_sas/THIRDPARTYLICENSE.descrip b/usr/src/uts/common/io/dr_sas/THIRDPARTYLICENSE.descrip new file mode 100644 index 0000000000..ac6d2d1b15 --- /dev/null +++ b/usr/src/uts/common/io/dr_sas/THIRDPARTYLICENSE.descrip @@ -0,0 +1 @@ +DR_SAS DRIVER diff --git a/usr/src/uts/common/io/dr_sas/dr_sas.c b/usr/src/uts/common/io/dr_sas/dr_sas.c new file mode 100644 index 0000000000..5b1dc82938 --- /dev/null +++ b/usr/src/uts/common/io/dr_sas/dr_sas.c @@ -0,0 +1,5506 @@ +/* + * dr_sas.c: source for dr_sas driver + * + * MegaRAID device driver for SAS2.0 controllers + * Copyright (c) 2008-2009, LSI Logic Corporation. + * All rights reserved. + * + * Version: + * Author: + * Arun Chandrashekhar + * Manju R + * Rajesh Prabhakaran + * Seokmann Ju + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/file.h> +#include <sys/errno.h> +#include <sys/open.h> +#include <sys/cred.h> +#include <sys/modctl.h> +#include <sys/conf.h> +#include <sys/devops.h> +#include <sys/cmn_err.h> +#include <sys/kmem.h> +#include <sys/stat.h> +#include <sys/mkdev.h> +#include <sys/pci.h> +#include <sys/scsi/scsi.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/atomic.h> +#include <sys/signal.h> +#include <sys/fs/dv_node.h> /* devfs_clean */ + +#include "dr_sas.h" + +/* + * FMA header files + */ +#include <sys/ddifm.h> +#include <sys/fm/protocol.h> +#include <sys/fm/util.h> +#include <sys/fm/io/ddi.h> + +/* + * Local static data + */ +static void *drsas_state = NULL; +static int debug_level_g = CL_NONE; + +#pragma weak scsi_hba_open +#pragma weak scsi_hba_close +#pragma weak scsi_hba_ioctl + +static ddi_dma_attr_t drsas_generic_dma_attr = { + DMA_ATTR_V0, /* dma_attr_version */ + 0, /* low DMA address range */ + 0xFFFFFFFFU, /* high DMA address range */ + 0xFFFFFFFFU, /* DMA counter register */ + 8, /* DMA address alignment */ + 0x07, /* DMA burstsizes */ + 1, /* min DMA size */ + 0xFFFFFFFFU, /* max DMA size */ + 0xFFFFFFFFU, /* segment boundary */ + DRSAS_MAX_SGE_CNT, /* dma_attr_sglen */ + 512, /* granularity of device */ + 0 /* bus specific DMA flags */ +}; + +int32_t drsas_max_cap_maxxfer = 0x1000000; + +/* + * cb_ops contains base level routines + */ +static struct cb_ops drsas_cb_ops = { + drsas_open, /* open */ + drsas_close, /* close */ + nodev, /* strategy */ + nodev, /* print */ + nodev, /* dump */ + nodev, /* read */ + nodev, /* write */ + drsas_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + nochpoll, /* poll */ + nodev, /* cb_prop_op */ + 0, /* streamtab */ + D_NEW | D_HOTPLUG, /* cb_flag */ + CB_REV, /* cb_rev */ + nodev, /* cb_aread */ + nodev /* cb_awrite */ +}; + +/* + * dev_ops contains configuration routines + */ +static struct dev_ops drsas_ops = { + DEVO_REV, /* rev, */ + 0, /* refcnt */ + drsas_getinfo, /* getinfo */ + nulldev, /* identify */ + nulldev, /* probe */ + drsas_attach, /* attach */ + drsas_detach, /* detach */ + drsas_reset, /* reset */ + &drsas_cb_ops, /* char/block ops */ + NULL, /* bus ops */ + NULL, /* power */ + ddi_quiesce_not_supported, /* quiesce */ +}; + +char _depends_on[] = "misc/scsi"; + +static struct modldrv modldrv = { + &mod_driverops, /* module type - driver */ + DRSAS_VERSION, + &drsas_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, /* ml_rev - must be MODREV_1 */ + &modldrv, /* ml_linkage */ + NULL /* end of driver linkage */ +}; + +static struct ddi_device_acc_attr endian_attr = { + DDI_DEVICE_ATTR_V0, + DDI_STRUCTURE_LE_ACC, + DDI_STRICTORDER_ACC +}; + + +/* + * ************************************************************************** * + * * + * common entry points - for loadable kernel modules * + * * + * ************************************************************************** * + */ + +int +_init(void) +{ + int ret; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + ret = ddi_soft_state_init(&drsas_state, + sizeof (struct drsas_instance), 0); + + if (ret != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "dr_sas: could not init state")); + return (ret); + } + + if ((ret = scsi_hba_init(&modlinkage)) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "dr_sas: could not init scsi hba")); + ddi_soft_state_fini(&drsas_state); + return (ret); + } + + ret = mod_install(&modlinkage); + + if (ret != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "dr_sas: mod_install failed")); + scsi_hba_fini(&modlinkage); + ddi_soft_state_fini(&drsas_state); + } + + return (ret); +} + +int +_info(struct modinfo *modinfop) +{ + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + return (mod_info(&modlinkage, modinfop)); +} + +int +_fini(void) +{ + int ret; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + if ((ret = mod_remove(&modlinkage)) != DDI_SUCCESS) + return (ret); + + scsi_hba_fini(&modlinkage); + + ddi_soft_state_fini(&drsas_state); + + return (ret); +} + + +/* + * ************************************************************************** * + * * + * common entry points - for autoconfiguration * + * * + * ************************************************************************** * + */ + +static int +drsas_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int instance_no; + int nregs; + uint8_t added_isr_f = 0; + uint8_t added_soft_isr_f = 0; + uint8_t create_devctl_node_f = 0; + uint8_t create_scsi_node_f = 0; + uint8_t create_ioc_node_f = 0; + uint8_t tran_alloc_f = 0; + uint8_t irq; + uint16_t vendor_id; + uint16_t device_id; + uint16_t subsysvid; + uint16_t subsysid; + uint16_t command; + off_t reglength = 0; + int intr_types = 0; + char *data; + int msi_enable = 0; + + scsi_hba_tran_t *tran; + ddi_dma_attr_t tran_dma_attr; + struct drsas_instance *instance; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* CONSTCOND */ + ASSERT(NO_COMPETING_THREADS); + + instance_no = ddi_get_instance(dip); + + /* + * check to see whether this device is in a DMA-capable slot. + */ + if (ddi_slaveonly(dip) == DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "dr_sas%d: Device in slave-only slot, unused", + instance_no)); + return (DDI_FAILURE); + } + + switch (cmd) { + case DDI_ATTACH: + con_log(CL_DLEVEL1, (CE_NOTE, "dr_sas: DDI_ATTACH")); + /* allocate the soft state for the instance */ + if (ddi_soft_state_zalloc(drsas_state, instance_no) + != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "dr_sas%d: Failed to allocate soft state", + instance_no)); + + return (DDI_FAILURE); + } + + instance = (struct drsas_instance *)ddi_get_soft_state + (drsas_state, instance_no); + + if (instance == NULL) { + con_log(CL_ANN, (CE_WARN, + "dr_sas%d: Bad soft state", instance_no)); + + ddi_soft_state_free(drsas_state, instance_no); + + return (DDI_FAILURE); + } + + bzero((caddr_t)instance, + sizeof (struct drsas_instance)); + + instance->func_ptr = kmem_zalloc( + sizeof (struct drsas_func_ptr), KM_SLEEP); + ASSERT(instance->func_ptr); + + /* Setup the PCI configuration space handles */ + if (pci_config_setup(dip, &instance->pci_handle) != + DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "dr_sas%d: pci config setup failed ", + instance_no)); + + kmem_free(instance->func_ptr, + sizeof (struct drsas_func_ptr)); + ddi_soft_state_free(drsas_state, instance_no); + + return (DDI_FAILURE); + } + + if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: failed to get registers.")); + + pci_config_teardown(&instance->pci_handle); + kmem_free(instance->func_ptr, + sizeof (struct drsas_func_ptr)); + ddi_soft_state_free(drsas_state, instance_no); + + return (DDI_FAILURE); + } + + vendor_id = pci_config_get16(instance->pci_handle, + PCI_CONF_VENID); + device_id = pci_config_get16(instance->pci_handle, + PCI_CONF_DEVID); + + subsysvid = pci_config_get16(instance->pci_handle, + PCI_CONF_SUBVENID); + subsysid = pci_config_get16(instance->pci_handle, + PCI_CONF_SUBSYSID); + + pci_config_put16(instance->pci_handle, PCI_CONF_COMM, + (pci_config_get16(instance->pci_handle, + PCI_CONF_COMM) | PCI_COMM_ME)); + irq = pci_config_get8(instance->pci_handle, + PCI_CONF_ILINE); + + con_log(CL_DLEVEL1, (CE_CONT, "dr_sas%d: " + "0x%x:0x%x 0x%x:0x%x, irq:%d drv-ver:%s", + instance_no, vendor_id, device_id, subsysvid, + subsysid, irq, DRSAS_VERSION)); + + /* enable bus-mastering */ + command = pci_config_get16(instance->pci_handle, + PCI_CONF_COMM); + + if (!(command & PCI_COMM_ME)) { + command |= PCI_COMM_ME; + + pci_config_put16(instance->pci_handle, + PCI_CONF_COMM, command); + + con_log(CL_ANN, (CE_CONT, "dr_sas%d: " + "enable bus-mastering", instance_no)); + } else { + con_log(CL_DLEVEL1, (CE_CONT, "dr_sas%d: " + "bus-mastering already set", instance_no)); + } + + /* initialize function pointers */ + if ((device_id == PCI_DEVICE_ID_LSI_2108VDE) || + (device_id == PCI_DEVICE_ID_LSI_2108V)) { + con_log(CL_DLEVEL1, (CE_CONT, "dr_sas%d: " + "2108V/DE detected", instance_no)); + instance->func_ptr->read_fw_status_reg = + read_fw_status_reg_ppc; + instance->func_ptr->issue_cmd = issue_cmd_ppc; + instance->func_ptr->issue_cmd_in_sync_mode = + issue_cmd_in_sync_mode_ppc; + instance->func_ptr->issue_cmd_in_poll_mode = + issue_cmd_in_poll_mode_ppc; + instance->func_ptr->enable_intr = + enable_intr_ppc; + instance->func_ptr->disable_intr = + disable_intr_ppc; + instance->func_ptr->intr_ack = intr_ack_ppc; + } else { + con_log(CL_ANN, (CE_WARN, + "dr_sas: Invalid device detected")); + + pci_config_teardown(&instance->pci_handle); + kmem_free(instance->func_ptr, + sizeof (struct drsas_func_ptr)); + ddi_soft_state_free(drsas_state, instance_no); + + return (DDI_FAILURE); + } + + instance->baseaddress = pci_config_get32( + instance->pci_handle, PCI_CONF_BASE0); + instance->baseaddress &= 0x0fffc; + + instance->dip = dip; + instance->vendor_id = vendor_id; + instance->device_id = device_id; + instance->subsysvid = subsysvid; + instance->subsysid = subsysid; + instance->instance = instance_no; + + /* Initialize FMA */ + instance->fm_capabilities = ddi_prop_get_int( + DDI_DEV_T_ANY, instance->dip, DDI_PROP_DONTPASS, + "fm-capable", DDI_FM_EREPORT_CAPABLE | + DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE + | DDI_FM_ERRCB_CAPABLE); + + drsas_fm_init(instance); + + /* Initialize Interrupts */ + if ((ddi_dev_regsize(instance->dip, + REGISTER_SET_IO_2108, ®length) != DDI_SUCCESS) || + reglength < MINIMUM_MFI_MEM_SZ) { + return (DDI_FAILURE); + } + if (reglength > DEFAULT_MFI_MEM_SZ) { + reglength = DEFAULT_MFI_MEM_SZ; + con_log(CL_DLEVEL1, (CE_NOTE, + "dr_sas: register length to map is " + "0x%lx bytes", reglength)); + } + if (ddi_regs_map_setup(instance->dip, + REGISTER_SET_IO_2108, &instance->regmap, 0, + reglength, &endian_attr, &instance->regmap_handle) + != DDI_SUCCESS) { + con_log(CL_ANN, (CE_NOTE, + "dr_sas: couldn't map control registers")); + goto fail_attach; + } + + /* + * Disable Interrupt Now. + * Setup Software interrupt + */ + instance->func_ptr->disable_intr(instance); + + msi_enable = 0; + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0, + "drsas-enable-msi", &data) == DDI_SUCCESS) { + if (strncmp(data, "yes", 3) == 0) { + msi_enable = 1; + con_log(CL_ANN, (CE_WARN, + "msi_enable = %d ENABLED", + msi_enable)); + } + ddi_prop_free(data); + } + + con_log(CL_DLEVEL1, (CE_WARN, "msi_enable = %d", + msi_enable)); + + /* Check for all supported interrupt types */ + if (ddi_intr_get_supported_types( + dip, &intr_types) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "ddi_intr_get_supported_types() failed")); + goto fail_attach; + } + + con_log(CL_DLEVEL1, (CE_NOTE, + "ddi_intr_get_supported_types() ret: 0x%x", + intr_types)); + + /* Initialize and Setup Interrupt handler */ + if (msi_enable && (intr_types & DDI_INTR_TYPE_MSIX)) { + if (drsas_add_intrs(instance, + DDI_INTR_TYPE_MSIX) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "MSIX interrupt query failed")); + goto fail_attach; + } + instance->intr_type = DDI_INTR_TYPE_MSIX; + } else if (msi_enable && (intr_types & + DDI_INTR_TYPE_MSI)) { + if (drsas_add_intrs(instance, + DDI_INTR_TYPE_MSI) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "MSI interrupt query failed")); + goto fail_attach; + } + instance->intr_type = DDI_INTR_TYPE_MSI; + } else if (intr_types & DDI_INTR_TYPE_FIXED) { + msi_enable = 0; + if (drsas_add_intrs(instance, + DDI_INTR_TYPE_FIXED) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "FIXED interrupt query failed")); + goto fail_attach; + } + instance->intr_type = DDI_INTR_TYPE_FIXED; + } else { + con_log(CL_ANN, (CE_WARN, "Device cannot " + "suppport either FIXED or MSI/X " + "interrupts")); + goto fail_attach; + } + + added_isr_f = 1; + + /* setup the mfi based low level driver */ + if (init_mfi(instance) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "dr_sas: " + "could not initialize the low level driver")); + + goto fail_attach; + } + + /* Initialize all Mutex */ + INIT_LIST_HEAD(&instance->completed_pool_list); + mutex_init(&instance->completed_pool_mtx, + "completed_pool_mtx", MUTEX_DRIVER, + DDI_INTR_PRI(instance->intr_pri)); + + mutex_init(&instance->int_cmd_mtx, "int_cmd_mtx", + MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); + cv_init(&instance->int_cmd_cv, NULL, CV_DRIVER, NULL); + + mutex_init(&instance->cmd_pool_mtx, "cmd_pool_mtx", + MUTEX_DRIVER, DDI_INTR_PRI(instance->intr_pri)); + + /* Register our soft-isr for highlevel interrupts. */ + instance->isr_level = instance->intr_pri; + if (instance->isr_level == HIGH_LEVEL_INTR) { + if (ddi_add_softintr(dip, DDI_SOFTINT_HIGH, + &instance->soft_intr_id, NULL, NULL, + drsas_softintr, (caddr_t)instance) != + DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + " Software ISR did not register")); + + goto fail_attach; + } + + added_soft_isr_f = 1; + } + + /* Allocate a transport structure */ + tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); + + if (tran == NULL) { + con_log(CL_ANN, (CE_WARN, + "scsi_hba_tran_alloc failed")); + goto fail_attach; + } + + tran_alloc_f = 1; + + instance->tran = tran; + + tran->tran_hba_private = instance; + tran->tran_tgt_init = drsas_tran_tgt_init; + tran->tran_tgt_probe = scsi_hba_probe; + tran->tran_tgt_free = drsas_tran_tgt_free; + tran->tran_init_pkt = drsas_tran_init_pkt; + tran->tran_start = drsas_tran_start; + tran->tran_abort = drsas_tran_abort; + tran->tran_reset = drsas_tran_reset; + tran->tran_getcap = drsas_tran_getcap; + tran->tran_setcap = drsas_tran_setcap; + tran->tran_destroy_pkt = drsas_tran_destroy_pkt; + tran->tran_dmafree = drsas_tran_dmafree; + tran->tran_sync_pkt = drsas_tran_sync_pkt; + tran->tran_bus_config = drsas_tran_bus_config; + + tran_dma_attr = drsas_generic_dma_attr; + tran_dma_attr.dma_attr_sgllen = instance->max_num_sge; + + /* Attach this instance of the hba */ + if (scsi_hba_attach_setup(dip, &tran_dma_attr, tran, 0) + != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "scsi_hba_attach failed")); + + goto fail_attach; + } + + /* create devctl node for cfgadm command */ + if (ddi_create_minor_node(dip, "devctl", + S_IFCHR, INST2DEVCTL(instance_no), + DDI_NT_SCSI_NEXUS, 0) == DDI_FAILURE) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: failed to create devctl node.")); + + goto fail_attach; + } + + create_devctl_node_f = 1; + + /* create scsi node for cfgadm command */ + if (ddi_create_minor_node(dip, "scsi", S_IFCHR, + INST2SCSI(instance_no), + DDI_NT_SCSI_ATTACHMENT_POINT, 0) == + DDI_FAILURE) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: failed to create scsi node.")); + + goto fail_attach; + } + + create_scsi_node_f = 1; + + (void) sprintf(instance->iocnode, "%d:lsirdctl", + instance_no); + + /* + * Create a node for applications + * for issuing ioctl to the driver. + */ + if (ddi_create_minor_node(dip, instance->iocnode, + S_IFCHR, INST2LSIRDCTL(instance_no), + DDI_PSEUDO, 0) == DDI_FAILURE) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: failed to create ioctl node.")); + + goto fail_attach; + } + + create_ioc_node_f = 1; + + /* Create a taskq to handle dr events */ + if ((instance->taskq = ddi_taskq_create(dip, + "drsas_dr_taskq", 1, + TASKQ_DEFAULTPRI, 0)) == NULL) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: failed to create taskq ")); + instance->taskq = NULL; + goto fail_attach; + } + + /* enable interrupt */ + instance->func_ptr->enable_intr(instance); + + /* initiate AEN */ + if (start_mfi_aen(instance)) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: failed to initiate AEN.")); + goto fail_initiate_aen; + } + + con_log(CL_DLEVEL1, (CE_NOTE, + "AEN started for instance %d.", instance_no)); + + /* Finally! We are on the air. */ + ddi_report_dev(dip); + + if (drsas_check_acc_handle(instance->regmap_handle) != + DDI_SUCCESS) { + goto fail_attach; + } + if (drsas_check_acc_handle(instance->pci_handle) != + DDI_SUCCESS) { + goto fail_attach; + } + instance->dr_ld_list = + kmem_zalloc(MRDRV_MAX_LD * sizeof (struct drsas_ld), + KM_SLEEP); + break; + case DDI_PM_RESUME: + con_log(CL_ANN, (CE_NOTE, + "dr_sas: DDI_PM_RESUME")); + break; + case DDI_RESUME: + con_log(CL_ANN, (CE_NOTE, + "dr_sas: DDI_RESUME")); + break; + default: + con_log(CL_ANN, (CE_WARN, + "dr_sas: invalid attach cmd=%x", cmd)); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); + +fail_initiate_aen: +fail_attach: + if (create_devctl_node_f) { + ddi_remove_minor_node(dip, "devctl"); + } + + if (create_scsi_node_f) { + ddi_remove_minor_node(dip, "scsi"); + } + + if (create_ioc_node_f) { + ddi_remove_minor_node(dip, instance->iocnode); + } + + if (tran_alloc_f) { + scsi_hba_tran_free(tran); + } + + + if (added_soft_isr_f) { + ddi_remove_softintr(instance->soft_intr_id); + } + + if (added_isr_f) { + drsas_rem_intrs(instance); + } + + if (instance && instance->taskq) { + ddi_taskq_destroy(instance->taskq); + } + + drsas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); + ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); + + drsas_fm_fini(instance); + + pci_config_teardown(&instance->pci_handle); + + ddi_soft_state_free(drsas_state, instance_no); + + con_log(CL_ANN, (CE_NOTE, + "dr_sas: return failure from drsas_attach")); + + return (DDI_FAILURE); +} + +/*ARGSUSED*/ +static int +drsas_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) +{ + int rval; + int drsas_minor = getminor((dev_t)arg); + + struct drsas_instance *instance; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + instance = (struct drsas_instance *) + ddi_get_soft_state(drsas_state, + MINOR2INST(drsas_minor)); + + if (instance == NULL) { + *resultp = NULL; + rval = DDI_FAILURE; + } else { + *resultp = instance->dip; + rval = DDI_SUCCESS; + } + break; + case DDI_INFO_DEVT2INSTANCE: + *resultp = (void *)instance; + rval = DDI_SUCCESS; + break; + default: + *resultp = NULL; + rval = DDI_FAILURE; + } + + return (rval); +} + +static int +drsas_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int instance_no; + + struct drsas_instance *instance; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* CONSTCOND */ + ASSERT(NO_COMPETING_THREADS); + + instance_no = ddi_get_instance(dip); + + instance = (struct drsas_instance *)ddi_get_soft_state(drsas_state, + instance_no); + + if (!instance) { + con_log(CL_ANN, (CE_WARN, + "dr_sas:%d could not get instance in detach", + instance_no)); + + return (DDI_FAILURE); + } + + con_log(CL_ANN, (CE_NOTE, + "dr_sas%d: detaching device 0x%4x:0x%4x:0x%4x:0x%4x", + instance_no, instance->vendor_id, instance->device_id, + instance->subsysvid, instance->subsysid)); + + switch (cmd) { + case DDI_DETACH: + con_log(CL_ANN, (CE_NOTE, + "drsas_detach: DDI_DETACH")); + + if (scsi_hba_detach(dip) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, + "dr_sas:%d failed to detach", + instance_no)); + + return (DDI_FAILURE); + } + + scsi_hba_tran_free(instance->tran); + + flush_cache(instance); + + if (abort_aen_cmd(instance, instance->aen_cmd)) { + con_log(CL_ANN, (CE_WARN, "drsas_detach: " + "failed to abort prevous AEN command")); + + return (DDI_FAILURE); + } + + instance->func_ptr->disable_intr(instance); + + if (instance->isr_level == HIGH_LEVEL_INTR) { + ddi_remove_softintr(instance->soft_intr_id); + } + + drsas_rem_intrs(instance); + + if (instance->taskq) { + ddi_taskq_destroy(instance->taskq); + } + kmem_free(instance->dr_ld_list, MRDRV_MAX_LD + * sizeof (struct drsas_ld)); + free_space_for_mfi(instance); + + drsas_fm_fini(instance); + + pci_config_teardown(&instance->pci_handle); + + kmem_free(instance->func_ptr, + sizeof (struct drsas_func_ptr)); + + ddi_soft_state_free(drsas_state, instance_no); + break; + case DDI_PM_SUSPEND: + con_log(CL_ANN, (CE_NOTE, + "drsas_detach: DDI_PM_SUSPEND")); + + break; + case DDI_SUSPEND: + con_log(CL_ANN, (CE_NOTE, + "drsas_detach: DDI_SUSPEND")); + + break; + default: + con_log(CL_ANN, (CE_WARN, + "invalid detach command:0x%x", cmd)); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * ************************************************************************** * + * * + * common entry points - for character driver types * + * * + * ************************************************************************** * + */ +static int +drsas_open(dev_t *dev, int openflags, int otyp, cred_t *credp) +{ + int rval = 0; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* Check root permissions */ + if (drv_priv(credp) != 0) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: Non-root ioctl access denied!")); + return (EPERM); + } + + /* Verify we are being opened as a character device */ + if (otyp != OTYP_CHR) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: ioctl node must be a char node")); + return (EINVAL); + } + + if (ddi_get_soft_state(drsas_state, MINOR2INST(getminor(*dev))) + == NULL) { + return (ENXIO); + } + + if (scsi_hba_open) { + rval = scsi_hba_open(dev, openflags, otyp, credp); + } + + return (rval); +} + +static int +drsas_close(dev_t dev, int openflags, int otyp, cred_t *credp) +{ + int rval = 0; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* no need for locks! */ + + if (scsi_hba_close) { + rval = scsi_hba_close(dev, openflags, otyp, credp); + } + + return (rval); +} + +static int +drsas_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + int rval = 0; + + struct drsas_instance *instance; + struct drsas_ioctl *ioctl; + struct drsas_aen aen; + int i; + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + instance = ddi_get_soft_state(drsas_state, MINOR2INST(getminor(dev))); + + if (instance == NULL) { + /* invalid minor number */ + con_log(CL_ANN, (CE_WARN, "dr_sas: adapter not found.")); + return (ENXIO); + } + + ioctl = (struct drsas_ioctl *)kmem_zalloc(sizeof (struct drsas_ioctl), + KM_SLEEP); + ASSERT(ioctl); + + switch ((uint_t)cmd) { + case DRSAS_IOCTL_FIRMWARE: + for (i = 0; i < sizeof (struct drsas_ioctl); i++) { + if (ddi_copyin((uint8_t *)arg+i, + (uint8_t *)ioctl+i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, "drsas_ioctl " + "ERROR IOCTL copyin")); + kmem_free(ioctl, + sizeof (struct drsas_ioctl)); + return (EFAULT); + } + } + if (ioctl->control_code == DRSAS_DRIVER_IOCTL_COMMON) { + rval = handle_drv_ioctl(instance, ioctl, mode); + } else { + rval = handle_mfi_ioctl(instance, ioctl, mode); + } + for (i = 0; i < sizeof (struct drsas_ioctl) - 1; i++) { + if (ddi_copyout((uint8_t *)ioctl+i, + (uint8_t *)arg+i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "drsas_ioctl: ddi_copyout " + "failed")); + rval = 1; + break; + } + } + + break; + case DRSAS_IOCTL_AEN: + for (i = 0; i < sizeof (struct drsas_aen); i++) { + if (ddi_copyin((uint8_t *)arg+i, + (uint8_t *)&aen+i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "drsas_ioctl: " + "ERROR AEN copyin")); + kmem_free(ioctl, + sizeof (struct drsas_ioctl)); + return (EFAULT); + } + } + + rval = handle_mfi_aen(instance, &aen); + for (i = 0; i < sizeof (struct drsas_aen); i++) { + if (ddi_copyout((uint8_t *)&aen + i, + (uint8_t *)arg + i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "drsas_ioctl: " + "ddi_copyout failed")); + rval = 1; + break; + } + } + + break; + default: + rval = scsi_hba_ioctl(dev, cmd, arg, + mode, credp, rvalp); + + con_log(CL_DLEVEL1, (CE_NOTE, "drsas_ioctl: " + "scsi_hba_ioctl called, ret = %x.", rval)); + } + + kmem_free(ioctl, sizeof (struct drsas_ioctl)); + return (rval); +} + +/* + * ************************************************************************** * + * * + * common entry points - for block driver types * + * * + * ************************************************************************** * + */ +/*ARGSUSED*/ +static int +drsas_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) +{ + int instance_no; + + struct drsas_instance *instance; + + instance_no = ddi_get_instance(dip); + instance = (struct drsas_instance *)ddi_get_soft_state + (drsas_state, instance_no); + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + if (!instance) { + con_log(CL_ANN, (CE_WARN, "dr_sas:%d could not get adapter " + "in reset", instance_no)); + return (DDI_FAILURE); + } + + instance->func_ptr->disable_intr(instance); + + con_log(CL_ANN1, (CE_NOTE, "flushing cache for instance %d", + instance_no)); + + flush_cache(instance); + + return (DDI_SUCCESS); +} + + +/* + * ************************************************************************** * + * * + * entry points (SCSI HBA) * + * * + * ************************************************************************** * + */ +/*ARGSUSED*/ +static int +drsas_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, + scsi_hba_tran_t *tran, struct scsi_device *sd) +{ + struct drsas_instance *instance; + uint16_t tgt = sd->sd_address.a_target; + uint8_t lun = sd->sd_address.a_lun; + + con_log(CL_ANN1, (CE_NOTE, "drsas_tgt_init target %d lun %d", + tgt, lun)); + + instance = ADDR2MR(&sd->sd_address); + + if (ndi_dev_is_persistent_node(tgt_dip) == 0) { + (void) ndi_merge_node(tgt_dip, drsas_name_node); + ddi_set_name_addr(tgt_dip, NULL); + + con_log(CL_ANN1, (CE_NOTE, "drsas_tgt_init in " + "ndi_dev_is_persistent_node DDI_FAILURE t = %d l = %d", + tgt, lun)); + return (DDI_FAILURE); + } + + con_log(CL_ANN1, (CE_NOTE, "drsas_tgt_init dev_dip %p tgt_dip %p", + (void *)instance->dr_ld_list[tgt].dip, (void *)tgt_dip)); + + if (tgt < MRDRV_MAX_LD && lun == 0) { + if (instance->dr_ld_list[tgt].dip == NULL && + strcmp(ddi_driver_name(sd->sd_dev), "sd") == 0) { + instance->dr_ld_list[tgt].dip = tgt_dip; + instance->dr_ld_list[tgt].lun_type = DRSAS_LD_LUN; + } + } + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static void +drsas_tran_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, + scsi_hba_tran_t *hba_tran, struct scsi_device *sd) +{ + struct drsas_instance *instance; + int tgt = sd->sd_address.a_target; + int lun = sd->sd_address.a_lun; + + instance = ADDR2MR(&sd->sd_address); + + con_log(CL_ANN1, (CE_NOTE, "tgt_free t = %d l = %d", tgt, lun)); + + if (tgt < MRDRV_MAX_LD && lun == 0) { + if (instance->dr_ld_list[tgt].dip == tgt_dip) { + instance->dr_ld_list[tgt].dip = NULL; + } + } +} + +static dev_info_t * +drsas_find_child(struct drsas_instance *instance, uint16_t tgt, uint8_t lun) +{ + dev_info_t *child = NULL; + char addr[SCSI_MAXNAMELEN]; + char tmp[MAXNAMELEN]; + + (void) sprintf(addr, "%x,%x", tgt, lun); + for (child = ddi_get_child(instance->dip); child; + child = ddi_get_next_sibling(child)) { + + if (drsas_name_node(child, tmp, MAXNAMELEN) != + DDI_SUCCESS) { + continue; + } + + if (strcmp(addr, tmp) == 0) { + break; + } + } + con_log(CL_ANN1, (CE_NOTE, "drsas_find_child: return child = %p", + (void *)child)); + return (child); +} + +static int +drsas_name_node(dev_info_t *dip, char *name, int len) +{ + int tgt, lun; + + tgt = ddi_prop_get_int(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "target", -1); + con_log(CL_ANN1, (CE_NOTE, + "drsas_name_node: dip %p tgt %d", (void *)dip, tgt)); + if (tgt == -1) { + return (DDI_FAILURE); + } + lun = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "lun", -1); + con_log(CL_ANN1, + (CE_NOTE, "drsas_name_node: tgt %d lun %d", tgt, lun)); + if (lun == -1) { + return (DDI_FAILURE); + } + (void) snprintf(name, len, "%x,%x", tgt, lun); + return (DDI_SUCCESS); +} + +static struct scsi_pkt * +drsas_tran_init_pkt(struct scsi_address *ap, register struct scsi_pkt *pkt, + struct buf *bp, int cmdlen, int statuslen, int tgtlen, + int flags, int (*callback)(), caddr_t arg) +{ + struct scsa_cmd *acmd; + struct drsas_instance *instance; + struct scsi_pkt *new_pkt; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + instance = ADDR2MR(ap); + + /* step #1 : pkt allocation */ + if (pkt == NULL) { + pkt = scsi_hba_pkt_alloc(instance->dip, ap, cmdlen, statuslen, + tgtlen, sizeof (struct scsa_cmd), callback, arg); + if (pkt == NULL) { + return (NULL); + } + + acmd = PKT2CMD(pkt); + + /* + * Initialize the new pkt - we redundantly initialize + * all the fields for illustrative purposes. + */ + acmd->cmd_pkt = pkt; + acmd->cmd_flags = 0; + acmd->cmd_scblen = statuslen; + acmd->cmd_cdblen = cmdlen; + acmd->cmd_dmahandle = NULL; + acmd->cmd_ncookies = 0; + acmd->cmd_cookie = 0; + acmd->cmd_cookiecnt = 0; + acmd->cmd_nwin = 0; + + pkt->pkt_address = *ap; + pkt->pkt_comp = (void (*)())NULL; + pkt->pkt_flags = 0; + pkt->pkt_time = 0; + pkt->pkt_resid = 0; + pkt->pkt_state = 0; + pkt->pkt_statistics = 0; + pkt->pkt_reason = 0; + new_pkt = pkt; + } else { + acmd = PKT2CMD(pkt); + new_pkt = NULL; + } + + /* step #2 : dma allocation/move */ + if (bp && bp->b_bcount != 0) { + if (acmd->cmd_dmahandle == NULL) { + if (drsas_dma_alloc(instance, pkt, bp, flags, + callback) == DDI_FAILURE) { + if (new_pkt) { + scsi_hba_pkt_free(ap, new_pkt); + } + return ((struct scsi_pkt *)NULL); + } + } else { + if (drsas_dma_move(instance, pkt, bp) == DDI_FAILURE) { + return ((struct scsi_pkt *)NULL); + } + } + } + + return (pkt); +} + +static int +drsas_tran_start(struct scsi_address *ap, register struct scsi_pkt *pkt) +{ + uchar_t cmd_done = 0; + + struct drsas_instance *instance = ADDR2MR(ap); + struct drsas_cmd *cmd; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d:SCSI CDB[0]=0x%x", + __func__, __LINE__, pkt->pkt_cdbp[0])); + + pkt->pkt_reason = CMD_CMPLT; + *pkt->pkt_scbp = STATUS_GOOD; /* clear arq scsi_status */ + + cmd = build_cmd(instance, ap, pkt, &cmd_done); + + /* + * Check if the command is already completed by the drsas_build_cmd() + * routine. In which case the busy_flag would be clear and scb will be + * NULL and appropriate reason provided in pkt_reason field + */ + if (cmd_done) { + pkt->pkt_reason = CMD_CMPLT; + pkt->pkt_scbp[0] = STATUS_GOOD; + pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET + | STATE_SENT_CMD; + if (((pkt->pkt_flags & FLAG_NOINTR) == 0) && pkt->pkt_comp) { + (*pkt->pkt_comp)(pkt); + } + + return (TRAN_ACCEPT); + } + + if (cmd == NULL) { + return (TRAN_BUSY); + } + + if ((pkt->pkt_flags & FLAG_NOINTR) == 0) { + if (instance->fw_outstanding > instance->max_fw_cmds) { + con_log(CL_ANN, (CE_CONT, "dr_sas:Firmware busy")); + return_mfi_pkt(instance, cmd); + return (TRAN_BUSY); + } + + /* Synchronize the Cmd frame for the controller */ + (void) ddi_dma_sync(cmd->frame_dma_obj.dma_handle, 0, 0, + DDI_DMA_SYNC_FORDEV); + + instance->func_ptr->issue_cmd(cmd, instance); + + } else { + struct drsas_header *hdr = &cmd->frame->hdr; + + cmd->sync_cmd = DRSAS_TRUE; + + instance->func_ptr-> issue_cmd_in_poll_mode(instance, cmd); + + pkt->pkt_reason = CMD_CMPLT; + pkt->pkt_statistics = 0; + pkt->pkt_state |= STATE_XFERRED_DATA | STATE_GOT_STATUS; + + switch (ddi_get8(cmd->frame_dma_obj.acc_handle, + &hdr->cmd_status)) { + case MFI_STAT_OK: + pkt->pkt_scbp[0] = STATUS_GOOD; + break; + + case MFI_STAT_SCSI_DONE_WITH_ERROR: + + pkt->pkt_reason = CMD_CMPLT; + pkt->pkt_statistics = 0; + + ((struct scsi_status *)pkt->pkt_scbp)->sts_chk = 1; + break; + + case MFI_STAT_DEVICE_NOT_FOUND: + pkt->pkt_reason = CMD_DEV_GONE; + pkt->pkt_statistics = STAT_DISCON; + break; + + default: + ((struct scsi_status *)pkt->pkt_scbp)->sts_busy = 1; + } + + return_mfi_pkt(instance, cmd); + (void) drsas_common_check(instance, cmd); + + if (pkt->pkt_comp) { + (*pkt->pkt_comp)(pkt); + } + + } + + return (TRAN_ACCEPT); +} + +/*ARGSUSED*/ +static int +drsas_tran_abort(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* abort command not supported by H/W */ + + return (DDI_FAILURE); +} + +/*ARGSUSED*/ +static int +drsas_tran_reset(struct scsi_address *ap, int level) +{ + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* reset command not supported by H/W */ + + return (DDI_FAILURE); + +} + +/*ARGSUSED*/ +static int +drsas_tran_getcap(struct scsi_address *ap, char *cap, int whom) +{ + int rval = 0; + + struct drsas_instance *instance = ADDR2MR(ap); + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* we do allow inquiring about capabilities for other targets */ + if (cap == NULL) { + return (-1); + } + + switch (scsi_hba_lookup_capstr(cap)) { + case SCSI_CAP_DMA_MAX: + /* Limit to 16MB max transfer */ + rval = drsas_max_cap_maxxfer; + break; + case SCSI_CAP_MSG_OUT: + rval = 1; + break; + case SCSI_CAP_DISCONNECT: + rval = 0; + break; + case SCSI_CAP_SYNCHRONOUS: + rval = 0; + break; + case SCSI_CAP_WIDE_XFER: + rval = 1; + break; + case SCSI_CAP_TAGGED_QING: + rval = 1; + break; + case SCSI_CAP_UNTAGGED_QING: + rval = 1; + break; + case SCSI_CAP_PARITY: + rval = 1; + break; + case SCSI_CAP_INITIATOR_ID: + rval = instance->init_id; + break; + case SCSI_CAP_ARQ: + rval = 1; + break; + case SCSI_CAP_LINKED_CMDS: + rval = 0; + break; + case SCSI_CAP_RESET_NOTIFICATION: + rval = 1; + break; + case SCSI_CAP_GEOMETRY: + rval = -1; + + break; + default: + con_log(CL_DLEVEL2, (CE_NOTE, "Default cap coming 0x%x", + scsi_hba_lookup_capstr(cap))); + rval = -1; + break; + } + + return (rval); +} + +/*ARGSUSED*/ +static int +drsas_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) +{ + int rval = 1; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + /* We don't allow setting capabilities for other targets */ + if (cap == NULL || whom == 0) { + return (-1); + } + + switch (scsi_hba_lookup_capstr(cap)) { + case SCSI_CAP_DMA_MAX: + case SCSI_CAP_MSG_OUT: + case SCSI_CAP_PARITY: + case SCSI_CAP_LINKED_CMDS: + case SCSI_CAP_RESET_NOTIFICATION: + case SCSI_CAP_DISCONNECT: + case SCSI_CAP_SYNCHRONOUS: + case SCSI_CAP_UNTAGGED_QING: + case SCSI_CAP_WIDE_XFER: + case SCSI_CAP_INITIATOR_ID: + case SCSI_CAP_ARQ: + /* + * None of these are settable via + * the capability interface. + */ + break; + case SCSI_CAP_TAGGED_QING: + rval = 1; + break; + case SCSI_CAP_SECTOR_SIZE: + rval = 1; + break; + + case SCSI_CAP_TOTAL_SECTORS: + rval = 1; + break; + default: + rval = -1; + break; + } + + return (rval); +} + +static void +drsas_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + struct scsa_cmd *acmd = PKT2CMD(pkt); + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + if (acmd->cmd_flags & CFLAG_DMAVALID) { + acmd->cmd_flags &= ~CFLAG_DMAVALID; + + (void) ddi_dma_unbind_handle(acmd->cmd_dmahandle); + + ddi_dma_free_handle(&acmd->cmd_dmahandle); + + acmd->cmd_dmahandle = NULL; + } + + /* free the pkt */ + scsi_hba_pkt_free(ap, pkt); +} + +/*ARGSUSED*/ +static void +drsas_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + register struct scsa_cmd *acmd = PKT2CMD(pkt); + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + if (acmd->cmd_flags & CFLAG_DMAVALID) { + acmd->cmd_flags &= ~CFLAG_DMAVALID; + + (void) ddi_dma_unbind_handle(acmd->cmd_dmahandle); + + ddi_dma_free_handle(&acmd->cmd_dmahandle); + + acmd->cmd_dmahandle = NULL; + } +} + +/*ARGSUSED*/ +static void +drsas_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + register struct scsa_cmd *acmd = PKT2CMD(pkt); + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + if (acmd->cmd_flags & CFLAG_DMAVALID) { + (void) ddi_dma_sync(acmd->cmd_dmahandle, acmd->cmd_dma_offset, + acmd->cmd_dma_len, (acmd->cmd_flags & CFLAG_DMASEND) ? + DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); + } +} + +/* + * drsas_isr(caddr_t) + * + * The Interrupt Service Routine + * + * Collect status for all completed commands and do callback + * + */ +static uint_t +drsas_isr(struct drsas_instance *instance) +{ + int need_softintr; + uint32_t producer; + uint32_t consumer; + uint32_t context; + + struct drsas_cmd *cmd; + + con_log(CL_ANN1, (CE_NOTE, "chkpnt:%s:%d", __func__, __LINE__)); + + ASSERT(instance); + if ((instance->intr_type == DDI_INTR_TYPE_FIXED) && + !instance->func_ptr->intr_ack(instance)) { + return (DDI_INTR_UNCLAIMED); + } + + (void) ddi_dma_sync(instance->mfi_internal_dma_obj.dma_handle, + 0, 0, DDI_DMA_SYNC_FORCPU); + + if (drsas_check_dma_handle(instance->mfi_internal_dma_obj.dma_handle) + != DDI_SUCCESS) { + drsas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); + ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); + return (DDI_INTR_UNCLAIMED); + } + + producer = ddi_get32(instance->mfi_internal_dma_obj.acc_handle, + instance->producer); + consumer = ddi_get32(instance->mfi_internal_dma_obj.acc_handle, + instance->consumer); + + con_log(CL_ANN1, (CE_CONT, " producer %x consumer %x ", + producer, consumer)); + if (producer == consumer) { + con_log(CL_ANN1, (CE_WARN, "producer = consumer case")); + return (DDI_INTR_UNCLAIMED); + } + mutex_enter(&instance->completed_pool_mtx); + + while (consumer != producer) { + context = ddi_get32(instance->mfi_internal_dma_obj.acc_handle, + &instance->reply_queue[consumer]); + cmd = instance->cmd_list[context]; + mlist_add_tail(&cmd->list, &instance->completed_pool_list); + + consumer++; + if (consumer == (instance->max_fw_cmds + 1)) { + consumer = 0; + } + } + + mutex_exit(&instance->completed_pool_mtx); + + ddi_put32(instance->mfi_internal_dma_obj.acc_handle, + instance->consumer, consumer); + (void) ddi_dma_sync(instance->mfi_internal_dma_obj.dma_handle, + 0, 0, DDI_DMA_SYNC_FORDEV); + + if (instance->softint_running) { + need_softintr = 0; + } else { + need_softintr = 1; + } + + if (instance->isr_level == HIGH_LEVEL_INTR) { + if (need_softintr) { + ddi_trigger_softintr(instance->soft_intr_id); + } + } else { + /* + * Not a high-level interrupt, therefore call the soft level + * interrupt explicitly + */ + (void) drsas_softintr(instance); + } + + return (DDI_INTR_CLAIMED); +} + + +/* + * ************************************************************************** * + * * + * libraries * + * * + * ************************************************************************** * + */ +/* + * get_mfi_pkt : Get a command from the free pool + * After successful allocation, the caller of this routine + * must clear the frame buffer (memset to zero) before + * using the packet further. + * + * ***** Note ***** + * After clearing the frame buffer the context id of the + * frame buffer SHOULD be restored back. + */ +static struct drsas_cmd * +get_mfi_pkt(struct drsas_instance *instance) +{ + mlist_t *head = &instance->cmd_pool_list; + struct drsas_cmd *cmd = NULL; + + mutex_enter(&instance->cmd_pool_mtx); + ASSERT(mutex_owned(&instance->cmd_pool_mtx)); + + if (!mlist_empty(head)) { + cmd = mlist_entry(head->next, struct drsas_cmd, list); + mlist_del_init(head->next); + } + if (cmd != NULL) + cmd->pkt = NULL; + mutex_exit(&instance->cmd_pool_mtx); + + return (cmd); +} + +/* + * return_mfi_pkt : Return a cmd to free command pool + */ +static void +return_mfi_pkt(struct drsas_instance *instance, struct drsas_cmd *cmd) +{ + mutex_enter(&instance->cmd_pool_mtx); + ASSERT(mutex_owned(&instance->cmd_pool_mtx)); + + mlist_add(&cmd->list, &instance->cmd_pool_list); + + mutex_exit(&instance->cmd_pool_mtx); +} + +/* + * destroy_mfi_frame_pool + */ +static void +destroy_mfi_frame_pool(struct drsas_instance *instance) +{ + int i; + uint32_t max_cmd = instance->max_fw_cmds; + + struct drsas_cmd *cmd; + + /* return all frames to pool */ + for (i = 0; i < max_cmd+1; i++) { + + cmd = instance->cmd_list[i]; + + if (cmd->frame_dma_obj_status == DMA_OBJ_ALLOCATED) + (void) drsas_free_dma_obj(instance, cmd->frame_dma_obj); + + cmd->frame_dma_obj_status = DMA_OBJ_FREED; + } + +} + +/* + * create_mfi_frame_pool + */ +static int +create_mfi_frame_pool(struct drsas_instance *instance) +{ + int i = 0; + int cookie_cnt; + uint16_t max_cmd; + uint16_t sge_sz; + uint32_t sgl_sz; + uint32_t tot_frame_size; + + struct drsas_cmd *cmd; + + max_cmd = instance->max_fw_cmds; + + sge_sz = sizeof (struct drsas_sge64); + + /* calculated the number of 64byte frames required for SGL */ + sgl_sz = sge_sz * instance->max_num_sge; + tot_frame_size = sgl_sz + MRMFI_FRAME_SIZE + SENSE_LENGTH; + + con_log(CL_DLEVEL3, (CE_NOTE, "create_mfi_frame_pool: " + "sgl_sz %x tot_frame_size %x", sgl_sz, tot_frame_size)); + + while (i < max_cmd+1) { + cmd = instance->cmd_list[i]; + + cmd->frame_dma_obj.size = tot_frame_size; + cmd->frame_dma_obj.dma_attr = drsas_generic_dma_attr; + cmd->frame_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + cmd->frame_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + cmd->frame_dma_obj.dma_attr.dma_attr_sgllen = 1; + cmd->frame_dma_obj.dma_attr.dma_attr_align = 64; + + + cookie_cnt = drsas_alloc_dma_obj(instance, &cmd->frame_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC); + + if (cookie_cnt == -1 || cookie_cnt > 1) { + con_log(CL_ANN, (CE_WARN, + "create_mfi_frame_pool: could not alloc.")); + return (DDI_FAILURE); + } + + bzero(cmd->frame_dma_obj.buffer, tot_frame_size); + + cmd->frame_dma_obj_status = DMA_OBJ_ALLOCATED; + cmd->frame = (union drsas_frame *)cmd->frame_dma_obj.buffer; + cmd->frame_phys_addr = + cmd->frame_dma_obj.dma_cookie[0].dmac_address; + + cmd->sense = (uint8_t *)(((unsigned long) + cmd->frame_dma_obj.buffer) + + tot_frame_size - SENSE_LENGTH); + cmd->sense_phys_addr = + cmd->frame_dma_obj.dma_cookie[0].dmac_address + + tot_frame_size - SENSE_LENGTH; + + if (!cmd->frame || !cmd->sense) { + con_log(CL_ANN, (CE_NOTE, + "dr_sas: pci_pool_alloc failed")); + + return (ENOMEM); + } + + ddi_put32(cmd->frame_dma_obj.acc_handle, + &cmd->frame->io.context, cmd->index); + i++; + + con_log(CL_DLEVEL3, (CE_NOTE, "[%x]-%x", + cmd->index, cmd->frame_phys_addr)); + } + + return (DDI_SUCCESS); +} + +/* + * free_additional_dma_buffer + */ +static void +free_additional_dma_buffer(struct drsas_instance *instance) +{ + if (instance->mfi_internal_dma_obj.status == DMA_OBJ_ALLOCATED) { + (void) drsas_free_dma_obj(instance, + instance->mfi_internal_dma_obj); + instance->mfi_internal_dma_obj.status = DMA_OBJ_FREED; + } + + if (instance->mfi_evt_detail_obj.status == DMA_OBJ_ALLOCATED) { + (void) drsas_free_dma_obj(instance, + instance->mfi_evt_detail_obj); + instance->mfi_evt_detail_obj.status = DMA_OBJ_FREED; + } +} + +/* + * alloc_additional_dma_buffer + */ +static int +alloc_additional_dma_buffer(struct drsas_instance *instance) +{ + uint32_t reply_q_sz; + uint32_t internal_buf_size = PAGESIZE*2; + + /* max cmds plus 1 + producer & consumer */ + reply_q_sz = sizeof (uint32_t) * (instance->max_fw_cmds + 1 + 2); + + instance->mfi_internal_dma_obj.size = internal_buf_size; + instance->mfi_internal_dma_obj.dma_attr = drsas_generic_dma_attr; + instance->mfi_internal_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + instance->mfi_internal_dma_obj.dma_attr.dma_attr_count_max = + 0xFFFFFFFFU; + instance->mfi_internal_dma_obj.dma_attr.dma_attr_sgllen = 1; + + if (drsas_alloc_dma_obj(instance, &instance->mfi_internal_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, + "dr_sas: could not alloc reply queue")); + return (DDI_FAILURE); + } + + bzero(instance->mfi_internal_dma_obj.buffer, internal_buf_size); + + instance->mfi_internal_dma_obj.status |= DMA_OBJ_ALLOCATED; + + instance->producer = (uint32_t *)((unsigned long) + instance->mfi_internal_dma_obj.buffer); + instance->consumer = (uint32_t *)((unsigned long) + instance->mfi_internal_dma_obj.buffer + 4); + instance->reply_queue = (uint32_t *)((unsigned long) + instance->mfi_internal_dma_obj.buffer + 8); + instance->internal_buf = (caddr_t)(((unsigned long) + instance->mfi_internal_dma_obj.buffer) + reply_q_sz + 8); + instance->internal_buf_dmac_add = + instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address + + (reply_q_sz + 8); + instance->internal_buf_size = internal_buf_size - + (reply_q_sz + 8); + + /* allocate evt_detail */ + instance->mfi_evt_detail_obj.size = sizeof (struct drsas_evt_detail); + instance->mfi_evt_detail_obj.dma_attr = drsas_generic_dma_attr; + instance->mfi_evt_detail_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + instance->mfi_evt_detail_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + instance->mfi_evt_detail_obj.dma_attr.dma_attr_sgllen = 1; + instance->mfi_evt_detail_obj.dma_attr.dma_attr_align = 1; + + if (drsas_alloc_dma_obj(instance, &instance->mfi_evt_detail_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "alloc_additional_dma_buffer: " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + bzero(instance->mfi_evt_detail_obj.buffer, + sizeof (struct drsas_evt_detail)); + + instance->mfi_evt_detail_obj.status |= DMA_OBJ_ALLOCATED; + + return (DDI_SUCCESS); +} + +/* + * free_space_for_mfi + */ +static void +free_space_for_mfi(struct drsas_instance *instance) +{ + int i; + uint32_t max_cmd = instance->max_fw_cmds; + + /* already freed */ + if (instance->cmd_list == NULL) { + return; + } + + free_additional_dma_buffer(instance); + + /* first free the MFI frame pool */ + destroy_mfi_frame_pool(instance); + + /* free all the commands in the cmd_list */ + for (i = 0; i < instance->max_fw_cmds+1; i++) { + kmem_free(instance->cmd_list[i], + sizeof (struct drsas_cmd)); + + instance->cmd_list[i] = NULL; + } + + /* free the cmd_list buffer itself */ + kmem_free(instance->cmd_list, + sizeof (struct drsas_cmd *) * (max_cmd+1)); + + instance->cmd_list = NULL; + + INIT_LIST_HEAD(&instance->cmd_pool_list); +} + +/* + * alloc_space_for_mfi + */ +static int +alloc_space_for_mfi(struct drsas_instance *instance) +{ + int i; + uint32_t max_cmd; + size_t sz; + + struct drsas_cmd *cmd; + + max_cmd = instance->max_fw_cmds; + + /* reserve 1 more slot for flush_cache */ + sz = sizeof (struct drsas_cmd *) * (max_cmd+1); + + /* + * instance->cmd_list is an array of struct drsas_cmd pointers. + * Allocate the dynamic array first and then allocate individual + * commands. + */ + instance->cmd_list = kmem_zalloc(sz, KM_SLEEP); + ASSERT(instance->cmd_list); + + for (i = 0; i < max_cmd+1; i++) { + instance->cmd_list[i] = kmem_zalloc(sizeof (struct drsas_cmd), + KM_SLEEP); + ASSERT(instance->cmd_list[i]); + } + + INIT_LIST_HEAD(&instance->cmd_pool_list); + + /* add all the commands to command pool (instance->cmd_pool) */ + for (i = 0; i < max_cmd; i++) { + cmd = instance->cmd_list[i]; + cmd->index = i; + + mlist_add_tail(&cmd->list, &instance->cmd_pool_list); + } + + /* single slot for flush_cache won't be added in command pool */ + cmd = instance->cmd_list[max_cmd]; + cmd->index = i; + + /* create a frame pool and assign one frame to each cmd */ + if (create_mfi_frame_pool(instance)) { + con_log(CL_ANN, (CE_NOTE, "error creating frame DMA pool")); + return (DDI_FAILURE); + } + + /* create a frame pool and assign one frame to each cmd */ + if (alloc_additional_dma_buffer(instance)) { + con_log(CL_ANN, (CE_NOTE, "error creating frame DMA pool")); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * get_ctrl_info + */ +static int +get_ctrl_info(struct drsas_instance *instance, + struct drsas_ctrl_info *ctrl_info) +{ + int ret = 0; + + struct drsas_cmd *cmd; + struct drsas_dcmd_frame *dcmd; + struct drsas_ctrl_info *ci; + + cmd = get_mfi_pkt(instance); + + if (!cmd) { + con_log(CL_ANN, (CE_WARN, + "Failed to get a cmd for ctrl info")); + return (DDI_FAILURE); + } + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &cmd->frame->hdr.context, + cmd->index); + + dcmd = &cmd->frame->dcmd; + + ci = (struct drsas_ctrl_info *)instance->internal_buf; + + if (!ci) { + con_log(CL_ANN, (CE_WARN, + "Failed to alloc mem for ctrl info")); + return_mfi_pkt(instance, cmd); + return (DDI_FAILURE); + } + + (void) memset(ci, 0, sizeof (struct drsas_ctrl_info)); + + /* for( i = 0; i < DCMD_MBOX_SZ; i++ ) dcmd->mbox.b[i] = 0; */ + (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); + + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd, MFI_CMD_OP_DCMD); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd_status, + MFI_CMD_STATUS_POLL_MODE); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->sge_count, 1); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->flags, + MFI_FRAME_DIR_READ); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->timeout, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->data_xfer_len, + sizeof (struct drsas_ctrl_info)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->opcode, + DR_DCMD_CTRL_GET_INFO); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->sgl.sge32[0].phys_addr, + instance->internal_buf_dmac_add); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->sgl.sge32[0].length, + sizeof (struct drsas_ctrl_info)); + + cmd->frame_count = 1; + + if (!instance->func_ptr->issue_cmd_in_poll_mode(instance, cmd)) { + ret = 0; + ddi_rep_get8(cmd->frame_dma_obj.acc_handle, + (uint8_t *)ctrl_info, (uint8_t *)ci, + sizeof (struct drsas_ctrl_info), DDI_DEV_AUTOINCR); + } else { + con_log(CL_ANN, (CE_WARN, "get_ctrl_info: Ctrl info failed")); + ret = -1; + } + + return_mfi_pkt(instance, cmd); + if (drsas_common_check(instance, cmd) != DDI_SUCCESS) { + ret = -1; + } + + return (ret); +} + +/* + * abort_aen_cmd + */ +static int +abort_aen_cmd(struct drsas_instance *instance, + struct drsas_cmd *cmd_to_abort) +{ + int ret = 0; + + struct drsas_cmd *cmd; + struct drsas_abort_frame *abort_fr; + + cmd = get_mfi_pkt(instance); + + if (!cmd) { + con_log(CL_ANN, (CE_WARN, + "Failed to get a cmd for ctrl info")); + return (DDI_FAILURE); + } + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &cmd->frame->hdr.context, + cmd->index); + + abort_fr = &cmd->frame->abort; + + /* prepare and issue the abort frame */ + ddi_put8(cmd->frame_dma_obj.acc_handle, + &abort_fr->cmd, MFI_CMD_OP_ABORT); + ddi_put8(cmd->frame_dma_obj.acc_handle, &abort_fr->cmd_status, + MFI_CMD_STATUS_SYNC_MODE); + ddi_put16(cmd->frame_dma_obj.acc_handle, &abort_fr->flags, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, &abort_fr->abort_context, + cmd_to_abort->index); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &abort_fr->abort_mfi_phys_addr_lo, cmd_to_abort->frame_phys_addr); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &abort_fr->abort_mfi_phys_addr_hi, 0); + + instance->aen_cmd->abort_aen = 1; + + cmd->sync_cmd = DRSAS_TRUE; + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { + con_log(CL_ANN, (CE_WARN, + "abort_aen_cmd: issue_cmd_in_sync_mode failed")); + ret = -1; + } else { + ret = 0; + } + + instance->aen_cmd->abort_aen = 1; + instance->aen_cmd = 0; + + return_mfi_pkt(instance, cmd); + (void) drsas_common_check(instance, cmd); + + return (ret); +} + +/* + * init_mfi + */ +static int +init_mfi(struct drsas_instance *instance) +{ + struct drsas_cmd *cmd; + struct drsas_ctrl_info ctrl_info; + struct drsas_init_frame *init_frame; + struct drsas_init_queue_info *initq_info; + + /* we expect the FW state to be READY */ + if (mfi_state_transition_to_ready(instance)) { + con_log(CL_ANN, (CE_WARN, "dr_sas: F/W is not ready")); + goto fail_ready_state; + } + + /* get various operational parameters from status register */ + instance->max_num_sge = + (instance->func_ptr->read_fw_status_reg(instance) & + 0xFF0000) >> 0x10; + /* + * Reduce the max supported cmds by 1. This is to ensure that the + * reply_q_sz (1 more than the max cmd that driver may send) + * does not exceed max cmds that the FW can support + */ + instance->max_fw_cmds = + instance->func_ptr->read_fw_status_reg(instance) & 0xFFFF; + instance->max_fw_cmds = instance->max_fw_cmds - 1; + + instance->max_num_sge = + (instance->max_num_sge > DRSAS_MAX_SGE_CNT) ? + DRSAS_MAX_SGE_CNT : instance->max_num_sge; + + /* create a pool of commands */ + if (alloc_space_for_mfi(instance) != DDI_SUCCESS) + goto fail_alloc_fw_space; + + /* + * Prepare a init frame. Note the init frame points to queue info + * structure. Each frame has SGL allocated after first 64 bytes. For + * this frame - since we don't need any SGL - we use SGL's space as + * queue info structure + */ + cmd = get_mfi_pkt(instance); + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &cmd->frame->hdr.context, + cmd->index); + + init_frame = (struct drsas_init_frame *)cmd->frame; + initq_info = (struct drsas_init_queue_info *) + ((unsigned long)init_frame + 64); + + (void) memset(init_frame, 0, MRMFI_FRAME_SIZE); + (void) memset(initq_info, 0, sizeof (struct drsas_init_queue_info)); + + ddi_put32(cmd->frame_dma_obj.acc_handle, &initq_info->init_flags, 0); + + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->reply_queue_entries, instance->max_fw_cmds + 1); + + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->producer_index_phys_addr_hi, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->producer_index_phys_addr_lo, + instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address); + + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->consumer_index_phys_addr_hi, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->consumer_index_phys_addr_lo, + instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address + 4); + + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->reply_queue_start_phys_addr_hi, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &initq_info->reply_queue_start_phys_addr_lo, + instance->mfi_internal_dma_obj.dma_cookie[0].dmac_address + 8); + + ddi_put8(cmd->frame_dma_obj.acc_handle, + &init_frame->cmd, MFI_CMD_OP_INIT); + ddi_put8(cmd->frame_dma_obj.acc_handle, &init_frame->cmd_status, + MFI_CMD_STATUS_POLL_MODE); + ddi_put16(cmd->frame_dma_obj.acc_handle, &init_frame->flags, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &init_frame->queue_info_new_phys_addr_lo, + cmd->frame_phys_addr + 64); + ddi_put32(cmd->frame_dma_obj.acc_handle, + &init_frame->queue_info_new_phys_addr_hi, 0); + + ddi_put32(cmd->frame_dma_obj.acc_handle, &init_frame->data_xfer_len, + sizeof (struct drsas_init_queue_info)); + + cmd->frame_count = 1; + + /* issue the init frame in polled mode */ + if (instance->func_ptr->issue_cmd_in_poll_mode(instance, cmd)) { + con_log(CL_ANN, (CE_WARN, "failed to init firmware")); + goto fail_fw_init; + } + + return_mfi_pkt(instance, cmd); + if (drsas_common_check(instance, cmd) != DDI_SUCCESS) { + goto fail_fw_init; + } + + /* gather misc FW related information */ + if (!get_ctrl_info(instance, &ctrl_info)) { + instance->max_sectors_per_req = ctrl_info.max_request_size; + con_log(CL_ANN1, (CE_NOTE, "product name %s ld present %d", + ctrl_info.product_name, ctrl_info.ld_present_count)); + } else { + instance->max_sectors_per_req = instance->max_num_sge * + PAGESIZE / 512; + } + + if (drsas_check_acc_handle(instance->regmap_handle) != DDI_SUCCESS) { + goto fail_fw_init; + } + + return (DDI_SUCCESS); + +fail_fw_init: +fail_alloc_fw_space: + + free_space_for_mfi(instance); + +fail_ready_state: + ddi_regs_map_free(&instance->regmap_handle); + +fail_mfi_reg_setup: + return (DDI_FAILURE); +} + +/* + * mfi_state_transition_to_ready : Move the FW to READY state + * + * @reg_set : MFI register set + */ +static int +mfi_state_transition_to_ready(struct drsas_instance *instance) +{ + int i; + uint8_t max_wait; + uint32_t fw_ctrl; + uint32_t fw_state; + uint32_t cur_state; + + fw_state = + instance->func_ptr->read_fw_status_reg(instance) & MFI_STATE_MASK; + con_log(CL_ANN1, (CE_NOTE, + "mfi_state_transition_to_ready:FW state = 0x%x", fw_state)); + + while (fw_state != MFI_STATE_READY) { + con_log(CL_ANN, (CE_NOTE, + "mfi_state_transition_to_ready:FW state%x", fw_state)); + + switch (fw_state) { + case MFI_STATE_FAULT: + con_log(CL_ANN, (CE_NOTE, + "dr_sas: FW in FAULT state!!")); + + return (ENODEV); + case MFI_STATE_WAIT_HANDSHAKE: + /* set the CLR bit in IMR0 */ + con_log(CL_ANN, (CE_NOTE, + "dr_sas: FW waiting for HANDSHAKE")); + /* + * PCI_Hot Plug: MFI F/W requires + * (MFI_INIT_CLEAR_HANDSHAKE|MFI_INIT_HOTPLUG) + * to be set + */ + /* WR_IB_MSG_0(MFI_INIT_CLEAR_HANDSHAKE, instance); */ + WR_IB_DOORBELL(MFI_INIT_CLEAR_HANDSHAKE | + MFI_INIT_HOTPLUG, instance); + + max_wait = 2; + cur_state = MFI_STATE_WAIT_HANDSHAKE; + break; + case MFI_STATE_BOOT_MESSAGE_PENDING: + /* set the CLR bit in IMR0 */ + con_log(CL_ANN, (CE_NOTE, + "dr_sas: FW state boot message pending")); + /* + * PCI_Hot Plug: MFI F/W requires + * (MFI_INIT_CLEAR_HANDSHAKE|MFI_INIT_HOTPLUG) + * to be set + */ + WR_IB_DOORBELL(MFI_INIT_HOTPLUG, instance); + + max_wait = 10; + cur_state = MFI_STATE_BOOT_MESSAGE_PENDING; + break; + case MFI_STATE_OPERATIONAL: + /* bring it to READY state; assuming max wait 2 secs */ + instance->func_ptr->disable_intr(instance); + con_log(CL_ANN1, (CE_NOTE, + "dr_sas: FW in OPERATIONAL state")); + /* + * PCI_Hot Plug: MFI F/W requires + * (MFI_INIT_READY | MFI_INIT_MFIMODE | MFI_INIT_ABORT) + * to be set + */ + /* WR_IB_DOORBELL(MFI_INIT_READY, instance); */ + WR_IB_DOORBELL(MFI_RESET_FLAGS, instance); + + max_wait = 10; + cur_state = MFI_STATE_OPERATIONAL; + break; + case MFI_STATE_UNDEFINED: + /* this state should not last for more than 2 seconds */ + con_log(CL_ANN, (CE_NOTE, "FW state undefined")); + + max_wait = 2; + cur_state = MFI_STATE_UNDEFINED; + break; + case MFI_STATE_BB_INIT: + max_wait = 2; + cur_state = MFI_STATE_BB_INIT; + break; + case MFI_STATE_FW_INIT: + max_wait = 2; + cur_state = MFI_STATE_FW_INIT; + break; + case MFI_STATE_DEVICE_SCAN: + max_wait = 10; + cur_state = MFI_STATE_DEVICE_SCAN; + break; + default: + con_log(CL_ANN, (CE_NOTE, + "dr_sas: Unknown state 0x%x", fw_state)); + return (ENODEV); + } + + /* the cur_state should not last for more than max_wait secs */ + for (i = 0; i < (max_wait * MILLISEC); i++) { + /* fw_state = RD_OB_MSG_0(instance) & MFI_STATE_MASK; */ + fw_state = + instance->func_ptr->read_fw_status_reg(instance) & + MFI_STATE_MASK; + + if (fw_state == cur_state) { + delay(1 * drv_usectohz(MILLISEC)); + } else { + break; + } + } + + /* return error if fw_state hasn't changed after max_wait */ + if (fw_state == cur_state) { + con_log(CL_ANN, (CE_NOTE, + "FW state hasn't changed in %d secs", max_wait)); + return (ENODEV); + } + }; + + fw_ctrl = RD_IB_DOORBELL(instance); + + con_log(CL_ANN1, (CE_NOTE, + "mfi_state_transition_to_ready:FW ctrl = 0x%x", fw_ctrl)); + + /* + * Write 0xF to the doorbell register to do the following. + * - Abort all outstanding commands (bit 0). + * - Transition from OPERATIONAL to READY state (bit 1). + * - Discard (possible) low MFA posted in 64-bit mode (bit-2). + * - Set to release FW to continue running (i.e. BIOS handshake + * (bit 3). + */ + WR_IB_DOORBELL(0xF, instance); + + if (drsas_check_acc_handle(instance->regmap_handle) != DDI_SUCCESS) { + return (ENODEV); + } + return (DDI_SUCCESS); +} + +/* + * get_seq_num + */ +static int +get_seq_num(struct drsas_instance *instance, + struct drsas_evt_log_info *eli) +{ + int ret = DDI_SUCCESS; + + dma_obj_t dcmd_dma_obj; + struct drsas_cmd *cmd; + struct drsas_dcmd_frame *dcmd; + + cmd = get_mfi_pkt(instance); + + if (!cmd) { + cmn_err(CE_WARN, "dr_sas: failed to get a cmd"); + return (ENOMEM); + } + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &cmd->frame->hdr.context, + cmd->index); + + dcmd = &cmd->frame->dcmd; + + /* allocate the data transfer buffer */ + dcmd_dma_obj.size = sizeof (struct drsas_evt_log_info); + dcmd_dma_obj.dma_attr = drsas_generic_dma_attr; + dcmd_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + dcmd_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + dcmd_dma_obj.dma_attr.dma_attr_sgllen = 1; + dcmd_dma_obj.dma_attr.dma_attr_align = 1; + + if (drsas_alloc_dma_obj(instance, &dcmd_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, + "get_seq_num: could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + (void) memset(dcmd_dma_obj.buffer, 0, + sizeof (struct drsas_evt_log_info)); + + (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); + + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd, MFI_CMD_OP_DCMD); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd_status, 0); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->sge_count, 1); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->flags, + MFI_FRAME_DIR_READ); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->timeout, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->data_xfer_len, + sizeof (struct drsas_evt_log_info)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->opcode, + DR_DCMD_CTRL_EVENT_GET_INFO); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->sgl.sge32[0].length, + sizeof (struct drsas_evt_log_info)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->sgl.sge32[0].phys_addr, + dcmd_dma_obj.dma_cookie[0].dmac_address); + + cmd->sync_cmd = DRSAS_TRUE; + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { + cmn_err(CE_WARN, "get_seq_num: " + "failed to issue DRSAS_DCMD_CTRL_EVENT_GET_INFO"); + ret = DDI_FAILURE; + } else { + /* copy the data back into callers buffer */ + ddi_rep_get8(cmd->frame_dma_obj.acc_handle, (uint8_t *)eli, + (uint8_t *)dcmd_dma_obj.buffer, + sizeof (struct drsas_evt_log_info), DDI_DEV_AUTOINCR); + ret = DDI_SUCCESS; + } + + if (drsas_free_dma_obj(instance, dcmd_dma_obj) != DDI_SUCCESS) + ret = DDI_FAILURE; + + return_mfi_pkt(instance, cmd); + if (drsas_common_check(instance, cmd) != DDI_SUCCESS) { + ret = DDI_FAILURE; + } + return (ret); +} + +/* + * start_mfi_aen + */ +static int +start_mfi_aen(struct drsas_instance *instance) +{ + int ret = 0; + + struct drsas_evt_log_info eli; + union drsas_evt_class_locale class_locale; + + /* get the latest sequence number from FW */ + (void) memset(&eli, 0, sizeof (struct drsas_evt_log_info)); + + if (get_seq_num(instance, &eli)) { + cmn_err(CE_WARN, "start_mfi_aen: failed to get seq num"); + return (-1); + } + + /* register AEN with FW for latest sequence number plus 1 */ + class_locale.members.reserved = 0; + class_locale.members.locale = DR_EVT_LOCALE_ALL; + class_locale.members.class = DR_EVT_CLASS_INFO; + ret = register_mfi_aen(instance, eli.newest_seq_num + 1, + class_locale.word); + + if (ret) { + cmn_err(CE_WARN, "start_mfi_aen: aen registration failed"); + return (-1); + } + + return (ret); +} + +/* + * flush_cache + */ +static void +flush_cache(struct drsas_instance *instance) +{ + struct drsas_cmd *cmd = NULL; + struct drsas_dcmd_frame *dcmd; + uint32_t max_cmd = instance->max_fw_cmds; + + cmd = instance->cmd_list[max_cmd]; + + if (cmd == NULL) + return; + + dcmd = &cmd->frame->dcmd; + + (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); + + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd, MFI_CMD_OP_DCMD); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd_status, 0x0); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->sge_count, 0); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->flags, + MFI_FRAME_DIR_NONE); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->timeout, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->data_xfer_len, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->opcode, + DR_DCMD_CTRL_CACHE_FLUSH); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->mbox.b[0], + DR_FLUSH_CTRL_CACHE | DR_FLUSH_DISK_CACHE); + + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_poll_mode(instance, cmd)) { + con_log(CL_ANN1, (CE_WARN, + "flush_cache: failed to issue MFI_DCMD_CTRL_CACHE_FLUSH")); + } + con_log(CL_DLEVEL1, (CE_NOTE, "done")); +} + +/* + * service_mfi_aen- Completes an AEN command + * @instance: Adapter soft state + * @cmd: Command to be completed + * + */ +static void +service_mfi_aen(struct drsas_instance *instance, struct drsas_cmd *cmd) +{ + uint32_t seq_num; + struct drsas_evt_detail *evt_detail = + (struct drsas_evt_detail *)instance->mfi_evt_detail_obj.buffer; + int rval = 0; + int tgt = 0; + ddi_acc_handle_t acc_handle; + + acc_handle = cmd->frame_dma_obj.acc_handle; + + cmd->cmd_status = ddi_get8(acc_handle, &cmd->frame->io.cmd_status); + + if (cmd->cmd_status == ENODATA) { + cmd->cmd_status = 0; + } + + /* + * log the MFI AEN event to the sysevent queue so that + * application will get noticed + */ + if (ddi_log_sysevent(instance->dip, DDI_VENDOR_LSI, "LSIMEGA", "SAS", + NULL, NULL, DDI_NOSLEEP) != DDI_SUCCESS) { + int instance_no = ddi_get_instance(instance->dip); + con_log(CL_ANN, (CE_WARN, + "dr_sas%d: Failed to log AEN event", instance_no)); + } + /* + * Check for any ld devices that has changed state. i.e. online + * or offline. + */ + con_log(CL_ANN1, (CE_NOTE, + "AEN: code = %x class = %x locale = %x args = %x", + ddi_get32(acc_handle, &evt_detail->code), + evt_detail->cl.members.class, + ddi_get16(acc_handle, &evt_detail->cl.members.locale), + ddi_get8(acc_handle, &evt_detail->arg_type))); + + switch (ddi_get32(acc_handle, &evt_detail->code)) { + case DR_EVT_CFG_CLEARED: { + for (tgt = 0; tgt < MRDRV_MAX_LD; tgt++) { + if (instance->dr_ld_list[tgt].dip != NULL) { + rval = drsas_service_evt(instance, tgt, 0, + DRSAS_EVT_UNCONFIG_TGT, NULL); + con_log(CL_ANN1, (CE_WARN, + "dr_sas: CFG CLEARED AEN rval = %d " + "tgt id = %d", rval, tgt)); + } + } + break; + } + + case DR_EVT_LD_DELETED: { + rval = drsas_service_evt(instance, + ddi_get16(acc_handle, &evt_detail->args.ld.target_id), 0, + DRSAS_EVT_UNCONFIG_TGT, NULL); + con_log(CL_ANN1, (CE_WARN, "dr_sas: LD DELETED AEN rval = %d " + "tgt id = %d index = %d", rval, + ddi_get16(acc_handle, &evt_detail->args.ld.target_id), + ddi_get8(acc_handle, &evt_detail->args.ld.ld_index))); + break; + } /* End of DR_EVT_LD_DELETED */ + + case DR_EVT_LD_CREATED: { + rval = drsas_service_evt(instance, + ddi_get16(acc_handle, &evt_detail->args.ld.target_id), 0, + DRSAS_EVT_CONFIG_TGT, NULL); + con_log(CL_ANN1, (CE_WARN, "dr_sas: LD CREATED AEN rval = %d " + "tgt id = %d index = %d", rval, + ddi_get16(acc_handle, &evt_detail->args.ld.target_id), + ddi_get8(acc_handle, &evt_detail->args.ld.ld_index))); + break; + } /* End of DR_EVT_LD_CREATED */ + } /* End of Main Switch */ + + /* get copy of seq_num and class/locale for re-registration */ + seq_num = ddi_get32(acc_handle, &evt_detail->seq_num); + seq_num++; + (void) memset(instance->mfi_evt_detail_obj.buffer, 0, + sizeof (struct drsas_evt_detail)); + + ddi_put8(acc_handle, &cmd->frame->dcmd.cmd_status, 0x0); + ddi_put32(acc_handle, &cmd->frame->dcmd.mbox.w[0], seq_num); + + instance->aen_seq_num = seq_num; + + cmd->frame_count = 1; + + /* Issue the aen registration frame */ + instance->func_ptr->issue_cmd(cmd, instance); +} + +/* + * complete_cmd_in_sync_mode - Completes an internal command + * @instance: Adapter soft state + * @cmd: Command to be completed + * + * The issue_cmd_in_sync_mode() function waits for a command to complete + * after it issues a command. This function wakes up that waiting routine by + * calling wake_up() on the wait queue. + */ +static void +complete_cmd_in_sync_mode(struct drsas_instance *instance, + struct drsas_cmd *cmd) +{ + cmd->cmd_status = ddi_get8(cmd->frame_dma_obj.acc_handle, + &cmd->frame->io.cmd_status); + + cmd->sync_cmd = DRSAS_FALSE; + + if (cmd->cmd_status == ENODATA) { + cmd->cmd_status = 0; + } + + cv_broadcast(&instance->int_cmd_cv); +} + +/* + * drsas_softintr - The Software ISR + * @param arg : HBA soft state + * + * called from high-level interrupt if hi-level interrupt are not there, + * otherwise triggered as a soft interrupt + */ +static uint_t +drsas_softintr(struct drsas_instance *instance) +{ + struct scsi_pkt *pkt; + struct scsa_cmd *acmd; + struct drsas_cmd *cmd; + struct mlist_head *pos, *next; + mlist_t process_list; + struct drsas_header *hdr; + struct scsi_arq_status *arqstat; + + con_log(CL_ANN1, (CE_CONT, "drsas_softintr called")); + + ASSERT(instance); + mutex_enter(&instance->completed_pool_mtx); + + if (mlist_empty(&instance->completed_pool_list)) { + mutex_exit(&instance->completed_pool_mtx); + return (DDI_INTR_UNCLAIMED); + } + + instance->softint_running = 1; + + INIT_LIST_HEAD(&process_list); + mlist_splice(&instance->completed_pool_list, &process_list); + INIT_LIST_HEAD(&instance->completed_pool_list); + + mutex_exit(&instance->completed_pool_mtx); + + /* perform all callbacks first, before releasing the SCBs */ + mlist_for_each_safe(pos, next, &process_list) { + cmd = mlist_entry(pos, struct drsas_cmd, list); + + /* syncronize the Cmd frame for the controller */ + (void) ddi_dma_sync(cmd->frame_dma_obj.dma_handle, + 0, 0, DDI_DMA_SYNC_FORCPU); + + if (drsas_check_dma_handle(cmd->frame_dma_obj.dma_handle) != + DDI_SUCCESS) { + drsas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); + ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); + return (DDI_INTR_UNCLAIMED); + } + + hdr = &cmd->frame->hdr; + + /* remove the internal command from the process list */ + mlist_del_init(&cmd->list); + + switch (ddi_get8(cmd->frame_dma_obj.acc_handle, &hdr->cmd)) { + case MFI_CMD_OP_PD_SCSI: + case MFI_CMD_OP_LD_SCSI: + case MFI_CMD_OP_LD_READ: + case MFI_CMD_OP_LD_WRITE: + /* + * MFI_CMD_OP_PD_SCSI and MFI_CMD_OP_LD_SCSI + * could have been issued either through an + * IO path or an IOCTL path. If it was via IOCTL, + * we will send it to internal completion. + */ + if (cmd->sync_cmd == DRSAS_TRUE) { + complete_cmd_in_sync_mode(instance, cmd); + break; + } + + /* regular commands */ + acmd = cmd->cmd; + pkt = CMD2PKT(acmd); + + if (acmd->cmd_flags & CFLAG_DMAVALID) { + if (acmd->cmd_flags & CFLAG_CONSISTENT) { + (void) ddi_dma_sync(acmd->cmd_dmahandle, + acmd->cmd_dma_offset, + acmd->cmd_dma_len, + DDI_DMA_SYNC_FORCPU); + } + } + + pkt->pkt_reason = CMD_CMPLT; + pkt->pkt_statistics = 0; + pkt->pkt_state = STATE_GOT_BUS + | STATE_GOT_TARGET | STATE_SENT_CMD + | STATE_XFERRED_DATA | STATE_GOT_STATUS; + + con_log(CL_ANN1, (CE_CONT, + "CDB[0] = %x completed for %s: size %lx context %x", + pkt->pkt_cdbp[0], ((acmd->islogical) ? "LD" : "PD"), + acmd->cmd_dmacount, hdr->context)); + + if (pkt->pkt_cdbp[0] == SCMD_INQUIRY) { + struct scsi_inquiry *inq; + + if (acmd->cmd_dmacount != 0) { + bp_mapin(acmd->cmd_buf); + inq = (struct scsi_inquiry *) + acmd->cmd_buf->b_un.b_addr; + + /* don't expose physical drives to OS */ + if (acmd->islogical && + (hdr->cmd_status == MFI_STAT_OK)) { + display_scsi_inquiry( + (caddr_t)inq); + } else if ((hdr->cmd_status == + MFI_STAT_OK) && inq->inq_dtype == + DTYPE_DIRECT) { + + display_scsi_inquiry( + (caddr_t)inq); + + /* for physical disk */ + hdr->cmd_status = + MFI_STAT_DEVICE_NOT_FOUND; + } + } + } + + switch (hdr->cmd_status) { + case MFI_STAT_OK: + pkt->pkt_scbp[0] = STATUS_GOOD; + break; + case MFI_STAT_LD_CC_IN_PROGRESS: + case MFI_STAT_LD_RECON_IN_PROGRESS: + pkt->pkt_scbp[0] = STATUS_GOOD; + break; + case MFI_STAT_LD_INIT_IN_PROGRESS: + con_log(CL_ANN, + (CE_WARN, "Initialization in Progress")); + pkt->pkt_reason = CMD_TRAN_ERR; + + break; + case MFI_STAT_SCSI_DONE_WITH_ERROR: + con_log(CL_ANN1, (CE_CONT, "scsi_done error")); + + pkt->pkt_reason = CMD_CMPLT; + ((struct scsi_status *) + pkt->pkt_scbp)->sts_chk = 1; + + if (pkt->pkt_cdbp[0] == SCMD_TEST_UNIT_READY) { + + con_log(CL_ANN, + (CE_WARN, "TEST_UNIT_READY fail")); + + } else { + pkt->pkt_state |= STATE_ARQ_DONE; + arqstat = (void *)(pkt->pkt_scbp); + arqstat->sts_rqpkt_reason = CMD_CMPLT; + arqstat->sts_rqpkt_resid = 0; + arqstat->sts_rqpkt_state |= + STATE_GOT_BUS | STATE_GOT_TARGET + | STATE_SENT_CMD + | STATE_XFERRED_DATA; + *(uint8_t *)&arqstat->sts_rqpkt_status = + STATUS_GOOD; + ddi_rep_get8( + cmd->frame_dma_obj.acc_handle, + (uint8_t *) + &(arqstat->sts_sensedata), + cmd->sense, + acmd->cmd_scblen - + offsetof(struct scsi_arq_status, + sts_sensedata), DDI_DEV_AUTOINCR); + } + break; + case MFI_STAT_LD_OFFLINE: + case MFI_STAT_DEVICE_NOT_FOUND: + con_log(CL_ANN1, (CE_CONT, + "device not found error")); + pkt->pkt_reason = CMD_DEV_GONE; + pkt->pkt_statistics = STAT_DISCON; + break; + case MFI_STAT_LD_LBA_OUT_OF_RANGE: + pkt->pkt_state |= STATE_ARQ_DONE; + pkt->pkt_reason = CMD_CMPLT; + ((struct scsi_status *) + pkt->pkt_scbp)->sts_chk = 1; + + arqstat = (void *)(pkt->pkt_scbp); + arqstat->sts_rqpkt_reason = CMD_CMPLT; + arqstat->sts_rqpkt_resid = 0; + arqstat->sts_rqpkt_state |= STATE_GOT_BUS + | STATE_GOT_TARGET | STATE_SENT_CMD + | STATE_XFERRED_DATA; + *(uint8_t *)&arqstat->sts_rqpkt_status = + STATUS_GOOD; + + arqstat->sts_sensedata.es_valid = 1; + arqstat->sts_sensedata.es_key = + KEY_ILLEGAL_REQUEST; + arqstat->sts_sensedata.es_class = + CLASS_EXTENDED_SENSE; + + /* + * LOGICAL BLOCK ADDRESS OUT OF RANGE: + * ASC: 0x21h; ASCQ: 0x00h; + */ + arqstat->sts_sensedata.es_add_code = 0x21; + arqstat->sts_sensedata.es_qual_code = 0x00; + + break; + + default: + con_log(CL_ANN, (CE_CONT, "Unknown status!")); + pkt->pkt_reason = CMD_TRAN_ERR; + + break; + } + + atomic_add_16(&instance->fw_outstanding, (-1)); + + return_mfi_pkt(instance, cmd); + + (void) drsas_common_check(instance, cmd); + + if (acmd->cmd_dmahandle) { + if (drsas_check_dma_handle( + acmd->cmd_dmahandle) != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, + DDI_SERVICE_UNAFFECTED); + pkt->pkt_reason = CMD_TRAN_ERR; + pkt->pkt_statistics = 0; + } + } + + /* Call the callback routine */ + if (((pkt->pkt_flags & FLAG_NOINTR) == 0) && + pkt->pkt_comp) { + (*pkt->pkt_comp)(pkt); + } + + break; + case MFI_CMD_OP_SMP: + case MFI_CMD_OP_STP: + complete_cmd_in_sync_mode(instance, cmd); + break; + case MFI_CMD_OP_DCMD: + /* see if got an event notification */ + if (ddi_get32(cmd->frame_dma_obj.acc_handle, + &cmd->frame->dcmd.opcode) == + DR_DCMD_CTRL_EVENT_WAIT) { + if ((instance->aen_cmd == cmd) && + (instance->aen_cmd->abort_aen)) { + con_log(CL_ANN, (CE_WARN, + "drsas_softintr: " + "aborted_aen returned")); + } else { + atomic_add_16(&instance->fw_outstanding, + (-1)); + service_mfi_aen(instance, cmd); + } + } else { + complete_cmd_in_sync_mode(instance, cmd); + } + + break; + case MFI_CMD_OP_ABORT: + con_log(CL_ANN, (CE_WARN, "MFI_CMD_OP_ABORT complete")); + /* + * MFI_CMD_OP_ABORT successfully completed + * in the synchronous mode + */ + complete_cmd_in_sync_mode(instance, cmd); + break; + default: + drsas_fm_ereport(instance, DDI_FM_DEVICE_NO_RESPONSE); + ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); + + if (cmd->pkt != NULL) { + pkt = cmd->pkt; + if (((pkt->pkt_flags & FLAG_NOINTR) == 0) && + pkt->pkt_comp) { + (*pkt->pkt_comp)(pkt); + } + } + con_log(CL_ANN, (CE_WARN, "Cmd type unknown !")); + break; + } + } + + instance->softint_running = 0; + + return (DDI_INTR_CLAIMED); +} + +/* + * drsas_alloc_dma_obj + * + * Allocate the memory and other resources for an dma object. + */ +static int +drsas_alloc_dma_obj(struct drsas_instance *instance, dma_obj_t *obj, + uchar_t endian_flags) +{ + int i; + size_t alen = 0; + uint_t cookie_cnt; + struct ddi_device_acc_attr tmp_endian_attr; + + tmp_endian_attr = endian_attr; + tmp_endian_attr.devacc_attr_endian_flags = endian_flags; + + i = ddi_dma_alloc_handle(instance->dip, &obj->dma_attr, + DDI_DMA_SLEEP, NULL, &obj->dma_handle); + if (i != DDI_SUCCESS) { + + switch (i) { + case DDI_DMA_BADATTR : + con_log(CL_ANN, (CE_WARN, + "Failed ddi_dma_alloc_handle- Bad attribute")); + break; + case DDI_DMA_NORESOURCES : + con_log(CL_ANN, (CE_WARN, + "Failed ddi_dma_alloc_handle- No Resources")); + break; + default : + con_log(CL_ANN, (CE_WARN, + "Failed ddi_dma_alloc_handle: " + "unknown status %d", i)); + break; + } + + return (-1); + } + + if ((ddi_dma_mem_alloc(obj->dma_handle, obj->size, &tmp_endian_attr, + DDI_DMA_RDWR | DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, + &obj->buffer, &alen, &obj->acc_handle) != DDI_SUCCESS) || + alen < obj->size) { + + ddi_dma_free_handle(&obj->dma_handle); + + con_log(CL_ANN, (CE_WARN, "Failed : ddi_dma_mem_alloc")); + + return (-1); + } + + if (ddi_dma_addr_bind_handle(obj->dma_handle, NULL, obj->buffer, + obj->size, DDI_DMA_RDWR | DDI_DMA_STREAMING, DDI_DMA_SLEEP, + NULL, &obj->dma_cookie[0], &cookie_cnt) != DDI_SUCCESS) { + + ddi_dma_mem_free(&obj->acc_handle); + ddi_dma_free_handle(&obj->dma_handle); + + con_log(CL_ANN, (CE_WARN, "Failed : ddi_dma_addr_bind_handle")); + + return (-1); + } + + if (drsas_check_dma_handle(obj->dma_handle) != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); + return (-1); + } + + if (drsas_check_acc_handle(obj->acc_handle) != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_LOST); + return (-1); + } + + return (cookie_cnt); +} + +/* + * drsas_free_dma_obj(struct drsas_instance *, dma_obj_t) + * + * De-allocate the memory and other resources for an dma object, which must + * have been alloated by a previous call to drsas_alloc_dma_obj() + */ +static int +drsas_free_dma_obj(struct drsas_instance *instance, dma_obj_t obj) +{ + + if (drsas_check_dma_handle(obj.dma_handle) != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_UNAFFECTED); + return (DDI_FAILURE); + } + + if (drsas_check_acc_handle(obj.acc_handle) != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_UNAFFECTED); + return (DDI_FAILURE); + } + + (void) ddi_dma_unbind_handle(obj.dma_handle); + ddi_dma_mem_free(&obj.acc_handle); + ddi_dma_free_handle(&obj.dma_handle); + + return (DDI_SUCCESS); +} + +/* + * drsas_dma_alloc(instance_t *, struct scsi_pkt *, struct buf *, + * int, int (*)()) + * + * Allocate dma resources for a new scsi command + */ +static int +drsas_dma_alloc(struct drsas_instance *instance, struct scsi_pkt *pkt, + struct buf *bp, int flags, int (*callback)()) +{ + int dma_flags; + int (*cb)(caddr_t); + int i; + + ddi_dma_attr_t tmp_dma_attr = drsas_generic_dma_attr; + struct scsa_cmd *acmd = PKT2CMD(pkt); + + acmd->cmd_buf = bp; + + if (bp->b_flags & B_READ) { + acmd->cmd_flags &= ~CFLAG_DMASEND; + dma_flags = DDI_DMA_READ; + } else { + acmd->cmd_flags |= CFLAG_DMASEND; + dma_flags = DDI_DMA_WRITE; + } + + if (flags & PKT_CONSISTENT) { + acmd->cmd_flags |= CFLAG_CONSISTENT; + dma_flags |= DDI_DMA_CONSISTENT; + } + + if (flags & PKT_DMA_PARTIAL) { + dma_flags |= DDI_DMA_PARTIAL; + } + + dma_flags |= DDI_DMA_REDZONE; + + cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; + + tmp_dma_attr.dma_attr_sgllen = instance->max_num_sge; + tmp_dma_attr.dma_attr_addr_hi = 0xffffffffffffffffull; + + if ((i = ddi_dma_alloc_handle(instance->dip, &tmp_dma_attr, + cb, 0, &acmd->cmd_dmahandle)) != DDI_SUCCESS) { + switch (i) { + case DDI_DMA_BADATTR: + bioerror(bp, EFAULT); + return (DDI_FAILURE); + + case DDI_DMA_NORESOURCES: + bioerror(bp, 0); + return (DDI_FAILURE); + + default: + con_log(CL_ANN, (CE_PANIC, "ddi_dma_alloc_handle: " + "impossible result (0x%x)", i)); + bioerror(bp, EFAULT); + return (DDI_FAILURE); + } + } + + i = ddi_dma_buf_bind_handle(acmd->cmd_dmahandle, bp, dma_flags, + cb, 0, &acmd->cmd_dmacookies[0], &acmd->cmd_ncookies); + + switch (i) { + case DDI_DMA_PARTIAL_MAP: + if ((dma_flags & DDI_DMA_PARTIAL) == 0) { + con_log(CL_ANN, (CE_PANIC, "ddi_dma_buf_bind_handle: " + "DDI_DMA_PARTIAL_MAP impossible")); + goto no_dma_cookies; + } + + if (ddi_dma_numwin(acmd->cmd_dmahandle, &acmd->cmd_nwin) == + DDI_FAILURE) { + con_log(CL_ANN, (CE_PANIC, "ddi_dma_numwin failed")); + goto no_dma_cookies; + } + + if (ddi_dma_getwin(acmd->cmd_dmahandle, acmd->cmd_curwin, + &acmd->cmd_dma_offset, &acmd->cmd_dma_len, + &acmd->cmd_dmacookies[0], &acmd->cmd_ncookies) == + DDI_FAILURE) { + + con_log(CL_ANN, (CE_PANIC, "ddi_dma_getwin failed")); + goto no_dma_cookies; + } + + goto get_dma_cookies; + case DDI_DMA_MAPPED: + acmd->cmd_nwin = 1; + acmd->cmd_dma_len = 0; + acmd->cmd_dma_offset = 0; + +get_dma_cookies: + i = 0; + acmd->cmd_dmacount = 0; + for (;;) { + acmd->cmd_dmacount += + acmd->cmd_dmacookies[i++].dmac_size; + + if (i == instance->max_num_sge || + i == acmd->cmd_ncookies) + break; + + ddi_dma_nextcookie(acmd->cmd_dmahandle, + &acmd->cmd_dmacookies[i]); + } + + acmd->cmd_cookie = i; + acmd->cmd_cookiecnt = i; + + acmd->cmd_flags |= CFLAG_DMAVALID; + + if (bp->b_bcount >= acmd->cmd_dmacount) { + pkt->pkt_resid = bp->b_bcount - acmd->cmd_dmacount; + } else { + pkt->pkt_resid = 0; + } + + return (DDI_SUCCESS); + case DDI_DMA_NORESOURCES: + bioerror(bp, 0); + break; + case DDI_DMA_NOMAPPING: + bioerror(bp, EFAULT); + break; + case DDI_DMA_TOOBIG: + bioerror(bp, EINVAL); + break; + case DDI_DMA_INUSE: + con_log(CL_ANN, (CE_PANIC, "ddi_dma_buf_bind_handle:" + " DDI_DMA_INUSE impossible")); + break; + default: + con_log(CL_ANN, (CE_PANIC, "ddi_dma_buf_bind_handle: " + "impossible result (0x%x)", i)); + break; + } + +no_dma_cookies: + ddi_dma_free_handle(&acmd->cmd_dmahandle); + acmd->cmd_dmahandle = NULL; + acmd->cmd_flags &= ~CFLAG_DMAVALID; + return (DDI_FAILURE); +} + +/* + * drsas_dma_move(struct drsas_instance *, struct scsi_pkt *, struct buf *) + * + * move dma resources to next dma window + * + */ +static int +drsas_dma_move(struct drsas_instance *instance, struct scsi_pkt *pkt, + struct buf *bp) +{ + int i = 0; + + struct scsa_cmd *acmd = PKT2CMD(pkt); + + /* + * If there are no more cookies remaining in this window, + * must move to the next window first. + */ + if (acmd->cmd_cookie == acmd->cmd_ncookies) { + if (acmd->cmd_curwin == acmd->cmd_nwin && acmd->cmd_nwin == 1) { + return (DDI_SUCCESS); + } + + /* at last window, cannot move */ + if (++acmd->cmd_curwin >= acmd->cmd_nwin) { + return (DDI_FAILURE); + } + + if (ddi_dma_getwin(acmd->cmd_dmahandle, acmd->cmd_curwin, + &acmd->cmd_dma_offset, &acmd->cmd_dma_len, + &acmd->cmd_dmacookies[0], &acmd->cmd_ncookies) == + DDI_FAILURE) { + return (DDI_FAILURE); + } + + acmd->cmd_cookie = 0; + } else { + /* still more cookies in this window - get the next one */ + ddi_dma_nextcookie(acmd->cmd_dmahandle, + &acmd->cmd_dmacookies[0]); + } + + /* get remaining cookies in this window, up to our maximum */ + for (;;) { + acmd->cmd_dmacount += acmd->cmd_dmacookies[i++].dmac_size; + acmd->cmd_cookie++; + + if (i == instance->max_num_sge || + acmd->cmd_cookie == acmd->cmd_ncookies) { + break; + } + + ddi_dma_nextcookie(acmd->cmd_dmahandle, + &acmd->cmd_dmacookies[i]); + } + + acmd->cmd_cookiecnt = i; + + if (bp->b_bcount >= acmd->cmd_dmacount) { + pkt->pkt_resid = bp->b_bcount - acmd->cmd_dmacount; + } else { + pkt->pkt_resid = 0; + } + + return (DDI_SUCCESS); +} + +/* + * build_cmd + */ +static struct drsas_cmd * +build_cmd(struct drsas_instance *instance, struct scsi_address *ap, + struct scsi_pkt *pkt, uchar_t *cmd_done) +{ + uint16_t flags = 0; + uint32_t i; + uint32_t context; + uint32_t sge_bytes; + ddi_acc_handle_t acc_handle; + struct drsas_cmd *cmd; + struct drsas_sge64 *mfi_sgl; + struct scsa_cmd *acmd = PKT2CMD(pkt); + struct drsas_pthru_frame *pthru; + struct drsas_io_frame *ldio; + + /* find out if this is logical or physical drive command. */ + acmd->islogical = MRDRV_IS_LOGICAL(ap); + acmd->device_id = MAP_DEVICE_ID(instance, ap); + *cmd_done = 0; + + /* get the command packet */ + if (!(cmd = get_mfi_pkt(instance))) { + return (NULL); + } + + acc_handle = cmd->frame_dma_obj.acc_handle; + + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(acc_handle, &cmd->frame->hdr.context, cmd->index); + + cmd->pkt = pkt; + cmd->cmd = acmd; + + /* lets get the command directions */ + if (acmd->cmd_flags & CFLAG_DMASEND) { + flags = MFI_FRAME_DIR_WRITE; + + if (acmd->cmd_flags & CFLAG_CONSISTENT) { + (void) ddi_dma_sync(acmd->cmd_dmahandle, + acmd->cmd_dma_offset, acmd->cmd_dma_len, + DDI_DMA_SYNC_FORDEV); + } + } else if (acmd->cmd_flags & ~CFLAG_DMASEND) { + flags = MFI_FRAME_DIR_READ; + + if (acmd->cmd_flags & CFLAG_CONSISTENT) { + (void) ddi_dma_sync(acmd->cmd_dmahandle, + acmd->cmd_dma_offset, acmd->cmd_dma_len, + DDI_DMA_SYNC_FORCPU); + } + } else { + flags = MFI_FRAME_DIR_NONE; + } + + flags |= MFI_FRAME_SGL64; + + switch (pkt->pkt_cdbp[0]) { + + /* + * case SCMD_SYNCHRONIZE_CACHE: + * flush_cache(instance); + * return_mfi_pkt(instance, cmd); + * *cmd_done = 1; + * + * return (NULL); + */ + + case SCMD_READ: + case SCMD_WRITE: + case SCMD_READ_G1: + case SCMD_WRITE_G1: + if (acmd->islogical) { + ldio = (struct drsas_io_frame *)cmd->frame; + + /* + * preare the Logical IO frame: + * 2nd bit is zero for all read cmds + */ + ddi_put8(acc_handle, &ldio->cmd, + (pkt->pkt_cdbp[0] & 0x02) ? MFI_CMD_OP_LD_WRITE + : MFI_CMD_OP_LD_READ); + ddi_put8(acc_handle, &ldio->cmd_status, 0x0); + ddi_put8(acc_handle, &ldio->scsi_status, 0x0); + ddi_put8(acc_handle, &ldio->target_id, acmd->device_id); + ddi_put16(acc_handle, &ldio->timeout, 0); + ddi_put8(acc_handle, &ldio->reserved_0, 0); + ddi_put16(acc_handle, &ldio->pad_0, 0); + ddi_put16(acc_handle, &ldio->flags, flags); + + /* Initialize sense Information */ + bzero(cmd->sense, SENSE_LENGTH); + ddi_put8(acc_handle, &ldio->sense_len, SENSE_LENGTH); + ddi_put32(acc_handle, &ldio->sense_buf_phys_addr_hi, 0); + ddi_put32(acc_handle, &ldio->sense_buf_phys_addr_lo, + cmd->sense_phys_addr); + ddi_put32(acc_handle, &ldio->start_lba_hi, 0); + ddi_put8(acc_handle, &ldio->access_byte, + (acmd->cmd_cdblen != 6) ? pkt->pkt_cdbp[1] : 0); + ddi_put8(acc_handle, &ldio->sge_count, + acmd->cmd_cookiecnt); + mfi_sgl = (struct drsas_sge64 *)&ldio->sgl; + + context = ddi_get32(acc_handle, &ldio->context); + + if (acmd->cmd_cdblen == CDB_GROUP0) { + ddi_put32(acc_handle, &ldio->lba_count, ( + (uint16_t)(pkt->pkt_cdbp[4]))); + + ddi_put32(acc_handle, &ldio->start_lba_lo, ( + ((uint32_t)(pkt->pkt_cdbp[3])) | + ((uint32_t)(pkt->pkt_cdbp[2]) << 8) | + ((uint32_t)((pkt->pkt_cdbp[1]) & 0x1F) + << 16))); + } else if (acmd->cmd_cdblen == CDB_GROUP1) { + ddi_put32(acc_handle, &ldio->lba_count, ( + ((uint16_t)(pkt->pkt_cdbp[8])) | + ((uint16_t)(pkt->pkt_cdbp[7]) << 8))); + + ddi_put32(acc_handle, &ldio->start_lba_lo, ( + ((uint32_t)(pkt->pkt_cdbp[5])) | + ((uint32_t)(pkt->pkt_cdbp[4]) << 8) | + ((uint32_t)(pkt->pkt_cdbp[3]) << 16) | + ((uint32_t)(pkt->pkt_cdbp[2]) << 24))); + } else if (acmd->cmd_cdblen == CDB_GROUP2) { + ddi_put32(acc_handle, &ldio->lba_count, ( + ((uint16_t)(pkt->pkt_cdbp[9])) | + ((uint16_t)(pkt->pkt_cdbp[8]) << 8) | + ((uint16_t)(pkt->pkt_cdbp[7]) << 16) | + ((uint16_t)(pkt->pkt_cdbp[6]) << 24))); + + ddi_put32(acc_handle, &ldio->start_lba_lo, ( + ((uint32_t)(pkt->pkt_cdbp[5])) | + ((uint32_t)(pkt->pkt_cdbp[4]) << 8) | + ((uint32_t)(pkt->pkt_cdbp[3]) << 16) | + ((uint32_t)(pkt->pkt_cdbp[2]) << 24))); + } else if (acmd->cmd_cdblen == CDB_GROUP3) { + ddi_put32(acc_handle, &ldio->lba_count, ( + ((uint16_t)(pkt->pkt_cdbp[13])) | + ((uint16_t)(pkt->pkt_cdbp[12]) << 8) | + ((uint16_t)(pkt->pkt_cdbp[11]) << 16) | + ((uint16_t)(pkt->pkt_cdbp[10]) << 24))); + + ddi_put32(acc_handle, &ldio->start_lba_lo, ( + ((uint32_t)(pkt->pkt_cdbp[9])) | + ((uint32_t)(pkt->pkt_cdbp[8]) << 8) | + ((uint32_t)(pkt->pkt_cdbp[7]) << 16) | + ((uint32_t)(pkt->pkt_cdbp[6]) << 24))); + + ddi_put32(acc_handle, &ldio->start_lba_lo, ( + ((uint32_t)(pkt->pkt_cdbp[5])) | + ((uint32_t)(pkt->pkt_cdbp[4]) << 8) | + ((uint32_t)(pkt->pkt_cdbp[3]) << 16) | + ((uint32_t)(pkt->pkt_cdbp[2]) << 24))); + } + + break; + } + /* fall through For all non-rd/wr cmds */ + default: + + switch (pkt->pkt_cdbp[0]) { + case SCMD_MODE_SENSE: + case SCMD_MODE_SENSE_G1: { + union scsi_cdb *cdbp; + uint16_t page_code; + + cdbp = (void *)pkt->pkt_cdbp; + page_code = (uint16_t)cdbp->cdb_un.sg.scsi[0]; + switch (page_code) { + case 0x3: + case 0x4: + (void) drsas_mode_sense_build(pkt); + return_mfi_pkt(instance, cmd); + *cmd_done = 1; + return (NULL); + } + break; + } + default: + break; + } + + pthru = (struct drsas_pthru_frame *)cmd->frame; + + /* prepare the DCDB frame */ + ddi_put8(acc_handle, &pthru->cmd, (acmd->islogical) ? + MFI_CMD_OP_LD_SCSI : MFI_CMD_OP_PD_SCSI); + ddi_put8(acc_handle, &pthru->cmd_status, 0x0); + ddi_put8(acc_handle, &pthru->scsi_status, 0x0); + ddi_put8(acc_handle, &pthru->target_id, acmd->device_id); + ddi_put8(acc_handle, &pthru->lun, 0); + ddi_put8(acc_handle, &pthru->cdb_len, acmd->cmd_cdblen); + ddi_put16(acc_handle, &pthru->timeout, 0); + ddi_put16(acc_handle, &pthru->flags, flags); + ddi_put32(acc_handle, &pthru->data_xfer_len, + acmd->cmd_dmacount); + ddi_put8(acc_handle, &pthru->sge_count, acmd->cmd_cookiecnt); + mfi_sgl = (struct drsas_sge64 *)&pthru->sgl; + + bzero(cmd->sense, SENSE_LENGTH); + ddi_put8(acc_handle, &pthru->sense_len, SENSE_LENGTH); + ddi_put32(acc_handle, &pthru->sense_buf_phys_addr_hi, 0); + ddi_put32(acc_handle, &pthru->sense_buf_phys_addr_lo, + cmd->sense_phys_addr); + + context = ddi_get32(acc_handle, &pthru->context); + ddi_rep_put8(acc_handle, (uint8_t *)pkt->pkt_cdbp, + (uint8_t *)pthru->cdb, acmd->cmd_cdblen, DDI_DEV_AUTOINCR); + + break; + } +#ifdef lint + context = context; +#endif + /* prepare the scatter-gather list for the firmware */ + for (i = 0; i < acmd->cmd_cookiecnt; i++, mfi_sgl++) { + ddi_put64(acc_handle, &mfi_sgl->phys_addr, + acmd->cmd_dmacookies[i].dmac_laddress); + ddi_put32(acc_handle, &mfi_sgl->length, + acmd->cmd_dmacookies[i].dmac_size); + } + + sge_bytes = sizeof (struct drsas_sge64)*acmd->cmd_cookiecnt; + + cmd->frame_count = (sge_bytes / MRMFI_FRAME_SIZE) + + ((sge_bytes % MRMFI_FRAME_SIZE) ? 1 : 0) + 1; + + if (cmd->frame_count >= 8) { + cmd->frame_count = 8; + } + + return (cmd); +} + +/* + * issue_mfi_pthru + */ +static int +issue_mfi_pthru(struct drsas_instance *instance, struct drsas_ioctl *ioctl, + struct drsas_cmd *cmd, int mode) +{ + void *ubuf; + uint32_t kphys_addr = 0; + uint32_t xferlen = 0; + uint_t model; + ddi_acc_handle_t acc_handle = cmd->frame_dma_obj.acc_handle; + dma_obj_t pthru_dma_obj; + struct drsas_pthru_frame *kpthru; + struct drsas_pthru_frame *pthru; + int i; + pthru = &cmd->frame->pthru; + kpthru = (struct drsas_pthru_frame *)&ioctl->frame[0]; + + model = ddi_model_convert_from(mode & FMODELS); + if (model == DDI_MODEL_ILP32) { + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_pthru: DDI_MODEL_LP32")); + + xferlen = kpthru->sgl.sge32[0].length; + + ubuf = (void *)(ulong_t)kpthru->sgl.sge32[0].phys_addr; + } else { +#ifdef _ILP32 + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_pthru: DDI_MODEL_LP32")); + xferlen = kpthru->sgl.sge32[0].length; + ubuf = (void *)(ulong_t)kpthru->sgl.sge32[0].phys_addr; +#else + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_pthru: DDI_MODEL_LP64")); + xferlen = kpthru->sgl.sge64[0].length; + ubuf = (void *)(ulong_t)kpthru->sgl.sge64[0].phys_addr; +#endif + } + + if (xferlen) { + /* means IOCTL requires DMA */ + /* allocate the data transfer buffer */ + pthru_dma_obj.size = xferlen; + pthru_dma_obj.dma_attr = drsas_generic_dma_attr; + pthru_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + pthru_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + pthru_dma_obj.dma_attr.dma_attr_sgllen = 1; + pthru_dma_obj.dma_attr.dma_attr_align = 1; + + /* allocate kernel buffer for DMA */ + if (drsas_alloc_dma_obj(instance, &pthru_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_pthru: " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + /* If IOCTL requires DMA WRITE, do ddi_copyin IOCTL data copy */ + if (kpthru->flags & MFI_FRAME_DIR_WRITE) { + for (i = 0; i < xferlen; i++) { + if (ddi_copyin((uint8_t *)ubuf+i, + (uint8_t *)pthru_dma_obj.buffer+i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_pthru : " + "copy from user space failed")); + return (DDI_FAILURE); + } + } + } + + kphys_addr = pthru_dma_obj.dma_cookie[0].dmac_address; + } + + ddi_put8(acc_handle, &pthru->cmd, kpthru->cmd); + ddi_put8(acc_handle, &pthru->sense_len, kpthru->sense_len); + ddi_put8(acc_handle, &pthru->cmd_status, 0); + ddi_put8(acc_handle, &pthru->scsi_status, 0); + ddi_put8(acc_handle, &pthru->target_id, kpthru->target_id); + ddi_put8(acc_handle, &pthru->lun, kpthru->lun); + ddi_put8(acc_handle, &pthru->cdb_len, kpthru->cdb_len); + ddi_put8(acc_handle, &pthru->sge_count, kpthru->sge_count); + ddi_put16(acc_handle, &pthru->timeout, kpthru->timeout); + ddi_put32(acc_handle, &pthru->data_xfer_len, kpthru->data_xfer_len); + + ddi_put32(acc_handle, &pthru->sense_buf_phys_addr_hi, 0); + /* pthru->sense_buf_phys_addr_lo = cmd->sense_phys_addr; */ + ddi_put32(acc_handle, &pthru->sense_buf_phys_addr_lo, 0); + + ddi_rep_put8(acc_handle, (uint8_t *)kpthru->cdb, (uint8_t *)pthru->cdb, + pthru->cdb_len, DDI_DEV_AUTOINCR); + + ddi_put16(acc_handle, &pthru->flags, kpthru->flags & ~MFI_FRAME_SGL64); + ddi_put32(acc_handle, &pthru->sgl.sge32[0].length, xferlen); + ddi_put32(acc_handle, &pthru->sgl.sge32[0].phys_addr, kphys_addr); + + cmd->sync_cmd = DRSAS_TRUE; + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_pthru: fw_ioctl failed")); + } else { + if (xferlen && kpthru->flags & MFI_FRAME_DIR_READ) { + for (i = 0; i < xferlen; i++) { + if (ddi_copyout( + (uint8_t *)pthru_dma_obj.buffer+i, + (uint8_t *)ubuf+i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_pthru : " + "copy to user space failed")); + return (DDI_FAILURE); + } + } + } + } + + kpthru->cmd_status = ddi_get8(acc_handle, &pthru->cmd_status); + kpthru->scsi_status = ddi_get8(acc_handle, &pthru->scsi_status); + + con_log(CL_ANN, (CE_NOTE, "issue_mfi_pthru: cmd_status %x, " + "scsi_status %x", kpthru->cmd_status, kpthru->scsi_status)); + + if (xferlen) { + /* free kernel buffer */ + if (drsas_free_dma_obj(instance, pthru_dma_obj) != DDI_SUCCESS) + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * issue_mfi_dcmd + */ +static int +issue_mfi_dcmd(struct drsas_instance *instance, struct drsas_ioctl *ioctl, + struct drsas_cmd *cmd, int mode) +{ + void *ubuf; + uint32_t kphys_addr = 0; + uint32_t xferlen = 0; + uint32_t model; + dma_obj_t dcmd_dma_obj; + struct drsas_dcmd_frame *kdcmd; + struct drsas_dcmd_frame *dcmd; + ddi_acc_handle_t acc_handle = cmd->frame_dma_obj.acc_handle; + int i; + dcmd = &cmd->frame->dcmd; + kdcmd = (struct drsas_dcmd_frame *)&ioctl->frame[0]; + + model = ddi_model_convert_from(mode & FMODELS); + if (model == DDI_MODEL_ILP32) { + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_dcmd: DDI_MODEL_ILP32")); + + xferlen = kdcmd->sgl.sge32[0].length; + + ubuf = (void *)(ulong_t)kdcmd->sgl.sge32[0].phys_addr; + } else { +#ifdef _ILP32 + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_dcmd: DDI_MODEL_ILP32")); + xferlen = kdcmd->sgl.sge32[0].length; + ubuf = (void *)(ulong_t)kdcmd->sgl.sge32[0].phys_addr; +#else + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_dcmd: DDI_MODEL_LP64")); + xferlen = kdcmd->sgl.sge64[0].length; + ubuf = (void *)(ulong_t)kdcmd->sgl.sge64[0].phys_addr; +#endif + } + if (xferlen) { + /* means IOCTL requires DMA */ + /* allocate the data transfer buffer */ + dcmd_dma_obj.size = xferlen; + dcmd_dma_obj.dma_attr = drsas_generic_dma_attr; + dcmd_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + dcmd_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + dcmd_dma_obj.dma_attr.dma_attr_sgllen = 1; + dcmd_dma_obj.dma_attr.dma_attr_align = 1; + + /* allocate kernel buffer for DMA */ + if (drsas_alloc_dma_obj(instance, &dcmd_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_dcmd: " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + /* If IOCTL requires DMA WRITE, do ddi_copyin IOCTL data copy */ + if (kdcmd->flags & MFI_FRAME_DIR_WRITE) { + for (i = 0; i < xferlen; i++) { + if (ddi_copyin((uint8_t *)ubuf + i, + (uint8_t *)dcmd_dma_obj.buffer + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_dcmd : " + "copy from user space failed")); + return (DDI_FAILURE); + } + } + } + + kphys_addr = dcmd_dma_obj.dma_cookie[0].dmac_address; + } + + ddi_put8(acc_handle, &dcmd->cmd, kdcmd->cmd); + ddi_put8(acc_handle, &dcmd->cmd_status, 0); + ddi_put8(acc_handle, &dcmd->sge_count, kdcmd->sge_count); + ddi_put16(acc_handle, &dcmd->timeout, kdcmd->timeout); + ddi_put32(acc_handle, &dcmd->data_xfer_len, kdcmd->data_xfer_len); + ddi_put32(acc_handle, &dcmd->opcode, kdcmd->opcode); + + ddi_rep_put8(acc_handle, (uint8_t *)kdcmd->mbox.b, + (uint8_t *)dcmd->mbox.b, DCMD_MBOX_SZ, DDI_DEV_AUTOINCR); + + ddi_put16(acc_handle, &dcmd->flags, kdcmd->flags & ~MFI_FRAME_SGL64); + ddi_put32(acc_handle, &dcmd->sgl.sge32[0].length, xferlen); + ddi_put32(acc_handle, &dcmd->sgl.sge32[0].phys_addr, kphys_addr); + + cmd->sync_cmd = DRSAS_TRUE; + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_dcmd: fw_ioctl failed")); + } else { + if (xferlen && (kdcmd->flags & MFI_FRAME_DIR_READ)) { + for (i = 0; i < xferlen; i++) { + if (ddi_copyout( + (uint8_t *)dcmd_dma_obj.buffer + i, + (uint8_t *)ubuf + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_dcmd : " + "copy to user space failed")); + return (DDI_FAILURE); + } + } + } + } + + kdcmd->cmd_status = ddi_get8(acc_handle, &dcmd->cmd_status); + + if (xferlen) { + /* free kernel buffer */ + if (drsas_free_dma_obj(instance, dcmd_dma_obj) != DDI_SUCCESS) + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * issue_mfi_smp + */ +static int +issue_mfi_smp(struct drsas_instance *instance, struct drsas_ioctl *ioctl, + struct drsas_cmd *cmd, int mode) +{ + void *request_ubuf; + void *response_ubuf; + uint32_t request_xferlen = 0; + uint32_t response_xferlen = 0; + uint_t model; + dma_obj_t request_dma_obj; + dma_obj_t response_dma_obj; + ddi_acc_handle_t acc_handle = cmd->frame_dma_obj.acc_handle; + struct drsas_smp_frame *ksmp; + struct drsas_smp_frame *smp; + struct drsas_sge32 *sge32; +#ifndef _ILP32 + struct drsas_sge64 *sge64; +#endif + int i; + uint64_t tmp_sas_addr; + + smp = &cmd->frame->smp; + ksmp = (struct drsas_smp_frame *)&ioctl->frame[0]; + + model = ddi_model_convert_from(mode & FMODELS); + if (model == DDI_MODEL_ILP32) { + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp: DDI_MODEL_ILP32")); + + sge32 = &ksmp->sgl[0].sge32[0]; + response_xferlen = sge32[0].length; + request_xferlen = sge32[1].length; + con_log(CL_ANN, (CE_NOTE, "issue_mfi_smp: " + "response_xferlen = %x, request_xferlen = %x", + response_xferlen, request_xferlen)); + + response_ubuf = (void *)(ulong_t)sge32[0].phys_addr; + request_ubuf = (void *)(ulong_t)sge32[1].phys_addr; + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp: " + "response_ubuf = %p, request_ubuf = %p", + response_ubuf, request_ubuf)); + } else { +#ifdef _ILP32 + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp: DDI_MODEL_ILP32")); + + sge32 = &ksmp->sgl[0].sge32[0]; + response_xferlen = sge32[0].length; + request_xferlen = sge32[1].length; + con_log(CL_ANN, (CE_NOTE, "issue_mfi_smp: " + "response_xferlen = %x, request_xferlen = %x", + response_xferlen, request_xferlen)); + + response_ubuf = (void *)(ulong_t)sge32[0].phys_addr; + request_ubuf = (void *)(ulong_t)sge32[1].phys_addr; + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp: " + "response_ubuf = %p, request_ubuf = %p", + response_ubuf, request_ubuf)); +#else + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp: DDI_MODEL_LP64")); + + sge64 = &ksmp->sgl[0].sge64[0]; + response_xferlen = sge64[0].length; + request_xferlen = sge64[1].length; + + response_ubuf = (void *)(ulong_t)sge64[0].phys_addr; + request_ubuf = (void *)(ulong_t)sge64[1].phys_addr; +#endif + } + if (request_xferlen) { + /* means IOCTL requires DMA */ + /* allocate the data transfer buffer */ + request_dma_obj.size = request_xferlen; + request_dma_obj.dma_attr = drsas_generic_dma_attr; + request_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + request_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + request_dma_obj.dma_attr.dma_attr_sgllen = 1; + request_dma_obj.dma_attr.dma_attr_align = 1; + + /* allocate kernel buffer for DMA */ + if (drsas_alloc_dma_obj(instance, &request_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_smp: " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + /* If IOCTL requires DMA WRITE, do ddi_copyin IOCTL data copy */ + for (i = 0; i < request_xferlen; i++) { + if (ddi_copyin((uint8_t *)request_ubuf + i, + (uint8_t *)request_dma_obj.buffer + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_smp: " + "copy from user space failed")); + return (DDI_FAILURE); + } + } + } + + if (response_xferlen) { + /* means IOCTL requires DMA */ + /* allocate the data transfer buffer */ + response_dma_obj.size = response_xferlen; + response_dma_obj.dma_attr = drsas_generic_dma_attr; + response_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + response_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + response_dma_obj.dma_attr.dma_attr_sgllen = 1; + response_dma_obj.dma_attr.dma_attr_align = 1; + + /* allocate kernel buffer for DMA */ + if (drsas_alloc_dma_obj(instance, &response_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_smp: " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + /* If IOCTL requires DMA WRITE, do ddi_copyin IOCTL data copy */ + for (i = 0; i < response_xferlen; i++) { + if (ddi_copyin((uint8_t *)response_ubuf + i, + (uint8_t *)response_dma_obj.buffer + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_smp: " + "copy from user space failed")); + return (DDI_FAILURE); + } + } + } + + ddi_put8(acc_handle, &smp->cmd, ksmp->cmd); + ddi_put8(acc_handle, &smp->cmd_status, 0); + ddi_put8(acc_handle, &smp->connection_status, 0); + ddi_put8(acc_handle, &smp->sge_count, ksmp->sge_count); + /* smp->context = ksmp->context; */ + ddi_put16(acc_handle, &smp->timeout, ksmp->timeout); + ddi_put32(acc_handle, &smp->data_xfer_len, ksmp->data_xfer_len); + + bcopy((void *)&ksmp->sas_addr, (void *)&tmp_sas_addr, + sizeof (uint64_t)); + ddi_put64(acc_handle, &smp->sas_addr, tmp_sas_addr); + + ddi_put16(acc_handle, &smp->flags, ksmp->flags & ~MFI_FRAME_SGL64); + + model = ddi_model_convert_from(mode & FMODELS); + if (model == DDI_MODEL_ILP32) { + con_log(CL_ANN1, (CE_NOTE, + "handle_drv_ioctl: DDI_MODEL_ILP32")); + + sge32 = &smp->sgl[0].sge32[0]; + ddi_put32(acc_handle, &sge32[0].length, response_xferlen); + ddi_put32(acc_handle, &sge32[0].phys_addr, + response_dma_obj.dma_cookie[0].dmac_address); + ddi_put32(acc_handle, &sge32[1].length, request_xferlen); + ddi_put32(acc_handle, &sge32[1].phys_addr, + request_dma_obj.dma_cookie[0].dmac_address); + } else { +#ifdef _ILP32 + con_log(CL_ANN1, (CE_NOTE, + "handle_drv_ioctl: DDI_MODEL_ILP32")); + sge32 = &smp->sgl[0].sge32[0]; + ddi_put32(acc_handle, &sge32[0].length, response_xferlen); + ddi_put32(acc_handle, &sge32[0].phys_addr, + response_dma_obj.dma_cookie[0].dmac_address); + ddi_put32(acc_handle, &sge32[1].length, request_xferlen); + ddi_put32(acc_handle, &sge32[1].phys_addr, + request_dma_obj.dma_cookie[0].dmac_address); +#else + con_log(CL_ANN1, (CE_NOTE, + "issue_mfi_smp: DDI_MODEL_LP64")); + sge64 = &smp->sgl[0].sge64[0]; + ddi_put32(acc_handle, &sge64[0].length, response_xferlen); + ddi_put64(acc_handle, &sge64[0].phys_addr, + response_dma_obj.dma_cookie[0].dmac_address); + ddi_put32(acc_handle, &sge64[1].length, request_xferlen); + ddi_put64(acc_handle, &sge64[1].phys_addr, + request_dma_obj.dma_cookie[0].dmac_address); +#endif + } + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp : " + "smp->response_xferlen = %d, smp->request_xferlen = %d " + "smp->data_xfer_len = %d", ddi_get32(acc_handle, &sge32[0].length), + ddi_get32(acc_handle, &sge32[1].length), + ddi_get32(acc_handle, &smp->data_xfer_len))); + + cmd->sync_cmd = DRSAS_TRUE; + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_smp: fw_ioctl failed")); + } else { + con_log(CL_ANN1, (CE_NOTE, + "issue_mfi_smp: copy to user space")); + + if (request_xferlen) { + for (i = 0; i < request_xferlen; i++) { + if (ddi_copyout( + (uint8_t *)request_dma_obj.buffer + + i, (uint8_t *)request_ubuf + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_smp : copy to user space" + " failed")); + return (DDI_FAILURE); + } + } + } + + if (response_xferlen) { + for (i = 0; i < response_xferlen; i++) { + if (ddi_copyout( + (uint8_t *)response_dma_obj.buffer + + i, (uint8_t *)response_ubuf + + i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_smp : copy to " + "user space failed")); + return (DDI_FAILURE); + } + } + } + } + + ksmp->cmd_status = ddi_get8(acc_handle, &smp->cmd_status); + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_smp: smp->cmd_status = %d", + ddi_get8(acc_handle, &smp->cmd_status))); + + + if (request_xferlen) { + /* free kernel buffer */ + if (drsas_free_dma_obj(instance, request_dma_obj) != + DDI_SUCCESS) + return (DDI_FAILURE); + } + + if (response_xferlen) { + /* free kernel buffer */ + if (drsas_free_dma_obj(instance, response_dma_obj) != + DDI_SUCCESS) + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * issue_mfi_stp + */ +static int +issue_mfi_stp(struct drsas_instance *instance, struct drsas_ioctl *ioctl, + struct drsas_cmd *cmd, int mode) +{ + void *fis_ubuf; + void *data_ubuf; + uint32_t fis_xferlen = 0; + uint32_t data_xferlen = 0; + uint_t model; + dma_obj_t fis_dma_obj; + dma_obj_t data_dma_obj; + struct drsas_stp_frame *kstp; + struct drsas_stp_frame *stp; + ddi_acc_handle_t acc_handle = cmd->frame_dma_obj.acc_handle; + int i; + + stp = &cmd->frame->stp; + kstp = (struct drsas_stp_frame *)&ioctl->frame[0]; + + model = ddi_model_convert_from(mode & FMODELS); + if (model == DDI_MODEL_ILP32) { + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_stp: DDI_MODEL_ILP32")); + + fis_xferlen = kstp->sgl.sge32[0].length; + data_xferlen = kstp->sgl.sge32[1].length; + + fis_ubuf = (void *)(ulong_t)kstp->sgl.sge32[0].phys_addr; + data_ubuf = (void *)(ulong_t)kstp->sgl.sge32[1].phys_addr; + } + else + { +#ifdef _ILP32 + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_stp: DDI_MODEL_ILP32")); + + fis_xferlen = kstp->sgl.sge32[0].length; + data_xferlen = kstp->sgl.sge32[1].length; + + fis_ubuf = (void *)(ulong_t)kstp->sgl.sge32[0].phys_addr; + data_ubuf = (void *)(ulong_t)kstp->sgl.sge32[1].phys_addr; +#else + con_log(CL_ANN1, (CE_NOTE, "issue_mfi_stp: DDI_MODEL_LP64")); + + fis_xferlen = kstp->sgl.sge64[0].length; + data_xferlen = kstp->sgl.sge64[1].length; + + fis_ubuf = (void *)(ulong_t)kstp->sgl.sge64[0].phys_addr; + data_ubuf = (void *)(ulong_t)kstp->sgl.sge64[1].phys_addr; +#endif + } + + + if (fis_xferlen) { + con_log(CL_ANN, (CE_NOTE, "issue_mfi_stp: " + "fis_ubuf = %p fis_xferlen = %x", fis_ubuf, fis_xferlen)); + + /* means IOCTL requires DMA */ + /* allocate the data transfer buffer */ + fis_dma_obj.size = fis_xferlen; + fis_dma_obj.dma_attr = drsas_generic_dma_attr; + fis_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + fis_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + fis_dma_obj.dma_attr.dma_attr_sgllen = 1; + fis_dma_obj.dma_attr.dma_attr_align = 1; + + /* allocate kernel buffer for DMA */ + if (drsas_alloc_dma_obj(instance, &fis_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_stp : " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + /* If IOCTL requires DMA WRITE, do ddi_copyin IOCTL data copy */ + for (i = 0; i < fis_xferlen; i++) { + if (ddi_copyin((uint8_t *)fis_ubuf + i, + (uint8_t *)fis_dma_obj.buffer + i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_stp: " + "copy from user space failed")); + return (DDI_FAILURE); + } + } + } + + if (data_xferlen) { + con_log(CL_ANN, (CE_NOTE, "issue_mfi_stp: data_ubuf = %p " + "data_xferlen = %x", data_ubuf, data_xferlen)); + + /* means IOCTL requires DMA */ + /* allocate the data transfer buffer */ + data_dma_obj.size = data_xferlen; + data_dma_obj.dma_attr = drsas_generic_dma_attr; + data_dma_obj.dma_attr.dma_attr_addr_hi = 0xFFFFFFFFU; + data_dma_obj.dma_attr.dma_attr_count_max = 0xFFFFFFFFU; + data_dma_obj.dma_attr.dma_attr_sgllen = 1; + data_dma_obj.dma_attr.dma_attr_align = 1; + +/* allocate kernel buffer for DMA */ + if (drsas_alloc_dma_obj(instance, &data_dma_obj, + (uchar_t)DDI_STRUCTURE_LE_ACC) != 1) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_stp: " + "could not allocate data transfer buffer.")); + return (DDI_FAILURE); + } + + /* If IOCTL requires DMA WRITE, do ddi_copyin IOCTL data copy */ + for (i = 0; i < data_xferlen; i++) { + if (ddi_copyin((uint8_t *)data_ubuf + i, + (uint8_t *)data_dma_obj.buffer + i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_stp: " + "copy from user space failed")); + return (DDI_FAILURE); + } + } + } + + ddi_put8(acc_handle, &stp->cmd, kstp->cmd); + ddi_put8(acc_handle, &stp->cmd_status, 0); + ddi_put8(acc_handle, &stp->connection_status, 0); + ddi_put8(acc_handle, &stp->target_id, kstp->target_id); + ddi_put8(acc_handle, &stp->sge_count, kstp->sge_count); + + ddi_put16(acc_handle, &stp->timeout, kstp->timeout); + ddi_put32(acc_handle, &stp->data_xfer_len, kstp->data_xfer_len); + + ddi_rep_put8(acc_handle, (uint8_t *)kstp->fis, (uint8_t *)stp->fis, 10, + DDI_DEV_AUTOINCR); + + ddi_put16(acc_handle, &stp->flags, kstp->flags & ~MFI_FRAME_SGL64); + ddi_put32(acc_handle, &stp->stp_flags, kstp->stp_flags); + ddi_put32(acc_handle, &stp->sgl.sge32[0].length, fis_xferlen); + ddi_put32(acc_handle, &stp->sgl.sge32[0].phys_addr, + fis_dma_obj.dma_cookie[0].dmac_address); + ddi_put32(acc_handle, &stp->sgl.sge32[1].length, data_xferlen); + ddi_put32(acc_handle, &stp->sgl.sge32[1].phys_addr, + data_dma_obj.dma_cookie[0].dmac_address); + + cmd->sync_cmd = DRSAS_TRUE; + cmd->frame_count = 1; + + if (instance->func_ptr->issue_cmd_in_sync_mode(instance, cmd)) { + con_log(CL_ANN, (CE_WARN, "issue_mfi_stp: fw_ioctl failed")); + } else { + + if (fis_xferlen) { + for (i = 0; i < fis_xferlen; i++) { + if (ddi_copyout( + (uint8_t *)fis_dma_obj.buffer + i, + (uint8_t *)fis_ubuf + i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_stp : copy to " + "user space failed")); + return (DDI_FAILURE); + } + } + } + } + if (data_xferlen) { + for (i = 0; i < data_xferlen; i++) { + if (ddi_copyout( + (uint8_t *)data_dma_obj.buffer + i, + (uint8_t *)data_ubuf + i, 1, mode)) { + con_log(CL_ANN, (CE_WARN, + "issue_mfi_stp : copy to" + " user space failed")); + return (DDI_FAILURE); + } + } + } + + kstp->cmd_status = ddi_get8(acc_handle, &stp->cmd_status); + + if (fis_xferlen) { + /* free kernel buffer */ + if (drsas_free_dma_obj(instance, fis_dma_obj) != DDI_SUCCESS) + return (DDI_FAILURE); + } + + if (data_xferlen) { + /* free kernel buffer */ + if (drsas_free_dma_obj(instance, data_dma_obj) != DDI_SUCCESS) + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * fill_up_drv_ver + */ +static void +fill_up_drv_ver(struct drsas_drv_ver *dv) +{ + (void) memset(dv, 0, sizeof (struct drsas_drv_ver)); + + (void) memcpy(dv->signature, "$LSI LOGIC$", strlen("$LSI LOGIC$")); + (void) memcpy(dv->os_name, "Solaris", strlen("Solaris")); + (void) memcpy(dv->drv_name, "dr_sas", strlen("dr_sas")); + (void) memcpy(dv->drv_ver, DRSAS_VERSION, strlen(DRSAS_VERSION)); + (void) memcpy(dv->drv_rel_date, DRSAS_RELDATE, + strlen(DRSAS_RELDATE)); +} + +/* + * handle_drv_ioctl + */ +static int +handle_drv_ioctl(struct drsas_instance *instance, struct drsas_ioctl *ioctl, + int mode) +{ + int i; + int rval = DDI_SUCCESS; + int *props = NULL; + void *ubuf; + + uint8_t *pci_conf_buf; + uint32_t xferlen; + uint32_t num_props; + uint_t model; + struct drsas_dcmd_frame *kdcmd; + struct drsas_drv_ver dv; + struct drsas_pci_information pi; + + kdcmd = (struct drsas_dcmd_frame *)&ioctl->frame[0]; + + model = ddi_model_convert_from(mode & FMODELS); + if (model == DDI_MODEL_ILP32) { + con_log(CL_ANN1, (CE_NOTE, + "handle_drv_ioctl: DDI_MODEL_ILP32")); + + xferlen = kdcmd->sgl.sge32[0].length; + + ubuf = (void *)(ulong_t)kdcmd->sgl.sge32[0].phys_addr; + } else { +#ifdef _ILP32 + con_log(CL_ANN1, (CE_NOTE, + "handle_drv_ioctl: DDI_MODEL_ILP32")); + xferlen = kdcmd->sgl.sge32[0].length; + ubuf = (void *)(ulong_t)kdcmd->sgl.sge32[0].phys_addr; +#else + con_log(CL_ANN1, (CE_NOTE, + "handle_drv_ioctl: DDI_MODEL_LP64")); + xferlen = kdcmd->sgl.sge64[0].length; + ubuf = (void *)(ulong_t)kdcmd->sgl.sge64[0].phys_addr; +#endif + } + con_log(CL_ANN1, (CE_NOTE, "handle_drv_ioctl: " + "dataBuf=%p size=%d bytes", ubuf, xferlen)); + + switch (kdcmd->opcode) { + case DRSAS_DRIVER_IOCTL_DRIVER_VERSION: + con_log(CL_ANN1, (CE_NOTE, "handle_drv_ioctl: " + "DRSAS_DRIVER_IOCTL_DRIVER_VERSION")); + + fill_up_drv_ver(&dv); + for (i = 0; i < xferlen; i++) { + if (ddi_copyout((uint8_t *)&dv + i, (uint8_t *)ubuf + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, "handle_drv_ioctl: " + "DRSAS_DRIVER_IOCTL_DRIVER_VERSION" + " : copy to user space failed")); + kdcmd->cmd_status = 1; + rval = DDI_FAILURE; + break; + } + } + if (i == xferlen) + kdcmd->cmd_status = 0; + break; + case DRSAS_DRIVER_IOCTL_PCI_INFORMATION: + con_log(CL_ANN1, (CE_NOTE, "handle_drv_ioctl: " + "DRSAS_DRIVER_IOCTL_PCI_INFORMAITON")); + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, instance->dip, + 0, "reg", &props, &num_props)) { + con_log(CL_ANN, (CE_WARN, "handle_drv_ioctl: " + "DRSAS_DRIVER_IOCTL_PCI_INFORMATION : " + "ddi_prop_look_int_array failed")); + rval = DDI_FAILURE; + } else { + + pi.busNumber = (props[0] >> 16) & 0xFF; + pi.deviceNumber = (props[0] >> 11) & 0x1f; + pi.functionNumber = (props[0] >> 8) & 0x7; + ddi_prop_free((void *)props); + } + + pci_conf_buf = (uint8_t *)&pi.pciHeaderInfo; + + for (i = 0; i < (sizeof (struct drsas_pci_information) - + offsetof(struct drsas_pci_information, pciHeaderInfo)); + i++) { + pci_conf_buf[i] = + pci_config_get8(instance->pci_handle, i); + } + for (i = 0; i < xferlen; i++) { + if (ddi_copyout((uint8_t *)&pi + i, (uint8_t *)ubuf + i, + 1, mode)) { + con_log(CL_ANN, (CE_WARN, "handle_drv_ioctl: " + "DRSAS_DRIVER_IOCTL_PCI_INFORMATION" + " : copy to user space failed")); + kdcmd->cmd_status = 1; + rval = DDI_FAILURE; + break; + } + } + + if (i == xferlen) + kdcmd->cmd_status = 0; + + break; + default: + con_log(CL_ANN, (CE_WARN, "handle_drv_ioctl: " + "invalid driver specific IOCTL opcode = 0x%x", + kdcmd->opcode)); + kdcmd->cmd_status = 1; + rval = DDI_FAILURE; + break; + } + + return (rval); +} + +/* + * handle_mfi_ioctl + */ +static int +handle_mfi_ioctl(struct drsas_instance *instance, struct drsas_ioctl *ioctl, + int mode) +{ + int rval = DDI_SUCCESS; + + struct drsas_header *hdr; + struct drsas_cmd *cmd; + + cmd = get_mfi_pkt(instance); + + if (!cmd) { + con_log(CL_ANN, (CE_WARN, "dr_sas: " + "failed to get a cmd packet")); + return (DDI_FAILURE); + } + + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &cmd->frame->hdr.context, + cmd->index); + + hdr = (struct drsas_header *)&ioctl->frame[0]; + + switch (hdr->cmd) { + case MFI_CMD_OP_DCMD: + rval = issue_mfi_dcmd(instance, ioctl, cmd, mode); + break; + case MFI_CMD_OP_SMP: + rval = issue_mfi_smp(instance, ioctl, cmd, mode); + break; + case MFI_CMD_OP_STP: + rval = issue_mfi_stp(instance, ioctl, cmd, mode); + break; + case MFI_CMD_OP_LD_SCSI: + case MFI_CMD_OP_PD_SCSI: + rval = issue_mfi_pthru(instance, ioctl, cmd, mode); + break; + default: + con_log(CL_ANN, (CE_WARN, "handle_mfi_ioctl: " + "invalid mfi ioctl hdr->cmd = %d", hdr->cmd)); + rval = DDI_FAILURE; + break; + } + + + return_mfi_pkt(instance, cmd); + if (drsas_common_check(instance, cmd) != DDI_SUCCESS) + rval = DDI_FAILURE; + return (rval); +} + +/* + * AEN + */ +static int +handle_mfi_aen(struct drsas_instance *instance, struct drsas_aen *aen) +{ + int rval = 0; + + rval = register_mfi_aen(instance, instance->aen_seq_num, + aen->class_locale_word); + + aen->cmd_status = (uint8_t)rval; + + return (rval); +} + +static int +register_mfi_aen(struct drsas_instance *instance, uint32_t seq_num, + uint32_t class_locale_word) +{ + int ret_val; + + struct drsas_cmd *cmd, *aen_cmd; + struct drsas_dcmd_frame *dcmd; + union drsas_evt_class_locale curr_aen; + union drsas_evt_class_locale prev_aen; + + /* + * If there an AEN pending already (aen_cmd), check if the + * class_locale of that pending AEN is inclusive of the new + * AEN request we currently have. If it is, then we don't have + * to do anything. In other words, whichever events the current + * AEN request is subscribing to, have already been subscribed + * to. + * + * If the old_cmd is _not_ inclusive, then we have to abort + * that command, form a class_locale that is superset of both + * old and current and re-issue to the FW + */ + + curr_aen.word = class_locale_word; + aen_cmd = instance->aen_cmd; + if (aen_cmd) { + prev_aen.word = ddi_get32(aen_cmd->frame_dma_obj.acc_handle, + &aen_cmd->frame->dcmd.mbox.w[1]); + + /* + * A class whose enum value is smaller is inclusive of all + * higher values. If a PROGRESS (= -1) was previously + * registered, then a new registration requests for higher + * classes need not be sent to FW. They are automatically + * included. + * + * Locale numbers don't have such hierarchy. They are bitmap + * values + */ + if ((prev_aen.members.class <= curr_aen.members.class) && + !((prev_aen.members.locale & curr_aen.members.locale) ^ + curr_aen.members.locale)) { + /* + * Previously issued event registration includes + * current request. Nothing to do. + */ + + return (0); + } else { + curr_aen.members.locale |= prev_aen.members.locale; + + if (prev_aen.members.class < curr_aen.members.class) + curr_aen.members.class = prev_aen.members.class; + + ret_val = abort_aen_cmd(instance, aen_cmd); + + if (ret_val) { + con_log(CL_ANN, (CE_WARN, "register_mfi_aen: " + "failed to abort prevous AEN command")); + + return (ret_val); + } + } + } else { + curr_aen.word = class_locale_word; + } + + cmd = get_mfi_pkt(instance); + + if (!cmd) + return (ENOMEM); + /* Clear the frame buffer and assign back the context id */ + (void) memset((char *)&cmd->frame[0], 0, sizeof (union drsas_frame)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &cmd->frame->hdr.context, + cmd->index); + + dcmd = &cmd->frame->dcmd; + + /* for(i = 0; i < DCMD_MBOX_SZ; i++) dcmd->mbox.b[i] = 0; */ + (void) memset(dcmd->mbox.b, 0, DCMD_MBOX_SZ); + + (void) memset(instance->mfi_evt_detail_obj.buffer, 0, + sizeof (struct drsas_evt_detail)); + + /* Prepare DCMD for aen registration */ + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd, MFI_CMD_OP_DCMD); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->cmd_status, 0x0); + ddi_put8(cmd->frame_dma_obj.acc_handle, &dcmd->sge_count, 1); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->flags, + MFI_FRAME_DIR_READ); + ddi_put16(cmd->frame_dma_obj.acc_handle, &dcmd->timeout, 0); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->data_xfer_len, + sizeof (struct drsas_evt_detail)); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->opcode, + DR_DCMD_CTRL_EVENT_WAIT); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->mbox.w[0], seq_num); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->mbox.w[1], + curr_aen.word); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->sgl.sge32[0].phys_addr, + instance->mfi_evt_detail_obj.dma_cookie[0].dmac_address); + ddi_put32(cmd->frame_dma_obj.acc_handle, &dcmd->sgl.sge32[0].length, + sizeof (struct drsas_evt_detail)); + + instance->aen_seq_num = seq_num; + + + /* + * Store reference to the cmd used to register for AEN. When an + * application wants us to register for AEN, we have to abort this + * cmd and re-register with a new EVENT LOCALE supplied by that app + */ + instance->aen_cmd = cmd; + + cmd->frame_count = 1; + + /* Issue the aen registration frame */ + /* atomic_add_16 (&instance->fw_outstanding, 1); */ + instance->func_ptr->issue_cmd(cmd, instance); + + return (0); +} + +static void +display_scsi_inquiry(caddr_t scsi_inq) +{ +#define MAX_SCSI_DEVICE_CODE 14 + int i; + char inquiry_buf[256] = {0}; + int len; + const char *const scsi_device_types[] = { + "Direct-Access ", + "Sequential-Access", + "Printer ", + "Processor ", + "WORM ", + "CD-ROM ", + "Scanner ", + "Optical Device ", + "Medium Changer ", + "Communications ", + "Unknown ", + "Unknown ", + "Unknown ", + "Enclosure ", + }; + + len = 0; + + len += snprintf(inquiry_buf + len, 265 - len, " Vendor: "); + for (i = 8; i < 16; i++) { + len += snprintf(inquiry_buf + len, 265 - len, "%c", + scsi_inq[i]); + } + + len += snprintf(inquiry_buf + len, 265 - len, " Model: "); + + for (i = 16; i < 32; i++) { + len += snprintf(inquiry_buf + len, 265 - len, "%c", + scsi_inq[i]); + } + + len += snprintf(inquiry_buf + len, 265 - len, " Rev: "); + + for (i = 32; i < 36; i++) { + len += snprintf(inquiry_buf + len, 265 - len, "%c", + scsi_inq[i]); + } + + len += snprintf(inquiry_buf + len, 265 - len, "\n"); + + + i = scsi_inq[0] & 0x1f; + + + len += snprintf(inquiry_buf + len, 265 - len, " Type: %s ", + i < MAX_SCSI_DEVICE_CODE ? scsi_device_types[i] : + "Unknown "); + + + len += snprintf(inquiry_buf + len, 265 - len, + " ANSI SCSI revision: %02x", scsi_inq[2] & 0x07); + + if ((scsi_inq[2] & 0x07) == 1 && (scsi_inq[3] & 0x0f) == 1) { + len += snprintf(inquiry_buf + len, 265 - len, " CCS\n"); + } else { + len += snprintf(inquiry_buf + len, 265 - len, "\n"); + } + + con_log(CL_ANN1, (CE_CONT, inquiry_buf)); +} + +static int +read_fw_status_reg_ppc(struct drsas_instance *instance) +{ + return ((int)RD_OB_SCRATCH_PAD_0(instance)); +} + +static void +issue_cmd_ppc(struct drsas_cmd *cmd, struct drsas_instance *instance) +{ + atomic_add_16(&instance->fw_outstanding, 1); + + /* Issue the command to the FW */ + WR_IB_QPORT((cmd->frame_phys_addr) | + (((cmd->frame_count - 1) << 1) | 1), instance); +} + +/* + * issue_cmd_in_sync_mode + */ +static int +issue_cmd_in_sync_mode_ppc(struct drsas_instance *instance, + struct drsas_cmd *cmd) +{ + int i; + uint32_t msecs = MFI_POLL_TIMEOUT_SECS * (10 * MILLISEC); + + con_log(CL_ANN1, (CE_NOTE, "issue_cmd_in_sync_mode_ppc: called")); + + cmd->cmd_status = ENODATA; + + WR_IB_QPORT((cmd->frame_phys_addr) | + (((cmd->frame_count - 1) << 1) | 1), instance); + + mutex_enter(&instance->int_cmd_mtx); + + for (i = 0; i < msecs && (cmd->cmd_status == ENODATA); i++) { + cv_wait(&instance->int_cmd_cv, &instance->int_cmd_mtx); + } + + mutex_exit(&instance->int_cmd_mtx); + + con_log(CL_ANN1, (CE_NOTE, "issue_cmd_in_sync_mode_ppc: done")); + + if (i < (msecs -1)) { + return (DDI_SUCCESS); + } else { + return (DDI_FAILURE); + } +} + +/* + * issue_cmd_in_poll_mode + */ +static int +issue_cmd_in_poll_mode_ppc(struct drsas_instance *instance, + struct drsas_cmd *cmd) +{ + int i; + uint16_t flags; + uint32_t msecs = MFI_POLL_TIMEOUT_SECS * MILLISEC; + struct drsas_header *frame_hdr; + + con_log(CL_ANN1, (CE_NOTE, "issue_cmd_in_poll_mode_ppc: called")); + + frame_hdr = (struct drsas_header *)cmd->frame; + ddi_put8(cmd->frame_dma_obj.acc_handle, &frame_hdr->cmd_status, + MFI_CMD_STATUS_POLL_MODE); + flags = ddi_get16(cmd->frame_dma_obj.acc_handle, &frame_hdr->flags); + flags |= MFI_FRAME_DONT_POST_IN_REPLY_QUEUE; + + ddi_put16(cmd->frame_dma_obj.acc_handle, &frame_hdr->flags, flags); + + /* issue the frame using inbound queue port */ + WR_IB_QPORT((cmd->frame_phys_addr) | + (((cmd->frame_count - 1) << 1) | 1), instance); + + /* wait for cmd_status to change from 0xFF */ + for (i = 0; i < msecs && ( + ddi_get8(cmd->frame_dma_obj.acc_handle, &frame_hdr->cmd_status) + == MFI_CMD_STATUS_POLL_MODE); i++) { + drv_usecwait(MILLISEC); /* wait for 1000 usecs */ + } + + if (ddi_get8(cmd->frame_dma_obj.acc_handle, &frame_hdr->cmd_status) + == MFI_CMD_STATUS_POLL_MODE) { + con_log(CL_ANN, (CE_NOTE, "issue_cmd_in_poll_mode: " + "cmd polling timed out")); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +static void +enable_intr_ppc(struct drsas_instance *instance) +{ + uint32_t mask; + + con_log(CL_ANN1, (CE_NOTE, "enable_intr_ppc: called")); + + /* WR_OB_DOORBELL_CLEAR(0xFFFFFFFF, instance); */ + WR_OB_DOORBELL_CLEAR(OB_DOORBELL_CLEAR_MASK, instance); + + /* WR_OB_INTR_MASK(~0x80000000, instance); */ + WR_OB_INTR_MASK(~(MFI_REPLY_2108_MESSAGE_INTR_MASK), instance); + + /* dummy read to force PCI flush */ + mask = RD_OB_INTR_MASK(instance); + + con_log(CL_ANN1, (CE_NOTE, "enable_intr_ppc: " + "outbound_intr_mask = 0x%x", mask)); +} + +static void +disable_intr_ppc(struct drsas_instance *instance) +{ + uint32_t mask; + + con_log(CL_ANN1, (CE_NOTE, "disable_intr_ppc: called")); + + con_log(CL_ANN1, (CE_NOTE, "disable_intr_ppc: before : " + "outbound_intr_mask = 0x%x", RD_OB_INTR_MASK(instance))); + + /* WR_OB_INTR_MASK(0xFFFFFFFF, instance); */ + WR_OB_INTR_MASK(OB_INTR_MASK, instance); + + con_log(CL_ANN1, (CE_NOTE, "disable_intr_ppc: after : " + "outbound_intr_mask = 0x%x", RD_OB_INTR_MASK(instance))); + + /* dummy read to force PCI flush */ + mask = RD_OB_INTR_MASK(instance); +#ifdef lint + mask = mask; +#endif +} + +static int +intr_ack_ppc(struct drsas_instance *instance) +{ + uint32_t status; + + con_log(CL_ANN1, (CE_NOTE, "intr_ack_ppc: called")); + + /* check if it is our interrupt */ + status = RD_OB_INTR_STATUS(instance); + + con_log(CL_ANN1, (CE_NOTE, "intr_ack_ppc: status = 0x%x", status)); + + if (!(status & MFI_REPLY_2108_MESSAGE_INTR)) { + return (DDI_INTR_UNCLAIMED); + } + + /* clear the interrupt by writing back the same value */ + WR_OB_DOORBELL_CLEAR(status, instance); + + /* dummy READ */ + status = RD_OB_INTR_STATUS(instance); + + con_log(CL_ANN1, (CE_NOTE, "intr_ack_ppc: interrupt cleared")); + + return (DDI_INTR_CLAIMED); +} + +static int +drsas_common_check(struct drsas_instance *instance, + struct drsas_cmd *cmd) +{ + int ret = DDI_SUCCESS; + + if (drsas_check_dma_handle(cmd->frame_dma_obj.dma_handle) != + DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_UNAFFECTED); + if (cmd->pkt != NULL) { + cmd->pkt->pkt_reason = CMD_TRAN_ERR; + cmd->pkt->pkt_statistics = 0; + } + ret = DDI_FAILURE; + } + if (drsas_check_dma_handle(instance->mfi_internal_dma_obj.dma_handle) + != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_UNAFFECTED); + if (cmd->pkt != NULL) { + cmd->pkt->pkt_reason = CMD_TRAN_ERR; + cmd->pkt->pkt_statistics = 0; + } + ret = DDI_FAILURE; + } + if (drsas_check_dma_handle(instance->mfi_evt_detail_obj.dma_handle) != + DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_UNAFFECTED); + if (cmd->pkt != NULL) { + cmd->pkt->pkt_reason = CMD_TRAN_ERR; + cmd->pkt->pkt_statistics = 0; + } + ret = DDI_FAILURE; + } + if (drsas_check_acc_handle(instance->regmap_handle) != DDI_SUCCESS) { + ddi_fm_service_impact(instance->dip, DDI_SERVICE_UNAFFECTED); + + ddi_fm_acc_err_clear(instance->regmap_handle, DDI_FME_VER0); + + if (cmd->pkt != NULL) { + cmd->pkt->pkt_reason = CMD_TRAN_ERR; + cmd->pkt->pkt_statistics = 0; + } + ret = DDI_FAILURE; + } + + return (ret); +} + +/*ARGSUSED*/ +static int +drsas_fm_error_cb(dev_info_t *dip, ddi_fm_error_t *err, const void *impl_data) +{ + /* + * as the driver can always deal with an error in any dma or + * access handle, we can just return the fme_status value. + */ + pci_ereport_post(dip, err, NULL); + return (err->fme_status); +} + +static void +drsas_fm_init(struct drsas_instance *instance) +{ + /* Need to change iblock to priority for new MSI intr */ + ddi_iblock_cookie_t fm_ibc; + + /* Only register with IO Fault Services if we have some capability */ + if (instance->fm_capabilities) { + /* Adjust access and dma attributes for FMA */ + endian_attr.devacc_attr_access = DDI_FLAGERR_ACC; + drsas_generic_dma_attr.dma_attr_flags = DDI_DMA_FLAGERR; + + /* + * Register capabilities with IO Fault Services. + * fm_capabilities will be updated to indicate + * capabilities actually supported (not requested.) + */ + + ddi_fm_init(instance->dip, &instance->fm_capabilities, &fm_ibc); + + /* + * Initialize pci ereport capabilities if ereport + * capable (should always be.) + */ + + if (DDI_FM_EREPORT_CAP(instance->fm_capabilities) || + DDI_FM_ERRCB_CAP(instance->fm_capabilities)) { + pci_ereport_setup(instance->dip); + } + + /* + * Register error callback if error callback capable. + */ + if (DDI_FM_ERRCB_CAP(instance->fm_capabilities)) { + ddi_fm_handler_register(instance->dip, + drsas_fm_error_cb, (void*) instance); + } + } else { + endian_attr.devacc_attr_access = DDI_DEFAULT_ACC; + drsas_generic_dma_attr.dma_attr_flags = 0; + } +} + +static void +drsas_fm_fini(struct drsas_instance *instance) +{ + /* Only unregister FMA capabilities if registered */ + if (instance->fm_capabilities) { + /* + * Un-register error callback if error callback capable. + */ + if (DDI_FM_ERRCB_CAP(instance->fm_capabilities)) { + ddi_fm_handler_unregister(instance->dip); + } + + /* + * Release any resources allocated by pci_ereport_setup() + */ + if (DDI_FM_EREPORT_CAP(instance->fm_capabilities) || + DDI_FM_ERRCB_CAP(instance->fm_capabilities)) { + pci_ereport_teardown(instance->dip); + } + + /* Unregister from IO Fault Services */ + ddi_fm_fini(instance->dip); + + /* Adjust access and dma attributes for FMA */ + endian_attr.devacc_attr_access = DDI_DEFAULT_ACC; + drsas_generic_dma_attr.dma_attr_flags = 0; + } +} + +int +drsas_check_acc_handle(ddi_acc_handle_t handle) +{ + ddi_fm_error_t de; + + if (handle == NULL) { + return (DDI_FAILURE); + } + + ddi_fm_acc_err_get(handle, &de, DDI_FME_VERSION); + + return (de.fme_status); +} + +int +drsas_check_dma_handle(ddi_dma_handle_t handle) +{ + ddi_fm_error_t de; + + if (handle == NULL) { + return (DDI_FAILURE); + } + + ddi_fm_dma_err_get(handle, &de, DDI_FME_VERSION); + + return (de.fme_status); +} + +void +drsas_fm_ereport(struct drsas_instance *instance, char *detail) +{ + uint64_t ena; + char buf[FM_MAX_CLASS]; + + (void) snprintf(buf, FM_MAX_CLASS, "%s.%s", DDI_FM_DEVICE, detail); + ena = fm_ena_generate(0, FM_ENA_FMT1); + if (DDI_FM_EREPORT_CAP(instance->fm_capabilities)) { + ddi_fm_ereport_post(instance->dip, buf, ena, DDI_NOSLEEP, + FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERSION, NULL); + } +} + +static int +drsas_add_intrs(struct drsas_instance *instance, int intr_type) +{ + + dev_info_t *dip = instance->dip; + int avail, actual, count; + int i, flag, ret; + + con_log(CL_DLEVEL1, (CE_WARN, "drsas_add_intrs: intr_type = %x", + intr_type)); + + /* Get number of interrupts */ + ret = ddi_intr_get_nintrs(dip, intr_type, &count); + if ((ret != DDI_SUCCESS) || (count == 0)) { + con_log(CL_ANN, (CE_WARN, "ddi_intr_get_nintrs() failed:" + "ret %d count %d", ret, count)); + + return (DDI_FAILURE); + } + + con_log(CL_DLEVEL1, (CE_WARN, "drsas_add_intrs: count = %d ", count)); + + /* Get number of available interrupts */ + ret = ddi_intr_get_navail(dip, intr_type, &avail); + if ((ret != DDI_SUCCESS) || (avail == 0)) { + con_log(CL_ANN, (CE_WARN, "ddi_intr_get_navail() failed:" + "ret %d avail %d", ret, avail)); + + return (DDI_FAILURE); + } + con_log(CL_DLEVEL1, (CE_WARN, "drsas_add_intrs: avail = %d ", avail)); + + /* Only one interrupt routine. So limit the count to 1 */ + if (count > 1) { + count = 1; + } + + /* + * Allocate an array of interrupt handlers. Currently we support + * only one interrupt. The framework can be extended later. + */ + instance->intr_size = count * sizeof (ddi_intr_handle_t); + instance->intr_htable = kmem_zalloc(instance->intr_size, KM_SLEEP); + ASSERT(instance->intr_htable); + + flag = ((intr_type == DDI_INTR_TYPE_MSI) || (intr_type == + DDI_INTR_TYPE_MSIX)) ? DDI_INTR_ALLOC_STRICT:DDI_INTR_ALLOC_NORMAL; + + /* Allocate interrupt */ + ret = ddi_intr_alloc(dip, instance->intr_htable, intr_type, 0, + count, &actual, flag); + + if ((ret != DDI_SUCCESS) || (actual == 0)) { + con_log(CL_ANN, (CE_WARN, "drsas_add_intrs: " + "avail = %d", avail)); + kmem_free(instance->intr_htable, instance->intr_size); + return (DDI_FAILURE); + } + if (actual < count) { + con_log(CL_ANN, (CE_WARN, "drsas_add_intrs: " + "Requested = %d Received = %d", count, actual)); + } + instance->intr_cnt = actual; + + /* + * Get the priority of the interrupt allocated. + */ + if ((ret = ddi_intr_get_pri(instance->intr_htable[0], + &instance->intr_pri)) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "drsas_add_intrs: " + "get priority call failed")); + + for (i = 0; i < actual; i++) { + (void) ddi_intr_free(instance->intr_htable[i]); + } + kmem_free(instance->intr_htable, instance->intr_size); + return (DDI_FAILURE); + } + + /* + * Test for high level mutex. we don't support them. + */ + if (instance->intr_pri >= ddi_intr_get_hilevel_pri()) { + con_log(CL_ANN, (CE_WARN, "drsas_add_intrs: " + "High level interrupts not supported.")); + + for (i = 0; i < actual; i++) { + (void) ddi_intr_free(instance->intr_htable[i]); + } + kmem_free(instance->intr_htable, instance->intr_size); + return (DDI_FAILURE); + } + + con_log(CL_DLEVEL1, (CE_NOTE, "drsas_add_intrs: intr_pri = 0x%x ", + instance->intr_pri)); + + /* Call ddi_intr_add_handler() */ + for (i = 0; i < actual; i++) { + ret = ddi_intr_add_handler(instance->intr_htable[i], + (ddi_intr_handler_t *)drsas_isr, (caddr_t)instance, + (caddr_t)(uintptr_t)i); + + if (ret != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "drsas_add_intrs:" + "failed %d", ret)); + + for (i = 0; i < actual; i++) { + (void) ddi_intr_free(instance->intr_htable[i]); + } + kmem_free(instance->intr_htable, instance->intr_size); + return (DDI_FAILURE); + } + + } + + con_log(CL_DLEVEL1, (CE_WARN, " ddi_intr_add_handler done")); + + if ((ret = ddi_intr_get_cap(instance->intr_htable[0], + &instance->intr_cap)) != DDI_SUCCESS) { + con_log(CL_ANN, (CE_WARN, "ddi_intr_get_cap() failed %d", + ret)); + + /* Free already allocated intr */ + for (i = 0; i < actual; i++) { + (void) ddi_intr_remove_handler( + instance->intr_htable[i]); + (void) ddi_intr_free(instance->intr_htable[i]); + } + kmem_free(instance->intr_htable, instance->intr_size); + return (DDI_FAILURE); + } + + if (instance->intr_cap & DDI_INTR_FLAG_BLOCK) { + con_log(CL_ANN, (CE_WARN, "Calling ddi_intr_block _enable")); + + (void) ddi_intr_block_enable(instance->intr_htable, + instance->intr_cnt); + } else { + con_log(CL_ANN, (CE_NOTE, " calling ddi_intr_enable")); + + for (i = 0; i < instance->intr_cnt; i++) { + (void) ddi_intr_enable(instance->intr_htable[i]); + con_log(CL_ANN, (CE_NOTE, "ddi intr enable returns " + "%d", i)); + } + } + + return (DDI_SUCCESS); + +} + + +static void +drsas_rem_intrs(struct drsas_instance *instance) +{ + int i; + + con_log(CL_ANN, (CE_NOTE, "drsas_rem_intrs called")); + + /* Disable all interrupts first */ + if (instance->intr_cap & DDI_INTR_FLAG_BLOCK) { + (void) ddi_intr_block_disable(instance->intr_htable, + instance->intr_cnt); + } else { + for (i = 0; i < instance->intr_cnt; i++) { + (void) ddi_intr_disable(instance->intr_htable[i]); + } + } + + /* Remove all the handlers */ + + for (i = 0; i < instance->intr_cnt; i++) { + (void) ddi_intr_remove_handler(instance->intr_htable[i]); + (void) ddi_intr_free(instance->intr_htable[i]); + } + + kmem_free(instance->intr_htable, instance->intr_size); +} + +static int +drsas_tran_bus_config(dev_info_t *parent, uint_t flags, + ddi_bus_config_op_t op, void *arg, dev_info_t **childp) +{ + struct drsas_instance *instance; + int config; + int rval; + + char *ptr = NULL; + int tgt, lun; + + con_log(CL_ANN1, (CE_NOTE, "Bus config called for op = %x", op)); + + if ((instance = ddi_get_soft_state(drsas_state, + ddi_get_instance(parent))) == NULL) { + return (NDI_FAILURE); + } + + /* Hold nexus during bus_config */ + ndi_devi_enter(parent, &config); + switch (op) { + case BUS_CONFIG_ONE: { + + /* parse wwid/target name out of name given */ + if ((ptr = strchr((char *)arg, '@')) == NULL) { + rval = NDI_FAILURE; + break; + } + ptr++; + + if (drsas_parse_devname(arg, &tgt, &lun) != 0) { + rval = NDI_FAILURE; + break; + } + + if (lun == 0) { + rval = drsas_config_ld(instance, tgt, lun, childp); + } else { + rval = NDI_FAILURE; + } + + break; + } + case BUS_CONFIG_DRIVER: + case BUS_CONFIG_ALL: { + + rval = drsas_config_all_devices(instance); + + rval = NDI_SUCCESS; + break; + } + } + + if (rval == NDI_SUCCESS) { + rval = ndi_busop_bus_config(parent, flags, op, arg, childp, 0); + + } + ndi_devi_exit(parent, config); + + con_log(CL_ANN1, (CE_NOTE, "drsas_tran_bus_config: rval = %x", + rval)); + return (rval); +} + +static int +drsas_config_all_devices(struct drsas_instance *instance) +{ + int rval, tgt; + + for (tgt = 0; tgt < MRDRV_MAX_LD; tgt++) { + (void) drsas_config_ld(instance, tgt, 0, NULL); + + } + + rval = NDI_SUCCESS; + return (rval); +} + +static int +drsas_parse_devname(char *devnm, int *tgt, int *lun) +{ + char devbuf[SCSI_MAXNAMELEN]; + char *addr; + char *p, *tp, *lp; + long num; + + /* Parse dev name and address */ + (void) strcpy(devbuf, devnm); + addr = ""; + for (p = devbuf; *p != '\0'; p++) { + if (*p == '@') { + addr = p + 1; + *p = '\0'; + } else if (*p == ':') { + *p = '\0'; + break; + } + } + + /* Parse target and lun */ + for (p = tp = addr, lp = NULL; *p != '\0'; p++) { + if (*p == ',') { + lp = p + 1; + *p = '\0'; + break; + } + } + if (tgt && tp) { + if (ddi_strtol(tp, NULL, 0x10, &num)) { + return (DDI_FAILURE); /* Can declare this as constant */ + } + *tgt = (int)num; + } + if (lun && lp) { + if (ddi_strtol(lp, NULL, 0x10, &num)) { + return (DDI_FAILURE); + } + *lun = (int)num; + } + return (DDI_SUCCESS); /* Success case */ +} + +static int +drsas_config_ld(struct drsas_instance *instance, uint16_t tgt, + uint8_t lun, dev_info_t **ldip) +{ + struct scsi_device *sd; + dev_info_t *child; + int rval; + + con_log(CL_ANN1, (CE_NOTE, "drsas_config_ld: t = %d l = %d", + tgt, lun)); + + if ((child = drsas_find_child(instance, tgt, lun)) != NULL) { + if (ldip) { + *ldip = child; + } + con_log(CL_ANN1, (CE_NOTE, + "drsas_config_ld: Child = %p found t = %d l = %d", + (void *)child, tgt, lun)); + return (NDI_SUCCESS); + } + + sd = kmem_zalloc(sizeof (struct scsi_device), KM_SLEEP); + sd->sd_address.a_hba_tran = instance->tran; + sd->sd_address.a_target = (uint16_t)tgt; + sd->sd_address.a_lun = (uint8_t)lun; + + if (scsi_hba_probe(sd, NULL) == SCSIPROBE_EXISTS) + rval = drsas_config_scsi_device(instance, sd, ldip); + else + rval = NDI_FAILURE; + + /* sd_unprobe is blank now. Free buffer manually */ + if (sd->sd_inq) { + kmem_free(sd->sd_inq, SUN_INQSIZE); + sd->sd_inq = (struct scsi_inquiry *)NULL; + } + + kmem_free(sd, sizeof (struct scsi_device)); + con_log(CL_ANN1, (CE_NOTE, "drsas_config_ld: return rval = %d", + rval)); + return (rval); +} + +static int +drsas_config_scsi_device(struct drsas_instance *instance, + struct scsi_device *sd, dev_info_t **dipp) +{ + char *nodename = NULL; + char **compatible = NULL; + int ncompatible = 0; + char *childname; + dev_info_t *ldip = NULL; + int tgt = sd->sd_address.a_target; + int lun = sd->sd_address.a_lun; + int dtype = sd->sd_inq->inq_dtype & DTYPE_MASK; + int rval; + + con_log(CL_ANN1, (CE_WARN, "dr_sas: scsi_device t%dL%d", tgt, lun)); + scsi_hba_nodename_compatible_get(sd->sd_inq, NULL, dtype, + NULL, &nodename, &compatible, &ncompatible); + + if (nodename == NULL) { + con_log(CL_ANN1, (CE_WARN, "dr_sas: Found no compatible driver " + "for t%dL%d", tgt, lun)); + rval = NDI_FAILURE; + goto finish; + } + + childname = (dtype == DTYPE_DIRECT) ? "sd" : nodename; + con_log(CL_ANN1, (CE_WARN, + "dr_sas: Childname = %2s nodename = %s", childname, nodename)); + + /* Create a dev node */ + rval = ndi_devi_alloc(instance->dip, childname, DEVI_SID_NODEID, &ldip); + con_log(CL_ANN1, (CE_WARN, + "dr_sas_config_scsi_device: ndi_devi_alloc rval = %x", rval)); + if (rval == NDI_SUCCESS) { + if (ndi_prop_update_int(DDI_DEV_T_NONE, ldip, "target", tgt) != + DDI_PROP_SUCCESS) { + con_log(CL_ANN1, (CE_WARN, "dr_sas: unable to create " + "property for t%dl%d target", tgt, lun)); + rval = NDI_FAILURE; + goto finish; + } + if (ndi_prop_update_int(DDI_DEV_T_NONE, ldip, "lun", lun) != + DDI_PROP_SUCCESS) { + con_log(CL_ANN1, (CE_WARN, "dr_sas: unable to create " + "property for t%dl%d lun", tgt, lun)); + rval = NDI_FAILURE; + goto finish; + } + + if (ndi_prop_update_string_array(DDI_DEV_T_NONE, ldip, + "compatible", compatible, ncompatible) != + DDI_PROP_SUCCESS) { + con_log(CL_ANN1, (CE_WARN, "dr_sas: unable to create " + "property for t%dl%d compatible", tgt, lun)); + rval = NDI_FAILURE; + goto finish; + } + + rval = ndi_devi_online(ldip, NDI_ONLINE_ATTACH); + if (rval != NDI_SUCCESS) { + con_log(CL_ANN1, (CE_WARN, "dr_sas: unable to online " + "t%dl%d", tgt, lun)); + ndi_prop_remove_all(ldip); + (void) ndi_devi_free(ldip); + } else { + con_log(CL_ANN1, (CE_WARN, "dr_sas: online Done :" + "0 t%dl%d", tgt, lun)); + } + + } +finish: + if (dipp) { + *dipp = ldip; + } + + con_log(CL_DLEVEL1, (CE_WARN, + "dr_sas: config_scsi_device rval = %d t%dL%d", + rval, tgt, lun)); + scsi_hba_nodename_compatible_free(nodename, compatible); + return (rval); +} + +/*ARGSUSED*/ +static int +drsas_service_evt(struct drsas_instance *instance, int tgt, int lun, int event, + uint64_t wwn) +{ + struct drsas_eventinfo *mrevt = NULL; + + con_log(CL_ANN1, (CE_NOTE, + "drsas_service_evt called for t%dl%d event = %d", + tgt, lun, event)); + + if ((instance->taskq == NULL) || (mrevt = + kmem_zalloc(sizeof (struct drsas_eventinfo), KM_NOSLEEP)) == NULL) { + return (ENOMEM); + } + + mrevt->instance = instance; + mrevt->tgt = tgt; + mrevt->lun = lun; + mrevt->event = event; + + if ((ddi_taskq_dispatch(instance->taskq, + (void (*)(void *))drsas_issue_evt_taskq, mrevt, DDI_NOSLEEP)) != + DDI_SUCCESS) { + con_log(CL_ANN1, (CE_NOTE, + "dr_sas: Event task failed for t%dl%d event = %d", + tgt, lun, event)); + kmem_free(mrevt, sizeof (struct drsas_eventinfo)); + return (DDI_FAILURE); + } + return (DDI_SUCCESS); +} + +static void +drsas_issue_evt_taskq(struct drsas_eventinfo *mrevt) +{ + struct drsas_instance *instance = mrevt->instance; + dev_info_t *dip, *pdip; + int circ1 = 0; + char *devname; + + con_log(CL_ANN1, (CE_NOTE, "drsas_issue_evt_taskq: called for" + " tgt %d lun %d event %d", + mrevt->tgt, mrevt->lun, mrevt->event)); + + if (mrevt->tgt < MRDRV_MAX_LD && mrevt->lun == 0) { + dip = instance->dr_ld_list[mrevt->tgt].dip; + } else { + return; + } + + ndi_devi_enter(instance->dip, &circ1); + switch (mrevt->event) { + case DRSAS_EVT_CONFIG_TGT: + if (dip == NULL) { + + if (mrevt->lun == 0) { + (void) drsas_config_ld(instance, mrevt->tgt, + 0, NULL); + } + con_log(CL_ANN1, (CE_NOTE, + "dr_sas: EVT_CONFIG_TGT called:" + " for tgt %d lun %d event %d", + mrevt->tgt, mrevt->lun, mrevt->event)); + + } else { + con_log(CL_ANN1, (CE_NOTE, + "dr_sas: EVT_CONFIG_TGT dip != NULL:" + " for tgt %d lun %d event %d", + mrevt->tgt, mrevt->lun, mrevt->event)); + } + break; + case DRSAS_EVT_UNCONFIG_TGT: + if (dip) { + if (i_ddi_devi_attached(dip)) { + + pdip = ddi_get_parent(dip); + + devname = kmem_zalloc(MAXNAMELEN + 1, KM_SLEEP); + (void) ddi_deviname(dip, devname); + + (void) devfs_clean(pdip, devname + 1, + DV_CLEAN_FORCE); + kmem_free(devname, MAXNAMELEN + 1); + } + (void) ndi_devi_offline(dip, NDI_DEVI_REMOVE); + con_log(CL_ANN1, (CE_NOTE, + "dr_sas: EVT_UNCONFIG_TGT called:" + " for tgt %d lun %d event %d", + mrevt->tgt, mrevt->lun, mrevt->event)); + } else { + con_log(CL_ANN1, (CE_NOTE, + "dr_sas: EVT_UNCONFIG_TGT dip == NULL:" + " for tgt %d lun %d event %d", + mrevt->tgt, mrevt->lun, mrevt->event)); + } + break; + } + kmem_free(mrevt, sizeof (struct drsas_eventinfo)); + ndi_devi_exit(instance->dip, circ1); +} + +static int +drsas_mode_sense_build(struct scsi_pkt *pkt) +{ + union scsi_cdb *cdbp; + uint16_t page_code; + struct scsa_cmd *acmd; + struct buf *bp; + struct mode_header *modehdrp; + + cdbp = (void *)pkt->pkt_cdbp; + page_code = cdbp->cdb_un.sg.scsi[0]; + acmd = PKT2CMD(pkt); + bp = acmd->cmd_buf; + if ((!bp) && bp->b_un.b_addr && bp->b_bcount && acmd->cmd_dmacount) { + con_log(CL_ANN1, (CE_WARN, "Failing MODESENSE Command")); + /* ADD pkt statistics as Command failed. */ + return (NULL); + } + + bp_mapin(bp); + bzero(bp->b_un.b_addr, bp->b_bcount); + + switch (page_code) { + case 0x3: { + struct mode_format *page3p = NULL; + modehdrp = (struct mode_header *)(bp->b_un.b_addr); + modehdrp->bdesc_length = MODE_BLK_DESC_LENGTH; + + page3p = (void *)((caddr_t)modehdrp + + MODE_HEADER_LENGTH + MODE_BLK_DESC_LENGTH); + page3p->mode_page.code = 0x3; + page3p->mode_page.length = + (uchar_t)(sizeof (struct mode_format)); + page3p->data_bytes_sect = 512; + page3p->sect_track = 63; + break; + } + case 0x4: { + struct mode_geometry *page4p = NULL; + modehdrp = (struct mode_header *)(bp->b_un.b_addr); + modehdrp->bdesc_length = MODE_BLK_DESC_LENGTH; + + page4p = (void *)((caddr_t)modehdrp + + MODE_HEADER_LENGTH + MODE_BLK_DESC_LENGTH); + page4p->mode_page.code = 0x4; + page4p->mode_page.length = + (uchar_t)(sizeof (struct mode_geometry)); + page4p->heads = 255; + page4p->rpm = 10000; + break; + } + default: + break; + } + return (NULL); +} diff --git a/usr/src/uts/common/io/dr_sas/dr_sas.conf b/usr/src/uts/common/io/dr_sas/dr_sas.conf new file mode 100644 index 0000000000..3792f43ca4 --- /dev/null +++ b/usr/src/uts/common/io/dr_sas/dr_sas.conf @@ -0,0 +1,15 @@ +# +# Copyright (c) 2008-2009, LSI Logic Corporation. +# All rights reserved. +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# dr_sas.conf for sol 10 (and later) for all supported architectures +# +# global definitions + +# MSI specific flag. user can uncomment this line and set flag "yes" to enable MSI +#drsas-enable-msi="yes"; diff --git a/usr/src/uts/common/io/dr_sas/dr_sas.h b/usr/src/uts/common/io/dr_sas/dr_sas.h new file mode 100644 index 0000000000..8f78658edf --- /dev/null +++ b/usr/src/uts/common/io/dr_sas/dr_sas.h @@ -0,0 +1,1766 @@ +/* + * dr_sas.h: header for dr_sas + * + * Solaris MegaRAID driver for SAS2.0 controllers + * Copyright (c) 2008-2009, LSI Logic Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _DR_SAS_H_ +#define _DR_SAS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/scsi/scsi.h> +#include "dr_sas_list.h" + +/* + * MegaRAID SAS2.0 Driver meta data + */ +#define DRSAS_VERSION "LSIv2.0" +#define DRSAS_RELDATE "Jan 9, 2009" + +#define DRSAS_TRUE 1 +#define DRSAS_FALSE 0 + +/* + * MegaRAID SAS2.0 device id conversion definitions. + */ +#define INST2LSIRDCTL(x) ((x) << INST_MINOR_SHIFT) + +/* + * MegaRAID SAS2.0 supported controllers + */ +#define PCI_DEVICE_ID_LSI_2108VDE 0x0078 +#define PCI_DEVICE_ID_LSI_2108V 0x0079 + +/* + * Register Index for 2108 Controllers. + */ +#define REGISTER_SET_IO_2108 (2) + +#define DRSAS_MAX_SGE_CNT 0x50 + +#define DRSAS_IOCTL_DRIVER 0x12341234 +#define DRSAS_IOCTL_FIRMWARE 0x12345678 +#define DRSAS_IOCTL_AEN 0x87654321 + +#define DRSAS_1_SECOND 1000000 + +/* Dynamic Enumeration Flags */ +#define DRSAS_PD_LUN 1 +#define DRSAS_LD_LUN 0 +#define DRSAS_PD_TGT_MAX 255 +#define DRSAS_GET_PD_MAX(s) ((s)->dr_pd_max) +#define WWN_STRLEN 17 + +/* + * ===================================== + * MegaRAID SAS2.0 MFI firmware definitions + * ===================================== + */ +/* + * MFI stands for MegaRAID SAS2.0 FW Interface. This is just a moniker for + * protocol between the software and firmware. Commands are issued using + * "message frames" + */ + +/* + * FW posts its state in upper 4 bits of outbound_msg_0 register + */ +#define MFI_STATE_SHIFT 28 +#define MFI_STATE_MASK ((uint32_t)0xF<<MFI_STATE_SHIFT) +#define MFI_STATE_UNDEFINED ((uint32_t)0x0<<MFI_STATE_SHIFT) +#define MFI_STATE_BB_INIT ((uint32_t)0x1<<MFI_STATE_SHIFT) +#define MFI_STATE_FW_INIT ((uint32_t)0x4<<MFI_STATE_SHIFT) +#define MFI_STATE_WAIT_HANDSHAKE ((uint32_t)0x6<<MFI_STATE_SHIFT) +#define MFI_STATE_FW_INIT_2 ((uint32_t)0x7<<MFI_STATE_SHIFT) +#define MFI_STATE_DEVICE_SCAN ((uint32_t)0x8<<MFI_STATE_SHIFT) +#define MFI_STATE_BOOT_MESSAGE_PENDING ((uint32_t)0x9<<MFI_STATE_SHIFT) +#define MFI_STATE_FLUSH_CACHE ((uint32_t)0xA<<MFI_STATE_SHIFT) +#define MFI_STATE_READY ((uint32_t)0xB<<MFI_STATE_SHIFT) +#define MFI_STATE_OPERATIONAL ((uint32_t)0xC<<MFI_STATE_SHIFT) +#define MFI_STATE_FAULT ((uint32_t)0xF<<MFI_STATE_SHIFT) + +#define MRMFI_FRAME_SIZE 64 + +/* + * During FW init, clear pending cmds & reset state using inbound_msg_0 + * + * ABORT : Abort all pending cmds + * READY : Move from OPERATIONAL to READY state; discard queue info + * MFIMODE : Discard (possible) low MFA posted in 64-bit mode (??) + * CLR_HANDSHAKE: FW is waiting for HANDSHAKE from BIOS or Driver + */ +#define MFI_INIT_ABORT 0x00000001 +#define MFI_INIT_READY 0x00000002 +#define MFI_INIT_MFIMODE 0x00000004 +#define MFI_INIT_CLEAR_HANDSHAKE 0x00000008 +#define MFI_INIT_HOTPLUG 0x00000010 +#define MFI_STOP_ADP 0x00000020 +#define MFI_RESET_FLAGS MFI_INIT_READY|MFI_INIT_MFIMODE|MFI_INIT_ABORT + +/* + * MFI frame flags + */ +#define MFI_FRAME_POST_IN_REPLY_QUEUE 0x0000 +#define MFI_FRAME_DONT_POST_IN_REPLY_QUEUE 0x0001 +#define MFI_FRAME_SGL32 0x0000 +#define MFI_FRAME_SGL64 0x0002 +#define MFI_FRAME_SENSE32 0x0000 +#define MFI_FRAME_SENSE64 0x0004 +#define MFI_FRAME_DIR_NONE 0x0000 +#define MFI_FRAME_DIR_WRITE 0x0008 +#define MFI_FRAME_DIR_READ 0x0010 +#define MFI_FRAME_DIR_BOTH 0x0018 + +/* + * Definition for cmd_status + */ +#define MFI_CMD_STATUS_POLL_MODE 0xFF +#define MFI_CMD_STATUS_SYNC_MODE 0xFF + +/* + * MFI command opcodes + */ +#define MFI_CMD_OP_INIT 0x00 +#define MFI_CMD_OP_LD_READ 0x01 +#define MFI_CMD_OP_LD_WRITE 0x02 +#define MFI_CMD_OP_LD_SCSI 0x03 +#define MFI_CMD_OP_PD_SCSI 0x04 +#define MFI_CMD_OP_DCMD 0x05 +#define MFI_CMD_OP_ABORT 0x06 +#define MFI_CMD_OP_SMP 0x07 +#define MFI_CMD_OP_STP 0x08 + +#define DR_DCMD_CTRL_GET_INFO 0x01010000 + +#define DR_DCMD_CTRL_CACHE_FLUSH 0x01101000 +#define DR_FLUSH_CTRL_CACHE 0x01 +#define DR_FLUSH_DISK_CACHE 0x02 + +#define DR_DCMD_CTRL_SHUTDOWN 0x01050000 +#define DRSAS_ENABLE_DRIVE_SPINDOWN 0x01 + +#define DR_DCMD_CTRL_EVENT_GET_INFO 0x01040100 +#define DR_DCMD_CTRL_EVENT_GET 0x01040300 +#define DR_DCMD_CTRL_EVENT_WAIT 0x01040500 +#define DR_DCMD_LD_GET_PROPERTIES 0x03030000 +#define DR_DCMD_PD_GET_INFO 0x02020000 + +/* + * Solaris Specific MAX values + */ +#define MAX_SGL 24 +/* + * MFI command completion codes + */ +enum MFI_STAT { + MFI_STAT_OK = 0x00, + MFI_STAT_INVALID_CMD = 0x01, + MFI_STAT_INVALID_DCMD = 0x02, + MFI_STAT_INVALID_PARAMETER = 0x03, + MFI_STAT_INVALID_SEQUENCE_NUMBER = 0x04, + MFI_STAT_ABORT_NOT_POSSIBLE = 0x05, + MFI_STAT_APP_HOST_CODE_NOT_FOUND = 0x06, + MFI_STAT_APP_IN_USE = 0x07, + MFI_STAT_APP_NOT_INITIALIZED = 0x08, + MFI_STAT_ARRAY_INDEX_INVALID = 0x09, + MFI_STAT_ARRAY_ROW_NOT_EMPTY = 0x0a, + MFI_STAT_CONFIG_RESOURCE_CONFLICT = 0x0b, + MFI_STAT_DEVICE_NOT_FOUND = 0x0c, + MFI_STAT_DRIVE_TOO_SMALL = 0x0d, + MFI_STAT_FLASH_ALLOC_FAIL = 0x0e, + MFI_STAT_FLASH_BUSY = 0x0f, + MFI_STAT_FLASH_ERROR = 0x10, + MFI_STAT_FLASH_IMAGE_BAD = 0x11, + MFI_STAT_FLASH_IMAGE_INCOMPLETE = 0x12, + MFI_STAT_FLASH_NOT_OPEN = 0x13, + MFI_STAT_FLASH_NOT_STARTED = 0x14, + MFI_STAT_FLUSH_FAILED = 0x15, + MFI_STAT_HOST_CODE_NOT_FOUNT = 0x16, + MFI_STAT_LD_CC_IN_PROGRESS = 0x17, + MFI_STAT_LD_INIT_IN_PROGRESS = 0x18, + MFI_STAT_LD_LBA_OUT_OF_RANGE = 0x19, + MFI_STAT_LD_MAX_CONFIGURED = 0x1a, + MFI_STAT_LD_NOT_OPTIMAL = 0x1b, + MFI_STAT_LD_RBLD_IN_PROGRESS = 0x1c, + MFI_STAT_LD_RECON_IN_PROGRESS = 0x1d, + MFI_STAT_LD_WRONG_RAID_LEVEL = 0x1e, + MFI_STAT_MAX_SPARES_EXCEEDED = 0x1f, + MFI_STAT_MEMORY_NOT_AVAILABLE = 0x20, + MFI_STAT_MFC_HW_ERROR = 0x21, + MFI_STAT_NO_HW_PRESENT = 0x22, + MFI_STAT_NOT_FOUND = 0x23, + MFI_STAT_NOT_IN_ENCL = 0x24, + MFI_STAT_PD_CLEAR_IN_PROGRESS = 0x25, + MFI_STAT_PD_TYPE_WRONG = 0x26, + MFI_STAT_PR_DISABLED = 0x27, + MFI_STAT_ROW_INDEX_INVALID = 0x28, + MFI_STAT_SAS_CONFIG_INVALID_ACTION = 0x29, + MFI_STAT_SAS_CONFIG_INVALID_DATA = 0x2a, + MFI_STAT_SAS_CONFIG_INVALID_PAGE = 0x2b, + MFI_STAT_SAS_CONFIG_INVALID_TYPE = 0x2c, + MFI_STAT_SCSI_DONE_WITH_ERROR = 0x2d, + MFI_STAT_SCSI_IO_FAILED = 0x2e, + MFI_STAT_SCSI_RESERVATION_CONFLICT = 0x2f, + MFI_STAT_SHUTDOWN_FAILED = 0x30, + MFI_STAT_TIME_NOT_SET = 0x31, + MFI_STAT_WRONG_STATE = 0x32, + MFI_STAT_LD_OFFLINE = 0x33, + /* UNUSED: 0x34 to 0xfe */ + MFI_STAT_INVALID_STATUS = 0xFF +}; + +enum DR_EVT_CLASS { + DR_EVT_CLASS_DEBUG = -2, + DR_EVT_CLASS_PROGRESS = -1, + DR_EVT_CLASS_INFO = 0, + DR_EVT_CLASS_WARNING = 1, + DR_EVT_CLASS_CRITICAL = 2, + DR_EVT_CLASS_FATAL = 3, + DR_EVT_CLASS_DEAD = 4 +}; + +enum DR_EVT_LOCALE { + DR_EVT_LOCALE_LD = 0x0001, + DR_EVT_LOCALE_PD = 0x0002, + DR_EVT_LOCALE_ENCL = 0x0004, + DR_EVT_LOCALE_BBU = 0x0008, + DR_EVT_LOCALE_SAS = 0x0010, + DR_EVT_LOCALE_CTRL = 0x0020, + DR_EVT_LOCALE_CONFIG = 0x0040, + DR_EVT_LOCALE_CLUSTER = 0x0080, + DR_EVT_LOCALE_ALL = 0xffff +}; + +#define DR_EVT_CFG_CLEARED 0x0004 +#define DR_EVT_LD_CREATED 0x008a +#define DR_EVT_LD_DELETED 0x008b +#define DR_EVT_PD_REMOVED_EXT 0x00f8 +#define DR_EVT_PD_INSERTED_EXT 0x00f7 + +enum LD_STATE { + LD_OFFLINE = 0, + LD_PARTIALLY_DEGRADED = 1, + LD_DEGRADED = 2, + LD_OPTIMAL = 3, + LD_INVALID = 0xFF +}; + +enum DRSAS_EVT { + DRSAS_EVT_CONFIG_TGT = 0, + DRSAS_EVT_UNCONFIG_TGT = 1, + DRSAS_EVT_UNCONFIG_SMP = 2 +}; + +#define DMA_OBJ_ALLOCATED 1 +#define DMA_OBJ_REALLOCATED 2 +#define DMA_OBJ_FREED 3 + +/* + * dma_obj_t - Our DMA object + * @param buffer : kernel virtual address + * @param size : size of the data to be allocated + * @param acc_handle : access handle + * @param dma_handle : dma handle + * @param dma_cookie : scatter-gather list + * @param dma_attr : dma attributes for this buffer + * Our DMA object. The caller must initialize the size and dma attributes + * (dma_attr) fields before allocating the resources. + */ +typedef struct { + caddr_t buffer; + uint32_t size; + ddi_acc_handle_t acc_handle; + ddi_dma_handle_t dma_handle; + ddi_dma_cookie_t dma_cookie[DRSAS_MAX_SGE_CNT]; + ddi_dma_attr_t dma_attr; + uint8_t status; + uint8_t reserved[3]; +} dma_obj_t; + +struct drsas_eventinfo { + struct drsas_instance *instance; + int tgt; + int lun; + int event; +}; + +struct drsas_ld { + dev_info_t *dip; + uint8_t lun_type; + uint8_t reserved[3]; +}; + +struct drsas_pd { + dev_info_t *dip; + uint8_t lun_type; + uint8_t dev_id; + uint8_t flags; + uint8_t reserved; +}; + +struct drsas_pd_info { + uint16_t deviceId; + uint16_t seqNum; + uint8_t inquiryData[96]; + uint8_t vpdPage83[64]; + uint8_t notSupported; + uint8_t scsiDevType; + uint8_t a; + uint8_t device_speed; + uint32_t mediaerrcnt; + uint32_t other; + uint32_t pred; + uint32_t lastpred; + uint16_t fwState; + uint8_t disabled; + uint8_t linkspwwd; + uint32_t ddfType; + struct { + uint8_t count; + uint8_t isPathBroken; + uint8_t connectorIndex[2]; + uint8_t reserved[4]; + uint64_t sasAddr[2]; + uint8_t reserved2[16]; + } pathInfo; +}; + +typedef struct drsas_instance { + uint32_t *producer; + uint32_t *consumer; + + uint32_t *reply_queue; + dma_obj_t mfi_internal_dma_obj; + + uint8_t init_id; + uint8_t reserved[3]; + + uint16_t max_num_sge; + uint16_t max_fw_cmds; + uint32_t max_sectors_per_req; + + struct drsas_cmd **cmd_list; + + mlist_t cmd_pool_list; + kmutex_t cmd_pool_mtx; + + mlist_t cmd_pend_list; + kmutex_t cmd_pend_mtx; + + dma_obj_t mfi_evt_detail_obj; + struct drsas_cmd *aen_cmd; + + uint32_t aen_seq_num; + uint32_t aen_class_locale_word; + + scsi_hba_tran_t *tran; + + kcondvar_t int_cmd_cv; + kmutex_t int_cmd_mtx; + + kcondvar_t aen_cmd_cv; + kmutex_t aen_cmd_mtx; + + kcondvar_t abort_cmd_cv; + kmutex_t abort_cmd_mtx; + + dev_info_t *dip; + ddi_acc_handle_t pci_handle; + + timeout_id_t timeout_id; + uint32_t unique_id; + uint16_t fw_outstanding; + caddr_t regmap; + ddi_acc_handle_t regmap_handle; + uint8_t isr_level; + ddi_iblock_cookie_t iblock_cookie; + ddi_iblock_cookie_t soft_iblock_cookie; + ddi_softintr_t soft_intr_id; + uint8_t softint_running; + kmutex_t completed_pool_mtx; + mlist_t completed_pool_list; + + caddr_t internal_buf; + uint32_t internal_buf_dmac_add; + uint32_t internal_buf_size; + + uint16_t vendor_id; + uint16_t device_id; + uint16_t subsysvid; + uint16_t subsysid; + int instance; + int baseaddress; + char iocnode[16]; + + int fm_capabilities; + + struct drsas_func_ptr *func_ptr; + /* MSI interrupts specific */ + ddi_intr_handle_t *intr_htable; + int intr_type; + int intr_cnt; + size_t intr_size; + uint_t intr_pri; + int intr_cap; + + ddi_taskq_t *taskq; + struct drsas_ld *dr_ld_list; +} drsas_t; + +struct drsas_func_ptr { + int (*read_fw_status_reg)(struct drsas_instance *); + void (*issue_cmd)(struct drsas_cmd *, struct drsas_instance *); + int (*issue_cmd_in_sync_mode)(struct drsas_instance *, + struct drsas_cmd *); + int (*issue_cmd_in_poll_mode)(struct drsas_instance *, + struct drsas_cmd *); + void (*enable_intr)(struct drsas_instance *); + void (*disable_intr)(struct drsas_instance *); + int (*intr_ack)(struct drsas_instance *); +}; + +/* + * ### Helper routines ### + */ + +/* + * con_log() - console log routine + * @param level : indicates the severity of the message. + * @fparam mt : format string + * + * con_log displays the error messages on the console based on the current + * debug level. Also it attaches the appropriate kernel severity level with + * the message. + * + * + * console messages debug levels + */ +#define CL_NONE 0 /* No debug information */ +#define CL_ANN 1 /* print unconditionally, announcements */ +#define CL_ANN1 2 /* No o/p */ +#define CL_DLEVEL1 3 /* debug level 1, informative */ +#define CL_DLEVEL2 4 /* debug level 2, verbose */ +#define CL_DLEVEL3 5 /* debug level 3, very verbose */ + +#ifdef __SUNPRO_C +#define __func__ "" +#endif + +#define con_log(level, fmt) { if (debug_level_g >= level) cmn_err fmt; } + +/* + * ### SCSA definitions ### + */ +#define PKT2TGT(pkt) ((pkt)->pkt_address.a_target) +#define PKT2LUN(pkt) ((pkt)->pkt_address.a_lun) +#define PKT2TRAN(pkt) ((pkt)->pkt_adress.a_hba_tran) +#define ADDR2TRAN(ap) ((ap)->a_hba_tran) + +#define TRAN2MR(tran) (struct drsas_instance *)(tran)->tran_hba_private) +#define ADDR2MR(ap) (TRAN2MR(ADDR2TRAN(ap)) + +#define PKT2CMD(pkt) ((struct scsa_cmd *)(pkt)->pkt_ha_private) +#define CMD2PKT(sp) ((sp)->cmd_pkt) +#define PKT2REQ(pkt) (&(PKT2CMD(pkt)->request)) + +#define CMD2ADDR(cmd) (&CMD2PKT(cmd)->pkt_address) +#define CMD2TRAN(cmd) (CMD2PKT(cmd)->pkt_address.a_hba_tran) +#define CMD2MR(cmd) (TRAN2MR(CMD2TRAN(cmd))) + +#define CFLAG_DMAVALID 0x0001 /* requires a dma operation */ +#define CFLAG_DMASEND 0x0002 /* Transfer from the device */ +#define CFLAG_CONSISTENT 0x0040 /* consistent data transfer */ + +/* + * ### Data structures for ioctl inteface and internal commands ### + */ + +/* + * Data direction flags + */ +#define UIOC_RD 0x00001 +#define UIOC_WR 0x00002 + +#define SCP2HOST(scp) (scp)->device->host /* to host */ +#define SCP2HOSTDATA(scp) SCP2HOST(scp)->hostdata /* to soft state */ +#define SCP2CHANNEL(scp) (scp)->device->channel /* to channel */ +#define SCP2TARGET(scp) (scp)->device->id /* to target */ +#define SCP2LUN(scp) (scp)->device->lun /* to LUN */ + +#define SCSIHOST2ADAP(host) (((caddr_t *)(host->hostdata))[0]) +#define SCP2ADAPTER(scp) \ + (struct drsas_instance *)SCSIHOST2ADAP(SCP2HOST(scp)) + +#define MRDRV_IS_LOGICAL_SCSA(instance, acmd) \ + (acmd->device_id < MRDRV_MAX_LD) ? 1 : 0 +#define MRDRV_IS_LOGICAL(ap) \ + ((ap->a_target < MRDRV_MAX_LD) && (ap->a_lun == 0)) ? 1 : 0 +#define MAP_DEVICE_ID(instance, ap) \ + (ap->a_target) + +#define HIGH_LEVEL_INTR 1 +#define NORMAL_LEVEL_INTR 0 + +/* + * scsa_cmd - Per-command mr private data + * @param cmd_dmahandle : dma handle + * @param cmd_dmacookies : current dma cookies + * @param cmd_pkt : scsi_pkt reference + * @param cmd_dmacount : dma count + * @param cmd_cookie : next cookie + * @param cmd_ncookies : cookies per window + * @param cmd_cookiecnt : cookies per sub-win + * @param cmd_nwin : number of dma windows + * @param cmd_curwin : current dma window + * @param cmd_dma_offset : current window offset + * @param cmd_dma_len : current window length + * @param cmd_flags : private flags + * @param cmd_cdblen : length of cdb + * @param cmd_scblen : length of scb + * @param cmd_buf : command buffer + * @param channel : channel for scsi sub-system + * @param target : target for scsi sub-system + * @param lun : LUN for scsi sub-system + * + * - Allocated at same time as scsi_pkt by scsi_hba_pkt_alloc(9E) + * - Pointed to by pkt_ha_private field in scsi_pkt + */ +struct scsa_cmd { + ddi_dma_handle_t cmd_dmahandle; + ddi_dma_cookie_t cmd_dmacookies[DRSAS_MAX_SGE_CNT]; + struct scsi_pkt *cmd_pkt; + ulong_t cmd_dmacount; + uint_t cmd_cookie; + uint_t cmd_ncookies; + uint_t cmd_cookiecnt; + uint_t cmd_nwin; + uint_t cmd_curwin; + off_t cmd_dma_offset; + ulong_t cmd_dma_len; + ulong_t cmd_flags; + uint_t cmd_cdblen; + uint_t cmd_scblen; + struct buf *cmd_buf; + ushort_t device_id; + uchar_t islogical; + uchar_t lun; + struct drsas_device *drsas_dev; +}; + + +struct drsas_cmd { + union drsas_frame *frame; + uint32_t frame_phys_addr; + uint8_t *sense; + uint32_t sense_phys_addr; + dma_obj_t frame_dma_obj; + uint8_t frame_dma_obj_status; + + uint32_t index; + uint8_t sync_cmd; + uint8_t cmd_status; + uint16_t abort_aen; + mlist_t list; + uint32_t frame_count; + struct scsa_cmd *cmd; + struct scsi_pkt *pkt; +}; + +#define MAX_MGMT_ADAPTERS 1024 +#define IOC_SIGNATURE "MR-SAS" + +#define IOC_CMD_FIRMWARE 0x0 +#define DRSAS_DRIVER_IOCTL_COMMON 0xF0010000 +#define DRSAS_DRIVER_IOCTL_DRIVER_VERSION 0xF0010100 +#define DRSAS_DRIVER_IOCTL_PCI_INFORMATION 0xF0010200 +#define DRSAS_DRIVER_IOCTL_MRRAID_STATISTICS 0xF0010300 + + +#define DRSAS_MAX_SENSE_LENGTH 32 + +struct drsas_mgmt_info { + + uint16_t count; + struct drsas_instance *instance[MAX_MGMT_ADAPTERS]; + uint16_t map[MAX_MGMT_ADAPTERS]; + int max_index; +}; + +#pragma pack(1) + +/* + * SAS controller properties + */ +struct drsas_ctrl_prop { + uint16_t seq_num; + uint16_t pred_fail_poll_interval; + uint16_t intr_throttle_count; + uint16_t intr_throttle_timeouts; + + uint8_t rebuild_rate; + uint8_t patrol_read_rate; + uint8_t bgi_rate; + uint8_t cc_rate; + uint8_t recon_rate; + + uint8_t cache_flush_interval; + + uint8_t spinup_drv_count; + uint8_t spinup_delay; + + uint8_t cluster_enable; + uint8_t coercion_mode; + uint8_t disk_write_cache_disable; + uint8_t alarm_enable; + + uint8_t reserved[44]; +}; + +/* + * SAS controller information + */ +struct drsas_ctrl_info { + /* PCI device information */ + struct { + uint16_t vendor_id; + uint16_t device_id; + uint16_t sub_vendor_id; + uint16_t sub_device_id; + uint8_t reserved[24]; + } pci; + + /* Host interface information */ + struct { + uint8_t PCIX : 1; + uint8_t PCIE : 1; + uint8_t iSCSI : 1; + uint8_t SAS_3G : 1; + uint8_t reserved_0 : 4; + uint8_t reserved_1[6]; + uint8_t port_count; + uint64_t port_addr[8]; + } host_interface; + + /* Device (backend) interface information */ + struct { + uint8_t SPI : 1; + uint8_t SAS_3G : 1; + uint8_t SATA_1_5G : 1; + uint8_t SATA_3G : 1; + uint8_t reserved_0 : 4; + uint8_t reserved_1[6]; + uint8_t port_count; + uint64_t port_addr[8]; + } device_interface; + + /* List of components residing in flash. All str are null terminated */ + uint32_t image_check_word; + uint32_t image_component_count; + + struct { + char name[8]; + char version[32]; + char build_date[16]; + char built_time[16]; + } image_component[8]; + + /* + * List of flash components that have been flashed on the card, but + * are not in use, pending reset of the adapter. This list will be + * empty if a flash operation has not occurred. All stings are null + * terminated + */ + uint32_t pending_image_component_count; + + struct { + char name[8]; + char version[32]; + char build_date[16]; + char build_time[16]; + } pending_image_component[8]; + + uint8_t max_arms; + uint8_t max_spans; + uint8_t max_arrays; + uint8_t max_lds; + + char product_name[80]; + char serial_no[32]; + + /* + * Other physical/controller/operation information. Indicates the + * presence of the hardware + */ + struct { + uint32_t bbu : 1; + uint32_t alarm : 1; + uint32_t nvram : 1; + uint32_t uart : 1; + uint32_t reserved : 28; + } hw_present; + + uint32_t current_fw_time; + + /* Maximum data transfer sizes */ + uint16_t max_concurrent_cmds; + uint16_t max_sge_count; + uint32_t max_request_size; + + /* Logical and physical device counts */ + uint16_t ld_present_count; + uint16_t ld_degraded_count; + uint16_t ld_offline_count; + + uint16_t pd_present_count; + uint16_t pd_disk_present_count; + uint16_t pd_disk_pred_failure_count; + uint16_t pd_disk_failed_count; + + /* Memory size information */ + uint16_t nvram_size; + uint16_t memory_size; + uint16_t flash_size; + + /* Error counters */ + uint16_t mem_correctable_error_count; + uint16_t mem_uncorrectable_error_count; + + /* Cluster information */ + uint8_t cluster_permitted; + uint8_t cluster_active; + uint8_t reserved_1[2]; + + /* Controller capabilities structures */ + struct { + uint32_t raid_level_0 : 1; + uint32_t raid_level_1 : 1; + uint32_t raid_level_5 : 1; + uint32_t raid_level_1E : 1; + uint32_t reserved : 28; + } raid_levels; + + struct { + uint32_t rbld_rate : 1; + uint32_t cc_rate : 1; + uint32_t bgi_rate : 1; + uint32_t recon_rate : 1; + uint32_t patrol_rate : 1; + uint32_t alarm_control : 1; + uint32_t cluster_supported : 1; + uint32_t bbu : 1; + uint32_t spanning_allowed : 1; + uint32_t dedicated_hotspares : 1; + uint32_t revertible_hotspares : 1; + uint32_t foreign_config_import : 1; + uint32_t self_diagnostic : 1; + uint32_t reserved : 19; + } adapter_operations; + + struct { + uint32_t read_policy : 1; + uint32_t write_policy : 1; + uint32_t io_policy : 1; + uint32_t access_policy : 1; + uint32_t reserved : 28; + } ld_operations; + + struct { + uint8_t min; + uint8_t max; + uint8_t reserved[2]; + } stripe_size_operations; + + struct { + uint32_t force_online : 1; + uint32_t force_offline : 1; + uint32_t force_rebuild : 1; + uint32_t reserved : 29; + } pd_operations; + + struct { + uint32_t ctrl_supports_sas : 1; + uint32_t ctrl_supports_sata : 1; + uint32_t allow_mix_in_encl : 1; + uint32_t allow_mix_in_ld : 1; + uint32_t allow_sata_in_cluster : 1; + uint32_t reserved : 27; + } pd_mix_support; + + /* Include the controller properties (changeable items) */ + uint8_t reserved_2[12]; + struct drsas_ctrl_prop properties; + + uint8_t pad[0x800 - 0x640]; +}; + +/* + * ================================== + * MegaRAID SAS2.0 driver definitions + * ================================== + */ +#define MRDRV_MAX_NUM_CMD 1024 + +#define MRDRV_MAX_PD_CHANNELS 2 +#define MRDRV_MAX_LD_CHANNELS 2 +#define MRDRV_MAX_CHANNELS (MRDRV_MAX_PD_CHANNELS + \ + MRDRV_MAX_LD_CHANNELS) +#define MRDRV_MAX_DEV_PER_CHANNEL 128 +#define MRDRV_DEFAULT_INIT_ID -1 +#define MRDRV_MAX_CMD_PER_LUN 1000 +#define MRDRV_MAX_LUN 1 +#define MRDRV_MAX_LD 64 + +#define MRDRV_RESET_WAIT_TIME 300 +#define MRDRV_RESET_NOTICE_INTERVAL 5 + +#define DRSAS_IOCTL_CMD 0 + +/* + * FW can accept both 32 and 64 bit SGLs. We want to allocate 32/64 bit + * SGLs based on the size of dma_addr_t + */ +#define IS_DMA64 (sizeof (dma_addr_t) == 8) + +#define IB_MSG_0_OFF 0x10 /* XScale */ +#define OB_MSG_0_OFF 0x18 /* XScale */ +#define IB_DOORBELL_OFF 0x20 /* XScale & ROC */ +#define OB_INTR_STATUS_OFF 0x30 /* XScale & ROC */ +#define OB_INTR_MASK_OFF 0x34 /* XScale & ROC */ +#define IB_QPORT_OFF 0x40 /* XScale & ROC */ +#define OB_DOORBELL_CLEAR_OFF 0xA0 /* ROC */ +#define OB_SCRATCH_PAD_0_OFF 0xB0 /* ROC */ +#define OB_INTR_MASK 0xFFFFFFFF +#define OB_DOORBELL_CLEAR_MASK 0xFFFFFFFF + +/* + * All MFI register set macros accept drsas_register_set* + */ +#define WR_IB_MSG_0(v, instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + IB_MSG_0_OFF), (v)) + +#define RD_OB_MSG_0(instance) ddi_get32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_MSG_0_OFF)) + +#define WR_IB_DOORBELL(v, instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + IB_DOORBELL_OFF), (v)) + +#define RD_IB_DOORBELL(instance) ddi_get32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + IB_DOORBELL_OFF)) + +#define WR_OB_INTR_STATUS(v, instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_INTR_STATUS_OFF), (v)) + +#define RD_OB_INTR_STATUS(instance) ddi_get32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_INTR_STATUS_OFF)) + +#define WR_OB_INTR_MASK(v, instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_INTR_MASK_OFF), (v)) + +#define RD_OB_INTR_MASK(instance) ddi_get32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_INTR_MASK_OFF)) + +#define WR_IB_QPORT(v, instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + IB_QPORT_OFF), (v)) + +#define WR_OB_DOORBELL_CLEAR(v, instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_DOORBELL_CLEAR_OFF), \ + (v)) + +#define RD_OB_SCRATCH_PAD_0(instance) ddi_get32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_SCRATCH_PAD_0_OFF)) + +/* + * When FW is in MFI_STATE_READY or MFI_STATE_OPERATIONAL, the state data + * of Outbound Msg Reg 0 indicates max concurrent cmds supported, max SGEs + * supported per cmd and if 64-bit MFAs (M64) is enabled or disabled. + */ +#define MFI_OB_INTR_STATUS_MASK 0x00000002 + +/* + * This MFI_REPLY_2108_MESSAGE_INTR flag is used also + * in enable_intr_ppc also. Hence bit 2, i.e. 0x4 has + * been set in this flag along with bit 1. + */ +#define MFI_REPLY_2108_MESSAGE_INTR 0x00000001 +#define MFI_REPLY_2108_MESSAGE_INTR_MASK 0x00000005 + +#define MFI_POLL_TIMEOUT_SECS 60 + +#define MFI_ENABLE_INTR(instance) ddi_put32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_INTR_MASK_OFF), 1) +#define MFI_DISABLE_INTR(instance) \ +{ \ + uint32_t disable = 1; \ + uint32_t mask = ddi_get32((instance)->regmap_handle, \ + (uint32_t *)((uintptr_t)(instance)->regmap + OB_INTR_MASK_OFF));\ + mask &= ~disable; \ + ddi_put32((instance)->regmap_handle, (uint32_t *) \ + (uintptr_t)((instance)->regmap + OB_INTR_MASK_OFF), mask); \ +} + +/* By default, the firmware programs for 8 Kbytes of memory */ +#define DEFAULT_MFI_MEM_SZ 8192 +#define MINIMUM_MFI_MEM_SZ 4096 + +/* DCMD Message Frame MAILBOX0-11 */ +#define DCMD_MBOX_SZ 12 + + +struct drsas_register_set { + uint32_t reserved_0[4]; + + uint32_t inbound_msg_0; + uint32_t inbound_msg_1; + uint32_t outbound_msg_0; + uint32_t outbound_msg_1; + + uint32_t inbound_doorbell; + uint32_t inbound_intr_status; + uint32_t inbound_intr_mask; + + uint32_t outbound_doorbell; + uint32_t outbound_intr_status; + uint32_t outbound_intr_mask; + + uint32_t reserved_1[2]; + + uint32_t inbound_queue_port; + uint32_t outbound_queue_port; + + uint32_t reserved_2[22]; + + uint32_t outbound_doorbell_clear; + + uint32_t reserved_3[3]; + + uint32_t outbound_scratch_pad; + + uint32_t reserved_4[3]; + + uint32_t inbound_low_queue_port; + + uint32_t inbound_high_queue_port; + + uint32_t reserved_5; + uint32_t index_registers[820]; +}; + +struct drsas_sge32 { + uint32_t phys_addr; + uint32_t length; +}; + +struct drsas_sge64 { + uint64_t phys_addr; + uint32_t length; +}; + +union drsas_sgl { + struct drsas_sge32 sge32[1]; + struct drsas_sge64 sge64[1]; +}; + +struct drsas_header { + uint8_t cmd; + uint8_t sense_len; + uint8_t cmd_status; + uint8_t scsi_status; + + uint8_t target_id; + uint8_t lun; + uint8_t cdb_len; + uint8_t sge_count; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t timeout; + uint32_t data_xferlen; +}; + +union drsas_sgl_frame { + struct drsas_sge32 sge32[8]; + struct drsas_sge64 sge64[5]; +}; + +struct drsas_init_frame { + uint8_t cmd; + uint8_t reserved_0; + uint8_t cmd_status; + + uint8_t reserved_1; + uint32_t reserved_2; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t reserved_3; + uint32_t data_xfer_len; + + uint32_t queue_info_new_phys_addr_lo; + uint32_t queue_info_new_phys_addr_hi; + uint32_t queue_info_old_phys_addr_lo; + uint32_t queue_info_old_phys_addr_hi; + + uint32_t reserved_4[6]; +}; + +struct drsas_init_queue_info { + uint32_t init_flags; + uint32_t reply_queue_entries; + + uint32_t reply_queue_start_phys_addr_lo; + uint32_t reply_queue_start_phys_addr_hi; + uint32_t producer_index_phys_addr_lo; + uint32_t producer_index_phys_addr_hi; + uint32_t consumer_index_phys_addr_lo; + uint32_t consumer_index_phys_addr_hi; +}; + +struct drsas_io_frame { + uint8_t cmd; + uint8_t sense_len; + uint8_t cmd_status; + uint8_t scsi_status; + + uint8_t target_id; + uint8_t access_byte; + uint8_t reserved_0; + uint8_t sge_count; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t timeout; + uint32_t lba_count; + + uint32_t sense_buf_phys_addr_lo; + uint32_t sense_buf_phys_addr_hi; + + uint32_t start_lba_lo; + uint32_t start_lba_hi; + + union drsas_sgl sgl; +}; + +struct drsas_pthru_frame { + uint8_t cmd; + uint8_t sense_len; + uint8_t cmd_status; + uint8_t scsi_status; + + uint8_t target_id; + uint8_t lun; + uint8_t cdb_len; + uint8_t sge_count; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t timeout; + uint32_t data_xfer_len; + + uint32_t sense_buf_phys_addr_lo; + uint32_t sense_buf_phys_addr_hi; + + uint8_t cdb[16]; + union drsas_sgl sgl; +}; + +struct drsas_dcmd_frame { + uint8_t cmd; + uint8_t reserved_0; + uint8_t cmd_status; + uint8_t reserved_1[4]; + uint8_t sge_count; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t timeout; + + uint32_t data_xfer_len; + uint32_t opcode; + + union { + uint8_t b[DCMD_MBOX_SZ]; + uint16_t s[6]; + uint32_t w[3]; + } mbox; + + union drsas_sgl sgl; +}; + +struct drsas_abort_frame { + uint8_t cmd; + uint8_t reserved_0; + uint8_t cmd_status; + + uint8_t reserved_1; + uint32_t reserved_2; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t reserved_3; + uint32_t reserved_4; + + uint32_t abort_context; + uint32_t pad_1; + + uint32_t abort_mfi_phys_addr_lo; + uint32_t abort_mfi_phys_addr_hi; + + uint32_t reserved_5[6]; +}; + +struct drsas_smp_frame { + uint8_t cmd; + uint8_t reserved_1; + uint8_t cmd_status; + uint8_t connection_status; + + uint8_t reserved_2[3]; + uint8_t sge_count; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t timeout; + + uint32_t data_xfer_len; + + uint64_t sas_addr; + + union drsas_sgl sgl[2]; +}; + +struct drsas_stp_frame { + uint8_t cmd; + uint8_t reserved_1; + uint8_t cmd_status; + uint8_t connection_status; + + uint8_t target_id; + uint8_t reserved_2[2]; + uint8_t sge_count; + + uint32_t context; + uint8_t req_id; + uint8_t msgvector; + uint16_t pad_0; + + uint16_t flags; + uint16_t timeout; + + uint32_t data_xfer_len; + + uint16_t fis[10]; + uint32_t stp_flags; + union drsas_sgl sgl; +}; + +union drsas_frame { + struct drsas_header hdr; + struct drsas_init_frame init; + struct drsas_io_frame io; + struct drsas_pthru_frame pthru; + struct drsas_dcmd_frame dcmd; + struct drsas_abort_frame abort; + struct drsas_smp_frame smp; + struct drsas_stp_frame stp; + + uint8_t raw_bytes[64]; +}; + +typedef struct drsas_pd_address { + uint16_t device_id; + uint16_t encl_id; + + union { + struct { + uint8_t encl_index; + uint8_t slot_number; + } pd_address; + struct { + uint8_t encl_position; + uint8_t encl_connector_index; + } encl_address; + }address; + + uint8_t scsi_dev_type; + + union { + uint8_t port_bitmap; + uint8_t port_numbers; + } connected; + + uint64_t sas_addr[2]; +} drsas_pd_address_t; + +union drsas_evt_class_locale { + struct { + uint16_t locale; + uint8_t reserved; + int8_t class; + } members; + + uint32_t word; +}; + +struct drsas_evt_log_info { + uint32_t newest_seq_num; + uint32_t oldest_seq_num; + uint32_t clear_seq_num; + uint32_t shutdown_seq_num; + uint32_t boot_seq_num; +}; + +struct drsas_progress { + uint16_t progress; + uint16_t elapsed_seconds; +}; + +struct drsas_evtarg_ld { + uint16_t target_id; + uint8_t ld_index; + uint8_t reserved; +}; + +struct drsas_evtarg_pd { + uint16_t device_id; + uint8_t encl_index; + uint8_t slot_number; +}; + +struct drsas_evt_detail { + uint32_t seq_num; + uint32_t time_stamp; + uint32_t code; + union drsas_evt_class_locale cl; + uint8_t arg_type; + uint8_t reserved1[15]; + + union { + struct { + struct drsas_evtarg_pd pd; + uint8_t cdb_length; + uint8_t sense_length; + uint8_t reserved[2]; + uint8_t cdb[16]; + uint8_t sense[64]; + } cdbSense; + + struct drsas_evtarg_ld ld; + + struct { + struct drsas_evtarg_ld ld; + uint64_t count; + } ld_count; + + struct { + uint64_t lba; + struct drsas_evtarg_ld ld; + } ld_lba; + + struct { + struct drsas_evtarg_ld ld; + uint32_t prevOwner; + uint32_t newOwner; + } ld_owner; + + struct { + uint64_t ld_lba; + uint64_t pd_lba; + struct drsas_evtarg_ld ld; + struct drsas_evtarg_pd pd; + } ld_lba_pd_lba; + + struct { + struct drsas_evtarg_ld ld; + struct drsas_progress prog; + } ld_prog; + + struct { + struct drsas_evtarg_ld ld; + uint32_t prev_state; + uint32_t new_state; + } ld_state; + + struct { + uint64_t strip; + struct drsas_evtarg_ld ld; + } ld_strip; + + struct drsas_evtarg_pd pd; + + struct { + struct drsas_evtarg_pd pd; + uint32_t err; + } pd_err; + + struct { + uint64_t lba; + struct drsas_evtarg_pd pd; + } pd_lba; + + struct { + uint64_t lba; + struct drsas_evtarg_pd pd; + struct drsas_evtarg_ld ld; + } pd_lba_ld; + + struct { + struct drsas_evtarg_pd pd; + struct drsas_progress prog; + } pd_prog; + + struct { + struct drsas_evtarg_pd pd; + uint32_t prevState; + uint32_t newState; + } pd_state; + + struct { + uint16_t vendorId; + uint16_t deviceId; + uint16_t subVendorId; + uint16_t subDeviceId; + } pci; + + uint32_t rate; + char str[96]; + + struct { + uint32_t rtc; + uint32_t elapsedSeconds; + } time; + + struct { + uint32_t ecar; + uint32_t elog; + char str[64]; + } ecc; + + drsas_pd_address_t pd_addr; + + uint8_t b[96]; + uint16_t s[48]; + uint32_t w[24]; + uint64_t d[12]; + } args; + + char description[128]; + +}; + +/* only 63 are usable by the application */ +#define MAX_LOGICAL_DRIVES 64 +/* only 255 physical devices may be used */ +#define MAX_PHYSICAL_DEVICES 256 +#define MAX_PD_PER_ENCLOSURE 64 +/* maximum disks per array */ +#define MAX_ROW_SIZE 32 +/* maximum spans per logical drive */ +#define MAX_SPAN_DEPTH 8 +/* maximum number of arrays a hot spare may be dedicated to */ +#define MAX_ARRAYS_DEDICATED 16 +/* maximum number of arrays which may exist */ +#define MAX_ARRAYS 128 +/* maximum number of foreign configs that may ha managed at once */ +#define MAX_FOREIGN_CONFIGS 8 +/* maximum spares (global and dedicated combined) */ +#define MAX_SPARES_FOR_THE_CONTROLLER MAX_PHYSICAL_DEVICES +/* maximum possible Target IDs (i.e. 0 to 63) */ +#define MAX_TARGET_ID 63 +/* maximum number of supported enclosures */ +#define MAX_ENCLOSURES 32 +/* maximum number of PHYs per controller */ +#define MAX_PHYS_PER_CONTROLLER 16 +/* maximum number of LDs per array (due to DDF limitations) */ +#define MAX_LDS_PER_ARRAY 16 + +/* + * ----------------------------------------------------------------------------- + * ----------------------------------------------------------------------------- + * + * Logical Drive commands + * + * ----------------------------------------------------------------------------- + * ----------------------------------------------------------------------------- + */ +#define DR_DCMD_LD 0x03000000, /* Logical Device (LD) opcodes */ + +/* + * Input: dcmd.opcode - DR_DCMD_LD_GET_LIST + * dcmd.mbox - reserved + * dcmd.sge IN - ptr to returned DR_LD_LIST structure + * Desc: Return the logical drive list structure + * Status: No error + */ + +/* + * defines the logical drive reference structure + */ +typedef union _DR_LD_REF { /* LD reference structure */ + struct { + uint8_t targetId; /* LD target id (0 to MAX_TARGET_ID) */ + uint8_t reserved; /* reserved for in line with DR_PD_REF */ + uint16_t seqNum; /* Sequence Number */ + } ld_ref; + uint32_t ref; /* shorthand reference to full 32-bits */ +} DR_LD_REF; /* 4 bytes */ + +/* + * defines the logical drive list structure + */ +typedef struct _DR_LD_LIST { + uint32_t ldCount; /* number of LDs */ + uint32_t reserved; /* pad to 8-byte boundary */ + struct { + DR_LD_REF ref; /* LD reference */ + uint8_t state; /* current LD state (DR_LD_STATE) */ + uint8_t reserved[3]; /* pad to 8-byte boundary */ + uint64_t size; /* LD size */ + } ldList[MAX_LOGICAL_DRIVES]; +} DR_LD_LIST; + +struct drsas_drv_ver { + uint8_t signature[12]; + uint8_t os_name[16]; + uint8_t os_ver[12]; + uint8_t drv_name[20]; + uint8_t drv_ver[32]; + uint8_t drv_rel_date[20]; +}; + +#define PCI_TYPE0_ADDRESSES 6 +#define PCI_TYPE1_ADDRESSES 2 +#define PCI_TYPE2_ADDRESSES 5 + +struct drsas_pci_common_header { + uint16_t vendorID; /* (ro) */ + uint16_t deviceID; /* (ro) */ + uint16_t command; /* Device control */ + uint16_t status; + uint8_t revisionID; /* (ro) */ + uint8_t progIf; /* (ro) */ + uint8_t subClass; /* (ro) */ + uint8_t baseClass; /* (ro) */ + uint8_t cacheLineSize; /* (ro+) */ + uint8_t latencyTimer; /* (ro+) */ + uint8_t headerType; /* (ro) */ + uint8_t bist; /* Built in self test */ + + union { + struct { + uint32_t baseAddresses[PCI_TYPE0_ADDRESSES]; + uint32_t cis; + uint16_t subVendorID; + uint16_t subSystemID; + uint32_t romBaseAddress; + uint8_t capabilitiesPtr; + uint8_t reserved1[3]; + uint32_t reserved2; + uint8_t interruptLine; + uint8_t interruptPin; /* (ro) */ + uint8_t minimumGrant; /* (ro) */ + uint8_t maximumLatency; /* (ro) */ + } type_0; + + struct { + uint32_t baseAddresses[PCI_TYPE1_ADDRESSES]; + uint8_t primaryBus; + uint8_t secondaryBus; + uint8_t subordinateBus; + uint8_t secondaryLatency; + uint8_t ioBase; + uint8_t ioLimit; + uint16_t secondaryStatus; + uint16_t memoryBase; + uint16_t memoryLimit; + uint16_t prefetchBase; + uint16_t prefetchLimit; + uint32_t prefetchBaseUpper32; + uint32_t prefetchLimitUpper32; + uint16_t ioBaseUpper16; + uint16_t ioLimitUpper16; + uint8_t capabilitiesPtr; + uint8_t reserved1[3]; + uint32_t romBaseAddress; + uint8_t interruptLine; + uint8_t interruptPin; + uint16_t bridgeControl; + } type_1; + + struct { + uint32_t socketRegistersBaseAddress; + uint8_t capabilitiesPtr; + uint8_t reserved; + uint16_t secondaryStatus; + uint8_t primaryBus; + uint8_t secondaryBus; + uint8_t subordinateBus; + uint8_t secondaryLatency; + struct { + uint32_t base; + uint32_t limit; + } range[PCI_TYPE2_ADDRESSES-1]; + uint8_t interruptLine; + uint8_t interruptPin; + uint16_t bridgeControl; + } type_2; + } header; +}; + +struct drsas_pci_link_capability { + union { + struct { + uint32_t linkSpeed :4; + uint32_t linkWidth :6; + uint32_t aspmSupport :2; + uint32_t losExitLatency :3; + uint32_t l1ExitLatency :3; + uint32_t rsvdp :6; + uint32_t portNumber :8; + } bits; + + uint32_t asUlong; + } cap; + +}; + +struct drsas_pci_link_status_capability { + union { + struct { + uint16_t linkSpeed :4; + uint16_t negotiatedLinkWidth :6; + uint16_t linkTrainingError :1; + uint16_t linkTraning :1; + uint16_t slotClockConfig :1; + uint16_t rsvdZ :3; + } bits; + + uint16_t asUshort; + } stat_cap; + + uint16_t reserved; + +}; + +struct drsas_pci_capabilities { + struct drsas_pci_link_capability linkCapability; + struct drsas_pci_link_status_capability linkStatusCapability; +}; + +struct drsas_pci_information +{ + uint32_t busNumber; + uint8_t deviceNumber; + uint8_t functionNumber; + uint8_t interruptVector; + uint8_t reserved; + struct drsas_pci_common_header pciHeaderInfo; + struct drsas_pci_capabilities capability; + uint8_t reserved2[32]; +}; + +struct drsas_ioctl { + uint16_t version; + uint16_t controller_id; + uint8_t signature[8]; + uint32_t reserved_1; + uint32_t control_code; + uint32_t reserved_2[2]; + uint8_t frame[64]; + union drsas_sgl_frame sgl_frame; + uint8_t sense_buff[DRSAS_MAX_SENSE_LENGTH]; + uint8_t data[1]; +}; + +struct drsas_aen { + uint16_t host_no; + uint16_t cmd_status; + uint32_t seq_num; + uint32_t class_locale_word; +}; +#pragma pack() + +#ifndef DDI_VENDOR_LSI +#define DDI_VENDOR_LSI "LSI" +#endif /* DDI_VENDOR_LSI */ + +static int drsas_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); +static int drsas_attach(dev_info_t *, ddi_attach_cmd_t); +static int drsas_reset(dev_info_t *, ddi_reset_cmd_t); +static int drsas_detach(dev_info_t *, ddi_detach_cmd_t); +static int drsas_open(dev_t *, int, int, cred_t *); +static int drsas_close(dev_t, int, int, cred_t *); +static int drsas_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); + +static int drsas_tran_tgt_init(dev_info_t *, dev_info_t *, + scsi_hba_tran_t *, struct scsi_device *); +static struct scsi_pkt *drsas_tran_init_pkt(struct scsi_address *, register + struct scsi_pkt *, struct buf *, int, int, int, int, + int (*)(), caddr_t); +static int drsas_tran_start(struct scsi_address *, + register struct scsi_pkt *); +static int drsas_tran_abort(struct scsi_address *, struct scsi_pkt *); +static int drsas_tran_reset(struct scsi_address *, int); +static int drsas_tran_getcap(struct scsi_address *, char *, int); +static int drsas_tran_setcap(struct scsi_address *, char *, int, int); +static void drsas_tran_destroy_pkt(struct scsi_address *, + struct scsi_pkt *); +static void drsas_tran_dmafree(struct scsi_address *, struct scsi_pkt *); +static void drsas_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); +static uint_t drsas_isr(); +static uint_t drsas_softintr(); + +static int init_mfi(struct drsas_instance *); +static int drsas_free_dma_obj(struct drsas_instance *, dma_obj_t); +static int drsas_alloc_dma_obj(struct drsas_instance *, dma_obj_t *, + uchar_t); +static struct drsas_cmd *get_mfi_pkt(struct drsas_instance *); +static void return_mfi_pkt(struct drsas_instance *, + struct drsas_cmd *); + +static void free_space_for_mfi(struct drsas_instance *); +static void free_additional_dma_buffer(struct drsas_instance *); +static int alloc_additional_dma_buffer(struct drsas_instance *); +static int read_fw_status_reg_ppc(struct drsas_instance *); +static void issue_cmd_ppc(struct drsas_cmd *, struct drsas_instance *); +static int issue_cmd_in_poll_mode_ppc(struct drsas_instance *, + struct drsas_cmd *); +static int issue_cmd_in_sync_mode_ppc(struct drsas_instance *, + struct drsas_cmd *); +static void enable_intr_ppc(struct drsas_instance *); +static void disable_intr_ppc(struct drsas_instance *); +static int intr_ack_ppc(struct drsas_instance *); +static int mfi_state_transition_to_ready(struct drsas_instance *); +static void destroy_mfi_frame_pool(struct drsas_instance *); +static int create_mfi_frame_pool(struct drsas_instance *); +static int drsas_dma_alloc(struct drsas_instance *, struct scsi_pkt *, + struct buf *, int, int (*)()); +static int drsas_dma_move(struct drsas_instance *, + struct scsi_pkt *, struct buf *); +static void flush_cache(struct drsas_instance *instance); +static void display_scsi_inquiry(caddr_t); +static int start_mfi_aen(struct drsas_instance *instance); +static int handle_drv_ioctl(struct drsas_instance *instance, + struct drsas_ioctl *ioctl, int mode); +static int handle_mfi_ioctl(struct drsas_instance *instance, + struct drsas_ioctl *ioctl, int mode); +static int handle_mfi_aen(struct drsas_instance *instance, + struct drsas_aen *aen); +static void fill_up_drv_ver(struct drsas_drv_ver *dv); +static struct drsas_cmd *build_cmd(struct drsas_instance *instance, + struct scsi_address *ap, struct scsi_pkt *pkt, + uchar_t *cmd_done); +static int register_mfi_aen(struct drsas_instance *instance, + uint32_t seq_num, uint32_t class_locale_word); +static int issue_mfi_pthru(struct drsas_instance *instance, struct + drsas_ioctl *ioctl, struct drsas_cmd *cmd, int mode); +static int issue_mfi_dcmd(struct drsas_instance *instance, struct + drsas_ioctl *ioctl, struct drsas_cmd *cmd, int mode); +static int issue_mfi_smp(struct drsas_instance *instance, struct + drsas_ioctl *ioctl, struct drsas_cmd *cmd, int mode); +static int issue_mfi_stp(struct drsas_instance *instance, struct + drsas_ioctl *ioctl, struct drsas_cmd *cmd, int mode); +static int abort_aen_cmd(struct drsas_instance *instance, + struct drsas_cmd *cmd_to_abort); + +static int drsas_common_check(struct drsas_instance *instance, + struct drsas_cmd *cmd); +static void drsas_fm_init(struct drsas_instance *instance); +static void drsas_fm_fini(struct drsas_instance *instance); +static int drsas_fm_error_cb(dev_info_t *, ddi_fm_error_t *, + const void *); +static void drsas_fm_ereport(struct drsas_instance *instance, + char *detail); +static int drsas_check_dma_handle(ddi_dma_handle_t handle); +static int drsas_check_acc_handle(ddi_acc_handle_t handle); + +static void drsas_rem_intrs(struct drsas_instance *instance); +static int drsas_add_intrs(struct drsas_instance *instance, int intr_type); + +static void drsas_tran_tgt_free(dev_info_t *, dev_info_t *, + scsi_hba_tran_t *, struct scsi_device *); +static int drsas_tran_bus_config(dev_info_t *, uint_t, + ddi_bus_config_op_t, void *, dev_info_t **); +static int drsas_parse_devname(char *, int *, int *); +static int drsas_config_all_devices(struct drsas_instance *); +static int drsas_config_scsi_device(struct drsas_instance *, + struct scsi_device *, dev_info_t **); +static int drsas_config_ld(struct drsas_instance *, uint16_t, + uint8_t, dev_info_t **); +static dev_info_t *drsas_find_child(struct drsas_instance *, uint16_t, + uint8_t); +static int drsas_name_node(dev_info_t *, char *, int); +static void drsas_issue_evt_taskq(struct drsas_eventinfo *); +static int drsas_service_evt(struct drsas_instance *, int, int, int, + uint64_t); +static int drsas_mode_sense_build(struct scsi_pkt *); + +#ifdef __cplusplus +} +#endif + +#endif /* _DR_SAS_H_ */ diff --git a/usr/src/uts/common/io/dr_sas/dr_sas_list.h b/usr/src/uts/common/io/dr_sas/dr_sas_list.h new file mode 100644 index 0000000000..4154a77796 --- /dev/null +++ b/usr/src/uts/common/io/dr_sas/dr_sas_list.h @@ -0,0 +1,212 @@ +/* + * dr_sas_list.h: header for dr_sas + * + * Solaris MegaRAID driver for SAS2.0 controllers + * Copyright (c) 2008-2009, LSI Logic Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _DR_SAS_LIST_H_ +#define _DR_SAS_LIST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct mlist_head { + struct mlist_head *next, *prev; +}; + +typedef struct mlist_head mlist_t; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct mlist_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} + + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static void __list_add(struct mlist_head *new, + struct mlist_head *prev, + struct mlist_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + + +/* + * mlist_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static void mlist_add(struct mlist_head *new, struct mlist_head *head) +{ + __list_add(new, head, head->next); +} + + +/* + * mlist_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static void mlist_add_tail(struct mlist_head *new, struct mlist_head *head) +{ + __list_add(new, head->prev, head); +} + + + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static void __list_del(struct mlist_head *prev, + struct mlist_head *next) +{ + next->prev = prev; + prev->next = next; +} + + +/* + * mlist_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static void mlist_del_init(struct mlist_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + + +/* + * mlist_empty - tests whether a list is empty + * @head: the list to test. + */ +static int mlist_empty(struct mlist_head *head) +{ + return (head->next == head); +} + + +/* + * mlist_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static void mlist_splice(struct mlist_head *list, struct mlist_head *head) +{ + struct mlist_head *first = list->next; + + if (first != list) { + struct mlist_head *last = list->prev; + struct mlist_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + + +/* + * mlist_entry - get the struct for this entry + * @ptr: the &struct mlist_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define mlist_entry(ptr, type, member) \ + ((type *)((size_t)(ptr) - offsetof(type, member))) + + +/* + * mlist_for_each - iterate over a list + * @pos: the &struct mlist_head to use as a loop counter. + * @head: the head for your list. + */ +#define mlist_for_each(pos, head) \ + for (pos = (head)->next, prefetch(pos->next); pos != (head); \ + pos = pos->next, prefetch(pos->next)) + + +/* + * mlist_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct mlist_head to use as a loop counter. + * @n: another &struct mlist_head to use as temporary storage + * @head: the head for your list. + */ +#define mlist_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#ifdef __cplusplus +} +#endif + +#endif /* _DR_SAS_LIST_H_ */ diff --git a/usr/src/uts/common/io/eventfd.c b/usr/src/uts/common/io/eventfd.c new file mode 100644 index 0000000000..e452154bf0 --- /dev/null +++ b/usr/src/uts/common/io/eventfd.c @@ -0,0 +1,409 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright (c) 2014 Joyent, Inc. All rights reserved. + */ + +/* + * Support for the eventfd facility, a Linux-borne facility for user-generated + * file descriptor-based events. + */ + +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/eventfd.h> +#include <sys/conf.h> +#include <sys/vmem.h> +#include <sys/sysmacros.h> +#include <sys/filio.h> +#include <sys/stat.h> +#include <sys/file.h> + +struct eventfd_state; +typedef struct eventfd_state eventfd_state_t; + +struct eventfd_state { + kmutex_t efd_lock; /* lock protecting state */ + boolean_t efd_semaphore; /* boolean: sema. semantics */ + kcondvar_t efd_cv; /* condvar */ + pollhead_t efd_pollhd; /* poll head */ + uint64_t efd_value; /* value */ + eventfd_state_t *efd_next; /* next state on global list */ +}; + +/* + * Internal global variables. + */ +static kmutex_t eventfd_lock; /* lock protecting state */ +static dev_info_t *eventfd_devi; /* device info */ +static vmem_t *eventfd_minor; /* minor number arena */ +static void *eventfd_softstate; /* softstate pointer */ +static eventfd_state_t *eventfd_state; /* global list of state */ + +/*ARGSUSED*/ +static int +eventfd_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) +{ + eventfd_state_t *state; + major_t major = getemajor(*devp); + minor_t minor = getminor(*devp); + + if (minor != EVENTFDMNRN_INOTIFY) + return (ENXIO); + + mutex_enter(&eventfd_lock); + + minor = (minor_t)(uintptr_t)vmem_alloc(eventfd_minor, 1, + VM_BESTFIT | VM_SLEEP); + + if (ddi_soft_state_zalloc(eventfd_softstate, minor) != DDI_SUCCESS) { + vmem_free(eventfd_minor, (void *)(uintptr_t)minor, 1); + mutex_exit(&eventfd_lock); + return (NULL); + } + + state = ddi_get_soft_state(eventfd_softstate, minor); + *devp = makedevice(major, minor); + + state->efd_next = eventfd_state; + eventfd_state = state; + + mutex_exit(&eventfd_lock); + + return (0); +} + +/*ARGSUSED*/ +static int +eventfd_read(dev_t dev, uio_t *uio, cred_t *cr) +{ + eventfd_state_t *state; + minor_t minor = getminor(dev); + uint64_t val, oval; + int err; + + if (uio->uio_resid < sizeof (val)) + return (EINVAL); + + state = ddi_get_soft_state(eventfd_softstate, minor); + + mutex_enter(&state->efd_lock); + + while (state->efd_value == 0) { + if (uio->uio_fmode & (FNDELAY|FNONBLOCK)) { + mutex_exit(&state->efd_lock); + return (EAGAIN); + } + + if (!cv_wait_sig_swap(&state->efd_cv, &state->efd_lock)) { + mutex_exit(&state->efd_lock); + return (EINTR); + } + } + + /* + * We have a non-zero value and we own the lock; our behavior now + * depends on whether or not EFD_SEMAPHORE was set when the eventfd + * was created. + */ + val = oval = state->efd_value; + + if (state->efd_semaphore) { + state->efd_value--; + val = 1; + } else { + state->efd_value = 0; + } + + err = uiomove(&val, sizeof (val), UIO_READ, uio); + + mutex_exit(&state->efd_lock); + + if (oval == EVENTFD_VALMAX) { + cv_broadcast(&state->efd_cv); + pollwakeup(&state->efd_pollhd, POLLWRNORM | POLLOUT); + } + + return (err); +} + +/*ARGSUSED*/ +static int +eventfd_write(dev_t dev, struct uio *uio, cred_t *credp) +{ + eventfd_state_t *state; + minor_t minor = getminor(dev); + uint64_t val, oval; + int err; + + if (uio->uio_resid < sizeof (val)) + return (EINVAL); + + if ((err = uiomove(&val, sizeof (val), UIO_WRITE, uio)) != 0) + return (err); + + if (val > EVENTFD_VALMAX) + return (EINVAL); + + state = ddi_get_soft_state(eventfd_softstate, minor); + + mutex_enter(&state->efd_lock); + + while (val > EVENTFD_VALMAX - state->efd_value) { + if (uio->uio_fmode & (FNDELAY|FNONBLOCK)) { + mutex_exit(&state->efd_lock); + return (EAGAIN); + } + + if (!cv_wait_sig_swap(&state->efd_cv, &state->efd_lock)) { + mutex_exit(&state->efd_lock); + return (EINTR); + } + } + + /* + * We now know that we can add the value without overflowing. + */ + state->efd_value = (oval = state->efd_value) + val; + + mutex_exit(&state->efd_lock); + + if (oval == 0) { + cv_broadcast(&state->efd_cv); + pollwakeup(&state->efd_pollhd, POLLRDNORM | POLLIN); + } + + return (0); +} + +/*ARGSUSED*/ +static int +eventfd_poll(dev_t dev, short events, int anyyet, short *reventsp, + struct pollhead **phpp) +{ + eventfd_state_t *state; + minor_t minor = getminor(dev); + short revents = 0; + + state = ddi_get_soft_state(eventfd_softstate, minor); + + mutex_enter(&state->efd_lock); + + if (state->efd_value > 0) + revents |= POLLRDNORM | POLLIN; + + if (state->efd_value < EVENTFD_VALMAX) + revents |= POLLWRNORM | POLLOUT; + + if (!(*reventsp = revents & events) && !anyyet) + *phpp = &state->efd_pollhd; + + mutex_exit(&state->efd_lock); + + return (0); +} + +/*ARGSUSED*/ +static int +eventfd_ioctl(dev_t dev, int cmd, intptr_t arg, int md, cred_t *cr, int *rv) +{ + eventfd_state_t *state; + minor_t minor = getminor(dev); + + state = ddi_get_soft_state(eventfd_softstate, minor); + + switch (cmd) { + case EVENTFDIOC_SEMAPHORE: { + mutex_enter(&state->efd_lock); + state->efd_semaphore ^= 1; + mutex_exit(&state->efd_lock); + + return (0); + } + + default: + break; + } + + return (ENOTTY); +} + +/*ARGSUSED*/ +static int +eventfd_close(dev_t dev, int flag, int otyp, cred_t *cred_p) +{ + eventfd_state_t *state, **sp; + minor_t minor = getminor(dev); + + state = ddi_get_soft_state(eventfd_softstate, minor); + + mutex_enter(&eventfd_lock); + + /* + * Remove our state from our global list. + */ + for (sp = &eventfd_state; *sp != state; sp = &((*sp)->efd_next)) + VERIFY(*sp != NULL); + + *sp = (*sp)->efd_next; + + ddi_soft_state_free(eventfd_softstate, minor); + vmem_free(eventfd_minor, (void *)(uintptr_t)minor, 1); + + mutex_exit(&eventfd_lock); + + return (0); +} + +/*ARGSUSED*/ +static int +eventfd_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) +{ + mutex_enter(&eventfd_lock); + + if (ddi_soft_state_init(&eventfd_softstate, + sizeof (eventfd_state_t), 0) != 0) { + cmn_err(CE_NOTE, "/dev/eventfd failed to create soft state"); + mutex_exit(&eventfd_lock); + return (DDI_FAILURE); + } + + if (ddi_create_minor_node(devi, "eventfd", S_IFCHR, + EVENTFDMNRN_INOTIFY, DDI_PSEUDO, NULL) == DDI_FAILURE) { + cmn_err(CE_NOTE, "/dev/eventfd couldn't create minor node"); + ddi_soft_state_fini(&eventfd_softstate); + mutex_exit(&eventfd_lock); + return (DDI_FAILURE); + } + + ddi_report_dev(devi); + eventfd_devi = devi; + + eventfd_minor = vmem_create("eventfd_minor", (void *)EVENTFDMNRN_CLONE, + UINT32_MAX - EVENTFDMNRN_CLONE, 1, NULL, NULL, NULL, 0, + VM_SLEEP | VMC_IDENTIFIER); + + mutex_exit(&eventfd_lock); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +eventfd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + switch (cmd) { + case DDI_DETACH: + break; + + case DDI_SUSPEND: + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } + + mutex_enter(&eventfd_lock); + vmem_destroy(eventfd_minor); + + ddi_remove_minor_node(eventfd_devi, NULL); + eventfd_devi = NULL; + + ddi_soft_state_fini(&eventfd_softstate); + mutex_exit(&eventfd_lock); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +eventfd_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) +{ + int error; + + switch (infocmd) { + case DDI_INFO_DEVT2DEVINFO: + *result = (void *)eventfd_devi; + error = DDI_SUCCESS; + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)0; + error = DDI_SUCCESS; + break; + default: + error = DDI_FAILURE; + } + return (error); +} + +static struct cb_ops eventfd_cb_ops = { + eventfd_open, /* open */ + eventfd_close, /* close */ + nulldev, /* strategy */ + nulldev, /* print */ + nodev, /* dump */ + eventfd_read, /* read */ + eventfd_write, /* write */ + eventfd_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + eventfd_poll, /* poll */ + ddi_prop_op, /* cb_prop_op */ + 0, /* streamtab */ + D_NEW | D_MP /* Driver compatibility flag */ +}; + +static struct dev_ops eventfd_ops = { + DEVO_REV, /* devo_rev */ + 0, /* refcnt */ + eventfd_info, /* get_dev_info */ + nulldev, /* identify */ + nulldev, /* probe */ + eventfd_attach, /* attach */ + eventfd_detach, /* detach */ + nodev, /* reset */ + &eventfd_cb_ops, /* driver operations */ + NULL, /* bus operations */ + nodev, /* dev power */ + ddi_quiesce_not_needed, /* quiesce */ +}; + +static struct modldrv modldrv = { + &mod_driverops, /* module type (this is a pseudo driver) */ + "eventfd support", /* name of module */ + &eventfd_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modldrv, + NULL +}; + +int +_init(void) +{ + return (mod_install(&modlinkage)); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +int +_fini(void) +{ + return (mod_remove(&modlinkage)); +} diff --git a/usr/src/uts/common/io/eventfd.conf b/usr/src/uts/common/io/eventfd.conf new file mode 100644 index 0000000000..f9c6dc11b2 --- /dev/null +++ b/usr/src/uts/common/io/eventfd.conf @@ -0,0 +1,16 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2014 Joyent, Inc. All rights reserved. +# + +name="eventfd" parent="pseudo" instance=0; diff --git a/usr/src/uts/common/io/fibre-channel/fca/oce/oce_rx.c b/usr/src/uts/common/io/fibre-channel/fca/oce/oce_rx.c index c8af6ae527..2930fe578c 100644 --- a/usr/src/uts/common/io/fibre-channel/fca/oce/oce_rx.c +++ b/usr/src/uts/common/io/fibre-channel/fca/oce/oce_rx.c @@ -532,8 +532,7 @@ oce_drain_rq_cq(void *arg) if (dev->function_mode & FLEX10_MODE) { if (cqe->u0.s.vlan_tag_present && cqe->u0.s.qnq) { - oce_rx_insert_tag(mp, - cqe->u0.s.vlan_tag); + oce_rx_insert_tag(mp, cqe->u0.s.vlan_tag); } } else if (cqe->u0.s.vlan_tag_present) { oce_rx_insert_tag(mp, cqe->u0.s.vlan_tag); diff --git a/usr/src/uts/common/io/gsqueue/gsqueue.c b/usr/src/uts/common/io/gsqueue/gsqueue.c new file mode 100644 index 0000000000..b484b16142 --- /dev/null +++ b/usr/src/uts/common/io/gsqueue/gsqueue.c @@ -0,0 +1,612 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright (c) 2014 Joyent, Inc. All rights reserved. + */ + +/* + * Serialization queues are a technique used in illumos to provide what's + * commonly known as a 'vertical' perimeter. The idea (described a bit in + * uts/common/inet/squeue.c) is to provide a means to make sure that message + * blocks (mblk_t) are processed in a specific order. Subsystems like ip and vnd + * consume these on different policies, ip on a conn_t basis, vnd on a per + * device basis, and use this to ensure that only one packet is being processed + * at a given time. + * + * Serialization queues were originally used by ip. As part of that + * implementation, many of the details of ip were baked into it. That includes + * things like conn_t, ip receive attributes, and the notion of sets. While an + * individual serialization queue, or gsqueue_t, is a useful level of + * abstraction, it isn't the basis on which monst consumers want to manage them. + * Instead, we have the notion of a set of serialization queues. These sets are + * DR (CPU Dynamic reconfiguration) aware, and allow consumers to have a + * gsqueue_t per CPU to fanout on without managing them all itself. In the + * original implementation, this existed, but they were heavily tied into the + * infrastructure of IP, and its notion of polling on the underlying MAC + * devices. + * + * The result of that past is a new interface to serialization queues and a + * similar, but slightly different, abstraction to sets of these + * (gsqueue_set_t). When designing this there are two different approaches that + * one could consider. The first is that the system has one gsqueue_set_t that + * the entire world shares, whether IP or some other consumer. The other is that + * every consumer has their own set. + * + * The trade offs between these two failure modes are the pathological failure + * modes. There is no guarantee that any two consumers here are equivalent. In + * fact, they very likely have very different latency profiles. If they are + * being processed in the same queue, that can lead to very odd behaviors. More + * generally, if we have a series of processing functions from one consumer + * which are generally short, and another which are generally long, that'll + * cause undue latency that's harder to observe. If we instead take the approach + * that each consumer should have its own set that it fans out over then we + * won't end up with the problem that a given serialization queue will have + * multiple latency profiles, but instead we'll see cpu contention for the bound + * gsqueue_t worker thread. Keep in mind though, that only the gsqueue_t worker + * thread is bound and it is in fact possible for it to be processed by other + * threads on other CPUs. + * + * We've opted to go down the second path, so each consumer has its own + * independent set of serialization queues that it is bound over. + * + * Structure Hierarchies + * --------------------- + * + * At the top level, we have a single list of gsqueue_set_t. The gsqueue_set_t + * encapsulates all the per-CPU gsqueue_t that exist in the form of + * gsqueue_cpu_t. The gsqueue_cpu_t has been designed such that it could + * accommodate more than one gsqueue_t, but today there is a one to one mapping. + * + * We maintain two different lists of gsqueue_cpu_t, the active and defunct + * sets. The active set is maintained in the array `gs_cpus`. There are NCPU + * entries available in `gs_cpus` with the total number of currently active cpus + * described in `gs_ncpus`. The ordering of `gs_cpus` is unimportant. When + * there is no longer a need for a given binding (see the following section for + * more explanation on when this is the case) then we move the entry to the + * `gs_defunct` list which is just a singly linked list of gsqueue_cpu_t. + * + * In addition, each gsqueue_set_t can have a series of callbacks registered + * with it. These are described in the following section. Graphically, a given + * gsqueue_set_t looks roughly like the following: + * + * +---------------+ + * | gsqueue_set_t | + * +---------------+ + * | | | + * | | * . . . gs_cpus + * | | | + * | | | +-------------------------------------------------+ + * | | +----->| gsqueue_cpu_t || gsqueue_cpu_t || gsqueue_cpu_t |... + * | | +-------------------------------------------------+ + * | | + * | * . . . gs_defunct + * | | + * | | +---------------+ +---------------+ +---------------+ + * | +--->| gsqueue_cpu_t |-->| gsqueue_cpu_t |-->| gsqueue_cpu_t |... + * | +---------------+ +---------------+ +---------------+ + * * . . . gs_cbs + * | + * | +--------------+ +--------------+ +--------------+ + * +--->| gsqueue_cb_t |-->| gsqueue_cb_t |->| gsqueue_cb_t |... + * +--------------+ +--------------+ +--------------+ + * + * CPU DR, gsqueue_t, and gsqueue_t + * -------------------------------- + * + * Recall, that every serialization queue (gsqueue_t or squeue_t) has a worker + * thread that may end up doing work. As part of supporting fanout, we have one + * gsqueue_t per CPU, and its worker thread is bound to that CPU. Because of + * this binding, we need to deal with CPU DR changes. + * + * The gsqueue driver maintains a single CPU DR callback that is used for the + * entire sub-system. We break down CPU DR events into three groups. Offline + * events, online events, and events we can ignore. When the first group occurs, + * we need to go through every gsqueue_t, find the gsqueue_cpu_t that + * corresponds to that processor id, and unbind all of its gsqueue_t's. It's + * rather important that we only unbind the gsqueue_t's and not actually destroy + * them. When this happens, they could very easily have data queued inside of + * them and it's unreasonable to just throw out everything in them at this + * point. The data remains intact and service continues uinterrupted. + * + * When we receive an online event, we do the opposite. We try to find a + * gsqueue_cpu_t that previously was bound to this CPU (by leaving its gqc_cpuid + * field intact) in the defunct list. If we find one, we remove it from the + * defunct list and add it to the active list as well as binding the gsqueue_t + * to the CPU in question. If we don't find one, then we create a new one. + * + * To deal with these kinds of situations, we allow a consumer to register + * callbacks for the gsqueue_t that they are interested in. These callbacks will + * fire whenever we are handling a topology change. The design of the callbacks + * is not that the user can take any administrative action during them, but + * rather set something for them to do asynchronously. It is illegal to make any + * calls into the gsqueue system while you are in a callback. + * + * Locking + * ------- + * + * The lock ordering here is fairly straightforward. Due to our use of CPU + * binding and the CPU DR callbacks, we have an additional lock to consider + * cpu_lock. Because of that, the following are the rules for locking: + * + * + * o If performing binding operations, you must grab cpu_lock. cpu_lock is + * also at the top of the order. + * + * o cpu_lock > gsqueue_lock > gsqueue_t`gs_lock > squeue_t`sq_lock + * If you need to take multiple locks, you must take the greatest + * (left-most) one first. + */ + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/stat.h> +#include <sys/kmem.h> +#include <sys/stream.h> +#include <sys/modctl.h> +#include <sys/cpuvar.h> +#include <sys/list.h> +#include <sys/sysmacros.h> + +#include <sys/gsqueue.h> +#include <sys/squeue_impl.h> + +typedef struct gsqueue_cb { + struct gsqueue_cb *gcb_next; + gsqueue_cb_f gcb_func; + void *gcb_arg; +} gsqueue_cb_t; + +typedef struct gsqueue_cpu { + struct gsqueue_cpu *gqc_next; + squeue_t *gqc_head; + processorid_t gqc_cpuid; +} gsqueue_cpu_t; + +struct gsqueue_set { + list_node_t gs_next; + uint_t gs_wwait; + pri_t gs_wpri; + kmutex_t gs_lock; + int gs_ncpus; + gsqueue_cpu_t **gs_cpus; + gsqueue_cpu_t *gs_defunct; + gsqueue_cb_t *gs_cbs; +}; + +static kmutex_t gsqueue_lock; +static list_t gsqueue_list; +static kmem_cache_t *gsqueue_cb_cache; +static kmem_cache_t *gsqueue_cpu_cache; +static kmem_cache_t *gsqueue_set_cache; + +static gsqueue_cpu_t * +gsqueue_cpu_create(uint_t wwait, pri_t wpri, processorid_t cpuid) +{ + gsqueue_cpu_t *scp; + + scp = kmem_cache_alloc(gsqueue_cpu_cache, KM_SLEEP); + + scp->gqc_next = NULL; + scp->gqc_cpuid = cpuid; + scp->gqc_head = squeue_create(wwait, wpri, B_FALSE); + scp->gqc_head->sq_state = SQS_DEFAULT; + squeue_bind(scp->gqc_head, cpuid); + + return (scp); +} + +static void +gsqueue_cpu_destroy(gsqueue_cpu_t *scp) +{ + squeue_destroy(scp->gqc_head); + kmem_cache_free(gsqueue_cpu_cache, scp); +} + +gsqueue_set_t * +gsqueue_set_create(uint_t wwait, pri_t wpri) +{ + int i; + gsqueue_set_t *gssp; + + gssp = kmem_cache_alloc(gsqueue_set_cache, KM_SLEEP); + gssp->gs_wwait = wwait; + gssp->gs_wpri = wpri; + gssp->gs_ncpus = 0; + + /* + * We're grabbing CPU lock. Once we let go of it we have to ensure all + * set up of the gsqueue_set_t is complete, as it'll be in there for the + * various CPU DR bits. + */ + mutex_enter(&cpu_lock); + + for (i = 0; i < NCPU; i++) { + gsqueue_cpu_t *scp; + cpu_t *cp = cpu_get(i); + if (cp != NULL && CPU_ACTIVE(cp) && + cp->cpu_flags & CPU_EXISTS) { + scp = gsqueue_cpu_create(wwait, wpri, cp->cpu_id); + gssp->gs_cpus[gssp->gs_ncpus] = scp; + gssp->gs_ncpus++; + } + } + + /* Finally we can add it to our global list and be done */ + mutex_enter(&gsqueue_lock); + list_insert_tail(&gsqueue_list, gssp); + mutex_exit(&gsqueue_lock); + mutex_exit(&cpu_lock); + + return (gssp); +} + +void +gsqueue_set_destroy(gsqueue_set_t *gssp) +{ + int i; + gsqueue_cpu_t *scp; + + /* + * Go through and unbind all of the squeues while cpu_lock is held and + * move them to the defunct list. Once that's done, we don't need to do + * anything else with cpu_lock. + */ + mutex_enter(&cpu_lock); + mutex_enter(&gsqueue_lock); + list_remove(&gsqueue_list, gssp); + mutex_exit(&gsqueue_lock); + + mutex_enter(&gssp->gs_lock); + + for (i = 0; i < gssp->gs_ncpus; i++) { + scp = gssp->gs_cpus[i]; + squeue_unbind(scp->gqc_head); + scp->gqc_next = gssp->gs_defunct; + gssp->gs_defunct = scp; + gssp->gs_cpus[i] = NULL; + } + gssp->gs_ncpus = 0; + + mutex_exit(&gssp->gs_lock); + mutex_exit(&cpu_lock); + + while (gssp->gs_defunct != NULL) { + gsqueue_cpu_t *scp; + + scp = gssp->gs_defunct; + gssp->gs_defunct = scp->gqc_next; + gsqueue_cpu_destroy(scp); + } + + while (gssp->gs_cbs != NULL) { + gsqueue_cb_t *cbp; + + cbp = gssp->gs_cbs; + gssp->gs_cbs = cbp->gcb_next; + kmem_cache_free(gsqueue_cb_cache, cbp); + } + + ASSERT(gssp->gs_ncpus == 0); + ASSERT(gssp->gs_defunct == NULL); + ASSERT(gssp->gs_cbs == NULL); + kmem_cache_free(gsqueue_set_cache, gssp); +} + +gsqueue_t * +gsqueue_set_get(gsqueue_set_t *gssp, uint_t index) +{ + squeue_t *sqp; + gsqueue_cpu_t *scp; + + mutex_enter(&gssp->gs_lock); + scp = gssp->gs_cpus[index % gssp->gs_ncpus]; + sqp = scp->gqc_head; + mutex_exit(&gssp->gs_lock); + return ((gsqueue_t *)sqp); +} + +uintptr_t +gsqueue_set_cb_add(gsqueue_set_t *gssp, gsqueue_cb_f cb, void *arg) +{ + gsqueue_cb_t *cbp; + + cbp = kmem_cache_alloc(gsqueue_cb_cache, KM_SLEEP); + cbp->gcb_func = cb; + cbp->gcb_arg = arg; + + mutex_enter(&gssp->gs_lock); + cbp->gcb_next = gssp->gs_cbs; + gssp->gs_cbs = cbp; + mutex_exit(&gssp->gs_lock); + return ((uintptr_t)cbp); +} + +int +gsqueue_set_cb_remove(gsqueue_set_t *gssp, uintptr_t id) +{ + gsqueue_cb_t *cbp, *prev; + mutex_enter(&gssp->gs_lock); + cbp = gssp->gs_cbs; + prev = NULL; + while (cbp != NULL) { + if ((uintptr_t)cbp != id) { + prev = cbp; + cbp = cbp->gcb_next; + continue; + } + + if (prev == NULL) { + gssp->gs_cbs = cbp->gcb_next; + } else { + prev->gcb_next = cbp->gcb_next; + } + + mutex_exit(&gssp->gs_lock); + kmem_cache_free(gsqueue_cb_cache, cbp); + return (0); + } + mutex_exit(&gssp->gs_lock); + return (-1); +} + +void +gsqueue_enter_one(gsqueue_t *gsp, mblk_t *mp, gsqueue_proc_f func, void *arg, + int flags, uint8_t tag) +{ + squeue_t *sqp = (squeue_t *)gsp; + + ASSERT(mp->b_next == NULL); + ASSERT(mp->b_prev == NULL); + mp->b_queue = (queue_t *)func; + mp->b_prev = arg; + sqp->sq_enter(sqp, mp, mp, 1, NULL, flags, tag); +} + +static void +gsqueue_notify(gsqueue_set_t *gssp, squeue_t *sqp, boolean_t online) +{ + gsqueue_cb_t *cbp; + + ASSERT(MUTEX_HELD(&gssp->gs_lock)); + cbp = gssp->gs_cbs; + while (cbp != NULL) { + cbp->gcb_func(gssp, (gsqueue_t *)sqp, cbp->gcb_arg, online); + cbp = cbp->gcb_next; + } + +} + +/* + * When we online a processor we need to go through and either bind a defunct + * squeue or create a new one. We'll try to reuse a gsqueue_cpu_t from the + * defunct list that used to be on that processor. If no such gsqueue_cpu_t + * exists, then we'll create a new one. We'd rather avoid taking over an + * existing defunct one that used to be on another CPU, as its not unreasonable + * to believe that its CPU will come back. More CPUs are offlined and onlined by + * the administrator or by creating cpu sets than actually get offlined by FMA. + */ +static void +gsqueue_handle_online(processorid_t id) +{ + gsqueue_set_t *gssp; + + ASSERT(MUTEX_HELD(&cpu_lock)); + mutex_enter(&gsqueue_lock); + for (gssp = list_head(&gsqueue_list); gssp != NULL; + gssp = list_next(&gsqueue_list, gssp)) { + gsqueue_cpu_t *scp; + + mutex_enter(&gssp->gs_lock); + scp = gssp->gs_defunct; + while (scp != NULL) { + if (scp->gqc_cpuid == id) + break; + scp = scp->gqc_next; + } + + if (scp == NULL) { + scp = gsqueue_cpu_create(gssp->gs_wwait, + gssp->gs_wpri, id); + } else { + squeue_bind(scp->gqc_head, id); + } + ASSERT(gssp->gs_ncpus < NCPU); + gssp->gs_cpus[gssp->gs_ncpus] = scp; + gssp->gs_ncpus++; + gsqueue_notify(gssp, scp->gqc_head, B_TRUE); + mutex_exit(&gssp->gs_lock); + } + mutex_exit(&gsqueue_lock); +} + +static void +gsqueue_handle_offline(processorid_t id) +{ + gsqueue_set_t *gssp; + + ASSERT(MUTEX_HELD(&cpu_lock)); + mutex_enter(&gsqueue_lock); + for (gssp = list_head(&gsqueue_list); gssp != NULL; + gssp = list_next(&gsqueue_list, gssp)) { + int i; + gsqueue_cpu_t *scp = NULL; + + mutex_enter(&gssp->gs_lock); + for (i = 0; i < gssp->gs_ncpus; i++) { + if (gssp->gs_cpus[i]->gqc_cpuid == id) { + scp = gssp->gs_cpus[i]; + break; + } + } + + if (scp != NULL) { + squeue_unbind(scp->gqc_head); + scp->gqc_next = gssp->gs_defunct; + gssp->gs_defunct = scp; + gssp->gs_cpus[i] = gssp->gs_cpus[gssp->gs_ncpus-1]; + gssp->gs_ncpus--; + gsqueue_notify(gssp, scp->gqc_head, B_FALSE); + } + mutex_exit(&gssp->gs_lock); + } + mutex_exit(&gsqueue_lock); +} + +/* ARGSUSED */ +static int +gsqueue_cpu_setup(cpu_setup_t what, int id, void *unused) +{ + cpu_t *cp; + + ASSERT(MUTEX_HELD(&cpu_lock)); + cp = cpu_get(id); + switch (what) { + case CPU_CONFIG: + case CPU_ON: + case CPU_INIT: + case CPU_CPUPART_IN: + if (cp != NULL && CPU_ACTIVE(cp) && cp->cpu_flags & CPU_EXISTS) + gsqueue_handle_online(cp->cpu_id); + break; + case CPU_UNCONFIG: + case CPU_OFF: + case CPU_CPUPART_OUT: + gsqueue_handle_offline(cp->cpu_id); + break; + default: + break; + } + + return (0); +} + + +/* ARGSUSED */ +static int +gsqueue_set_cache_construct(void *buf, void *arg, int kmflags) +{ + gsqueue_set_t *gssp = buf; + + gssp->gs_cpus = kmem_alloc(sizeof (gsqueue_cpu_t *) * NCPU, kmflags); + if (gssp->gs_cpus == NULL) + return (-1); + + mutex_init(&gssp->gs_lock, NULL, MUTEX_DRIVER, NULL); + gssp->gs_ncpus = 0; + gssp->gs_defunct = NULL; + gssp->gs_cbs = NULL; + + return (0); +} + +static void +gsqueue_set_cache_destruct(void *buf, void *arg) +{ + gsqueue_set_t *gssp = buf; + + kmem_free(gssp->gs_cpus, sizeof (gsqueue_cpu_t *) * NCPU); + gssp->gs_cpus = NULL; + mutex_destroy(&gssp->gs_lock); +} + +static void +gsqueue_ddiinit(void) +{ + list_create(&gsqueue_list, sizeof (gsqueue_set_t), + offsetof(gsqueue_set_t, gs_next)); + mutex_init(&gsqueue_lock, NULL, MUTEX_DRIVER, NULL); + + gsqueue_cb_cache = kmem_cache_create("gsqueue_cb_cache", + sizeof (gsqueue_cb_t), + 0, NULL, NULL, NULL, NULL, NULL, 0); + gsqueue_cpu_cache = kmem_cache_create("gsqueue_cpu_cache", + sizeof (gsqueue_cpu_t), + 0, NULL, NULL, NULL, NULL, NULL, 0); + gsqueue_set_cache = kmem_cache_create("squeue_set_cache", + sizeof (gsqueue_set_t), + 0, gsqueue_set_cache_construct, gsqueue_set_cache_destruct, + NULL, NULL, NULL, 0); + + + mutex_enter(&cpu_lock); + register_cpu_setup_func(gsqueue_cpu_setup, NULL); + mutex_exit(&cpu_lock); +} + +static int +gsqueue_ddifini(void) +{ + mutex_enter(&gsqueue_lock); + if (list_is_empty(&gsqueue_list) == 0) { + mutex_exit(&gsqueue_lock); + return (EBUSY); + } + list_destroy(&gsqueue_list); + mutex_exit(&gsqueue_lock); + + mutex_enter(&cpu_lock); + register_cpu_setup_func(gsqueue_cpu_setup, NULL); + mutex_exit(&cpu_lock); + + kmem_cache_destroy(gsqueue_set_cache); + kmem_cache_destroy(gsqueue_cpu_cache); + kmem_cache_destroy(gsqueue_cb_cache); + + mutex_destroy(&gsqueue_lock); + + return (0); +} + +static struct modlmisc gsqueue_modmisc = { + &mod_miscops, + "gsqueue" +}; + +static struct modlinkage gsqueue_modlinkage = { + MODREV_1, + &gsqueue_modmisc, + NULL +}; + +int +_init(void) +{ + int ret; + + gsqueue_ddiinit(); + if ((ret = mod_install(&gsqueue_modlinkage)) != 0) { + VERIFY(gsqueue_ddifini() == 0); + return (ret); + } + + return (ret); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&gsqueue_modlinkage, modinfop)); +} + +int +_fini(void) +{ + int ret; + + if ((ret = gsqueue_ddifini()) != 0) + return (ret); + + if ((ret = mod_remove(&gsqueue_modlinkage)) != 0) + return (ret); + + return (0); +} diff --git a/usr/src/uts/common/io/inotify.c b/usr/src/uts/common/io/inotify.c new file mode 100644 index 0000000000..8096ac0fe3 --- /dev/null +++ b/usr/src/uts/common/io/inotify.c @@ -0,0 +1,1486 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright (c) 2014 Joyent, Inc. All rights reserved. + */ + +/* + * Support for the inotify facility, a Linux-borne facility for asynchronous + * notification of certain events on specified files or directories. Our + * implementation broadly leverages the file event monitoring facility, and + * would actually be quite straightforward were it not for a very serious + * blunder in the inotify interface: in addition to allowing for one to be + * notified on events on a particular file or directory, inotify also allows + * for one to be notified on certain events on files _within_ a watched + * directory -- even though those events have absolutely nothing to do with + * the directory itself. This leads to all sorts of madness because file + * operations are (of course) not undertaken on paths but rather on open + * files -- and the relationships between open files and the paths that resolve + * to those files are neither static nor isomorphic. We implement this + * concept by having _child watches_ when directories are watched with events + * in IN_CHILD_EVENTS. We add child watches when a watch on a directory is + * first added, and we modify those child watches dynamically as files are + * created, deleted, moved into or moved out of the specified directory. This + * mechanism works well, absent hard links. Hard links, unfortunately, break + * this rather badly, and the user is warned that watches on directories that + * have multiple directory entries referring to the same file may behave + * unexpectedly. + */ + +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/inotify.h> +#include <sys/fem.h> +#include <sys/conf.h> +#include <sys/stat.h> +#include <sys/vfs_opreg.h> +#include <sys/vmem.h> +#include <sys/avl.h> +#include <sys/sysmacros.h> +#include <sys/cyclic.h> +#include <sys/filio.h> + +struct inotify_state; +struct inotify_kevent; + +typedef struct inotify_watch inotify_watch_t; +typedef struct inotify_state inotify_state_t; +typedef struct inotify_kevent inotify_kevent_t; + +struct inotify_watch { + kmutex_t inw_lock; /* lock protecting ref count */ + int inw_refcnt; /* reference count */ + uint8_t inw_zombie:1; /* boolean: is zombie */ + uint8_t inw_fired:1; /* boolean: fired one-shot */ + uint8_t inw_active:1; /* boolean: watch is active */ + uint8_t inw_orphaned:1; /* boolean: orphaned */ + kcondvar_t inw_cv; /* condvar for zombifier */ + uint32_t inw_mask; /* mask of watch */ + int32_t inw_wd; /* watch descriptor */ + vnode_t *inw_vp; /* underlying vnode */ + inotify_watch_t *inw_parent; /* parent, if a child */ + avl_node_t inw_byvp; /* watches by vnode */ + avl_node_t inw_bywd; /* watches by descriptor */ + avl_tree_t inw_children; /* children, if a parent */ + char *inw_name; /* name, if a child */ + list_node_t inw_orphan; /* orphan list */ + inotify_state_t *inw_state; /* corresponding state */ +}; + +struct inotify_kevent { + inotify_kevent_t *ine_next; /* next event in queue */ + struct inotify_event ine_event; /* event (variable size) */ +}; + +#define INOTIFY_EVENT_LENGTH(ev) \ + (sizeof (inotify_kevent_t) + (ev)->ine_event.len) + +struct inotify_state { + kmutex_t ins_lock; /* lock protecting state */ + avl_tree_t ins_byvp; /* watches by vnode */ + avl_tree_t ins_bywd; /* watches by descriptor */ + vmem_t *ins_wds; /* watch identifier arena */ + int ins_maxwatches; /* maximum number of watches */ + int ins_maxevents; /* maximum number of events */ + int ins_nevents; /* current # of events */ + int32_t ins_size; /* total size of events */ + inotify_kevent_t *ins_head; /* head of event queue */ + inotify_kevent_t *ins_tail; /* tail of event queue */ + pollhead_t ins_pollhd; /* poll head */ + kcondvar_t ins_cv; /* condvar for reading */ + list_t ins_orphans; /* orphan list */ + cyclic_id_t ins_cleaner; /* cyclic for cleaning */ + inotify_watch_t *ins_zombies; /* zombie watch list */ + cred_t *ins_cred; /* creator's credentials */ + inotify_state_t *ins_next; /* next state on global list */ +}; + +/* + * Tunables (exported read-only in lx-branded zones via /proc). + */ +int inotify_maxwatches = 8192; /* max watches per instance */ +int inotify_maxevents = 16384; /* max events */ +int inotify_maxinstances = 128; /* max instances per user */ + +/* + * Internal global variables. + */ +static kmutex_t inotify_lock; /* lock protecting state */ +static dev_info_t *inotify_devi; /* device info */ +static fem_t *inotify_femp; /* FEM pointer */ +static vmem_t *inotify_minor; /* minor number arena */ +static void *inotify_softstate; /* softstate pointer */ +static inotify_state_t *inotify_state; /* global list if state */ + +static void inotify_watch_event(inotify_watch_t *, uint64_t, char *); +static void inotify_watch_insert(inotify_watch_t *, vnode_t *, char *); +static void inotify_watch_delete(inotify_watch_t *, uint32_t); +static void inotify_watch_remove(inotify_state_t *state, + inotify_watch_t *watch); + +static int +inotify_fop_close(femarg_t *vf, int flag, int count, offset_t offset, + cred_t *cr, caller_context_t *ct) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_close(vf, flag, count, offset, cr, ct)) == 0) { + inotify_watch_event(watch, flag & FWRITE ? + IN_CLOSE_WRITE : IN_CLOSE_NOWRITE, NULL); + } + + return (rval); +} + +static int +inotify_fop_create(femarg_t *vf, char *name, vattr_t *vap, vcexcl_t excl, + int mode, vnode_t **vpp, cred_t *cr, int flag, caller_context_t *ct, + vsecattr_t *vsecp) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_create(vf, name, vap, excl, mode, + vpp, cr, flag, ct, vsecp)) == 0) { + inotify_watch_insert(watch, *vpp, name); + inotify_watch_event(watch, IN_CREATE, name); + } + + return (rval); +} + +static int +inotify_fop_link(femarg_t *vf, vnode_t *svp, char *tnm, cred_t *cr, + caller_context_t *ct, int flags) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_link(vf, svp, tnm, cr, ct, flags)) == 0) { + inotify_watch_insert(watch, svp, tnm); + inotify_watch_event(watch, IN_CREATE, tnm); + } + + return (rval); +} + +static int +inotify_fop_mkdir(femarg_t *vf, char *name, vattr_t *vap, vnode_t **vpp, + cred_t *cr, caller_context_t *ct, int flags, vsecattr_t *vsecp) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_mkdir(vf, name, vap, vpp, cr, + ct, flags, vsecp)) == 0) { + inotify_watch_insert(watch, *vpp, name); + inotify_watch_event(watch, IN_CREATE | IN_ISDIR, name); + } + + return (rval); +} + +static int +inotify_fop_open(femarg_t *vf, int mode, cred_t *cr, caller_context_t *ct) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_open(vf, mode, cr, ct)) == 0) + inotify_watch_event(watch, IN_OPEN, NULL); + + return (rval); +} + +static int +inotify_fop_read(femarg_t *vf, struct uio *uiop, int ioflag, struct cred *cr, + caller_context_t *ct) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval = vnext_read(vf, uiop, ioflag, cr, ct); + inotify_watch_event(watch, IN_ACCESS, NULL); + + return (rval); +} + +static int +inotify_fop_readdir(femarg_t *vf, uio_t *uiop, cred_t *cr, int *eofp, + caller_context_t *ct, int flags) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval = vnext_readdir(vf, uiop, cr, eofp, ct, flags); + inotify_watch_event(watch, IN_ACCESS | IN_ISDIR, NULL); + + return (rval); +} + +int +inotify_fop_remove(femarg_t *vf, char *nm, cred_t *cr, caller_context_t *ct, + int flags) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_remove(vf, nm, cr, ct, flags)) == 0) + inotify_watch_event(watch, IN_DELETE, nm); + + return (rval); +} + +int +inotify_fop_rmdir(femarg_t *vf, char *nm, vnode_t *cdir, cred_t *cr, + caller_context_t *ct, int flags) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_rmdir(vf, nm, cdir, cr, ct, flags)) == 0) + inotify_watch_event(watch, IN_DELETE | IN_ISDIR, nm); + + return (rval); +} + +static int +inotify_fop_setattr(femarg_t *vf, vattr_t *vap, int flags, cred_t *cr, + caller_context_t *ct) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval; + + if ((rval = vnext_setattr(vf, vap, flags, cr, ct)) == 0) + inotify_watch_event(watch, IN_ATTRIB, NULL); + + return (rval); +} + +static int +inotify_fop_write(femarg_t *vf, struct uio *uiop, int ioflag, struct cred *cr, + caller_context_t *ct) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + int rval = vnext_write(vf, uiop, ioflag, cr, ct); + inotify_watch_event(watch, IN_MODIFY, NULL); + + return (rval); +} + +static int +inotify_fop_vnevent(femarg_t *vf, vnevent_t vnevent, vnode_t *dvp, char *name, + caller_context_t *ct) +{ + inotify_watch_t *watch = vf->fa_fnode->fn_available; + + switch (vnevent) { + case VE_RENAME_SRC: + inotify_watch_event(watch, IN_MOVE_SELF, NULL); + inotify_watch_delete(watch, IN_MOVE_SELF); + break; + case VE_REMOVE: + /* + * Linux will apparently fire an IN_ATTRIB event when the link + * count changes (including when it drops to 0 on a remove). + * This is merely somewhat odd; what is amazing is that this + * IN_ATTRIB event is not visible on an inotify watch on the + * parent directory. (IN_ATTRIB events are normally sent to + * watches on the parent directory). While it's hard to + * believe that this constitutes desired semantics, ltp + * unfortunately tests this case (if implicitly); in the name + * of bug-for-bug compatibility, we fire IN_ATTRIB iff we are + * explicitly watching the file that has been removed. + */ + if (watch->inw_parent == NULL) + inotify_watch_event(watch, IN_ATTRIB, NULL); + + /*FALLTHROUGH*/ + case VE_RENAME_DEST: + inotify_watch_event(watch, IN_DELETE_SELF, NULL); + inotify_watch_delete(watch, IN_DELETE_SELF); + break; + case VE_RMDIR: + /* + * It seems that IN_ISDIR should really be OR'd in here, but + * Linux doesn't seem to do that in this case; for the sake of + * bug-for-bug compatibility, we don't do it either. + */ + inotify_watch_event(watch, IN_DELETE_SELF, NULL); + inotify_watch_delete(watch, IN_DELETE_SELF); + break; + case VE_CREATE: + inotify_watch_event(watch, IN_MODIFY | IN_ATTRIB, NULL); + break; + case VE_LINK: + inotify_watch_event(watch, IN_ATTRIB, NULL); + break; + case VE_RENAME_SRC_DIR: + inotify_watch_event(watch, IN_MOVED_FROM, name); + break; + case VE_RENAME_DEST_DIR: + if (name == NULL) + name = dvp->v_path; + + inotify_watch_insert(watch, dvp, name); + inotify_watch_event(watch, IN_MOVED_TO, name); + break; + case VE_SUPPORT: + case VE_MOUNTEDOVER: + case VE_TRUNCATE: + break; + } + + return (vnext_vnevent(vf, vnevent, dvp, name, ct)); +} + +const fs_operation_def_t inotify_vnodesrc_template[] = { + VOPNAME_CLOSE, { .femop_close = inotify_fop_close }, + VOPNAME_CREATE, { .femop_create = inotify_fop_create }, + VOPNAME_LINK, { .femop_link = inotify_fop_link }, + VOPNAME_MKDIR, { .femop_mkdir = inotify_fop_mkdir }, + VOPNAME_OPEN, { .femop_open = inotify_fop_open }, + VOPNAME_READ, { .femop_read = inotify_fop_read }, + VOPNAME_READDIR, { .femop_readdir = inotify_fop_readdir }, + VOPNAME_REMOVE, { .femop_remove = inotify_fop_remove }, + VOPNAME_RMDIR, { .femop_rmdir = inotify_fop_rmdir }, + VOPNAME_SETATTR, { .femop_setattr = inotify_fop_setattr }, + VOPNAME_WRITE, { .femop_write = inotify_fop_write }, + VOPNAME_VNEVENT, { .femop_vnevent = inotify_fop_vnevent }, + NULL, NULL +}; + +static int +inotify_watch_cmpwd(inotify_watch_t *lhs, inotify_watch_t *rhs) +{ + if (lhs->inw_wd < rhs->inw_wd) + return (-1); + + if (lhs->inw_wd > rhs->inw_wd) + return (1); + + return (0); +} + +static int +inotify_watch_cmpvp(inotify_watch_t *lhs, inotify_watch_t *rhs) +{ + uintptr_t lvp = (uintptr_t)lhs->inw_vp, rvp = (uintptr_t)rhs->inw_vp; + + if (lvp < rvp) + return (-1); + + if (lvp > rvp) + return (1); + + return (0); +} + +static void +inotify_watch_hold(inotify_watch_t *watch) +{ + mutex_enter(&watch->inw_lock); + VERIFY(watch->inw_refcnt > 0); + watch->inw_refcnt++; + mutex_exit(&watch->inw_lock); +} + +static void +inotify_watch_release(inotify_watch_t *watch) +{ + mutex_enter(&watch->inw_lock); + VERIFY(watch->inw_refcnt > 1); + + if (--watch->inw_refcnt == 1 && watch->inw_zombie) { + /* + * We're down to our last reference; kick anyone that might be + * waiting. + */ + cv_signal(&watch->inw_cv); + } + + mutex_exit(&watch->inw_lock); +} + +static void +inotify_watch_event(inotify_watch_t *watch, uint64_t mask, char *name) +{ + inotify_kevent_t *event, *tail; + inotify_state_t *state = watch->inw_state; + uint32_t wd = watch->inw_wd, cookie = 0, len; + int align = sizeof (uintptr_t) - 1; + boolean_t removal = mask & IN_REMOVAL ? B_TRUE : B_FALSE; + inotify_watch_t *source = watch; + + if (!(mask &= watch->inw_mask) || mask == IN_ISDIR) + return; + + if (watch->inw_parent != NULL) { + /* + * This is an event on the child; if this isn't a valid child + * event, return. Otherwise, we move our watch to be our + * parent (which we know is around because we have a hold on + * it) and continue. + */ + if (!(mask & IN_CHILD_EVENTS)) + return; + + name = watch->inw_name; + watch = watch->inw_parent; + } + + if (!removal) { + mutex_enter(&state->ins_lock); + + if (watch->inw_zombie || + watch->inw_fired || !watch->inw_active) { + mutex_exit(&state->ins_lock); + return; + } + } else { + if (!watch->inw_active) + return; + + VERIFY(MUTEX_HELD(&state->ins_lock)); + } + + /* + * If this is an operation on a directory and it's a child event + * (event if it's not on a child), we specify IN_ISDIR. + */ + if (source->inw_vp->v_type == VDIR && (mask & IN_CHILD_EVENTS)) + mask |= IN_ISDIR; + + if (mask & (IN_MOVED_FROM | IN_MOVED_TO)) + cookie = (uint32_t)curthread->t_did; + + if (state->ins_nevents >= state->ins_maxevents) { + /* + * We're at our maximum number of events -- turn our event + * into an IN_Q_OVERFLOW event, which will be coalesced if + * it's already the tail event. + */ + mask = IN_Q_OVERFLOW; + wd = (uint32_t)-1; + cookie = 0; + len = 0; + } + + if ((tail = state->ins_tail) != NULL && tail->ine_event.wd == wd && + tail->ine_event.mask == mask && tail->ine_event.cookie == cookie && + ((tail->ine_event.len == 0 && len == 0) || + (name != NULL && tail->ine_event.len != 0 && + strcmp(tail->ine_event.name, name) == 0))) { + /* + * This is an implicitly coalesced event; we're done. + */ + if (!removal) + mutex_exit(&state->ins_lock); + return; + } + + if (name != NULL) { + if ((len = strlen(name) + 1) & align) + len += (align + 1) - (len & align); + } else { + len = 0; + } + + event = kmem_zalloc(sizeof (inotify_kevent_t) + len, KM_SLEEP); + event->ine_event.wd = wd; + event->ine_event.mask = (uint32_t)mask; + event->ine_event.cookie = cookie; + event->ine_event.len = len; + + if (name != NULL) + strcpy(event->ine_event.name, name); + + if (tail != NULL) { + tail->ine_next = event; + } else { + VERIFY(state->ins_head == NULL); + state->ins_head = event; + cv_broadcast(&state->ins_cv); + } + + state->ins_tail = event; + state->ins_nevents++; + state->ins_size += sizeof (event->ine_event) + len; + + if ((watch->inw_mask & IN_ONESHOT) && !watch->inw_fired) { + /* + * If this is a one-shot, we need to remove the watch. (Note + * that this will recurse back into inotify_watch_event() to + * fire the IN_IGNORED event -- but with "removal" set.) + */ + watch->inw_fired = 1; + inotify_watch_remove(state, watch); + } + + if (removal) + return; + + mutex_exit(&state->ins_lock); + pollwakeup(&state->ins_pollhd, POLLRDNORM | POLLIN); +} + +/* + * Destroy a watch. By the time we're in here, the watch must have exactly + * one reference. + */ +static void +inotify_watch_destroy(inotify_watch_t *watch) +{ + VERIFY(MUTEX_HELD(&watch->inw_lock)); + + if (watch->inw_name != NULL) + kmem_free(watch->inw_name, strlen(watch->inw_name) + 1); + + kmem_free(watch, sizeof (inotify_watch_t)); +} + +/* + * Zombify a watch. By the time we come in here, it must be true that the + * watch has already been fem_uninstall()'d -- the only reference should be + * in the state's data structure. If we can get away with freeing it, we'll + * do that -- but if the reference count is greater than one due to an active + * vnode operation, we'll put this watch on the zombie list on the state + * structure. + */ +static void +inotify_watch_zombify(inotify_watch_t *watch) +{ + inotify_state_t *state = watch->inw_state; + + VERIFY(MUTEX_HELD(&state->ins_lock)); + VERIFY(!watch->inw_zombie); + + watch->inw_zombie = 1; + + if (watch->inw_parent != NULL) { + inotify_watch_release(watch->inw_parent); + } else { + avl_remove(&state->ins_byvp, watch); + avl_remove(&state->ins_bywd, watch); + vmem_free(state->ins_wds, (void *)(uintptr_t)watch->inw_wd, 1); + watch->inw_wd = -1; + } + + mutex_enter(&watch->inw_lock); + + if (watch->inw_refcnt == 1) { + /* + * There are no operations in flight and there is no way + * for anyone to discover this watch -- we can destroy it. + */ + inotify_watch_destroy(watch); + } else { + /* + * There are operations in flight; we will need to enqueue + * this for later destruction. + */ + watch->inw_parent = state->ins_zombies; + state->ins_zombies = watch; + mutex_exit(&watch->inw_lock); + } +} + +static inotify_watch_t * +inotify_watch_add(inotify_state_t *state, inotify_watch_t *parent, + const char *name, vnode_t *vp, uint32_t mask) +{ + inotify_watch_t *watch; + int err; + + VERIFY(MUTEX_HELD(&state->ins_lock)); + + watch = kmem_zalloc(sizeof (inotify_watch_t), KM_SLEEP); + + watch->inw_vp = vp; + watch->inw_mask = mask; + watch->inw_state = state; + watch->inw_refcnt = 1; + + if (parent == NULL) { + watch->inw_wd = (int)(uintptr_t)vmem_alloc(state->ins_wds, + 1, VM_BESTFIT | VM_SLEEP); + avl_add(&state->ins_byvp, watch); + avl_add(&state->ins_bywd, watch); + + avl_create(&watch->inw_children, + (int(*)(const void *, const void *))inotify_watch_cmpvp, + sizeof (inotify_watch_t), + offsetof(inotify_watch_t, inw_byvp)); + } else { + VERIFY(name != NULL); + inotify_watch_hold(parent); + watch->inw_mask &= IN_CHILD_EVENTS; + watch->inw_parent = parent; + watch->inw_name = kmem_alloc(strlen(name) + 1, KM_SLEEP); + strcpy(watch->inw_name, name); + + avl_add(&parent->inw_children, watch); + } + + /* + * Add our monitor to the vnode. We must not have the watch lock held + * when we do this, as it will immediately hold our watch. + */ + err = fem_install(vp, inotify_femp, watch, OPARGUNIQ, + (void (*)(void *))inotify_watch_hold, + (void (*)(void *))inotify_watch_release); + + VERIFY(err == 0); + + return (watch); +} + +/* + * Remove a (non-child) watch. This is called from either synchronous context + * via inotify_rm_watch() or monitor context via either a vnevent or a + * one-shot. + */ +static void +inotify_watch_remove(inotify_state_t *state, inotify_watch_t *watch) +{ + inotify_watch_t *child; + int err; + + VERIFY(MUTEX_HELD(&state->ins_lock)); + VERIFY(watch->inw_parent == NULL); + + err = fem_uninstall(watch->inw_vp, inotify_femp, watch); + VERIFY(err == 0); + + /* + * If we have children, we're going to remove them all and set them + * all to be zombies. + */ + while ((child = avl_first(&watch->inw_children)) != NULL) { + VERIFY(child->inw_parent == watch); + avl_remove(&watch->inw_children, child); + + err = fem_uninstall(child->inw_vp, inotify_femp, child); + VERIFY(err == 0); + + /* + * If this child watch has been orphaned, remove it from the + * state's list of orphans. + */ + if (child->inw_orphaned) + list_remove(&state->ins_orphans, child); + + VN_RELE(child->inw_vp); + + /* + * We're down (or should be down) to a single reference to + * this child watch; it's safe to zombify it. + */ + inotify_watch_zombify(child); + } + + inotify_watch_event(watch, IN_IGNORED | IN_REMOVAL, NULL); + VN_RELE(watch->inw_vp); + + /* + * It's now safe to zombify the watch -- we know that the only reference + * can come from operations in flight. + */ + inotify_watch_zombify(watch); +} + +/* + * Delete a watch. Should only be called from VOP context. + */ +static void +inotify_watch_delete(inotify_watch_t *watch, uint32_t event) +{ + inotify_state_t *state = watch->inw_state; + inotify_watch_t cmp = { .inw_vp = watch->inw_vp }, *parent; + int err; + + if (event != IN_DELETE_SELF && !(watch->inw_mask & IN_CHILD_EVENTS)) + return; + + mutex_enter(&state->ins_lock); + + if (watch->inw_zombie) { + mutex_exit(&state->ins_lock); + return; + } + + if ((parent = watch->inw_parent) == NULL) { + if (event == IN_DELETE_SELF) { + /* + * If we're here because we're being deleted and we + * are not a child watch, we need to delete the entire + * watch, children and all. + */ + inotify_watch_remove(state, watch); + } + + mutex_exit(&state->ins_lock); + return; + } else { + if (event == IN_DELETE_SELF && + !(parent->inw_mask & IN_EXCL_UNLINK)) { + /* + * This is a child watch for a file that is being + * removed and IN_EXCL_UNLINK has not been specified; + * indicate that it is orphaned and add it to the list + * of orphans. (This list will be checked by the + * cleaning cyclic to determine when the watch has + * become the only hold on the vnode, at which point + * the watch can be zombified.) Note that we check + * if the watch is orphaned before we orphan it: hard + * links make it possible for VE_REMOVE to be called + * multiple times on the same vnode. (!) + */ + if (!watch->inw_orphaned) { + watch->inw_orphaned = 1; + list_insert_head(&state->ins_orphans, watch); + } + + mutex_exit(&state->ins_lock); + return; + } + + if (watch->inw_orphaned) { + /* + * If we're here, a file was orphaned and then later + * moved -- which almost certainly means that hard + * links are on the scene. We choose the orphan over + * the move because we don't want to spuriously + * drop events if we can avoid it. + */ + list_remove(&state->ins_orphans, watch); + } + } + + if (avl_find(&parent->inw_children, &cmp, NULL) == NULL) { + /* + * This watch has already been deleted from the parent. + */ + mutex_exit(&state->ins_lock); + return; + } + + avl_remove(&parent->inw_children, watch); + err = fem_uninstall(watch->inw_vp, inotify_femp, watch); + VERIFY(err == 0); + + VN_RELE(watch->inw_vp); + + /* + * It's now safe to zombify the watch -- which won't actually delete + * it as we know that the reference count is greater than 1. + */ + inotify_watch_zombify(watch); + mutex_exit(&state->ins_lock); +} + +/* + * Insert a new child watch. Should only be called from VOP context when + * a child is created in a watched directory. + */ +static void +inotify_watch_insert(inotify_watch_t *watch, vnode_t *vp, char *name) +{ + inotify_state_t *state = watch->inw_state; + inotify_watch_t cmp = { .inw_vp = vp }; + + if (!(watch->inw_mask & IN_CHILD_EVENTS)) + return; + + mutex_enter(&state->ins_lock); + + if (watch->inw_zombie || watch->inw_parent != NULL || vp == NULL) { + mutex_exit(&state->ins_lock); + return; + } + + if (avl_find(&watch->inw_children, &cmp, NULL) != NULL) { + mutex_exit(&state->ins_lock); + return; + } + + VN_HOLD(vp); + watch = inotify_watch_add(state, watch, name, vp, watch->inw_mask); + VERIFY(watch != NULL); + + mutex_exit(&state->ins_lock); +} + + +static int +inotify_add_watch(inotify_state_t *state, vnode_t *vp, uint32_t mask, + int32_t *wdp) +{ + inotify_watch_t *watch, cmp = { .inw_vp = vp }; + uint32_t set; + + set = (mask & (IN_ALL_EVENTS | IN_MODIFIERS)) | IN_UNMASKABLE; + + /* + * Lookup our vnode to determine if we already have a watch on it. + */ + mutex_enter(&state->ins_lock); + + if ((watch = avl_find(&state->ins_byvp, &cmp, NULL)) == NULL) { + /* + * We don't have this watch; allocate a new one, provided that + * we have fewer than our limit. + */ + if (avl_numnodes(&state->ins_bywd) >= state->ins_maxwatches) { + mutex_exit(&state->ins_lock); + return (ENOSPC); + } + + VN_HOLD(vp); + watch = inotify_watch_add(state, NULL, NULL, vp, set); + *wdp = watch->inw_wd; + mutex_exit(&state->ins_lock); + + return (0); + } + + VERIFY(!watch->inw_zombie); + + if (!(mask & IN_MASK_ADD)) { + /* + * Note that if we're resetting our event mask and we're + * transitioning from an event mask that includes child events + * to one that doesn't, there will be potentially some stale + * child watches. This is basically fine: they won't fire, + * and they will correctly be removed when the watch is + * removed. + */ + watch->inw_mask = 0; + } + + watch->inw_mask |= set; + + *wdp = watch->inw_wd; + + mutex_exit(&state->ins_lock); + + return (0); +} + +static int +inotify_add_child(inotify_state_t *state, vnode_t *vp, char *name) +{ + inotify_watch_t *watch, cmp = { .inw_vp = vp }; + vnode_t *cvp; + int err; + + /* + * Verify that the specified child doesn't have a directory component + * within it. + */ + if (strchr(name, '/') != NULL) + return (EINVAL); + + /* + * Lookup the underlying file. Note that this will succeed even if + * we don't have permissions to actually read the file. + */ + if ((err = lookupnameat(name, + UIO_SYSSPACE, NO_FOLLOW, NULL, &cvp, vp)) != 0) { + return (err); + } + + /* + * Use our vnode to find our watch, and then add our child watch to it. + */ + mutex_enter(&state->ins_lock); + + if ((watch = avl_find(&state->ins_byvp, &cmp, NULL)) == NULL) { + /* + * This is unexpected -- it means that we don't have the + * watch that we thought we had. + */ + mutex_exit(&state->ins_lock); + VN_RELE(cvp); + return (ENXIO); + } + + /* + * Now lookup the child vnode in the watch; we'll only add it if it + * isn't already there. + */ + cmp.inw_vp = cvp; + + if (avl_find(&watch->inw_children, &cmp, NULL) != NULL) { + mutex_exit(&state->ins_lock); + VN_RELE(cvp); + return (0); + } + + watch = inotify_watch_add(state, watch, name, cvp, watch->inw_mask); + VERIFY(watch != NULL); + mutex_exit(&state->ins_lock); + + return (0); +} + +static int +inotify_rm_watch(inotify_state_t *state, int32_t wd) +{ + inotify_watch_t *watch, cmp = { .inw_wd = wd }; + + mutex_enter(&state->ins_lock); + + if ((watch = avl_find(&state->ins_bywd, &cmp, NULL)) == NULL) { + mutex_exit(&state->ins_lock); + return (EINVAL); + } + + inotify_watch_remove(state, watch); + mutex_exit(&state->ins_lock); + + return (0); +} + +static int +inotify_activate(inotify_state_t *state, int32_t wd) +{ + inotify_watch_t *watch, cmp = { .inw_wd = wd }; + + mutex_enter(&state->ins_lock); + + if ((watch = avl_find(&state->ins_bywd, &cmp, NULL)) == NULL) { + mutex_exit(&state->ins_lock); + return (EINVAL); + } + + watch->inw_active = 1; + + mutex_exit(&state->ins_lock); + + return (0); +} + +/* + * Called periodically as a cyclic to process the orphans and zombies. + */ +static void +inotify_clean(void *arg) +{ + inotify_state_t *state = arg; + inotify_watch_t *watch, *parent, *next, **prev; + int err; + + mutex_enter(&state->ins_lock); + + for (watch = list_head(&state->ins_orphans); + watch != NULL; watch = next) { + next = list_next(&state->ins_orphans, watch); + + VERIFY(!watch->inw_zombie); + VERIFY((parent = watch->inw_parent) != NULL); + + if (watch->inw_vp->v_count > 1) + continue; + + avl_remove(&parent->inw_children, watch); + err = fem_uninstall(watch->inw_vp, inotify_femp, watch); + VERIFY(err == 0); + + list_remove(&state->ins_orphans, watch); + + VN_RELE(watch->inw_vp); + inotify_watch_zombify(watch); + } + + prev = &state->ins_zombies; + + while ((watch = *prev) != NULL) { + mutex_enter(&watch->inw_lock); + + if (watch->inw_refcnt == 1) { + *prev = watch->inw_parent; + inotify_watch_destroy(watch); + continue; + } + + prev = &watch->inw_parent; + mutex_exit(&watch->inw_lock); + } + + mutex_exit(&state->ins_lock); +} + +/*ARGSUSED*/ +static int +inotify_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) +{ + inotify_state_t *state; + major_t major = getemajor(*devp); + minor_t minor = getminor(*devp); + int instances = 0; + cyc_handler_t hdlr; + cyc_time_t when; + char c[64]; + + if (minor != INOTIFYMNRN_INOTIFY) + return (ENXIO); + + mutex_enter(&inotify_lock); + + for (state = inotify_state; state != NULL; state = state->ins_next) { + if (state->ins_cred == cred_p) + instances++; + } + + if (instances >= inotify_maxinstances) { + mutex_exit(&inotify_lock); + return (EMFILE); + } + + minor = (minor_t)(uintptr_t)vmem_alloc(inotify_minor, 1, + VM_BESTFIT | VM_SLEEP); + + if (ddi_soft_state_zalloc(inotify_softstate, minor) != DDI_SUCCESS) { + vmem_free(inotify_minor, (void *)(uintptr_t)minor, 1); + mutex_exit(&inotify_lock); + return (NULL); + } + + state = ddi_get_soft_state(inotify_softstate, minor); + *devp = makedevice(major, minor); + + crhold(cred_p); + state->ins_cred = cred_p; + state->ins_next = inotify_state; + inotify_state = state; + + (void) snprintf(c, sizeof (c), "inotify_watchid_%d", minor); + state->ins_wds = vmem_create(c, (void *)1, UINT32_MAX, 1, + NULL, NULL, NULL, 0, VM_SLEEP | VMC_IDENTIFIER); + + avl_create(&state->ins_bywd, + (int(*)(const void *, const void *))inotify_watch_cmpwd, + sizeof (inotify_watch_t), + offsetof(inotify_watch_t, inw_bywd)); + + avl_create(&state->ins_byvp, + (int(*)(const void *, const void *))inotify_watch_cmpvp, + sizeof (inotify_watch_t), + offsetof(inotify_watch_t, inw_byvp)); + + list_create(&state->ins_orphans, sizeof (inotify_watch_t), + offsetof(inotify_watch_t, inw_orphan)); + + state->ins_maxwatches = inotify_maxwatches; + state->ins_maxevents = inotify_maxevents; + + mutex_exit(&inotify_lock); + + mutex_enter(&cpu_lock); + + hdlr.cyh_func = inotify_clean; + hdlr.cyh_level = CY_LOW_LEVEL; + hdlr.cyh_arg = state; + + when.cyt_when = 0; + when.cyt_interval = NANOSEC; + + state->ins_cleaner = cyclic_add(&hdlr, &when); + mutex_exit(&cpu_lock); + + return (0); +} + +/*ARGSUSED*/ +static int +inotify_read(dev_t dev, uio_t *uio, cred_t *cr) +{ + inotify_state_t *state; + inotify_kevent_t *event; + minor_t minor = getminor(dev); + int err = 0, nevents = 0; + size_t len; + + state = ddi_get_soft_state(inotify_softstate, minor); + + mutex_enter(&state->ins_lock); + + while (state->ins_head == NULL) { + if (uio->uio_fmode & (FNDELAY|FNONBLOCK)) { + mutex_exit(&state->ins_lock); + return (EAGAIN); + } + + if (!cv_wait_sig_swap(&state->ins_cv, &state->ins_lock)) { + mutex_exit(&state->ins_lock); + return (EINTR); + } + } + + /* + * We have events and we have our lock; return as many as we can. + */ + while ((event = state->ins_head) != NULL) { + len = sizeof (event->ine_event) + event->ine_event.len; + + if (uio->uio_resid < len) { + if (nevents == 0) + err = EINVAL; + break; + } + + nevents++; + + if ((err = uiomove(&event->ine_event, len, UIO_READ, uio)) != 0) + break; + + VERIFY(state->ins_nevents > 0); + state->ins_nevents--; + + VERIFY(state->ins_size > 0); + state->ins_size -= len; + + if ((state->ins_head = event->ine_next) == NULL) { + VERIFY(event == state->ins_tail); + VERIFY(state->ins_nevents == 0); + state->ins_tail = NULL; + } + + kmem_free(event, INOTIFY_EVENT_LENGTH(event)); + } + + mutex_exit(&state->ins_lock); + + return (err); +} + +/*ARGSUSED*/ +static int +inotify_poll(dev_t dev, short events, int anyyet, short *reventsp, + struct pollhead **phpp) +{ + inotify_state_t *state; + minor_t minor = getminor(dev); + + state = ddi_get_soft_state(inotify_softstate, minor); + + mutex_enter(&state->ins_lock); + + if (state->ins_head != NULL) { + *reventsp = events & (POLLRDNORM | POLLIN); + } else { + *reventsp = 0; + + if (!anyyet) + *phpp = &state->ins_pollhd; + } + + mutex_exit(&state->ins_lock); + + return (0); +} + +/*ARGSUSED*/ +static int +inotify_ioctl(dev_t dev, int cmd, intptr_t arg, int md, cred_t *cr, int *rv) +{ + inotify_state_t *state; + minor_t minor = getminor(dev); + file_t *fp; + int rval; + + state = ddi_get_soft_state(inotify_softstate, minor); + + switch (cmd) { + case INOTIFYIOC_ADD_WATCH: { + inotify_addwatch_t addwatch; + file_t *fp; + + if (copyin((void *)arg, &addwatch, sizeof (addwatch)) != 0) + return (EFAULT); + + if ((fp = getf(addwatch.inaw_fd)) == NULL) + return (EBADF); + + rval = inotify_add_watch(state, fp->f_vnode, + addwatch.inaw_mask, rv); + + releasef(addwatch.inaw_fd); + return (rval); + } + + case INOTIFYIOC_ADD_CHILD: { + inotify_addchild_t addchild; + char name[MAXPATHLEN]; + + if (copyin((void *)arg, &addchild, sizeof (addchild)) != 0) + return (EFAULT); + + if (copyinstr(addchild.inac_name, name, MAXPATHLEN, NULL) != 0) + return (EFAULT); + + if ((fp = getf(addchild.inac_fd)) == NULL) + return (EBADF); + + rval = inotify_add_child(state, fp->f_vnode, name); + + releasef(addchild.inac_fd); + return (rval); + } + + case INOTIFYIOC_RM_WATCH: + return (inotify_rm_watch(state, arg)); + + case INOTIFYIOC_ACTIVATE: + return (inotify_activate(state, arg)); + + case FIONREAD: { + int32_t size; + + mutex_enter(&state->ins_lock); + size = state->ins_size; + mutex_exit(&state->ins_lock); + + if (copyout(&size, (void *)arg, sizeof (size)) != 0) + return (EFAULT); + + return (0); + } + + default: + break; + } + + return (ENOTTY); +} + +/*ARGSUSED*/ +static int +inotify_close(dev_t dev, int flag, int otyp, cred_t *cred_p) +{ + inotify_state_t *state, **sp; + inotify_watch_t *watch, *zombies; + inotify_kevent_t *event; + minor_t minor = getminor(dev); + + state = ddi_get_soft_state(inotify_softstate, minor); + + mutex_enter(&state->ins_lock); + + /* + * First, destroy all of our watches. + */ + while ((watch = avl_first(&state->ins_bywd)) != NULL) + inotify_watch_remove(state, watch); + + /* + * And now destroy our event queue. + */ + while ((event = state->ins_head) != NULL) { + state->ins_head = event->ine_next; + kmem_free(event, INOTIFY_EVENT_LENGTH(event)); + } + + zombies = state->ins_zombies; + state->ins_zombies = NULL; + mutex_exit(&state->ins_lock); + + /* + * Now that our state lock is dropped, we can synchronously wait on + * any zombies. + */ + while ((watch = zombies) != NULL) { + zombies = zombies->inw_parent; + + mutex_enter(&watch->inw_lock); + + while (watch->inw_refcnt > 1) + cv_wait(&watch->inw_cv, &watch->inw_lock); + + inotify_watch_destroy(watch); + } + + mutex_enter(&cpu_lock); + cyclic_remove(state->ins_cleaner); + mutex_exit(&cpu_lock); + + mutex_enter(&inotify_lock); + + /* + * Remove our state from our global list, and release our hold on + * the cred. + */ + for (sp = &inotify_state; *sp != state; sp = &((*sp)->ins_next)) + VERIFY(*sp != NULL); + + *sp = (*sp)->ins_next; + crfree(state->ins_cred); + + ddi_soft_state_free(inotify_softstate, minor); + vmem_free(inotify_minor, (void *)(uintptr_t)minor, 1); + + mutex_exit(&inotify_lock); + + return (0); +} + +/*ARGSUSED*/ +static int +inotify_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) +{ + mutex_enter(&inotify_lock); + + if (ddi_soft_state_init(&inotify_softstate, + sizeof (inotify_state_t), 0) != 0) { + cmn_err(CE_NOTE, "/dev/inotify failed to create soft state"); + mutex_exit(&inotify_lock); + return (DDI_FAILURE); + } + + if (ddi_create_minor_node(devi, "inotify", S_IFCHR, + INOTIFYMNRN_INOTIFY, DDI_PSEUDO, NULL) == DDI_FAILURE) { + cmn_err(CE_NOTE, "/dev/inotify couldn't create minor node"); + ddi_soft_state_fini(&inotify_softstate); + mutex_exit(&inotify_lock); + return (DDI_FAILURE); + } + + if (fem_create("inotify_fem", + inotify_vnodesrc_template, &inotify_femp) != 0) { + cmn_err(CE_NOTE, "/dev/inotify couldn't create FEM state"); + ddi_remove_minor_node(devi, NULL); + ddi_soft_state_fini(&inotify_softstate); + mutex_exit(&inotify_lock); + return (DDI_FAILURE); + } + + ddi_report_dev(devi); + inotify_devi = devi; + + inotify_minor = vmem_create("inotify_minor", (void *)INOTIFYMNRN_CLONE, + UINT32_MAX - INOTIFYMNRN_CLONE, 1, NULL, NULL, NULL, 0, + VM_SLEEP | VMC_IDENTIFIER); + + mutex_exit(&inotify_lock); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +inotify_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + switch (cmd) { + case DDI_DETACH: + break; + + case DDI_SUSPEND: + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } + + mutex_enter(&inotify_lock); + fem_free(inotify_femp); + vmem_destroy(inotify_minor); + + ddi_remove_minor_node(inotify_devi, NULL); + inotify_devi = NULL; + + ddi_soft_state_fini(&inotify_softstate); + mutex_exit(&inotify_lock); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +inotify_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) +{ + int error; + + switch (infocmd) { + case DDI_INFO_DEVT2DEVINFO: + *result = (void *)inotify_devi; + error = DDI_SUCCESS; + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)0; + error = DDI_SUCCESS; + break; + default: + error = DDI_FAILURE; + } + return (error); +} + +static struct cb_ops inotify_cb_ops = { + inotify_open, /* open */ + inotify_close, /* close */ + nulldev, /* strategy */ + nulldev, /* print */ + nodev, /* dump */ + inotify_read, /* read */ + nodev, /* write */ + inotify_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + inotify_poll, /* poll */ + ddi_prop_op, /* cb_prop_op */ + 0, /* streamtab */ + D_NEW | D_MP /* Driver compatibility flag */ +}; + +static struct dev_ops inotify_ops = { + DEVO_REV, /* devo_rev */ + 0, /* refcnt */ + inotify_info, /* get_dev_info */ + nulldev, /* identify */ + nulldev, /* probe */ + inotify_attach, /* attach */ + inotify_detach, /* detach */ + nodev, /* reset */ + &inotify_cb_ops, /* driver operations */ + NULL, /* bus operations */ + nodev, /* dev power */ + ddi_quiesce_not_needed, /* quiesce */ +}; + +static struct modldrv modldrv = { + &mod_driverops, /* module type (this is a pseudo driver) */ + "inotify support", /* name of module */ + &inotify_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modldrv, + NULL +}; + +int +_init(void) +{ + return (mod_install(&modlinkage)); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +int +_fini(void) +{ + return (mod_remove(&modlinkage)); +} diff --git a/usr/src/uts/common/io/inotify.conf b/usr/src/uts/common/io/inotify.conf new file mode 100644 index 0000000000..ce9da6180f --- /dev/null +++ b/usr/src/uts/common/io/inotify.conf @@ -0,0 +1,16 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2014 Joyent, Inc. All rights reserved. +# + +name="inotify" parent="pseudo" instance=0; diff --git a/usr/src/uts/common/io/ixgbe/ixgbe_main.c b/usr/src/uts/common/io/ixgbe/ixgbe_main.c index 848e3470c7..117b7da16a 100644 --- a/usr/src/uts/common/io/ixgbe/ixgbe_main.c +++ b/usr/src/uts/common/io/ixgbe/ixgbe_main.c @@ -1792,6 +1792,7 @@ ixgbe_cbfunc(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg, void *arg1, void *arg2) { ixgbe_t *ixgbe = (ixgbe_t *)arg1; + int prev = ixgbe->intr_cnt; switch (cbaction) { /* IRM callback */ @@ -1805,7 +1806,8 @@ ixgbe_cbfunc(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg, if (ixgbe_intr_adjust(ixgbe, cbaction, count) != DDI_SUCCESS) { ixgbe_error(ixgbe, - "IRM CB: Failed to adjust interrupts"); + "IRM CB: Failed to adjust interrupts [%d %d %d]", + cbaction, count, prev); goto cb_fail; } break; diff --git a/usr/src/uts/common/io/ksocket/ksocket.c b/usr/src/uts/common/io/ksocket/ksocket.c index 49ca6f0475..8944fcbff3 100644 --- a/usr/src/uts/common/io/ksocket/ksocket.c +++ b/usr/src/uts/common/io/ksocket/ksocket.c @@ -22,6 +22,7 @@ /* * Copyright 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, Joyent, Inc. All rights reserved. */ #include <sys/file.h> @@ -820,7 +821,7 @@ ksocket_spoll(ksocket_t ks, int timo, short events, short *revents, if (error != 0 || *revents != 0) break; - if (pcp->pc_flag & T_POLLWAKE) + if (pcp->pc_flag & PC_POLLWAKE) continue; if (timo == -1) { diff --git a/usr/src/uts/common/io/mac/mac.c b/usr/src/uts/common/io/mac/mac.c index ed809e5f45..42b83c7daf 100644 --- a/usr/src/uts/common/io/mac/mac.c +++ b/usr/src/uts/common/io/mac/mac.c @@ -3094,6 +3094,9 @@ mac_prop_check_size(mac_prop_id_t id, uint_t valsize, boolean_t is_range) case MAC_PROP_WL_MLME: minsize = sizeof (wl_mlme_t); break; + case MAC_PROP_VN_PROMISC_FILTERED: + minsize = sizeof (boolean_t); + break; } return (valsize >= minsize); diff --git a/usr/src/uts/common/io/mac/mac_client.c b/usr/src/uts/common/io/mac/mac_client.c index 078d0e816d..88620518f1 100644 --- a/usr/src/uts/common/io/mac/mac_client.c +++ b/usr/src/uts/common/io/mac/mac_client.c @@ -1344,6 +1344,7 @@ mac_client_open(mac_handle_t mh, mac_client_handle_t *mchp, char *name, mcip->mci_p_unicast_list = NULL; mcip->mci_direct_rx_fn = NULL; mcip->mci_direct_rx_arg = NULL; + mcip->mci_vidcache = MCIP_VIDCACHE_INVALID; mcip->mci_unicast_list = NULL; @@ -3255,7 +3256,8 @@ mac_promisc_add(mac_client_handle_t mch, mac_client_promisc_type_t type, } if ((mcip->mci_state_flags & MCIS_IS_VNIC) && - type == MAC_CLIENT_PROMISC_ALL) { + type == MAC_CLIENT_PROMISC_ALL && + (mcip->mci_protect_flags & MPT_FLAG_PROMISC_FILTERED)) { /* * The function is being invoked by the upper MAC client * of a VNIC. The VNIC should only see the traffic @@ -4134,16 +4136,15 @@ mac_info_get(const char *name, mac_info_t *minfop) /* * To get the capabilities that MAC layer cares about, such as rings, factory * mac address, vnic or not, it should directly invoke this function. If the - * link is part of a bridge, then the only "capability" it has is the inability - * to do zero copy. + * link is part of a bridge, then the link is unable to do zero copy. */ boolean_t i_mac_capab_get(mac_handle_t mh, mac_capab_t cap, void *cap_data) { mac_impl_t *mip = (mac_impl_t *)mh; - if (mip->mi_bridge_link != NULL) - return (cap == MAC_CAPAB_NO_ZCOPY); + if (mip->mi_bridge_link != NULL && cap == MAC_CAPAB_NO_ZCOPY) + return (B_TRUE); else if (mip->mi_callbacks->mc_callbacks & MC_GETCAPAB) return (mip->mi_getcapab(mip->mi_driver, cap, cap_data)); else @@ -4800,6 +4801,8 @@ mac_client_add_to_flow_list(mac_client_impl_t *mcip, flow_entry_t *flent) */ rw_enter(&mcip->mci_rw_lock, RW_WRITER); + mcip->mci_vidcache = MCIP_VIDCACHE_INVALID; + /* Add it to the head */ flent->fe_client_next = mcip->mci_flent_list; mcip->mci_flent_list = flent; @@ -4830,6 +4833,8 @@ mac_client_remove_flow_from_list(mac_client_impl_t *mcip, flow_entry_t *flent) * using mci_rw_lock */ rw_enter(&mcip->mci_rw_lock, RW_WRITER); + mcip->mci_vidcache = MCIP_VIDCACHE_INVALID; + while ((fe != NULL) && (fe != flent)) { prev_fe = fe; fe = fe->fe_client_next; @@ -4858,6 +4863,14 @@ mac_client_check_flow_vid(mac_client_impl_t *mcip, uint16_t vid) { flow_entry_t *flent; uint16_t mci_vid; + uint32_t cache = mcip->mci_vidcache; + + /* + * In hopes of not having to touch the mci_rw_lock, check to see if + * this vid matches our cached result. + */ + if (MCIP_VIDCACHE_ISVALID(cache) && MCIP_VIDCACHE_VID(cache) == vid) + return (MCIP_VIDCACHE_BOOL(cache) ? B_TRUE : B_FALSE); /* The mci_flent_list is protected by mci_rw_lock */ rw_enter(&mcip->mci_rw_lock, RW_WRITER); @@ -4865,10 +4878,13 @@ mac_client_check_flow_vid(mac_client_impl_t *mcip, uint16_t vid) flent = flent->fe_client_next) { mci_vid = i_mac_flow_vid(flent); if (vid == mci_vid) { + mcip->mci_vidcache = MCIP_VIDCACHE_CACHE(vid, B_TRUE); rw_exit(&mcip->mci_rw_lock); return (B_TRUE); } } + + mcip->mci_vidcache = MCIP_VIDCACHE_CACHE(vid, B_FALSE); rw_exit(&mcip->mci_rw_lock); return (B_FALSE); } @@ -5521,3 +5537,23 @@ mac_client_set_rings(mac_client_handle_t mch, int rxrings, int txrings) mrp->mrp_ntxrings = txrings; } } + +boolean_t +mac_get_promisc_filtered(mac_client_handle_t mch) +{ + mac_client_impl_t *mcip = (mac_client_impl_t *)mch; + + return (mcip->mci_protect_flags & MPT_FLAG_PROMISC_FILTERED); +} + +void +mac_set_promisc_filtered(mac_client_handle_t mch, boolean_t enable) +{ + mac_client_impl_t *mcip = (mac_client_impl_t *)mch; + + ASSERT(MAC_PERIM_HELD((mac_handle_t)mcip->mci_mip)); + if (enable) + mcip->mci_protect_flags |= MPT_FLAG_PROMISC_FILTERED; + else + mcip->mci_protect_flags &= ~MPT_FLAG_PROMISC_FILTERED; +} diff --git a/usr/src/uts/common/io/mac/mac_protect.c b/usr/src/uts/common/io/mac/mac_protect.c index cd7fcb9a5d..e341f3d562 100644 --- a/usr/src/uts/common/io/mac/mac_protect.c +++ b/usr/src/uts/common/io/mac/mac_protect.c @@ -2297,6 +2297,9 @@ mac_protect_init(mac_client_impl_t *mcip) sizeof (dhcpv6_cid_t), offsetof(dhcpv6_cid_t, dc_node)); avl_create(&mcip->mci_v6_dyn_ip, compare_dhcpv6_ip, sizeof (dhcpv6_addr_t), offsetof(dhcpv6_addr_t, da_node)); + + if (mcip->mci_state_flags & MCIS_IS_VNIC) + mcip->mci_protect_flags |= MPT_FLAG_PROMISC_FILTERED; } void diff --git a/usr/src/uts/common/io/mac/mac_stat.c b/usr/src/uts/common/io/mac/mac_stat.c index 31972f94d8..c1a5c9c069 100644 --- a/usr/src/uts/common/io/mac/mac_stat.c +++ b/usr/src/uts/common/io/mac/mac_stat.c @@ -21,6 +21,7 @@ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright 2013 Joyent, Inc. All rights reserved. */ /* @@ -390,8 +391,8 @@ i_mac_stat_create(void *handle, const char *modname, const char *statname, kstat_t *ksp; kstat_named_t *knp; - ksp = kstat_create(modname, 0, statname, "net", - KSTAT_TYPE_NAMED, count, 0); + ksp = kstat_create_zone(modname, 0, statname, "net", + KSTAT_TYPE_NAMED, count, 0, getzoneid()); if (ksp == NULL) return (NULL); @@ -948,9 +949,9 @@ mac_driver_stat_create(mac_impl_t *mip) major_t major = getmajor(mip->mi_phy_dev); count = MAC_MOD_NKSTAT + MAC_NKSTAT + mip->mi_type->mt_statcount; - ksp = kstat_create((const char *)ddi_major_to_name(major), + ksp = kstat_create_zone((const char *)ddi_major_to_name(major), getminor(mip->mi_phy_dev) - 1, MAC_KSTAT_NAME, - MAC_KSTAT_CLASS, KSTAT_TYPE_NAMED, count, 0); + MAC_KSTAT_CLASS, KSTAT_TYPE_NAMED, count, 0, getzoneid()); if (ksp == NULL) return; diff --git a/usr/src/uts/common/io/mr_sas/mr_sas.conf b/usr/src/uts/common/io/mr_sas/mr_sas.conf index cfda434e23..6c585c6a42 100644 --- a/usr/src/uts/common/io/mr_sas/mr_sas.conf +++ b/usr/src/uts/common/io/mr_sas/mr_sas.conf @@ -13,3 +13,11 @@ # Fast-Path specific flag. Default is "yes". # mrsas-enable-fp="yes"; +flow_control="dmult" queue="qsort" tape="sctp"; + +# MSI specific flag. To enable MSI modify the flag value to "yes" +mrsas-enable-msi="yes"; + +# Fast-Path specific flag. To enable Fast-Path modify the flag value to "yes" +mrsas-enable-fp="yes"; + diff --git a/usr/src/uts/common/io/nfp/THIRDPARTYLICENSE b/usr/src/uts/common/io/nfp/THIRDPARTYLICENSE new file mode 100644 index 0000000000..187088ff34 --- /dev/null +++ b/usr/src/uts/common/io/nfp/THIRDPARTYLICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014, Thales UK Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/usr/src/uts/common/io/nfp/THIRDPARTYLICENSE.descrip b/usr/src/uts/common/io/nfp/THIRDPARTYLICENSE.descrip new file mode 100644 index 0000000000..cde8b65b37 --- /dev/null +++ b/usr/src/uts/common/io/nfp/THIRDPARTYLICENSE.descrip @@ -0,0 +1 @@ +NFAST CRYPTO ACCELERATOR DRIVER diff --git a/usr/src/uts/common/io/nfp/autoversion.h b/usr/src/uts/common/io/nfp/autoversion.h new file mode 100644 index 0000000000..b9021942b2 --- /dev/null +++ b/usr/src/uts/common/io/nfp/autoversion.h @@ -0,0 +1,21 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +/* AUTOGENERATED - DO NOT EDIT */ +#ifndef AUTOVERSION_H +#define AUTOVERSION_H + +#define VERSION_RELEASEMAJOR 2 +#define VERSION_RELEASEMINOR 26 +#define VERSION_RELEASEPATCH 40 +#define VERSION_NO "2.26.40cam999" +#define VERSION_COMPNAME "nfdrv" + +#endif diff --git a/usr/src/uts/common/io/nfp/drvlist.c b/usr/src/uts/common/io/nfp/drvlist.c new file mode 100644 index 0000000000..a04b1fd5b0 --- /dev/null +++ b/usr/src/uts/common/io/nfp/drvlist.c @@ -0,0 +1,19 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +#include "nfp_common.h" +#include "nfp_cmd.h" + +const nfpcmd_dev *nfp_drvlist[] = { + &i21285_cmddev, + &i21555_cmddev, + NULL +}; + diff --git a/usr/src/uts/common/io/nfp/hostif.c b/usr/src/uts/common/io/nfp/hostif.c new file mode 100644 index 0000000000..684be703ea --- /dev/null +++ b/usr/src/uts/common/io/nfp/hostif.c @@ -0,0 +1,1192 @@ +/* + +hostif.c: nFast PCI driver for Solaris 2.5, 2.6, 2.7 and 2.8 + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +06/05/1998 jsh Original solaris 2.6 +21/05/1999 jsh added support for solaris 2.5 +10/06/1999 jsh added support for solaris 2.7 (32 and 64 bit) +??/??/2001 jsh added support for solaris 2.8 (32 and 64 bit) +16/10/2001 jsh moved from nfast to new structure in nfdrv +12/02/2002 jsh added high level interrupt support + +*/ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/file.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <sys/map.h> +#include <sys/debug.h> +#include <sys/modctl.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/open.h> +#include <sys/stat.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/pci.h> + +#include "nfp_common.h" +#include "nfp_hostif.h" +#include "nfp_osif.h" +#include "nfp_cmd.h" + +#include "nfp.h" + +/* mapped memory attributes, no-swap endianess (done in higher level) */ +static struct ddi_device_acc_attr nosw_attr = { + DDI_DEVICE_ATTR_V0, + DDI_NEVERSWAP_ACC, + DDI_STRICTORDER_ACC +}; + +/* dma attributes */ +static ddi_dma_attr_t dma_attrs = { + DMA_ATTR_V0, /* version number */ + (uint64_t)0x0, /* low address */ + (uint64_t)0xffffffff, /* high address */ + (uint64_t)0xffffff, /* DMA counter max */ + (uint64_t)0x1, /* alignment */ + 0x0c, /* burst sizes */ + 0x1, /* minimum transfer size */ + (uint64_t)0x3ffffff, /* maximum transfer size */ + (uint64_t)0x7fff, /* maximum segment size */ + 1, /* no scatter/gather lists */ + 1, /* granularity */ + 0 /* DMA flags */ +}; + +/* + * Debug message control + * Debug Levels: + * 0 = no messages + * 1 = Errors + * 2 = Subroutine calls & control flow + * 3 = I/O Data (verbose!) + * Can be set with adb or in the /etc/system file with + * "set nfp:nfp_debug=<value>" + */ + +int nfp_debug= 1; + +static void *state_head; /* opaque handle top of state structs */ + +static int nfp_open(dev_t *dev, int openflags, int otyp, cred_t *credp); +static int nfp_close(dev_t dev, int openflags, int otyp, cred_t *credp); +static int nfp_release_dev( dev_info_t *dip ); + +static int nfp_read(dev_t dev, struct uio *uiop, cred_t *credp); +static int nfp_write(dev_t dev, struct uio *uiop, cred_t *credp); +static int nfp_strategy(struct buf *bp); + +static int nfp_ioctl(dev_t dev, int cmd, ioctlptr_t arg, int mode, cred_t *credp, int *rvalp); +static int nfp_chpoll(dev_t dev, short events, int anyyet, short *reventsp, + struct pollhead **phpp); + +static void nfp_wrtimeout (void *pdev); +static void nfp_rdtimeout (void *pdev); + +static int nfp_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); +static int nfp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static int nfp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); + +static void nfp_read_complete_final(nfp_dev *pdev, int ok); +static void nfp_write_complete_final(nfp_dev *pdev, int ok); + +/* nfp file ops --------------------------------------------------- */ + +static struct cb_ops nfp_cb_ops = { + nfp_open, + nfp_close, + nodev, /* no nfp_strategy */ + nodev, /* no print routine */ + nodev, /* no dump routine */ + nfp_read, + nfp_write, + nfp_ioctl, + nodev, /* no devmap routine */ + nodev, /* no mmap routine */ + nodev, /* no segmap routine */ + nfp_chpoll, + ddi_prop_op, + 0, /* not a STREAMS driver, no cb_str routine */ + D_NEW | D_MP | EXTRA_CB_FLAGS, /* must be safe for multi-thread/multi-processor */ + CB_REV, + nodev, /* aread */ + nodev /* awrite */ +}; + +static struct dev_ops nfp_ops = { + DEVO_REV, /* DEVO_REV indicated by manual */ + 0, /* device reference count */ + nfp_getinfo, + nulldev, /* identify */ + nulldev, /* probe */ + nfp_attach, + nfp_detach, + nodev, /* device reset routine */ + &nfp_cb_ops, + (struct bus_ops *)0, /* bus operations */ +}; + +extern struct mod_ops mod_driverops; +static struct modldrv modldrv = { + &mod_driverops, + NFP_DRVNAME, + &nfp_ops, +}; + +static struct modlinkage modlinkage = { + MODREV_1, /* MODREV_1 indicated by manual */ + (void *)&modldrv, + NULL, /* termination of list of linkage structures */ +}; + +/* interface resource allocation */ + +int nfp_alloc_pci_push( nfp_dev *pdev ) { + /* allocate resources needed for PCI Push, + * if not already allocated. + * return True if successful + */ + nfp_err ret; + uint_t cookie_count; + size_t real_length; + + if(!pdev->read_buf) { + /* allocate read buffer */ + pdev->read_buf = kmem_zalloc( NFP_READBUF_SIZE, KM_NOSLEEP ); + } + if(!pdev->read_buf) { + nfp_log( NFP_DBG1, "nfp_attach: kmem_zalloc read buffer failed"); + pdev->read_buf = NULL; + return 0; + } + + if(!pdev->rd_dma_ok) { + /* allocate dma handle for read buffer */ + ret = ddi_dma_alloc_handle( pdev->dip, + &dma_attrs, + DDI_DMA_DONTWAIT, + NULL, + &pdev->read_dma_handle ); + if( ret != DDI_SUCCESS ) { + nfp_log( NFP_DBG1, + "nfp_alloc_pci_push: ddi_dma_alloc_handle failed (%d)", + ret ); + return 0; + } + + /* Allocate the memory for dma transfers */ + ret = ddi_dma_mem_alloc(pdev->read_dma_handle, NFP_READBUF_SIZE, &nosw_attr, + DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, + (caddr_t*)&pdev->read_buf, &real_length, &pdev->acchandle); + if (ret != DDI_SUCCESS) { + nfp_log( NFP_DBG1, "nfp_alloc_pci_push: ddi_dma_mem_alloc failed (%d)", ret); + ddi_dma_free_handle( &pdev->read_dma_handle ); + return 0; + } + + ret = ddi_dma_addr_bind_handle( pdev->read_dma_handle, + NULL, /* kernel address space */ + (caddr_t)pdev->read_buf, real_length, + DDI_DMA_READ | DDI_DMA_CONSISTENT, /* dma flags */ + DDI_DMA_DONTWAIT, NULL, + &pdev->read_dma_cookie, &cookie_count ); + if( ret != DDI_DMA_MAPPED ) { + nfp_log( NFP_DBG1, + "nfp_alloc_pci_push: ddi_dma_addr_bind_handle failed (%d)", + ret); + ddi_dma_mem_free(&pdev->acchandle); + ddi_dma_free_handle( &pdev->read_dma_handle ); + return 0; + } + if( cookie_count > 1 ) { + nfp_log( NFP_DBG1, + "nfp_alloc_pci_push: error:" + " ddi_dma_addr_bind_handle wants %d transfers", + cookie_count); + ddi_dma_mem_free(&pdev->acchandle); + (void) ddi_dma_unbind_handle( pdev->read_dma_handle ); + ddi_dma_free_handle( &pdev->read_dma_handle ); + return 0; + } + pdev->rd_dma_ok = 1; + } + return pdev->rd_dma_ok; +} + +void nfp_free_pci_push( nfp_dev *pdev ) { + /* free resources allocated to PCI Push */ + if( pdev->rd_dma_ok ) { + (void) ddi_dma_sync(pdev->read_dma_handle,0,0,DDI_DMA_SYNC_FORKERNEL); + ddi_dma_mem_free(&pdev->acchandle); + (void) ddi_dma_unbind_handle( pdev->read_dma_handle ); + ddi_dma_free_handle( &pdev->read_dma_handle ); + pdev->rd_dma_ok = 0; + } + if( pdev->read_buf ) { + kmem_free( pdev->read_buf, NFP_READBUF_SIZE ); + pdev->read_buf = NULL; + } +} + +/* include definition of nfp_set_ifvers() */ +#define nfp_ifvers NFDEV_IF_PCI_PUSH +#include "nfp_ifvers.c" +#undef nfp_ifvers + +/*--------------------*/ +/* nfp_isr */ +/*--------------------*/ + +static u_int nfp_isr( char *pdev_in ) { + /* LINTED: alignment */ + nfp_dev *pdev= (nfp_dev *)pdev_in; + nfp_err ne; + int handled; + + nfp_log( NFP_DBG3, "nfp_isr: entered"); + + if( !pdev ) { + nfp_log( NFP_DBG1, "nfp_isr: cannot find dev"); + return DDI_INTR_UNCLAIMED; + } + + /* The isr needs to be mutex'ed - an SMP can call us while we're still + * running! + */ + mutex_enter(&pdev->low_mutex); + ne= pdev->cmddev->isr( pdev->common.cmdctx, &handled ); + mutex_exit(&pdev->low_mutex); + + if( !ne && handled ) + return DDI_INTR_CLAIMED; + if (ne) + nfp_log( NFP_DBG1, "nfp_isr: failed"); + else + nfp_log( NFP_DBG3, "nfp_isr: unclaimed"); + return DDI_INTR_UNCLAIMED; +} + +static u_int nfp_soft_isr( char *pdev_in ) { + /* LINTED: alignment */ + nfp_dev *pdev= (nfp_dev *)pdev_in; + int rd, wr; + + nfp_log( NFP_DBG3, "nfp_soft_isr: entered"); + + if( !pdev ) { + nfp_log( NFP_DBG1, "nfp_soft_isr: cannot find dev"); + return DDI_INTR_UNCLAIMED; + } + rd= wr= 0; + + mutex_enter(&pdev->high_mutex); + if(pdev->high_read) { + pdev->high_read= 0; + mutex_exit(&pdev->high_mutex); + rd= 1; + } + if(pdev->high_write) { + pdev->high_write= 0; + wr= 1; + } + mutex_exit(&pdev->high_mutex); + + if(rd) { + nfp_log( NFP_DBG3, "nfp_soft_isr: read done"); + nfp_read_complete_final(pdev, pdev->rd_ok); + } + if(wr) { + nfp_log( NFP_DBG3, "nfp_soft_isr: write done"); + nfp_write_complete_final(pdev, pdev->wr_ok); + } + if( rd || wr ) + return DDI_INTR_CLAIMED; + + nfp_log( NFP_DBG2, "nfp_isr: unclaimed"); + return DDI_INTR_UNCLAIMED; +} + + +/*-------------------------*/ +/* nfp_read */ +/*-------------------------*/ + +void nfp_read_complete(nfp_dev *pdev, int ok) { + nfp_log( NFP_DBG2,"nfp_read_complete: entering"); + + if(pdev->high_intr) { + nfp_log(NFP_DBG2, "nfp_read_complete: high_intr"); + mutex_enter(&pdev->high_mutex); + nfp_log(NFP_DBG3, "nfp_read_complete: high_mutex entered"); + if(pdev->high_read) + nfp_log(NFP_DBG1, "nfp_read_complete: high_read allread set!"); + pdev->high_read= 1; + pdev->rd_ok= ok; + nfp_log(NFP_DBG3, "nfp_read_complete: exiting high_mutex"); + mutex_exit(&pdev->high_mutex); + ddi_trigger_softintr(pdev->soft_int_id); + } else + nfp_read_complete_final( pdev, ok ); + nfp_log( NFP_DBG2,"nfp_read_complete: exiting"); +} + +static void nfp_read_complete_final(nfp_dev *pdev, int ok) { + nfp_log( NFP_DBG2,"nfp_read_complete_final: entering"); + if(pdev->rdtimeout) + (void) untimeout(pdev->rdtimeout); + if(!pdev->rd_outstanding) { + nfp_log( NFP_DBG1,"nfp_read_complete_final: !pdev->rd_outstanding"); + } + nfp_log( NFP_DBG2,"nfp_read_complete_final: pdev->rd_outstanding=0, ok %d", ok); + mutex_enter(&pdev->isr_mutex); + pdev->rd_outstanding= 0; + pdev->rd_ready= 1; + pdev->rd_ok= ok; + cv_broadcast(&pdev->rd_cv); + mutex_exit(&pdev->isr_mutex); + pollwakeup (&pdev->pollhead, POLLRDNORM); + nfp_log( NFP_DBG2,"nfp_read_complete_final: exiting"); +} + +static void nfp_rdtimeout( void *pdev_in ) +{ + nfp_dev *pdev= (nfp_dev *)pdev_in; + + nfp_log( NFP_DBG1, "nfp_rdtimeout: read timed out"); + + if (!pdev) { + nfp_log( NFP_DBG1, "nfp_rdtimeout: NULL pdev." ); + return; + } + pdev->rdtimeout= 0; + nfp_read_complete_final(pdev, 0); +} + +/* ARGSUSED */ +static int nfp_read(dev_t dev, struct uio *uiop, cred_t *credp) { + int ret; + nfp_log( NFP_DBG2, "nfp_read: entered" ); + if (ddi_get_soft_state(state_head, getminor(dev)) != NULL) { + nfp_log( NFP_DBG1, "nfp_read: unable to get nfp_dev"); + return (ENODEV); + } + nfp_log( NFP_DBG2, "nfp_read: about to physio." ); + ret = physio(nfp_strategy, (struct buf *)0, dev, B_READ, minphys, uiop ); + if(ret) + nfp_log( NFP_DBG1, "nfp_read: physio returned %x.", ret ); + return ret; +} + +/*-------------------------*/ +/* nfp_write */ +/*-------------------------*/ + +void nfp_write_complete( nfp_dev *pdev, int ok) { + nfp_log( NFP_DBG2,"nfp_write_complete: entering"); + + if(pdev->high_intr) { + mutex_enter(&pdev->high_mutex); + if(pdev->high_write) + nfp_log(NFP_DBG1, "nfp_write_complete: high_write allread set!"); + pdev->high_write= 1; + pdev->wr_ok= ok; + mutex_exit(&pdev->high_mutex); + ddi_trigger_softintr(pdev->soft_int_id); + } else + nfp_write_complete_final( pdev, ok ); + nfp_log( NFP_DBG2,"nfp_write_complete: exiting"); +} + +static void nfp_write_complete_final( nfp_dev *pdev, int ok) { + struct buf *local_wr_bp; + nfp_log( NFP_DBG2,"nfp_write_complete_final: entering"); + if(pdev->wrtimeout) + (void) untimeout(pdev->wrtimeout); + + if (!pdev->wr_bp) { + nfp_log( NFP_DBG2, "nfp_write_complete_final: write: wr_bp == NULL." ); + return; + } + + bp_mapout(pdev->wr_bp); + pdev->wr_bp->b_resid = ok ? 0 : pdev->wr_bp->b_bcount; + /* Make sure we set wr_ready before calling biodone to avoid a race */ + pdev->wr_ready = 1; + bioerror(pdev->wr_bp, ok ? 0 : ENXIO); + local_wr_bp = pdev->wr_bp; + pdev->wr_bp = 0; + biodone(local_wr_bp); + nfp_log( NFP_DBG2, "nfp_write_complete_final: isr_mutex extited"); + pollwakeup (&pdev->pollhead, POLLWRNORM); + + nfp_log( NFP_DBG2, "nfp_write_complete_final: leaving"); +} + +static void nfp_wrtimeout( void *pdev_in ) +{ + nfp_dev *pdev= (nfp_dev *)pdev_in; + + nfp_log( NFP_DBG1, "nfp_wrtimeout: write timed out"); + + if (!pdev) { + nfp_log( NFP_DBG1, "nfp_wrtimeout: NULL pdev." ); + return; + } + pdev->wrtimeout= 0; + nfp_write_complete_final(pdev, 0); +} + +/* ARGSUSED */ +static int nfp_write(dev_t dev, struct uio *uiop, cred_t *credp) { + int ret; + nfp_log( NFP_DBG2, "nfp_write: entered." ); + if (ddi_get_soft_state(state_head, getminor(dev)) == NULL) { + nfp_log( NFP_DBG1, "nfp_chread: unable to get nfp_dev."); + return (ENODEV); + } + nfp_log( NFP_DBG2, "nfp_write: about to physio." ); + ret = physio(nfp_strategy, (struct buf *)0, dev, B_WRITE, minphys, uiop ); + if(ret) + nfp_log( NFP_DBG1, "nfp_write: physio returned %x.", ret ); + return ret; +} + +/*-------------------------*/ +/* nfp_strategy */ +/*-------------------------*/ + +#define NFP_STRAT_ERR(thebp,err,txt) \ + nfp_log( NFP_DBG1, "nfp_strategy: " txt ".\n"); \ + (thebp)->b_resid = (thebp)->b_bcount; \ + bioerror ((thebp), err); \ + biodone ((thebp)); + +static int nfp_strategy(struct buf *bp) { + register struct nfp_dev *pdev; + nfp_err ne; + + nfp_log( NFP_DBG2, "nfp_strategy: entered." ); + if (!(pdev = ddi_get_soft_state(state_head, getminor(bp->b_edev)))) { + NFP_STRAT_ERR (bp, ENXIO, "unable to get nfp_dev"); + return (0); + } + + if (bp->b_flags & B_READ) { + int count; + /* read */ + if (!pdev->rd_ready) { + NFP_STRAT_ERR (bp,ENXIO,"read called when not ready"); + return (0); + } + pdev->rd_ready=0; + pdev->rd_pending = 0; + if( !pdev->rd_ok) { + NFP_STRAT_ERR (bp,ENXIO,"read failed"); + return (0); + } + /* copy data from module */ + if(pdev->ifvers >= NFDEV_IF_PCI_PUSH) { + nfp_log( NFP_DBG3, "nfp_strategy: copying kernel read buffer"); + if( ddi_dma_sync(pdev->read_dma_handle,0,0,DDI_DMA_SYNC_FORKERNEL) != DDI_SUCCESS ) + { + NFP_STRAT_ERR(bp,ENXIO,"ddi_dma_sync(read_dma_handle) failed"); + return (0); + } + /* LINTED: alignment */ + count= *(unsigned int *)(pdev->read_buf+4); + count= FROM_LE32_MEM(&count); + nfp_log( NFP_DBG3, "nfp_strategy: read count %d", count); + if(count<0 || count>bp->b_bcount) { + NFP_STRAT_ERR(bp,ENXIO,"bad read byte count from device"); + nfp_log( NFP_DBG1, "nfp_strategy: bad read byte count (%d) from device", count); + return (0); + } + bp_mapin (bp); + bcopy( pdev->read_buf + 8, bp->b_un.b_addr, count ); + bp_mapout (bp); + } else { + bp_mapin (bp); + ne= pdev->cmddev->read_block( bp->b_un.b_addr, bp->b_bcount, pdev->common.cmdctx, &count ); + bp_mapout (bp); + if( ne != NFP_SUCCESS) { + NFP_STRAT_ERR (bp,nfp_oserr(ne),"read_block failed"); + return (0); + } + } + bioerror(bp, 0); + bp->b_resid = 0; + biodone (bp); + } else { + /* write */ + if (!pdev->wr_ready) { + NFP_STRAT_ERR (bp,ENXIO,"write called when not ready"); + return (0); + } + if (pdev->wr_bp) { + NFP_STRAT_ERR (bp,ENXIO,"wr_bp != NULL"); + return (0); + } + pdev->wrtimeout= timeout(nfp_wrtimeout, (caddr_t)pdev, NFP_TIMEOUT_SEC * drv_usectohz(1000000)); + pdev->wr_bp = bp; + pdev->wr_ready = 0; + bp_mapin (bp); + ne= pdev->cmddev->write_block( bp->b_un.b_addr, bp->b_bcount, pdev->common.cmdctx); + if( ne != NFP_SUCCESS ) { + bp_mapout (bp); + (void) untimeout(pdev->wrtimeout); + pdev->wr_bp = 0; + pdev->wr_ready = 1; + NFP_STRAT_ERR (bp,nfp_oserr(ne),"write failed"); + return (0); + } + } + nfp_log( NFP_DBG2, "nfp_strategy: leaving"); + + return (0); +} + + +/*--------------------*/ +/* poll / select */ +/*--------------------*/ + +static int nfp_chpoll(dev_t dev, short events, int anyyet, short *reventsp, + struct pollhead **phpp) { + nfp_dev *pdev; + short revents; + + if (!(pdev = ddi_get_soft_state(state_head, getminor(dev)))) { + nfp_log( NFP_DBG1, "nfp_chpoll: unable to get nfp_dev"); + *reventsp=0; + return (0); + } + nfp_log( NFP_DBG2, "nfp_chpoll: entered %x", events); + + revents=0; + if (events&POLLWRNORM) { + if (pdev->wr_ready) { + nfp_log( NFP_DBG2, "nfp_chpoll: write ready"); + revents|=POLLWRNORM; + } + } + + if (events&POLLRDNORM) { + if (pdev->rd_ready) { + nfp_log( NFP_DBG2, "nfp_chpoll: read ready"); + revents|=POLLRDNORM; + } + } + + if (!revents && !anyyet) { + *phpp=&pdev->pollhead; + } + *reventsp=revents; + + nfp_log( NFP_DBG2, "nfp_chpoll: leaving"); + return (0); +} + + +/*--------------------*/ +/* ioctl */ +/*--------------------*/ + +/* ARGSUSED */ +static int nfp_ioctl(dev_t dev, int cmd, ioctlptr_t arg, int mode, cred_t *credp, int *rvalp) { + register struct nfp_dev *pdev; + + nfp_log( NFP_DBG2, "nfp_ioctl: entered." ); + + if (!(pdev = ddi_get_soft_state(state_head, getminor(dev)))) { + nfp_log( NFP_DBG1, "nfp_ioctl: unable to get nfp dev."); + return (ENXIO); + } + + switch (cmd) { + case NFDEV_IOCTL_ENQUIRY: + { + long *outp; + int outlen; + nfdev_enquiry_str enq_data; + + enq_data.busno = (unsigned int)-1; + enq_data.slotno = (unsigned char)-1; + + /* get our bus and slot num */ + if (ddi_getlongprop (DDI_DEV_T_NONE, + pdev->dip, 0, "reg", + (caddr_t)&outp, &outlen) != DDI_PROP_NOT_FOUND) { + nfp_log( NFP_DBG2, "ddi_getlongprop('reg') ok." ); + if( outlen > 0 ) { + enq_data.busno = ((*outp)>>16) & 0xff; + enq_data.slotno = ((*outp)>>11) & 0x1f; + nfp_log( NFP_DBG2, "busno %d, slotno %d.", + enq_data.busno, enq_data.slotno ); + } + } else + nfp_log( NFP_DBG1, "ddi_getlongprop('reg') failed." ); + + if( ddi_copyout( (char *)&enq_data, (void *)arg, sizeof(enq_data), mode ) != 0 ) { + nfp_log( NFP_DBG1, "ddi_copyout() failed." ); + return EFAULT; + } + } + break; + + case NFDEV_IOCTL_ENSUREREADING: + { + unsigned int addr, len; + nfp_err ret; + if( ddi_copyin( (void *)arg, (char *)&len, sizeof(unsigned int), mode ) != 0 ) { + nfp_log( NFP_DBG1, "ddi_copyin() failed." ); + return (EFAULT); + } + /* signal a read to the module */ + nfp_log( NFP_DBG2, "nfp_ioctl: signalling read request to module, len = %x.", len ); + if (len>8192) { + nfp_log( NFP_DBG1, "nfp_ioctl: len >8192 = %x.", len ); + return EINVAL; + } + if (pdev->rd_outstanding==1) { + nfp_log( NFP_DBG1, "nfp_ioctl: not about to call read with read outstanding."); + return EIO; + } + + addr= 0; + if(pdev->ifvers >= NFDEV_IF_PCI_PUSH) { + if( len > NFP_READBUF_SIZE ) { + nfp_log( NFP_DBG1, "nfp_ioctl: len > NFP_READBUF_SIZE = %x.", len ); + return EINVAL; + } + addr= pdev->read_dma_cookie.dmac_address; + } + + pdev->rd_outstanding = 1; + nfp_log( NFP_DBG2,"nfp_ioctl: pdev->rd_outstanding=1"); + + /* setup timeout timer */ + pdev->rdtimeout= timeout(nfp_rdtimeout, (caddr_t)pdev, NFP_TIMEOUT_SEC * drv_usectohz(1000000)); + + nfp_log( NFP_DBG2, "nfp_ioctl: read request"); + ret = pdev->cmddev->ensure_reading(addr, len, pdev->common.cmdctx); + if ( ret != NFP_SUCCESS ) { + (void) untimeout(pdev->rdtimeout); + pdev->rdtimeout = 0; + pdev->rd_outstanding = 0; + nfp_log( NFP_DBG1, "nfp_ioctl : cmddev->ensure_reading failed "); + return nfp_oserr( ret ); + } + } + break; + + case NFDEV_IOCTL_PCI_IFVERS: + { + int vers; + + nfp_log( NFP_DBG2, "nfp_ioctl: NFDEV_IOCTL_PCI_IFVERS"); + + if( ddi_copyin( (void *)arg, (char *)&vers, sizeof(vers), mode ) != 0 ) { + nfp_log( NFP_DBG1, "ddi_copyin() failed." ); + return (EFAULT); + } + + if( pdev->rd_outstanding ) { + nfp_log( NFP_DBG1, "nfp_ioctl: can't set ifvers %d as read outstanding", vers); + return EIO; + } + + nfp_set_ifvers(pdev, vers); + if( pdev->ifvers != vers ) { + nfp_log( NFP_DBG1, "nfp_ioctl: can't set ifvers %d", vers); + return EIO; + } + } + break; + + case NFDEV_IOCTL_STATS: + { + if( ddi_copyout( (char *)&(pdev->common.stats), + (void *)arg, + sizeof(nfdev_stats_str), + mode ) != 0 ) { + nfp_log( NFP_DBG1, "ddi_copyout() failed." ); + return EFAULT; + } + } + break; + + default: + nfp_log( NFP_DBG1, "nfp_ioctl: unknown ioctl." ); + return EINVAL; + } + + return 0; +} + +/*-------------------------*/ +/* nfp_open */ +/*-------------------------*/ + +/* ARGSUSED */ +int nfp_open(dev_t *dev, int openflags, int otyp, cred_t *credp) +{ + nfp_err ret; + register struct nfp_dev *pdev; + + nfp_log( NFP_DBG2, "entered nfp_open." ); + + pdev = (nfp_dev *)ddi_get_soft_state(state_head, getminor(*dev)); + + if( !pdev ) { + nfp_log( NFP_DBG1, "nfp_open: unable to get nfp dev."); + return (ENODEV); + } + + if( otyp != OTYP_CHR ) { + nfp_log( NFP_DBG1, "nfp_open: not opened as character device"); + return (EINVAL); + } + + mutex_enter(&pdev->busy_mutex); + + if (pdev->busy) { + mutex_exit(&pdev->busy_mutex); + nfp_log( NFP_DBG1, "nfp_open: device busy"); + return EBUSY; + } + pdev->busy= 1; + mutex_exit(&pdev->busy_mutex); + + /* use oldest possible interface until told otherwise */ + pdev->ifvers= NFDEV_IF_STANDARD; + nfp_log( NFP_DBG3, "nfp_open: setting ifvers %d", pdev->ifvers); + pdev->rd_ready= 0; /* drop any old data */ + + ret = pdev->cmddev->open(pdev->common.cmdctx); + if( ret != NFP_SUCCESS ) { + nfp_log( NFP_DBG1, "nfp_open : cmddev->open failed "); + return nfp_oserr( ret ); + } + + nfp_log( NFP_DBG2, "nfp_open: done"); + + return 0; +} + +/*--------------------*/ +/* nfp_close */ +/*--------------------*/ + +/* ARGSUSED */ +static int nfp_close(dev_t dev, int openflags, int otyp, cred_t *credp) { + nfp_dev *pdev; + nfp_err ret; + + nfp_log( NFP_DBG2, "nfp_close: entered"); + + pdev = (struct nfp_dev *)ddi_get_soft_state(state_head, getminor(dev)); + if( !pdev ) { + nfp_log( NFP_DBG1, "nfp_close: cannot find dev."); + return ENODEV; + } + + mutex_enter(&pdev->isr_mutex); + if(pdev->rd_outstanding) { + int lbolt, err; + nfp_get_lbolt(&lbolt, err); + if(!err) + (void) cv_timedwait(&pdev->rd_cv, &pdev->isr_mutex, lbolt + (NFP_TIMEOUT_SEC * drv_usectohz(1000000)) ); + } + mutex_exit(&pdev->isr_mutex); + ret = pdev->cmddev->close(pdev->common.cmdctx); + if (ret != NFP_SUCCESS ) { + nfp_log( NFP_DBG1, " nfp_close : cmddev->close failed"); + return nfp_oserr( ret ); + } + + mutex_enter(&pdev->busy_mutex); + pdev->busy= 0; + mutex_exit(&pdev->busy_mutex); + + return 0; +} + +/**************************************************************************** + + nfp driver config + + ****************************************************************************/ + +/*-------------------------*/ +/* nfp_getinfo */ +/*-------------------------*/ + +/* ARGSUSED */ +static int nfp_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { + int error; + nfp_dev *pdev; + + nfp_log( NFP_DBG2, "nfp_getinfo: entered" ); + + pdev = (struct nfp_dev *)ddi_get_soft_state(state_head, getminor((dev_t)arg)); + if( !pdev ) { + nfp_log( NFP_DBG1, "nfp_close: cannot find dev."); + return ENODEV; + } + + switch (infocmd) { + case DDI_INFO_DEVT2DEVINFO: + if (pdev == NULL) { + *result = NULL; + error = DDI_FAILURE; + } else { + /* + * don't need to use a MUTEX even though we are + * accessing our instance structure; dev->dip + * never changes. + */ + *result = pdev->dip; + error = DDI_SUCCESS; + } + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)(uintptr_t)getminor((dev_t)arg); + error = DDI_SUCCESS; + break; + default: + *result = NULL; + error = DDI_FAILURE; + } + + nfp_log( NFP_DBG2, "nfp_getinfo: leaving." ); + return (error); +} + +/*-------------------------*/ +/* nfp_release */ +/*-------------------------*/ + +static int nfp_release_dev( dev_info_t *dip ) { + nfp_dev *pdev; + int instance, i; + nfp_err ret; + + nfp_log( NFP_DBG2, "nfp_release_dev: entering" ); + + instance = ddi_get_instance(dip); + pdev = (struct nfp_dev *)ddi_get_soft_state(state_head, instance); + if (pdev) { + nfp_log( NFP_DBG3, "nfp_release_dev: removing device" ); + + nfp_free_pci_push(pdev); + + if( pdev->cmddev ) { + nfp_log( NFP_DBG3, "nfp_release_dev: destroying cmd dev" ); + ret = pdev->cmddev->destroy(pdev->common.cmdctx); + if (ret != NFP_SUCCESS) { + nfp_log( NFP_DBG1, " nfp_release_dev : cmddev->destroy failed "); + return nfp_oserr( ret ); + } + } + + if(pdev->high_iblock_cookie) { + nfp_log( NFP_DBG3, "nfp_release_dev: removing high and soft irq" ); + ddi_remove_softintr(pdev->soft_int_id); + ddi_remove_intr(pdev->dip, 0, pdev->high_iblock_cookie); + mutex_destroy( &pdev->busy_mutex ); + cv_destroy( &pdev->rd_cv ); + mutex_destroy( &pdev->isr_mutex ); + mutex_destroy( &pdev->high_mutex ); + } else if(pdev->iblock_cookie) { + nfp_log( NFP_DBG3, "nfp_release_dev: removing irq" ); + ddi_remove_intr(pdev->dip, 0, pdev->iblock_cookie); + mutex_destroy( &pdev->busy_mutex ); + cv_destroy( &pdev->rd_cv ); + mutex_destroy( &pdev->isr_mutex ); + } + if(pdev->low_iblock_cookie) { + ddi_remove_intr(pdev->dip, 0, pdev->low_iblock_cookie); + mutex_destroy( &pdev->low_mutex); + } + + for(i=0;i<6;i++) { + if( pdev->common.extra[i] ) { + nfp_log( NFP_DBG3, "nfp_release_dev: unmapping BAR %d", i ); + ddi_regs_map_free ((ddi_acc_handle_t *)&pdev->common.extra[i]); + } + } + + ddi_remove_minor_node(dip, NULL); + + if (pdev->conf_handle) + pci_config_teardown( &pdev->conf_handle ); + + ddi_soft_state_free(state_head, instance); + } + nfp_log( NFP_DBG2, "nfp_release: finished" ); + + return DDI_SUCCESS; +} + + +/*-------------------------*/ +/* nfp_attach */ +/*-------------------------*/ + +static int nfp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { + int instance; + nfp_dev *pdev = NULL; + int intres; + uint16_t device, vendor, sub_device, sub_vendor; + long *outp; + nfpcmd_dev const *cmddev; + int index, i; + nfp_err ret; + + nfp_log( NFP_DBG2, "nfp_attach: entered." ); + + if (cmd != DDI_ATTACH) { + nfp_log( NFP_DBG1, "nfp_attach: bad command." ); + goto bailout; + } + + instance = ddi_get_instance(dip); + + if (ddi_soft_state_zalloc(state_head, instance) != 0) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_soft_state_zalloc() failed." ); + goto bailout; + } + + pdev = (struct nfp_dev *)ddi_get_soft_state(state_head, instance); + if( !pdev ) { + nfp_log( NFP_DBG1, "nfp_attach: cannot find dev."); + return ENODEV; + } + pdev->dip = dip; + + /* map in pci config registers */ + if (pci_config_setup(dip, &pdev->conf_handle)) { + nfp_log( NFP_DBG1, "nfp_attach: pci_config_setup() failed." ); + goto bailout; + } + + /* find out what we have got */ + vendor= PCI_CONFIG_GET16( pdev->conf_handle, PCI_CONF_VENID ); + device = PCI_CONFIG_GET16( pdev->conf_handle, PCI_CONF_DEVID ); + sub_vendor = PCI_CONFIG_GET16( pdev->conf_handle, PCI_CONF_SUBVENID ); + sub_device = PCI_CONFIG_GET16( pdev->conf_handle, PCI_CONF_SUBSYSID ); + + index= 0; + while( (cmddev = nfp_drvlist[index++]) != NULL ) { + if( cmddev->vendorid == vendor && + cmddev->deviceid == device && + cmddev->sub_vendorid == sub_vendor && + cmddev->sub_deviceid == sub_device ) + break; + } + if( !cmddev ) { + nfp_log( NFP_DBG1, "nfp_attach: unknonw device." ); + goto bailout; + } + + /* map BARs */ + for( i=0; i<6; i++ ) { + if( cmddev->bar_sizes[i] ) { + off_t size; + if( ddi_dev_regsize(dip, i+1, &size) != DDI_SUCCESS) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_dev_regsize() failed for BAR %d", i ); + goto bailout; + } + if( size < (cmddev->bar_sizes[i] & ~NFP_MEMBAR_MASK) ) { + nfp_log( NFP_DBG1, "nfp_attach: BAR %d too small %x (%x)", i, size, (cmddev->bar_sizes[i] & ~0xF) ); + goto bailout; + } + if (ddi_regs_map_setup(dip, i+1, (caddr_t *)&pdev->common.bar[i], + 0, cmddev->bar_sizes[i] & ~NFP_MEMBAR_MASK, &nosw_attr, (ddi_acc_handle_t *)&pdev->common.extra[i] )) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_regs_map_setup() failed for BAR %d", i ); + goto bailout; + } + nfp_log( NFP_DBG3, "nfp_attach: BAR[%d] mapped to %x (%x)", i, pdev->common.bar[i], size ); + } + } + + pdev->read_buf = NULL; + pdev->rd_dma_ok = 0; + + /* attach to minor node */ + if (ddi_create_minor_node(dip, "nfp", S_IFCHR, instance, (char *)cmddev->name, 0) == DDI_FAILURE) { + ddi_remove_minor_node(dip, NULL); + nfp_log( NFP_DBG1, "nfp_attach: ddi_create_minor_node() failed." ); + goto bailout; + } + + pdev->wr_ready = 1; + pdev->rd_ready = 0; + pdev->rd_pending = 0; + pdev->rd_outstanding = 0; + pdev->busy=0; + pdev->cmddev= cmddev; + + ret = pdev->cmddev->create(&pdev->common); + if( ret != NFP_SUCCESS) { + nfp_log( NFP_DBG1, "nfp_attach: failed to create command device"); + goto bailout; + } + pdev->common.dev= pdev; + + if (ddi_intr_hilevel(dip, 0) != 0){ + nfp_log( NFP_DBG2, "nfp_attach: high-level interrupt"); + if( ddi_get_iblock_cookie(dip, 0, &pdev->high_iblock_cookie) ) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_get_iblock_cookie(high) failed." ); + goto bailout; + } + if( ddi_get_iblock_cookie(dip, 0, &pdev->low_iblock_cookie) ) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_get_iblock_cookie(low) failed." ); + goto bailout; + } + mutex_init(&pdev->high_mutex, NULL, MUTEX_DRIVER, + (void *)pdev->high_iblock_cookie); + mutex_init(&pdev->low_mutex, NULL, MUTEX_DRIVER, + (void *)pdev->low_iblock_cookie); + if (ddi_add_intr(dip, 0, NULL, + NULL, nfp_isr, + (caddr_t)pdev) != DDI_SUCCESS) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_add_intr(high) failed." ); + goto bailout; + } + if( ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_HIGH, + &pdev->iblock_cookie) ) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_get_iblock_cookie(soft) failed." ); + goto bailout; + } + mutex_init(&pdev->isr_mutex, NULL, MUTEX_DRIVER, + (void *)pdev->iblock_cookie); + if (ddi_add_softintr(dip, DDI_SOFTINT_HIGH, &pdev->soft_int_id, + &pdev->iblock_cookie, NULL, + nfp_soft_isr, (caddr_t)pdev) != DDI_SUCCESS) + goto bailout; + pdev->high_intr= 1; + } else { + nfp_log( NFP_DBG2, "nfp_attach: low-level interrupt"); + + if (ddi_get_iblock_cookie (dip, 0, &pdev->iblock_cookie)) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_get_iblock_cookie() failed." ); + goto bailout; + } + + mutex_init(&pdev->isr_mutex, "nfp isr mutex", MUTEX_DRIVER, (void *)pdev->iblock_cookie); + + if (ddi_add_intr(dip, 0, NULL, + (ddi_idevice_cookie_t *)NULL, nfp_isr, + (caddr_t)pdev) != DDI_SUCCESS) { + nfp_log( NFP_DBG1, "nfp_attach: ddi_add_intr() failed." ); + goto bailout; + } + } + mutex_init(&pdev->busy_mutex, "nfp busy mutex", MUTEX_DRIVER, NULL ); + cv_init(&pdev->rd_cv, "nfp read condvar", CV_DRIVER, NULL ); + + /* get our bus and slot num */ + if (ddi_getlongprop (DDI_DEV_T_NONE, + pdev->dip, 0, "reg", + (caddr_t)&outp, &intres) != DDI_PROP_NOT_FOUND) { + nfp_log( NFP_DBG2, "nfp_attach: ddi_getlongprop('reg') ok." ); + if( intres > 0 ) { + nfp_log( NFP_DBG1, "nfp_attach: found PCI nfast bus %x slot %x.", + ((*outp)>>16) & 0xff, ((*outp)>>11) & 0x1f ); + } + } + + nfp_log( NFP_DBG2, "nfp_attach: attach succeeded." ); + return DDI_SUCCESS; + +bailout: + (void) nfp_release_dev( dip ); + + return DDI_FAILURE; +} + +/*-------------------------*/ +/* nfp_detach */ +/*-------------------------*/ + +/* + * When our driver is unloaded, nfp_detach cleans up and frees the resources + * we allocated in nfp_attach. + */ +static int nfp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { + if (cmd != DDI_DETACH) + return (DDI_FAILURE); + + (void) nfp_release_dev(dip); + + return (DDI_SUCCESS); +} + +/*-------------------------*/ +/* _init */ +/*-------------------------*/ + +int _init(void) { + register int error; + + nfp_log( NFP_DBG2, "_init: entered" ); + + if ((error = ddi_soft_state_init(&state_head, sizeof (struct nfp_dev), 1)) != 0) { + nfp_log( NFP_DBG1, "_init: soft_state_init() failed" ); + return (error); + } + + if ((error = mod_install(&modlinkage)) != 0) { + nfp_log( NFP_DBG1, "_init: mod_install() failed" ); + ddi_soft_state_fini(&state_head); + } + + nfp_log( NFP_DBG2, "_init: leaving" ); + return (error); +} + +/*-------------------------*/ +/* _info */ +/*-------------------------*/ + +int _info(struct modinfo *modinfop) { + nfp_log( NFP_DBG2, "_info: entered" ); + + return (mod_info(&modlinkage, modinfop)); +} + +/*-------------------------*/ +/* _fini */ +/*-------------------------*/ + +int _fini(void) { + int status; + + nfp_log( NFP_DBG2, "_fini: entered" ); + + if ((status = mod_remove(&modlinkage)) != 0) { + nfp_log( NFP_DBG2, "_fini: mod_remove() failed." ); + return (status); + } + + ddi_soft_state_fini(&state_head); + + nfp_log( NFP_DBG2, "_fini: leaving" ); + + return (status); +} + diff --git a/usr/src/uts/common/io/nfp/i21285.c b/usr/src/uts/common/io/nfp/i21285.c new file mode 100644 index 0000000000..f51a09188d --- /dev/null +++ b/usr/src/uts/common/io/nfp/i21285.c @@ -0,0 +1,310 @@ +/* + +i21285.c: nCipher PCI HSM intel/digital 21285 command driver + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + + +history + +09/10/2001 jsh Original + +*/ + +#include "nfp_common.h" +#include "nfp_error.h" +#include "nfp_hostif.h" +#include "nfp_osif.h" +#include "i21285.h" +#include "nfp_cmd.h" +#include "nfpci.h" + +/* create ------------------------------------------------------- */ + +static nfp_err i21285_create( nfp_cdev *pdev ) { + unsigned int tmp32; + + nfp_log( NFP_DBG2, "i21285_create: entered"); + pdev->cmdctx= pdev; /* set our context to just be a pointer to our nfp_cdev */ + + nfp_log( NFP_DBG2, "i21285_create: enable doorbell"); + if(!pdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21285_create: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + TO_LE32_IO( &tmp32, DOORBELL_ENABLE | POSTLIST_ENABLE); + nfp_outl( pdev, IOBAR, I21285_OFFSET_INTERRUPT_MASK, tmp32 ); + + return NFP_SUCCESS; +} + +/* stop ------------------------------------------------------- */ + +static nfp_err i21285_destroy( void * ctx ) { + nfp_cdev *pdev; + unsigned int tmp32; + + nfp_log( NFP_DBG2, "i21285_destroy: entered"); + + pdev= (nfp_cdev *)ctx; + if(!pdev) { + nfp_log( NFP_DBG1, "i21285_destroy: NULL pdev"); + return NFP_ENODEV; + } + if(!pdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21285_destroy: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + TO_LE32_IO( &tmp32, DOORBELL_DISABLE | POSTLIST_DISABLE ); + nfp_outl( pdev, IOBAR, I21285_OFFSET_INTERRUPT_MASK, tmp32 ); + + return NFP_SUCCESS; +} + +/* open ------------------------------------------------------- */ + +/* ARGSUSED */ +static nfp_err i21285_open( void * ctx ) { + nfp_log( NFP_DBG2, "i21285_open: entered"); + + return NFP_SUCCESS; +} + +/* close ------------------------------------------------------- */ + +/* ARGSUSED */ +static nfp_err i21285_close( void * ctx ) { + nfp_log( NFP_DBG2, "i21285_close: entered"); + + return NFP_SUCCESS; +} + +/* isr ------------------------------------------------------- */ + +static nfp_err i21285_isr( void *ctx, int *handled ) { + nfp_cdev *pdev; + unsigned int doorbell; + unsigned int tmp32; + + nfp_log( NFP_DBG3, "i21285_isr: entered"); + + *handled= 0; + pdev= (nfp_cdev *)ctx; + if(!pdev) { + nfp_log( NFP_DBG1, "i21285_isr: NULL pdev"); + return NFP_ENODEV; + } + + doorbell= nfp_inl( pdev, IOBAR, I21285_OFFSET_DOORBELL); + doorbell= FROM_LE32_IO(&doorbell) & 0xffff; + while( doorbell && doorbell != 0xffff) { + *handled= 1; + /* service interrupts */ + if( doorbell & (NFAST_INT_DEVICE_WRITE_OK | NFAST_INT_DEVICE_WRITE_FAILED)) { + TO_LE32_IO( &tmp32, NFAST_INT_DEVICE_WRITE_OK | NFAST_INT_DEVICE_WRITE_FAILED); + nfp_outl( pdev, IOBAR, I21285_OFFSET_DOORBELL, tmp32 ); + + nfp_log(NFP_DBG2, "i21285_isr: write done interrupt, ok = %d.", doorbell & NFAST_INT_DEVICE_WRITE_OK ? 1 : 0 ); + + nfp_write_complete(pdev->dev, doorbell & NFAST_INT_DEVICE_WRITE_OK ? 1 : 0 ); + } + + if( doorbell & (NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED)) { + TO_LE32_IO( &tmp32, NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED ); + nfp_outl( pdev, IOBAR, I21285_OFFSET_DOORBELL, tmp32 ); + + nfp_log(NFP_DBG2, "i21285_isr: read ack interrupt, ok = %d.", doorbell & NFAST_INT_DEVICE_READ_OK ? 1 : 0 ); + nfp_read_complete( pdev->dev, doorbell & NFAST_INT_DEVICE_READ_OK ? 1 : 0); + } + + if( doorbell & ~(NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED | + NFAST_INT_DEVICE_WRITE_OK | NFAST_INT_DEVICE_WRITE_FAILED)) { + nfp_log( NFP_DBG1, "i21285_isr: unexpected interrupt %x", doorbell ); + TO_LE32_IO( &tmp32, 0xffff & doorbell ); + nfp_outl( pdev, IOBAR, I21285_OFFSET_DOORBELL, tmp32 ); + } + doorbell= nfp_inl( pdev, IOBAR, I21285_OFFSET_DOORBELL); + doorbell= FROM_LE32_IO(&doorbell) & 0xffff; + } + return 0; +} + +/* write ------------------------------------------------------- */ + +static nfp_err i21285_write( const char *block, int len, void *ctx ) { + nfp_cdev *cdev; + unsigned int hdr[2]; + nfp_err ne; + unsigned int tmp32; + + nfp_log( NFP_DBG2, "i21285_write: entered"); + + cdev= (nfp_cdev *)ctx; + if(!cdev) { + nfp_log( NFP_DBG1, "i21285_write: NULL pdev"); + return NFP_ENODEV; + } + + nfp_log(NFP_DBG2, "i21285_write: pdev->bar[ MEMBAR ]= %x\n", cdev->bar[ MEMBAR ]); + nfp_log(NFP_DBG2, "i21285_write: pdev->bar[ IOBAR ]= %x\n", cdev->bar[ IOBAR ]); + if(!cdev->bar[ MEMBAR ]) { + nfp_log( NFP_DBG1, "i21285_write: null BAR[%d]", MEMBAR ); + return NFP_ENOMEM; + } + ne= nfp_copy_from_user_to_dev( cdev, MEMBAR, NFPCI_JOBS_WR_DATA, block, len); + if (ne) { + nfp_log( NFP_DBG1, "i21285_write: nfp_copy_from_user_to_dev failed"); + return ne; + } + TO_LE32_MEM(&hdr[0], NFPCI_JOB_CONTROL); + TO_LE32_MEM(&hdr[1], len); + + ne= nfp_copy_to_dev( cdev, MEMBAR, NFPCI_JOBS_WR_CONTROL, (const char *)hdr, 8); + if (ne) { + nfp_log( NFP_DBG1, "i21285_write: nfp_copy_to_dev failed"); + return ne; + } + + ne= nfp_copy_from_dev( cdev, MEMBAR, NFPCI_JOBS_WR_LENGTH, (char *)hdr, 4); + if (ne) { + nfp_log( NFP_DBG1, "i21285_write: nfp_copy_from_dev failed"); + return ne; + } + + TO_LE32_MEM( &tmp32, len ); + if ( hdr[0] != tmp32 ) { + nfp_log( NFP_DBG1, "i21285_write: length not written"); + return NFP_EIO; + } + + TO_LE32_IO( &tmp32, NFAST_INT_HOST_WRITE_REQUEST); + + nfp_outl( cdev, IOBAR, I21285_OFFSET_DOORBELL, tmp32 ); + + nfp_log( NFP_DBG2, "i21285_write: done"); + return NFP_SUCCESS; +} + +/* read ------------------------------------------------------- */ + +static nfp_err i21285_read( char *block, int len, void *ctx, int *rcount) { + nfp_cdev *cdev; + nfp_err ne; + int count; + + nfp_log( NFP_DBG2, "i21285_read: entered, len %d", len); + *rcount= 0; + + cdev= (nfp_cdev *)ctx; + if(!cdev) { + nfp_log( NFP_DBG1, "i21285_read: NULL pdev"); + return NFP_ENODEV; + } + + if(!cdev->bar[ MEMBAR ]) { + nfp_log( NFP_DBG1, "i21285_read: null BAR[%d]", MEMBAR ); + return NFP_ENOMEM; + } + ne= nfp_copy_from_dev( cdev, MEMBAR, NFPCI_JOBS_RD_LENGTH, (char *)&count, 4); + if(ne) { + nfp_log( NFP_DBG1, "i21285_read: nfp_copy_from_dev failed."); + return ne; + } + count= FROM_LE32_MEM(&count); + if(count<0 || count>len) { + nfp_log( NFP_DBG1, "i21285_read: bad byte count (%d) from device", count); + return NFP_EIO; + } + ne= nfp_copy_to_user_from_dev( cdev, MEMBAR, NFPCI_JOBS_RD_DATA, block, count); + if( ne ) { + nfp_log( NFP_DBG1, "i21285_read: nfp_copy_to_user_from_dev failed."); + return ne; + } + nfp_log( NFP_DBG2, "i21285_read: done"); + *rcount= count; + return NFP_SUCCESS; +} + +/* chupdate ------------------------------------------------------- */ + +/* ARGSUSED */ +static nfp_err i21285_chupdate( char *data, int len, void *ctx ) { + nfp_log( NFP_DBG1, "i21285_chupdate: NYI"); + return NFP_SUCCESS; +} + +/* ensure reading -------------------------------------------------- */ + +static nfp_err i21285_ensure_reading( unsigned int addr, int len, void *ctx ) { + nfp_cdev *cdev; + unsigned int hdr[2]; + unsigned int tmp32; + nfp_err ne; + + nfp_log( NFP_DBG2, "i21285_ensure_reading: entered"); + + if(addr) { + nfp_log( NFP_DBG2, "i21285_ensure_reading: bad addr"); + return -NFP_EINVAL; + } + + cdev= (nfp_cdev *)ctx; + if(!cdev) { + nfp_log( NFP_DBG1, "i21285_ensure_reading: NULL pdev"); + return NFP_ENODEV; + } + + if(!cdev->bar[ MEMBAR ]) { + nfp_log( NFP_DBG1, "i21285_ensure_reading: null BAR[%d]", MEMBAR ); + return NFP_ENXIO; + } + nfp_log( NFP_DBG3, "i21285_ensure_reading: pdev->bar[ MEMBAR ]= %x", cdev->bar[ MEMBAR ]); + nfp_log( NFP_DBG3, "i21285_ensure_reading: pdev->bar[ IOBAR ]= %x", cdev->bar[ IOBAR ]); + TO_LE32_MEM( &hdr[0], NFPCI_JOB_CONTROL); + TO_LE32_MEM( &hdr[1], len); + ne= nfp_copy_to_dev( cdev, MEMBAR, NFPCI_JOBS_RD_CONTROL, (const char *)hdr, 8); + if (ne) { + nfp_log( NFP_DBG1, "i21285_ensure_reading: nfp_copy_to_dev failed"); + return ne; + } + ne= nfp_copy_from_dev( cdev, MEMBAR, NFPCI_JOBS_RD_LENGTH, (char *)hdr, 4); + if (ne) { + nfp_log( NFP_DBG1, "i21285_ensure_reading: nfp_copy_from_dev failed"); + return ne; + } + TO_LE32_MEM( &tmp32, len ); + if ( hdr[0] != tmp32 ) { + nfp_log( NFP_DBG1, "i21285_ensure_reading: len not written"); + return NFP_EIO; + }; + TO_LE32_IO( &tmp32, NFAST_INT_HOST_READ_REQUEST ); + nfp_outl( cdev, IOBAR, I21285_OFFSET_DOORBELL, tmp32 ); + + return NFP_SUCCESS; +} + +/* command device structure ------------------------------------- */ + + +const nfpcmd_dev i21285_cmddev = { + "nCipher Gen 1 PCI", + PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21285, + PCI_VENDOR_ID_NCIPHER, PCI_DEVICE_ID_NFAST_GEN1, + { 0, IOSIZE | PCI_BASE_ADDRESS_SPACE_IO, NFPCI_RAM_MINSIZE, 0, 0, 0 }, + NFP_CMD_FLG_NEED_IOBUF, + i21285_create, + i21285_destroy, + i21285_open, + i21285_close, + i21285_isr, + i21285_write, + i21285_read, + i21285_chupdate, + i21285_ensure_reading, + 0, /* no debug */ +}; + diff --git a/usr/src/uts/common/io/nfp/i21285.h b/usr/src/uts/common/io/nfp/i21285.h new file mode 100644 index 0000000000..4ea1d853ec --- /dev/null +++ b/usr/src/uts/common/io/nfp/i21285.h @@ -0,0 +1,43 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +#ifndef NFP_I21285_H +#define NFP_I21285_H + +#ifndef PCI_VENDOR_ID_DEC +#define PCI_VENDOR_ID_DEC 0x1011 +#endif +#ifndef PCI_DEVICE_ID_DEC_21285 +#define PCI_DEVICE_ID_DEC_21285 0x1065 +#endif +#ifndef PCI_VENDOR_ID_NCIPHER +#define PCI_VENDOR_ID_NCIPHER 0x0100 +#endif + +#ifndef PCI_DEVICE_ID_NFAST_GEN1 +#define PCI_DEVICE_ID_NFAST_GEN1 0x0100 +#endif + +#define I21285_OFFSET_DOORBELL 0x60 +#define I21285_OFFSET_INTERRUPT_MASK 0x34 + +#define DOORBELL_ENABLE 0x0 +#define DOORBELL_DISABLE 0x4 + +#define POSTLIST_ENABLE 0x0 +#define POSTLIST_DISABLE 0x8 + +#define IOBAR 1 +#define MEMBAR 2 + +#define IOSIZE 0x80 +#define MEMSIZE 0x100000 + +#endif diff --git a/usr/src/uts/common/io/nfp/i21555.c b/usr/src/uts/common/io/nfp/i21555.c new file mode 100644 index 0000000000..82024dc800 --- /dev/null +++ b/usr/src/uts/common/io/nfp/i21555.c @@ -0,0 +1,423 @@ +/* + +i21555.c: nCipher PCI HSM intel 21555 command driver + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +09/10/2001 jsh Original + +*/ + +#include "nfp_common.h" +#include "nfp_error.h" +#include "nfp_hostif.h" +#include "nfp_osif.h" +#include "i21555.h" +#include "nfp_cmd.h" +#include "nfpci.h" + +/* started ------------------------------------------------------ + * + * Check that device is ready to talk, by checking that + * the i21555 has master enabled on its secondary interface + */ + +static nfp_err i21555_started( nfp_cdev *pdev ) { + unsigned int tmp32; +#ifdef CONFIGSPACE_DEBUG + unsigned int reg32[64]; + int i; +#endif + nfp_err ne; + + nfp_log( NFP_DBG2, "i21555_started: entered"); + +#ifdef CONFIGSPACE_DEBUG + /* Suck up all the registers */ + for (i=0; i < 64; i++) { + ne = nfp_config_inl( pdev, i*4, ®32[i] ); + } + + for (i=0; i < 16; i++) { + int j = i * 4; + nfp_log( NFP_DBG3, "i21555 config reg %2x: %08x %08x %08x %08x", j*4, + reg32[j], reg32[j+1], reg32[j+2], reg32[j+3]); + } +#endif + + ne = nfp_config_inl( pdev, I21555_CFG_SEC_CMD_STATUS, &tmp32 ); + if (ne) { + /* succeed if PCI config reads are not implemented */ + if (ne == NFP_EUNKNOWN) + return NFP_SUCCESS; + nfp_log( NFP_DBG1, "i21555_started: nfp_config_inl failed"); + return ne; + } + + tmp32= FROM_LE32_IO(&tmp32) & 0xffff; + + if ( tmp32 & CFG_CMD_MASTER ) { + nfp_log( NFP_DBG3, "i21555_started: Yes %x", tmp32); + return NFP_SUCCESS; + } else { + nfp_log( NFP_DBG1, "i21555_started: device not started yet %x", tmp32); + return NFP_ESTARTING; + } +} + +/* create ------------------------------------------------------- */ + +static nfp_err i21555_create( nfp_cdev *pdev ) { + unsigned int tmp32; + + nfp_log( NFP_DBG2, "i21555_create: entered"); + pdev->cmdctx= pdev; /* set our context to just be a pointer to our nfp_cdev */ + + if(!pdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21555_create: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + nfp_log( NFP_DBG2, "i21555_create: enable doorbell"); + TO_LE32_IO( &tmp32, I21555_DOORBELL_PRI_ENABLE ); + nfp_outl( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_SET_MASK, tmp32 ); + nfp_outl( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_CLEAR_MASK, tmp32 ); + return NFP_SUCCESS; +} + +/* stop ------------------------------------------------------- */ + +static nfp_err i21555_destroy( void * ctx ) { + nfp_cdev *pdev; + unsigned int tmp32; + + nfp_log( NFP_DBG2, "i21555_destroy: entered"); + + pdev= (nfp_cdev *)ctx; + if(!pdev) { + nfp_log( NFP_DBG1, "i21555_destroy: NULL pdev"); + return NFP_ENODEV; + } + if(!pdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21555_destroy: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + TO_LE32_IO( &tmp32, I21555_DOORBELL_PRI_DISABLE ); + nfp_outl( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_SET_MASK, tmp32 ); + nfp_outl( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_CLEAR_MASK, tmp32 ); + + return NFP_SUCCESS; +} + +/* open ------------------------------------------------------- */ + +/* ARGSUSED */ +static nfp_err i21555_open( void * ctx ) { + + nfp_log( NFP_DBG2, "i21555_open: entered"); + + return NFP_SUCCESS; +} + +/* close ------------------------------------------------------- */ + +/* ARGSUSED */ +static nfp_err i21555_close( void * ctx ) { + nfp_log( NFP_DBG2, "i21555_close: entered"); + + return NFP_SUCCESS; +} + +/* isr ------------------------------------------------------- */ + +static nfp_err i21555_isr( void *ctx, int *handled ) { + nfp_cdev *pdev; + nfp_err ne; + unsigned short doorbell; + unsigned short tmp16; + + nfp_log( NFP_DBG3, "i21555_isr: entered"); + + *handled= 0; + pdev= (nfp_cdev *)ctx; + if(!pdev) { + nfp_log( NFP_DBG1, "i21555_isr: NULL pdev"); + return NFP_ENODEV; + } + + pdev->stats.isr++; + + if(!pdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21555_isr: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + + /* This interrupt may not be from our module, so check that it actually is + * us before handling it. + */ + ne = i21555_started( pdev ); + if (ne) { + if (ne != NFP_ESTARTING) { + nfp_log( NFP_DBG1, "i21555_isr: i21555_started failed"); + } + return ne; + } + + doorbell= nfp_inw( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_SET); + doorbell= FROM_LE16_IO(&doorbell); + while( doorbell && doorbell != 0xffff) { + *handled= 1; + /* service interrupts */ + if( doorbell & (NFAST_INT_DEVICE_WRITE_OK | NFAST_INT_DEVICE_WRITE_FAILED)) { + pdev->stats.isr_write++; + TO_LE16_IO(&tmp16,NFAST_INT_DEVICE_WRITE_OK | NFAST_INT_DEVICE_WRITE_FAILED); + nfp_outw( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_CLEAR, tmp16 ); + + nfp_log( NFP_DBG2, "i21555_isr: write done interrupt, ok = %d.", doorbell & NFAST_INT_DEVICE_WRITE_OK ? 1 : 0 ); + + nfp_write_complete(pdev->dev, doorbell & NFAST_INT_DEVICE_WRITE_OK ? 1 : 0 ); + } + + if( doorbell & (NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED)) { + pdev->stats.isr_read++; + TO_LE16_IO(&tmp16,NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED); + nfp_outw( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_CLEAR, tmp16 ); + + nfp_log( NFP_DBG2, "i21555_isr: read ack interrupt, ok = %d.", doorbell & NFAST_INT_DEVICE_READ_OK ? 1 : 0 ); + nfp_read_complete( pdev->dev, doorbell & NFAST_INT_DEVICE_READ_OK ? 1 : 0); + } + + if( doorbell & ~(NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED | + NFAST_INT_DEVICE_WRITE_OK | NFAST_INT_DEVICE_WRITE_FAILED)) { + TO_LE16_IO(&tmp16,doorbell); + nfp_outw( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_CLEAR, tmp16 ); + nfp_log( NFP_DBG1, "i21555_isr: unexpected interrupt %x", doorbell ); + } + doorbell= nfp_inw( pdev, IOBAR, I21555_OFFSET_DOORBELL_PRI_SET); + doorbell= FROM_LE16_IO(&doorbell); + } + nfp_log( NFP_DBG3, "i21555_isr: exiting"); + return 0; +} + +/* write ------------------------------------------------------- */ + +static nfp_err i21555_write( const char *block, int len, void *ctx) { + nfp_cdev *cdev; + unsigned int hdr[2]; + nfp_err ne; + unsigned short tmp16; + unsigned int tmp32; + + nfp_log( NFP_DBG2, "i21555_write: entered"); + + cdev= (nfp_cdev *)ctx; + if(!cdev) { + nfp_log( NFP_DBG1, "i21555_write: NULL cdev"); + return NFP_ENODEV; + } + + cdev->stats.write_fail++; + + if(!cdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21555_write: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + + ne = i21555_started( cdev ); + if (ne) { + if (ne != NFP_ESTARTING) { + nfp_log( NFP_DBG1, "i21555_write: i21555_started failed"); + } + return ne; + } + + nfp_log( NFP_DBG3, "i21555_write: cdev->bar[ MEMBAR ]= %x", cdev->bar[ MEMBAR ]); + nfp_log( NFP_DBG3, "i21555_write: cdev->bar[ IOBAR ]= %x", cdev->bar[ IOBAR ]); + nfp_log( NFP_DBG3, "i21555_write: block len %d", len ); + ne= nfp_copy_from_user_to_dev( cdev, MEMBAR, NFPCI_JOBS_WR_DATA, block, len); + if (ne) { + nfp_log( NFP_DBG1, "i21555_write: nfp_copy_from_user_to_dev failed"); + return ne; + } + TO_LE32_MEM(&hdr[0], NFPCI_JOB_CONTROL); + TO_LE32_MEM(&hdr[1], len); + ne= nfp_copy_to_dev( cdev, MEMBAR, NFPCI_JOBS_WR_CONTROL, (const char *)hdr, 8); + if (ne) { + nfp_log( NFP_DBG1, "i21555_write: nfp_copy_to_dev failed"); + return ne; + } + + ne= nfp_copy_from_dev( cdev, MEMBAR, NFPCI_JOBS_WR_LENGTH, (char *)hdr, 4); + if (ne) { + nfp_log( NFP_DBG1, "i21555_write: nfp_copy_from_dev failed"); + return ne; + } + + TO_LE32_MEM(&tmp32, len); + if ( hdr[0] != tmp32 ) { + nfp_log( NFP_DBG1, "i21555_write: length not written"); + return NFP_EIO; + } + TO_LE16_IO(&tmp16, NFAST_INT_HOST_WRITE_REQUEST >> 16); + nfp_outw( cdev, IOBAR, I21555_OFFSET_DOORBELL_SEC_SET, tmp16); + + cdev->stats.write_fail--; + cdev->stats.write_block++; + cdev->stats.write_byte += len; + + nfp_log( NFP_DBG2, "i21555_write: done"); + return NFP_SUCCESS; +} + +/* read ------------------------------------------------------- */ + +static nfp_err i21555_read( char *block, int len, void *ctx, int *rcount) { + nfp_cdev *cdev; + nfp_err ne; + int count; + + nfp_log( NFP_DBG2, "i21555_read: entered"); + *rcount= 0; + + cdev= (nfp_cdev *)ctx; + if(!cdev) { + nfp_log( NFP_DBG1, "i21555_read: NULL pdev"); + return NFP_ENODEV; + } + + cdev->stats.read_fail++; + + if(!cdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21555_read: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + + ne= nfp_copy_from_dev( cdev, MEMBAR, NFPCI_JOBS_RD_LENGTH, (char *)&count, 4); + if (ne) { + nfp_log( NFP_DBG1, "i21555_read: nfp_copy_from_dev failed."); + return ne; + } + count= FROM_LE32_MEM(&count); + if(count<0 || count>len) { + nfp_log( NFP_DBG1, "i21555_read: bad byte count (%d) from device", count); + return NFP_EIO; + } + ne= nfp_copy_to_user_from_dev( cdev, MEMBAR, NFPCI_JOBS_RD_DATA, block, count); + if (ne) { + nfp_log( NFP_DBG1, "i21555_read: nfp_copy_to_user failed."); + return ne; + } + nfp_log( NFP_DBG2, "i21555_read: done"); + *rcount= count; + cdev->stats.read_fail--; + cdev->stats.read_block++; + cdev->stats.read_byte += len; + return NFP_SUCCESS; +} + +/* chupdate ------------------------------------------------------- */ + +/* ARGSUSED */ +static nfp_err i21555_chupdate( char *data, int len, void *ctx ) { + nfp_log( NFP_DBG1, "i21555_chupdate: NYI"); + return NFP_SUCCESS; +} + +/* ensure reading -------------------------------------------------- */ + +static nfp_err i21555_ensure_reading( unsigned int addr, int len, void *ctx ) { + nfp_cdev *cdev; + unsigned int hdr[3]; + unsigned short tmp16; + unsigned int tmp32; + nfp_err ne; + int hdr_len; + + nfp_log( NFP_DBG2, "i21555_ensure_reading: entered"); + + cdev= (nfp_cdev *)ctx; + if(!cdev) { + nfp_log( NFP_DBG1, "i21555_ensure_reading: NULL pdev"); + return NFP_ENODEV; + } + + cdev->stats.ensure_fail++; + + if(!cdev->bar[ IOBAR ]) { + nfp_log( NFP_DBG1, "i21555_ensure_reading: null BAR[%d]", IOBAR ); + return NFP_ENOMEM; + } + + ne = i21555_started( cdev ); + if (ne) { + if (ne != NFP_ESTARTING) { + nfp_log( NFP_DBG1, "i21555_ensure_reading: i21555_started failed"); + } + return ne; + } + + nfp_log( NFP_DBG3, "i21555_ensure_reading: pdev->bar[ MEMBAR ]= %x", cdev->bar[ MEMBAR ]); + nfp_log( NFP_DBG3, "i21555_ensure_reading: pdev->bar[ IOBAR ]= %x", cdev->bar[ IOBAR ]); + if(addr) { + nfp_log( NFP_DBG3, "i21555_ensure_reading: new format, addr %x", addr); + TO_LE32_MEM(&hdr[0], NFPCI_JOB_CONTROL_PCI_PUSH); + TO_LE32_MEM(&hdr[1], len); + TO_LE32_MEM(&hdr[2], addr); + hdr_len= 12; + } else { + TO_LE32_MEM(&hdr[0], NFPCI_JOB_CONTROL); + TO_LE32_MEM(&hdr[1], len); + hdr_len= 8; + } + ne= nfp_copy_to_dev( cdev, MEMBAR, NFPCI_JOBS_RD_CONTROL, (const char *)hdr, hdr_len); + if (ne) { + nfp_log( NFP_DBG1, "i21555_ensure_reading: nfp_copy_to_dev failed"); + return ne; + } + + ne= nfp_copy_from_dev( cdev, MEMBAR, NFPCI_JOBS_RD_LENGTH, (char *)hdr, 4); + if (ne) { + nfp_log( NFP_DBG1, "i21555_ensure_reading: nfp_copy_from_dev failed"); + return ne; + } + + TO_LE32_MEM(&tmp32, len); + + if ( hdr[0] != tmp32 ) { + nfp_log( NFP_DBG1, "i21555_ensure_reading: len not written"); + return NFP_EIO; + } + TO_LE16_IO( &tmp16, NFAST_INT_HOST_READ_REQUEST >> 16); + nfp_outw( cdev, IOBAR, I21555_OFFSET_DOORBELL_SEC_SET, tmp16); + + cdev->stats.ensure_fail--; + cdev->stats.ensure++; + + return NFP_SUCCESS; +} + +/* command device structure ------------------------------------- */ + +const nfpcmd_dev i21555_cmddev = { + "nCipher Gen 2 PCI", + PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_21555, + PCI_VENDOR_ID_NCIPHER, PCI_SUBSYSTEM_ID_NFAST_REV1, + { 0, IOSIZE | PCI_BASE_ADDRESS_SPACE_IO, NFPCI_RAM_MINSIZE_JOBS, 0, 0, 0 }, + NFP_CMD_FLG_NEED_IOBUF, + i21555_create, + i21555_destroy, + i21555_open, + i21555_close, + i21555_isr, + i21555_write, + i21555_read, + i21555_chupdate, + i21555_ensure_reading, + i21555_debug, +}; diff --git a/usr/src/uts/common/io/nfp/i21555.h b/usr/src/uts/common/io/nfp/i21555.h new file mode 100644 index 0000000000..d8f3965938 --- /dev/null +++ b/usr/src/uts/common/io/nfp/i21555.h @@ -0,0 +1,51 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +#ifndef I21555_H +#define I21555_H + +#ifndef PCI_VENDOR_ID_INTEL +#define PCI_VENDOR_ID_INTEL 0x8086 +#endif + +#ifndef PCI_DEVICE_ID_INTEL_21555 +#define PCI_DEVICE_ID_INTEL_21555 0xb555 +#endif + +#ifndef PCI_VENDOR_ID_NCIPHER +#define PCI_VENDOR_ID_NCIPHER 0x0100 +#endif + +#ifndef PCI_SUBSYSTEM_ID_NFAST_REV1 +#define PCI_SUBSYSTEM_ID_NFAST_REV1 0x0100 +#endif + +#define I21555_OFFSET_DOORBELL_PRI_SET 0x9C +#define I21555_OFFSET_DOORBELL_SEC_SET 0x9E +#define I21555_OFFSET_DOORBELL_PRI_CLEAR 0x98 + +#define I21555_OFFSET_DOORBELL_PRI_SET_MASK 0xA4 +#define I21555_OFFSET_DOORBELL_PRI_CLEAR_MASK 0xA0 + +#define I21555_DOORBELL_PRI_ENABLE 0x0000 +#define I21555_DOORBELL_PRI_DISABLE 0xFFFF + +#define I21555_CFG_SEC_CMD_STATUS 0x44 + +#define CFG_CMD_MASTER 0x0004 + +#define IOBAR 1 +#define MEMBAR 2 + +#define IOSIZE 0x100 + +extern nfp_err i21555_debug( int cmd, void *ctx ); + +#endif diff --git a/usr/src/uts/common/io/nfp/i21555d.c b/usr/src/uts/common/io/nfp/i21555d.c new file mode 100644 index 0000000000..183ace8275 --- /dev/null +++ b/usr/src/uts/common/io/nfp/i21555d.c @@ -0,0 +1,28 @@ +/* + +i21555d.c: nCipher PCI HSM intel 21555 debug ioctl + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + + +history + +15/05/2002 jsh Original, does nothing + +*/ + +#include "nfp_common.h" +#include "nfp_error.h" +#include "nfp_osif.h" +#include "i21555.h" + +/* ARGSUSED */ +nfp_err i21555_debug( int cmd, void *ctx) { + nfp_log( NFP_DBG1, "i21555_debug: entered"); + + return NFP_EUNKNOWN; +} diff --git a/usr/src/uts/common/io/nfp/nfdev-common.h b/usr/src/uts/common/io/nfp/nfdev-common.h new file mode 100644 index 0000000000..8a97bf2c63 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfdev-common.h @@ -0,0 +1,141 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ +/** \file nfdev-common.h + * + * \brief nFast device driver (not generic SCSI) ioctl struct definition file + * include NFDEV-$(system) for ioctl number definitions + * + * 1998.07.13 jsh Started + * + * + */ + +#ifndef NFDEV_COMMON_H +#define NFDEV_COMMON_H + +/** + * Result of the ENQUIRY ioctl. + */ +typedef struct nfdev_enquiry_str { + unsigned int busno; /**< Which bus is the PCI device on. */ + unsigned char slotno; /**< Which slot is the PCI device in. */ + unsigned char reserved[3]; /**< for consistant struct alignment */ +} nfdev_enquiry_str; + +/** + * Result of the STATS ioctl. + */ +typedef struct nfdev_stats_str { + unsigned long isr; /**< Count interrupts. */ + unsigned long isr_read; /**< Count read interrupts. */ + unsigned long isr_write; /**< Count write interrupts. */ + unsigned long write_fail; /**< Count write failures. */ + unsigned long write_block; /**< Count blocks written. */ + unsigned long write_byte; /**< Count bytes written. */ + unsigned long read_fail; /**< Count read failures. */ + unsigned long read_block; /**< Count blocks read. */ + unsigned long read_byte; /**< Count bytes read. */ + unsigned long ensure_fail; /**< Count read request failures. */ + unsigned long ensure; /**< Count read requests. */ +} nfdev_stats_str; + +/** + * Input to the CONTROL ioctl. + */ +typedef struct nfdev_control_str { + unsigned control; /**< Control flags. */ +} nfdev_control_str; + +/** Control bit indicating host supports MOI control */ +#define NFDEV_CONTROL_HOST_MOI 0x0001 + +/** Index of control bits indicating desired mode + * + * Desired mode follows the M_ModuleMode enumeration. + */ +#define NFDEV_CONTROL_MODE_SHIFT 1 + +/** Detect a backwards-compatible control value + * + * Returns true if the request control value "makes no difference", i.e. + * and the failure of an attempt to set it is therefore uninteresting. + */ +#define NFDEV_CONTROL_HARMLESS(c) ((c) <= 1) + +/** + * Result of the STATUS ioctl. + */ +typedef struct nfdev_status_str { + unsigned status; /**< Status flags. */ + char error[8]; /**< Error string. */ +} nfdev_status_str; + +/** Monitor firmware supports MOI control and error reporting */ +#define NFDEV_STATUS_MONITOR_MOI 0x0001 + +/** Application firmware supports MOI control and error reporting */ +#define NFDEV_STATUS_APPLICATION_MOI 0x0002 + +/** Application firmware running and supports error reporting */ +#define NFDEV_STATUS_APPLICATION_RUNNING 0x0004 + +/** HSM failed + * + * Consult error[] for additional information. + */ +#define NFDEV_STATUS_FAILED 0x0008 + +/** Standard PCI interface. */ +#define NFDEV_IF_STANDARD 0x01 + +/** PCI interface with results pushed from device + * via DMA. + */ +#define NFDEV_IF_PCI_PUSH 0x02 + +/* platform independant base ioctl numbers */ + +/** Enquiry ioctl. + * \return nfdev_enquiry_str describing the attached device. */ +#define NFDEV_IOCTL_NUM_ENQUIRY 0x01 +/** Channel Update ioctl. + * \deprecated */ +#define NFDEV_IOCTL_NUM_CHUPDATE 0x02 +/** Ensure Reading ioctl. + * Signal a read request to the device. + * \param (unsigned int) Length of data to be read. + */ +#define NFDEV_IOCTL_NUM_ENSUREREADING 0x03 +/** Device Count ioctl. + * Not implemented for on all platforms. + * \return (int) the number of attached devices. */ +#define NFDEV_IOCTL_NUM_DEVCOUNT 0x04 +/** Internal Debug ioctl. + * Not implemented in release drivers. */ +#define NFDEV_IOCTL_NUM_DEBUG 0x05 +/** PCI Interface Version ioctl. + * \param (int) Maximum PCI interface version + * supported by the user of the device. */ +#define NFDEV_IOCTL_NUM_PCI_IFVERS 0x06 +/** Statistics ioctl. + * \return nfdev_enquiry_str describing the attached device. */ +#define NFDEV_IOCTL_NUM_STATS 0x07 + +/** Module control ioctl + * \param (nfdev_control_str) Value to write to HSM control register + */ +#define NFDEV_IOCTL_NUM_CONTROL 0x08 + +/** Module state ioctl + * \return (nfdev_status_str) Values read from HSM status/error registers + */ +#define NFDEV_IOCTL_NUM_STATUS 0x09 + +#endif diff --git a/usr/src/uts/common/io/nfp/nfdev-solaris.h b/usr/src/uts/common/io/nfp/nfdev-solaris.h new file mode 100644 index 0000000000..923b902e46 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfdev-solaris.h @@ -0,0 +1,37 @@ +/* + +nfdev-solaris.h: nFast solaris specific device ioctl interface. + +(C) Copyright nCipher Corporation Ltd 1998-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +14/07/1998 jsh Original + +*/ + +#ifndef NFDEV_SOLARIS_H +#define NFDEV_SOLARIS_H + +#include "nfdev-common.h" + +#define NFDEV_IOCTL_TYPE ('n'<<8) + +#define NFDEV_IOCTL_ENQUIRY ( NFDEV_IOCTL_TYPE | \ + NFDEV_IOCTL_NUM_ENQUIRY ) +#define NFDEV_IOCTL_ENSUREREADING ( NFDEV_IOCTL_TYPE | \ + NFDEV_IOCTL_NUM_ENSUREREADING ) +#define NFDEV_IOCTL_DEVCOUNT ( NFDEV_IOCTL_TYPE | \ + NFDEV_IOCTL_NUM_DEVCOUNT ) +#define NFDEV_IOCTL_DEBUG ( NFDEV_IOCTL_TYPE | \ + NFDEV_IOCTL_NUM_DEBUG ) +#define NFDEV_IOCTL_PCI_IFVERS ( NFDEV_IOCTL_TYPE | \ + NFDEV_IOCTL_NUM_PCI_IFVERS ) +#define NFDEV_IOCTL_STATS ( NFDEV_IOCTL_TYPE | \ + NFDEV_IOCTL_NUM_STATS ) + +#endif /* NFDEV_SOLARIS_H */ diff --git a/usr/src/uts/common/io/nfp/nfp.h b/usr/src/uts/common/io/nfp/nfp.h new file mode 100644 index 0000000000..9704f04fbc --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp.h @@ -0,0 +1,113 @@ +/* + +nfp.h: nFast PCI driver for Solaris 2.5, 2.6 and 2.7 + +(C) Copyright nCipher Corporation Ltd 2001-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +06/05/1998 jsh Original solaris 2.6 +21/05/1999 jsh added support for solaris 2.5 +10/06/1999 jsh added support for solaris 2.7 (32 and 64 bit) +16/10/2001 jsh moved from nfast to new structure in nfdrv + +*/ + +#ifndef NFP_H +#define NFP_H + +#ifndef _KERNEL +#error Hello? this is a driver, please compile with -D_KERNEL +#endif + +#if ( CH_KERNELVER < 260 ) +typedef int ioctlptr_t; +typedef unsigned short uint16_t; +#define DDI_GET32 ddi_getl +#define DDI_PUT32 ddi_putl +#define DDI_GET16 ddi_getw +#define DDI_PUT16 ddi_putw +#define DDI_REP_GET8 ddi_rep_getb +#define DDI_REP_PUT8 ddi_rep_putb +#define DDI_REP_GET32 ddi_rep_getl +#define DDI_REP_PUT32 ddi_rep_putl +#define PCI_CONFIG_GET16 pci_config_getw +#else /* ( CH_KERNELVER >= 260 ) */ +typedef intptr_t ioctlptr_t; +#define DDI_GET32 ddi_get32 +#define DDI_PUT32 ddi_put32 +#define DDI_GET16 ddi_get16 +#define DDI_PUT16 ddi_put16 +#define DDI_REP_GET8 ddi_rep_get8 +#define DDI_REP_PUT8 ddi_rep_put8 +#define DDI_REP_GET32 ddi_rep_get32 +#define DDI_REP_PUT32 ddi_rep_put32 +#define PCI_CONFIG_GET16 pci_config_get16 +#endif + +#if ( CH_KERNELVER < 270 ) +typedef int nfp_timeout_t; +#define EXTRA_CB_FLAGS 0 +#define VSXPRINTF(s, n, format, ap) vsprintf (s, format, ap) +#else /* ( CH_KERNELVER >= 270 ) */ +typedef timeout_id_t nfp_timeout_t; +#define EXTRA_CB_FLAGS D_64BIT +#define VSXPRINTF(s, n, format, ap) vsnprintf(s, n, format, ap) +#endif + +typedef struct nfp_dev { + int rd_ok; + int wr_ok; + + int ifvers; + + /* for PCI push read interface */ + unsigned char *read_buf; + ddi_dma_handle_t read_dma_handle; + ddi_dma_cookie_t read_dma_cookie; + + ddi_acc_handle_t acchandle; + + int rd_dma_ok; + + nfp_timeout_t wrtimeout; + nfp_timeout_t rdtimeout; + + struct buf *wr_bp; + int wr_ready; + int rd_ready; + int rd_pending; + int rd_outstanding; + kcondvar_t rd_cv; + + struct pollhead pollhead; + dev_info_t *dip; + + ddi_iblock_cookie_t high_iblock_cookie; /* for mutex */ + ddi_iblock_cookie_t low_iblock_cookie; /* for mutex */ + kmutex_t high_mutex; + kmutex_t low_mutex; + int high_intr; + ddi_softintr_t soft_int_id; + int high_read; + int high_write; + + ddi_iblock_cookie_t iblock_cookie; /* for mutex */ + kmutex_t isr_mutex; + + kmutex_t busy_mutex; + int busy; + + ddi_acc_handle_t conf_handle; + + nfp_cdev common; + const nfpcmd_dev *cmddev; +} nfp_dev; + +extern struct nfp_dev *nfp_dev_list[]; + +#endif /* NFP_H */ diff --git a/usr/src/uts/common/io/nfp/nfp_cmd.h b/usr/src/uts/common/io/nfp/nfp_cmd.h new file mode 100644 index 0000000000..db8af0b2f9 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp_cmd.h @@ -0,0 +1,68 @@ +/* + +nfp_cmd.h: nCipher PCI HSM command driver decalrations + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +10/10/2001 jsh Original + +*/ + +#ifndef NFPCMD_H +#define NFPCMD_H + +#include "nfp_hostif.h" +#include "nfp_error.h" + +/* read and write called with userspace buffer */ + +typedef struct nfpcmd_dev { + const char *name; + unsigned short vendorid, deviceid, + sub_vendorid, sub_deviceid; + unsigned int bar_sizes[6]; /* includes IO bit */ + unsigned int flags; + nfp_err (*create)(struct nfp_cdev *pdev); + nfp_err (*destroy)(void * ctx); + nfp_err (*open)(void * ctx); + nfp_err (*close)(void * ctx); + nfp_err (*isr)(void *ctx, int *handled); + nfp_err (*write_block)( const char *ublock, int len, void *ctx ); + nfp_err (*read_block)( char *ublock, int len, void *ctx, int *rcount); + nfp_err (*channel_update)( char *data, int len, void *ctx); + nfp_err (*ensure_reading)( unsigned int addr, int len, void *ctx ); + nfp_err (*debug)( int cmd, void *ctx); +} nfpcmd_dev; + +#define NFP_CMD_FLG_NEED_IOBUF 0x1 + +/* list of all supported drivers ---------------------------------------- */ + +extern const nfpcmd_dev *nfp_drvlist[]; + +extern const nfpcmd_dev i21285_cmddev; +extern const nfpcmd_dev i21555_cmddev; +extern const nfpcmd_dev bcm5820_cmddev; + +#ifndef PCI_BASE_ADDRESS_SPACE_IO +#define PCI_BASE_ADDRESS_SPACE_IO 0x1 +#endif + +#define NFP_MAXDEV 16 + + +#define NFP_MEMBAR_MASK ~0xf +#define NFP_IOBAR_MASK ~0x3 +/* + This masks off the bottom bits of the PCI_CSR_BAR which signify that the + BAR is an IO BAR rather than a MEM BAR +*/ + +#endif + diff --git a/usr/src/uts/common/io/nfp/nfp_common.h b/usr/src/uts/common/io/nfp/nfp_common.h new file mode 100644 index 0000000000..d1d2100fea --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp_common.h @@ -0,0 +1,68 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +#ifndef NFP_COMMON_H +#define NFP_COMMON_H + +#include <sys/types.h> +#include <sys/conf.h> + +typedef uint32_t UINT32; +typedef uint8_t BYTE; + +#define DEFINE_NFPCI_PACKED_STRUCTS +#include "nfpci.h" +#include "nfdev-solaris.h" + +typedef int oserr_t; + +#if CH_BIGENDIAN + +/* Big Endian Sparc */ + +#define SWP32(x) \ +( (((unsigned int)(x)>>24)&0xff) | (((unsigned int)(x)>>8)&0xff00) | (((unsigned int)(x)<<8)&0xff0000) | (((unsigned int)(x)<<24)&0xff000000) ) + +#define SWP16(x) ( (((x)>>8)&0xff) | (((x)<<8)&0xff00) ) + +#define FROM_LE32_IO(x) SWP32(*x) +#define TO_LE32_IO(x,y) *x=SWP32(y) + +#define FROM_LE32_MEM(x) SWP32(*x) +#define TO_LE32_MEM(x,y) *x=SWP32(y) + +#define FROM_LE16_IO(x) SWP16(*x) +#define TO_LE16_IO(x,y) *x=SWP16(y) + +#else + +/* Little Endian x86 */ + +#define FROM_LE32_IO(x) (*x) +#define TO_LE32_IO(x,y) (*x=y) + +#define FROM_LE32_MEM(x) (*x) +#define TO_LE32_MEM(x,y) (*x=y) + +#define FROM_LE16_IO(x) (*x) +#define TO_LE16_IO(x,y) (*x=y) + +#endif /* !CH_BIGENDIAN */ + +#include <sys/types.h> + +#if CH_KERNELVER == 260 +#define nfp_get_lbolt( lbolt, err ) err= drv_getparm( LBOLT, lbolt ) +#else +#define nfp_get_lbolt( lbolt, err ) { *lbolt= ddi_get_lbolt(); err= 0; } +#endif + +#endif + diff --git a/usr/src/uts/common/io/nfp/nfp_error.h b/usr/src/uts/common/io/nfp/nfp_error.h new file mode 100644 index 0000000000..d64cb78fd4 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp_error.h @@ -0,0 +1,48 @@ +/* + +nfp_error.h: nCipher PCI HSM error handling + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +05/12/2001 jsh Original + +*/ + +#ifndef NFP_ERROR_H +#define NFP_ERROR_H + +#include "nfp_common.h" + +#define NFP_SUCCESS 0x0 +#define NFP_EFAULT 0x1 +#define NFP_ENOMEM 0x2 +#define NFP_EINVAL 0x3 +#define NFP_EIO 0x4 +#define NFP_ENXIO 0x5 +#define NFP_ENODEV 0x6 +#define NFP_EINTR 0x7 +#define NFP_ESTARTING 0x8 +#define NFP_EAGAIN 0x9 +#define NFP_EUNKNOWN 0x100 + +typedef int nfp_err; + +extern oserr_t nfp_oserr( nfp_err nerr ); +extern nfp_err nfp_error( oserr_t oerr ); + +#define nfr( x) \ + return nfp_error((x)) + +#define nfer(x, fn, msg) \ + { oserr_t err=(x); if(err) { nfp_log( NFP_DBG1, #fn ": " msg); return nfp_error(err); } } + +#define er(x, fn, msg ) \ +{ nfp_err err=(x); if(err) { nfp_log( NFP_DBG1, #fn ": " msg); return err; } } + +#endif diff --git a/usr/src/uts/common/io/nfp/nfp_hostif.h b/usr/src/uts/common/io/nfp/nfp_hostif.h new file mode 100644 index 0000000000..3e7d8187e5 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp_hostif.h @@ -0,0 +1,54 @@ +/* + +nfp_hostif.h: nCipher PCI HSM host interface declarations + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +10/10/2001 jsh Original + +*/ + +#ifndef NFP_HOSTIF_H +#define NFP_HOSTIF_H + +#include "nfdev-common.h" + +struct nfp_dev; + +/* common device structure */ + +typedef struct nfp_cdev { + unsigned char *bar[6]; + void *extra[6]; + + int busno; + int slotno; + + void *cmdctx; + + char *iobuf; + + struct nfp_dev* dev; + + struct nfdev_stats_str stats; + +} nfp_cdev; + +/* callbacks from command drivers -------------------------------------- */ + +void nfp_read_complete( struct nfp_dev *pdev, int ok); +void nfp_write_complete( struct nfp_dev *pdev, int ok); + +#define NFP_READ_MAX (8 * 1024) +#define NFP_READBUF_SIZE (NFP_READ_MAX + 8) +#define NFP_TIMEOUT_SEC 10 + +#define NFP_DRVNAME "nCipher nFast PCI driver" + +#endif diff --git a/usr/src/uts/common/io/nfp/nfp_ifvers.c b/usr/src/uts/common/io/nfp/nfp_ifvers.c new file mode 100644 index 0000000000..807b4f24c5 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp_ifvers.c @@ -0,0 +1,51 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +/* + * nfp_ifervs.c - common pci interface versioning + * + * uses: + * + * int pdev->ifvers + * device interface version + * + * int nfp_ifvers + * interface version limit + * + * int nfp_alloc_pci_push( nfp_dev *pdev ) + * allocates resources needed for PCI Push, + * if not already allocated, and return True if successful + * + * void nfp_free_pci_push( nfp_dev *pdev ) { + * frees any resources allocated to PCI Push + */ + +void nfp_set_ifvers( nfp_dev *pdev, int vers ) { + if( nfp_ifvers != 0 && vers > nfp_ifvers ) { + nfp_log( NFP_DBG2, + "nfp_set_ifvers: can't set ifvers %d" + " as nfp_ifvers wants max ifvers %d", + vers, nfp_ifvers); + return; + } + if( vers >= NFDEV_IF_PCI_PUSH ) { + if(!nfp_alloc_pci_push(pdev)) { + nfp_log( NFP_DBG1, + "nfp_set_ifvers: can't set ifvers %d" + " as resources not available", + vers); + return; + } + } else { + nfp_free_pci_push(pdev); + } + pdev->ifvers= vers; + nfp_log( NFP_DBG3, "nfp_set_ifvers: setting ifvers %d", vers); +} diff --git a/usr/src/uts/common/io/nfp/nfp_osif.h b/usr/src/uts/common/io/nfp/nfp_osif.h new file mode 100644 index 0000000000..17ffe469ce --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfp_osif.h @@ -0,0 +1,105 @@ +/* + +nfp_osif.h: nCipher PCI HSM OS interface declarations + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +history + +10/10/2001 jsh Original + +*/ + +#ifndef NFP_OSIF_H +#define NFP_OSIF_H + +#include "nfp_hostif.h" +#include "nfp_error.h" + +/* general typedefs ----------------------------------------------- */ + +typedef volatile unsigned int reg32; +typedef volatile unsigned short reg16; +typedef volatile unsigned char reg8; + +/* sempaphores, mutexs and events --------------------------------- */ + +#if 0 +extern nfp_err nfp_sema_init( nfp_sema *sema, int initial); +extern void nfp_sema_destroy( nfp_sema *sema ); +extern void nfp_sema_post( nfp_sema *sema ); +extern void nfp_sema_wait( nfp_sema *sema ); +extern int nfp_sema_wait_sig( nfp_sema *sema ); + +extern nfp_err nfp_mutex_init( nfp_mutex *mutex ); +extern void nfp_mutex_destroy( nfp_mutex *mutex ); +extern void nfp_mutex_enter( nfp_mutex *mutex ); +extern void nfp_mutex_exit( nfp_mutex *mutex ); + +extern nfp_err nfp_event_init( nfp_event *event ); +extern void nfp_event_destroy( nfp_event *event ); +extern void nfp_event_set( nfp_event *event ); +extern void nfp_event_clear( nfp_event *event ); +extern void nfp_event_wait( nfp_event *event ); +extern void nfp_event_wait_sig( nfp_event *event ); + +#endif + +/* timeouts ------------------------------------------------------ */ + +extern void nfp_sleep( int ms ); + +/* memory handling ----------------------------------------------- */ + +#define KMALLOC_DMA 0 +#define KMALLOC_CACHED 1 + +extern void *nfp_kmalloc( int size, int flags ); +extern void *nfp_krealloc( void *ptr, int size, int flags ); +extern void nfp_kfree( void * ); + +/* config space access ------------------------------------------------ */ + +/* return Little Endian 32 bit config register */ +extern nfp_err nfp_config_inl( nfp_cdev *pdev, int offset, unsigned int *res ); + +/* io space access ------------------------------------------------ */ + +extern unsigned int nfp_inl( nfp_cdev *pdev, int bar, int offset ); +extern unsigned short nfp_inw( nfp_cdev *pdev, int bar, int offset ); +extern void nfp_outl( nfp_cdev *pdev, int bar, int offset, unsigned int data ); +extern void nfp_outw( nfp_cdev *pdev, int bar, int offset, unsigned short data ); + +/* user and device memory space access ---------------------------- */ + +/* NB these 2 functions are not guarenteed to be re-entrant for a given device */ +extern nfp_err nfp_copy_from_user_to_dev( nfp_cdev *cdev, int bar, int offset, const char *ubuf, int len); +extern nfp_err nfp_copy_to_user_from_dev( nfp_cdev *cdev, int bar, int offset, char *ubuf, int len); + +extern nfp_err nfp_copy_from_user( char *kbuf, const char *ubuf, int len ); +extern nfp_err nfp_copy_to_user( char *ubuf, const char *kbuf, int len ); + +extern nfp_err nfp_copy_from_dev( nfp_cdev *cdev, int bar, int offset, char *kbuf, int len ); +extern nfp_err nfp_copy_to_dev( nfp_cdev *cdev, int bar, int offset, const char *kbuf, int len); + +/* debug ------------------------------------------------------------ */ + +#define NFP_DBG1 1 +#define NFP_DBGE NFP_DBG1 +#define NFP_DBG2 2 +#define NFP_DBG3 3 +#define NFP_DBG4 4 + +#ifdef STRANGE_VARARGS +extern void nfp_log(); +#else +extern void nfp_log( int severity, const char *format, ...); +#endif + +extern int nfp_debug; + +#endif diff --git a/usr/src/uts/common/io/nfp/nfpci.h b/usr/src/uts/common/io/nfp/nfpci.h new file mode 100644 index 0000000000..793f5995e6 --- /dev/null +++ b/usr/src/uts/common/io/nfp/nfpci.h @@ -0,0 +1,171 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +/* +* +* NFPCI.H - nFast PCI interface definition file +* +* +* +* 1998.06.09 IH Started +* +* The interface presented by nFast PCI devices consists of: +* +* A region of shared RAM used for data transfer & control information +* A doorbell interrupt register, so both sides can give each other interrupts +* A number of DMA channels for transferring data +*/ + +#ifndef NFPCI_H +#define NFPCI_H + +/* Sizes of some regions */ +#define NFPCI_RAM_MINSIZE 0x00100000 +/* This is the minimum size of shared RAM. In future it may be possible to + negotiate larger sizes of shared RAM or auto-detect how big it is */ +#define NFPCI_RAM_MINSIZE_JOBS 0x00020000 /* standard jobs only */ +#define NFPCI_RAM_MINSIZE_KERN 0x00040000 /* standard and kernel jobs */ + +/* Offsets within shared memory space. + The following main regions are: + jobs input area + jobs output area + kernel jobs input area + kernel output area +*/ + +#define NFPCI_OFFSET_JOBS 0x00000000 +#define NFPCI_OFFSET_JOBS_WR 0x00000000 +#define NFPCI_OFFSET_JOBS_RD 0x00010000 +#define NFPCI_OFFSET_KERN 0x00020000 +#define NFPCI_OFFSET_KERN_WR 0x00020000 +#define NFPCI_OFFSET_KERN_RD 0x00030000 + +/* Interrupts, defined by bit position in doorbell register */ + +/* Interrupts from device to host */ +#define NFAST_INT_DEVICE_WRITE_OK 0x00000001 +#define NFAST_INT_DEVICE_WRITE_FAILED 0x00000002 +#define NFAST_INT_DEVICE_READ_OK 0x00000004 +#define NFAST_INT_DEVICE_READ_FAILED 0x00000008 +#define NFAST_INT_DEVICE_KERN_WRITE_OK 0x00000010 +#define NFAST_INT_DEVICE_KERN_WRITE_FAILED 0x00000020 +#define NFAST_INT_DEVICE_KERN_READ_OK 0x00000040 +#define NFAST_INT_DEVICE_KERN_READ_FAILED 0x00000080 + +/* Interrupts from host to device */ +#define NFAST_INT_HOST_WRITE_REQUEST 0x00010000 +#define NFAST_INT_HOST_READ_REQUEST 0x00020000 +#define NFAST_INT_HOST_DEBUG 0x00040000 +#define NFAST_INT_HOST_KERN_WRITE_REQUEST 0x00080000 +#define NFAST_INT_HOST_KERN_READ_REQUEST 0x00100000 + +/* Ordinary job submission ------------------------ */ + +/* The NFPCI_OFFSET_JOBS_WR and NFPCI_OFFSET_JOBS_RD regions are defined + by the following (byte) address offsets... */ + +#define NFPCI_OFFSET_CONTROL 0x0 +#define NFPCI_OFFSET_LENGTH 0x4 +#define NFPCI_OFFSET_DATA 0x8 +#define NFPCI_OFFSET_PUSH_ADDR 0x8 + +#define NFPCI_JOBS_WR_CONTROL (NFPCI_OFFSET_JOBS_WR + NFPCI_OFFSET_CONTROL) +#define NFPCI_JOBS_WR_LENGTH (NFPCI_OFFSET_JOBS_WR + NFPCI_OFFSET_LENGTH) +#define NFPCI_JOBS_WR_DATA (NFPCI_OFFSET_JOBS_WR + NFPCI_OFFSET_DATA) +#define NFPCI_MAX_JOBS_WR_LEN (0x0000FFF8) + +#define NFPCI_JOBS_RD_CONTROL (NFPCI_OFFSET_JOBS_RD + NFPCI_OFFSET_CONTROL) +#define NFPCI_JOBS_RD_LENGTH (NFPCI_OFFSET_JOBS_RD + NFPCI_OFFSET_LENGTH) +#define NFPCI_JOBS_RD_DATA (NFPCI_OFFSET_JOBS_RD + NFPCI_OFFSET_DATA) +/* address in PCI space of host buffer for NFPCI_JOB_CONTROL_PCI_PUSH */ +#define NFPCI_JOBS_RD_PUSH_ADDR (NFPCI_OFFSET_JOBS_RD + NFPCI_OFFSET_PUSH_ADDR) +#define NFPCI_MAX_JOBS_RD_LEN (0x000FFF8) + +/* Kernel inferface job submission ---------------- */ + +#define NFPCI_KERN_WR_CONTROL (NFPCI_OFFSET_KERN_WR + NFPCI_OFFSET_CONTROL) +#define NFPCI_KERN_WR_LENGTH (NFPCI_OFFSET_KERN_WR + NFPCI_OFFSET_LENGTH) +#define NFPCI_KERN_WR_DATA (NFPCI_OFFSET_KERN_WR + NFPCI_OFFSET_DATA) +#define NFPCI_MAX_KERN_WR_LEN (0x0000FFF8) + +#define NFPCI_KERN_RD_CONTROL (NFPCI_OFFSET_KERN_RD + NFPCI_OFFSET_CONTROL) +#define NFPCI_KERN_RD_LENGTH (NFPCI_OFFSET_KERN_RD + NFPCI_OFFSET_LENGTH) +#define NFPCI_KERN_RD_DATA (NFPCI_OFFSET_KERN_RD + NFPCI_OFFSET_DATA) +/* address in PCI space of host buffer for NFPCI_JOB_CONTROL_PCI_PUSH */ +#define NFPCI_KERN_RD_ADDR (NFPCI_OFFSET_KERN_RD + NFPCI_OFFSET_PUSH_ADDR) +#define NFPCI_MAX_KERN_RD_LEN (0x000FFF8) + +#ifdef DEFINE_NFPCI_PACKED_STRUCTS +typedef struct +{ + UINT32 controlword; + UINT32 length; /* length of data to follow */ + union { + BYTE data[1]; + UINT32 addr; + } uu; +} + NFPCI_JOBS_BLOCK; +#endif + + +#define NFPCI_JOB_CONTROL 0x00000001 +#define NFPCI_JOB_CONTROL_PCI_PUSH 0x00000002 +/* + The 'Control' word is analogous to the SCSI read/write address; + 1 = standard push/pull IO + 2 = push/push IO + + To submit a block of job data, the host: + - sets the (32-bit, little-endian) word at NFPCI_JOBS_WR_CONTROL to NFPCI_JOB_CONTROL + - sets the word at NFPCI_JOBS_WR_LENGTH to the length of the data + - copies the data to NFPCI_JOBS_WR_DATA + - sets interrupt NFAST_INT_HOST_WRITE_REQUEST in the doorbell register + - awaits the NFAST_INT_DEVICE_WRITE_OK (or _FAILED) interrupts back + + To read a block of jobs back, the host: + - sets the word at NFPCI_JOBS_RD_CONTROL to NFPCI_JOB_CONTROL + - sets the word at NFPCI_JOBS_RD_LENGTH to the max length for returned data + - sets interrupt NFAST_INT_HOST_READ_REQUEST + - awaits the NFAST_INT_DEVICE_READ_OK (or _FAILED) interrupt + - reads the data from NFPCI_JOBS_RD_DATA; the module will set the word at + NFPCI_JOBS_RD_LENGTH to its actual length. + + Optionally the host can request the PCI read data to be pushed to host PCI mapped ram: + - allocates a contiguous PCI addressable buffer for a NFPCI_JOBS_BLOCK of max + size NFPCI_MAX_JOBS_RD_LEN (or NFPCI_MAX_KERN_RD_LEN) + 8 + - sets the word at NFPCI_JOBS_RD_CONTROL to NFPCI_JOB_CONTROL_PCI_PUSH + - sets the word at NFPCI_JOBS_RD_LENGTH to the max length for returned data + - sets the word at NFPCI_JOBS_RD_PUSH_ADDR to be the host PCI address of + the buffer + - sets interrupt NFAST_INT_HOST_READ_REQUEST + - awaits the NFAST_INT_DEVICE_READ_OK (or _FAILED) interrupt + - reads the data from the buffer at NFPCI_OFFSET_DATA in the buffer. The + module will set NFPCI_OFFSET_LENGTH to the actual length. +*/ + +#define NFPCI_SCRATCH_CONTROL 0 + +#define NFPCI_SCRATCH_CONTROL_HOST_MOI (1<<0) +#define NFPCI_SCRATCH_CONTROL_MODE_SHIFT 1 +#define NFPCI_SCRATCH_CONTROL_MODE_MASK (3<<NFPCI_SCRATCH_CONTROL_MODE_SHIFT) + +#define NFPCI_SCRATCH_STATUS 1 + +#define NFPCI_SCRATCH_STATUS_MONITOR_MOI (1<<0) +#define NFPCI_SCRATCH_STATUS_APPLICATION_MOI (1<<1) +#define NFPCI_SCRATCH_STATUS_APPLICATION_RUNNING (1<<2) +#define NFPCI_SCRATCH_STATUS_ERROR (1<<3) + +#define NFPCI_SCRATCH_ERROR_LO 2 +#define NFPCI_SCRATCH_ERROR_HI 3 + +#endif diff --git a/usr/src/uts/common/io/nfp/osif.c b/usr/src/uts/common/io/nfp/osif.c new file mode 100644 index 0000000000..fba62f9a37 --- /dev/null +++ b/usr/src/uts/common/io/nfp/osif.c @@ -0,0 +1,184 @@ +/* + +(C) Copyright nCipher Corporation Ltd 2002-2008 All rights reserved + +Copyright (c) 2008-2013 Thales e-Security All rights reserved + +Copyright (c) 2014 Thales UK All rights reserved + +*/ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/file.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <sys/map.h> +#include <sys/debug.h> +#include <sys/modctl.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/open.h> +#include <sys/stat.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/pci.h> + +#include "nfp_common.h" +#include "nfp_hostif.h" +#include "nfp_error.h" +#include "nfp_osif.h" +#include "nfp_cmd.h" +#include "nfp.h" +#include "autoversion.h" + +/* config space access ---------------------------------- */ + +nfp_err nfp_config_inl( nfp_cdev *pdev, int offset, unsigned int *res ) { + unsigned int tmp32; + if ( !pdev || !pdev->dev || !pdev->dev->conf_handle ) + return NFP_ENODEV; + +/* pci_config_get32() does byte swapping, so put back to LE */ + tmp32 = pci_config_get32( pdev->dev->conf_handle, offset ); + TO_LE32_IO(res, tmp32); + + return NFP_SUCCESS; +} + +/* user space memory access ---------------------------------- */ + +nfp_err nfp_copy_from_user( char *kbuf, const char *ubuf, int len) { + bcopy(ubuf, kbuf, len); + return 0; +} + +nfp_err nfp_copy_to_user( char *ubuf, const char *kbuf, int len) { + bcopy(kbuf, ubuf, len); + return 0; +} + +nfp_err nfp_copy_from_user_to_dev( nfp_cdev *cdev, int bar, int offset, const char *ubuf, int len) { + /* dirty hack on Solaris, as we are called from strategy we are, in fact, copying from kernel mem */ + return nfp_copy_to_dev( cdev, bar, offset, ubuf, len ); +} + +nfp_err nfp_copy_to_user_from_dev( nfp_cdev *cdev, int bar, int offset, char *ubuf, int len) { + /* dirty hack on Solaris, as we are called from strategy we are, in fact, copying to kernel mem */ + return nfp_copy_from_dev( cdev, bar, offset, ubuf, len ); +} + +nfp_err nfp_copy_from_dev( nfp_cdev *cdev, int bar, int offset, char *kbuf, int len) { + if( len & 0x3 || offset & 0x3 ) + DDI_REP_GET8( cdev->extra[bar], (unsigned char *)kbuf, cdev->bar[bar] + offset, len, DDI_DEV_AUTOINCR); + else + /* LINTED: alignment */ + DDI_REP_GET32( cdev->extra[bar], (unsigned int *)kbuf, (unsigned int *)(cdev->bar[bar] + offset), len / 4, DDI_DEV_AUTOINCR); + return NFP_SUCCESS; +} + +nfp_err nfp_copy_to_dev( nfp_cdev *cdev, int bar, int offset, const char *kbuf, int len) { + if( len & 0x3 || offset & 0x3 ) + DDI_REP_PUT8( cdev->extra[bar], (unsigned char *)kbuf, cdev->bar[bar] + offset, len, DDI_DEV_AUTOINCR ); + else + /* LINTED: alignment */ + DDI_REP_PUT32( cdev->extra[bar], (unsigned int *)kbuf, (unsigned int *)(cdev->bar[bar] + offset), len / 4, DDI_DEV_AUTOINCR ); + return NFP_SUCCESS; +} + +/* pci io space access --------------------------------------- */ + +unsigned int nfp_inl( nfp_cdev *pdev, int bar, int offset ) { + nfp_log( NFP_DBG3, "nfp_inl: addr %x", (uintptr_t) pdev->bar[bar] + offset); + /* LINTED: alignment */ + return DDI_GET32( pdev->extra[bar], (uint32_t *)(pdev->bar[bar] + offset) ); +} + +unsigned short nfp_inw( nfp_cdev *pdev, int bar, int offset ) { + nfp_log( NFP_DBG3, "nfp_inw: addr %x", (uintptr_t) pdev->bar[bar] + offset); + /* LINTED: alignment */ + return DDI_GET16( pdev->extra[bar], (unsigned short *)(pdev->bar[ bar ] + offset) ); +} + +void nfp_outl( nfp_cdev *pdev, int bar, int offset, unsigned int data ) { + nfp_log( NFP_DBG3, "nfp_outl: addr %x, data %x", (uintptr_t) pdev->bar[bar] + offset, data); + /* LINTED: alignment */ + DDI_PUT32( pdev->extra[bar], (uint32_t *)(pdev->bar[ bar ] + offset), data ); +} + +void nfp_outw( nfp_cdev *pdev, int bar, int offset, unsigned short data ) { + nfp_log( NFP_DBG3, "nfp_outl: addr %x, data %x", (uintptr_t) pdev->bar[bar] + offset, data); + /* LINTED: alignment */ + DDI_PUT16( pdev->extra[bar], (unsigned short *)(pdev->bar[ bar ] + offset), data ); +} + +/* logging ---------------------------------------------------- */ + +void nfp_log( int level, const char *fmt, ...) +{ + auto char buf[256]; + va_list ap; + + switch (level) { + case NFP_DBG4: if (nfp_debug < 4) break; + /*FALLTHROUGH*/ + case NFP_DBG3: if (nfp_debug < 3) break; + /*FALLTHROUGH*/ + case NFP_DBG2: if (nfp_debug < 2) break; + /*FALLTHROUGH*/ + case NFP_DBG1: if (nfp_debug < 1) break; + /*FALLTHROUGH*/ + default: + va_start(ap, fmt); + (void) vsnprintf(buf, 256, fmt, ap); + va_end(ap); + cmn_err(CE_CONT, "!" VERSION_COMPNAME " " VERSION_NO ": %s\n", buf); + break; + } +} + +struct errstr { + int oserr; + nfp_err nferr; +}; + + +static struct errstr errtab[] = { + { EFAULT, NFP_EFAULT }, + { ENOMEM, NFP_ENOMEM }, + { EINVAL, NFP_EINVAL }, + { EIO, NFP_EIO }, + { ENXIO, NFP_ENXIO }, + { ENODEV, NFP_ENODEV }, + { EINVAL, NFP_EUNKNOWN }, + { 0, 0 } +}; + +nfp_err nfp_error( int oserr ) +{ + struct errstr *perr; + if(!oserr) + return 0; + perr= errtab; + while(perr->nferr) { + if(perr->oserr == oserr) + return perr->nferr; + perr++; + } + return NFP_EUNKNOWN; +} + +int nfp_oserr( nfp_err nferr ) +{ + struct errstr *perr; + if(nferr == NFP_SUCCESS) + return 0; + perr= errtab; + while(perr->nferr) { + if(perr->nferr == nferr) + return perr->oserr; + perr++; + } + return EIO; +} diff --git a/usr/src/uts/common/io/pseudo.conf b/usr/src/uts/common/io/pseudo.conf index 42248e93d6..08affec609 100644 --- a/usr/src/uts/common/io/pseudo.conf +++ b/usr/src/uts/common/io/pseudo.conf @@ -22,8 +22,7 @@ # # Copyright 2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. -# -# ident "%Z%%M% %I% %E% SMI" +# Copyright 2014 Joyent, Inc. All rights reserved. # # This file is private to the pseudonex driver. It should not be edited. # @@ -38,3 +37,9 @@ name="pseudo" class="root" instance=0; # /pseudo; it has as its children the zone console pseudo nodes. # name="zconsnex" parent="/pseudo" instance=1 valid-children="zcons"; + +# +# zfdnex is an alias for pseudo; this node is instantiated as a child of +# /pseudo; it has as its children the zone fd pseudo nodes. +# +name="zfdnex" parent="/pseudo" instance=2 valid-children="zfd"; diff --git a/usr/src/uts/common/io/ptm.c b/usr/src/uts/common/io/ptm.c index 400e9ffd10..07ffddc123 100644 --- a/usr/src/uts/common/io/ptm.c +++ b/usr/src/uts/common/io/ptm.c @@ -447,6 +447,18 @@ ptmclose(queue_t *rqp, int flag, cred_t *credp) return (0); } +static boolean_t +ptmptsopencb(ptmptsopencb_arg_t arg) +{ + struct pt_ttys *ptmp = (struct pt_ttys *)arg; + boolean_t rval; + + PT_ENTER_READ(ptmp); + rval = (ptmp->pt_nullmsg != NULL); + PT_EXIT_READ(ptmp); + return (rval); +} + /* * The wput procedure will only handle ioctl and flush messages. */ @@ -574,6 +586,41 @@ ptmwput(queue_t *qp, mblk_t *mp) miocack(qp, mp, 0, 0); break; } + case PTMPTSOPENCB: + { + mblk_t *dp; /* ioctl reply data */ + ptmptsopencb_t *ppocb; + + /* only allow the kernel to invoke this ioctl */ + if (iocp->ioc_cr != kcred) { + miocnak(qp, mp, 0, EINVAL); + break; + } + + /* we don't support transparent ioctls */ + ASSERT(iocp->ioc_count != TRANSPARENT); + if (iocp->ioc_count == TRANSPARENT) { + miocnak(qp, mp, 0, EINVAL); + break; + } + + /* allocate a response message */ + dp = allocb(sizeof (ptmptsopencb_t), BPRI_MED); + if (dp == NULL) { + miocnak(qp, mp, 0, EAGAIN); + break; + } + + /* initialize the ioctl results */ + ppocb = (ptmptsopencb_t *)dp->b_rptr; + ppocb->ppocb_func = ptmptsopencb; + ppocb->ppocb_arg = (ptmptsopencb_arg_t)ptmp; + + /* send the reply data */ + mioc2ack(mp, dp, sizeof (ptmptsopencb_t), 0); + qreply(qp, mp); + break; + } } break; diff --git a/usr/src/uts/common/io/scsi/targets/sd.c b/usr/src/uts/common/io/scsi/targets/sd.c index ae1e7e0fc3..dc5dc22e37 100644 --- a/usr/src/uts/common/io/scsi/targets/sd.c +++ b/usr/src/uts/common/io/scsi/targets/sd.c @@ -3503,9 +3503,13 @@ sd_set_mmc_caps(sd_ssc_t *ssc) * according to the successful response to the page * 0x2A mode sense request. */ - scsi_log(SD_DEVINFO(un), sd_label, CE_WARN, - "sd_set_mmc_caps: Mode Sense returned " - "invalid block descriptor length\n"); + /* + * The following warning occurs due to the KVM CD-ROM + * mishandling the multi-media commands. Ignore it. + * scsi_log(SD_DEVINFO(un), sd_label, CE_WARN, + * "sd_set_mmc_caps: Mode Sense returned " + * "invalid block descriptor length\n"); + */ kmem_free(buf, BUFLEN_MODE_CDROM_CAP); return; } @@ -4450,18 +4454,77 @@ sd_sdconf_id_match(struct sd_lun *un, char *id, int idlen) { struct scsi_inquiry *sd_inq; int rval = SD_SUCCESS; + char *p; + int chk_vidlen = 0, chk_pidlen = 0; + int has_tail = 0; + static const int VSZ = sizeof (sd_inq->inq_vid); + static const int PSZ = sizeof (sd_inq->inq_pid); ASSERT(un != NULL); sd_inq = un->un_sd->sd_inq; ASSERT(id != NULL); /* - * We use the inq_vid as a pointer to a buffer containing the - * vid and pid and use the entire vid/pid length of the table - * entry for the comparison. This works because the inq_pid - * data member follows inq_vid in the scsi_inquiry structure. + * We would like to use the inq_vid as a pointer to a buffer + * containing the vid and pid and use the entire vid/pid length of + * the table entry for the comparison. However, this does not work + * because, while the inq_pid data member follows inq_vid in the + * scsi_inquiry structure, we do not control the contents of this + * buffer, and some broken devices violate SPC 4.3.1 and return + * fields with null bytes in them. + */ + chk_vidlen = MIN(VSZ, idlen); + p = id + chk_vidlen - 1; + while (*p == ' ' && chk_vidlen > 0) { + --p; + --chk_vidlen; + } + + /* + * If it's all spaces, check the whole thing. */ - if (strncasecmp(sd_inq->inq_vid, id, idlen) != 0) { + if (chk_vidlen == 0) + chk_vidlen = MIN(VSZ, idlen); + + if (idlen > VSZ) { + chk_pidlen = idlen - VSZ; + p = id + idlen - 1; + while (*p == ' ' && chk_pidlen > 0) { + --p; + --chk_pidlen; + } + if (chk_pidlen == 0) + chk_pidlen = MIN(PSZ, idlen - VSZ); + } + + /* + * There's one more thing we need to do here. If the user specified + * an ID with trailing spaces, we need to make sure the inquiry + * vid/pid has only spaces or NULs after the check length; otherwise, it + * can't match. + */ + if (idlen > chk_vidlen && chk_vidlen < VSZ) { + for (p = sd_inq->inq_vid + chk_vidlen; + p < sd_inq->inq_vid + VSZ; ++p) { + if (*p != ' ' && *p != '\0') { + ++has_tail; + break; + } + } + } + if (idlen > chk_pidlen + VSZ && chk_pidlen < PSZ) { + for (p = sd_inq->inq_pid + chk_pidlen; + p < sd_inq->inq_pid + PSZ; ++p) { + if (*p != ' ' && *p != '\0') { + ++has_tail; + break; + } + } + } + + if (has_tail || strncasecmp(sd_inq->inq_vid, id, chk_vidlen) != 0 || + (idlen > VSZ && + strncasecmp(sd_inq->inq_pid, id + VSZ, chk_pidlen) != 0)) { /* * The user id string is compared to the inquiry vid/pid * using a case insensitive comparison and ignoring diff --git a/usr/src/uts/common/io/udmf/dm9601reg.h b/usr/src/uts/common/io/udmf/dm9601reg.h new file mode 100644 index 0000000000..a36f2b0fc8 --- /dev/null +++ b/usr/src/uts/common/io/udmf/dm9601reg.h @@ -0,0 +1,348 @@ +/* + * %W% %E% + * Macro definitions for Davicom DM9601 USB to fast ethernet controler + * based on Davicom DM9601E data sheet + * This file is public domain. Coded by M.Murayama (KHF04453@nifty.com) + */ + +#ifndef __DM9601_H__ +#define __DM9601_H__ + +/* + * offset of registers + */ +#define NCR 0x00U /* network control register */ +#define NSR 0x01U /* network status register */ +#define TCR 0x02U /* tx control register */ +#define TSR1 0x03U /* tx status register 1 */ +#define TSR2 0x04U /* tx status register 2 */ +#define RCR 0x05U /* rx control register */ +#define RSR 0x06U /* rx status register */ +#define ROCR 0x07U /* rx overflow counter register */ +#define BPTR 0x08U /* back pressure threshold regster */ +#define FCTR 0x09U /* flow control threshold regster */ +#define FCR 0x0aU /* flow control threshold regster */ +#define EPCR 0x0bU /* eeprom & phy control register */ +#define EPAR 0x0cU /* eeprom & phy address register */ +#define EPDR 0x0dU /* eeprom & phy data register (2byte) */ +#define WCR 0x0fU /* wake up control register */ +#define PAR 0x10U /* physical address register (6byte) */ +#define MAR 0x16U /* multicast address register (8byte) */ +#define GPCR 0x1eU /* general purpose control register */ +#define GPR 0x1fU /* general purpose register */ +#define VID 0x28U /* vendor ID (2byte) */ +#define PID 0x2aU /* product ID (2byte) */ +#define CHIPR 0x2cU /* chip revision */ +#define USBDA 0xf0U /* usb device address register */ +#define RXC 0xf1U /* received packet counter register */ +#define TUSC 0xf2U /* tx packet counter/usb status register */ +#define USBC 0xf4U /* usb control register */ + +/* + * register definitions + */ +/* network control register */ +#define NCR_EXT_PHY 0x80U /* 1: select external phy */ +#define NCR_WAKEEN 0x40U /* 1: wake up event enable */ +#define NCR_FCOL 0x10U /* force collision mode for test */ +#define NCR_FDX 0x08U /* 1: full duplex mode (for external phy) */ +#define NCR_LBK 0x06U +#define NCR_LBK_SHIFT 1 +#define NCR_LBK_NORMAL (0U << NCR_LBK_SHIFT) +#define NCR_LBK_MAC (1U << NCR_LBK_SHIFT) +#define NCR_LBK_PHY_D (2U << NCR_LBK_SHIFT) +#define NCR_LBK_PHY_A (3U << NCR_LBK_SHIFT) +#define NCR_RST 0x01U /* 1: reset, auto clear */ + +#define NCR_BITS \ + "\020" \ + "\010EXT_PHY" \ + "\007WAKEEN" \ + "\005FCOL" \ + "\004FDX" \ + "\001RST" + +/* network status register */ +#define NSR_SPEED 0x80U /* 1:10M 0:100M */ +#define NSR_LINKST 0x40U /* 1:ok 0:fail */ +#define NSR_WAKEST 0x20U /* 1:enabled */ +#define NSR_TXFULL 0x10U /* 1:tx fifo full */ +#define NSR_TX2END 0x08U /* tx packet2 complete status */ +#define NSR_TX1END 0x04U /* tx packet1 complete status */ +#define NSR_RXOV 0x02U /* rx fifo overflow */ +#define NSR_RXRDY 0x01U /* rx packet ready */ + +#define NSR_BITS \ + "\020" \ + "\010SPEED_10" \ + "\007LINKST_UP" \ + "\006WAKEST" \ + "\005TXFULL" \ + "\004TX2END" \ + "\003TX1END" \ + "\002RXOV" \ + "\001RXRDY" + +/* tx control register */ +#define TCR_TJDIS 0x40U /* tx jitter control */ +#define TCR_EXCEDM 0x20U /* excessive collision mode */ +#define TCR_PAD_DIS2 0x10U /* PAD appends disable for pkt2 */ +#define TCR_CRC_DIS2 0x08U /* CRC appends disable for pkt2 */ +#define TCR_PAD_DIS1 0x04U /* PAD appends disable for pkt1 */ +#define TCR_CRC_DIS1 0x02U /* CRC appends disable for pkt1 */ + +#define TCR_BITS \ + "\020" \ + "\007TJDIS" \ + "\006EXCEDM" \ + "\005PAD_DIS2" \ + "\004CRC_DIS2" \ + "\003PAD_DIS1" \ + "\002CRC_DIS1" + +/* tx status register (ro) */ +#define TSR_TJTO 0x80U /* tx jabber time out */ +#define TSR_LC 0x40U /* loss of carrier */ +#define TSR_NC 0x20U /* no carrier */ +#define TSR_LATEC 0x10U /* late collision */ +#define TSR_COL 0x08U /* late collision */ +#define TSR_EL 0x04U /* excessive collision */ + +#define TSR_BITS \ + "\020" \ + "\010TJTO" \ + "\007LC" \ + "\006NC" \ + "\005LATEC" \ + "\004COL" \ + "\003EL" + +/* rx control register */ +#define RCR_WTDIS 0x40U /* watch dog timer disable */ +#define RCR_DIS_LONG 0x20U /* discard longer packets than 1522 */ +#define RCR_DIS_CRC 0x10U /* discard crc error packets */ +#define RCR_ALL 0x08U /* pass all multicast */ +#define RCR_RUNT 0x04U /* pass runt packets */ +#define RCR_PRMSC 0x02U /* promiscuous mode */ +#define RCR_RXEN 0x01U /* rx enable */ + +#define RCR_BITS \ + "\020" \ + "\007WTDIS" \ + "\006DIS_LONG" \ + "\005DIS_CRC" \ + "\004ALL" \ + "\003RUNT" \ + "\002PRMSC" \ + "\001RXEN" + +/* rx status register */ +#define RSR_RF 0x80U /* runt frame */ +#define RSR_MF 0x40U /* multicast frame */ +#define RSR_LCS 0x20U /* late collision seen */ +#define RSR_RWTO 0x10U /* receive watchdog timeout */ +#define RSR_PLE 0x08U /* physical layer error */ +#define RSR_AE 0x04U /* alignment error */ +#define RSR_CE 0x02U /* crc error */ +#define RSR_FOE 0x01U /* fifo overflow error */ + +#define RSR_BITS \ + "\020" \ + "\010RF" \ + "\007MF" \ + "\006LCS" \ + "\005RWTO" \ + "\004PLE" \ + "\003AE" \ + "\002CE" \ + "\001FOE" + +/* receive overflow counter register */ +#define ROCR_RXFU 0x80U /* receive overflow counter overflow */ +#define ROCR_ROC 0x7fU /* receive overflow counter */ + +#define ROCR_BITS \ + "\020" \ + "\010RXFU" + +/* back pressure threshold register */ +#define BPTR_BPHW 0xf0U /* high water overflow threshold */ +#define BPTR_BPHW_SHIFT 4 +#define BPTR_BPHW_UNIT 1024U +#define BPTR_BPHW_DEFAULT (3 << BPTR_BPHW_SHIFT) /* 3k */ +#define BPTR_JPT 0x0fU /* jam pattern time */ +#define BPTR_JPT_SHIFT 0 +#define BPTR_JPT_5us (0U << BPTR_JPT_SHIFT) +#define BPTR_JPT_10us (1U << BPTR_JPT_SHIFT) +#define BPTR_JPT_15us (2U << BPTR_JPT_SHIFT) +#define BPTR_JPT_25us (3U << BPTR_JPT_SHIFT) +#define BPTR_JPT_50us (4U << BPTR_JPT_SHIFT) +#define BPTR_JPT_100us (5U << BPTR_JPT_SHIFT) +#define BPTR_JPT_150us (6U << BPTR_JPT_SHIFT) +#define BPTR_JPT_200us (7U << BPTR_JPT_SHIFT) +#define BPTR_JPT_250us (8U << BPTR_JPT_SHIFT) +#define BPTR_JPT_300us (9U << BPTR_JPT_SHIFT) +#define BPTR_JPT_350us (10U << BPTR_JPT_SHIFT) +#define BPTR_JPT_400us (11U << BPTR_JPT_SHIFT) +#define BPTR_JPT_450us (12U << BPTR_JPT_SHIFT) +#define BPTR_JPT_500us (13U << BPTR_JPT_SHIFT) +#define BPTR_JPT_550us (14U << BPTR_JPT_SHIFT) +#define BPTR_JPT_600us (15U << BPTR_JPT_SHIFT) + +/* flow control threshold register */ +#define FCTR_HWOT 0xf0U /* rx fifo high water overflow threshold */ +#define FCTR_HWOT_SHIFT 4 +#define FCTR_HWOT_UNIT 1024U +#define FCTR_LWOT 0x0fU /* rx fifo low water overflow threshold */ +#define FCTR_LWOT_SHIFT 0 +#define FCTR_LWOT_UNIT 1024U + +/* rx/tx flow control register */ +#define FCR_TXPO 0x80U /* tx pause packet */ +#define FCR_TXPF 0x40U /* tx pause packet */ +#define FCR_TXPEN 0x20U /* tx pause packet */ +#define FCR_BKPA 0x10U /* back pressure mode */ +#define FCR_BKPM 0x08U /* back pressure mode */ +#define FCR_BKPS 0x04U /* rx pause packet current status (r/c) */ +#define FCR_RXPCS 0x02U /* rx pause packet current status (ro) */ +#define FCR_FLCE 0x01U /* flow control enbale */ + +#define FCR_BITS \ + "\020" \ + "\000TXPO" \ + "\000TXPF" \ + "\000TXPEN" \ + "\000BKPA" \ + "\000BKPM" \ + "\000BKPS" \ + "\000RXPCS" \ + "\000FLCE" + +/* EEPROM & PHY control register (0x0b) */ +#define EPCR_REEP 0x20U /* reload eeprom */ +#define EPCR_WEP 0x10U /* write eeprom enable */ +#define EPCR_EPOS 0x08U /* select device, 0:eeprom, 1:phy */ +#define EPCR_ERPRR 0x04U /* read command */ +#define EPCR_ERPRW 0x02U /* write command */ +#define EPCR_ERRE 0x01U /* eeprom/phy access in progress (ro) */ + +#define EPCR_BITS \ + "\020" \ + "\005REEP" \ + "\004WEP" \ + "\003EPOS" \ + "\002ERPRR" \ + "\001ERPRW" \ + "\000ERRE" + +/* EEPROM & PHY access register (0x0c) */ +#define EPAR_PHYADR 0xc0U /* phy address, internal phy(1) or external */ +#define EPAR_PHYADR_SHIFT 6 +#define EPAR_EROA 0x3fU /* eeprom word addr or phy register addr */ +#define EPAR_EROA_SHIFT 0 + +/* EEPROM & PHY data register (0x0d(low)-0x0e(hi)) */ + +/* wake up control register (0x0f) */ +#define WCR_LINKEN 0x20U /* enable link status event */ +#define WCR_SAMPLEEN 0x10U /* enable sample frame event */ +#define WCR_MAGICEN 0x08U /* enable magic pkt event */ +#define WCR_LINKST 0x04U /* link status change occur ro */ +#define WCR_SAMPLEST 0x02U /* sample frame rx occur ro */ +#define WCR_MAGICST 0x01U /* magic pkt rx occur ro */ + +#define WCR_BITS \ + "\020" \ + "\000LINKEN" \ + "\000SAMPLEEN" \ + "\000MAGICEN" \ + "\000LINKST" \ + "\000SAMPLEST" \ + "\000MAGICST" + +/* physical address register (0x10-0x15) */ +/* multicast address register (0x16-0x1c) */ +/* general purpose control register (0x1e) */ +#define GPCR_GEPCTRL 0x7f +#define GPCR_OUT(n) (1U << (n)) + +#define GPCR_BITS \ + "\020" \ + "\006OUT5" \ + "\005OUT4" \ + "\004OUT3" \ + "\003OUT2" \ + "\002OUT1" \ + "\001OUT0" + +/* general purpose register (0x1f) */ +#define GPR_GEPIO5 0x20U +#define GPR_GEPIO4 0x10U +#define GPR_GEPIO3 0x08U +#define GPR_GEPIO2 0x04U +#define GPR_GEPIO1 0x02U +#define GPR_GEPIO0 0x01U + +#define GPR_BITS \ + "\020" \ + "\006GEPIO5" \ + "\005GEPIO4" \ + "\004GEPIO3" \ + "\003GEPIO2" \ + "\002GEPIO1" \ + "\001GEPIO0" + +/* vendor id register (0x28-0x29) */ +/* product id register (0x2a-0x2b) */ +/* chip revision register (0x2c) */ + +/* usb device address register (0xf0) */ +#define USBDA_USBFA 0x3fU /* usb device address */ +#define USBDA_USBFA_SHIFT 0 + +/* receive packet counter register (0xf1) */ + +/* transmitpacket counter/usb status register (0xf2) */ +#define TUSR_RXFAULT 0x80U /* indicate rx has unexpected condition */ +#define TUSR_SUSFLAG 0x40U /* indicate device has suspended condition */ +#define TUSR_EP1RDY 0x20U /* ready for read from ep1 pipe */ +#define TUSR_SRAM 0x18U /* sram size 0:32K, 1:48K, 2:16K, 3:64K */ +#define TUSR_SRAM_SHIFT 3 +#define TUSR_SRAM_32K (0U << TUSR_SRAM_SHIFT) +#define TUSR_SRAM_48K (1U << TUSR_SRAM_SHIFT) +#define TUSR_SRAM_16K (2U << TUSR_SRAM_SHIFT) +#define TUSR_SRAM_64K (3U << TUSR_SRAM_SHIFT) +#define TUSR_TXC2 0x04U /* two or more packets in tx buffer */ +#define TUSR_TXC1 0x02U /* one packet in tx buffer */ +#define TUSR_TXC0 0x01U /* no packet in tx buffer */ + +#define TUSR_BITS \ + "\020" \ + "\010RXFAULT" \ + "\007SUSFLAG" \ + "\006EP1RDY" \ + "\003TXC2" \ + "\002TXC1" \ + "\001TXC0" + +/* usb control register (0xf4) */ +#define USBC_EP3ACK 0x20U /* ep3 will alway return 8byte data if NAK=0*/ +#define USBC_EP3NACK 0x10U /* ep3 will alway return NAK */ +#define USBC_MEMTST 0x01U + +/* bulk message format */ +#define TX_HEADER_SIZE 2 +#define RX_HEADER_SIZE 3 + +/* interrupt msg format */ +struct intr_msg { + uint8_t im_nsr; + uint8_t im_tsr1; + uint8_t im_tsr2; + uint8_t im_rsr; + uint8_t im_rocr; + uint8_t im_rxc; + uint8_t im_txc; + uint8_t im_gpr; +}; +#endif /* __DM9601_H__ */ diff --git a/usr/src/uts/common/io/udmf/udmf_usbgem.c b/usr/src/uts/common/io/udmf/udmf_usbgem.c new file mode 100644 index 0000000000..0637de054b --- /dev/null +++ b/usr/src/uts/common/io/udmf/udmf_usbgem.c @@ -0,0 +1,1036 @@ +/* + * udmfE_usbgem.c : Davicom DM9601E USB to Fast Ethernet Driver for Solaris + * + * Copyright (c) 2009-2012 Masayuki Murayama. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#pragma ident "%W% %E%" + +/* + * Changelog: + */ + +/* + * TODO + */ +/* ======================================================= */ + +/* + * Solaris system header files and macros + */ + +/* minimum kernel headers for drivers */ +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/debug.h> +#include <sys/kmem.h> +#include <sys/modctl.h> +#include <sys/errno.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/byteorder.h> + +/* ethernet stuff */ +#include <sys/ethernet.h> + +/* interface card depend stuff */ +#include <sys/stropts.h> +#include <sys/stream.h> +#include <sys/strlog.h> +#include <sys/usb/usba.h> +#include "usbgem.h" + +/* hardware stuff */ +#include "usbgem_mii.h" +#include "dm9601reg.h" + +char ident[] = "dm9601 usbnic driver v" VERSION; + +/* + * Useful macros + */ +#define CHECK_AND_JUMP(err, label) if (err != USB_SUCCESS) goto label +#define LE16P(p) ((((uint8_t *)(p))[1] << 8) | ((uint8_t *)(p))[0]) + +/* + * Debugging + */ +#ifdef DEBUG_LEVEL +static int udmf_debug = DEBUG_LEVEL; +#define DPRINTF(n, args) if (udmf_debug > (n)) cmn_err args +#else +#define DPRINTF(n, args) +#endif + +/* + * Our configration for dm9601 + */ +/* timeouts */ +#define ONESEC (drv_usectohz(1*1000000)) + +/* + * Local device definitions + */ +struct udmf_dev { + /* + * Misc HW information + */ + uint8_t rcr; + uint8_t last_nsr; + uint8_t mac_addr[ETHERADDRL]; +}; + +/* + * private functions + */ + +/* mii operations */ +static uint16_t udmf_mii_read(struct usbgem_dev *, uint_t, int *errp); +static void udmf_mii_write(struct usbgem_dev *, uint_t, uint16_t, int *errp); + +/* nic operations */ +static int udmf_reset_chip(struct usbgem_dev *); +static int udmf_init_chip(struct usbgem_dev *); +static int udmf_start_chip(struct usbgem_dev *); +static int udmf_stop_chip(struct usbgem_dev *); +static int udmf_set_media(struct usbgem_dev *); +static int udmf_set_rx_filter(struct usbgem_dev *); +static int udmf_get_stats(struct usbgem_dev *); +static void udmf_interrupt(struct usbgem_dev *, mblk_t *); + +/* packet operations */ +static mblk_t *udmf_tx_make_packet(struct usbgem_dev *, mblk_t *); +static mblk_t *udmf_rx_make_packet(struct usbgem_dev *, mblk_t *); + +/* =============================================================== */ +/* + * I/O functions + */ +/* =============================================================== */ +#define OUT(dp, ix, len, buf, errp, label) \ + if ((*(errp) = usbgem_ctrl_out((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ 1, \ + /* wValue */ 0, \ + /* wIndex */ (ix), \ + /* wLength */ (len), \ + /* value */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +#define OUTB(dp, ix, val, errp, label) \ + if ((*(errp) = usbgem_ctrl_out((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ 3, \ + /* wValue */ (val), \ + /* wIndex */ (ix), \ + /* wLength */ 0, \ + /* value */ NULL, \ + /* size */ 0)) != USB_SUCCESS) goto label + +#define IN(dp, ix, len, buf, errp, label) \ + if ((*(errp) = usbgem_ctrl_in((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ 0, \ + /* wValue */ 0, \ + /* wIndex */ (ix), \ + /* wLength */ (len), \ + /* valuep */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +/* =============================================================== */ +/* + * Hardware manupilation + */ +/* =============================================================== */ +static void +udmf_enable_phy(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + + /* de-assert reset signal to phy */ + OUTB(dp, GPCR, GPCR_OUT(0), &err, usberr); + OUTB(dp, GPR, 0, &err, usberr); +usberr: + ; +} + +static int +udmf_reset_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + OUTB(dp, NCR, NCR_LBK_NORMAL | NCR_RST, &err, usberr); + drv_usecwait(100); +usberr: + return (err); +} + +/* + * Setup dm9601 + */ +static int +udmf_init_chip(struct usbgem_dev *dp) +{ + int i; + uint32_t val; + int err = USB_SUCCESS; + uint16_t reg; + uint8_t buf[2]; + struct udmf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + OUTB(dp, NCR, NCR_LBK_NORMAL, &err, usberr); + + /* tx control regiser: enable padding and crc generation */ + OUTB(dp, TCR, 0, &err, usberr); + + /* rx control register: will be set later by udmf_set_rx_filer() */ + lp->rcr = RCR_RUNT; + + /* back pressure threshold: */ + OUTB(dp, BPTR, (2 << BPTR_BPHW_SHIFT) | BPTR_JPT_200us, + &err, usberr); + + /* flow control threshold: same as default */ + OUTB(dp, FCTR, (3 << FCTR_HWOT_SHIFT) | (8 << FCTR_LWOT_SHIFT), + &err, usberr); + + /* usb control register */ + OUTB(dp, USBC, USBC_EP3ACK | 0x06, &err, usberr); + + /* flow control: will be set later by udmf_set_media() */ + + /* wake up control register: */ + OUTB(dp, WCR, 0, &err, usberr); + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +static int +udmf_start_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + struct udmf_dev *lp = dp->private; + + /* enable Rx */ + lp->rcr |= RCR_RXEN; + OUTB(dp, RCR, lp->rcr, &err, usberr); + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +static int +udmf_stop_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + struct udmf_dev *lp = dp->private; + + /* disable rx */ + lp->rcr &= ~RCR_RXEN; + OUTB(dp, RCR, lp->rcr, &err, usberr); + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +static int +udmf_get_stats(struct usbgem_dev *dp) +{ + /* EMPTY */ + return (USB_SUCCESS); +} + +static uint_t +udmf_mcast_hash(struct usbgem_dev *dp, const uint8_t *addr) +{ + return (usbgem_ether_crc_le(addr) & 0x3f); +} + +static int +udmf_set_rx_filter(struct usbgem_dev *dp) +{ + int i; + uint8_t rcr; + uint8_t mode; + uint8_t mhash[8]; + uint8_t *mac; + uint_t h; + int err = USB_SUCCESS; + struct udmf_dev *lp = dp->private; + static uint8_t invalid_mac[ETHERADDRL] = {0, 0, 0, 0, 0, 0}; + + DPRINTF(2, (CE_CONT, "!%s: %s: called, rxmode:%x", + dp->name, __func__, dp->rxmode)); + + if (lp->rcr & RCR_RXEN) { + /* set promiscuous mode before changing rx filter mode */ + OUTB(dp, RCR, lp->rcr | RCR_PRMSC, &err, usberr); + } + + lp->rcr &= ~(RCR_ALL | RCR_PRMSC); + mode = 0; + bzero(mhash, sizeof (mhash)); + mac = dp->cur_addr.ether_addr_octet; + + if ((dp->rxmode & RXMODE_ENABLE) == 0) { + mac = invalid_mac; + } else if (dp->rxmode & RXMODE_PROMISC) { + /* promiscious mode implies all multicast and all physical */ + mode |= RCR_PRMSC; + } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 32) { + /* accept all multicast packets */ + mode |= RCR_ALL; + } else if (dp->mc_count > 0) { + /* + * make hash table to select interresting + * multicast address only. + */ + for (i = 0; i < dp->mc_count; i++) { + /* hash table is 64 = 2^6 bit width */ + h = dp->mc_list[i].hash; + mhash[h / 8] |= 1 << (h % 8); + } + } + + /* set node address */ + if (bcmp(mac, lp->mac_addr, ETHERADDRL) != 0) { + OUT(dp, PAR, ETHERADDRL, dp->cur_addr.ether_addr_octet, + &err, usberr); + bcopy(mac, lp->mac_addr, ETHERADDRL); + } + + /* set multicast hash table */ + OUT(dp, MAR, sizeof (mhash), &mhash[0], &err, usberr); + + /* update rcr */ + lp->rcr |= mode; + OUTB(dp, RCR, lp->rcr, &err, usberr); + +#if DEBUG_LEVEL > 1 + /* verify rcr */ + IN(dp, RCR, 1, &rcr, &err, usberr); + cmn_err(CE_CONT, "!%s: %s: rcr:%b returned", + dp->name, __func__, rcr, RCR_BITS); +#endif +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +static int +udmf_set_media(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + uint8_t fcr; + struct udmf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* setup flow control */ + fcr = 0; + if (dp->full_duplex) { + /* select flow control */ + switch (dp->flow_control) { + case FLOW_CONTROL_RX_PAUSE: + fcr |= FCR_FLCE; + break; + + case FLOW_CONTROL_TX_PAUSE: + fcr |= FCR_TXPEN; + break; + + case FLOW_CONTROL_SYMMETRIC: + fcr |= FCR_FLCE | FCR_TXPEN; + break; + } + } + + /* update flow control register */ + OUTB(dp, FCR, fcr, &err, usberr); + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + err, err == USB_SUCCESS ? "success" : "error")); + return (err); +} + +/* + * send/receive packet check + */ +static mblk_t * +udmf_tx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + int n; + size_t pkt_size; + mblk_t *new; + mblk_t *tp; + uint8_t *bp; + uint8_t *last_pos; + uint_t align_mask; + + pkt_size = msgdsize(mp); + align_mask = 63; + + /* + * re-allocate the mp + */ + + /* minimum ethernet packet size of ETHERMIN */ + pkt_size = max(pkt_size, ETHERMIN); + +#if 0 /* CONFIG_ADD_TX_DELIMITOR_ALWAYS */ + pkt_size += TX_HEADER_SIZE; +#endif + if (((pkt_size + TX_HEADER_SIZE) & align_mask) == 0) { + /* padding is required in usb communication */ + pkt_size += TX_HEADER_SIZE; + } + + if ((new = allocb(TX_HEADER_SIZE + pkt_size, 0)) == NULL) { + return (NULL); + } + new->b_wptr = new->b_rptr + TX_HEADER_SIZE + pkt_size; + + /* add a header */ + bp = new->b_rptr; + bp[0] = (uint8_t)pkt_size; + bp[1] = (uint8_t)(pkt_size >> 8); + bp += TX_HEADER_SIZE; + + /* copy contents of the buffer */ + for (tp = mp; tp; tp = tp->b_cont) { + n = tp->b_wptr - tp->b_rptr; + bcopy(tp->b_rptr, bp, n); + bp += n; + } + + /* clear the rest including the next zero length header */ + last_pos = new->b_wptr; + while (bp < last_pos) { + *bp++ = 0; + } + + return (new); +} + +static void +udmf_dump_packet(struct usbgem_dev *dp, uint8_t *bp, int n) +{ + int i; + + for (i = 0; i < n; i += 8, bp += 8) { + cmn_err(CE_CONT, "%02x %02x %02x %02x %02x %02x %02x %02x", + bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7]); + } +} + +static mblk_t * +udmf_rx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + int len; + uint8_t rx_stat; + + len = mp->b_wptr - mp->b_rptr; + + if (len <= RX_HEADER_SIZE) { + /* + * the usb bulk-in frame doesn't include a valid + * ethernet packet. + */ + return (NULL); + } + + /* remove rx header */ + rx_stat = mp->b_rptr[0]; + if (rx_stat & (RSR_RF | RSR_LCS | RSR_RWTO | + RSR_PLE | RSR_AE | RSR_CE | RSR_FOE)) { + if (rx_stat & RSR_RF) { + dp->stats.runt++; + } + if (rx_stat & RSR_LCS) { + /* late collision */ + dp->stats.rcv_internal_err++; + } + if (rx_stat & RSR_RWTO) { + /* rx timeout */ + dp->stats.rcv_internal_err++; + } + if (rx_stat & RSR_PLE) { + /* physical layer error */ + dp->stats.rcv_internal_err++; + } + if (rx_stat & RSR_AE) { + /* alignment error */ + dp->stats.frame++; + } + if (rx_stat & RSR_CE) { + /* crc error */ + dp->stats.crc++; + } + if (rx_stat & RSR_FOE) { + /* fifo overflow error */ + dp->stats.overflow++; + } + dp->stats.errrcv++; + } + len = LE16P(&mp->b_rptr[1]); + if (len >= ETHERFCSL) { + len -= ETHERFCSL; + } + mp->b_rptr += RX_HEADER_SIZE; + mp->b_wptr = mp->b_rptr + len; + + return (mp); +} + +/* + * MII Interfaces + */ +static uint16_t +udmf_ep_read(struct usbgem_dev *dp, uint_t which, uint_t addr, int *errp) +{ + int i; + uint8_t epcr; + uint16_t val; + + DPRINTF(4, (CE_CONT, "!%s: %s: called, ix:%d", + dp->name, __func__, addr)); + + OUTB(dp, EPAR, addr, errp, usberr); + OUTB(dp, EPCR, which | EPCR_ERPRR, errp, usberr); + + for (i = 0; i < 100; i++) { + IN(dp, EPCR, sizeof (epcr), &epcr, errp, usberr); + if ((epcr & EPCR_ERRE) == 0) { + /* done */ + IN(dp, EPDR, sizeof (val), &val, errp, usberr); + val = LE_16(val); + goto done; + } + drv_usecwait(10); + } + /* timeout */ + cmn_err(CE_WARN, "!%s: %s: timeout", dp->name, __func__); + val = 0; +done: + OUTB(dp, EPCR, 0, errp, usberr); + return (val); + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + *errp, *errp == USB_SUCCESS ? "success" : "error")); + return (0); +} + +static void +udmf_ep_write(struct usbgem_dev *dp, uint_t which, uint_t addr, + uint16_t val, int *errp) +{ + int i; + uint8_t epcr; + + DPRINTF(5, (CE_CONT, "!%s: %s called", dp->name, __func__)); + + val = LE_16(val); + OUT(dp, EPDR, sizeof (val), &val, errp, usberr); + + OUTB(dp, EPAR, addr, errp, usberr); + + OUTB(dp, EPCR, which | EPCR_WEP | EPCR_ERPRW, errp, usberr); + + for (i = 0; i < 100; i++) { + IN(dp, EPCR, 1, &epcr, errp, usberr); + if ((epcr & EPCR_ERRE) == 0) { + /* done */ + goto done; + } + drv_usecwait(10); + } + /* timeout */ + cmn_err(CE_WARN, "!%s: %s: timeout", dp->name, __func__); +done: + OUTB(dp, EPCR, 0, errp, usberr); + return; + +usberr: + DPRINTF(2, (CE_CONT, "!%s: %s: end err:%d(%s)", + dp->name, __func__, + *errp, *errp == USB_SUCCESS ? "success" : "error")); +} + +static uint16_t +udmf_mii_read(struct usbgem_dev *dp, uint_t index, int *errp) +{ + uint16_t val; + + val = udmf_ep_read(dp, EPCR_EPOS, + (dp->mii_phy_addr << EPAR_PHYADR_SHIFT) | index, errp); + + return (val); +} + +static void +udmf_mii_write(struct usbgem_dev *dp, uint_t index, uint16_t val, int *errp) +{ + udmf_ep_write(dp, EPCR_EPOS, + (dp->mii_phy_addr << EPAR_PHYADR_SHIFT) | index, val, errp); +} + +static void +udmf_interrupt(struct usbgem_dev *dp, mblk_t *mp) +{ + struct intr_msg *imp; + struct udmf_dev *lp = dp->private; + + imp = (struct intr_msg *)&mp->b_rptr[0]; + + DPRINTF(4, (CE_CONT, + "!%s: %s: size:%d, nsr:%b tsr1:%b tsr2:%b" + " rsr:%b rocr:%b rxc:%02x txc:%b gpr:%b", + dp->name, __func__, mp->b_wptr - mp->b_rptr, + imp->im_nsr, NSR_BITS, + imp->im_tsr1, TSR_BITS, + imp->im_tsr2, TSR_BITS, + imp->im_rsr, RSR_BITS, + imp->im_rocr, ROCR_BITS, + imp->im_rxc, + imp->im_txc, TUSR_BITS, + imp->im_gpr, GPR_BITS)); + + if ((lp->last_nsr ^ imp->im_nsr) & NSR_LINKST) { + usbgem_mii_update_link(dp); + } + + lp->last_nsr = imp->im_nsr; +} + +/* ======================================================== */ +/* + * OS depend (device driver DKI) routine + */ +/* ======================================================== */ +static uint16_t +udmf_eeprom_read(struct usbgem_dev *dp, uint_t index, int *errp) +{ + uint16_t val; + + val = udmf_ep_read(dp, 0, index, errp); + + return (val); +} + +#ifdef DEBUG_LEVEL +static void +udmf_eeprom_dump(struct usbgem_dev *dp, int size) +{ + int i; + int err; + uint16_t w0, w1, w2, w3; + + cmn_err(CE_CONT, "!%s: eeprom dump:", dp->name); + + err = USB_SUCCESS; + + for (i = 0; i < size; i += 4) { + w0 = udmf_eeprom_read(dp, i + 0, &err); + w1 = udmf_eeprom_read(dp, i + 1, &err); + w2 = udmf_eeprom_read(dp, i + 2, &err); + w3 = udmf_eeprom_read(dp, i + 3, &err); + cmn_err(CE_CONT, "!0x%02x: 0x%04x 0x%04x 0x%04x 0x%04x", + i, w0, w1, w2, w3); + } +usberr: + ; +} +#endif + +static int +udmf_attach_chip(struct usbgem_dev *dp) +{ + int i; + uint_t val; + uint8_t *m; + int err; + struct udmf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s enter", dp->name, __func__)); + + /* + * get mac address from EEPROM + */ + m = dp->dev_addr.ether_addr_octet; + for (i = 0; i < ETHERADDRL; i += 2) { + val = udmf_eeprom_read(dp, i/2, &err); + m[i + 0] = (uint8_t)val; + m[i + 1] = (uint8_t)(val >> 8); + } + + /* invalidate a private cache for mac addr */ + bzero(lp->mac_addr, sizeof (lp->mac_addr)); +#ifdef CONFIG_VLAN + dp->misc_flag = USBGEM_VLAN; +#endif +#if DEBUG_LEVEL > 0 + udmf_eeprom_dump(dp, /* 0x3f + 1 */ 128); +#endif +{ + static uint8_t bcst[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + DPRINTF(0, (CE_CONT, "!%s: %s: hash of bcast:%x", + dp->name, __func__, usbgem_ether_crc_be(bcst))); +} + return (USB_SUCCESS); + +usberr: + cmn_err(CE_WARN, "%s: %s: usb error detected (%d)", + dp->name, __func__, err); + return (USB_FAILURE); +} + +static int +udmf_mii_probe(struct usbgem_dev *dp) +{ + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + udmf_enable_phy(dp); + return (usbgem_mii_probe_default(dp)); +} + +static int +udmf_mii_init(struct usbgem_dev *dp) +{ + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + udmf_enable_phy(dp); + return (USB_SUCCESS); +} + +static int +udmfattach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int i; + ddi_iblock_cookie_t c; + int ret; + int revid; + int unit; + int len; + const char *drv_name; + struct usbgem_dev *dp; + void *base; + struct usbgem_conf *ugcp; + struct udmf_dev *lp; + + unit = ddi_get_instance(dip); + drv_name = ddi_driver_name(dip); + + DPRINTF(3, (CE_CONT, "!%s%d: %s: called, cmd:%d", + drv_name, unit, __func__, cmd)); + + if (cmd == DDI_ATTACH) { + /* + * construct usbgem configration + */ + ugcp = kmem_zalloc(sizeof (*ugcp), KM_SLEEP); + + /* name */ + /* + * softmac requires that ppa is the instance number + * of the device, otherwise it hangs in seaching the device. + */ + sprintf(ugcp->usbgc_name, "%s%d", drv_name, unit); + ugcp->usbgc_ppa = unit; + + ugcp->usbgc_ifnum = 0; + ugcp->usbgc_alt = 0; + + ugcp->usbgc_tx_list_max = 64; + + ugcp->usbgc_rx_header_len = RX_HEADER_SIZE; + ugcp->usbgc_rx_list_max = 64; + + /* time out parameters */ + ugcp->usbgc_tx_timeout = USBGEM_TX_TIMEOUT; + ugcp->usbgc_tx_timeout_interval = USBGEM_TX_TIMEOUT_INTERVAL; +#if 1 + /* flow control */ + ugcp->usbgc_flow_control = FLOW_CONTROL_RX_PAUSE; +#else + /* + * XXX - flow control caused link down frequently under + * heavy traffic + */ + ugcp->usbgc_flow_control = FLOW_CONTROL_NONE; +#endif + /* MII timeout parameters */ + ugcp->usbgc_mii_link_watch_interval = + USBGEM_LINK_WATCH_INTERVAL; + ugcp->usbgc_mii_an_watch_interval = + USBGEM_LINK_WATCH_INTERVAL/5; + ugcp->usbgc_mii_reset_timeout = MII_RESET_TIMEOUT; /* 1 sec */ + ugcp->usbgc_mii_an_timeout = MII_AN_TIMEOUT; /* 5 sec */ + ugcp->usbgc_mii_an_wait = (25*ONESEC)/10; + ugcp->usbgc_mii_linkdown_timeout = MII_LINKDOWN_TIMEOUT; + + ugcp->usbgc_mii_an_delay = ONESEC/10; + ugcp->usbgc_mii_linkdown_action = MII_ACTION_RSA; + ugcp->usbgc_mii_linkdown_timeout_action = MII_ACTION_RESET; + ugcp->usbgc_mii_dont_reset = B_FALSE; + ugcp->usbgc_mii_hw_link_detection = B_TRUE; + + /* I/O methods */ + + /* mac operation */ + ugcp->usbgc_attach_chip = &udmf_attach_chip; + ugcp->usbgc_reset_chip = &udmf_reset_chip; + ugcp->usbgc_init_chip = &udmf_init_chip; + ugcp->usbgc_start_chip = &udmf_start_chip; + ugcp->usbgc_stop_chip = &udmf_stop_chip; + ugcp->usbgc_multicast_hash = &udmf_mcast_hash; + + ugcp->usbgc_set_rx_filter = &udmf_set_rx_filter; + ugcp->usbgc_set_media = &udmf_set_media; + ugcp->usbgc_get_stats = &udmf_get_stats; + ugcp->usbgc_interrupt = &udmf_interrupt; + + /* packet operation */ + ugcp->usbgc_tx_make_packet = &udmf_tx_make_packet; + ugcp->usbgc_rx_make_packet = &udmf_rx_make_packet; + + /* mii operations */ + ugcp->usbgc_mii_probe = &udmf_mii_probe; + ugcp->usbgc_mii_init = &udmf_mii_init; + ugcp->usbgc_mii_config = &usbgem_mii_config_default; + ugcp->usbgc_mii_read = &udmf_mii_read; + ugcp->usbgc_mii_write = &udmf_mii_write; + ugcp->usbgc_mii_addr_min = 1; + + /* mtu */ + ugcp->usbgc_min_mtu = ETHERMTU; + ugcp->usbgc_max_mtu = ETHERMTU; + ugcp->usbgc_default_mtu = ETHERMTU; + + lp = kmem_zalloc(sizeof (struct udmf_dev), KM_SLEEP); + lp->last_nsr; + + ddi_set_driver_private(dip, NULL); + + dp = usbgem_do_attach(dip, ugcp, lp, sizeof (struct udmf_dev)); + + kmem_free(ugcp, sizeof (*ugcp)); + + if (dp != NULL) { + return (DDI_SUCCESS); + } + +err_free_mem: + kmem_free(lp, sizeof (struct udmf_dev)); +err_close_pipe: +err: + return (DDI_FAILURE); + } + + if (cmd == DDI_RESUME) { + return (usbgem_resume(dip)); + } + + return (DDI_FAILURE); +} + +static int +udmfdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int ret; + + if (cmd == DDI_DETACH) { + ret = usbgem_do_detach(dip); + if (ret != DDI_SUCCESS) { + return (DDI_FAILURE); + } + return (DDI_SUCCESS); + } + if (cmd == DDI_SUSPEND) { + return (usbgem_suspend(dip)); + } + return (DDI_FAILURE); +} + +/* ======================================================== */ +/* + * OS depend (loadable streams driver) routine + */ +/* ======================================================== */ +#ifdef USBGEM_CONFIG_GLDv3 +USBGEM_STREAM_OPS(udmf_ops, udmfattach, udmfdetach); +#else +static struct module_info udmfminfo = { + 0, /* mi_idnum */ + "udmf", /* mi_idname */ + 0, /* mi_minpsz */ + ETHERMTU, /* mi_maxpsz */ + ETHERMTU*128, /* mi_hiwat */ + 1, /* mi_lowat */ +}; + +static struct qinit udmfrinit = { + (int (*)()) NULL, /* qi_putp */ + usbgem_rsrv, /* qi_srvp */ + usbgem_open, /* qi_qopen */ + usbgem_close, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &udmfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct qinit udmfwinit = { + usbgem_wput, /* qi_putp */ + usbgem_wsrv, /* qi_srvp */ + (int (*)()) NULL, /* qi_qopen */ + (int (*)()) NULL, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &udmfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct streamtab udmf_info = { + &udmfrinit, /* st_rdinit */ + &udmfwinit, /* st_wrinit */ + NULL, /* st_muxrinit */ + NULL /* st_muxwrinit */ +}; + +static struct cb_ops cb_udmf_ops = { + nulldev, /* cb_open */ + nulldev, /* 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 */ + &udmf_info, /* cb_stream */ + D_NEW|D_MP /* cb_flag */ +}; + +static struct dev_ops udmf_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + usbgem_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + udmfattach, /* devo_attach */ + udmfdetach, /* devo_detach */ + nodev, /* devo_reset */ + &cb_udmf_ops, /* devo_cb_ops */ + NULL, /* devo_bus_ops */ + usbgem_power, /* devo_power */ +#if DEVO_REV >= 4 + usbgem_quiesce, /* devo_quiesce */ +#endif +}; +#endif + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + ident, + &udmf_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, &modldrv, NULL +}; + +/* ======================================================== */ +/* + * _init : done + */ +/* ======================================================== */ +int +_init(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!udmf: _init: called")); + + status = usbgem_mod_init(&udmf_ops, "udmf"); + if (status != DDI_SUCCESS) { + return (status); + } + status = mod_install(&modlinkage); + if (status != DDI_SUCCESS) { + usbgem_mod_fini(&udmf_ops); + } + return (status); +} + +/* + * _fini : done + */ +int +_fini(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!udmf: _fini: called")); + status = mod_remove(&modlinkage); + if (status == DDI_SUCCESS) { + usbgem_mod_fini(&udmf_ops); + } + return (status); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} diff --git a/usr/src/uts/common/io/upf/adm8511reg.h b/usr/src/uts/common/io/upf/adm8511reg.h new file mode 100644 index 0000000000..68a2207bb5 --- /dev/null +++ b/usr/src/uts/common/io/upf/adm8511reg.h @@ -0,0 +1,205 @@ +/* + * @(#)adm8511reg.h 1.1 09/06/20 + * Register dehinitsions of ADMtek ADM8511 Fast Ethernet to USB controller. + * Codeded by Masayuki Murayama(KHF04453@nifty.ne.jp) + * This file is public domain. + */ + +#define EC0 0x00 /* B */ +#define EC1 0x01 /* B */ +#define EC2 0x02 /* B */ +#define MA 0x08 /* 8byte array */ +#define EID 0x10 /* B */ +#define PAUSETIMER 0x18 /* B pause timer */ +#define RPNBFC 0x1a /* B */ +#define ORFBFC 0x1b /* B */ +#define EP1C 0x1c /* B */ +#define RXFC 0x1d /* B */ +#define BIST 0x1e /* B */ +#define EEOFFSET 0x20 /* B */ +#define EEDATA 0x21 /* W */ +#define EECTRL 0x23 /* B */ +#define PHYA 0x25 /* B */ +#define PHYD 0x26 /* W */ +#define PHYAC 0x28 /* B */ +#define USBSTAT 0x2a /* B */ +#define ETHTXSTAT 0x2b /* W */ +#define ETHRXSTAT 0x2d /* B */ +#define LOSTCNT 0x2e /* W */ +#define WF0MASK 0x30 /* 16byte array */ +#define WF0OFFSET 0x40 /* W */ +#define WF0CRC 0x41 /* W */ +#define WF1MASK 0x48 /* 16byte array */ +#define WF1OFFSET 0x58 /* W */ +#define WF1CRC 0x59 /* W */ +#define WF2MASK 0x60 /* 16byte array */ +#define WF2OFFSET 0x70 /* W */ +#define WF2CRC 0x71 /* W */ +#define WCTRL 0x78 /* B */ +#define WSTAT 0x7a /* B */ +#define IPHYC 0x7b /* B */ +#define GPIO54 0x7c /* B */ +#define GPIO10 0x7e /* B */ +#define GPIO32 0x7f /* B */ +#define TEST 0x80 /* B */ +#define TM 0x81 /* B */ +#define RPN 0x82 /* B */ + +/* Ethernet control register 0: offset 0 */ +#define EC0_TXE 0x80U +#define EC0_RXE 0x40U +#define EC0_RXFCE 0x20U +#define EC0_WOE 0x10U +#define EC0_RXSA 0x08U +#define EC0_SBO 0x04U +#define EC0_RXMA 0x02U +#define EC0_RXCS 0x01U + +#define EC0_BITS \ + "\020" \ + "\010TXE" \ + "\007RXE" \ + "\006RXFCE" \ + "\005WOE" \ + "\004RXSA" \ + "\003SBO" \ + "\002RXMA" \ + "\001RXCS" + +/* Ethernet control register 1: offset 1 */ +#define EC1_FD 0x20U +#define EC1_100M 0x10U /* 0:10Mbps 1:100Mbps */ +#define EC1_RM 0x08U /* reset mac */ + +#define EC1_BITS \ + "\020" \ + "\006FD" \ + "\005100M" \ + "\004RM" + +/* Ethernet control register 2: offset 2 */ +#define EC2_MEPL 0x80U /* 8515: MTU 0:1528, 1:1638 */ +#define EC2_RPNC 0x40U +#define EC2_LEEPRS 0x20U +#define EC2_EEPRW 0x10U +#define EC2_LB 0x08U +#define EC2_PROM 0x04U +#define EC2_RXBP 0x02U +#define EC2_EP3RC 0x01U + +#define EC2_BITS \ + "\020" \ + "\010MEPS" \ + "\007RPNC" \ + "\006LEEPRS" \ + "\005EEPRW" \ + "\004LB" \ + "\003PROM" \ + "\002RXBP" \ + "\001EP3RC" + +/* Recieve Packet number based Flow Control register: offset 0x1a */ +#define RPNBFC_PN 0x7eU /* */ +#define RPNBFC_PN_SHIFT 1 +#define RPNBFC_FCP 0x01U /* enable rx flow control */ + +/* Occupied Recieve FIFO based Flow Control register: offset 0x1b */ +#define ORFBFC_RXS 0x7eU /* */ +#define ORFBFC_RXS_SHIFT 1 +#define ORFBFC_RXS_UNIT 1024U +#define ORFBFC_FCRXS 0x01U /* enable rx flow control */ + +/* EP1 control register: offset 0x1c */ +#define EP1C_EP1S0E 0x80U /* send 0 enable */ +#define EP1C_ITMA 0x60U /* internal test mode A */ +#define EP1C_ITMB 0x1fU /* internal test mode B */ + +#define EP1C_BITS \ + "\020" \ + "\010EP1S0E" + +/* Rx FIFO Control register: offset 0x1d */ +#define RXFC_EXT_SRAM 0x02 /* enable external 32k sram */ +#define RXFC_RX32PKT 0x01 /* max 32 packet */ + +/* EEPROM offset register: offset 0x20 */ +#define EEOFFSET_MASK 0x3f /* eeprom offset address in word */ + +/* EEPROM access control register: offset 0x23 */ +#define EECTRL_DONE 0x04 +#define EECTRL_RD 0x02 +#define EECTRL_WR 0x01 + +#define EECTRL_BITS \ + "\020" \ + "\003DONE" \ + "\002RD" \ + "\001WR" + +/* PHY control register: offset 28 */ +#define PHYAC_DO 0x80U /* Done */ +#define PHYAC_RDPHY 0x40U /* read phy */ +#define PHYAC_WRPHY 0x20U /* write phy */ +#define PHYAC_PHYRA 0x1fU /* PHY register address */ + +#define PHYCTRL_BITS \ + "\020" \ + "\010DO" \ + "\007RDPHY" \ + "\006WRPHY" + +/* Internal PHY control register: offset 7b */ +#define IPHYC_EPHY 0x02 +#define IPHYC_PHYR 0x01 + +#define IPHYC_BITS \ + "\020" \ + "\002EPHY" \ + "\001PHYR" + +/* GPIO45 register: offset 7c */ +#define GPIO54_5OE 0x20 +#define GPIO54_5O 0x10 +#define GPIO54_5I 0x08 +#define GPIO54_4OE 0x04 +#define GPIO54_4O 0x02 +#define GPIO54_4I 0x01 + +/* GPIO01 register: offset 7e */ +#define GPIO10_1OE 0x20 +#define GPIO10_1O 0x10 +#define GPIO10_1I 0x08 +#define GPIO10_0OE 0x04 +#define GPIO10_0O 0x02 +#define GPIO10_0I 0x01 + +/* GPIO23 register: offset 7f */ +#define GPIO32_3OE 0x20 +#define GPIO32_3O 0x10 +#define GPIO32_3I 0x08 +#define GPIO32_2OE 0x04 +#define GPIO32_2O 0x02 +#define GPIO32_2I 0x01 + +/* rx status at the end of received packets */ +/* byte 0 and 1 is packet length in little endian */ +/* byte 2 is receive status */ +#define RSR_DRIBBLE 0x10 +#define RSR_CRC 0x08 +#define RSR_RUNT 0x04 +#define RSR_LONG 0x02 +#define RSR_MULTI 0x01 + +#define RSR_ERRORS \ + (RSR_DRIBBLE | RSR_CRC | RSR_RUNT | RSR_LONG | RSR_MULTI) + +#define RSR_BITS \ + "\020" \ + "\005DRIBBLE" \ + "\004CRC" \ + "\003RUNT" \ + "\002LONG" \ + "\001MULTI" +/* byte 3 is reserved */ + +/* TEST register: offset 80 */ diff --git a/usr/src/uts/common/io/upf/upf_usbgem.c b/usr/src/uts/common/io/upf/upf_usbgem.c new file mode 100644 index 0000000000..5614803158 --- /dev/null +++ b/usr/src/uts/common/io/upf/upf_usbgem.c @@ -0,0 +1,1213 @@ +/* + * upf_usbgem.c : ADMtek an986/adm8511/adm8513/adm8515 USB to + * Fast Ethernet Driver for Solaris + */ + +/* + * Copyright (c) 2004-2011 Masayuki Murayama. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#pragma ident "%W% %E%" + +/* + * Changelog: + */ + +/* + * TODO + */ +/* ======================================================= */ + +/* + * Solaris system header files and macros + */ +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/debug.h> +#include <sys/kmem.h> +#include <sys/modctl.h> +#include <sys/errno.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/byteorder.h> + +/* ethernet stuff */ +#include <sys/ethernet.h> + +/* interface card depend stuff */ +#include <sys/stropts.h> +#include <sys/stream.h> +#include <sys/strlog.h> +#include <sys/usb/usba.h> +#include "usbgem.h" + +/* hardware stuff */ +#include "usbgem_mii.h" +#include "adm8511reg.h" + +char ident[] = "pegasus usbnic driver v" VERSION; + +/* + * Useful macros + */ +#define CHECK_AND_JUMP(val, label) \ + if ((val) != USB_SUCCESS) { goto label; } + +/* + * Debugging + */ +#ifdef DEBUG_LEVEL +static int upf_debug = DEBUG_LEVEL; +#define DPRINTF(n, args) if (upf_debug > (n)) cmn_err args +#else +#define DPRINTF(n, args) +#endif + +/* + * Our configration for ADMtek Pegasus/PegasusII + */ +/* timeouts */ +#define ONESEC (drv_usectohz(1*1000000)) + +/* + * Local device definitions + */ +struct upf_dev { + /* + * Misc HW information + */ + uint8_t ec[3]; + uint8_t mac_addr[ETHERADDRL]; + int chip_type; +#define CHIP_AN986 1 /* avoid 0 */ +#define CHIP_ADM8511 2 /* including adm8515 */ +#define CHIP_ADM8513 3 + boolean_t phy_init_done; + uint8_t last_link_state; + + uint16_t vid; /* vendor id */ + uint16_t pid; /* product id */ +}; + +/* + * private functions + */ + +/* mii operations */ +static uint16_t upf_mii_read(struct usbgem_dev *, uint_t, int *errp); +static void upf_mii_write(struct usbgem_dev *, uint_t, uint16_t, int *errp); + +/* nic operations */ +static int upf_attach_chip(struct usbgem_dev *); +static int upf_reset_chip(struct usbgem_dev *); +static int upf_init_chip(struct usbgem_dev *); +static int upf_start_chip(struct usbgem_dev *); +static int upf_stop_chip(struct usbgem_dev *); +static int upf_set_media(struct usbgem_dev *); +static int upf_set_rx_filter(struct usbgem_dev *); +static int upf_get_stats(struct usbgem_dev *); + +/* packet operations */ +static mblk_t *upf_tx_make_packet(struct usbgem_dev *, mblk_t *); +static mblk_t *upf_rx_make_packet(struct usbgem_dev *, mblk_t *); + +/* interrupt handler */ +static void upf_interrupt(struct usbgem_dev *, mblk_t *); + +/* =============================================================== */ +/* + * I/O functions + */ +/* =============================================================== */ +#define UPF_REQ_GET_REGISTER 0xf0 +#define UPF_REQ_SET_REGISTER 0xf1 +#define OUTB(dp, p, v, errp, label) \ + if ((*(errp) = usbgem_ctrl_out((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ UPF_REQ_SET_REGISTER, \ + /* wValue */ (v), \ + /* wIndex */ (p), \ + /* wLength */ 1, \ + /* buf */ NULL, \ + /* size */ 0)) != USB_SUCCESS) goto label; + +#define OUTW(dp, p, v, errp, label) \ + if ((*(errp) = usbgem_ctrl_out_val((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ UPF_REQ_SET_REGISTER, \ + /* wValue */ 0, \ + /* wIndex */ (p), \ + /* wLength */ 2, \ + /* value */ (v))) != USB_SUCCESS) goto label + +#define OUTS(dp, p, buf, len, errp, label) \ + if ((*(errp) = usbgem_ctrl_out((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ UPF_REQ_SET_REGISTER, \ + /* wValue */ 0, \ + /* wIndex */ (p), \ + /* wLength */ (len), \ + /* buf */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +#define INB(dp, p, vp, errp, label) \ + if ((*(errp) = usbgem_ctrl_in_val((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ UPF_REQ_GET_REGISTER, \ + /* wValue */ 0, \ + /* wIndex */ (p), \ + /* wLength */ 1, \ + /* valuep */ (vp))) != USB_SUCCESS) goto label + +#define INW(dp, p, vp, errp, label) \ + if ((*(errp) = usbgem_ctrl_in_val((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ UPF_REQ_GET_REGISTER, \ + /* wValue */ 0, \ + /* wIndex */ (p), \ + /* wLength */ 2, \ + /* valuep */ (vp))) != USB_SUCCESS) goto label + +#define INS(dp, p, buf, len, errp, label) \ + if ((*(errp) = usbgem_ctrl_in((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ UPF_REQ_GET_REGISTER, \ + /* wValue */ 0, \ + /* wIndex */ (p), \ + /* wLength */ (len), \ + /* buf */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +/* =============================================================== */ +/* + * Hardware manupilation + */ +/* =============================================================== */ +static int +upf_reset_chip(struct usbgem_dev *dp) +{ + int i; + uint8_t val; + int err; + struct upf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + bzero(lp->mac_addr, sizeof (lp->mac_addr)); + + lp->ec[1] = 0; + OUTB(dp, EC1, EC1_RM, &err, usberr); + + for (i = 0; i < 1000; i++) { + INB(dp, EC1, &val, &err, usberr); + if ((val & EC1_RM) == 0) { + lp->ec[1] = val; + return (USB_SUCCESS); + } + drv_usecwait(10); + } + + /* time out */ + cmn_err(CE_WARN, "!%s: failed to reset: timeout", dp->name); + return (USB_FAILURE); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (USB_FAILURE); +} + +/* + * Setup an986/adm8511/adm8513/adm8515 + */ +static int +upf_init_chip(struct usbgem_dev *dp) +{ + uint64_t zero64 = 0; + int err = USB_SUCCESS; + struct upf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* ethernet control register 0 */ + lp->ec[0] |= EC0_RXSA | EC0_RXCS; + OUTB(dp, EC0, lp->ec[0], &err, usberr); + + /* ethernet control reg1: will be set later in set_rx_filter() */ + + /* ethernet control register 2: will be set later in set_rx_filter() */ + INB(dp, EC2, &lp->ec[2], &err, usberr); + lp->ec[2] |= EC2_RXBP | EC2_EP3RC; +#ifdef CONFIG_VLAN + if (dp->misc_flag & USBGEM_VLAN) { + lp->ec[2] |= EC2_MEPL; + } +#endif + OUTB(dp, EC2, lp->ec[2], &err, usberr); + + /* Multicast address hash: clear */ + OUTS(dp, MA, &zero64, 8, &err, usberr); + + /* Ethernet ID : will be set later in upf_set_rx_filter() */ + + /* PAUSE timer */ + OUTB(dp, PAUSETIMER, 0x1f, &err, usberr); + + /* receive packet number based pause control:set in upf_set_media() */ + + /* occupied receive FIFO based pause control:set in upf_set_media() */ + + /* EP1 control: default */ + + /* Rx FIFO control */ + if (lp->chip_type != CHIP_AN986) { + /* use 24K internal sram, 16pkts in fifo */ + OUTB(dp, RXFC, 0, &err, usberr); + } + + /* BIST contror: do nothing */ + err = upf_set_media(dp); + CHECK_AND_JUMP(err, usberr); + + DPRINTF(2, (CE_CONT, "!%s: %s: end (success)", dp->name, __func__)); + return (USB_SUCCESS); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr(%d) detected", + dp->name, __func__, err); + return (err); +} + +static int +upf_start_chip(struct usbgem_dev *dp) +{ + int err = USB_SUCCESS; + struct upf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* enable RX and TX */ + lp->ec[0] |= EC0_TXE | EC0_RXE; + OUTB(dp, EC0, lp->ec[0], &err, usberr); + return (USB_SUCCESS); + +usberr: + cmn_err(CE_WARN, "!%s: %s: usberr(%d) detected", + dp->name, __func__, err); + return (err); +} + +static int +upf_stop_chip(struct usbgem_dev *dp) +{ + int err; + struct upf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* disable RX and TX */ + lp->ec[0] &= ~(EC0_TXE | EC0_RXE); + OUTB(dp, EC0, lp->ec[0], &err, usberr); + + return (USB_SUCCESS); + +usberr: + cmn_err(CE_WARN, "!%s: %s: usberr(%d) detected", + dp->name, __func__, err); + return (err); +} + +static int +upf_get_stats(struct usbgem_dev *dp) +{ + /* do nothing */ + return (USB_SUCCESS); +} + +static uint_t +upf_mcast_hash(struct usbgem_dev *dp, const uint8_t *addr) +{ + /* hash table is 64 = 2^6 bit width */ + return (usbgem_ether_crc_le(addr) & 0x3f); +} + +static int +upf_set_rx_filter(struct usbgem_dev *dp) +{ + int i; + int err; +#ifdef DEBUG_LEVEL + uint8_t reg0; + uint8_t reg1; + uint8_t reg2; +#endif + struct upf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called, rxmode:%b", + dp->name, __func__, dp->rxmode, RXMODE_BITS)); + + /* reset rx mode */ + lp->ec[0] &= ~EC0_RXMA; + lp->ec[2] &= ~EC2_PROM; + + if (dp->rxmode & RXMODE_PROMISC) { + /* promiscious mode implies all multicast and all physical */ + lp->ec[0] |= EC0_RXMA; + lp->ec[2] |= EC2_PROM; + } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 0) { + /* XXX - multicast hash table didin't work */ + /* accept all multicast packets */ + lp->ec[0] |= EC0_RXMA; + } + + if (bcmp(dp->cur_addr.ether_addr_octet, + lp->mac_addr, ETHERADDRL) != 0) { + + /* need to update mac address */ + bcopy(dp->cur_addr.ether_addr_octet, + lp->mac_addr, ETHERADDRL); + OUTS(dp, EID, + lp->mac_addr, ETHERADDRL, &err, usberr); + } + + /* update rx mode */ + OUTS(dp, EC0, lp->ec, 3, &err, usberr); + +#if DEBUG_LEVEL > 0 + INB(dp, EC0, ®0, &err, usberr); + INB(dp, EC1, ®1, &err, usberr); + INB(dp, EC2, ®2, &err, usberr); + + cmn_err(CE_CONT, "!%s: %s: returned, ec:%b %b %b", + dp->name, __func__, + reg0, EC0_BITS, reg1, EC1_BITS, reg2, EC2_BITS); +#endif + return (USB_SUCCESS); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (err); +} + +static int +upf_set_media(struct usbgem_dev *dp) +{ + int err; + struct upf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + lp->ec[1] &= ~(EC1_FD | EC1_100M); + + /* select duplex */ + if (dp->full_duplex) { + lp->ec[1] |= EC1_FD; + } + + /* select speed */ + if (dp->speed == USBGEM_SPD_100) { + lp->ec[1] |= EC1_100M; + } + + /* rx flow control */ + switch (dp->flow_control) { + case FLOW_CONTROL_SYMMETRIC: + case FLOW_CONTROL_RX_PAUSE: + lp->ec[0] |= EC0_RXFCE; + break; + + default: + lp->ec[0] &= ~EC0_RXFCE; + break; + } + + /* tx flow control */ + switch (dp->flow_control) { + case FLOW_CONTROL_SYMMETRIC: + case FLOW_CONTROL_TX_PAUSE: + if (lp->chip_type != CHIP_AN986) { + /* pegasus II has internal 24k fifo */ + OUTB(dp, ORFBFC, + (12 << ORFBFC_RXS_SHIFT) | ORFBFC_FCRXS, + &err, usberr); + + /* 16 packts can be stored in rx fifo */ + OUTB(dp, RPNBFC_PN, + (8 << RPNBFC_PN_SHIFT) | RPNBFC_FCP, + &err, usberr); + } else { + /* an986 has external 32k fifo */ + OUTB(dp, ORFBFC, + (16 << ORFBFC_RXS_SHIFT) | ORFBFC_FCRXS, + &err, usberr); + + /* AN986 fails to link up when RPNBFC is enabled */ + OUTB(dp, RPNBFC, 0, &err, usberr); + } + break; + + default: + OUTB(dp, ORFBFC, 0, &err, usberr); + OUTB(dp, RPNBFC, 0, &err, usberr); + break; + } + + /* update ether control registers */ + OUTS(dp, EC0, lp->ec, 2, &err, usberr); + DPRINTF(0, (CE_CONT, "!%s: %s: returned, ec0:%b, ec1:%b", + dp->name, __func__, lp->ec[0], EC0_BITS, lp->ec[1], EC1_BITS)); + + return (USB_SUCCESS); + +usberr: + cmn_err(CE_WARN, "%s: %s: failed to write ec1", dp->name, __func__); + return (err); +} + +/* + * send/receive packet check + */ +static mblk_t * +upf_tx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + size_t len; + mblk_t *new; + mblk_t *tp; + uint8_t *bp; + uint8_t *last_pos; + int msglen; + + DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + len = msgdsize(mp); + if (len < ETHERMIN) { + len = ETHERMIN; + } + + /* allocate msg block */ + msglen = len + sizeof (uint16_t); + + /* avoid usb controller bug */ + if ((msglen & 0x3f) == 0) { + /* add a header for additional 0-length usb message */ + msglen += sizeof (uint16_t); + } + + if ((new = allocb(msglen, 0)) == NULL) { + return (NULL); + } + + /* copy contents of the buffer */ + new->b_wptr = new->b_rptr + msglen; + bp = new->b_rptr; + + /* the nic requires a two byte header of the packet size */ + bp[0] = (uint8_t)len; + bp[1] = (uint8_t)(len >> 8); + bp += sizeof (uint16_t); + + /* copy the payload */ + for (tp = mp; tp; tp = tp->b_cont) { + len = tp->b_wptr - tp->b_rptr; + if (len > 0) { + bcopy(tp->b_rptr, bp, len); + bp += len; + } + } + + /* clear ethernet pads and additional usb header if we have */ + last_pos = new->b_wptr; + while (bp < last_pos) { + *bp++ = 0; + } + + return (new); +} + +static void +upf_dump_packet(struct usbgem_dev *dp, uint8_t *bp, int n) +{ + int i; + + for (i = 0; i < n; i += 8, bp += 8) { + cmn_err(CE_CONT, "%02x %02x %02x %02x %02x %02x %02x %02x", + bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7]); + } +} + +static mblk_t * +upf_rx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + uint8_t *p; + uint16_t rxhd; + uint_t len; + uint8_t rsr; + struct upf_dev *lp = dp->private; + + ASSERT(mp != NULL); + +#ifdef DEBUG_LEVEL + len = msgdsize(mp); + DPRINTF(2, (CE_CONT, "!%s: time:%d %s: cont:%p", + dp->name, ddi_get_lbolt(), __func__, len, mp->b_cont)); + + if (upf_debug > 3) { + upf_dump_packet(dp, mp->b_rptr, max(6, len)); + } +#endif + /* get the length of Rx packet */ + p = mp->b_wptr - 4; + rsr = p[3]; + if (lp->chip_type == CHIP_ADM8513) { + /* As Rx packets from ADM8513 have two byte header, remove it */ + p = mp->b_rptr; + len = ((p[1] << 8) | p[0]) & 0x0fff; + mp->b_rptr += 2; + } else { + len = (((p[1] << 8) | p[0]) & 0x0fff) - ETHERFCSL - 4; + } + + DPRINTF(2, (CE_CONT, "!%s: %s: rsr:%b len:%d", + dp->name, __func__, rsr, RSR_BITS, len)); + + /* check if error happen */ + if (rsr & RSR_ERRORS) { + DPRINTF(0, (CE_CONT, "!%s: rsr:%b", dp->name, rsr, RSR_BITS)); + if (rsr & (RSR_CRC | RSR_DRIBBLE)) { + dp->stats.frame++; + } + if (rsr & RSR_LONG) { + dp->stats.frame_too_long++; + } + if (rsr & RSR_RUNT) { + dp->stats.runt++; + } + + dp->stats.errrcv++; + return (NULL); + } +#ifndef CONFIG_VLAN + /* check packet size */ + if (len > ETHERMAX) { + /* too long */ + dp->stats.frame_too_long++; + dp->stats.errrcv++; + return (NULL); + } else if (len < ETHERMIN) { + dp->stats.runt++; + dp->stats.errrcv++; + return (NULL); + } +#endif + /* remove tailing crc and rx status fields */ + mp->b_wptr = mp->b_rptr + len; + ASSERT(mp->b_next == NULL); + return (mp); +} + +/* + * Device depend interrupt handler + */ +static void +upf_interrupt(struct usbgem_dev *dp, mblk_t *mp) +{ + uint8_t *bp; + struct upf_dev *lp = dp->private; + + bp = mp->b_rptr; + + DPRINTF(2, (CE_CONT, + "!%s: %s: size:%d, %02x %02x %02x %02x %02x %02x %02x %02x", + dp->name, __func__, mp->b_wptr - mp->b_rptr, + bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7])); + + if ((lp->last_link_state ^ bp[5]) & 1) { + DPRINTF(1, (CE_CONT, "!%s:%s link status changed:", + dp->name, __func__)); + usbgem_mii_update_link(dp); + } + + lp->last_link_state = bp[5] & 1; +} + +/* + * MII Interfaces + */ +static uint16_t +upf_mii_read(struct usbgem_dev *dp, uint_t index, int *errp) +{ + uint8_t phyctrl; + uint16_t val; + int i; + + DPRINTF(4, (CE_CONT, "!%s: %s: called, ix:%d", + dp->name, __func__, index)); + ASSERT(index >= 0 && index < 32); + + *errp = USB_SUCCESS; + + /* set PHYADDR */ + OUTB(dp, PHYA, dp->mii_phy_addr, errp, usberr); + + /* Initiate MII read transaction */ + OUTB(dp, PHYAC, index | PHYAC_RDPHY, errp, usberr); + + for (i = 0; i < 100; i++) { + INB(dp, PHYAC, &phyctrl, errp, usberr); + if (phyctrl & PHYAC_DO) { + /* done */ + INW(dp, PHYD, &val, errp, usberr); + DPRINTF(4, (CE_CONT, "!%s: %s: return %04x", + dp->name, __func__, val)); + return (val); + } + drv_usecwait(10); + } + /* timeout */ + cmn_err(CE_WARN, "!%s: %s: timeout detected", dp->name, __func__); + *errp = USB_FAILURE; + return (0); + +usberr: + cmn_err(CE_CONT, + "!%s: %s: usberr(%d) detected", dp->name, __func__, *errp); + return (0); +} + +static void +upf_mii_write(struct usbgem_dev *dp, uint_t index, uint16_t val, int *errp) +{ + int i; + uint8_t phyctrl; + + DPRINTF(4, (CE_CONT, "!%s: %s called index:%d val:0x%04x", + dp->name, __func__, index, val)); + ASSERT(index >= 0 && index < 32); + + *errp = USB_SUCCESS; + + OUTW(dp, PHYD, val, errp, usberr); + OUTB(dp, PHYA, dp->mii_phy_addr, errp, usberr); + OUTB(dp, PHYAC, index | PHYAC_WRPHY, errp, usberr); + + for (i = 0; i < 100; i++) { + INB(dp, PHYAC, &phyctrl, errp, usberr); + if (phyctrl & PHYAC_DO) { + /* done */ + return; + } + drv_usecwait(10); + } + + /* time out */ + cmn_err(CE_WARN, "!%s: %s: timeout detected", dp->name, __func__); + *errp = USB_FAILURE; + return; + +usberr: + cmn_err(CE_CONT, + "!%s: %s: usberr(%d) detected", dp->name, __func__, *errp); +} + + +static int +upf_enable_phy(struct usbgem_dev *dp) +{ + uint8_t val; + int err; + struct upf_dev *lp = dp->private; + + /* + * first, try to enable internal phy + */ + INB(dp, IPHYC, &val, &err, usberr); + val = (val | IPHYC_EPHY) & ~IPHYC_PHYR; + OUTB(dp, IPHYC, val, &err, usberr); + + INB(dp, IPHYC, &val, &err, usberr); + DPRINTF(0, (CE_CONT, "!%s: %s: IPHYC: %b", + dp->name, __func__, val, IPHYC_BITS)); + if (val) { + /* reset internal phy */ + OUTB(dp, IPHYC, val | IPHYC_PHYR, &err, usberr); + OUTB(dp, IPHYC, val, &err, usberr); + delay(drv_usectohz(10000)); + + /* identify the chip generation */ + OUTB(dp, 0x83, 0xa5, &err, usberr); + INB(dp, 0x83, &val, &err, usberr); + if (val == 0xa5) { + lp->chip_type = CHIP_ADM8513; + } else { + /* adm8511 or adm8515 */ + lp->chip_type = CHIP_ADM8511; + } + dp->ugc.usbgc_mii_hw_link_detection = B_TRUE; + } else { + /* + * It should be AN986 which doesn't have an internal PHY. + * We need to setup gpio ports in AN986, which are + * connected to external PHY control pins. + */ + lp->chip_type = CHIP_AN986; + + /* reset external phy */ + /* output port#0 L, port#1 L */ + OUTB(dp, GPIO10, GPIO10_0O | GPIO10_0OE, &err, usberr); + + /* output port#0 H, port#1 L */ + OUTB(dp, GPIO10, + GPIO10_0O | GPIO10_0OE | GPIO10_1OE, &err, usberr); + + /* hw link detection doesn't work correctly */ + dp->ugc.usbgc_mii_hw_link_detection = B_FALSE; + } + + return (USB_SUCCESS); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (USB_FAILURE); +} + +static int +upf_mii_probe(struct usbgem_dev *dp) +{ + int err; + uint16_t val; + struct upf_dev *lp = dp->private; + + if (!lp->phy_init_done) { + upf_enable_phy(dp); + lp->phy_init_done = B_TRUE; + } + + return (usbgem_mii_probe_default(dp)); +} + +static int +upf_mii_init(struct usbgem_dev *dp) +{ + uint16_t val; + int err = USB_SUCCESS; + struct upf_dev *lp = dp->private; + + if (!lp->phy_init_done) { + upf_enable_phy(dp); + } + lp->phy_init_done = B_FALSE; + + if (lp->chip_type == CHIP_AN986 && + (lp->vid == 0x0db7 /* elecom */ || + lp->vid == 0x066b /* linksys */ || + lp->vid == 0x077b /* linksys */ || + lp->vid == 0x2001 /* dlink */)) { + /* special treatment for Linksys products */ + val = upf_mii_read(dp, 0x1b, &err) | 0x4; + upf_mii_write(dp, 0x1b, val, &err); + } + return (err); +} + +/* ======================================================== */ +/* + * OS depend (device driver DKI) routine + */ +/* ======================================================== */ +static uint16_t +upf_read_eeprom(struct usbgem_dev *dp, int index, int *errp) +{ + int i; + uint8_t eectrl; + uint16_t data; + + *errp = USB_SUCCESS; + + OUTB(dp, EECTRL, 0, errp, usberr); + + OUTB(dp, EEOFFSET, index, errp, usberr); + OUTB(dp, EECTRL, EECTRL_RD, errp, usberr); + + for (i = 0; i < 100; i++) { + INB(dp, EECTRL, &eectrl, errp, usberr); + if (eectrl & EECTRL_DONE) { + INW(dp, EEDATA, &data, errp, usberr); + return (data); + } + drv_usecwait(10); + } + + /* time out */ + *errp = USB_FAILURE; + return (0); + +usberr: + cmn_err(CE_CONT, + "!%s: %s: usberr(%d) detected", dp->name, __func__, *errp); + return (0); +} + +static void +upf_eeprom_dump(struct usbgem_dev *dp, int size) +{ + int i; + int err; + + cmn_err(CE_CONT, "!%s: %s dump:", dp->name, __func__); + + for (i = 0; i < size; i += 4) { + cmn_err(CE_CONT, "!0x%02x: 0x%04x 0x%04x 0x%04x 0x%04x", + i*2, + upf_read_eeprom(dp, i + 0, &err), + upf_read_eeprom(dp, i + 1, &err), + upf_read_eeprom(dp, i + 2, &err), + upf_read_eeprom(dp, i + 3, &err)); + } +} + +static int +upf_attach_chip(struct usbgem_dev *dp) +{ + int i; + int err; + uint16_t val; + uint8_t *mac; + struct upf_dev *lp = dp->private; + + /* + * Read mac address from EEPROM + */ + mac = dp->dev_addr.ether_addr_octet; + for (i = 0; i < 3; i++) { + val = upf_read_eeprom(dp, i, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + mac[i*2+0] = (uint8_t)val; + mac[i*2+1] = (uint8_t)(val >> 8); + } + + DPRINTF(0, (CE_CONT, + "%s: %s: mac: %02x:%02x:%02x:%02x:%02x:%02x", + dp->name, __func__, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])); + + dp->misc_flag = 0; +#ifdef CONFIG_VLAN + dp->misc_flag |= USBGEM_VLAN; +#endif +#if DEBUG_LEVEL > 3 + upf_eeprom_dump(dp, 0x80); +#endif + return (USB_SUCCESS); + +usberr: + cmn_err(CE_WARN, "!%s: %s: usb error detected", dp->name, __func__); + return (USB_FAILURE); +} + +static int +upfattach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int i; + ddi_iblock_cookie_t c; + int ret; + int unit; + uint32_t tcr; + int len; + const char *drv_name; + struct usbgem_dev *dp; + void *base; + struct usbgem_conf *ugcp; + struct upf_dev *lp; + + unit = ddi_get_instance(dip); + drv_name = ddi_driver_name(dip); + + DPRINTF(3, (CE_CONT, "!%s%d: %s: called, cmd:%d", + drv_name, unit, __func__, cmd)); + + if (cmd == DDI_ATTACH) { + /* + * construct usbgem configration + */ + ugcp = kmem_zalloc(sizeof (*ugcp), KM_SLEEP); + + /* name */ + sprintf(ugcp->usbgc_name, "%s%d", drv_name, unit); + ugcp->usbgc_ppa = unit; + + ugcp->usbgc_ifnum = 0; + ugcp->usbgc_alt = 0; + + ugcp->usbgc_tx_list_max = 16; + + ugcp->usbgc_rx_header_len = 4; + ugcp->usbgc_rx_list_max = 64; + + /* time out parameters */ + ugcp->usbgc_tx_timeout = USBGEM_TX_TIMEOUT; + ugcp->usbgc_tx_timeout_interval = USBGEM_TX_TIMEOUT_INTERVAL; + + /* flow control */ + ugcp->usbgc_flow_control = FLOW_CONTROL_NONE; + ugcp->usbgc_flow_control = FLOW_CONTROL_RX_PAUSE; + + /* MII timeout parameters */ + ugcp->usbgc_mii_link_watch_interval = ONESEC; + ugcp->usbgc_mii_an_watch_interval = ONESEC/5; + ugcp->usbgc_mii_reset_timeout = MII_RESET_TIMEOUT; /* 1 sec */ + ugcp->usbgc_mii_an_timeout = MII_AN_TIMEOUT; /* 5 sec */ + ugcp->usbgc_mii_an_wait = MII_AN_TIMEOUT/2; + ugcp->usbgc_mii_linkdown_timeout = MII_LINKDOWN_TIMEOUT; + ugcp->usbgc_mii_an_delay = ONESEC/10; + + ugcp->usbgc_mii_linkdown_action = MII_ACTION_RESET; + ugcp->usbgc_mii_linkdown_timeout_action = MII_ACTION_RESET; + ugcp->usbgc_mii_dont_reset = B_FALSE; + + /* I/O methods */ + + /* mac operation */ + ugcp->usbgc_attach_chip = &upf_attach_chip; + ugcp->usbgc_reset_chip = &upf_reset_chip; + ugcp->usbgc_init_chip = &upf_init_chip; + ugcp->usbgc_start_chip = &upf_start_chip; + ugcp->usbgc_stop_chip = &upf_stop_chip; + ugcp->usbgc_multicast_hash = &upf_mcast_hash; + + ugcp->usbgc_set_rx_filter = &upf_set_rx_filter; + ugcp->usbgc_set_media = &upf_set_media; + ugcp->usbgc_get_stats = &upf_get_stats; + ugcp->usbgc_interrupt = &upf_interrupt; + + /* packet operation */ + ugcp->usbgc_tx_make_packet = &upf_tx_make_packet; + ugcp->usbgc_rx_make_packet = &upf_rx_make_packet; + + /* mii operations */ + ugcp->usbgc_mii_probe = &upf_mii_probe; + ugcp->usbgc_mii_init = &upf_mii_init; + ugcp->usbgc_mii_config = &usbgem_mii_config_default; + ugcp->usbgc_mii_read = &upf_mii_read; + ugcp->usbgc_mii_write = &upf_mii_write; + + /* mtu */ + ugcp->usbgc_min_mtu = ETHERMTU; + ugcp->usbgc_max_mtu = ETHERMTU; + ugcp->usbgc_default_mtu = ETHERMTU; + + lp = kmem_zalloc(sizeof (struct upf_dev), KM_SLEEP); + + lp->vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "usb-vendor-id", -1); + lp->pid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "usb-product-id", -1); + + dp = usbgem_do_attach(dip, ugcp, lp, sizeof (struct upf_dev)); + + kmem_free(ugcp, sizeof (*ugcp)); + + if (dp != NULL) { + return (DDI_SUCCESS); + } + +err_free_mem: + kmem_free(lp, sizeof (struct upf_dev)); +err_close_pipe: +err: + return (DDI_FAILURE); + } + if (cmd == DDI_RESUME) { + dp = USBGEM_GET_DEV(dip); + lp = dp->private; + lp->phy_init_done = B_FALSE; + + return (usbgem_resume(dip)); + } + return (DDI_FAILURE); +} + +static int +upfdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int ret; + + if (cmd == DDI_DETACH) { + ret = usbgem_do_detach(dip); + if (ret != DDI_SUCCESS) { + return (DDI_FAILURE); + } + return (DDI_SUCCESS); + } + if (cmd == DDI_SUSPEND) { + return (usbgem_suspend(dip)); + } + return (DDI_FAILURE); +} + +/* ======================================================== */ +/* + * OS depend (loadable streams driver) routine + */ +/* ======================================================== */ +#ifdef USBGEM_CONFIG_GLDv3 +USBGEM_STREAM_OPS(upf_ops, upfattach, upfdetach); +#else +static struct module_info upfminfo = { + 0, /* mi_idnum */ + "upf", /* mi_idname */ + 0, /* mi_minpsz */ + ETHERMTU, /* mi_maxpsz */ + 32*1024, /* mi_hiwat */ + 1, /* mi_lowat */ +}; + +static struct qinit upfrinit = { + (int (*)()) NULL, /* qi_putp */ + usbgem_rsrv, /* qi_srvp */ + usbgem_open, /* qi_qopen */ + usbgem_close, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &upfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct qinit upfwinit = { + usbgem_wput, /* qi_putp */ + usbgem_wsrv, /* qi_srvp */ + (int (*)()) NULL, /* qi_qopen */ + (int (*)()) NULL, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &upfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct streamtab upf_info = { + &upfrinit, /* st_rdinit */ + &upfwinit, /* st_wrinit */ + NULL, /* st_muxrinit */ + NULL /* st_muxwrinit */ +}; + +static struct cb_ops cb_upf_ops = { + nulldev, /* cb_open */ + nulldev, /* 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 */ + &upf_info, /* cb_stream */ + D_MP /* cb_flag */ +}; + +static struct dev_ops upf_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + usbgem_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + upfattach, /* devo_attach */ + upfdetach, /* devo_detach */ + nodev, /* devo_reset */ + &cb_upf_ops, /* devo_cb_ops */ + NULL, /* devo_bus_ops */ + usbgem_power, /* devo_power */ +#if DEVO_REV >= 4 + usbgem_quiesce, /* devo_quiesce */ +#endif + +}; +#endif +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + ident, + &upf_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, &modldrv, NULL +}; + +/* ======================================================== */ +/* + * _init : done + */ +/* ======================================================== */ +int +_init(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!upf: _init: called")); + + status = usbgem_mod_init(&upf_ops, "upf"); + if (status != DDI_SUCCESS) { + return (status); + } + status = mod_install(&modlinkage); + if (status != DDI_SUCCESS) { + usbgem_mod_fini(&upf_ops); + } + return (status); +} + +/* + * _fini : done + */ +int +_fini(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!upf: _fini: called")); + status = mod_remove(&modlinkage); + if (status == DDI_SUCCESS) { + usbgem_mod_fini(&upf_ops); + } + return (status); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} diff --git a/usr/src/uts/common/io/urf/rtl8150reg.h b/usr/src/uts/common/io/urf/rtl8150reg.h new file mode 100644 index 0000000000..7cba53356e --- /dev/null +++ b/usr/src/uts/common/io/urf/rtl8150reg.h @@ -0,0 +1,218 @@ +/* + * @(#)rtl8150reg.h 1.1 04/09/16 + * Macro definitions for Realtek 8150 USB to fast ethernet controller + * based on Realtek RTL8150 data sheet + * This file is public domain. Coded by M.Murayama (KHF04453@nifty.com) + */ + +/* + * Register offset + */ +#define IDR 0x0120 /* Base of ID registers */ +#define MAR 0x0126 /* Base of multicast registers */ +#define CR 0x012e /* Command register */ +#define TCR 0x012f /* Transmit Configuration register */ +#define RCR 0x0130 /* Receive Configuration register */ +#define TSR 0x0132 /* Transmit Status register */ +#define RSR 0x0133 /* Receive Status register */ +#define CON0 0x0135 /* Configuration register 0 */ +#define CON1 0x0136 /* Configuration register 1 */ +#define MSR 0x0137 /* Media Status register */ +#define PHYADD 0x0138 /* PHY address register */ +#define PHYDAT 0x0139 /* PHY data register */ +#define PHYCNT 0x013b /* PHY control register */ +#define GPPC 0x013d /* General purpose pin control */ +#define WAKECNT 0x013e /* Wake up event control */ +#define BMCR 0x0140 /* Basic Mode Control register */ +#define BMSR 0x0142 /* Basic Mode Status register */ +#define ANAR 0x0144 /* Auto Negotiation Advertisement register */ +#define ANLP 0x0146 /* Auto Negotiation Link Partner register */ +#define ANER 0x0148 /* Auto Negotiation Expansion register */ +#define NWAYT 0x014a /* Nway test register */ +#define CSCR 0x014c /* CS configuration register */ +#define CRC0 0x014e /* Power management register for wakeup frame0 */ +#define CRC1 0x0150 /* Power management register for wakeup frame1 */ +#define CRC2 0x0152 /* Power management register for wakeup frame2 */ +#define CRC3 0x0154 /* Power management register for wakeup frame3 */ +#define CRC4 0x0156 /* Power management register for wakeup frame4 */ +#define BYTEMASK0 0x0158 /* Power management wakeup frame0 bytemask */ +#define BYTEMASK1 0x0160 /* Power management wakeup frame1 bytemask */ +#define BYTEMASK2 0x0168 /* Power management wakeup frame2 bytemask */ +#define BYTEMASK3 0x0170 /* Power management wakeup frame3 bytemask */ +#define BYTEMASK4 0x0178 /* Power management wakeup frame4 bytemask */ +#define PHY1 0x0180 /* PHY parameter 1 */ +#define PHY2 0x0184 /* PHY parameter 2 */ +#define TW1 0x0186 /* Twister parameter 1 */ + +/* + * Bit field definitions + */ +/* CR : Command register (uint8_t) */ +#define CR_WEPROM 0x20 /* EEPROM write enable */ +#define CR_SOFT_RST 0x10 /* Reset */ +#define CR_RE 0x08 /* Ethernet receive enable */ +#define CR_TE 0x04 /* Ethernet transmit enable */ +#define CR_EP3CLREN 0x02 /* clear performance counter after EP3 */ +#define CR_AUTOLOAD 0x01 /* autoload contents of 93c46 */ + +#define CR_BITS "\020\006WEPROM\005SOFT_RST\004RE\003TE\002EP3CLREN\001AUTOLOAD" + +/* TCR: Transmit Configuration register */ +#define TCR_TXRR 0xc0 /* Tx retry count */ +#define TCR_TXRR_SHIFT 6 +#define TCR_IFG 0x18 /* Interframe Gap */ +#define TCR_IFG_SHIFT 3 +#define TCR_IFG_802_3 (3 << TCR_IFG_SHIFT) /* 802.3 standard */ +#define TCR_NOCRC 0x01 /* Inhibit Appending CRC */ + +#define TCR_BITS "\020\001NOCRC" + +/* Receive Configuration register */ +#define RCR_TAIL 0x0080 /* Rx header forward to host in CRC field */ +#define RCR_AER 0x0040 /* Accept Error packet */ +#define RCR_AR 0x0020 /* Accept runt */ +#define RCR_AM 0x0010 /* Accept multicast */ +#define RCR_AB 0x0008 /* Accept broadcast */ +#define RCR_AD 0x0004 /* Accept physical match */ +#define RCR_AAM 0x0002 /* Accept all Multicast */ +#define RCR_AAP 0x0001 /* Accept all physical */ + +#define RCR_ACCEPT_MODE \ + (RCR_AER | RCR_AR | RCR_AM | RCR_AB | RCR_AD | RCR_AAM | RCR_AAP) + +#define RCR_BITS \ + "\020\010TAIL\007AER\006AR\005AM\004AB\003AD\002AAM\001AAP" + +/* Transmit Status register */ + +#define TSR_ECOL 0x20 /* excessive collision indication */ +#define TSR_LCOL 0x10 /* late collision indication */ +#define TSR_LOSS_CRS 0x08 /* lost of carrier indication */ +#define TSR_JBR 0x04 /* jabber time out indication */ +#define TSR_BUF_EMPTY 0x02 /* Tx buffer is empty */ +#define TSR_BUF_FULL 0x01 /* Tx buffer is full */ + +#define TSR_BITS \ + "\020" \ + "\006ECOL" \ + "\005LCOL" \ + "\004LOSS_CRS" \ + "\003JBR" \ + "\002BUF_EMPTY" \ + "\001BUF_FULL" + +/* Receive status register in Rx packet field */ +#define RSR_WEVENT 0x80 /* Wakeup event indication */ +#define RSR_RX_BUF_FULL 0x40 /* Receive buffer full indication */ +#define RSR_LKCHG 0x20 /* Link change indication */ +#define RSR_RUNT 0x10 /* short packet indication */ +#define RSR_LONG 0x08 /* Long packet indication*/ +#define RSR_CRC 0x04 /* CRC error indication*/ +#define RSR_FAE 0x02 /* Frame alignment error */ +#define RSR_ROK 0x01 /* Receive OK indication */ + +#define RSR_ERRS (RSR_RUNT | RSR_LONG | RSR_CRC | RSR_FAE) +#define RSR_BITS \ + "\020" \ + "\010WEVENT" \ + "\007RX_BUF_FULL" \ + "\006LKCHG" \ + "\005RUNT" \ + "\004LONG" \ + "\003CRC" \ + "\002FAE" \ + "\001ROK" + +/* Config 0 */ + +#define CON0_SUSLED 0x80 +#define CON0_PARM_EN 0x40 /* parameter enable */ +#define CON0_LDPS 0x08 +#define CON0_MSEL 0x04 /* media select 1:MII, 0:auto */ +#define CON0_LEDS 0x03 /* LED pattern */ + +/* Config 1 */ +#define CON0_BWF 0x40 /* Broadcast wakeup function 1:on 0:off */ +#define CON0_MWF 0x20 /* Multicast wakeup function 1:on 0:off */ +#define CON0_UWF 0x10 /* Unicast wakeup function 1:on 0:off */ +#define CON0_LONGWF1 0x02 /* */ +#define CON0_LONGWF0 0x01 /* */ + + +/* MSR : Media Status register */ +#define MSR_TXFCE 0x80 /* Tx Flow control enable */ +#define MSR_RXFCE 0x40 /* Rx Flow control enable */ +#define MSR_DUPLEX 0x10 /* full duplex */ +#define MSR_SPEED_100 0x08 /* 100Mbps mode */ +#define MSR_LINK 0x04 /* link status */ +#define MSR_TXPF 0x02 /* 8150 sends pause packet */ +#define MSR_RXPF 0x01 /* 8150 is in backoff state*/ + +#define MSR_BITS \ + "\020" \ + "\010TXFCE" \ + "\007RXFCE" \ + "\005DUPLEX" \ + "\004SPEED_100" \ + "\003LINK" \ + "\002TXPF" \ + "\001RXPF" + +/* MII PHY Address */ +#define PHYADD_MASK 0x1f + +/* MII PHY Data */ +#define PHYCNT_OWN 0x40 /* 8150 owns:1 not owns:0 */ +#define PHYCNT_RWCR 0x20 /* write:1 read:0 */ +#define PHYCNT_PHYOFF 0x1f + +/* BMCR (almost same with MII_CONTROL register) */ +#define BMCR_RESET 0x8000 /* PHY reset */ +#define BMCR_Spd_Set 0x2000 /* 100Mbps */ +#define BMCR_ANE 0x1000 /* auto negotiation enable */ +#define BMCR_RSA 0x0200 /* restart auto negotiation */ +#define BMCR_duplex 0x0100 /* 100Mbps */ + +/* Basic mode status register */ +/* Auto-negotiation Advertisement register */ +/* Auto-negotiation Link Partner Ability register */ +/* Auto-negotiation Expansion register */ + +/* Nway test register */ +#define NWAYT_NWLPBK 0x0080 +#define NWAYT_ENNWLE 0x0008 +#define NWAYT_FLAGABD 0x0004 +#define NWAYT_FLAGPDF 0x0002 +#define NWAYT_FLAGLSC 0x0001 + +/* CS configuration register */ +#define CS_TESTFUN 0x8000 /* */ +#define CS_LD 0x0200 /* */ +#define CS_HEARTBEAT 0x0100 /* */ +#define CS_JBEN 0x0080 /* */ +#define CS_F_LINK100 0x0040 /* */ +#define CS_F_CONNECT 0x0020 /* */ +#define CS_CON_STATUS 0x0008 /* */ +#define CS_CON_STATUS_EN 0x0004 /* */ +#define CS_PASS_SCR 0x0001 /* bypass scramble function */ + +/* + * header format of rx packet + */ +#define RXHD_MULT 0x8000 /* multicast packet */ +#define RXHD_PHYS 0x4000 /* physical match packet */ +#define RXHD_RUNT 0x2000 /* too short */ +#define RXHD_VALID 0x1000 /* packet is ok */ +#define RXHD_BYTECNT 0x0fff /* rx byte count */ + +#define RXHD_BITS \ + "\020" \ + "\020MULT" \ + "\017PHYS" \ + "\016RUNT" \ + "\015VALID" +/* + * Offset to EPROM contents + */ +#define URF_EEPROM_BASE 0x1200 +#define EPROM_EthernetID 0x0002 diff --git a/usr/src/uts/common/io/urf/urf_usbgem.c b/usr/src/uts/common/io/urf/urf_usbgem.c new file mode 100644 index 0000000000..f61c8e3502 --- /dev/null +++ b/usr/src/uts/common/io/urf/urf_usbgem.c @@ -0,0 +1,1039 @@ +/* + * urf_usbgem.c : Realtek RTL8150 USB to Fast Ethernet Driver for Solaris + * + * Copyright (c) 2003-2012 Masayuki Murayama. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#pragma ident "%W% %E%" + +/* + * Changelog: + */ + +/* + * TODO + */ +/* ======================================================= */ + +/* + * Solaris system header files and macros + */ + +/* minimum kernel headers for drivers */ +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/debug.h> +#include <sys/kmem.h> +#include <sys/modctl.h> +#include <sys/errno.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/byteorder.h> + +/* ethernet stuff */ +#include <sys/ethernet.h> + +/* interface card depend stuff */ +#include <sys/stropts.h> +#include <sys/stream.h> +#include <sys/strlog.h> +#include <sys/usb/usba.h> +#include "usbgem.h" +#include "usbgem_mii.h" +#include "rtl8150reg.h" + +char ident[] = "rtl8150 usbnic driver v" VERSION; + +/* + * Useful macros + */ +#define ROUNDUP2(x, y) (((x)+(y)-1) & ~((y)-1)) +#define CHECK_AND_JUMP(err, label) if (err != USB_SUCCESS) goto label + +/* + * Debugging + */ +#ifdef DEBUG_LEVEL +static int urf_debug = DEBUG_LEVEL; +#define DPRINTF(n, args) if (urf_debug > (n)) cmn_err args +#else +#define DPRINTF(n, args) +#endif + +/* + * Our configration for rtl8150 + */ +/* timeouts */ +#define ONESEC (drv_usectohz(1*1000000)) + +/* + * Local device definitions + */ +struct chip_info { + int flags; + char *name; + int type; +}; + +#define CHIPTABLESIZE (sizeof (chiptbl_8150) / sizeof (struct chip_info)) + +struct urf_dev { + /* + * Misc HW information + */ + struct chip_info *chip; + uint8_t cr; + uint8_t tsr; + uint16_t rcr; + uint8_t txok_cnt; +}; + +/* + * private functions + */ + +/* mii operations */ +static uint16_t urf_mii_read(struct usbgem_dev *, uint_t, int *errp); +static void urf_mii_write(struct usbgem_dev *, uint_t, uint16_t, int *errp); + +/* nic operations */ +static int urf_attach_chip(struct usbgem_dev *); +static int urf_reset_chip(struct usbgem_dev *); +static int urf_init_chip(struct usbgem_dev *); +static int urf_start_chip(struct usbgem_dev *); +static int urf_stop_chip(struct usbgem_dev *); +static int urf_set_media(struct usbgem_dev *); +static int urf_set_rx_filter(struct usbgem_dev *); +static int urf_get_stats(struct usbgem_dev *); + +/* packet operations */ +static mblk_t *urf_tx_make_packet(struct usbgem_dev *, mblk_t *); +static mblk_t *urf_rx_make_packet(struct usbgem_dev *, mblk_t *); + +/* =============================================================== */ +/* + * I/O functions + */ +/* =============================================================== */ +#define OUTB(dp, p, v, errp, label) \ + if ((*(errp) = usbgem_ctrl_out_val((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ USB_REQ_SET_ADDRESS, \ + /* wValue */ (p), \ + /* wIndex */ 0, \ + /* wLength */ 1, \ + /* value */ (v))) != USB_SUCCESS) goto label + +#define OUTW(dp, p, v, errp, label) \ + if ((*(errp) = usbgem_ctrl_out_val((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ USB_REQ_SET_ADDRESS, \ + /* wValue */ (p), \ + /* wIndex */ 0, \ + /* wLength */ 2, \ + /* value */ (v))) != USB_SUCCESS) goto label + +#define OUTS(dp, p, buf, len, errp, label) \ + if ((*(errp) = usbgem_ctrl_out((dp), \ + /* bmRequestType */ USB_DEV_REQ_HOST_TO_DEV \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ USB_REQ_SET_ADDRESS, \ + /* wValue */ (p), \ + /* wIndex */ 0, \ + /* wLength */ (len), \ + /* value */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +#define IN(dp, p, vp, errp, label) \ + if ((*(errp) = usbgem_ctrl_in_val((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ USB_REQ_SET_ADDRESS, \ + /* wValue */ (p), \ + /* wIndex */ 0, \ + /* wLength */ sizeof ((*vp)), \ + /* valuep */ (vp))) != USB_SUCCESS) goto label + +#define INS(dp, p, buf, len, errp, label) \ + if ((*(errp) = usbgem_ctrl_in((dp), \ + /* bmRequestType */ USB_DEV_REQ_DEV_TO_HOST \ + | USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \ + /* bRequest */ USB_REQ_SET_ADDRESS, \ + /* wValue */ (p), \ + /* wIndex */ 0, \ + /* wLength */ (len), \ + /* valuep */ (buf), \ + /* size */ (len))) != USB_SUCCESS) goto label + +/* =============================================================== */ +/* + * variables + */ +/* =============================================================== */ +static int urf_ppa = 0; + +/* =============================================================== */ +/* + * Hardware manupilation + */ +/* =============================================================== */ +static int +urf_reset_chip(struct usbgem_dev *dp) +{ + int i; + int err; + uint8_t reg; + struct urf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + lp->cr = 0; + OUTB(dp, CR, lp->cr | CR_SOFT_RST, &err, usberr); + + for (i = 0; i < 100; i++) { + IN(dp, CR, ®, &err, usberr); + if ((reg & CR_SOFT_RST) == 0) { + return (USB_SUCCESS); + } + } + /* time out */ + cmn_err(CE_WARN, "%s: failed to reset: timeout", dp->name); + return (USB_FAILURE); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (USB_FAILURE); +} + +/* + * Setup rtl8150 + */ +static int +urf_init_chip(struct usbgem_dev *dp) +{ + int i; + uint32_t val; + int err; + struct urf_dev *lp = dp->private; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* ID registers: set later by urf_set_rx_filter */ + + /* Multicast registers: set later by urf_set_rx_filter */ + + /* Command register : Enable Tx and Rx before writing TCR and RCR */ + lp->cr |= CR_RE | CR_TE; + OUTB(dp, CR, lp->cr, &err, usberr); + + /* Transmit configration register : */ + OUTB(dp, TCR, TCR_IFG_802_3, &err, usberr); + + /* Receive configuration register : disable rx filter */ + lp->rcr = RCR_TAIL | RCR_AER | RCR_AR; + OUTW(dp, RCR, lp->rcr, &err, usberr); +#ifdef notdef + /* Media status register */ + err = urf_set_media(dp); + CHECK_AND_JUMP(err, usberr); +#endif + /* Configuration register 0: no need to change */ + + DPRINTF(2, (CE_CONT, "!%s: %s: end (success)", dp->name, __func__)); + return (USB_SUCCESS); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (USB_FAILURE); +} + +static int +urf_start_chip(struct usbgem_dev *dp) +{ + struct urf_dev *lp = dp->private; + + /* do nothing */ + return (USB_SUCCESS); +} + +static int +urf_stop_chip(struct usbgem_dev *dp) +{ + return (urf_reset_chip(dp)); +} + +static int +urf_get_stats(struct usbgem_dev *dp) +{ + /* do nothing */ + return (USB_SUCCESS); +} + +static uint_t +urf_mcast_hash(struct usbgem_dev *dp, const uint8_t *addr) +{ + return (usbgem_ether_crc_be(addr)); +} + +static int +urf_set_rx_filter(struct usbgem_dev *dp) +{ + int i; + uint16_t mode; + uint8_t mhash[8]; + int err; + int16_t rcr; + struct urf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called, rxmode:%x", + dp->name, __func__, dp->rxmode)); + + if (lp->rcr & (RCR_AB | RCR_AD | RCR_AAM | RCR_AAP | RCR_AM)) { +#ifdef notdef + /* disable rx filter before changing it. */ + lp->rcr &= ~(RCR_AB | RCR_AD | RCR_AAM | RCR_AAP | RCR_AM); + OUTW(dp, RCR, lp->rcr, &err, usberr); +#else + /* receive all packets while we change rx filter*/ + OUTW(dp, RCR, lp->rcr | RCR_AAM | RCR_AAP, &err, usberr); +#endif + } + + mode = RCR_AB /* accept broadcast */ + | RCR_AD; /* accept physical match */ + bzero(mhash, sizeof (mhash)); + + if (dp->rxmode & RXMODE_PROMISC) { + /* promiscious mode implies all multicast and all physical */ + mode |= RCR_AAM | RCR_AAP; + } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 64/2) { + /* accept all multicast packets */ + mode |= RCR_AAM; + } else if (dp->mc_count > 0) { + /* + * make hash table to select interresting + * multicast address only. + */ + mode |= RCR_AM; + for (i = 0; i < dp->mc_count; i++) { + uint_t h; + /* hash table is 64 = 2^6 bit width */ + h = dp->mc_list[i].hash >> (32 - 6); + mhash[h / 8] |= 1 << (h % 8); + } + } + lp->rcr |= mode; + + /* set mac address */ + OUTS(dp, IDR, dp->cur_addr.ether_addr_octet, ETHERADDRL, &err, usberr); + + /* set multicast hash table */ + if (mode & RCR_AM) { + /* need to set up multicast hash table */ + OUTS(dp, MAR, mhash, sizeof (mhash), &err, usberr); + } + + OUTW(dp, RCR, lp->rcr, &err, usberr); + +#if DEBUG_LEVEL > 2 + IN(dp, RCR, &rcr, &err, usberr); + cmn_err(CE_CONT, "!%s: %s: rcr:%b returned", + dp->name, __func__, rcr, RCR_BITS); +#endif + return (USB_SUCCESS); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (USB_FAILURE); +} + +static int +urf_set_media(struct usbgem_dev *dp) +{ + uint8_t new; + uint8_t old; + int err; + struct urf_dev *lp = dp->private; + + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* select duplex: do nothing */ + + /* select speed: do nothing */ + + /* flow control */ + IN(dp, MSR, &old, &err, usberr); + + + /* setup flow control */ + new = old & ~(MSR_TXFCE | MSR_RXFCE); + switch (dp->flow_control) { + case FLOW_CONTROL_SYMMETRIC: + new |= MSR_TXFCE | MSR_RXFCE; + break; + + case FLOW_CONTROL_TX_PAUSE: + new |= MSR_TXFCE; + break; + + case FLOW_CONTROL_RX_PAUSE: + new |= MSR_RXFCE; + break; + + case FLOW_CONTROL_NONE: + default: + break; + } + + if (new != old) { + OUTB(dp, MSR, new, &err, usberr); + } + DPRINTF(2, (CE_CONT, "!%s: %s: returned", dp->name, __func__)); + return (USB_SUCCESS); + +usberr: + cmn_err(CE_NOTE, "!%s: %s: usberr detected", dp->name, __func__); + return (USB_FAILURE); +} + +/* + * send/receive packet check + */ +static mblk_t * +urf_tx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + size_t len; + mblk_t *new; + mblk_t *tp; + uint8_t *bp; + uint8_t *last_pos; + + len = msgdsize(mp); + + if (len < ETHERMIN || mp->b_cont != NULL || (len & 0x3f) == 0) { + /* + * re-allocate mp + */ + len = max(len, ETHERMIN); + + if ((len & 0x3f) == 0) { + /* workaround for buggy USB hba */ + len++; + } + + if ((new = allocb(len, 0)) == NULL) { + return (NULL); + } + + /* copy contents of the buffer */ + new->b_wptr = new->b_rptr + len; + bp = new->b_rptr; + for (tp = mp; tp; tp = tp->b_cont) { + len = tp->b_wptr - tp->b_rptr; + bcopy(tp->b_rptr, bp, len); + bp += len; + } + + last_pos = new->b_wptr; + while (bp < last_pos) { + *bp++ = 0; + } + + mp = new; + } + + return (mp); +} + +static void +urf_dump_packet(struct usbgem_dev *dp, uint8_t *bp, int n) +{ + int i; + + for (i = 0; i < n; i += 8, bp += 8) { + cmn_err(CE_CONT, "%02x %02x %02x %02x %02x %02x %02x %02x", + bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7]); + } +} + +static mblk_t * +urf_rx_make_packet(struct usbgem_dev *dp, mblk_t *mp) +{ + uint8_t *p; + uint16_t rxhd; + uint_t len; + + ASSERT(mp != NULL); + len = msgdsize(mp); +#ifdef DEBUG_LEVEL + DPRINTF(2, (CE_CONT, "!%s: time:%d %s: len:%d cont:%p", + dp->name, ddi_get_lbolt(), __func__, len, mp->b_cont)); + + if (urf_debug > 2) { + urf_dump_packet(dp, mp->b_rptr, max(6, len)); + } +#endif + if (len < ETHERMIN + ETHERFCSL) { + /* Too short */ + dp->stats.runt++; + dp->stats.errrcv++; + return (NULL); + } + + /* get Rx header which is placed at tail of the packet. */ + p = mp->b_wptr - 4; + rxhd = (p[1] << 8) | p[0]; + len = rxhd & RXHD_BYTECNT; + + DPRINTF(2, (CE_CONT, "!%s: %s: rsr:%b len:%d", + dp->name, __func__, rxhd, RXHD_BITS, len)); + + /* check if error happen */ + if ((rxhd & (RXHD_VALID)) == 0) { + DPRINTF(-1, (CE_CONT, "!%s: %s: rxhd:%b", + dp->name, __func__, rxhd, RXHD_BITS)); + if (rxhd & RXHD_RUNT) { + dp->stats.runt++; + } + + dp->stats.errrcv++; + return (NULL); + } +#ifdef notdef + /* check packet size */ + if (len > ETHERMAX + ETHERFCSL) { + /* too long */ + dp->stats.frame_too_long++; + dp->stats.errrcv++; + return (NULL); + } else if (len < ETHERMIN + ETHERFCSL) { + dp->stats.runt++; + dp->stats.errrcv++; + return (NULL); + } +#endif + /* remove tailing crc field */ + mp->b_wptr -= ETHERFCSL; + return (mp); +} + +/* + * MII Interfaces + */ +static uint16_t +urf_mii_read(struct usbgem_dev *dp, uint_t index, int *errp) +{ + int reg; + uint16_t val; + + DPRINTF(4, (CE_CONT, "!%s: %s: called, ix:%d", + dp->name, __func__, index)); + + *errp = USB_SUCCESS; + + switch (index) { + case MII_CONTROL: + reg = BMCR; + break; + + case MII_STATUS: + reg = BMSR; + break; + + case MII_AN_ADVERT: + reg = ANAR; + break; + + case MII_AN_LPABLE: + reg = ANLP; + break; + + case MII_AN_EXPANSION: + reg = ANER; + break; + + default: + return (0); + } + + IN(dp, reg, &val, errp, usberr); + + if (index == MII_STATUS) { + uint8_t msr; + /* + * Fix MII status register as it does't have LINKUP and + * MFPRMBLSUPR bits. + */ + IN(dp, MSR, &msr, errp, usberr); + + val |= (MII_STATUS_MFPRMBLSUPR | MII_STATUS_LINKUP); + if ((msr & MSR_LINK) == 0) { + val &= ~MII_STATUS_LINKUP; + } + } + + return (val); + +usberr: + cmn_err(CE_CONT, + "!%s: %s: usberr(%d) detected", dp->name, __func__, *errp); + + return (0); +} + +static void +urf_mii_write(struct usbgem_dev *dp, uint_t index, uint16_t val, int *errp) +{ + int reg; + + DPRINTF(5, (CE_CONT, "!%s: %s called", dp->name, __func__)); + + *errp = USB_SUCCESS; + + switch (index) { + case MII_CONTROL: + reg = BMCR; + break; + + case MII_STATUS: + reg = BMSR; + break; + + case MII_AN_ADVERT: + reg = ANAR; + break; + + case MII_AN_LPABLE: + reg = ANLP; + break; + + case MII_AN_EXPANSION: + reg = ANER; + break; + + default: + return; + } + + OUTW(dp, reg, val, errp, usberr); +usberr: + ; +} + +/* ======================================================== */ +/* + * OS depend (device driver DKI) routine + */ +/* ======================================================== */ +static void +urf_eeprom_dump(struct usbgem_dev *dp, int size) +{ + int i; + int err; + uint16_t w0, w1, w2, w3; + + cmn_err(CE_CONT, "!%s: eeprom dump:", dp->name); + for (i = URF_EEPROM_BASE; i < size + URF_EEPROM_BASE; i += 8) { + IN(dp, i + 0, &w0, &err, usberr); + IN(dp, i + 2, &w1, &err, usberr); + IN(dp, i + 4, &w2, &err, usberr); + IN(dp, i + 6, &w3, &err, usberr); + cmn_err(CE_CONT, "!0x%02x: 0x%04x 0x%04x 0x%04x 0x%04x", + i - URF_EEPROM_BASE, w0, w1, w2, w3); + } +usberr: + ; +} + +static int +urf_attach_chip(struct usbgem_dev *dp) +{ + int i; + uint8_t old; + uint_t new; + uint8_t reg; + int err; + struct urf_dev *lp = dp->private; + + /* + * setup flow control bit in eeprom + */ + IN(dp, URF_EEPROM_BASE + 9, &old, &err, usberr); + + DPRINTF(0, (CE_CONT, "!%s: eeprom offset 9: %02x", dp->name, old)); + + if (dp->ugc.usbgc_flow_control != FLOW_CONTROL_NONE) { + /* enable PAUSE bit */ + new = old | 0x04; + } else { + /* clear PAUSE bit */ + new = old & ~0x04; + } + if (new != old) { + /* make eeprom writable */ + OUTB(dp, CR, lp->cr | CR_WEPROM, &err, usberr); + + /* eerom allows only word access for writing */ + IN(dp, URF_EEPROM_BASE + 8, ®, &err, usberr); + new = (new << 8) | reg; + + OUTW(dp, URF_EEPROM_BASE + 8, new, &err, usberr); + + /* make eeprom non-writable */ + OUTB(dp, CR, lp->cr, &err, usberr); + } + + /* + * load EEPROM contents into nic + */ + OUTB(dp, CR, lp->cr | CR_AUTOLOAD, &err, usberr); + CHECK_AND_JUMP(err, usberr); + + for (i = 0; i < 100; i++) { + IN(dp, CR, ®, &err, usberr); + if ((reg & CR_AUTOLOAD) == 0) { + goto autoload_done; + } + } + /* timeout */ + cmn_err(CE_WARN, "%s: %s: failed to autoload: timeout", + dp->name, __func__); + goto usberr; + +autoload_done: + /* + * mac address in EEPROM has loaded to ID registers. + */ + INS(dp, IDR, dp->dev_addr.ether_addr_octet, ETHERADDRL, &err, usberr); + + /* no need to scan phy */ + dp->mii_phy_addr = -1; + +#if DEBUG_LEVEL > 2 + urf_eeprom_dump(dp, 0x80); +#endif + +#ifdef CONFIG_VLAN + dp->misc_flag = USBGEM_VLAN; +#endif + return (USB_SUCCESS); + +usberr: + cmn_err(CE_WARN, "%s: urf_attach_chip: usb error detected", dp->name); + return (USB_FAILURE); +} + +static int +urfattach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int i; + ddi_iblock_cookie_t c; + int ret; + int unit; + struct chip_info *p; + const char *drv_name; + struct usbgem_dev *dp; + void *base; + struct usbgem_conf *ugcp; + struct urf_dev *lp; + + unit = ddi_get_instance(dip); + drv_name = ddi_driver_name(dip); + + DPRINTF(3, (CE_CONT, "!%s%d: %s: called, cmd:%d", + drv_name, __func__, unit, cmd)); + + if (cmd == DDI_ATTACH) { + /* + * Check if the chip is supported. + */ + + /* + * Check the chip if it is really realtek rtl8150 + */ + + /* + * construct usbgem configration + */ + ugcp = kmem_zalloc(sizeof (*ugcp), KM_SLEEP); + + /* name */ + sprintf(ugcp->usbgc_name, + "%s%d(ppa=%d)", drv_name, unit, urf_ppa); +#ifdef USBGEM_CONFIG_GLDv3 + ugcp->usbgc_ppa = urf_ppa; +#else + ugcp->usbgc_ppa = unit; +#endif + ugcp->usbgc_ifnum = 0; + ugcp->usbgc_alt = 0; + + ugcp->usbgc_tx_list_max = 16; + + /* the rx status partially replaces FCS */ + ugcp->usbgc_rx_header_len = 0; + ugcp->usbgc_rx_list_max = 64; + + /* time out parameters */ + ugcp->usbgc_tx_timeout = USBGEM_TX_TIMEOUT; + ugcp->usbgc_tx_timeout_interval = ONESEC; + + /* flow control */ + ugcp->usbgc_flow_control = FLOW_CONTROL_RX_PAUSE; + + /* MII timeout parameters */ + ugcp->usbgc_mii_link_watch_interval = ONESEC; + ugcp->usbgc_mii_an_watch_interval = ONESEC/5; + ugcp->usbgc_mii_reset_timeout = MII_RESET_TIMEOUT; /* 1 sec */ + ugcp->usbgc_mii_an_timeout = MII_AN_TIMEOUT; /* 5 sec */ + ugcp->usbgc_mii_an_wait = (25*ONESEC)/10; + ugcp->usbgc_mii_linkdown_timeout = MII_LINKDOWN_TIMEOUT; + + ugcp->usbgc_mii_an_delay = ONESEC/10; + ugcp->usbgc_mii_linkdown_action = MII_ACTION_RSA; + ugcp->usbgc_mii_linkdown_timeout_action = MII_ACTION_RESET; + ugcp->usbgc_mii_dont_reset = B_FALSE; + + /* I/O methods */ + + /* mac operation */ + ugcp->usbgc_attach_chip = &urf_attach_chip; + ugcp->usbgc_reset_chip = &urf_reset_chip; + ugcp->usbgc_init_chip = &urf_init_chip; + ugcp->usbgc_start_chip = &urf_start_chip; + ugcp->usbgc_stop_chip = &urf_stop_chip; + ugcp->usbgc_multicast_hash = &urf_mcast_hash; + + ugcp->usbgc_set_rx_filter = &urf_set_rx_filter; + ugcp->usbgc_set_media = &urf_set_media; + ugcp->usbgc_get_stats = &urf_get_stats; +#ifdef notdef + ugcp->usbgc_interrupt = &urf_interrupt; +#else + ugcp->usbgc_interrupt = NULL; +#endif + /* packet operation */ + ugcp->usbgc_tx_make_packet = &urf_tx_make_packet; + ugcp->usbgc_rx_make_packet = &urf_rx_make_packet; + + /* mii operations */ + ugcp->usbgc_mii_probe = &usbgem_mii_probe_default; + ugcp->usbgc_mii_init = &usbgem_mii_init_default; + ugcp->usbgc_mii_config = &usbgem_mii_config_default; + ugcp->usbgc_mii_read = &urf_mii_read; + ugcp->usbgc_mii_write = &urf_mii_write; + + /* mtu */ + ugcp->usbgc_min_mtu = ETHERMTU; + ugcp->usbgc_max_mtu = ETHERMTU; + ugcp->usbgc_default_mtu = ETHERMTU; + + lp = kmem_zalloc(sizeof (struct urf_dev), KM_SLEEP); + lp->chip = p; + + ddi_set_driver_private(dip, NULL); + + dp = usbgem_do_attach(dip, ugcp, lp, sizeof (struct urf_dev)); + + kmem_free(ugcp, sizeof (*ugcp)); + + if (dp != NULL) { + urf_ppa++; + return (DDI_SUCCESS); + } + +err_free_mem: + kmem_free(lp, sizeof (struct urf_dev)); +err_close_pipe: +err: + return (DDI_FAILURE); + } + if (cmd == DDI_RESUME) { + return (usbgem_resume(dip)); + } + return (DDI_FAILURE); +} + +static int +urfdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int ret; + + if (cmd == DDI_DETACH) { + ret = usbgem_do_detach(dip); + if (ret != DDI_SUCCESS) { + return (DDI_FAILURE); + } + urf_ppa--; + return (DDI_SUCCESS); + } + if (cmd == DDI_SUSPEND) { + return (usbgem_suspend(dip)); + } + return (DDI_FAILURE); +} + +/* ======================================================== */ +/* + * OS depend (loadable streams driver) routine + */ +/* ======================================================== */ +#ifdef USBGEM_CONFIG_GLDv3 +USBGEM_STREAM_OPS(urf_ops, urfattach, urfdetach); +#else +static struct module_info urfminfo = { + 0, /* mi_idnum */ + "urf", /* mi_idname */ + 0, /* mi_minpsz */ + ETHERMTU, /* mi_maxpsz */ + ETHERMTU*128, /* mi_hiwat */ + 1, /* mi_lowat */ +}; + +static struct qinit urfrinit = { + (int (*)()) NULL, /* qi_putp */ + usbgem_rsrv, /* qi_srvp */ + usbgem_open, /* qi_qopen */ + usbgem_close, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &urfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct qinit urfwinit = { + usbgem_wput, /* qi_putp */ + usbgem_wsrv, /* qi_srvp */ + (int (*)()) NULL, /* qi_qopen */ + (int (*)()) NULL, /* qi_qclose */ + (int (*)()) NULL, /* qi_qadmin */ + &urfminfo, /* qi_minfo */ + NULL /* qi_mstat */ +}; + +static struct streamtab urf_info = { + &urfrinit, /* st_rdinit */ + &urfwinit, /* st_wrinit */ + NULL, /* st_muxrinit */ + NULL /* st_muxwrinit */ +}; + +static struct cb_ops cb_urf_ops = { + nulldev, /* cb_open */ + nulldev, /* 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 */ + &urf_info, /* cb_stream */ + D_NEW|D_MP /* cb_flag */ +}; + +static struct dev_ops urf_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + usbgem_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + urfattach, /* devo_attach */ + urfdetach, /* devo_detach */ + nodev, /* devo_reset */ + &cb_urf_ops, /* devo_cb_ops */ + NULL, /* devo_bus_ops */ + usbgem_power, /* devo_power */ +#if DEVO_REV >= 4 + usbgem_quiesce, /* devo_quiesce */ +#endif + +}; +#endif + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + ident, + &urf_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, &modldrv, NULL +}; + +/* ======================================================== */ +/* + * _init : done + */ +/* ======================================================== */ +int +_init(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!urf: _init: called")); + + status = usbgem_mod_init(&urf_ops, "urf"); + if (status != DDI_SUCCESS) { + return (status); + } + status = mod_install(&modlinkage); + if (status != DDI_SUCCESS) { + usbgem_mod_fini(&urf_ops); + } + return (status); +} + +/* + * _fini : done + */ +int +_fini(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!urf: _fini: called")); + status = mod_remove(&modlinkage); + if (status == DDI_SUCCESS) { + usbgem_mod_fini(&urf_ops); + } + return (status); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} diff --git a/usr/src/uts/common/io/usb/usba/usba_ugen.c b/usr/src/uts/common/io/usb/usba/usba_ugen.c index cb20c24270..5852e40799 100644 --- a/usr/src/uts/common/io/usb/usba/usba_ugen.c +++ b/usr/src/uts/common/io/usb/usba/usba_ugen.c @@ -23,6 +23,10 @@ */ /* + * Copyright (c) 2014, Joyent, Inc. All rights reserved. + */ + +/* * UGEN: USB Generic Driver support code * * This code provides entry points called by the ugen driver or other @@ -1082,7 +1086,10 @@ usb_ugen_poll(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, short events, ((epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_ON) == 0)) { *reventsp |= POLLIN; - } else if (!anyyet) { + } + + if ((!*reventsp && !anyyet) || + (events & POLLET)) { *phpp = &epp->ep_pollhead; epp->ep_state |= UGEN_EP_STATE_INTR_IN_POLL_PENDING; @@ -1101,7 +1108,10 @@ usb_ugen_poll(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, short events, ((epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_ON) == 0)) { *reventsp |= POLLIN; - } else if (!anyyet) { + } + + if ((!*reventsp && !anyyet) || + (events & POLLET)) { *phpp = &epp->ep_pollhead; epp->ep_state |= UGEN_EP_STATE_ISOC_IN_POLL_PENDING; @@ -1115,9 +1125,10 @@ usb_ugen_poll(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, short events, break; case UGEN_MINOR_DEV_STAT_NODE: - if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED) { + if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED) *reventsp |= POLLIN; - } else if (!anyyet) { + + if ((!*reventsp && !anyyet) || (events & POLLET)) { *phpp = &ugenp->ug_ds.dev_pollhead; ugenp->ug_ds.dev_stat |= UGEN_DEV_STATUS_POLL_PENDING; @@ -1131,9 +1142,10 @@ usb_ugen_poll(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, short events, break; } } else { - if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED) { + if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED) *reventsp |= POLLHUP|POLLIN; - } else if (!anyyet) { + + if ((!*reventsp && !anyyet) || (events & POLLET)) { *phpp = &ugenp->ug_ds.dev_pollhead; ugenp->ug_ds.dev_stat |= UGEN_DEV_STATUS_POLL_PENDING; diff --git a/usr/src/uts/common/io/usbgem/usbgem.c b/usr/src/uts/common/io/usbgem/usbgem.c new file mode 100644 index 0000000000..a42f7119ef --- /dev/null +++ b/usr/src/uts/common/io/usbgem/usbgem.c @@ -0,0 +1,6389 @@ +/* + * usbgem.c: General USB to Fast Ethernet mac driver framework + * + * Copyright (c) 2002-2012 Masayuki Murayama. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#pragma ident "@(#)usbgem.c 1.6 12/02/09" + +/* + * Change log + */ + +/* + * TODO: + * implement DELAYED_START + */ + +/* + * System Header files. + */ +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/debug.h> +#include <sys/kmem.h> +#include <sys/vtrace.h> +#include <sys/ethernet.h> +#include <sys/modctl.h> +#include <sys/errno.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#ifndef USBGEM_CONFIG_GLDv3 +#include <sys/dlpi.h> +#include <sys/strsubr.h> +#endif +#include <sys/stream.h> /* required for MBLK* */ +#include <sys/strsun.h> /* required for mionack() */ +#include <sys/byteorder.h> + +#include <sys/usb/usba.h> +#ifdef USBGEM_CONFIG_GLDv3 +#include <inet/common.h> +#include <inet/led.h> +#include <inet/mi.h> +#include <inet/nd.h> +#endif + +/* supplement definitions */ +extern const char *usb_str_cr(usb_cr_t); + +#ifndef USBGEM_CONFIG_GLDv3 +#pragma weak gld_linkstate +#endif +#include <sys/note.h> + +#include "usbgem_mii.h" +#include "usbgem.h" + +#ifdef MODULE +char ident[] = "usb general ethernet mac driver v" VERSION; +#else +extern char ident[]; +#endif + +/* Debugging support */ +#ifdef USBGEM_DEBUG_LEVEL +static int usbgem_debug = USBGEM_DEBUG_LEVEL; +#define DPRINTF(n, args) if (usbgem_debug > (n)) cmn_err args +#else +#define DPRINTF(n, args) +#endif + +/* + * Useful macros and typedefs + */ +#define ROUNDUP(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define DEFAULT_PIPE(dp) ((dp)->reg_data->dev_default_ph) +#define VTAG_SIZE 4 +#define BOOLEAN(x) ((x) != 0) +/* + * configuration parameters + */ +#define USBDRV_MAJOR_VER 2 +#define USBDRV_MINOR_VER 0 + +#define ETHERHEADERL (sizeof (struct ether_header)) +#define MAXPKTLEN(dp) ((dp)->mtu + ETHERHEADERL) +#define MAXPKTBUF(dp) ((dp)->mtu + ETHERHEADERL + ETHERFCSL) + +#define WATCH_INTERVAL_FAST drv_usectohz(100*1000) + +#define STOP_GRACEFUL B_TRUE + +/* + * Private functions + */ +static int usbgem_open_pipes(struct usbgem_dev *dp); +static int usbgem_close_pipes(struct usbgem_dev *dp); +static void usbgem_intr_cb(usb_pipe_handle_t, usb_intr_req_t *); +static void usbgem_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *); +static void usbgem_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *); + +static int usbgem_mii_start(struct usbgem_dev *); +static void usbgem_mii_stop(struct usbgem_dev *); + +/* local buffer management */ +static int usbgem_init_rx_buf(struct usbgem_dev *); + +/* internal mac interfaces */ +static void usbgem_tx_timeout(struct usbgem_dev *); +static void usbgem_mii_link_watcher(struct usbgem_dev *); +static int usbgem_mac_init(struct usbgem_dev *); +static int usbgem_mac_start(struct usbgem_dev *); +static int usbgem_mac_stop(struct usbgem_dev *, int, boolean_t); +static void usbgem_mac_ioctl(struct usbgem_dev *, queue_t *, mblk_t *); + +int usbgem_speed_value[] = {10, 100, 1000}; + +static int usbgem_ctrl_retry = 5; + +/* usb event support */ +static int usbgem_disconnect_cb(dev_info_t *dip); +static int usbgem_reconnect_cb(dev_info_t *dip); +int usbgem_suspend(dev_info_t *dip); +int usbgem_resume(dev_info_t *dip); + +static uint8_t usbgem_bcastaddr[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +#ifdef MODULE +extern struct mod_ops mod_miscops; + +static struct modlmisc modlmisc = { + &mod_miscops, + "usbgem v" VERSION, +}; + +static struct modlinkage modlinkage = { + MODREV_1, &modlmisc, NULL +}; + +/* + * _init : done + */ +int +_init(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!usbgem: _init: called")); + status = mod_install(&modlinkage); + + return (status); +} + +/* + * _fini : done + */ +int +_fini(void) +{ + int status; + + DPRINTF(2, (CE_CONT, "!usbgem: _fini: called")); + status = mod_remove(&modlinkage); + return (status); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} +#endif /* MODULE */ + +/* ============================================================== */ +/* + * Ether CRC calculation utilities + */ +/* ============================================================== */ +/* + * Ether CRC calculation according to 21143 data sheet + */ +#define CRC32_POLY_LE 0xedb88320 +uint32_t +usbgem_ether_crc_le(const uint8_t *addr) +{ + int idx; + int bit; + uint_t data; + uint32_t crc = 0xffffffff; + + crc = 0xffffffff; + for (idx = 0; idx < ETHERADDRL; idx++) { + for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) { + crc = (crc >> 1) ^ + (((crc ^ data) & 1) ? CRC32_POLY_LE : 0); + } + } + return (crc); +} + +#define CRC32_POLY_BE 0x04c11db7 +uint32_t +usbgem_ether_crc_be(const uint8_t *addr) +{ + int idx; + int bit; + uint_t data; + uint32_t crc; + + crc = 0xffffffff; + for (idx = 0; idx < ETHERADDRL; idx++) { + for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) { + crc = (crc << 1) ^ + ((((crc >> 31) ^ data) & 1) ? CRC32_POLY_BE : 0); + } + } + return (crc); +} + +int +usbgem_prop_get_int(struct usbgem_dev *dp, char *prop_template, int def_val) +{ + char propname[32]; + + (void) sprintf(propname, prop_template, dp->name); + + return (ddi_prop_get_int(DDI_DEV_T_ANY, dp->dip, + DDI_PROP_DONTPASS, propname, def_val)); +} + +static int +usbgem_population(uint32_t x) +{ + int i; + int cnt; + + cnt = 0; + for (i = 0; i < 32; i++) { + if (x & (1 << i)) { + cnt++; + } + } + return (cnt); +} + +static clock_t +usbgem_timestamp_nz() +{ + clock_t now; + now = ddi_get_lbolt(); + return (now ? now : (clock_t)1); +} + +#ifdef USBGEM_DEBUG_LEVEL +#ifdef USBGEM_DEBUG_VLAN +#ifdef notdef +#include <netinet/in.h> +#endif +static void +usbgem_dump_packet(struct usbgem_dev *dp, char *title, mblk_t *mp, + boolean_t check_cksum) +{ + char msg[180]; + uint8_t buf[18+20+20]; + uint8_t *p; + size_t offset; + uint_t ethertype; + uint_t proto; + uint_t ipproto = 0; + uint_t iplen; + uint_t iphlen; + uint_t tcplen; + uint_t udplen; + uint_t cksum; + int rest; + int len; + char *bp; + mblk_t *tp; + extern uint_t ip_cksum(mblk_t *, int, uint32_t); + + msg[0] = 0; + bp = msg; + + rest = sizeof (buf); + offset = 0; + for (tp = mp; tp; tp = tp->b_cont) { + len = tp->b_wptr - tp->b_rptr; + len = min(rest, len); + bcopy(tp->b_rptr, &buf[offset], len); + rest -= len; + offset += len; + if (rest == 0) { + break; + } + } + + offset = 0; + p = &buf[offset]; + + /* ethernet address */ + sprintf(bp, + "ether: %02x:%02x:%02x:%02x:%02x:%02x" + " -> %02x:%02x:%02x:%02x:%02x:%02x", + p[6], p[7], p[8], p[9], p[10], p[11], + p[0], p[1], p[2], p[3], p[4], p[5]); + bp = &msg[strlen(msg)]; + + /* vlag tag and etherrtype */ + ethertype = GET_ETHERTYPE(p); + if (ethertype == VTAG_TPID) { + sprintf(bp, " vtag:0x%04x", GET_NET16(&p[14])); + bp = &msg[strlen(msg)]; + + offset += VTAG_SIZE; + p = &buf[offset]; + ethertype = GET_ETHERTYPE(p); + } + sprintf(bp, " type:%04x", ethertype); + bp = &msg[strlen(msg)]; + + /* ethernet packet length */ + sprintf(bp, " mblklen:%d", msgdsize(mp)); + bp = &msg[strlen(msg)]; + if (mp->b_cont) { + sprintf(bp, "("); + bp = &msg[strlen(msg)]; + for (tp = mp; tp; tp = tp->b_cont) { + if (tp == mp) { + sprintf(bp, "%d", tp->b_wptr - tp->b_rptr); + } else { + sprintf(bp, "+%d", tp->b_wptr - tp->b_rptr); + } + bp = &msg[strlen(msg)]; + } + sprintf(bp, ")"); + bp = &msg[strlen(msg)]; + } + + if (ethertype != ETHERTYPE_IP) { + goto x; + } + + /* ip address */ + offset += sizeof (struct ether_header); + p = &buf[offset]; + ipproto = p[9]; + iplen = GET_NET16(&p[2]); + sprintf(bp, ", ip: %d.%d.%d.%d -> %d.%d.%d.%d proto:%d iplen:%d", + p[12], p[13], p[14], p[15], + p[16], p[17], p[18], p[19], + ipproto, iplen); + bp = (void *)&msg[strlen(msg)]; + + iphlen = (p[0] & 0xf) * 4; + + /* cksum for psuedo header */ + cksum = *(uint16_t *)&p[12]; + cksum += *(uint16_t *)&p[14]; + cksum += *(uint16_t *)&p[16]; + cksum += *(uint16_t *)&p[18]; + cksum += BE_16(ipproto); + + /* tcp or udp protocol header */ + offset += iphlen; + p = &buf[offset]; + if (ipproto == IPPROTO_TCP) { + tcplen = iplen - iphlen; + sprintf(bp, ", tcp: len:%d cksum:%x", + tcplen, GET_NET16(&p[16])); + bp = (void *)&msg[strlen(msg)]; + + if (check_cksum) { + cksum += BE_16(tcplen); + cksum = (uint16_t)ip_cksum(mp, offset, cksum); + sprintf(bp, " (%s)", + (cksum == 0 || cksum == 0xffff) ? "ok" : "ng"); + bp = (void *)&msg[strlen(msg)]; + } + } else if (ipproto == IPPROTO_UDP) { + udplen = GET_NET16(&p[4]); + sprintf(bp, ", udp: len:%d cksum:%x", + udplen, GET_NET16(&p[6])); + bp = (void *)&msg[strlen(msg)]; + + if (GET_NET16(&p[6]) && check_cksum) { + cksum += *(uint16_t *)&p[4]; + cksum = (uint16_t)ip_cksum(mp, offset, cksum); + sprintf(bp, " (%s)", + (cksum == 0 || cksum == 0xffff) ? "ok" : "ng"); + bp = (void *)&msg[strlen(msg)]; + } + } +x: + cmn_err(CE_CONT, "!%s: %s: %s", dp->name, title, msg); +} +#endif /* USBGEM_DEBUG_VLAN */ +#endif /* USBGEM_DEBUG_LEVEL */ + +#ifdef GEM_GCC_RUNTIME +/* + * gcc3 runtime routines + */ +#pragma weak memcmp +int +memcmp(const void *s1, const void *s2, size_t n) +{ + int i; + int ret; + + ret = 0; + for (i = 0; i < n; i++) { + ret = (int)((uint8_t *)s1)[i] - (int)((uint8_t *)s2)[i]; + if (ret) { + return (ret); + } + } + return (0); +} + +#pragma weak memset +void * +memset(void *s, int c, size_t n) +{ + if ((c & 0xff) == 0) { + bzero(s, n); + } else { + while (n--) { + ((uint8_t *)s)[n] = c; + } + } + return (s); +} + +#pragma weak _memcpy = memcpy +#pragma weak memcpy +void * +memcpy(void *s1, const void *s2, size_t n) +{ + bcopy(s2, s1, n); + return (s1); +} +#endif /* GEM_GCC_RUNTIME */ +/* ============================================================== */ +/* + * hardware operations + */ +/* ============================================================== */ +static int +usbgem_hal_reset_chip(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_reset_chip)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_init_chip(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_init_chip)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_attach_chip(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_attach_chip)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_set_rx_filter(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_set_rx_filter)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_set_media(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_set_media)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_start_chip(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_start_chip)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_stop_chip(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_stop_chip)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + +static int +usbgem_hal_get_stats(struct usbgem_dev *dp) +{ + int err; + + sema_p(&dp->hal_op_lock); + err = (*dp->ugc.usbgc_get_stats)(dp); + sema_v(&dp->hal_op_lock); + return (err); +} + + +/* ============================================================== */ +/* + * USB pipe management + */ +/* ============================================================== */ +static boolean_t +usbgem_rx_start_unit(struct usbgem_dev *dp, usb_bulk_req_t *req) +{ + mblk_t *mp; + int err; + usb_flags_t flags; + + ASSERT(req); + + mp = allocb(dp->rx_buf_len, BPRI_MED); + if (mp == NULL) { + cmn_err(CE_WARN, "!%s: %s: failed to allocate mblk", + dp->name, __func__); + goto err; + } + + req->bulk_len = dp->rx_buf_len; + req->bulk_data = mp; + req->bulk_client_private = (usb_opaque_t)dp; + req->bulk_timeout = 0; + req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK; + req->bulk_cb = usbgem_bulkin_cb; + req->bulk_exc_cb = usbgem_bulkin_cb; + req->bulk_completion_reason = 0; + req->bulk_cb_flags = 0; + + flags = 0; + err = usb_pipe_bulk_xfer(dp->bulkin_pipe, req, flags); + + if (err != USB_SUCCESS) { + cmn_err(CE_WARN, "%s: failed to bulk_xfer for rx, err:%d", + dp->name, err); + + /* free req and mp */ + usb_free_bulk_req(req); + goto err; + } + return (B_TRUE); +err: + return (B_FALSE); +} + +/* ============================================================== */ +/* + * Rx/Tx buffer management + */ +/* ============================================================== */ +static int +usbgem_init_rx_buf(struct usbgem_dev *dp) +{ + int i; + usb_bulk_req_t *req; + + ASSERT(dp->mac_state == MAC_STATE_ONLINE); + + for (i = 0; i < dp->ugc.usbgc_rx_list_max; i++) { + req = usb_alloc_bulk_req(dp->dip, 0, USB_FLAGS_SLEEP); + if (req == NULL) { + cmn_err(CE_WARN, + "!%s: %s: failed to allocate bulkreq for rx", + dp->name, __func__); + return (USB_FAILURE); + } + if (!usbgem_rx_start_unit(dp, req)) { + return (USB_FAILURE); + } + mutex_enter(&dp->rxlock); + dp->rx_busy_cnt++; + mutex_exit(&dp->rxlock); + } + return (USB_SUCCESS); +} + +/* ============================================================== */ +/* + * memory resource management + */ +/* ============================================================== */ +static int +usbgem_free_memory(struct usbgem_dev *dp) +{ + usb_bulk_req_t *req; + + /* free all tx requst structure */ + while ((req = dp->tx_free_list) != NULL) { + dp->tx_free_list = + (usb_bulk_req_t *)req->bulk_client_private; + req->bulk_data = NULL; + usb_free_bulk_req(req); + } + return (USB_SUCCESS); +} + +static int +usbgem_alloc_memory(struct usbgem_dev *dp) +{ + int i; + usb_bulk_req_t *req; + + /* allocate tx requests */ + dp->tx_free_list = NULL; + for (i = 0; i < dp->ugc.usbgc_tx_list_max; i++) { + req = usb_alloc_bulk_req(dp->dip, 0, USB_FLAGS_SLEEP); + if (req == NULL) { + cmn_err(CE_WARN, + "%s:%s failed to allocate tx requests", + dp->name, __func__); + + /* free partially allocated tx requests */ + (void) usbgem_free_memory(dp); + return (USB_FAILURE); + } + + /* add the new one allocated into tx free list */ + req->bulk_client_private = (usb_opaque_t)dp->tx_free_list; + dp->tx_free_list = req; + } + + return (USB_SUCCESS); +} + +/* ========================================================== */ +/* + * Start transmission. + * Return zero on success, + */ +/* ========================================================== */ + +#ifdef TXTIMEOUT_TEST +static int usbgem_send_cnt = 0; +#endif + +/* + * usbgem_send is used only to send data packet into ethernet line. + */ +static mblk_t * +usbgem_send_common(struct usbgem_dev *dp, mblk_t *mp, uint32_t flags) +{ + int err; + mblk_t *new; + usb_bulk_req_t *req; + int mcast; + int bcast; + int len; + boolean_t intr; + usb_flags_t usb_flags = 0; +#ifdef USBGEM_DEBUG_LEVEL + usb_pipe_state_t p_state; +#endif + DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + intr = (flags & 1) != 0; + len = msgdsize(mp); + bcast = 0; + mcast = 0; + if (mp->b_rptr[0] & 1) { + if (bcmp(mp->b_rptr, &usbgem_bcastaddr, ETHERADDRL) == 0) { + bcast = 1; + } else { + mcast = 1; + } + } + new = (*dp->ugc.usbgc_tx_make_packet)(dp, mp); + if (new == NULL) { + /* + * no memory resource. we don't stop downstream, + * we just discard the packet. + */ + DPRINTF(0, (CE_CONT, "!%s: %s: no memory", + dp->name, __func__)); + freemsg(mp); + + mutex_enter(&dp->txlock); + dp->stats.noxmtbuf++; + dp->stats.errxmt++; + mutex_exit(&dp->txlock); + + return (NULL); + } + + ASSERT(new->b_cont == NULL); + + mutex_enter(&dp->txlock); + if (dp->tx_free_list == NULL) { + /* + * no tx free slot + */ + ASSERT(dp->tx_busy_cnt == dp->ugc.usbgc_tx_list_max); + mutex_exit(&dp->txlock); + + DPRINTF(4, (CE_CONT, "!%s: %s: no free slot", + dp->name, __func__)); + if (new && new != mp) { + /* free reallocated message */ + freemsg(new); + } + return (mp); + } + req = dp->tx_free_list; + dp->tx_free_list = (usb_bulk_req_t *)req->bulk_client_private; + dp->tx_busy_cnt++; + + if (dp->tx_free_list == NULL) { + intr = B_TRUE; + } + if (intr) { + dp->tx_intr_pended++; + } + DB_TCI(new) = intr; +#ifdef USBGEM_DEBUG_LEVEL + new->b_datap->db_cksum32 = dp->tx_seq_num; + dp->tx_seq_num++; +#endif + dp->stats.obytes += len; + dp->stats.opackets++; + if (bcast | mcast) { + dp->stats.obcast += bcast; + dp->stats.omcast += mcast; + } + mutex_exit(&dp->txlock); + + DPRINTF(2, (CE_CONT, "!%s: %s: sending", dp->name, __func__)); + + req->bulk_len = (long)new->b_wptr - (long)new->b_rptr; + req->bulk_data = new; + req->bulk_client_private = (usb_opaque_t)dp; + req->bulk_timeout = dp->bulkout_timeout; /* in second */ + req->bulk_attributes = 0; + req->bulk_cb = usbgem_bulkout_cb; + req->bulk_exc_cb = usbgem_bulkout_cb; + req->bulk_completion_reason = 0; + req->bulk_cb_flags = 0; + + if (intr) { + usb_flags = USB_FLAGS_SLEEP; + } + if ((err = usb_pipe_bulk_xfer(dp->bulkout_pipe, req, usb_flags)) + != USB_SUCCESS) { + + /* failed to transfer the packet, discard it. */ + freemsg(new); + req->bulk_data = NULL; + + /* recycle the request block */ + mutex_enter(&dp->txlock); + dp->tx_busy_cnt--; + req->bulk_client_private = (usb_opaque_t)dp->tx_free_list; + dp->tx_free_list = req; + mutex_exit(&dp->txlock); + + cmn_err(CE_NOTE, + "%s: %s: usb_pipe_bulk_xfer: failed: err:%d", + dp->name, __func__, err); + + /* we use another flag to indicate error state. */ + if (dp->fatal_error == (clock_t)0) { + dp->fatal_error = usbgem_timestamp_nz(); + } + } else { + /* record the start time */ + dp->tx_start_time = ddi_get_lbolt(); + } + + if (err == USB_SUCCESS && (usb_flags & USB_FLAGS_SLEEP)) { + usbgem_bulkout_cb(dp->bulkout_pipe, req); + } + + if (new != mp) { + freemsg(mp); + } + return (NULL); +} + +int +usbgem_restart_nic(struct usbgem_dev *dp) +{ + int ret; + int flags = 0; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + ASSERT(dp->mac_state != MAC_STATE_DISCONNECTED); + + /* + * ensure to stop the nic + */ + if (dp->mac_state == MAC_STATE_ONLINE) { + (void) usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL); + } + + /* now the nic become quiescent, reset the chip */ + if (usbgem_hal_reset_chip(dp) != USB_SUCCESS) { + cmn_err(CE_WARN, "%s: %s: failed to reset chip", + dp->name, __func__); + goto err; + } + + /* + * restore the nic state step by step + */ + if (dp->nic_state < NIC_STATE_INITIALIZED) { + goto done; + } + + if (usbgem_mac_init(dp) != USB_SUCCESS) { + cmn_err(CE_WARN, "%s: %s: failed to initialize chip", + dp->name, __func__); + goto err; + } + + /* setup mac address and enable rx filter */ + sema_p(&dp->rxfilter_lock); + dp->rxmode |= RXMODE_ENABLE; + ret = usbgem_hal_set_rx_filter(dp); + sema_v(&dp->rxfilter_lock); + if (ret != USB_SUCCESS) { + goto err; + } + + /* + * update the link state asynchronously + */ + cv_signal(&dp->link_watcher_wait_cv); + + /* + * XXX - a panic happened because of linkdown. + * We must check mii_state here, because the link can be down just + * before the restart event happen. If the link is down now, + * gem_mac_start() will be called from gem_mii_link_check() when + * the link become up later. + */ + if (dp->mii_state == MII_STATE_LINKUP) { + if (usbgem_hal_set_media(dp) != USB_SUCCESS) { + goto err; + } + if (dp->nic_state < NIC_STATE_ONLINE) { + goto done; + } + + (void) usbgem_mac_start(dp); + + } +done: + return (USB_SUCCESS); +err: +#ifdef GEM_CONFIG_FMA + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); +#endif + return (USB_FAILURE); +} + +static void +usbgem_tx_timeout(struct usbgem_dev *dp) +{ + int ret; + uint_t rwlock; + clock_t now; + + for (; ; ) { + mutex_enter(&dp->tx_watcher_lock); + ret = cv_timedwait(&dp->tx_watcher_cv, &dp->tx_watcher_lock, + dp->tx_watcher_interval + ddi_get_lbolt()); + mutex_exit(&dp->tx_watcher_lock); + + if (dp->tx_watcher_stop) { + break; + } + + now = ddi_get_lbolt(); + + rwlock = RW_READER; +again: + rw_enter(&dp->dev_state_lock, rwlock); + + if ((dp->mac_state != MAC_STATE_DISCONNECTED && + dp->fatal_error && + now - dp->fatal_error >= dp->ugc.usbgc_tx_timeout) || + (dp->mac_state == MAC_STATE_ONLINE && + dp->mii_state == MII_STATE_LINKUP && + dp->tx_busy_cnt != 0 && + now - dp->tx_start_time >= dp->ugc.usbgc_tx_timeout)) { + if (rwlock == RW_READER) { + /* + * Upgrade dev_state_lock from shared mode + * to exclusive mode to restart nic + */ + rwlock = RW_WRITER; + rw_exit(&dp->dev_state_lock); + goto again; + } + cmn_err(CE_WARN, "%s: %s: restarting the nic:" + " fatal_error:%ld nic_state:%d" + " mac_state:%d starttime:%ld", + dp->name, __func__, + dp->fatal_error ? now - dp->fatal_error: 0, + dp->nic_state, dp->mac_state, + dp->tx_busy_cnt ? now - dp->tx_start_time : 0); + + (void) usbgem_restart_nic(dp); + } + + rw_exit(&dp->dev_state_lock); + } +} + +static int +usbgem_tx_watcher_start(struct usbgem_dev *dp) +{ + int err; + kthread_t *wdth; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* make a first call of uwgem_lw_link_check() */ + dp->tx_watcher_stop = 0; + dp->tx_watcher_interval = drv_usectohz(1000*1000); + + wdth = thread_create(NULL, 0, usbgem_tx_timeout, dp, 0, &p0, + TS_RUN, minclsyspri); + if (wdth == NULL) { + cmn_err(CE_WARN, + "!%s: %s: failed to create a tx_watcher thread", + dp->name, __func__); + return (USB_FAILURE); + } + dp->tx_watcher_did = wdth->t_did; + + return (USB_SUCCESS); +} + +static void +usbgem_tx_watcher_stop(struct usbgem_dev *dp) +{ + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + if (dp->tx_watcher_did) { + /* Ensure timer routine stopped */ + dp->tx_watcher_stop = 1; + cv_signal(&dp->tx_watcher_cv); + thread_join(dp->tx_watcher_did); + dp->tx_watcher_did = NULL; + } +} + +/* ================================================================== */ +/* + * Callback handlers + */ +/* ================================================================== */ +static void +usbgem_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) +{ + mblk_t *newmp; + mblk_t *mp; + mblk_t *tp; + int len = 0; + int pkts = 0; + int bcast = 0; + int mcast = 0; + boolean_t busy; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)req->bulk_client_private; + mp = req->bulk_data; + req->bulk_data = NULL; + + DPRINTF(2, (CE_CONT, "!%s: %s: mp:%p, cr:%s(%d)", + dp->name, __func__, mp, + usb_str_cr(req->bulk_completion_reason), + req->bulk_completion_reason)); + + /* + * we cannot acquire dev_state_lock because the routine + * must be executed during usbgem_mac_stop() to avoid + * dead lock. + * we use a simle membar operation to get the state correctly. + */ + membar_consumer(); + + if (req->bulk_completion_reason == USB_CR_OK && + dp->nic_state == NIC_STATE_ONLINE) { + newmp = (*dp->ugc.usbgc_rx_make_packet)(dp, mp); + + if (newmp != mp) { + /* the message has been reallocated, free old one */ + freemsg(mp); + } + + /* the message may includes one or more ethernet packets */ + for (tp = newmp; tp; tp = tp->b_next) { + len += tp->b_wptr - tp->b_rptr; + pkts++; + if (tp->b_rptr[0] & 1) { + if (bcmp(tp->b_rptr, &usbgem_bcastaddr, + ETHERADDRL) == 0) { + bcast++; + } else { + mcast++; + } + } + } + + /* send up if it is a valid packet */ +#ifdef USBGEM_CONFIG_GLDv3 + mac_rx(dp->mh, NULL, newmp); +#else + while (newmp) { + tp = newmp; + newmp = newmp->b_next; + tp->b_next = NULL; + gld_recv(dp->macinfo, tp); + } +#endif + } else { + freemsg(mp); + len = 0; + } + + mutex_enter(&dp->rxlock); + /* update rx_active */ + if (dp->rx_active) { + dp->rx_active = dp->mac_state == MAC_STATE_ONLINE; + } + + dp->stats.rbytes += len; + dp->stats.rpackets += pkts; + if (bcast | mcast) { + dp->stats.rbcast += bcast; + dp->stats.rmcast += mcast; + } + mutex_exit(&dp->rxlock); + + if (dp->rx_active) { + /* prepare to receive the next packets */ + if (usbgem_rx_start_unit(dp, req)) { + /* we successed */ + goto done; + } + cmn_err(CE_WARN, + "!%s: %s: failed to fill next rx packet", + dp->name, __func__); + /* + * we use another flag to indicate error state. + * if we acquire dev_state_lock for RW_WRITER here, + * usbgem_mac_stop() may hang. + */ + if (dp->fatal_error == (clock_t)0) { + dp->fatal_error = usbgem_timestamp_nz(); + } + } else { + /* no need to prepare the next packets */ + usb_free_bulk_req(req); + } + + mutex_enter(&dp->rxlock); + dp->rx_active = B_FALSE; + dp->rx_busy_cnt--; + if (dp->rx_busy_cnt == 0) { + /* wake up someone waits for me */ + cv_broadcast(&dp->rx_drain_cv); + } + mutex_exit(&dp->rxlock); +done: + ; +} + +static void +usbgem_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) +{ + boolean_t intr; + boolean_t tx_sched; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)req->bulk_client_private; + tx_sched = B_FALSE; + + DPRINTF(2, (CE_CONT, + "!%s: %s: cr:%s(%d) cb_flags:0x%x head:%d tail:%d", + dp->name, __func__, + usb_str_cr(req->bulk_completion_reason), + req->bulk_completion_reason, + req->bulk_cb_flags, + dp->tx_busy_cnt)); + + /* we have finished to transfer the packet into tx fifo */ + intr = DB_TCI(req->bulk_data); + freemsg(req->bulk_data); + + if (req->bulk_completion_reason != USB_CR_OK && + dp->fatal_error == (clock_t)0) { + dp->fatal_error = usbgem_timestamp_nz(); + } + + mutex_enter(&dp->txlock); + + if (intr) { + ASSERT(dp->tx_intr_pended > 0); + /* find the last interrupt we have scheduled */ + if (--(dp->tx_intr_pended) == 0) { + tx_sched = B_TRUE; + } + } + + ASSERT(dp->tx_busy_cnt > 0); + req->bulk_client_private = (usb_opaque_t)dp->tx_free_list; + dp->tx_free_list = req; + dp->tx_busy_cnt--; + +#ifdef CONFIG_TX_LIMITER + if (tx_sched) { + dp->tx_max_packets = + min(dp->tx_max_packets + 1, dp->ugc.usbgc_tx_list_max); + } +#endif + if (dp->mac_state != MAC_STATE_ONLINE && dp->tx_busy_cnt == 0) { + cv_broadcast(&dp->tx_drain_cv); + } + + mutex_exit(&dp->txlock); + + if (tx_sched) { +#ifdef USBGEM_CONFIG_GLDv3 + mac_tx_update(dp->mh); +#else + gld_sched(dp->macinfo); +#endif + } +} + +static void +usbgem_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req) +{ + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)req->intr_client_private; + dp->stats.intr++; + + if (req->intr_completion_reason == USB_CR_OK) { + (*dp->ugc.usbgc_interrupt)(dp, req->intr_data); + } + + /* free the request and data */ + usb_free_intr_req(req); +} + +/* ======================================================================== */ +/* + * MII support routines + */ +/* ======================================================================== */ +static void +usbgem_choose_forcedmode(struct usbgem_dev *dp) +{ + /* choose media mode */ + if (dp->anadv_1000fdx || dp->anadv_1000hdx) { + dp->speed = USBGEM_SPD_1000; + dp->full_duplex = dp->anadv_1000fdx; + } else if (dp->anadv_100fdx || dp->anadv_100t4) { + dp->speed = USBGEM_SPD_100; + dp->full_duplex = B_TRUE; + } else if (dp->anadv_100hdx) { + dp->speed = USBGEM_SPD_100; + dp->full_duplex = B_FALSE; + } else { + dp->speed = USBGEM_SPD_10; + dp->full_duplex = dp->anadv_10fdx; + } +} + +static uint16_t +usbgem_mii_read(struct usbgem_dev *dp, uint_t reg, int *errp) +{ + uint16_t val; + + sema_p(&dp->hal_op_lock); + val = (*dp->ugc.usbgc_mii_read)(dp, reg, errp); + sema_v(&dp->hal_op_lock); + + return (val); +} + +static void +usbgem_mii_write(struct usbgem_dev *dp, uint_t reg, uint16_t val, int *errp) +{ + sema_p(&dp->hal_op_lock); + (*dp->ugc.usbgc_mii_write)(dp, reg, val, errp); + sema_v(&dp->hal_op_lock); +} + +static int +usbgem_mii_probe(struct usbgem_dev *dp) +{ + int err; + + err = (*dp->ugc.usbgc_mii_probe)(dp); + return (err); +} + +static int +usbgem_mii_init(struct usbgem_dev *dp) +{ + int err; + + err = (*dp->ugc.usbgc_mii_init)(dp); + return (err); +} + +#define fc_cap_decode(x) \ + ((((x) & MII_ABILITY_PAUSE) != 0 ? 1 : 0) | \ + (((x) & MII_ABILITY_ASM_DIR) != 0 ? 2 : 0)) + +int +usbgem_mii_config_default(struct usbgem_dev *dp, int *errp) +{ + uint16_t mii_stat; + uint16_t val; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* + * Configure bits in advertisement register + */ + mii_stat = dp->mii_status; + + DPRINTF(1, (CE_CONT, "!%s: %s: MII_STATUS reg:%b", + dp->name, __func__, mii_stat, MII_STATUS_BITS)); + + if ((mii_stat & MII_STATUS_ABILITY_TECH) == 0) { + /* it's funny */ + cmn_err(CE_WARN, "!%s: wrong ability bits: mii_status:%b", + dp->name, mii_stat, MII_STATUS_BITS); + return (USB_FAILURE); + } + + /* Do not change the rest of ability bits in advert reg */ + val = usbgem_mii_read(dp, MII_AN_ADVERT, errp) & ~MII_ABILITY_ALL; + if (*errp != USB_SUCCESS) { + goto usberr; + } + + DPRINTF(0, (CE_CONT, + "!%s: %s: 100T4:%d 100F:%d 100H:%d 10F:%d 10H:%d", + dp->name, __func__, + dp->anadv_100t4, dp->anadv_100fdx, dp->anadv_100hdx, + dp->anadv_10fdx, dp->anadv_10hdx)); + + /* set technology bits */ + if (dp->anadv_100t4) { + val |= MII_ABILITY_100BASE_T4; + } + if (dp->anadv_100fdx) { + val |= MII_ABILITY_100BASE_TX_FD; + } + if (dp->anadv_100hdx) { + val |= MII_ABILITY_100BASE_TX; + } + if (dp->anadv_10fdx) { + val |= MII_ABILITY_10BASE_T_FD; + } + if (dp->anadv_10hdx) { + val |= MII_ABILITY_10BASE_T; + } + + /* set flow control capabilities */ + if (dp->anadv_pause) { + val |= MII_ABILITY_PAUSE; + } + if (dp->anadv_asmpause) { + val |= MII_ABILITY_ASM_DIR; + } + + DPRINTF(0, (CE_CONT, + "!%s: %s: setting MII_AN_ADVERT reg:%b, pause:%d, asmpause:%d", + dp->name, __func__, val, MII_ABILITY_BITS, + dp->anadv_pause, dp->anadv_asmpause)); + + usbgem_mii_write(dp, MII_AN_ADVERT, val, errp); + if (*errp != USB_SUCCESS) { + goto usberr; + } + + if (dp->mii_status & MII_STATUS_XSTATUS) { + /* + * 1000Base-T GMII support + */ + if (!dp->anadv_autoneg) { + /* enable manual configuration */ + val = MII_1000TC_CFG_EN; + if (dp->anadv_1000t_ms == 2) { + val |= MII_1000TC_CFG_VAL; + } + } else { + val = 0; + if (dp->anadv_1000fdx) { + val |= MII_1000TC_ADV_FULL; + } + if (dp->anadv_1000hdx) { + val |= MII_1000TC_ADV_HALF; + } + switch (dp->anadv_1000t_ms) { + case 1: + /* slave */ + val |= MII_1000TC_CFG_EN; + break; + + case 2: + /* master */ + val |= MII_1000TC_CFG_EN | MII_1000TC_CFG_VAL; + break; + + default: + /* auto: do nothing */ + break; + } + } + DPRINTF(0, (CE_CONT, + "!%s: %s: setting MII_1000TC reg:%b", + dp->name, __func__, val, MII_1000TC_BITS)); + + usbgem_mii_write(dp, MII_1000TC, val, errp); + if (*errp != USB_SUCCESS) { + goto usberr; + } + } + return (USB_SUCCESS); + +usberr: + return (*errp); +} + +static char *usbgem_fc_type[] = { + "without", + "with symmetric", + "with tx", + "with rx", +}; + +#ifdef USBGEM_CONFIG_GLDv3 +#define USBGEM_LINKUP(dp) mac_link_update((dp)->mh, LINK_STATE_UP) +#define USBGEM_LINKDOWN(dp) mac_link_update((dp)->mh, LINK_STATE_DOWN) +#else +#define USBGEM_LINKUP(dp) \ + if (gld_linkstate) { \ + gld_linkstate((dp)->macinfo, GLD_LINKSTATE_UP); \ + } +#define USBGEM_LINKDOWN(dp) \ + if (gld_linkstate) { \ + gld_linkstate((dp)->macinfo, GLD_LINKSTATE_DOWN); \ + } +#endif + +static uint8_t usbgem_fc_result[4 /* my cap */][4 /* lp cap */] = { +/* none symm tx rx/symm */ +/* none */ + {FLOW_CONTROL_NONE, + FLOW_CONTROL_NONE, + FLOW_CONTROL_NONE, + FLOW_CONTROL_NONE}, +/* sym */ + {FLOW_CONTROL_NONE, + FLOW_CONTROL_SYMMETRIC, + FLOW_CONTROL_NONE, + FLOW_CONTROL_SYMMETRIC}, +/* tx */ + {FLOW_CONTROL_NONE, + FLOW_CONTROL_NONE, + FLOW_CONTROL_NONE, + FLOW_CONTROL_TX_PAUSE}, +/* rx/symm */ + {FLOW_CONTROL_NONE, + FLOW_CONTROL_SYMMETRIC, + FLOW_CONTROL_RX_PAUSE, + FLOW_CONTROL_SYMMETRIC}, +}; + +static boolean_t +usbgem_mii_link_check(struct usbgem_dev *dp, int *oldstatep, int *newstatep) +{ + boolean_t tx_sched = B_FALSE; + uint16_t status; + uint16_t advert; + uint16_t lpable; + uint16_t exp; + uint16_t ctl1000; + uint16_t stat1000; + uint16_t val; + clock_t now; + clock_t diff; + int linkdown_action; + boolean_t fix_phy = B_FALSE; + int err; + uint_t rwlock; + + DPRINTF(4, (CE_CONT, "!%s: %s: time:%d state:%d", + dp->name, __func__, ddi_get_lbolt(), dp->mii_state)); + + if (dp->mii_state != MII_STATE_LINKUP) { + rwlock = RW_WRITER; + } else { + rwlock = RW_READER; + } +again: + rw_enter(&dp->dev_state_lock, rwlock); + + /* save old mii state */ + *oldstatep = dp->mii_state; + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + /* stop periodic execution of the link watcher */ + dp->mii_interval = 0; + tx_sched = B_FALSE; + goto next; + } + + now = ddi_get_lbolt(); + diff = now - dp->mii_last_check; + dp->mii_last_check = now; + + /* + * For NWAM, don't show linkdown state right + * when the device is attached. + */ + if (dp->linkup_delay > 0) { + if (dp->linkup_delay > diff) { + dp->linkup_delay -= diff; + } else { + /* link up timeout */ + dp->linkup_delay = -1; + } + } + +next_nowait: + switch (dp->mii_state) { + case MII_STATE_UNKNOWN: + goto reset_phy; + + case MII_STATE_RESETTING: + dp->mii_timer -= diff; + if (dp->mii_timer > 0) { + /* don't read phy registers in resetting */ + dp->mii_interval = WATCH_INTERVAL_FAST; + goto next; + } + + val = usbgem_mii_read(dp, MII_CONTROL, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + if (val & MII_CONTROL_RESET) { + cmn_err(CE_NOTE, + "!%s: time:%ld resetting phy not complete." + " mii_control:0x%b", + dp->name, ddi_get_lbolt(), + val, MII_CONTROL_BITS); + } + + /* ensure neither isolated nor pwrdown nor auto-nego mode */ + usbgem_mii_write(dp, MII_CONTROL, 0, &err); + if (err != USB_SUCCESS) { + goto usberr; + } +#if USBGEM_DEBUG_LEVEL > 10 + val = usbgem_mii_read(dp, MII_CONTROL, &err); + cmn_err(CE_CONT, "!%s: readback control %b", + dp->name, val, MII_CONTROL_BITS); +#endif + /* As resetting PHY has completed, configure PHY registers */ + if ((*dp->ugc.usbgc_mii_config)(dp, &err) != USB_SUCCESS) { + /* we failed to configure PHY */ + goto usberr; + } + + /* prepare for forced mode */ + usbgem_choose_forcedmode(dp); + + dp->mii_lpable = 0; + dp->mii_advert = 0; + dp->mii_exp = 0; + dp->mii_ctl1000 = 0; + dp->mii_stat1000 = 0; + + dp->flow_control = FLOW_CONTROL_NONE; + + if (!dp->anadv_autoneg) { + /* skip auto-negotiation phase */ + dp->mii_state = MII_STATE_MEDIA_SETUP; + dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout; + goto next_nowait; + } + + /* issue an auto-negotiation command */ + goto autonego; + + case MII_STATE_AUTONEGOTIATING: + /* + * Autonegotiation in progress + */ + dp->mii_timer -= diff; + if (dp->mii_timer - + (dp->ugc.usbgc_mii_an_timeout - dp->ugc.usbgc_mii_an_wait) + > 0) { + /* wait for minimum time (2.3 - 2.5 sec) */ + dp->mii_interval = WATCH_INTERVAL_FAST; + goto next; + } + + /* read PHY status */ + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + DPRINTF(4, (CE_CONT, + "!%s: %s: called: mii_state:%d MII_STATUS reg:%b", + dp->name, __func__, dp->mii_state, + status, MII_STATUS_BITS)); + + if (status & MII_STATUS_REMFAULT) { + /* + * The link parnert told me something wrong happend. + * What do we do ? + */ + cmn_err(CE_CONT, + "!%s: auto-negotiation failed: remote fault", + dp->name); + goto autonego; + } + + if ((status & MII_STATUS_ANDONE) == 0) { + if (dp->mii_timer <= 0) { + /* + * Auto-negotiation has been timed out, + * Reset PHY and try again. + */ + if (!dp->mii_supress_msg) { + cmn_err(CE_WARN, + "!%s: auto-negotiation failed:" + " timeout", + dp->name); + dp->mii_supress_msg = B_TRUE; + } + goto autonego; + } + /* + * Auto-negotiation is in progress. Wait for a while. + */ + dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval; + goto next; + } + + /* + * Auto-negotiation has been completed. Let's go to AN_DONE. + */ + dp->mii_state = MII_STATE_AN_DONE; + dp->mii_supress_msg = B_FALSE; + DPRINTF(0, (CE_CONT, + "!%s: auto-negotiation completed, MII_STATUS:%b", + dp->name, status, MII_STATUS_BITS)); + + if (dp->ugc.usbgc_mii_an_delay > 0) { + dp->mii_timer = dp->ugc.usbgc_mii_an_delay; + dp->mii_interval = drv_usectohz(20*1000); + goto next; + } + + dp->mii_timer = 0; + diff = 0; + goto next_nowait; + + case MII_STATE_AN_DONE: + /* + * Auto-negotiation has done. Now we can set up media. + */ + dp->mii_timer -= diff; + if (dp->mii_timer > 0) { + /* wait for a while */ + dp->mii_interval = WATCH_INTERVAL_FAST; + goto next; + } + + /* + * Setup speed and duplex mode according with + * the result of auto negotiation. + */ + + /* + * Read registers required to determin current + * duplex mode and media speed. + */ + if (dp->ugc.usbgc_mii_an_delay > 0) { + /* the 'status' variable is not initialized yet */ + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + } + advert = usbgem_mii_read(dp, MII_AN_ADVERT, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + lpable = usbgem_mii_read(dp, MII_AN_LPABLE, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + exp = usbgem_mii_read(dp, MII_AN_EXPANSION, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + if (exp == 0xffff) { + /* some phys don't have exp register */ + exp = 0; + } + + ctl1000 = 0; + stat1000 = 0; + if (dp->mii_status & MII_STATUS_XSTATUS) { + ctl1000 = usbgem_mii_read(dp, MII_1000TC, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + stat1000 = usbgem_mii_read(dp, MII_1000TS, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + } + dp->mii_lpable = lpable; + dp->mii_advert = advert; + dp->mii_exp = exp; + dp->mii_ctl1000 = ctl1000; + dp->mii_stat1000 = stat1000; + + cmn_err(CE_CONT, + "!%s: auto-negotiation done: " + "status:%b, advert:%b, lpable:%b, exp:%b", + dp->name, + status, MII_STATUS_BITS, + advert, MII_ABILITY_BITS, + lpable, MII_ABILITY_BITS, + exp, MII_AN_EXP_BITS); + + DPRINTF(0, (CE_CONT, "!%s: MII_STATUS:%b", + dp->name, status, MII_STATUS_BITS)); + + if (dp->mii_status & MII_STATUS_XSTATUS) { + cmn_err(CE_CONT, + "! MII_1000TC reg:%b, MII_1000TS reg:%b", + ctl1000, MII_1000TC_BITS, + stat1000, MII_1000TS_BITS); + } + + if (usbgem_population(lpable) <= 1 && + (exp & MII_AN_EXP_LPCANAN) == 0) { + if ((advert & MII_ABILITY_TECH) != lpable) { + cmn_err(CE_WARN, + "!%s: but the link partner doesn't seem" + " to have auto-negotiation capability." + " please check the link configuration.", + dp->name); + } + /* + * it should be a result of pararell detection, + * which cannot detect duplex mode. + */ + if ((advert & lpable) == 0 && + lpable & MII_ABILITY_10BASE_T) { + /* no common technology, try 10M half mode */ + lpable |= advert & MII_ABILITY_10BASE_T; + fix_phy = B_TRUE; + } + } else if (lpable == 0) { + cmn_err(CE_WARN, "!%s: wrong lpable.", dp->name); + goto reset_phy; + } + /* + * configure current link mode according to AN priority. + */ + val = advert & lpable; + if ((ctl1000 & MII_1000TC_ADV_FULL) && + (stat1000 & MII_1000TS_LP_FULL)) { + /* 1000BaseT & full duplex */ + dp->speed = USBGEM_SPD_1000; + dp->full_duplex = B_TRUE; + } else if ((ctl1000 & MII_1000TC_ADV_HALF) && + (stat1000 & MII_1000TS_LP_HALF)) { + /* 1000BaseT & half duplex */ + dp->speed = USBGEM_SPD_1000; + dp->full_duplex = B_FALSE; + } else if ((val & MII_ABILITY_100BASE_TX_FD)) { + /* 100BaseTx & fullduplex */ + dp->speed = USBGEM_SPD_100; + dp->full_duplex = B_TRUE; + } else if ((val & MII_ABILITY_100BASE_T4)) { + /* 100BaseTx & fullduplex */ + dp->speed = USBGEM_SPD_100; + dp->full_duplex = B_TRUE; + } else if ((val & MII_ABILITY_100BASE_TX)) { + /* 100BaseTx & half duplex */ + dp->speed = USBGEM_SPD_100; + dp->full_duplex = B_FALSE; + } else if ((val & MII_ABILITY_10BASE_T_FD)) { + /* 10BaseT & full duplex */ + dp->speed = USBGEM_SPD_10; + dp->full_duplex = B_TRUE; + } else if ((val & MII_ABILITY_10BASE_T)) { + /* 10BaseT & half duplex */ + dp->speed = USBGEM_SPD_10; + dp->full_duplex = B_FALSE; + } else { + /* + * the link partner doesn't seem to have + * auto-negotiation capability and our PHY + * could not report current mode correctly. + * We guess current mode by mii_control register. + */ + val = usbgem_mii_read(dp, MII_CONTROL, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + + /* select 100m half or 10m half */ + dp->speed = (val & MII_CONTROL_100MB) ? + USBGEM_SPD_100 : USBGEM_SPD_10; + dp->full_duplex = B_FALSE; + fix_phy = B_TRUE; + + cmn_err(CE_NOTE, + "!%s: auto-negotiation done but " + "common ability not found.\n" + "PHY state: control:%b advert:%b lpable:%b\n" + "guessing %d Mbps %s duplex mode", + dp->name, + val, MII_CONTROL_BITS, + advert, MII_ABILITY_BITS, + lpable, MII_ABILITY_BITS, + usbgem_speed_value[dp->speed], + dp->full_duplex ? "full" : "half"); + } + + if (dp->full_duplex) { + dp->flow_control = + usbgem_fc_result[fc_cap_decode(advert)] + [fc_cap_decode(lpable)]; + } else { + dp->flow_control = FLOW_CONTROL_NONE; + } + dp->mii_state = MII_STATE_MEDIA_SETUP; + dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout; + goto next_nowait; + + case MII_STATE_MEDIA_SETUP: + DPRINTF(2, (CE_CONT, "!%s: setup midia mode", dp->name)); + + /* assume the link state is down */ + dp->mii_state = MII_STATE_LINKDOWN; + dp->mii_supress_msg = B_FALSE; + + /* use short interval */ + dp->mii_interval = WATCH_INTERVAL_FAST; + + if ((!dp->anadv_autoneg) || + dp->ugc.usbgc_mii_an_oneshot || fix_phy) { + + /* + * write the result of auto negotiation back. + */ + val = usbgem_mii_read(dp, MII_CONTROL, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + val &= ~(MII_CONTROL_SPEED | MII_CONTROL_FDUPLEX | + MII_CONTROL_ANE | MII_CONTROL_RSAN); + + if (dp->full_duplex) { + val |= MII_CONTROL_FDUPLEX; + } + + switch (dp->speed) { + case USBGEM_SPD_1000: + val |= MII_CONTROL_1000MB; + break; + + case USBGEM_SPD_100: + val |= MII_CONTROL_100MB; + break; + + default: + cmn_err(CE_WARN, "%s: unknown speed:%d", + dp->name, dp->speed); + /* FALLTHROUGH */ + + case USBGEM_SPD_10: + /* for USBGEM_SPD_10, do nothing */ + break; + } + + if (dp->mii_status & MII_STATUS_XSTATUS) { + usbgem_mii_write(dp, + MII_1000TC, MII_1000TC_CFG_EN, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + } + usbgem_mii_write(dp, MII_CONTROL, val, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + } + /* + * XXX -- nic state should be one of + * NIC_STATE_DISCONNECTED + * NIC_STATE_STOPPED + * NIC_STATE_INITIALIZED + * NIC_STATE_ONLINE + */ + if (dp->nic_state >= NIC_STATE_INITIALIZED) { + /* notify the result of autonegotiation to mac */ + if (usbgem_hal_set_media(dp) != USB_SUCCESS) { + goto usberr; + } + } + goto next_nowait; + + case MII_STATE_LINKDOWN: + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + if (status & MII_STATUS_LINKUP) { + /* + * Link is going up + */ + dp->mii_state = MII_STATE_LINKUP; + dp->mii_supress_msg = B_FALSE; + + DPRINTF(0, (CE_CONT, + "!%s: link up detected: status:%b", + dp->name, status, MII_STATUS_BITS)); + + /* + * MII_CONTROL_100MB and MII_CONTROL_FDUPLEX are + * ignored when MII_CONTROL_ANE is set. + */ + cmn_err(CE_CONT, + "!%s: Link up: %d Mbps %s duplex %s flow control", + dp->name, + usbgem_speed_value[dp->speed], + dp->full_duplex ? "full" : "half", + usbgem_fc_type[dp->flow_control]); + + dp->mii_interval = + dp->ugc.usbgc_mii_link_watch_interval; + + if (dp->ugc.usbgc_mii_hw_link_detection && + dp->nic_state == NIC_STATE_ONLINE) { + dp->mii_interval = 0; + } + + if (dp->nic_state == NIC_STATE_ONLINE) { + if (dp->mac_state == MAC_STATE_INITIALIZED) { + (void) usbgem_mac_start(dp); + } + tx_sched = B_TRUE; + } + + goto next; + } + + dp->mii_supress_msg = B_TRUE; + if (dp->anadv_autoneg) { + dp->mii_timer -= diff; + if (dp->mii_timer <= 0) { + /* + * the link down timer expired. + * need to restart auto-negotiation. + */ + linkdown_action = + dp->ugc.usbgc_mii_linkdown_timeout_action; + goto restart_autonego; + } + } + /* don't change mii_state */ + goto next; + + case MII_STATE_LINKUP: + if (rwlock == RW_READER) { + /* first pass, read mii status */ + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + } + if ((status & MII_STATUS_LINKUP) == 0) { + /* + * Link is going down + */ + cmn_err(CE_NOTE, + "!%s: link down detected: status:%b", + dp->name, status, MII_STATUS_BITS); + /* + * Acquire exclusive lock to change mii_state + */ + if (rwlock == RW_READER) { + rwlock = RW_WRITER; + rw_exit(&dp->dev_state_lock); + goto again; + } + + dp->mii_state = MII_STATE_LINKDOWN; + dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout; + + /* + * As we may change the state of the device, + * let us acquire exclusive lock for the state. + */ + if (dp->nic_state == NIC_STATE_ONLINE && + dp->mac_state == MAC_STATE_ONLINE && + dp->ugc.usbgc_mii_stop_mac_on_linkdown) { + (void) usbgem_restart_nic(dp); + /* drain tx */ + tx_sched = B_TRUE; + } + + if (dp->anadv_autoneg) { + /* need to restart auto-negotiation */ + linkdown_action = + dp->ugc.usbgc_mii_linkdown_action; + goto restart_autonego; + } + /* + * don't use hw link down detection until the link + * status become stable for a while. + */ + dp->mii_interval = + dp->ugc.usbgc_mii_link_watch_interval; + + goto next; + } + + /* + * still link up, no need to change mii_state + */ + if (dp->ugc.usbgc_mii_hw_link_detection && + dp->nic_state == NIC_STATE_ONLINE) { + /* + * no need to check link status periodicly + * if nic can generate interrupts when link go down. + */ + dp->mii_interval = 0; + } + goto next; + } + /* NOTREACHED */ + cmn_err(CE_PANIC, "!%s: %s: not reached", dp->name, __func__); + + /* + * Actions for new state. + */ +restart_autonego: + switch (linkdown_action) { + case MII_ACTION_RESET: + if (!dp->mii_supress_msg) { + cmn_err(CE_CONT, "!%s: resetting PHY", dp->name); + } + dp->mii_supress_msg = B_TRUE; + goto reset_phy; + + case MII_ACTION_NONE: + dp->mii_supress_msg = B_TRUE; + if (dp->ugc.usbgc_mii_an_oneshot) { + goto autonego; + } + /* PHY will restart autonego automatically */ + dp->mii_state = MII_STATE_AUTONEGOTIATING; + dp->mii_timer = dp->ugc.usbgc_mii_an_timeout; + dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval; + goto next; + + case MII_ACTION_RSA: + if (!dp->mii_supress_msg) { + cmn_err(CE_CONT, "!%s: restarting auto-negotiation", + dp->name); + } + dp->mii_supress_msg = B_TRUE; + goto autonego; + + default: + cmn_err(CE_PANIC, "!%s: unknowm linkdown action: %d", + dp->name, dp->ugc.usbgc_mii_linkdown_action); + dp->mii_supress_msg = B_TRUE; + } + /* NOTREACHED */ + +reset_phy: + if (!dp->mii_supress_msg) { + cmn_err(CE_CONT, "!%s: resetting PHY", dp->name); + } + dp->mii_state = MII_STATE_RESETTING; + dp->mii_timer = dp->ugc.usbgc_mii_reset_timeout; + if (!dp->ugc.usbgc_mii_dont_reset) { + usbgem_mii_write(dp, MII_CONTROL, MII_CONTROL_RESET, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + } + dp->mii_interval = WATCH_INTERVAL_FAST; + goto next; + +autonego: + if (!dp->mii_supress_msg) { + cmn_err(CE_CONT, "!%s: auto-negotiation started", dp->name); + } + dp->mii_state = MII_STATE_AUTONEGOTIATING; + dp->mii_timer = dp->ugc.usbgc_mii_an_timeout; + + /* start/restart autoneg */ + val = usbgem_mii_read(dp, MII_CONTROL, &err) & + ~(MII_CONTROL_ISOLATE | MII_CONTROL_PWRDN | MII_CONTROL_RESET); + if (err != USB_SUCCESS) { + goto usberr; + } + if (val & MII_CONTROL_ANE) { + val |= MII_CONTROL_RSAN; + } + usbgem_mii_write(dp, MII_CONTROL, + val | dp->ugc.usbgc_mii_an_cmd | MII_CONTROL_ANE, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + + dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval; + goto next; + +usberr: + dp->mii_state = MII_STATE_UNKNOWN; + dp->mii_interval = dp->ugc.usbgc_mii_link_watch_interval; + tx_sched = B_TRUE; + +next: + *newstatep = dp->mii_state; + rw_exit(&dp->dev_state_lock); + return (tx_sched); +} + +static void +usbgem_mii_link_watcher(struct usbgem_dev *dp) +{ + int old_mii_state; + int new_mii_state; + boolean_t tx_sched; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + for (; ; ) { + + mutex_enter(&dp->link_watcher_lock); + if (dp->mii_interval) { + (void) cv_timedwait(&dp->link_watcher_wait_cv, + &dp->link_watcher_lock, + dp->mii_interval + ddi_get_lbolt()); + } else { + cv_wait(&dp->link_watcher_wait_cv, + &dp->link_watcher_lock); + } + mutex_exit(&dp->link_watcher_lock); + + if (dp->link_watcher_stop) { + break; + } + + /* we block callbacks from disconnect/suspend and restart */ + tx_sched = usbgem_mii_link_check(dp, + &old_mii_state, &new_mii_state); + + /* + * gld v2 notifier functions are not able to + * be called with any locks in this layer. + */ + if (tx_sched) { + /* kick potentially stopped downstream */ +#ifdef USBGEM_CONFIG_GLDv3 + mac_tx_update(dp->mh); +#else + gld_sched(dp->macinfo); +#endif + } + + if (old_mii_state != new_mii_state) { + /* notify new mii link state */ + if (new_mii_state == MII_STATE_LINKUP) { + dp->linkup_delay = 0; + USBGEM_LINKUP(dp); + } else if (dp->linkup_delay <= 0) { + USBGEM_LINKDOWN(dp); + } + } else if (dp->linkup_delay < 0) { + /* first linkup timeout */ + dp->linkup_delay = 0; + USBGEM_LINKDOWN(dp); + } + } + + thread_exit(); +} + +void +usbgem_mii_update_link(struct usbgem_dev *dp) +{ + cv_signal(&dp->link_watcher_wait_cv); +} + +int +usbgem_mii_probe_default(struct usbgem_dev *dp) +{ + int phy; + uint16_t status; + uint16_t xstatus; + int err; + uint16_t adv; + uint16_t adv_org; + + DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* + * Scan PHY + */ + dp->mii_status = 0; + + /* Try default phy first */ + if (dp->mii_phy_addr) { + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + if (status != 0xffff && status != 0x0000) { + goto PHY_found; + } + + if (dp->mii_phy_addr < 0) { + cmn_err(CE_NOTE, + "!%s: failed to probe default internal and/or non-MII PHY", + dp->name); + return (USB_FAILURE); + } + + cmn_err(CE_NOTE, + "!%s: failed to probe default MII PHY at %d", + dp->name, dp->mii_phy_addr); + } + + /* Try all possible address */ + for (phy = dp->ugc.usbgc_mii_addr_min; phy < 32; phy++) { + dp->mii_phy_addr = phy; + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + DPRINTF(0, (CE_CONT, + "!%s: %s: mii_read(status) failed", + dp->name, __func__)); + goto usberr; + } + + if (status != 0xffff && status != 0x0000) { + usbgem_mii_write(dp, MII_CONTROL, 0, &err); + if (err != USB_SUCCESS) { + DPRINTF(0, (CE_CONT, + "!%s: %s: mii_write(control) failed", + dp->name, __func__)); + goto usberr; + } + goto PHY_found; + } + } + for (phy = dp->ugc.usbgc_mii_addr_min; phy < 32; phy++) { + dp->mii_phy_addr = phy; + usbgem_mii_write(dp, MII_CONTROL, 0, &err); + if (err != USB_SUCCESS) { + DPRINTF(0, (CE_CONT, + "!%s: %s: mii_write(control) failed", + dp->name, __func__)); + goto usberr; + } + status = usbgem_mii_read(dp, MII_STATUS, &err); + if (err != USB_SUCCESS) { + DPRINTF(0, (CE_CONT, + "!%s: %s: mii_read(status) failed", + dp->name, __func__)); + goto usberr; + } + + if (status != 0xffff && status != 0) { + goto PHY_found; + } + } + + cmn_err(CE_NOTE, "!%s: no MII PHY found", dp->name); + return (USB_FAILURE); + +PHY_found: + dp->mii_status = status; + dp->mii_status_ro = ~status; + dp->mii_phy_id = usbgem_mii_read(dp, MII_PHYIDH, &err) << 16; + if (err != USB_SUCCESS) { + DPRINTF(0, (CE_CONT, + "!%s: %s: mii_read(PHYIDH) failed", + dp->name, __func__)); + goto usberr; + } + dp->mii_phy_id |= usbgem_mii_read(dp, MII_PHYIDL, &err); + if (err != USB_SUCCESS) { + DPRINTF(0, (CE_CONT, + "!%s: %s: mii_read(PHYIDL) failed", + dp->name, __func__)); + goto usberr; + } + + if (dp->mii_phy_addr < 0) { + cmn_err(CE_CONT, "!%s: using internal/non-MII PHY(0x%08x)", + dp->name, dp->mii_phy_id); + } else { + cmn_err(CE_CONT, "!%s: MII PHY (0x%08x) found at %d", + dp->name, dp->mii_phy_id, dp->mii_phy_addr); + } + + cmn_err(CE_CONT, + "!%s: PHY control:%b, status:%b, advert:%b, lpar:%b, exp:%b", + dp->name, + usbgem_mii_read(dp, MII_CONTROL, &err), MII_CONTROL_BITS, + status, MII_STATUS_BITS, + usbgem_mii_read(dp, MII_AN_ADVERT, &err), MII_ABILITY_BITS, + usbgem_mii_read(dp, MII_AN_LPABLE, &err), MII_ABILITY_BITS, + usbgem_mii_read(dp, MII_AN_EXPANSION, &err), MII_AN_EXP_BITS); + + dp->mii_xstatus = 0; + if (status & MII_STATUS_XSTATUS) { + dp->mii_xstatus = usbgem_mii_read(dp, MII_XSTATUS, &err); + + cmn_err(CE_CONT, "!%s: xstatus:%b", + dp->name, dp->mii_xstatus, MII_XSTATUS_BITS); + } + dp->mii_xstatus_ro = ~dp->mii_xstatus; + + /* check if the phy can advertize pause abilities */ + adv_org = usbgem_mii_read(dp, MII_AN_ADVERT, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + + usbgem_mii_write(dp, MII_AN_ADVERT, + MII_ABILITY_PAUSE | MII_ABILITY_ASM_DIR, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + + adv = usbgem_mii_read(dp, MII_AN_ADVERT, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + + if ((adv & MII_ABILITY_PAUSE) == 0) { + dp->ugc.usbgc_flow_control &= ~1; + } + + if ((adv & MII_ABILITY_ASM_DIR) == 0) { + dp->ugc.usbgc_flow_control &= ~2; + } + + usbgem_mii_write(dp, MII_AN_ADVERT, adv_org, &err); + if (err != USB_SUCCESS) { + goto usberr; + } + return (USB_SUCCESS); + +usberr: + return (USB_FAILURE); +} + +int +usbgem_mii_init_default(struct usbgem_dev *dp) +{ + /* ENPTY */ + return (USB_SUCCESS); +} + +static int +usbgem_mii_start(struct usbgem_dev *dp) +{ + int err; + kthread_t *lwth; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* make a first call of usbgem_mii_link_check() */ + dp->link_watcher_stop = 0; + dp->mii_state = MII_STATE_UNKNOWN; + dp->mii_interval = drv_usectohz(1000*1000); /* 1sec */ + dp->mii_last_check = ddi_get_lbolt(); + dp->linkup_delay = 600 * drv_usectohz(1000*1000); /* 10 minutes */ + + lwth = thread_create(NULL, 0, usbgem_mii_link_watcher, dp, 0, &p0, + TS_RUN, minclsyspri); + if (lwth == NULL) { + cmn_err(CE_WARN, + "!%s: %s: failed to create a link watcher thread", + dp->name, __func__); + return (USB_FAILURE); + } + dp->link_watcher_did = lwth->t_did; + + return (USB_SUCCESS); +} + +static void +usbgem_mii_stop(struct usbgem_dev *dp) +{ + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* Ensure timer routine stopped */ + dp->link_watcher_stop = 1; + cv_signal(&dp->link_watcher_wait_cv); + thread_join(dp->link_watcher_did); +} + +/* ============================================================== */ +/* + * internal mac register operation interface + */ +/* ============================================================== */ +/* + * usbgem_mac_init: cold start + */ +static int +usbgem_mac_init(struct usbgem_dev *dp) +{ + int err; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + /* pretend we succeeded */ + return (USB_SUCCESS); + } + + ASSERT(dp->mac_state == MAC_STATE_STOPPED); + + /* reset fatal error timestamp */ + dp->fatal_error = (clock_t)0; + + /* reset tx side state */ + mutex_enter(&dp->txlock); + dp->tx_busy_cnt = 0; + dp->tx_max_packets = dp->ugc.usbgc_tx_list_max; + mutex_exit(&dp->txlock); + + /* reset rx side state */ + mutex_enter(&dp->rxlock); + dp->rx_busy_cnt = 0; + mutex_exit(&dp->rxlock); + + err = usbgem_hal_init_chip(dp); + if (err == USB_SUCCESS) { + dp->mac_state = MAC_STATE_INITIALIZED; + } + + return (err); +} + +/* + * usbgem_mac_start: warm start + */ +static int +usbgem_mac_start(struct usbgem_dev *dp) +{ + int err; + int i; + usb_flags_t flags = 0; + usb_intr_req_t *req; +#ifdef USBGEM_DEBUG_LEVEL + usb_pipe_state_t p_state; +#endif + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + /* do nothing but don't return failure */ + return (USB_SUCCESS); + } + + if (dp->mac_state != MAC_STATE_INITIALIZED) { + /* don't return failer */ + DPRINTF(0, (CE_CONT, + "!%s: %s: mac_state(%d) is not MAC_STATE_INITIALIZED", + dp->name, __func__, dp->mac_state)); + goto x; + } + + dp->mac_state = MAC_STATE_ONLINE; + + if (usbgem_hal_start_chip(dp) != USB_SUCCESS) { + cmn_err(CE_NOTE, + "!%s: %s: usb error was detected during start_chip", + dp->name, __func__); + goto x; + } + +#ifdef USBGEM_DEBUG_LEVEL + usb_pipe_get_state(dp->intr_pipe, &p_state, 0); + ASSERT(p_state == USB_PIPE_STATE_IDLE); +#endif /* USBGEM_DEBUG_LEVEL */ + + if (dp->ugc.usbgc_interrupt && dp->intr_pipe) { + + /* make a request for interrupt */ + + req = usb_alloc_intr_req(dp->dip, 0, USB_FLAGS_SLEEP); + if (req == NULL) { + cmn_err(CE_WARN, "!%s: %s: failed to allocate intreq", + dp->name, __func__); + goto x; + } + req->intr_data = NULL; + req->intr_client_private = (usb_opaque_t)dp; + req->intr_timeout = 0; + req->intr_attributes = + USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING; + req->intr_len = dp->ep_intr->wMaxPacketSize; + req->intr_cb = usbgem_intr_cb; + req->intr_exc_cb = usbgem_intr_cb; + req->intr_completion_reason = 0; + req->intr_cb_flags = 0; + + err = usb_pipe_intr_xfer(dp->intr_pipe, req, flags); + if (err != USB_SUCCESS) { + cmn_err(CE_WARN, + "%s: err:%d failed to start polling of intr pipe", + dp->name, err); + goto x; + } + } + + /* kick to receive the first packet */ + if (usbgem_init_rx_buf(dp) != USB_SUCCESS) { + goto err_stop_intr; + } + dp->rx_active = B_TRUE; + + return (USB_SUCCESS); + +err_stop_intr: + /* stop the interrupt pipe */ + DPRINTF(0, (CE_CONT, "!%s: %s: FAULURE", dp->name, __func__)); + if (dp->ugc.usbgc_interrupt && dp->intr_pipe) { + usb_pipe_stop_intr_polling(dp->intr_pipe, USB_FLAGS_SLEEP); + } +x: + ASSERT(dp->mac_state == MAC_STATE_ONLINE); + /* we use another flag to indicate error state. */ + if (dp->fatal_error == (clock_t)0) { + dp->fatal_error = usbgem_timestamp_nz(); + } + return (USB_FAILURE); +} + +static int +usbgem_mac_stop(struct usbgem_dev *dp, int new_state, boolean_t graceful) +{ + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* + * we must have writer lock for dev_state_lock + */ + ASSERT(new_state == MAC_STATE_STOPPED + || new_state == MAC_STATE_DISCONNECTED); + + /* stop polling interrupt pipe */ + if (dp->ugc.usbgc_interrupt && dp->intr_pipe) { + usb_pipe_stop_intr_polling(dp->intr_pipe, USB_FLAGS_SLEEP); + } + + if (new_state == MAC_STATE_STOPPED || graceful) { + /* stop the nic hardware completely */ + if (usbgem_hal_stop_chip(dp) != USB_SUCCESS) { + (void) usbgem_hal_reset_chip(dp); + } + } + + /* stop preparing new rx packets and sending new packets */ + dp->mac_state = new_state; + + /* other processors must get mac_state correctly after here */ + membar_producer(); + + /* cancel all requests we have sent */ + usb_pipe_reset(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0); + usb_pipe_reset(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0); + + DPRINTF(0, (CE_CONT, + "!%s: %s: rx_busy_cnt:%d tx_busy_cnt:%d", + dp->name, __func__, dp->rx_busy_cnt, dp->tx_busy_cnt)); + + /* + * Here all rx packets has been cancelled and their call back + * function has been exeuted, because we called usb_pipe_reset + * synchronously. + * So actually we just ensure rx_busy_cnt == 0. + */ + mutex_enter(&dp->rxlock); + while (dp->rx_busy_cnt > 0) { + cv_wait(&dp->rx_drain_cv, &dp->rxlock); + } + mutex_exit(&dp->rxlock); + + DPRINTF(0, (CE_CONT, "!%s: %s: rx_busy_cnt is %d now", + dp->name, __func__, dp->rx_busy_cnt)); + + mutex_enter(&dp->txlock); + while (dp->tx_busy_cnt > 0) { + cv_wait(&dp->tx_drain_cv, &dp->txlock); + } + mutex_exit(&dp->txlock); + + DPRINTF(0, (CE_CONT, "!%s: %s: tx_busy_cnt is %d now", + dp->name, __func__, dp->tx_busy_cnt)); + + return (USB_SUCCESS); +} + +static int +usbgem_add_multicast(struct usbgem_dev *dp, const uint8_t *ep) +{ + int cnt; + int err; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + sema_p(&dp->rxfilter_lock); + if (dp->mc_count_req++ < USBGEM_MAXMC) { + /* append the new address at the end of the mclist */ + cnt = dp->mc_count; + bcopy(ep, dp->mc_list[cnt].addr.ether_addr_octet, + ETHERADDRL); + if (dp->ugc.usbgc_multicast_hash) { + dp->mc_list[cnt].hash = + (*dp->ugc.usbgc_multicast_hash)(dp, ep); + } + dp->mc_count = cnt + 1; + } + + if (dp->mc_count_req != dp->mc_count) { + /* multicast address list overflow */ + dp->rxmode |= RXMODE_MULTI_OVF; + } else { + dp->rxmode &= ~RXMODE_MULTI_OVF; + } + + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + /* tell new multicast list to the hardware */ + err = usbgem_hal_set_rx_filter(dp); + } + sema_v(&dp->rxfilter_lock); + + return (err); +} + +static int +usbgem_remove_multicast(struct usbgem_dev *dp, const uint8_t *ep) +{ + size_t len; + int i; + int cnt; + int err; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + sema_p(&dp->rxfilter_lock); + dp->mc_count_req--; + cnt = dp->mc_count; + for (i = 0; i < cnt; i++) { + if (bcmp(ep, &dp->mc_list[i].addr, ETHERADDRL)) { + continue; + } + /* shrink the mclist by copying forward */ + len = (cnt - (i + 1)) * sizeof (*dp->mc_list); + if (len > 0) { + bcopy(&dp->mc_list[i+1], &dp->mc_list[i], len); + } + dp->mc_count--; + break; + } + + if (dp->mc_count_req != dp->mc_count) { + /* multicast address list overflow */ + dp->rxmode |= RXMODE_MULTI_OVF; + } else { + dp->rxmode &= ~RXMODE_MULTI_OVF; + } + + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + err = usbgem_hal_set_rx_filter(dp); + } + sema_v(&dp->rxfilter_lock); + + return (err); +} + + +/* ============================================================== */ +/* + * ioctl + */ +/* ============================================================== */ +enum ioc_reply { + IOC_INVAL = -1, /* bad, NAK with EINVAL */ + IOC_DONE, /* OK, reply sent */ + IOC_ACK, /* OK, just send ACK */ + IOC_REPLY, /* OK, just send reply */ + IOC_RESTART_ACK, /* OK, restart & ACK */ + IOC_RESTART_REPLY /* OK, restart & reply */ +}; + + +#ifdef USBGEM_CONFIG_MAC_PROP +static int +usbgem_get_def_val(struct usbgem_dev *dp, mac_prop_id_t pr_num, + uint_t pr_valsize, void *pr_val) +{ + link_flowctrl_t fl; + int err = 0; + + ASSERT(pr_valsize > 0); + switch (pr_num) { + case MAC_PROP_AUTONEG: + *(uint8_t *)pr_val = + BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); + break; + + case MAC_PROP_FLOWCTRL: + if (pr_valsize < sizeof (link_flowctrl_t)) { + return (EINVAL); + } + switch (dp->ugc.usbgc_flow_control) { + case FLOW_CONTROL_NONE: + fl = LINK_FLOWCTRL_NONE; + break; + case FLOW_CONTROL_SYMMETRIC: + fl = LINK_FLOWCTRL_BI; + break; + case FLOW_CONTROL_TX_PAUSE: + fl = LINK_FLOWCTRL_TX; + break; + case FLOW_CONTROL_RX_PAUSE: + fl = LINK_FLOWCTRL_RX; + break; + } + bcopy(&fl, pr_val, sizeof (fl)); + break; + + case MAC_PROP_ADV_1000FDX_CAP: + case MAC_PROP_EN_1000FDX_CAP: + *(uint8_t *)pr_val = + (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); + break; + + case MAC_PROP_ADV_1000HDX_CAP: + case MAC_PROP_EN_1000HDX_CAP: + *(uint8_t *)pr_val = + (dp->mii_xstatus & MII_XSTATUS_1000BASET) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX); + break; + + case MAC_PROP_ADV_100T4_CAP: + case MAC_PROP_EN_100T4_CAP: + *(uint8_t *)pr_val = + BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); + break; + + case MAC_PROP_ADV_100FDX_CAP: + case MAC_PROP_EN_100FDX_CAP: + *(uint8_t *)pr_val = + BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); + break; + + case MAC_PROP_ADV_100HDX_CAP: + case MAC_PROP_EN_100HDX_CAP: + *(uint8_t *)pr_val = + BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); + break; + + case MAC_PROP_ADV_10FDX_CAP: + case MAC_PROP_EN_10FDX_CAP: + *(uint8_t *)pr_val = + BOOLEAN(dp->mii_status & MII_STATUS_10_FD); + break; + + case MAC_PROP_ADV_10HDX_CAP: + case MAC_PROP_EN_10HDX_CAP: + *(uint8_t *)pr_val = + BOOLEAN(dp->mii_status & MII_STATUS_10); + break; + + default: + err = ENOTSUP; + break; + } + return (err); +} + +#ifdef MAC_VERSION_V1 +static void +usbgem_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num, + mac_prop_info_handle_t prh) +{ + struct usbgem_dev *dp = arg; + link_flowctrl_t fl; + + /* + * By default permissions are read/write unless specified + * otherwise by the driver. + */ + + switch (pr_num) { + case MAC_PROP_DUPLEX: + case MAC_PROP_SPEED: + case MAC_PROP_STATUS: + case MAC_PROP_ADV_1000FDX_CAP: + case MAC_PROP_ADV_1000HDX_CAP: + case MAC_PROP_ADV_100FDX_CAP: + case MAC_PROP_ADV_100HDX_CAP: + case MAC_PROP_ADV_10FDX_CAP: + case MAC_PROP_ADV_10HDX_CAP: + case MAC_PROP_ADV_100T4_CAP: + case MAC_PROP_EN_100T4_CAP: + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + break; + + case MAC_PROP_EN_1000FDX_CAP: + if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN( + dp->mii_xstatus & MII_XSTATUS_1000BASET_FD)); + } else if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD) + == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN( + dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_EN_1000HDX_CAP: + if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN( + dp->mii_xstatus & MII_XSTATUS_1000BASET)); + } else if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN( + dp->mii_xstatus & MII_XSTATUS_1000BASEX)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_EN_100FDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_100_BASEX_FD) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_EN_100HDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_100_BASEX) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_EN_10FDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN(dp->mii_status & MII_STATUS_10_FD)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_EN_10HDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_10) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN(dp->mii_status & MII_STATUS_10)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_AUTONEG: + if ((dp->mii_status_ro & MII_STATUS_CANAUTONEG) == 0) { + mac_prop_info_set_default_uint8(prh, + BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG)); + } else { + mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); + } + break; + + case MAC_PROP_FLOWCTRL: + switch (dp->ugc.usbgc_flow_control) { + case FLOW_CONTROL_NONE: + fl = LINK_FLOWCTRL_NONE; + break; + case FLOW_CONTROL_SYMMETRIC: + fl = LINK_FLOWCTRL_BI; + break; + case FLOW_CONTROL_TX_PAUSE: + fl = LINK_FLOWCTRL_TX; + break; + case FLOW_CONTROL_RX_PAUSE: + fl = LINK_FLOWCTRL_RX; + break; + } + mac_prop_info_set_default_link_flowctrl(prh, fl); + break; + + case MAC_PROP_MTU: + mac_prop_info_set_range_uint32(prh, + dp->ugc.usbgc_min_mtu, dp->ugc.usbgc_max_mtu); + break; + + case MAC_PROP_PRIVATE: + break; + } +} +#endif + +static int +usbgem_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, + uint_t pr_valsize, const void *pr_val) +{ + struct usbgem_dev *dp = arg; + int err = 0; + boolean_t update = B_FALSE; + link_flowctrl_t flowctrl; + uint32_t cur_mtu, new_mtu; + + rw_enter(&dp->dev_state_lock, RW_WRITER); + switch (pr_num) { + case MAC_PROP_EN_1000FDX_CAP: + if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) == 0 || + (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD) == 0) { + if (dp->anadv_1000fdx != *(uint8_t *)pr_val) { + dp->anadv_1000fdx = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_EN_1000HDX_CAP: + if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) == 0 || + (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX) == 0) { + if (dp->anadv_1000hdx != *(uint8_t *)pr_val) { + dp->anadv_1000hdx = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_EN_100FDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_100_BASEX_FD) == 0) { + if (dp->anadv_100fdx != *(uint8_t *)pr_val) { + dp->anadv_100fdx = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_EN_100HDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_100_BASEX) == 0) { + if (dp->anadv_100hdx != *(uint8_t *)pr_val) { + dp->anadv_100hdx = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_EN_10FDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) { + if (dp->anadv_10fdx != *(uint8_t *)pr_val) { + dp->anadv_10fdx = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_EN_10HDX_CAP: + if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) { + if (dp->anadv_10hdx != *(uint8_t *)pr_val) { + dp->anadv_10hdx = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_AUTONEG: + if ((dp->mii_status_ro & MII_STATUS_CANAUTONEG) == 0) { + if (dp->anadv_autoneg != *(uint8_t *)pr_val) { + dp->anadv_autoneg = *(uint8_t *)pr_val; + update = B_TRUE; + } + } else { + err = ENOTSUP; + } + break; + + case MAC_PROP_FLOWCTRL: + bcopy(pr_val, &flowctrl, sizeof (flowctrl)); + + switch (flowctrl) { + default: + err = EINVAL; + break; + + case LINK_FLOWCTRL_NONE: + if (dp->flow_control != FLOW_CONTROL_NONE) { + dp->flow_control = FLOW_CONTROL_NONE; + update = B_TRUE; + } + break; + + case LINK_FLOWCTRL_RX: + if (dp->flow_control != FLOW_CONTROL_RX_PAUSE) { + dp->flow_control = FLOW_CONTROL_RX_PAUSE; + update = B_TRUE; + } + break; + + case LINK_FLOWCTRL_TX: + if (dp->flow_control != FLOW_CONTROL_TX_PAUSE) { + dp->flow_control = FLOW_CONTROL_TX_PAUSE; + update = B_TRUE; + } + break; + + case LINK_FLOWCTRL_BI: + if (dp->flow_control != FLOW_CONTROL_SYMMETRIC) { + dp->flow_control = FLOW_CONTROL_SYMMETRIC; + update = B_TRUE; + } + break; + } + break; + + case MAC_PROP_ADV_1000FDX_CAP: + case MAC_PROP_ADV_1000HDX_CAP: + case MAC_PROP_ADV_100FDX_CAP: + case MAC_PROP_ADV_100HDX_CAP: + case MAC_PROP_ADV_10FDX_CAP: + case MAC_PROP_ADV_10HDX_CAP: + case MAC_PROP_STATUS: + case MAC_PROP_SPEED: + case MAC_PROP_DUPLEX: + err = ENOTSUP; /* read-only prop. Can't set this. */ + break; + + case MAC_PROP_MTU: + bcopy(pr_val, &new_mtu, sizeof (new_mtu)); + if (new_mtu != dp->mtu) { + err = EINVAL; + } + break; + + case MAC_PROP_PRIVATE: + err = ENOTSUP; + break; + + default: + err = ENOTSUP; + break; + } + + if (update) { + /* sync with PHY */ + usbgem_choose_forcedmode(dp); + dp->mii_state = MII_STATE_UNKNOWN; + cv_signal(&dp->link_watcher_wait_cv); + } + rw_exit(&dp->dev_state_lock); + return (err); +} + +static int +#ifdef MAC_VERSION_V1 +usbgem_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, + uint_t pr_valsize, void *pr_val) +#else +usbgem_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, + uint_t pr_flags, uint_t pr_valsize, void *pr_val, uint_t *perm) +#endif +{ + struct usbgem_dev *dp = arg; + int err = 0; + link_flowctrl_t flowctrl; + uint64_t tmp = 0; + + if (pr_valsize == 0) { + return (EINVAL); + } +#ifndef MAC_VERSION_V1 + *perm = MAC_PROP_PERM_RW; +#endif + bzero(pr_val, pr_valsize); +#ifndef MAC_VERSION_V1 + if ((pr_flags & MAC_PROP_DEFAULT) && (pr_num != MAC_PROP_PRIVATE)) { + return (usbgem_get_def_val(dp, pr_num, pr_valsize, pr_val)); + } +#endif + rw_enter(&dp->dev_state_lock, RW_READER); + switch (pr_num) { + case MAC_PROP_DUPLEX: +#ifndef MAC_VERSION_V1 + *perm = MAC_PROP_PERM_READ; +#endif + if (pr_valsize >= sizeof (link_duplex_t)) { + if (dp->mii_state != MII_STATE_LINKUP) { + *(link_duplex_t *)pr_val = LINK_DUPLEX_UNKNOWN; + } else if (dp->full_duplex) { + *(link_duplex_t *)pr_val = LINK_DUPLEX_FULL; + } else { + *(link_duplex_t *)pr_val = LINK_DUPLEX_HALF; + } + } else { + err = EINVAL; + } + break; + case MAC_PROP_SPEED: +#ifndef MAC_VERSION_V1 + *perm = MAC_PROP_PERM_READ; +#endif + if (pr_valsize >= sizeof (uint64_t)) { + switch (dp->speed) { + case USBGEM_SPD_1000: + tmp = 1000000000; + break; + case USBGEM_SPD_100: + tmp = 100000000; + break; + case USBGEM_SPD_10: + tmp = 10000000; + break; + default: + tmp = 0; + } + bcopy(&tmp, pr_val, sizeof (tmp)); + } else { + err = EINVAL; + } + break; + + case MAC_PROP_AUTONEG: +#ifndef MAC_VERSION_V1 + if (dp->mii_status_ro & MII_STATUS_CANAUTONEG) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_autoneg; + break; + + case MAC_PROP_FLOWCTRL: + if (pr_valsize >= sizeof (link_flowctrl_t)) { + switch (dp->flow_control) { + case FLOW_CONTROL_NONE: + flowctrl = LINK_FLOWCTRL_NONE; + break; + case FLOW_CONTROL_RX_PAUSE: + flowctrl = LINK_FLOWCTRL_RX; + break; + case FLOW_CONTROL_TX_PAUSE: + flowctrl = LINK_FLOWCTRL_TX; + break; + case FLOW_CONTROL_SYMMETRIC: + flowctrl = LINK_FLOWCTRL_BI; + break; + } + bcopy(&flowctrl, pr_val, sizeof (flowctrl)); + } else { + err = EINVAL; + } + break; + + case MAC_PROP_ADV_1000FDX_CAP: + case MAC_PROP_ADV_1000HDX_CAP: + case MAC_PROP_ADV_100FDX_CAP: + case MAC_PROP_ADV_100HDX_CAP: + case MAC_PROP_ADV_10FDX_CAP: + case MAC_PROP_ADV_10HDX_CAP: + case MAC_PROP_ADV_100T4_CAP: + usbgem_get_def_val(dp, pr_num, pr_valsize, pr_val); + break; + + case MAC_PROP_EN_1000FDX_CAP: +#ifndef MAC_VERSION_V1 + if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) && + (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD)) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_1000fdx; + break; + + case MAC_PROP_EN_1000HDX_CAP: +#ifndef MAC_VERSION_V1 + if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) && + (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX)) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_1000hdx; + break; + + case MAC_PROP_EN_100FDX_CAP: +#ifndef MAC_VERSION_V1 + if (dp->mii_status_ro & MII_STATUS_100_BASEX_FD) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_100fdx; + break; + + case MAC_PROP_EN_100HDX_CAP: +#ifndef MAC_VERSION_V1 + if (dp->mii_status_ro & MII_STATUS_100_BASEX) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_100hdx; + break; + + case MAC_PROP_EN_10FDX_CAP: +#ifndef MAC_VERSION_V1 + if (dp->mii_status_ro & MII_STATUS_10_FD) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_10fdx; + break; + + case MAC_PROP_EN_10HDX_CAP: +#ifndef MAC_VERSION_V1 + if (dp->mii_status_ro & MII_STATUS_10) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_10hdx; + break; + + case MAC_PROP_EN_100T4_CAP: +#ifndef MAC_VERSION_V1 + if (dp->mii_status_ro & MII_STATUS_100_BASE_T4) { + *perm = MAC_PROP_PERM_READ; + } +#endif + *(uint8_t *)pr_val = dp->anadv_100t4; + break; + + case MAC_PROP_PRIVATE: + err = ENOTSUP; + break; + +#ifndef MAC_VERSION_V1 + case MAC_PROP_MTU: { + mac_propval_range_t range; + if (!(pr_flags & MAC_PROP_POSSIBLE)) { + err = ENOTSUP; + break; + } + if (pr_valsize < sizeof (mac_propval_range_t)) { + err = EINVAL; + break; + } + range.mpr_count = 1; + range.mpr_type = MAC_PROPVAL_UINT32; + range.range_uint32[0].mpur_min = ETHERMTU; + range.range_uint32[0].mpur_max = dp->mtu; + bcopy(&range, pr_val, sizeof (range)); + break; + } +#endif + default: + err = ENOTSUP; + break; + } + + rw_exit(&dp->dev_state_lock); + return (err); +} +#endif /* USBGEM_CONFIG_MAC_PROP */ + +#ifdef USBGEM_CONFIG_ND +/* ============================================================== */ +/* + * ND interface + */ +/* ============================================================== */ +enum { + PARAM_AUTONEG_CAP, + PARAM_PAUSE_CAP, + PARAM_ASYM_PAUSE_CAP, + PARAM_1000FDX_CAP, + PARAM_1000HDX_CAP, + PARAM_100T4_CAP, + PARAM_100FDX_CAP, + PARAM_100HDX_CAP, + PARAM_10FDX_CAP, + PARAM_10HDX_CAP, + + PARAM_ADV_AUTONEG_CAP, + PARAM_ADV_PAUSE_CAP, + PARAM_ADV_ASYM_PAUSE_CAP, + PARAM_ADV_1000FDX_CAP, + PARAM_ADV_1000HDX_CAP, + PARAM_ADV_100T4_CAP, + PARAM_ADV_100FDX_CAP, + PARAM_ADV_100HDX_CAP, + PARAM_ADV_10FDX_CAP, + PARAM_ADV_10HDX_CAP, + PARAM_ADV_1000T_MS, + + PARAM_LP_AUTONEG_CAP, + PARAM_LP_PAUSE_CAP, + PARAM_LP_ASYM_PAUSE_CAP, + PARAM_LP_1000FDX_CAP, + PARAM_LP_1000HDX_CAP, + PARAM_LP_100T4_CAP, + PARAM_LP_100FDX_CAP, + PARAM_LP_100HDX_CAP, + PARAM_LP_10FDX_CAP, + PARAM_LP_10HDX_CAP, + + PARAM_LINK_STATUS, + PARAM_LINK_SPEED, + PARAM_LINK_DUPLEX, + + PARAM_LINK_AUTONEG, + PARAM_LINK_RX_PAUSE, + PARAM_LINK_TX_PAUSE, + + PARAM_LOOP_MODE, + PARAM_MSI_CNT, +#ifdef DEBUG_RESUME + PARAM_RESUME_TEST, +#endif + + PARAM_COUNT +}; + +struct usbgem_nd_arg { + struct usbgem_dev *dp; + int item; +}; + +static int +usbgem_param_get(queue_t *q, mblk_t *mp, caddr_t arg, cred_t *credp) +{ + struct usbgem_dev *dp = ((struct usbgem_nd_arg *)(void *)arg)->dp; + int item = ((struct usbgem_nd_arg *)(void *)arg)->item; + long val; + + DPRINTF(1, (CE_CONT, "!%s: %s: called, item:%d", + dp->name, __func__, item)); + + switch (item) { + case PARAM_AUTONEG_CAP: + val = BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); + DPRINTF(1, (CE_CONT, "autoneg_cap:%d", val)); + break; + + case PARAM_PAUSE_CAP: + val = dp->ugc.usbgc_flow_control != FLOW_CONTROL_NONE; + break; + + case PARAM_ASYM_PAUSE_CAP: + val = dp->ugc.usbgc_flow_control > FLOW_CONTROL_SYMMETRIC; + break; + + case PARAM_1000FDX_CAP: + val = (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); + break; + + case PARAM_1000HDX_CAP: + val = (dp->mii_xstatus & MII_XSTATUS_1000BASET) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX); + break; + + case PARAM_100T4_CAP: + val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); + break; + + case PARAM_100FDX_CAP: + val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); + break; + + case PARAM_100HDX_CAP: + val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); + break; + + case PARAM_10FDX_CAP: + val = BOOLEAN(dp->mii_status & MII_STATUS_10_FD); + break; + + case PARAM_10HDX_CAP: + val = BOOLEAN(dp->mii_status & MII_STATUS_10); + break; + + case PARAM_ADV_AUTONEG_CAP: + val = dp->anadv_autoneg; + break; + + case PARAM_ADV_PAUSE_CAP: + val = dp->anadv_pause; + break; + + case PARAM_ADV_ASYM_PAUSE_CAP: + val = dp->anadv_asmpause; + break; + + case PARAM_ADV_1000FDX_CAP: + val = dp->anadv_1000fdx; + break; + + case PARAM_ADV_1000HDX_CAP: + val = dp->anadv_1000hdx; + break; + + case PARAM_ADV_100T4_CAP: + val = dp->anadv_100t4; + break; + + case PARAM_ADV_100FDX_CAP: + val = dp->anadv_100fdx; + break; + + case PARAM_ADV_100HDX_CAP: + val = dp->anadv_100hdx; + break; + + case PARAM_ADV_10FDX_CAP: + val = dp->anadv_10fdx; + break; + + case PARAM_ADV_10HDX_CAP: + val = dp->anadv_10hdx; + break; + + case PARAM_ADV_1000T_MS: + val = dp->anadv_1000t_ms; + break; + + case PARAM_LP_AUTONEG_CAP: + val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); + break; + + case PARAM_LP_PAUSE_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_PAUSE); + break; + + case PARAM_LP_ASYM_PAUSE_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_ASM_DIR); + break; + + case PARAM_LP_1000FDX_CAP: + val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL); + break; + + case PARAM_LP_1000HDX_CAP: + val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF); + break; + + case PARAM_LP_100T4_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_T4); + break; + + case PARAM_LP_100FDX_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD); + break; + + case PARAM_LP_100HDX_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX); + break; + + case PARAM_LP_10FDX_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD); + break; + + case PARAM_LP_10HDX_CAP: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T); + break; + + case PARAM_LINK_STATUS: + val = (dp->mii_state == MII_STATE_LINKUP); + break; + + case PARAM_LINK_SPEED: + val = usbgem_speed_value[dp->speed]; + break; + + case PARAM_LINK_DUPLEX: + val = 0; + if (dp->mii_state == MII_STATE_LINKUP) { + val = dp->full_duplex ? 2 : 1; + } + break; + + case PARAM_LINK_AUTONEG: + val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); + break; + + case PARAM_LINK_RX_PAUSE: + val = (dp->flow_control == FLOW_CONTROL_SYMMETRIC) || + (dp->flow_control == FLOW_CONTROL_RX_PAUSE); + break; + + case PARAM_LINK_TX_PAUSE: + val = (dp->flow_control == FLOW_CONTROL_SYMMETRIC) || + (dp->flow_control == FLOW_CONTROL_TX_PAUSE); + break; + +#ifdef DEBUG_RESUME + case PARAM_RESUME_TEST: + val = 0; + break; +#endif + default: + cmn_err(CE_WARN, "%s: unimplemented ndd control (%d)", + dp->name, item); + break; + } + + (void) mi_mpprintf(mp, "%ld", val); + + return (0); +} + +static int +usbgem_param_set(queue_t *q, + mblk_t *mp, char *value, caddr_t arg, cred_t *credp) +{ + struct usbgem_dev *dp = ((struct usbgem_nd_arg *)(void *)arg)->dp; + int item = ((struct usbgem_nd_arg *)(void *)arg)->item; + long val; + char *end; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + if (ddi_strtol(value, &end, 10, &val)) { + return (EINVAL); + } + if (end == value) { + return (EINVAL); + } + + switch (item) { + case PARAM_ADV_AUTONEG_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_status & MII_STATUS_CANAUTONEG) == 0) { + goto err; + } + dp->anadv_autoneg = (int)val; + break; + + case PARAM_ADV_PAUSE_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && dp->ugc.usbgc_flow_control == FLOW_CONTROL_NONE) { + goto err; + } + dp->anadv_pause = (int)val; + break; + + case PARAM_ADV_ASYM_PAUSE_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && + dp->ugc.usbgc_flow_control <= FLOW_CONTROL_SYMMETRIC) { + goto err; + } + dp->anadv_asmpause = (int)val; + break; + + case PARAM_ADV_1000FDX_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_xstatus & + (MII_XSTATUS_1000BASET_FD | + MII_XSTATUS_1000BASEX_FD)) == 0) { + goto err; + } + dp->anadv_1000fdx = (int)val; + break; + + case PARAM_ADV_1000HDX_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_xstatus & + (MII_XSTATUS_1000BASET | MII_XSTATUS_1000BASEX)) == 0) { + goto err; + } + dp->anadv_1000hdx = (int)val; + break; + + case PARAM_ADV_100T4_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_status & MII_STATUS_100_BASE_T4) == 0) { + goto err; + } + dp->anadv_100t4 = (int)val; + break; + + case PARAM_ADV_100FDX_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_status & MII_STATUS_100_BASEX_FD) == 0) { + goto err; + } + dp->anadv_100fdx = (int)val; + break; + + case PARAM_ADV_100HDX_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_status & MII_STATUS_100_BASEX) == 0) { + goto err; + } + dp->anadv_100hdx = (int)val; + break; + + case PARAM_ADV_10FDX_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_status & MII_STATUS_10_FD) == 0) { + goto err; + } + dp->anadv_10fdx = (int)val; + break; + + case PARAM_ADV_10HDX_CAP: + if (val != 0 && val != 1) { + goto err; + } + if (val && (dp->mii_status & MII_STATUS_10) == 0) { + goto err; + } + dp->anadv_10hdx = (int)val; + break; + + case PARAM_ADV_1000T_MS: + if (val != 0 && val != 1 && val != 2) { + goto err; + } + if (val && (dp->mii_xstatus & + (MII_XSTATUS_1000BASET | MII_XSTATUS_1000BASET_FD)) == 0) { + goto err; + } + dp->anadv_1000t_ms = (int)val; + break; + +#ifdef DEBUG_RESUME + case PARAM_RESUME_TEST: + mutex_exit(&dp->xmitlock); + mutex_exit(&dp->intrlock); + gem_suspend(dp->dip); + gem_resume(dp->dip); + mutex_enter(&dp->intrlock); + mutex_enter(&dp->xmitlock); + break; +#endif + } + + /* sync with PHY */ + usbgem_choose_forcedmode(dp); + + dp->mii_state = MII_STATE_UNKNOWN; + if (dp->ugc.usbgc_mii_hw_link_detection) { + /* wake up link watcher possiblely sleeps */ + cv_signal(&dp->link_watcher_wait_cv); + } + + return (0); +err: + return (EINVAL); +} + +static void +usbgem_nd_load(struct usbgem_dev *dp, + char *name, ndgetf_t gf, ndsetf_t sf, int item) +{ + struct usbgem_nd_arg *arg; + + ASSERT(item >= 0); + ASSERT(item < PARAM_COUNT); + + arg = &((struct usbgem_nd_arg *)(void *)dp->nd_arg_p)[item]; + arg->dp = dp; + arg->item = item; + + DPRINTF(2, (CE_CONT, "!%s: %s: name:%s, item:%d", + dp->name, __func__, name, item)); + (void) nd_load(&dp->nd_data_p, name, gf, sf, (caddr_t)arg); +} + +static void +usbgem_nd_setup(struct usbgem_dev *dp) +{ + DPRINTF(1, (CE_CONT, "!%s: %s: called, mii_status:0x%b", + dp->name, __func__, dp->mii_status, MII_STATUS_BITS)); + + ASSERT(dp->nd_arg_p == NULL); + + dp->nd_arg_p = + kmem_zalloc(sizeof (struct usbgem_nd_arg) * PARAM_COUNT, KM_SLEEP); + +#define SETFUNC(x) ((x) ? usbgem_param_set : NULL) + + usbgem_nd_load(dp, "autoneg_cap", + usbgem_param_get, NULL, PARAM_AUTONEG_CAP); + usbgem_nd_load(dp, "pause_cap", + usbgem_param_get, NULL, PARAM_PAUSE_CAP); + usbgem_nd_load(dp, "asym_pause_cap", + usbgem_param_get, NULL, PARAM_ASYM_PAUSE_CAP); + usbgem_nd_load(dp, "1000fdx_cap", + usbgem_param_get, NULL, PARAM_1000FDX_CAP); + usbgem_nd_load(dp, "1000hdx_cap", + usbgem_param_get, NULL, PARAM_1000HDX_CAP); + usbgem_nd_load(dp, "100T4_cap", + usbgem_param_get, NULL, PARAM_100T4_CAP); + usbgem_nd_load(dp, "100fdx_cap", + usbgem_param_get, NULL, PARAM_100FDX_CAP); + usbgem_nd_load(dp, "100hdx_cap", + usbgem_param_get, NULL, PARAM_100HDX_CAP); + usbgem_nd_load(dp, "10fdx_cap", + usbgem_param_get, NULL, PARAM_10FDX_CAP); + usbgem_nd_load(dp, "10hdx_cap", + usbgem_param_get, NULL, PARAM_10HDX_CAP); + + /* Our advertised capabilities */ + usbgem_nd_load(dp, "adv_autoneg_cap", usbgem_param_get, + SETFUNC(dp->mii_status & MII_STATUS_CANAUTONEG), + PARAM_ADV_AUTONEG_CAP); + usbgem_nd_load(dp, "adv_pause_cap", usbgem_param_get, + SETFUNC(dp->ugc.usbgc_flow_control & 1), + PARAM_ADV_PAUSE_CAP); + usbgem_nd_load(dp, "adv_asym_pause_cap", usbgem_param_get, + SETFUNC(dp->ugc.usbgc_flow_control & 2), + PARAM_ADV_ASYM_PAUSE_CAP); + usbgem_nd_load(dp, "adv_1000fdx_cap", usbgem_param_get, + SETFUNC(dp->mii_xstatus & + (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASET_FD)), + PARAM_ADV_1000FDX_CAP); + usbgem_nd_load(dp, "adv_1000hdx_cap", usbgem_param_get, + SETFUNC(dp->mii_xstatus & + (MII_XSTATUS_1000BASEX | MII_XSTATUS_1000BASET)), + PARAM_ADV_1000HDX_CAP); + usbgem_nd_load(dp, "adv_100T4_cap", usbgem_param_get, + SETFUNC((dp->mii_status & MII_STATUS_100_BASE_T4) && + !dp->mii_advert_ro), + PARAM_ADV_100T4_CAP); + usbgem_nd_load(dp, "adv_100fdx_cap", usbgem_param_get, + SETFUNC((dp->mii_status & MII_STATUS_100_BASEX_FD) && + !dp->mii_advert_ro), + PARAM_ADV_100FDX_CAP); + usbgem_nd_load(dp, "adv_100hdx_cap", usbgem_param_get, + SETFUNC((dp->mii_status & MII_STATUS_100_BASEX) && + !dp->mii_advert_ro), + PARAM_ADV_100HDX_CAP); + usbgem_nd_load(dp, "adv_10fdx_cap", usbgem_param_get, + SETFUNC((dp->mii_status & MII_STATUS_10_FD) && + !dp->mii_advert_ro), + PARAM_ADV_10FDX_CAP); + usbgem_nd_load(dp, "adv_10hdx_cap", usbgem_param_get, + SETFUNC((dp->mii_status & MII_STATUS_10) && + !dp->mii_advert_ro), + PARAM_ADV_10HDX_CAP); + usbgem_nd_load(dp, "adv_1000t_ms", usbgem_param_get, + SETFUNC(dp->mii_xstatus & + (MII_XSTATUS_1000BASET_FD | MII_XSTATUS_1000BASET)), + PARAM_ADV_1000T_MS); + + + /* Partner's advertised capabilities */ + usbgem_nd_load(dp, "lp_autoneg_cap", + usbgem_param_get, NULL, PARAM_LP_AUTONEG_CAP); + usbgem_nd_load(dp, "lp_pause_cap", + usbgem_param_get, NULL, PARAM_LP_PAUSE_CAP); + usbgem_nd_load(dp, "lp_asym_pause_cap", + usbgem_param_get, NULL, PARAM_LP_ASYM_PAUSE_CAP); + usbgem_nd_load(dp, "lp_1000fdx_cap", + usbgem_param_get, NULL, PARAM_LP_1000FDX_CAP); + usbgem_nd_load(dp, "lp_1000hdx_cap", + usbgem_param_get, NULL, PARAM_LP_1000HDX_CAP); + usbgem_nd_load(dp, "lp_100T4_cap", + usbgem_param_get, NULL, PARAM_LP_100T4_CAP); + usbgem_nd_load(dp, "lp_100fdx_cap", + usbgem_param_get, NULL, PARAM_LP_100FDX_CAP); + usbgem_nd_load(dp, "lp_100hdx_cap", + usbgem_param_get, NULL, PARAM_LP_100HDX_CAP); + usbgem_nd_load(dp, "lp_10fdx_cap", + usbgem_param_get, NULL, PARAM_LP_10FDX_CAP); + usbgem_nd_load(dp, "lp_10hdx_cap", + usbgem_param_get, NULL, PARAM_LP_10HDX_CAP); + + /* Current operating modes */ + usbgem_nd_load(dp, "link_status", + usbgem_param_get, NULL, PARAM_LINK_STATUS); + usbgem_nd_load(dp, "link_speed", + usbgem_param_get, NULL, PARAM_LINK_SPEED); + usbgem_nd_load(dp, "link_duplex", + usbgem_param_get, NULL, PARAM_LINK_DUPLEX); + usbgem_nd_load(dp, "link_autoneg", + usbgem_param_get, NULL, PARAM_LINK_AUTONEG); + usbgem_nd_load(dp, "link_rx_pause", + usbgem_param_get, NULL, PARAM_LINK_RX_PAUSE); + usbgem_nd_load(dp, "link_tx_pause", + usbgem_param_get, NULL, PARAM_LINK_TX_PAUSE); +#ifdef DEBUG_RESUME + usbgem_nd_load(dp, "resume_test", + usbgem_param_get, usbgem_param_set, PARAM_RESUME_TEST); +#endif +#undef SETFUNC +} + +static +enum ioc_reply +usbgem_nd_ioctl(struct usbgem_dev *dp, + queue_t *wq, mblk_t *mp, struct iocblk *iocp) +{ + boolean_t ok; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + switch (iocp->ioc_cmd) { + case ND_GET: + ok = nd_getset(wq, dp->nd_data_p, mp); + DPRINTF(1, (CE_CONT, + "%s: get %s", dp->name, ok ? "OK" : "FAIL")); + return (ok ? IOC_REPLY : IOC_INVAL); + + case ND_SET: + ok = nd_getset(wq, dp->nd_data_p, mp); + + DPRINTF(1, (CE_CONT, "%s: set %s err %d", + dp->name, ok ? "OK" : "FAIL", iocp->ioc_error)); + + if (!ok) { + return (IOC_INVAL); + } + + if (iocp->ioc_error) { + return (IOC_REPLY); + } + + return (IOC_RESTART_REPLY); + } + + cmn_err(CE_WARN, "%s: invalid cmd 0x%x", dp->name, iocp->ioc_cmd); + + return (IOC_INVAL); +} + +static void +usbgem_nd_cleanup(struct usbgem_dev *dp) +{ + ASSERT(dp->nd_data_p != NULL); + ASSERT(dp->nd_arg_p != NULL); + + nd_free(&dp->nd_data_p); + + kmem_free(dp->nd_arg_p, sizeof (struct usbgem_nd_arg) * PARAM_COUNT); + dp->nd_arg_p = NULL; +} +#endif /* USBGEM_CONFIG_ND */ + +static void +usbgem_mac_ioctl(struct usbgem_dev *dp, queue_t *wq, mblk_t *mp) +{ + struct iocblk *iocp; + enum ioc_reply status; + int cmd; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* + * Validate the command before bothering with the mutex ... + */ + iocp = (void *)mp->b_rptr; + iocp->ioc_error = 0; + cmd = iocp->ioc_cmd; + + DPRINTF(1, (CE_CONT, "%s: %s cmd:0x%x", dp->name, __func__, cmd)); + +#ifdef USBGEM_CONFIG_ND + switch (cmd) { + default: + _NOTE(NOTREACHED) + status = IOC_INVAL; + break; + + case ND_GET: + case ND_SET: + status = usbgem_nd_ioctl(dp, wq, mp, iocp); + break; + } + + /* + * Finally, decide how to reply + */ + switch (status) { + default: + case IOC_INVAL: + /* + * Error, reply with a NAK and EINVAL or the specified error + */ + miocnak(wq, mp, 0, iocp->ioc_error == 0 ? + EINVAL : iocp->ioc_error); + break; + + case IOC_DONE: + /* + * OK, reply already sent + */ + break; + + case IOC_RESTART_ACK: + case IOC_ACK: + /* + * OK, reply with an ACK + */ + miocack(wq, mp, 0, 0); + break; + + case IOC_RESTART_REPLY: + case IOC_REPLY: + /* + * OK, send prepared reply as ACK or NAK + */ + mp->b_datap->db_type = + iocp->ioc_error == 0 ? M_IOCACK : M_IOCNAK; + qreply(wq, mp); + break; + } +#else + miocnak(wq, mp, 0, EINVAL); + return; +#endif /* USBGEM_CONFIG_GLDv3 */ +} + +#ifndef SYS_MAC_H +#define XCVR_UNDEFINED 0 +#define XCVR_NONE 1 +#define XCVR_10 2 +#define XCVR_100T4 3 +#define XCVR_100X 4 +#define XCVR_100T2 5 +#define XCVR_1000X 6 +#define XCVR_1000T 7 +#endif +static int +usbgem_mac_xcvr_inuse(struct usbgem_dev *dp) +{ + int val = XCVR_UNDEFINED; + + if ((dp->mii_status & MII_STATUS_XSTATUS) == 0) { + if (dp->mii_status & MII_STATUS_100_BASE_T4) { + val = XCVR_100T4; + } else if (dp->mii_status & + (MII_STATUS_100_BASEX_FD | + MII_STATUS_100_BASEX)) { + val = XCVR_100X; + } else if (dp->mii_status & + (MII_STATUS_100_BASE_T2_FD | + MII_STATUS_100_BASE_T2)) { + val = XCVR_100T2; + } else if (dp->mii_status & + (MII_STATUS_10_FD | MII_STATUS_10)) { + val = XCVR_10; + } + } else if (dp->mii_xstatus & + (MII_XSTATUS_1000BASET_FD | MII_XSTATUS_1000BASET)) { + val = XCVR_1000T; + } else if (dp->mii_xstatus & + (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASEX)) { + val = XCVR_1000X; + } + + return (val); +} + +#ifdef USBGEM_CONFIG_GLDv3 +/* ============================================================== */ +/* + * GLDv3 interface + */ +/* ============================================================== */ +static int usbgem_m_getstat(void *, uint_t, uint64_t *); +static int usbgem_m_start(void *); +static void usbgem_m_stop(void *); +static int usbgem_m_setpromisc(void *, boolean_t); +static int usbgem_m_multicst(void *, boolean_t, const uint8_t *); +static int usbgem_m_unicst(void *, const uint8_t *); +static mblk_t *usbgem_m_tx(void *, mblk_t *); +static void usbgem_m_ioctl(void *, queue_t *, mblk_t *); +#ifdef GEM_CONFIG_MAC_PROP +static int usbgem_m_setprop(void *, const char *, mac_prop_id_t, + uint_t, const void *); +#ifdef MAC_VERSION_V1 +static int usbgem_m_getprop(void *, const char *, mac_prop_id_t, + uint_t, void *); +#else +static int usbgem_m_getprop(void *, const char *, mac_prop_id_t, + uint_t, uint_t, void *, uint_t *); +#endif +#endif + +#ifdef _SYS_MAC_PROVIDER_H +#define GEM_M_CALLBACK_FLAGS (MC_IOCTL) +#else +#define GEM_M_CALLBACK_FLAGS (MC_IOCTL) +#endif + +static mac_callbacks_t gem_m_callbacks = { +#ifdef USBGEM_CONFIG_MAC_PROP +#ifdef MAC_VERSION_V1 + GEM_M_CALLBACK_FLAGS | MC_SETPROP | MC_GETPROP | MC_PROPINFO, +#else + GEM_M_CALLBACK_FLAGS | MC_SETPROP | MC_GETPROP, +#endif +#else + GEM_M_CALLBACK_FLAGS, +#endif + usbgem_m_getstat, + usbgem_m_start, + usbgem_m_stop, + usbgem_m_setpromisc, + usbgem_m_multicst, + usbgem_m_unicst, + usbgem_m_tx, +#ifdef _SYS_MAC_PROVIDER_H +#ifdef MAC_VERSION_V1 + NULL, +#endif +#else + NULL, /* m_resources */ +#endif + usbgem_m_ioctl, + NULL, /* m_getcapab */ +#ifdef USBGEM_CONFIG_MAC_PROP + NULL, + NULL, + usbgem_m_setprop, + usbgem_m_getprop, +#endif +#ifdef MAC_VERSION_V1 + usbgem_m_propinfo, +#endif +}; + +static int +usbgem_m_start(void *arg) +{ + int ret; + int err; + struct usbgem_dev *dp = arg; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + err = EIO; + + rw_enter(&dp->dev_state_lock, RW_WRITER); + dp->nic_state = NIC_STATE_ONLINE; + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + err = 0; + goto x; + } + if (usbgem_mac_init(dp) != USB_SUCCESS) { + goto x; + } + + /* initialize rx filter state */ + sema_p(&dp->rxfilter_lock); + dp->mc_count = 0; + dp->mc_count_req = 0; + + bcopy(dp->dev_addr.ether_addr_octet, + dp->cur_addr.ether_addr_octet, ETHERADDRL); + dp->rxmode |= RXMODE_ENABLE; + + ret = usbgem_hal_set_rx_filter(dp); + sema_v(&dp->rxfilter_lock); + + if (ret != USB_SUCCESS) { + goto x; + } + + if (dp->mii_state == MII_STATE_LINKUP) { + /* setup media mode if the link have been up */ + if (usbgem_hal_set_media(dp) != USB_SUCCESS) { + goto x; + } + if (usbgem_mac_start(dp) != USB_SUCCESS) { + goto x; + } + } + + err = 0; +x: + rw_exit(&dp->dev_state_lock); + return (err); +} + +static void +usbgem_m_stop(void *arg) +{ + struct usbgem_dev *dp = arg; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* stop rx gracefully */ + rw_enter(&dp->dev_state_lock, RW_READER); + sema_p(&dp->rxfilter_lock); + dp->rxmode &= ~RXMODE_ENABLE; + + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + (void) usbgem_hal_set_rx_filter(dp); + } + sema_v(&dp->rxfilter_lock); + rw_exit(&dp->dev_state_lock); + + /* make the nic state inactive */ + rw_enter(&dp->dev_state_lock, RW_WRITER); + dp->nic_state = NIC_STATE_STOPPED; + + /* stop mac completely */ + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + (void) usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL); + } + rw_exit(&dp->dev_state_lock); +} + +static int +usbgem_m_multicst(void *arg, boolean_t add, const uint8_t *ep) +{ + int err; + int ret; + struct usbgem_dev *dp = arg; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_READER); + if (add) { + ret = usbgem_add_multicast(dp, ep); + } else { + ret = usbgem_remove_multicast(dp, ep); + } + rw_exit(&dp->dev_state_lock); + + err = 0; + if (ret != USB_SUCCESS) { +#ifdef GEM_CONFIG_FMA + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); +#endif + err = EIO; + } + + return (err); +} + +static int +usbgem_m_setpromisc(void *arg, boolean_t on) +{ + int err; + struct usbgem_dev *dp = arg; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_READER); + + sema_p(&dp->rxfilter_lock); + if (on) { + dp->rxmode |= RXMODE_PROMISC; + } else { + dp->rxmode &= ~RXMODE_PROMISC; + } + + err = 0; + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + if (usbgem_hal_set_rx_filter(dp) != USB_SUCCESS) { + err = EIO; + } + } + sema_v(&dp->rxfilter_lock); + + rw_exit(&dp->dev_state_lock); + +#ifdef GEM_CONFIG_FMA + if (err != 0) { + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); + } +#endif + return (err); +} + +int +usbgem_m_getstat(void *arg, uint_t stat, uint64_t *valp) +{ + int ret; + uint64_t val; + struct usbgem_dev *dp = arg; + struct usbgem_stats *gstp = &dp->stats; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_READER); + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + rw_exit(&dp->dev_state_lock); + return (0); + } + ret = usbgem_hal_get_stats(dp); + rw_exit(&dp->dev_state_lock); + +#ifdef GEM_CONFIG_FMA + if (ret != USB_SUCCESS) { + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); + return (EIO); + } +#endif + + switch (stat) { + case MAC_STAT_IFSPEED: + val = usbgem_speed_value[dp->speed] *1000000ull; + break; + + case MAC_STAT_MULTIRCV: + val = gstp->rmcast; + break; + + case MAC_STAT_BRDCSTRCV: + val = gstp->rbcast; + break; + + case MAC_STAT_MULTIXMT: + val = gstp->omcast; + break; + + case MAC_STAT_BRDCSTXMT: + val = gstp->obcast; + break; + + case MAC_STAT_NORCVBUF: + val = gstp->norcvbuf + gstp->missed; + break; + + case MAC_STAT_IERRORS: + val = gstp->errrcv; + break; + + case MAC_STAT_NOXMTBUF: + val = gstp->noxmtbuf; + break; + + case MAC_STAT_OERRORS: + val = gstp->errxmt; + break; + + case MAC_STAT_COLLISIONS: + val = gstp->collisions; + break; + + case MAC_STAT_RBYTES: + val = gstp->rbytes; + break; + + case MAC_STAT_IPACKETS: + val = gstp->rpackets; + break; + + case MAC_STAT_OBYTES: + val = gstp->obytes; + break; + + case MAC_STAT_OPACKETS: + val = gstp->opackets; + break; + + case MAC_STAT_UNDERFLOWS: + val = gstp->underflow; + break; + + case MAC_STAT_OVERFLOWS: + val = gstp->overflow; + break; + + case ETHER_STAT_ALIGN_ERRORS: + val = gstp->frame; + break; + + case ETHER_STAT_FCS_ERRORS: + val = gstp->crc; + break; + + case ETHER_STAT_FIRST_COLLISIONS: + val = gstp->first_coll; + break; + + case ETHER_STAT_MULTI_COLLISIONS: + val = gstp->multi_coll; + break; + + case ETHER_STAT_SQE_ERRORS: + val = gstp->sqe; + break; + + case ETHER_STAT_DEFER_XMTS: + val = gstp->defer; + break; + + case ETHER_STAT_TX_LATE_COLLISIONS: + val = gstp->xmtlatecoll; + break; + + case ETHER_STAT_EX_COLLISIONS: + val = gstp->excoll; + break; + + case ETHER_STAT_MACXMT_ERRORS: + val = gstp->xmit_internal_err; + break; + + case ETHER_STAT_CARRIER_ERRORS: + val = gstp->nocarrier; + break; + + case ETHER_STAT_TOOLONG_ERRORS: + val = gstp->frame_too_long; + break; + + case ETHER_STAT_MACRCV_ERRORS: + val = gstp->rcv_internal_err; + break; + + case ETHER_STAT_XCVR_ADDR: + val = dp->mii_phy_addr; + break; + + case ETHER_STAT_XCVR_ID: + val = dp->mii_phy_id; + break; + + case ETHER_STAT_XCVR_INUSE: + val = usbgem_mac_xcvr_inuse(dp); + break; + + case ETHER_STAT_CAP_1000FDX: + val = (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); + break; + + case ETHER_STAT_CAP_1000HDX: + val = (dp->mii_xstatus & MII_XSTATUS_1000BASET) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX); + break; + + case ETHER_STAT_CAP_100FDX: + val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); + break; + + case ETHER_STAT_CAP_100HDX: + val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); + break; + + case ETHER_STAT_CAP_10FDX: + val = BOOLEAN(dp->mii_status & MII_STATUS_10_FD); + break; + + case ETHER_STAT_CAP_10HDX: + val = BOOLEAN(dp->mii_status & MII_STATUS_10); + break; + + case ETHER_STAT_CAP_ASMPAUSE: + val = dp->ugc.usbgc_flow_control > FLOW_CONTROL_SYMMETRIC; + break; + + case ETHER_STAT_CAP_PAUSE: + val = dp->ugc.usbgc_flow_control != FLOW_CONTROL_NONE; + break; + + case ETHER_STAT_CAP_AUTONEG: + val = BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); + break; + + case ETHER_STAT_ADV_CAP_1000FDX: + val = dp->anadv_1000fdx; + break; + + case ETHER_STAT_ADV_CAP_1000HDX: + val = dp->anadv_1000hdx; + break; + + case ETHER_STAT_ADV_CAP_100FDX: + val = dp->anadv_100fdx; + break; + + case ETHER_STAT_ADV_CAP_100HDX: + val = dp->anadv_100hdx; + break; + + case ETHER_STAT_ADV_CAP_10FDX: + val = dp->anadv_10fdx; + break; + + case ETHER_STAT_ADV_CAP_10HDX: + val = dp->anadv_10hdx; + break; + + case ETHER_STAT_ADV_CAP_ASMPAUSE: + val = dp->anadv_asmpause; + break; + + case ETHER_STAT_ADV_CAP_PAUSE: + val = dp->anadv_pause; + break; + + case ETHER_STAT_ADV_CAP_AUTONEG: + val = dp->anadv_autoneg; + break; + + case ETHER_STAT_LP_CAP_1000FDX: + val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL); + break; + + case ETHER_STAT_LP_CAP_1000HDX: + val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF); + break; + + case ETHER_STAT_LP_CAP_100FDX: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD); + break; + + case ETHER_STAT_LP_CAP_100HDX: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX); + break; + + case ETHER_STAT_LP_CAP_10FDX: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD); + break; + + case ETHER_STAT_LP_CAP_10HDX: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T); + break; + + case ETHER_STAT_LP_CAP_ASMPAUSE: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_ASM_DIR); + break; + + case ETHER_STAT_LP_CAP_PAUSE: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_PAUSE); + break; + + case ETHER_STAT_LP_CAP_AUTONEG: + val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); + break; + + case ETHER_STAT_LINK_ASMPAUSE: + val = BOOLEAN(dp->flow_control & 2); + break; + + case ETHER_STAT_LINK_PAUSE: + val = BOOLEAN(dp->flow_control & 1); + break; + + case ETHER_STAT_LINK_AUTONEG: + val = dp->anadv_autoneg && + BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); + break; + + case ETHER_STAT_LINK_DUPLEX: + val = (dp->mii_state == MII_STATE_LINKUP) ? + (dp->full_duplex ? 2 : 1) : 0; + break; + + case ETHER_STAT_TOOSHORT_ERRORS: + val = gstp->runt; + break; +#ifdef NEVER /* it doesn't make sense */ + case ETHER_STAT_CAP_REMFAULT: + val = B_TRUE; + break; + + case ETHER_STAT_ADV_REMFAULT: + val = dp->anadv_remfault; + break; +#endif + case ETHER_STAT_LP_REMFAULT: + val = BOOLEAN(dp->mii_lpable & MII_AN_ADVERT_REMFAULT); + break; + + case ETHER_STAT_JABBER_ERRORS: + val = gstp->jabber; + break; + + case ETHER_STAT_CAP_100T4: + val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); + break; + + case ETHER_STAT_ADV_CAP_100T4: + val = dp->anadv_100t4; + break; + + case ETHER_STAT_LP_CAP_100T4: + val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_T4); + break; + + default: +#if GEM_DEBUG_LEVEL > 2 + cmn_err(CE_WARN, + "%s: unrecognized parameter value = %d", + __func__, stat); +#endif + *valp = 0; + return (ENOTSUP); + } + + *valp = val; + + return (0); +} + +static int +usbgem_m_unicst(void *arg, const uint8_t *mac) +{ + int err; + struct usbgem_dev *dp = arg; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_READER); + + sema_p(&dp->rxfilter_lock); + bcopy(mac, dp->cur_addr.ether_addr_octet, ETHERADDRL); + dp->rxmode |= RXMODE_ENABLE; + + err = 0; + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + if (usbgem_hal_set_rx_filter(dp) != USB_SUCCESS) { + err = EIO; + } + } + sema_v(&dp->rxfilter_lock); + rw_exit(&dp->dev_state_lock); + +#ifdef GEM_CONFIG_FMA + if (err != 0) { + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); + } +#endif + return (err); +} + +/* + * usbgem_m_tx is used only for sending data packets into ethernet wire. + */ +static mblk_t * +usbgem_m_tx(void *arg, mblk_t *mp_head) +{ + int limit; + mblk_t *mp; + mblk_t *nmp; + uint32_t flags; + struct usbgem_dev *dp = arg; + + DPRINTF(4, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + mp = mp_head; + flags = 0; + + rw_enter(&dp->dev_state_lock, RW_READER); + + if (dp->mii_state != MII_STATE_LINKUP || + dp->mac_state != MAC_STATE_ONLINE) { + /* some nics hate to send packets during the link is down */ + for (; mp; mp = nmp) { + nmp = mp->b_next; + mp->b_next = NULL; + freemsg(mp); + } + goto x; + } + + ASSERT(dp->nic_state == NIC_STATE_ONLINE); + + limit = dp->tx_max_packets; + for (; limit-- && mp; mp = nmp) { + nmp = mp->b_next; + mp->b_next = NULL; + if (usbgem_send_common(dp, mp, + (limit == 0 && nmp) ? 1 : 0)) { + mp->b_next = nmp; + break; + } + } +#ifdef CONFIG_TX_LIMITER + if (mp == mp_head) { + /* no packets were sent, descrease allocation limit */ + mutex_enter(&dp->txlock); + dp->tx_max_packets = max(dp->tx_max_packets - 1, 1); + mutex_exit(&dp->txlock); + } +#endif +x: + rw_exit(&dp->dev_state_lock); + + return (mp); +} + +static void +usbgem_m_ioctl(void *arg, queue_t *wq, mblk_t *mp) +{ + struct usbgem_dev *dp = arg; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", + ((struct usbgem_dev *)arg)->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_READER); + usbgem_mac_ioctl((struct usbgem_dev *)arg, wq, mp); + rw_exit(&dp->dev_state_lock); +} + +static void +usbgem_gld3_init(struct usbgem_dev *dp, mac_register_t *macp) +{ + macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER; + macp->m_driver = dp; + macp->m_dip = dp->dip; + macp->m_src_addr = dp->dev_addr.ether_addr_octet; + macp->m_callbacks = &gem_m_callbacks; + macp->m_min_sdu = 0; + macp->m_max_sdu = dp->mtu; + + if (dp->misc_flag & USBGEM_VLAN) { + macp->m_margin = VTAG_SIZE; + } +} +#else +/* ============================================================== */ +/* + * GLDv2 interface + */ +/* ============================================================== */ +static int usbgem_gld_reset(gld_mac_info_t *); +static int usbgem_gld_start(gld_mac_info_t *); +static int usbgem_gld_stop(gld_mac_info_t *); +static int usbgem_gld_set_mac_address(gld_mac_info_t *, uint8_t *); +static int usbgem_gld_set_multicast(gld_mac_info_t *, uint8_t *, int); +static int usbgem_gld_set_promiscuous(gld_mac_info_t *, int); +static int usbgem_gld_get_stats(gld_mac_info_t *, struct gld_stats *); +static int usbgem_gld_send(gld_mac_info_t *, mblk_t *); +static int usbgem_gld_send_tagged(gld_mac_info_t *, mblk_t *, uint32_t); + +static int +usbgem_gld_reset(gld_mac_info_t *macinfo) +{ + int err; + struct usbgem_dev *dp; + + err = GLD_SUCCESS; + dp = (struct usbgem_dev *)macinfo->gldm_private; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_WRITER); + if (usbgem_mac_init(dp) != USB_SUCCESS) { + err = GLD_FAILURE; + goto x; + } + + dp->nic_state = NIC_STATE_INITIALIZED; + + /* setup media mode if the link have been up */ + if (dp->mii_state == MII_STATE_LINKUP) { + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + (void) usbgem_hal_set_media(dp); + } + } +x: + rw_exit(&dp->dev_state_lock); + return (err); +} + +static int +usbgem_gld_start(gld_mac_info_t *macinfo) +{ + int err; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_WRITER); + + dp->nic_state = NIC_STATE_ONLINE; + + if (dp->mii_state == MII_STATE_LINKUP) { + if (usbgem_mac_start(dp) != USB_SUCCESS) { + /* sema_v(&dp->mii_lock); */ + err = GLD_FAILURE; + goto x; + } + } + + /* + * XXX - don't call gld_linkstate() here, + * otherwise it cause recursive mutex call. + */ + err = GLD_SUCCESS; +x: + rw_exit(&dp->dev_state_lock); + + return (err); +} + +static int +usbgem_gld_stop(gld_mac_info_t *macinfo) +{ + int err = GLD_SUCCESS; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* try to stop rx gracefully */ + rw_enter(&dp->dev_state_lock, RW_READER); + sema_p(&dp->rxfilter_lock); + dp->rxmode &= ~RXMODE_ENABLE; + + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + (void) usbgem_hal_set_rx_filter(dp); + } + sema_v(&dp->rxfilter_lock); + rw_exit(&dp->dev_state_lock); + + /* make the nic state inactive */ + rw_enter(&dp->dev_state_lock, RW_WRITER); + dp->nic_state = NIC_STATE_STOPPED; + + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + if (usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL) + != USB_SUCCESS) { + err = GLD_FAILURE; + } + } + rw_exit(&dp->dev_state_lock); + + return (err); +} + +static int +usbgem_gld_set_multicast(gld_mac_info_t *macinfo, uint8_t *ep, int flag) +{ + int err; + int ret; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + rw_enter(&dp->dev_state_lock, RW_READER); + if (flag == GLD_MULTI_ENABLE) { + ret = usbgem_add_multicast(dp, ep); + } else { + ret = usbgem_remove_multicast(dp, ep); + } + rw_exit(&dp->dev_state_lock); + + err = GLD_SUCCESS; + if (ret != USB_SUCCESS) { +#ifdef GEM_CONFIG_FMA + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); +#endif + err = GLD_FAILURE; + } + return (err); +} + +static int +usbgem_gld_set_promiscuous(gld_mac_info_t *macinfo, int flag) +{ + boolean_t need_to_change = B_TRUE; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + sema_p(&dp->rxfilter_lock); + if (flag == GLD_MAC_PROMISC_NONE) { + dp->rxmode &= ~(RXMODE_PROMISC | RXMODE_ALLMULTI_REQ); + } else if (flag == GLD_MAC_PROMISC_MULTI) { + dp->rxmode |= RXMODE_ALLMULTI_REQ; + } else if (flag == GLD_MAC_PROMISC_PHYS) { + dp->rxmode |= RXMODE_PROMISC; + } else { + /* mode unchanged */ + need_to_change = B_FALSE; + } + + if (need_to_change) { + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + (void) usbgem_hal_set_rx_filter(dp); + } + } + sema_v(&dp->rxfilter_lock); + + return (GLD_SUCCESS); +} + +static int +usbgem_gld_set_mac_address(gld_mac_info_t *macinfo, uint8_t *mac) +{ + struct usbgem_dev *dp; + dp = (struct usbgem_dev *)macinfo->gldm_private; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + sema_p(&dp->rxfilter_lock); + bcopy(mac, dp->cur_addr.ether_addr_octet, ETHERADDRL); + dp->rxmode |= RXMODE_ENABLE; + + if (dp->mac_state != MAC_STATE_DISCONNECTED) { + (void) usbgem_hal_set_rx_filter(dp); + } + sema_v(&dp->rxfilter_lock); + + return (GLD_SUCCESS); +} + +static int +usbgem_gld_get_stats(gld_mac_info_t *macinfo, struct gld_stats *gs) +{ + struct usbgem_dev *dp; + struct usbgem_stats *vs; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + if ((*dp->ugc.usbgc_get_stats)(dp) != USB_SUCCESS) { +#ifdef GEM_CONFIG_FMA + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); +#endif + return (USB_FAILURE); + } + + vs = &dp->stats; + + gs->glds_errxmt = vs->errxmt; + gs->glds_errrcv = vs->errrcv; + gs->glds_collisions = vs->collisions; + + gs->glds_excoll = vs->excoll; + gs->glds_defer = vs->defer; + gs->glds_frame = vs->frame; + gs->glds_crc = vs->crc; + + gs->glds_overflow = vs->overflow; /* fifo err,underrun,rbufovf */ + gs->glds_underflow = vs->underflow; + gs->glds_short = vs->runt; + gs->glds_missed = vs->missed; /* missed pkts while rbuf ovf */ + gs->glds_xmtlatecoll = vs->xmtlatecoll; + gs->glds_nocarrier = vs->nocarrier; + gs->glds_norcvbuf = vs->norcvbuf; /* OS resource exaust */ + gs->glds_intr = vs->intr; + + /* all before here must be kept in place for v0 compatibility */ + gs->glds_speed = usbgem_speed_value[dp->speed] * 1000000; + gs->glds_media = GLDM_PHYMII; + gs->glds_duplex = dp->full_duplex ? GLD_DUPLEX_FULL : GLD_DUPLEX_HALF; + + /* gs->glds_media_specific */ + gs->glds_dot3_first_coll = vs->first_coll; + gs->glds_dot3_multi_coll = vs->multi_coll; + gs->glds_dot3_sqe_error = 0; + gs->glds_dot3_mac_xmt_error = 0; + gs->glds_dot3_mac_rcv_error = 0; + gs->glds_dot3_frame_too_long = vs->frame_too_long; + + return (GLD_SUCCESS); +} + +static int +usbgem_gld_ioctl(gld_mac_info_t *macinfo, queue_t *wq, mblk_t *mp) +{ + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + usbgem_mac_ioctl(dp, wq, mp); + + return (GLD_SUCCESS); +} + +/* + * gem_gld_send is used only for sending data packets into ethernet wire. + */ +static int +usbgem_gld_send(gld_mac_info_t *macinfo, mblk_t *mp) +{ + int ret; + uint32_t flags = 0; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + /* nic state must be online of suspended */ + rw_enter(&dp->dev_state_lock, RW_READER); + + ASSERT(dp->nic_state == NIC_STATE_ONLINE); + ASSERT(mp->b_next == NULL); + + if (dp->mii_state != MII_STATE_LINKUP) { + /* Some nics hate to send packets while the link is down. */ + /* we discard the untransmitted packets silently */ + rw_exit(&dp->dev_state_lock); + + freemsg(mp); +#ifdef GEM_CONFIG_FMA + /* FIXME - should we ignore the error? */ + ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); +#endif + return (GLD_SUCCESS); + } + + ret = (usbgem_send_common(dp, mp, flags) == NULL) + ? GLD_SUCCESS : GLD_NORESOURCES; + rw_exit(&dp->dev_state_lock); + + return (ret); +} + +/* + * usbgem_gld_send is used only for sending data packets into ethernet wire. + */ +static int +usbgem_gld_send_tagged(gld_mac_info_t *macinfo, mblk_t *mp, uint32_t vtag) +{ + uint32_t flags; + struct usbgem_dev *dp; + + dp = (struct usbgem_dev *)macinfo->gldm_private; + + /* + * Some nics hate to send packets while the link is down. + */ + if (dp->mii_state != MII_STATE_LINKUP) { + /* we dicard the untransmitted packets silently */ + freemsg(mp); +#ifdef GEM_CONFIG_FMA + /* FIXME - should we ignore the error? */ + ddi_fm_service_impact(dp->dip, DDI_SERVICE_UNAFFECTED); +#endif + return (GLD_SUCCESS); + } +#ifdef notyet + flags = GLD_VTAG_TCI(vtag) << GEM_SEND_VTAG_SHIFT; +#endif + return ((usbgem_send_common(dp, mp, 0) == NULL) ? + GLD_SUCCESS : GLD_NORESOURCES); +} + +static void +usbgem_gld_init(struct usbgem_dev *dp, gld_mac_info_t *macinfo, char *ident) +{ + /* + * configure GLD + */ + macinfo->gldm_devinfo = dp->dip; + macinfo->gldm_private = (caddr_t)dp; + + macinfo->gldm_reset = usbgem_gld_reset; + macinfo->gldm_start = usbgem_gld_start; + macinfo->gldm_stop = usbgem_gld_stop; + macinfo->gldm_set_mac_addr = usbgem_gld_set_mac_address; + macinfo->gldm_send = usbgem_gld_send; + macinfo->gldm_set_promiscuous = usbgem_gld_set_promiscuous; + macinfo->gldm_get_stats = usbgem_gld_get_stats; + macinfo->gldm_ioctl = usbgem_gld_ioctl; + macinfo->gldm_set_multicast = usbgem_gld_set_multicast; + macinfo->gldm_intr = NULL; + macinfo->gldm_mctl = NULL; + + macinfo->gldm_ident = ident; + macinfo->gldm_type = DL_ETHER; + macinfo->gldm_minpkt = 0; + macinfo->gldm_maxpkt = dp->mtu; + macinfo->gldm_addrlen = ETHERADDRL; + macinfo->gldm_saplen = -2; + macinfo->gldm_ppa = ddi_get_instance(dp->dip); +#ifdef GLD_CAP_LINKSTATE + macinfo->gldm_capabilities = GLD_CAP_LINKSTATE; +#endif + macinfo->gldm_vendor_addr = dp->dev_addr.ether_addr_octet; + macinfo->gldm_broadcast_addr = usbgem_bcastaddr; +} +#endif /* USBGEM_CONFIG_GLDv3 */ + + +/* ======================================================================== */ +/* + * .conf interface + */ +/* ======================================================================== */ +void +usbgem_generate_macaddr(struct usbgem_dev *dp, uint8_t *mac) +{ + extern char hw_serial[]; + char *hw_serial_p; + int i; + uint64_t val; + uint64_t key; + + cmn_err(CE_NOTE, + "!%s: using temp ether address," + " do not use this for long time", + dp->name); + + /* prefer a fixed address for DHCP */ + hw_serial_p = &hw_serial[0]; + val = stoi(&hw_serial_p); + + key = 0; + for (i = 0; i < USBGEM_NAME_LEN; i++) { + if (dp->name[i] == 0) { + break; + } + key ^= dp->name[i]; + } + key ^= ddi_get_instance(dp->dip); + val ^= key << 32; + + /* generate a local address */ + mac[0] = 0x02; + mac[1] = (uint8_t)(val >> 32); + mac[2] = (uint8_t)(val >> 24); + mac[3] = (uint8_t)(val >> 16); + mac[4] = (uint8_t)(val >> 8); + mac[5] = (uint8_t)val; +} + +boolean_t +usbgem_get_mac_addr_conf(struct usbgem_dev *dp) +{ + char propname[32]; + char *valstr; + uint8_t mac[ETHERADDRL]; + char *cp; + int c; + int i; + int j; + uint8_t v; + uint8_t d; + uint8_t ored; + + DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + /* + * Get ethernet address from .conf file + */ + (void) sprintf(propname, "mac-addr"); + if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, dp->dip, + DDI_PROP_DONTPASS, propname, &valstr)) != DDI_PROP_SUCCESS) { + return (B_FALSE); + } + + if (strlen(valstr) != ETHERADDRL*3-1) { + goto syntax_err; + } + + cp = valstr; + j = 0; + ored = 0; + for (;;) { + v = 0; + for (i = 0; i < 2; i++) { + c = *cp++; + + if (c >= 'a' && c <= 'f') { + d = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + d = c - 'A' + 10; + } else if (c >= '0' && c <= '9') { + d = c - '0'; + } else { + goto syntax_err; + } + v = (v << 4) | d; + } + + mac[j++] = v; + ored |= v; + if (j == ETHERADDRL) { + /* done */ + break; + } + + c = *cp++; + if (c != ':') { + goto syntax_err; + } + } + + if (ored == 0) { + usbgem_generate_macaddr(dp, mac); + } + for (i = 0; i < ETHERADDRL; i++) { + dp->dev_addr.ether_addr_octet[i] = mac[i]; + } + ddi_prop_free(valstr); + return (B_TRUE); + +syntax_err: + cmn_err(CE_CONT, + "!%s: read mac addr: trying .conf: syntax err %s", + dp->name, valstr); + ddi_prop_free(valstr); + + return (B_FALSE); +} + +static void +usbgem_read_conf(struct usbgem_dev *dp) +{ + int val; + + DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + /* + * Get media mode infomation from .conf file + */ + dp->anadv_autoneg = usbgem_prop_get_int(dp, "adv_autoneg_cap", 1) != 0; + dp->anadv_1000fdx = usbgem_prop_get_int(dp, "adv_1000fdx_cap", 1) != 0; + dp->anadv_1000hdx = usbgem_prop_get_int(dp, "adv_1000hdx_cap", 1) != 0; + dp->anadv_100t4 = usbgem_prop_get_int(dp, "adv_100T4_cap", 1) != 0; + dp->anadv_100fdx = usbgem_prop_get_int(dp, "adv_100fdx_cap", 1) != 0; + dp->anadv_100hdx = usbgem_prop_get_int(dp, "adv_100hdx_cap", 1) != 0; + dp->anadv_10fdx = usbgem_prop_get_int(dp, "adv_10fdx_cap", 1) != 0; + dp->anadv_10hdx = usbgem_prop_get_int(dp, "adv_10hdx_cap", 1) != 0; + dp->anadv_1000t_ms = usbgem_prop_get_int(dp, "adv_1000t_ms", 0); + + if ((ddi_prop_exists(DDI_DEV_T_ANY, dp->dip, + DDI_PROP_DONTPASS, "full-duplex"))) { + dp->full_duplex = + usbgem_prop_get_int(dp, "full-duplex", 1) != 0; + dp->anadv_autoneg = B_FALSE; + if (dp->full_duplex) { + dp->anadv_1000hdx = B_FALSE; + dp->anadv_100hdx = B_FALSE; + dp->anadv_10hdx = B_FALSE; + } else { + dp->anadv_1000fdx = B_FALSE; + dp->anadv_100fdx = B_FALSE; + dp->anadv_10fdx = B_FALSE; + } + } + + if ((val = usbgem_prop_get_int(dp, "speed", 0)) > 0) { + dp->anadv_autoneg = B_FALSE; + switch (val) { + case 1000: + dp->speed = USBGEM_SPD_1000; + dp->anadv_100t4 = B_FALSE; + dp->anadv_100fdx = B_FALSE; + dp->anadv_100hdx = B_FALSE; + dp->anadv_10fdx = B_FALSE; + dp->anadv_10hdx = B_FALSE; + break; + case 100: + dp->speed = USBGEM_SPD_100; + dp->anadv_1000fdx = B_FALSE; + dp->anadv_1000hdx = B_FALSE; + dp->anadv_10fdx = B_FALSE; + dp->anadv_10hdx = B_FALSE; + break; + case 10: + dp->speed = USBGEM_SPD_10; + dp->anadv_1000fdx = B_FALSE; + dp->anadv_1000hdx = B_FALSE; + dp->anadv_100t4 = B_FALSE; + dp->anadv_100fdx = B_FALSE; + dp->anadv_100hdx = B_FALSE; + break; + default: + cmn_err(CE_WARN, + "!%s: property %s: illegal value:%d", + dp->name, "speed", val); + dp->anadv_autoneg = B_TRUE; + break; + } + } + val = usbgem_prop_get_int(dp, + "adv_pause", dp->ugc.usbgc_flow_control & 1); + val |= usbgem_prop_get_int(dp, + "adv_asmpause", BOOLEAN(dp->ugc.usbgc_flow_control & 2)) << 1; + if (val > FLOW_CONTROL_RX_PAUSE || val < FLOW_CONTROL_NONE) { + cmn_err(CE_WARN, + "!%s: property %s: illegal value:%d", + dp->name, "flow-control", val); + } else { + val = min(val, dp->ugc.usbgc_flow_control); + } + dp->anadv_pause = BOOLEAN(val & 1); + dp->anadv_asmpause = BOOLEAN(val & 2); + + dp->mtu = usbgem_prop_get_int(dp, "mtu", dp->mtu); + dp->txthr = usbgem_prop_get_int(dp, "txthr", dp->txthr); + dp->rxthr = usbgem_prop_get_int(dp, "rxthr", dp->rxthr); + dp->txmaxdma = usbgem_prop_get_int(dp, "txmaxdma", dp->txmaxdma); + dp->rxmaxdma = usbgem_prop_get_int(dp, "rxmaxdma", dp->rxmaxdma); +#ifdef GEM_CONFIG_POLLING + dp->poll_pkt_delay = + usbgem_prop_get_int(dp, "pkt_delay", dp->poll_pkt_delay); + + dp->max_poll_interval[GEM_SPD_10] = + usbgem_prop_get_int(dp, "max_poll_interval_10", + dp->max_poll_interval[GEM_SPD_10]); + dp->max_poll_interval[GEM_SPD_100] = + usbgem_prop_get_int(dp, "max_poll_interval_100", + dp->max_poll_interval[GEM_SPD_100]); + dp->max_poll_interval[GEM_SPD_1000] = + usbgem_prop_get_int(dp, "max_poll_interval_1000", + dp->max_poll_interval[GEM_SPD_1000]); + + dp->min_poll_interval[GEM_SPD_10] = + usbgem_prop_get_int(dp, "min_poll_interval_10", + dp->min_poll_interval[GEM_SPD_10]); + dp->min_poll_interval[GEM_SPD_100] = + usbgem_prop_get_int(dp, "min_poll_interval_100", + dp->min_poll_interval[GEM_SPD_100]); + dp->min_poll_interval[GEM_SPD_1000] = + usbgem_prop_get_int(dp, "min_poll_interval_1000", + dp->min_poll_interval[GEM_SPD_1000]); +#endif +} + +/* + * usbem kstat support + */ +#ifndef GEM_CONFIG_GLDv3 +/* kstat items based from dmfe driver */ + +struct usbgem_kstat_named { + struct kstat_named ks_xcvr_addr; + struct kstat_named ks_xcvr_id; + struct kstat_named ks_xcvr_inuse; + struct kstat_named ks_link_up; + struct kstat_named ks_link_duplex; /* 0:unknwon, 1:half, 2:full */ + struct kstat_named ks_cap_1000fdx; + struct kstat_named ks_cap_1000hdx; + struct kstat_named ks_cap_100fdx; + struct kstat_named ks_cap_100hdx; + struct kstat_named ks_cap_10fdx; + struct kstat_named ks_cap_10hdx; +#ifdef NEVER + struct kstat_named ks_cap_remfault; +#endif + struct kstat_named ks_cap_autoneg; + + struct kstat_named ks_adv_cap_1000fdx; + struct kstat_named ks_adv_cap_1000hdx; + struct kstat_named ks_adv_cap_100fdx; + struct kstat_named ks_adv_cap_100hdx; + struct kstat_named ks_adv_cap_10fdx; + struct kstat_named ks_adv_cap_10hdx; +#ifdef NEVER + struct kstat_named ks_adv_cap_remfault; +#endif + struct kstat_named ks_adv_cap_autoneg; + struct kstat_named ks_lp_cap_1000fdx; + struct kstat_named ks_lp_cap_1000hdx; + struct kstat_named ks_lp_cap_100fdx; + struct kstat_named ks_lp_cap_100hdx; + struct kstat_named ks_lp_cap_10fdx; + struct kstat_named ks_lp_cap_10hdx; + struct kstat_named ks_lp_cap_remfault; + struct kstat_named ks_lp_cap_autoneg; +}; + +static int +usbgem_kstat_update(kstat_t *ksp, int rw) +{ + struct usbgem_kstat_named *knp; + struct usbgem_dev *dp = (struct usbgem_dev *)ksp->ks_private; + + if (rw != KSTAT_READ) { + return (0); + } + + knp = (struct usbgem_kstat_named *)ksp->ks_data; + + knp->ks_xcvr_addr.value.ul = dp->mii_phy_addr; + knp->ks_xcvr_id.value.ul = dp->mii_phy_id; + knp->ks_xcvr_inuse.value.ul = usbgem_mac_xcvr_inuse(dp); + knp->ks_link_up.value.ul = dp->mii_state == MII_STATE_LINKUP; + knp->ks_link_duplex.value.ul = + (dp->mii_state == MII_STATE_LINKUP) ? + (dp->full_duplex ? 2 : 1) : 0; + + knp->ks_cap_1000fdx.value.ul = + (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); + knp->ks_cap_1000hdx.value.ul = + (dp->mii_xstatus & MII_XSTATUS_1000BASET) || + (dp->mii_xstatus & MII_XSTATUS_1000BASEX); + knp->ks_cap_100fdx.value.ul = + BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); + knp->ks_cap_100hdx.value.ul = + BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); + knp->ks_cap_10fdx.value.ul = + BOOLEAN(dp->mii_status & MII_STATUS_10_FD); + knp->ks_cap_10hdx.value.ul = + BOOLEAN(dp->mii_status & MII_STATUS_10); +#ifdef NEVER + knp->ks_cap_remfault.value.ul = B_TRUE; +#endif + knp->ks_cap_autoneg.value.ul = + BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); + + knp->ks_adv_cap_1000fdx.value.ul = dp->anadv_1000fdx; + knp->ks_adv_cap_1000hdx.value.ul = dp->anadv_1000hdx; + knp->ks_adv_cap_100fdx.value.ul = dp->anadv_100fdx; + knp->ks_adv_cap_100hdx.value.ul = dp->anadv_100hdx; + knp->ks_adv_cap_10fdx.value.ul = dp->anadv_10fdx; + knp->ks_adv_cap_10hdx.value.ul = dp->anadv_10hdx; +#ifdef NEVER + knp->ks_adv_cap_remfault.value.ul = 0; +#endif + knp->ks_adv_cap_autoneg.value.ul = dp->anadv_autoneg; + + knp->ks_lp_cap_1000fdx.value.ul = + BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL); + knp->ks_lp_cap_1000hdx.value.ul = + BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF); + knp->ks_lp_cap_100fdx.value.ul = + BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD); + knp->ks_lp_cap_100hdx.value.ul = + BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX); + knp->ks_lp_cap_10fdx.value.ul = + BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD); + knp->ks_lp_cap_10hdx.value.ul = + BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T); + knp->ks_lp_cap_remfault.value.ul = + BOOLEAN(dp->mii_exp & MII_AN_EXP_PARFAULT); + knp->ks_lp_cap_autoneg.value.ul = + BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); + + return (0); +} + + +static int +usbgem_kstat_init(struct usbgem_dev *dp) +{ + int i; + kstat_t *ksp; + struct usbgem_kstat_named *knp; + + ksp = kstat_create( + (char *)ddi_driver_name(dp->dip), ddi_get_instance(dp->dip), + "mii", "net", KSTAT_TYPE_NAMED, + sizeof (*knp) / sizeof (knp->ks_xcvr_addr), 0); + + if (ksp == NULL) { + cmn_err(CE_WARN, "%s: %s() for mii failed", + dp->name, __func__); + return (USB_FAILURE); + } + + knp = (struct usbgem_kstat_named *)ksp->ks_data; + + kstat_named_init(&knp->ks_xcvr_addr, "xcvr_addr", + KSTAT_DATA_INT32); + kstat_named_init(&knp->ks_xcvr_id, "xcvr_id", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_xcvr_inuse, "xcvr_inuse", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_link_up, "link_up", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_link_duplex, "link_duplex", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_cap_1000fdx, "cap_1000fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_cap_1000hdx, "cap_1000hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_cap_100fdx, "cap_100fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_cap_100hdx, "cap_100hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_cap_10fdx, "cap_10fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_cap_10hdx, "cap_10hdx", + KSTAT_DATA_UINT32); +#ifdef NEVER + kstat_named_init(&knp->ks_cap_remfault, "cap_rem_fault", + KSTAT_DATA_UINT32); +#endif + kstat_named_init(&knp->ks_cap_autoneg, "cap_autoneg", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_adv_cap_1000fdx, "adv_cap_1000fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_adv_cap_1000hdx, "adv_cap_1000hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_adv_cap_100fdx, "adv_cap_100fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_adv_cap_100hdx, "adv_cap_100hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_adv_cap_10fdx, "adv_cap_10fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_adv_cap_10hdx, "adv_cap_10hdx", + KSTAT_DATA_UINT32); +#ifdef NEVER + kstat_named_init(&knp->ks_adv_cap_remfault, "adv_rem_fault", + KSTAT_DATA_UINT32); +#endif + kstat_named_init(&knp->ks_adv_cap_autoneg, "adv_cap_autoneg", + KSTAT_DATA_UINT32); + + kstat_named_init(&knp->ks_lp_cap_1000fdx, "lp_cap_1000fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_1000hdx, "lp_cap_1000hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_100fdx, "lp_cap_100fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_100hdx, "lp_cap_100hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_10fdx, "lp_cap_10fdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_10hdx, "lp_cap_10hdx", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_remfault, "lp_cap_rem_fault", + KSTAT_DATA_UINT32); + kstat_named_init(&knp->ks_lp_cap_autoneg, "lp_cap_autoneg", + KSTAT_DATA_UINT32); + + ksp->ks_private = (void *) dp; + ksp->ks_update = usbgem_kstat_update; + dp->ksp = ksp; + + kstat_install(ksp); + + return (USB_SUCCESS); +} +#endif /* GEM_CONFIG_GLDv3 */ +/* ======================================================================== */ +/* + * attach/detatch/usb support + */ +/* ======================================================================== */ +int +usbgem_ctrl_out(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + void *bp, int size) +{ + mblk_t *data; + usb_ctrl_setup_t setup; + usb_cr_t completion_reason; + usb_cb_flags_t cb_flags; + usb_flags_t flags; + int i; + int ret; + + DPRINTF(4, (CE_CONT, "!%s: %s " + "reqt:0x%02x req:0x%02x val:0x%04x ix:0x%04x len:0x%02x " + "bp:0x%p nic_state:%d", + dp->name, __func__, reqt, req, val, ix, len, bp, dp->nic_state)); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + return (USB_PIPE_ERROR); + } + + data = NULL; + if (size > 0) { + if ((data = allocb(size, 0)) == NULL) { + return (USB_FAILURE); + } + + bcopy(bp, data->b_rptr, size); + data->b_wptr = data->b_rptr + size; + } + + setup.bmRequestType = reqt; + setup.bRequest = req; + setup.wValue = val; + setup.wIndex = ix; + setup.wLength = len; + setup.attrs = 0; /* attributes */ + + for (i = usbgem_ctrl_retry; i > 0; i--) { + completion_reason = 0; + cb_flags = 0; + + ret = usb_pipe_ctrl_xfer_wait(DEFAULT_PIPE(dp), + &setup, &data, &completion_reason, &cb_flags, 0); + + if (ret == USB_SUCCESS) { + break; + } + if (i == 1) { + cmn_err(CE_WARN, + "!%s: %s failed: " + "reqt:0x%x req:0x%x val:0x%x ix:0x%x len:0x%x " + "ret:%d cr:%s(%d), cb_flags:0x%x %s", + dp->name, __func__, reqt, req, val, ix, len, + ret, usb_str_cr(completion_reason), + completion_reason, + cb_flags, + (i > 1) ? "retrying..." : "fatal"); + } + } + + if (data != NULL) { + freemsg(data); + } + + return (ret); +} + +int +usbgem_ctrl_in(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + void *bp, int size) +{ + mblk_t *data; + usb_ctrl_setup_t setup; + usb_cr_t completion_reason; + usb_cb_flags_t cb_flags; + int i; + int ret; + int reclen; + + DPRINTF(4, (CE_CONT, + "!%s: %s:" + " reqt:0x%02x req:0x%02x val:0x%04x ix:0x%04x len:0x%02x" + " bp:x%p mac_state:%d", + dp->name, __func__, reqt, req, val, ix, len, bp, dp->mac_state)); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + return (USB_PIPE_ERROR); + } + + data = NULL; + + setup.bmRequestType = reqt; + setup.bRequest = req; + setup.wValue = val; + setup.wIndex = ix; + setup.wLength = len; + setup.attrs = USB_ATTRS_AUTOCLEARING; /* XXX */ + + for (i = usbgem_ctrl_retry; i > 0; i--) { + completion_reason = 0; + cb_flags = 0; + ret = usb_pipe_ctrl_xfer_wait(DEFAULT_PIPE(dp), &setup, &data, + &completion_reason, &cb_flags, 0); + + if (ret == USB_SUCCESS) { + reclen = msgdsize(data); + bcopy(data->b_rptr, bp, min(reclen, size)); + break; + } + if (i == 1) { + cmn_err(CE_WARN, + "!%s: %s failed: " + "reqt:0x%x req:0x%x val:0x%x ix:0x%x len:0x%x " + "ret:%d cr:%s(%d) cb_flags:0x%x %s", + dp->name, __func__, + reqt, req, val, ix, len, + ret, usb_str_cr(completion_reason), + completion_reason, + cb_flags, + (i > 1) ? "retrying..." : "fatal"); + } + } + + if (data) { + freemsg(data); + } + + return (ret); +} + +int +usbgem_ctrl_out_val(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + uint32_t v) +{ + uint8_t buf[4]; + + /* convert to little endian from native byte order */ + switch (len) { + case 4: + buf[3] = v >> 24; + buf[2] = v >> 16; + /* fall thru */ + case 2: + buf[1] = v >> 8; + /* fall thru */ + case 1: + buf[0] = v; + } + + return (usbgem_ctrl_out(dp, reqt, req, val, ix, len, buf, len)); +} + +int +usbgem_ctrl_in_val(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + void *valp) +{ + uint8_t buf[4]; + uint_t v; + int err; + +#ifdef SANITY + bzero(buf, sizeof (buf)); +#endif + err = usbgem_ctrl_in(dp, reqt, req, val, ix, len, buf, len); + if (err == USB_SUCCESS) { + v = 0; + switch (len) { + case 4: + v |= buf[3] << 24; + v |= buf[2] << 16; + /* FALLTHROUGH */ + case 2: + v |= buf[1] << 8; + /* FALLTHROUGH */ + case 1: + v |= buf[0]; + } + + switch (len) { + case 4: + *(uint32_t *)valp = v; + break; + case 2: + *(uint16_t *)valp = v; + break; + case 1: + *(uint8_t *)valp = v; + break; + } + } + return (err); +} + +/* + * Attach / detach / disconnect / reconnect management + */ +static int +usbgem_open_pipes(struct usbgem_dev *dp) +{ + int i; + int ret; + int ifnum; + int alt; + usb_client_dev_data_t *reg_data; + usb_ep_data_t *ep_tree_node; + + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + ifnum = dp->ugc.usbgc_ifnum; + alt = dp->ugc.usbgc_alt; + + ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt, + 0, USB_EP_ATTR_BULK, USB_EP_DIR_IN); + if (ep_tree_node == NULL) { + cmn_err(CE_WARN, "!%s: %s: ep_bulkin is NULL", + dp->name, __func__); + goto err; + } + dp->ep_bulkin = &ep_tree_node->ep_descr; + + ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt, + 0, USB_EP_ATTR_BULK, USB_EP_DIR_OUT); + if (ep_tree_node == NULL) { + cmn_err(CE_WARN, "!%s: %s: ep_bulkout is NULL", + dp->name, __func__); + goto err; + } + dp->ep_bulkout = &ep_tree_node->ep_descr; + + ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt, + 0, USB_EP_ATTR_INTR, USB_EP_DIR_IN); + if (ep_tree_node) { + dp->ep_intr = &ep_tree_node->ep_descr; + } else { + /* don't care */ + DPRINTF(1, (CE_CONT, "!%s: %s: ep_intr is NULL", + dp->name, __func__)); + dp->ep_intr = NULL; + } + + /* XXX -- no need to open default pipe */ + + /* open bulk out pipe */ + bzero(&dp->policy_bulkout, sizeof (usb_pipe_policy_t)); + dp->policy_bulkout.pp_max_async_reqs = 1; + + if ((ret = usb_pipe_open(dp->dip, + dp->ep_bulkout, &dp->policy_bulkout, USB_FLAGS_SLEEP, + &dp->bulkout_pipe)) != USB_SUCCESS) { + cmn_err(CE_WARN, + "!%s: %s: err:%x: failed to open bulk-out pipe", + dp->name, __func__, ret); + dp->bulkout_pipe = NULL; + goto err; + } + DPRINTF(1, (CE_CONT, "!%s: %s: bulkout_pipe opened successfully", + dp->name, __func__)); + + /* open bulk in pipe */ + bzero(&dp->policy_bulkin, sizeof (usb_pipe_policy_t)); + dp->policy_bulkin.pp_max_async_reqs = 1; + if ((ret = usb_pipe_open(dp->dip, + dp->ep_bulkin, &dp->policy_bulkin, USB_FLAGS_SLEEP, + &dp->bulkin_pipe)) != USB_SUCCESS) { + cmn_err(CE_WARN, + "!%s: %s: ret:%x failed to open bulk-in pipe", + dp->name, __func__, ret); + dp->bulkin_pipe = NULL; + goto err; + } + DPRINTF(1, (CE_CONT, "!%s: %s: bulkin_pipe opened successfully", + dp->name, __func__)); + + if (dp->ep_intr) { + /* open interrupt pipe */ + bzero(&dp->policy_interrupt, sizeof (usb_pipe_policy_t)); + dp->policy_interrupt.pp_max_async_reqs = 1; + if ((ret = usb_pipe_open(dp->dip, dp->ep_intr, + &dp->policy_interrupt, USB_FLAGS_SLEEP, + &dp->intr_pipe)) != USB_SUCCESS) { + cmn_err(CE_WARN, + "!%s: %s: ret:%x failed to open interrupt pipe", + dp->name, __func__, ret); + dp->intr_pipe = NULL; + goto err; + } + } + DPRINTF(1, (CE_CONT, "!%s: %s: intr_pipe opened successfully", + dp->name, __func__)); + + return (USB_SUCCESS); + +err: + if (dp->bulkin_pipe) { + usb_pipe_close(dp->dip, + dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0); + dp->bulkin_pipe = NULL; + } + if (dp->bulkout_pipe) { + usb_pipe_close(dp->dip, + dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0); + dp->bulkout_pipe = NULL; + } + if (dp->intr_pipe) { + usb_pipe_close(dp->dip, + dp->intr_pipe, USB_FLAGS_SLEEP, NULL, 0); + dp->intr_pipe = NULL; + } + + return (USB_FAILURE); +} + +static int +usbgem_close_pipes(struct usbgem_dev *dp) +{ + DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); + + if (dp->intr_pipe) { + usb_pipe_close(dp->dip, + dp->intr_pipe, USB_FLAGS_SLEEP, NULL, 0); + dp->intr_pipe = NULL; + } + DPRINTF(1, (CE_CONT, "!%s: %s: 1", dp->name, __func__)); + + ASSERT(dp->bulkin_pipe); + usb_pipe_close(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0); + dp->bulkin_pipe = NULL; + DPRINTF(1, (CE_CONT, "!%s: %s: 2", dp->name, __func__)); + + ASSERT(dp->bulkout_pipe); + usb_pipe_close(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0); + dp->bulkout_pipe = NULL; + DPRINTF(1, (CE_CONT, "!%s: %s: 3", dp->name, __func__)); + + return (USB_SUCCESS); +} + +#define FREEZE_GRACEFUL (B_TRUE) +#define FREEZE_NO_GRACEFUL (B_FALSE) +static int +usbgem_freeze_device(struct usbgem_dev *dp, boolean_t graceful) +{ + DPRINTF(0, (CE_NOTE, "!%s: %s: called", dp->name, __func__)); + + /* stop nic activity */ + (void) usbgem_mac_stop(dp, MAC_STATE_DISCONNECTED, graceful); + + /* + * Here we free all memory resource allocated, because it will + * cause to panic the system that we free usb_bulk_req objects + * during the usb device is disconnected. + */ + (void) usbgem_free_memory(dp); + + return (USB_SUCCESS); +} + +static int +usbgem_disconnect_cb(dev_info_t *dip) +{ + int ret; + struct usbgem_dev *dp; + + dp = USBGEM_GET_DEV(dip); + + cmn_err(CE_NOTE, "!%s: the usb device was disconnected (dp=%p)", + dp->name, dp); + + /* start serialize */ + rw_enter(&dp->dev_state_lock, RW_WRITER); + + ret = usbgem_freeze_device(dp, 0); + + /* end of serialize */ + rw_exit(&dp->dev_state_lock); + + return (ret); +} + +static int +usbgem_recover_device(struct usbgem_dev *dp) +{ + int err; + + DPRINTF(0, (CE_NOTE, "!%s: %s: called", dp->name, __func__)); + + err = USB_SUCCESS; + + /* reinitialize the usb connection */ + usbgem_close_pipes(dp); + if ((err = usbgem_open_pipes(dp)) != USB_SUCCESS) { + goto x; + } + + /* initialize nic state */ + dp->mac_state = MAC_STATE_STOPPED; + dp->mii_state = MII_STATE_UNKNOWN; + + /* allocate memory resources again */ + if ((err = usbgem_alloc_memory(dp)) != USB_SUCCESS) { + goto x; + } + + /* restart nic and recover state */ + (void) usbgem_restart_nic(dp); + + usbgem_mii_init(dp); + + /* kick potentially stopped house keeping thread */ + cv_signal(&dp->link_watcher_wait_cv); +x: + return (err); +} + +static int +usbgem_reconnect_cb(dev_info_t *dip) +{ + int err = USB_SUCCESS; + struct usbgem_dev *dp; + + dp = USBGEM_GET_DEV(dip); + DPRINTF(0, (CE_CONT, "!%s: dp=%p", ddi_get_name(dip), dp)); +#ifdef notdef + /* check device changes after disconnect */ + if (usb_check_same_device(dp->dip, NULL, USB_LOG_L2, -1, + USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) { + cmn_err(CE_CONT, + "!%s: no or different device installed", dp->name); + return (DDI_SUCCESS); + } +#endif + cmn_err(CE_NOTE, "%s: the usb device was reconnected", dp->name); + + /* start serialize */ + rw_enter(&dp->dev_state_lock, RW_WRITER); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + err = usbgem_recover_device(dp); + } + + /* end of serialize */ + rw_exit(&dp->dev_state_lock); + + return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); +} + +int +usbgem_suspend(dev_info_t *dip) +{ + int err = USB_SUCCESS; + struct usbgem_dev *dp; + + dp = USBGEM_GET_DEV(dip); + + DPRINTF(0, (CE_CONT, "!%s: %s: callded", dp->name, __func__)); + + /* start serialize */ + rw_enter(&dp->dev_state_lock, RW_WRITER); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + err = usbgem_freeze_device(dp, STOP_GRACEFUL); + } + + /* end of serialize */ + rw_exit(&dp->dev_state_lock); + + return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); +} + +int +usbgem_resume(dev_info_t *dip) +{ + int err = USB_SUCCESS; + struct usbgem_dev *dp; + + dp = USBGEM_GET_DEV(dip); + + DPRINTF(0, (CE_CONT, "!%s: %s: callded", dp->name, __func__)); +#ifdef notdef + /* check device changes after disconnect */ + if (usb_check_same_device(dp->dip, NULL, USB_LOG_L2, -1, + USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) { + cmn_err(CE_CONT, + "!%s: no or different device installed", dp->name); + return (DDI_SUCCESS); + } +#endif + /* start serialize */ + rw_enter(&dp->dev_state_lock, RW_WRITER); + + if (dp->mac_state == MAC_STATE_DISCONNECTED) { + err = usbgem_recover_device(dp); + } + + /* end of serialize */ + rw_exit(&dp->dev_state_lock); + + return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); +} + +#define USBGEM_LOCAL_DATA_SIZE(gc) \ + (sizeof (struct usbgem_dev) + USBGEM_MCALLOC) + +struct usbgem_dev * +usbgem_do_attach(dev_info_t *dip, + struct usbgem_conf *gc, void *lp, int lmsize) +{ + struct usbgem_dev *dp; + int i; +#ifdef USBGEM_CONFIG_GLDv3 + mac_register_t *macp = NULL; +#else + gld_mac_info_t *macinfo; + void *tmp; +#endif + int ret; + int unit; + int err; + + unit = ddi_get_instance(dip); + + DPRINTF(2, (CE_CONT, "!usbgem%d: %s: called", unit, __func__)); + + /* + * Allocate soft data structure + */ + dp = kmem_zalloc(USBGEM_LOCAL_DATA_SIZE(gc), KM_SLEEP); + if (dp == NULL) { +#ifndef USBGEM_CONFIG_GLDv3 + gld_mac_free(macinfo); +#endif + return (NULL); + } +#ifdef USBGEM_CONFIG_GLDv3 + if ((macp = mac_alloc(MAC_VERSION)) == NULL) { + cmn_err(CE_WARN, "!gem%d: %s: mac_alloc failed", + unit, __func__); + return (NULL); + } +#else + macinfo = gld_mac_alloc(dip); + dp->macinfo = macinfo; +#endif + + /* link to private area */ + dp->private = lp; + dp->priv_size = lmsize; + dp->mc_list = (struct mcast_addr *)&dp[1]; + + dp->dip = dip; + bcopy(gc->usbgc_name, dp->name, USBGEM_NAME_LEN); + + /* + * register with usb service + */ + if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) { + cmn_err(CE_WARN, + "%s: %s: usb_client_attach failed", + dp->name, __func__); + goto err_free_private; + } + + if (usb_get_dev_data(dip, &dp->reg_data, + USB_PARSE_LVL_ALL, 0) != USB_SUCCESS) { + dp->reg_data = NULL; + goto err_unregister_client; + } +#ifdef USBGEM_DEBUG_LEVEL + usb_print_descr_tree(dp->dip, dp->reg_data); +#endif + + if (usbgem_open_pipes(dp) != USB_SUCCESS) { + /* failed to open pipes */ + cmn_err(CE_WARN, "!%s: %s: failed to open pipes", + dp->name, __func__); + goto err_unregister_client; + } + + /* + * Initialize mutexs and condition variables + */ + mutex_init(&dp->rxlock, NULL, MUTEX_DRIVER, NULL); + mutex_init(&dp->txlock, NULL, MUTEX_DRIVER, NULL); + cv_init(&dp->rx_drain_cv, NULL, CV_DRIVER, NULL); + cv_init(&dp->tx_drain_cv, NULL, CV_DRIVER, NULL); + rw_init(&dp->dev_state_lock, NULL, RW_DRIVER, NULL); + mutex_init(&dp->link_watcher_lock, NULL, MUTEX_DRIVER, NULL); + cv_init(&dp->link_watcher_wait_cv, NULL, CV_DRIVER, NULL); + sema_init(&dp->hal_op_lock, 1, NULL, SEMA_DRIVER, NULL); + sema_init(&dp->rxfilter_lock, 1, NULL, SEMA_DRIVER, NULL); + + /* + * Initialize configuration + */ + dp->ugc = *gc; + + dp->mtu = ETHERMTU; + dp->rxmode = 0; + dp->speed = USBGEM_SPD_10; /* default is 10Mbps */ + dp->full_duplex = B_FALSE; /* default is half */ + dp->flow_control = FLOW_CONTROL_NONE; + + dp->nic_state = NIC_STATE_STOPPED; + dp->mac_state = MAC_STATE_STOPPED; + dp->mii_state = MII_STATE_UNKNOWN; + + /* performance tuning parameters */ + dp->txthr = ETHERMAX; /* tx fifo threshoold */ + dp->txmaxdma = 16*4; /* tx max dma burst size */ + dp->rxthr = 128; /* rx fifo threshoold */ + dp->rxmaxdma = 16*4; /* rx max dma burst size */ + + /* + * Get media mode infomation from .conf file + */ + usbgem_read_conf(dp); + + /* rx_buf_len depend on MTU */ + dp->rx_buf_len = MAXPKTBUF(dp) + dp->ugc.usbgc_rx_header_len; + + /* + * Reset the chip + */ + if (usbgem_hal_reset_chip(dp) != USB_SUCCESS) { + cmn_err(CE_WARN, + "!%s: %s: failed to reset the usb device", + dp->name, __func__); + goto err_destroy_locks; + } + + /* + * HW dependant paremeter initialization + */ + if (usbgem_hal_attach_chip(dp) != USB_SUCCESS) { + cmn_err(CE_WARN, + "!%s: %s: failed to attach the usb device", + dp->name, __func__); + goto err_destroy_locks; + } + + /* allocate resources */ + if (usbgem_alloc_memory(dp) != USB_SUCCESS) { + goto err_destroy_locks; + } + + DPRINTF(0, (CE_CONT, + "!%s: %02x:%02x:%02x:%02x:%02x:%02x", + dp->name, + dp->dev_addr.ether_addr_octet[0], + dp->dev_addr.ether_addr_octet[1], + dp->dev_addr.ether_addr_octet[2], + dp->dev_addr.ether_addr_octet[3], + dp->dev_addr.ether_addr_octet[4], + dp->dev_addr.ether_addr_octet[5])); + + /* copy mac address */ + dp->cur_addr = dp->dev_addr; + + /* pre-calculated tx timeout in second for performance */ + dp->bulkout_timeout = + dp->ugc.usbgc_tx_timeout / drv_usectohz(1000*1000); + +#ifdef USBGEM_CONFIG_GLDv3 + usbgem_gld3_init(dp, macp); +#else + usbgem_gld_init(dp, macinfo, ident); +#endif + + /* Probe MII phy (scan phy) */ + dp->mii_lpable = 0; + dp->mii_advert = 0; + dp->mii_exp = 0; + dp->mii_ctl1000 = 0; + dp->mii_stat1000 = 0; + + dp->mii_status_ro = 0; + dp->mii_xstatus_ro = 0; + + if (usbgem_mii_probe(dp) != USB_SUCCESS) { + cmn_err(CE_WARN, "!%s: %s: mii_probe failed", + dp->name, __func__); + goto err_free_memory; + } + + /* mask unsupported abilities */ + dp->anadv_autoneg &= BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); + dp->anadv_1000fdx &= + BOOLEAN(dp->mii_xstatus & + (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASET_FD)); + dp->anadv_1000hdx &= + BOOLEAN(dp->mii_xstatus & + (MII_XSTATUS_1000BASEX | MII_XSTATUS_1000BASET)); + dp->anadv_100t4 &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); + dp->anadv_100fdx &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); + dp->anadv_100hdx &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); + dp->anadv_10fdx &= BOOLEAN(dp->mii_status & MII_STATUS_10_FD); + dp->anadv_10hdx &= BOOLEAN(dp->mii_status & MII_STATUS_10); + + if (usbgem_mii_init(dp) != USB_SUCCESS) { + cmn_err(CE_WARN, "!%s: %s: mii_init failed", + dp->name, __func__); + goto err_free_memory; + } + + /* + * initialize kstats including mii statistics + */ +#ifdef USBGEM_CONFIG_GLDv3 +#ifdef USBGEM_CONFIG_ND + usbgem_nd_setup(dp); +#endif +#else + if (usbgem_kstat_init(dp) != USB_SUCCESS) { + goto err_free_memory; + } +#endif + + /* + * Add interrupt to system. + */ +#ifdef USBGEM_CONFIG_GLDv3 + if (ret = mac_register(macp, &dp->mh)) { + cmn_err(CE_WARN, "!%s: mac_register failed, error:%d", + dp->name, ret); + goto err_release_stats; + } + mac_free(macp); + macp = NULL; +#else + /* gld_register will corrupts driver_private */ + tmp = ddi_get_driver_private(dip); + if (gld_register(dip, + (char *)ddi_driver_name(dip), macinfo) != DDI_SUCCESS) { + cmn_err(CE_WARN, "!%s: %s: gld_register failed", + dp->name, __func__); + ddi_set_driver_private(dip, tmp); + goto err_release_stats; + } + /* restore driver private */ + ddi_set_driver_private(dip, tmp); +#endif /* USBGEM_CONFIG_GLDv3 */ + if (usb_register_hotplug_cbs(dip, + usbgem_suspend, usbgem_resume) != USB_SUCCESS) { + cmn_err(CE_WARN, + "!%s: %s: failed to register hotplug cbs", + dp->name, __func__); + goto err_unregister_gld; + } + + /* reset mii and start mii link watcher */ + if (usbgem_mii_start(dp) != USB_SUCCESS) { + goto err_unregister_hotplug; + } + + /* start tx watchdow watcher */ + if (usbgem_tx_watcher_start(dp)) { + goto err_usbgem_mii_stop; + } + + ddi_set_driver_private(dip, (caddr_t)dp); + + DPRINTF(2, (CE_CONT, "!%s: %s: return: success", dp->name, __func__)); + + return (dp); + +err_usbgem_mii_stop: + usbgem_mii_stop(dp); + +err_unregister_hotplug: + usb_unregister_hotplug_cbs(dip); + +err_unregister_gld: +#ifdef USBGEM_CONFIG_GLDv3 + mac_unregister(dp->mh); +#else + gld_unregister(macinfo); +#endif + +err_release_stats: +#ifdef USBGEM_CONFIG_GLDv3 +#ifdef USBGEM_CONFIG_ND + /* release NDD resources */ + usbgem_nd_cleanup(dp); +#endif +#else + kstat_delete(dp->ksp); +#endif + +err_free_memory: + usbgem_free_memory(dp); + +err_destroy_locks: + cv_destroy(&dp->tx_drain_cv); + cv_destroy(&dp->rx_drain_cv); + mutex_destroy(&dp->txlock); + mutex_destroy(&dp->rxlock); + rw_destroy(&dp->dev_state_lock); + mutex_destroy(&dp->link_watcher_lock); + cv_destroy(&dp->link_watcher_wait_cv); + sema_destroy(&dp->hal_op_lock); + sema_destroy(&dp->rxfilter_lock); + +err_close_pipes: + (void) usbgem_close_pipes(dp); + +err_unregister_client: + usb_client_detach(dp->dip, dp->reg_data); + +err_free_private: +#ifdef USBGEM_CONFIG_GLDv3 + if (macp) { + mac_free(macp); + } +#else + gld_mac_free(macinfo); +#endif + kmem_free((caddr_t)dp, USBGEM_LOCAL_DATA_SIZE(gc)); + + return (NULL); +} + +int +usbgem_do_detach(dev_info_t *dip) +{ + struct usbgem_dev *dp; + + dp = USBGEM_GET_DEV(dip); + +#ifdef USBGEM_CONFIG_GLDv3 + /* unregister with gld v3 */ + if (mac_unregister(dp->mh) != DDI_SUCCESS) { + return (DDI_FAILURE); + } +#else + /* unregister with gld v2 */ + if (gld_unregister(dp->macinfo) != DDI_SUCCESS) { + return (DDI_FAILURE); + } +#endif + /* unregister with hotplug service */ + usb_unregister_hotplug_cbs(dip); + + /* stop tx watchdog watcher*/ + usbgem_tx_watcher_stop(dp); + + /* stop the link manager */ + usbgem_mii_stop(dp); + + /* unregister with usb service */ + (void) usbgem_free_memory(dp); + (void) usbgem_close_pipes(dp); + usb_client_detach(dp->dip, dp->reg_data); + dp->reg_data = NULL; + + /* unregister with kernel statistics */ +#ifdef USBGEM_CONFIG_GLDv3 +#ifdef USBGEM_CONFIG_ND + /* release ndd resources */ + usbgem_nd_cleanup(dp); +#endif +#else + /* destroy kstat objects */ + kstat_delete(dp->ksp); +#endif + + /* release locks and condition variables */ + mutex_destroy(&dp->txlock); + mutex_destroy(&dp->rxlock); + cv_destroy(&dp->tx_drain_cv); + cv_destroy(&dp->rx_drain_cv); + rw_destroy(&dp->dev_state_lock); + mutex_destroy(&dp->link_watcher_lock); + cv_destroy(&dp->link_watcher_wait_cv); + sema_destroy(&dp->hal_op_lock); + sema_destroy(&dp->rxfilter_lock); + + /* release basic memory resources */ +#ifndef USBGEM_CONFIG_GLDv3 + gld_mac_free(dp->macinfo); +#endif + kmem_free((caddr_t)(dp->private), dp->priv_size); + kmem_free((caddr_t)dp, USBGEM_LOCAL_DATA_SIZE(&dp->ugc)); + + DPRINTF(2, (CE_CONT, "!%s: %s: return: success", + ddi_driver_name(dip), __func__)); + + return (DDI_SUCCESS); +} + +int +usbgem_mod_init(struct dev_ops *dop, char *name) +{ +#ifdef USBGEM_CONFIG_GLDv3 + major_t major; + major = ddi_name_to_major(name); + if (major == DDI_MAJOR_T_NONE) { + return (DDI_FAILURE); + } + mac_init_ops(dop, name); +#endif + return (DDI_SUCCESS); +} + +void +usbgem_mod_fini(struct dev_ops *dop) +{ +#ifdef USBGEM_CONFIG_GLDv3 + mac_fini_ops(dop); +#endif +} + +int +usbgem_quiesce(dev_info_t *dip) +{ + struct usbgem_dev *dp; + + dp = USBGEM_GET_DEV(dip); + + ASSERT(dp != NULL); + + if (dp->mac_state != MAC_STATE_DISCONNECTED && + dp->mac_state != MAC_STATE_STOPPED) { + if (usbgem_hal_stop_chip(dp) != USB_SUCCESS) { + (void) usbgem_hal_reset_chip(dp); + } + } + + /* devo_quiesce() must return DDI_SUCCESS always */ + return (DDI_SUCCESS); +} diff --git a/usr/src/uts/common/io/usbgem/usbgem.h b/usr/src/uts/common/io/usbgem/usbgem.h new file mode 100644 index 0000000000..80b89a260e --- /dev/null +++ b/usr/src/uts/common/io/usbgem/usbgem.h @@ -0,0 +1,428 @@ +/* + * usbgem.h: General USB to Ethernet MAC driver framework + * @(#)usbgem.h 1.4 12/02/09 + * (C) Copyright 2003-2009 Masayuki Murayama KHF04453@nifty.ne.jp + */ + +#ifndef __USBGEM_H__ +#define __USBGEM_H__ + +#pragma ident "@(#)usbgem.h 1.4 12/02/09" + +#ifdef USBGEM_CONFIG_GLDv3 +#include <sys/mac.h> +#ifndef MAC_VERSION +#include <sys/mac_provider.h> +#endif +#include <sys/mac_ether.h> +#else +#include <sys/gld.h> +#endif /* GLDv3 */ + +/* + * Useful macros and typedefs + */ +#define USBGEM_NAME_LEN 32 + +#define USBGEM_TX_TIMEOUT (drv_usectohz(3*1000000)) +#define USBGEM_TX_TIMEOUT_INTERVAL (drv_usectohz(1*1000000)) +#define USBGEM_LINK_WATCH_INTERVAL (drv_usectohz(1*1000000)) + +/* general return code */ +#define USBGEM_SUCCESS 0 +#define USBGEM_FAILURE 1 + +/* return code of usbgem_tx_done */ +#define INTR_RESTART_TX 0x80000000U + +struct usbgem_stats { + uint32_t intr; + + uint32_t crc; + uint32_t errrcv; + uint32_t overflow; + uint32_t frame; + uint32_t missed; + uint32_t runt; + uint32_t frame_too_long; + uint32_t norcvbuf; + uint32_t sqe; + + uint32_t collisions; + uint32_t first_coll; + uint32_t multi_coll; + uint32_t excoll; + uint32_t xmit_internal_err; + uint32_t nocarrier; + uint32_t defer; + uint32_t errxmt; + uint32_t underflow; + uint32_t xmtlatecoll; + uint32_t noxmtbuf; + uint32_t jabber; + + + uint64_t rbytes; + uint64_t obytes; + uint64_t rpackets; + uint64_t opackets; + uint32_t rbcast; + uint32_t obcast; + uint32_t rmcast; + uint32_t omcast; + uint32_t rcv_internal_err; +}; + +struct mcast_addr { + struct ether_addr addr; + uint32_t hash; +}; + +#define USBGEM_MAXMC 64 +#define USBGEM_MCALLOC (sizeof(struct mcast_addr) * USBGEM_MAXMC) + +#define SLOT(dp, n) ((n) % (dp)->ugc.usbgc_tx_list_max) + +/* + * mac soft state + */ +struct usbgem_dev { + dev_info_t *dip; +#ifdef USBGEM_CONFIG_GLDv3 + mac_handle_t mh; +#else + void *macinfo; /* opaque handle for upper layer */ +#endif + char name[USBGEM_NAME_LEN]; + + /* pointer to usb private data */ + usb_client_dev_data_t *reg_data; + + /* usb handles */ + usb_pipe_handle_t default_pipe; + usb_pipe_handle_t bulkin_pipe; + usb_pipe_handle_t bulkout_pipe; + usb_pipe_handle_t intr_pipe; + + /* usb endpoints */ + usb_ep_descr_t *ep_default; + usb_ep_descr_t *ep_bulkin; + usb_ep_descr_t *ep_bulkout; + usb_ep_descr_t *ep_intr; + + /* usb policies */ + usb_pipe_policy_t policy_default; + usb_pipe_policy_t policy_bulkin; + usb_pipe_policy_t policy_bulkout; + usb_pipe_policy_t policy_interrupt; + + /* MAC address information */ + struct ether_addr cur_addr; + struct ether_addr dev_addr; + + /* RX state and resource management */ + kmutex_t rxlock; + int rx_busy_cnt; + boolean_t rx_active; + kcondvar_t rx_drain_cv; + + /* RX buffer management */ + int rx_buf_len; + + /* TX state and resource management */ + kmutex_t txlock; + int tx_busy_cnt; + usb_bulk_req_t *tx_free_list; + kcondvar_t tx_drain_cv; + clock_t tx_start_time; + int bulkout_timeout; /* in second */ + int tx_max_packets; + int tx_seq_num; + int tx_intr_pended; + + /* NIC state from OS view */ + int nic_state; +#define NIC_STATE_UNKNOWN 0 +#define NIC_STATE_STOPPED 1 +#define NIC_STATE_INITIALIZED 2 +#define NIC_STATE_ONLINE 3 + + /* MAC state from hardware view */ + int mac_state; +#define MAC_STATE_DISCONNECTED 0 /* it includes suspended state too */ +#define MAC_STATE_STOPPED 1 /* powered up / buf not initialized */ +#define MAC_STATE_INITIALIZED 2 /* initialized */ +#define MAC_STATE_ONLINE 3 /* working correctly */ +#define MAC_STATE_ERROR 4 /* need to restart nic */ + + clock_t fatal_error; + + /* robustness: timer and watchdog */ + uint_t tx_watcher_stop; + kt_did_t tx_watcher_did; + kcondvar_t tx_watcher_cv; + kmutex_t tx_watcher_lock; + clock_t tx_watcher_timeout; + clock_t tx_watcher_interval; + + /* MII mamagement */ + boolean_t anadv_autoneg:1; + boolean_t anadv_1000fdx:1; + boolean_t anadv_1000hdx:1; + boolean_t anadv_100t4:1; + boolean_t anadv_100fdx:1; + boolean_t anadv_100hdx:1; + boolean_t anadv_10fdx:1; + boolean_t anadv_10hdx:1; + boolean_t anadv_1000t_ms:2; + boolean_t anadv_pause:1; + boolean_t anadv_asmpause:1; + boolean_t mii_advert_ro:1; + + boolean_t full_duplex:1; + int speed:3; +#define USBGEM_SPD_10 0 +#define USBGEM_SPD_100 1 +#define USBGEM_SPD_1000 2 +#define USBGEM_SPD_NUM 3 + unsigned int flow_control:2; +#define FLOW_CONTROL_NONE 0 +#define FLOW_CONTROL_SYMMETRIC 1 +#define FLOW_CONTROL_TX_PAUSE 2 +#define FLOW_CONTROL_RX_PAUSE 3 + + boolean_t mii_supress_msg:1; + + uint32_t mii_phy_id; + uint16_t mii_status; + uint16_t mii_advert; + uint16_t mii_lpable; + uint16_t mii_exp; + uint16_t mii_ctl1000; + uint16_t mii_stat1000; + uint16_t mii_xstatus; + int8_t mii_phy_addr; /* must be signed */ + + uint16_t mii_status_ro; + uint16_t mii_xstatus_ro; + + int mii_state; +#define MII_STATE_UNKNOWN 0 +#define MII_STATE_RESETTING 1 +#define MII_STATE_AUTONEGOTIATING 2 +#define MII_STATE_AN_DONE 3 +#define MII_STATE_MEDIA_SETUP 4 +#define MII_STATE_LINKUP 5 +#define MII_STATE_LINKDOWN 6 + + clock_t mii_last_check; /* in tick */ + clock_t mii_timer; /* in tick */ +#define MII_RESET_TIMEOUT drv_usectohz(1000*1000) +#define MII_AN_TIMEOUT drv_usectohz(5000*1000) +#define MII_LINKDOWN_TIMEOUT drv_usectohz(10000*1000) + + clock_t mii_interval; /* in tick */ + clock_t linkup_delay; /* in tick */ + + uint_t link_watcher_stop; + kt_did_t link_watcher_did; + kcondvar_t link_watcher_wait_cv; + kmutex_t link_watcher_lock; + + krwlock_t dev_state_lock; /* mac_state and nic_state */ + ksema_t hal_op_lock; /* serialize hw operations */ + ksema_t drv_op_lock; /* hotplug op lock */ + + /* multcast list */ + ksema_t rxfilter_lock; + int mc_count; + int mc_count_req; + struct mcast_addr *mc_list; + int rxmode; +#define RXMODE_PROMISC 0x01 +#define RXMODE_ALLMULTI_REQ 0x02 +#define RXMODE_MULTI_OVF 0x04 +#define RXMODE_ENABLE 0x08 +#define RXMODE_ALLMULTI (RXMODE_ALLMULTI_REQ | RXMODE_MULTI_OVF) +#define RXMODE_BITS \ + "\020" \ + "\004ENABLE" \ + "\003MULTI_OVF" \ + "\002ALLMULTI_REQ" \ + "\001PROMISC" + + /* statistcs */ + struct usbgem_stats stats; + + /* pointer to local structure */ + void *private; + int priv_size; + + /* configuration */ + struct usbgem_conf { + /* name */ + char usbgc_name[USBGEM_NAME_LEN]; + int usbgc_ppa; + + /* specification on usb */ + int usbgc_ifnum; /* interface number */ + int usbgc_alt; /* alternate */ + + /* specification on tx engine */ + int usbgc_tx_list_max; + + /* specification on rx engine */ + int usbgc_rx_header_len; + int usbgc_rx_list_max; + + /* time out parameters */ + clock_t usbgc_tx_timeout; + clock_t usbgc_tx_timeout_interval; + + /* flow control */ + int usbgc_flow_control; + + /* MII timeout parameters */ + clock_t usbgc_mii_linkdown_timeout; + clock_t usbgc_mii_link_watch_interval; + clock_t usbgc_mii_reset_timeout; + + clock_t usbgc_mii_an_watch_interval; + clock_t usbgc_mii_an_timeout; + clock_t usbgc_mii_an_wait; + clock_t usbgc_mii_an_delay; + + /* MII configuration */ + int usbgc_mii_addr_min; + int usbgc_mii_linkdown_action; + int usbgc_mii_linkdown_timeout_action; +#define MII_ACTION_NONE 0 +#define MII_ACTION_RESET 1 +#define MII_ACTION_RSA 2 + boolean_t usbgc_mii_dont_reset:1; + boolean_t usbgc_mii_an_oneshot:1; + boolean_t usbgc_mii_hw_link_detection:1; + boolean_t usbgc_mii_stop_mac_on_linkdown:1; + uint16_t usbgc_mii_an_cmd; + + /* I/O methods */ + + /* mac operation */ + int (*usbgc_attach_chip)(struct usbgem_dev *dp); + int (*usbgc_reset_chip)(struct usbgem_dev *dp); + int (*usbgc_init_chip)(struct usbgem_dev *dp); + int (*usbgc_start_chip)(struct usbgem_dev *dp); + int (*usbgc_stop_chip)(struct usbgem_dev *dp); + uint32_t (*usbgc_multicast_hash)(struct usbgem_dev *dp, + const uint8_t *); + int (*usbgc_set_rx_filter)(struct usbgem_dev *dp); + int (*usbgc_set_media)(struct usbgem_dev *dp); + int (*usbgc_get_stats)(struct usbgem_dev *dp); + void (*usbgc_interrupt)(struct usbgem_dev *dp, mblk_t *mp); + + /* packet manupilation */ + mblk_t *(*usbgc_tx_make_packet)(struct usbgem_dev *dp, mblk_t *mp); + mblk_t *(*usbgc_rx_make_packet)(struct usbgem_dev *dp, mblk_t *mp); + /* mii operations */ + int (*usbgc_mii_probe)(struct usbgem_dev *dp); + int (*usbgc_mii_init)(struct usbgem_dev *dp); + int (*usbgc_mii_config)(struct usbgem_dev *dp, int *errp); + uint16_t (*usbgc_mii_read)(struct usbgem_dev *dp, uint_t reg, int *errp); + void (*usbgc_mii_write)(struct usbgem_dev *dp, uint_t reg, uint16_t val, int *errp); + + /* jumbo frame */ + int usbgc_max_mtu; + int usbgc_default_mtu; + int usbgc_min_mtu; + } ugc; + + int misc_flag; +#define USBGEM_VLAN 0x0001 + timeout_id_t intr_watcher_id; + + /* buffer size */ + uint_t mtu; + + /* performance tuning parameters */ + uint_t txthr; /* tx fifo threshoold */ + uint_t txmaxdma; /* tx max dma burst size */ + uint_t rxthr; /* rx fifo threshoold */ + uint_t rxmaxdma; /* tx max dma burst size */ + + /* kstat stuff */ + kstat_t *ksp; + + /* ndd stuff */ + caddr_t nd_data_p; + caddr_t nd_arg_p; + +#ifdef USBGEM_DEBUG_LEVEL + int tx_cnt; +#endif +}; + +/* + * Exported functions + */ +int usbgem_ctrl_out(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + void *bp, int size); + +int usbgem_ctrl_in(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + void *bp, int size); + +int usbgem_ctrl_out_val(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + uint32_t v); + +int usbgem_ctrl_in_val(struct usbgem_dev *dp, + uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, + void *valp); + +void usbgem_generate_macaddr(struct usbgem_dev *, uint8_t *); +boolean_t usbgem_get_mac_addr_conf(struct usbgem_dev *); +int usbgem_mii_probe_default(struct usbgem_dev *); +int usbgem_mii_init_default(struct usbgem_dev *); +int usbgem_mii_config_default(struct usbgem_dev *, int *errp); +void usbgem_mii_update_link(struct usbgem_dev *); +void usbgem_restart_tx(struct usbgem_dev *); +boolean_t usbgem_tx_done(struct usbgem_dev *, int); +void usbgem_receive(struct usbgem_dev *); +struct usbgem_dev *usbgem_do_attach(dev_info_t *, + struct usbgem_conf *, void *, int); +int usbgem_do_detach(dev_info_t *); + +uint32_t usbgem_ether_crc_le(const uint8_t *addr); +uint32_t usbgem_ether_crc_be(const uint8_t *addr); + +int usbgem_resume(dev_info_t *); +int usbgem_suspend(dev_info_t *); +int usbgem_quiesce(dev_info_t *); + +#ifdef USBGEM_CONFIG_GLDv3 +#if DEVO_REV < 4 +#define USBGEM_STREAM_OPS(dev_ops, attach, detach) \ + DDI_DEFINE_STREAM_OPS(dev_ops, nulldev, nulldev, attach, detach, \ + nodev, NULL, D_MP, NULL) +#else +#define USBGEM_STREAM_OPS(dev_ops, attach, detach) \ + DDI_DEFINE_STREAM_OPS(dev_ops, nulldev, nulldev, attach, detach, \ + nodev, NULL, D_MP, NULL, usbgem_quiesce) +#endif +#else +#define usbgem_getinfo gld_getinfo +#define usbgem_open gld_open +#define usbgem_close gld_close +#define usbgem_wput gld_wput +#define usbgem_wsrv gld_wsrv +#define usbgem_rsrv gld_rsrv +#define usbgem_power NULL +#endif +int usbgem_mod_init(struct dev_ops *, char *); +void usbgem_mod_fini(struct dev_ops *); + +#define USBGEM_GET_DEV(dip) \ + ((struct usbgem_dev *)(ddi_get_driver_private(dip))) + +#endif /* __USBGEM_H__ */ diff --git a/usr/src/uts/common/io/usbgem/usbgem_mii.h b/usr/src/uts/common/io/usbgem/usbgem_mii.h new file mode 100644 index 0000000000..2b4176a340 --- /dev/null +++ b/usr/src/uts/common/io/usbgem/usbgem_mii.h @@ -0,0 +1,242 @@ +/* + * gem_mii.h: mii header for gem + * + * Copyright (c) 2002-2007 Masayuki Murayama. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the author nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +#pragma ident "@(#)gem_mii.h 1.4 07/11/30" + +/* + * gem_mii.h : MII registers + */ +#ifndef _GEM_MII_H_ +#define _GEM_MII_H_ + +#ifdef GEM_CONFIG_GLDv3 +#include <sys/miiregs.h> +#else +#define MII_CONTROL 0 +#define MII_STATUS 1 +#define MII_PHYIDH 2 +#define MII_PHYIDL 3 +#define MII_AN_ADVERT 4 +#define MII_AN_LPABLE 5 +#define MII_AN_EXPANSION 6 +#define MII_AN_NXTPGXMIT 7 +#endif /* GEM_CONFIG_GLDv3 */ + +#define MII_AN_LPANXT 8 +#define MII_MS_CONTROL 9 +#define MII_MS_STATUS 10 +#define MII_XSTATUS 15 + +/* for 1000BaseT support */ +#define MII_1000TC MII_MS_CONTROL +#define MII_1000TS MII_MS_STATUS +#ifndef GEM_CONFIG_GLDv3 +#define MII_CONTROL_RESET 0x8000 +#define MII_CONTROL_LOOPBACK 0x4000 +#define MII_CONTROL_100MB 0x2000 +#define MII_CONTROL_ANE 0x1000 +#define MII_CONTROL_PWRDN 0x0800 +#define MII_CONTROL_ISOLATE 0x0400 +#define MII_CONTROL_RSAN 0x0200 +#define MII_CONTROL_FDUPLEX 0x0100 +#define MII_CONTROL_COLTST 0x0080 +#endif /* !GEM_CONFIG_GLDv3 */ +#define MII_CONTROL_SPEED 0x2040 + +#define MII_CONTROL_10MB 0x0000 +#define MII_CONTROL_1000MB 0x0040 + +#define MII_CONTROL_BITS \ + "\020" \ + "\020RESET" \ + "\017LOOPBACK" \ + "\016100MB" \ + "\015ANE" \ + "\014PWRDN" \ + "\013ISOLATE" \ + "\012RSAN" \ + "\011FDUPLEX" \ + "\010COLTST" \ + "\0071000M" +#ifndef GEM_CONFIG_GLDv3 +#define MII_STATUS_100_BASE_T4 0x8000 +#define MII_STATUS_100_BASEX_FD 0x4000 +#define MII_STATUS_100_BASEX 0x2000 +#define MII_STATUS_10_FD 0x1000 +#define MII_STATUS_10 0x0800 +#define MII_STATUS_MFPRMBLSUPR 0x0040 +#define MII_STATUS_ANDONE 0x0020 +#define MII_STATUS_REMFAULT 0x0010 +#define MII_STATUS_CANAUTONEG 0x0008 +#define MII_STATUS_LINKUP 0x0004 +#define MII_STATUS_JABBERING 0x0002 +#define MII_STATUS_EXTENDED 0x0001 +#endif /* !GEM_CONFIG_GLDv3 */ +#define MII_STATUS_XSTATUS 0x0100 +#define MII_STATUS_100_BASE_T2_FD 0x0400 +#define MII_STATUS_100_BASE_T2 0x0200 + +#define MII_STATUS_ABILITY_TECH \ + (MII_STATUS_100_BASE_T4 | \ + MII_STATUS_100_BASEX_FD | \ + MII_STATUS_100_BASEX | \ + MII_STATUS_10 | \ + MII_STATUS_10_FD) + + +#define MII_STATUS_BITS \ + "\020" \ + "\020100_BASE_T4" \ + "\017100_BASEX_FD" \ + "\016100_BASEX" \ + "\01510_BASE_FD" \ + "\01410_BASE" \ + "\013100_BASE_T2_FD" \ + "\012100_BASE_T2" \ + "\011XSTATUS" \ + "\007MFPRMBLSUPR" \ + "\006ANDONE" \ + "\005REMFAULT" \ + "\004CANAUTONEG" \ + "\003LINKUP" \ + "\002JABBERING" \ + "\001EXTENDED" +#ifndef GEM_CONFIG_GLDv3 +#define MII_AN_ADVERT_NP 0x8000 +#define MII_AN_ADVERT_REMFAULT 0x2000 +#define MII_AN_ADVERT_SELECTOR 0x001f +#endif /* !GEM_CONFIG_GLDv3 */ + +#define MII_ABILITY_ASM_DIR 0x0800 /* for annex 28B */ +#ifndef MII_ABILITY_PAUSE +#define MII_ABILITY_PAUSE 0x0400 /* for IEEE 802.3x */ +#endif +#ifndef GEM_CONFIG_GLDv3 +#define MII_ABILITY_100BASE_T4 0x0200 +#define MII_ABILITY_100BASE_TX_FD 0x0100 +#define MII_ABILITY_100BASE_TX 0x0080 +#define MII_ABILITY_10BASE_T_FD 0x0040 +#define MII_ABILITY_10BASE_T 0x0020 +#endif /* !GEM_CONFIG_GLDv3 */ + +#define MII_AN_LPABLE_NP 0x8000 + +#define MII_ABILITY_TECH \ + (MII_ABILITY_100BASE_T4 | \ + MII_ABILITY_100BASE_TX_FD | \ + MII_ABILITY_100BASE_TX | \ + MII_ABILITY_10BASE_T | \ + MII_ABILITY_10BASE_T_FD) + +#define MII_ABILITY_ALL \ + (MII_AN_ADVERT_REMFAULT | \ + MII_ABILITY_ASM_DIR | \ + MII_ABILITY_PAUSE | \ + MII_ABILITY_TECH) + + +#define MII_ABILITY_BITS \ + "\020" \ + "\016REMFAULT" \ + "\014ASM_DIR" \ + "\013PAUSE" \ + "\012100BASE_T4" \ + "\011100BASE_TX_FD" \ + "\010100BASE_TX" \ + "\00710BASE_T_FD" \ + "\00610BASE_T" +#ifndef GEM_CONFIG_GLDv3 +#define MII_AN_EXP_PARFAULT 0x0010 +#define MII_AN_EXP_LPCANNXTP 0x0008 +#define MII_AN_EXP_CANNXTPP 0x0004 +#define MII_AN_EXP_PAGERCVD 0x0002 +#define MII_AN_EXP_LPCANAN 0x0001 +#endif /* !GEM_CONFIG_GLDv3 */ + +#define MII_AN_EXP_BITS \ + "\020" \ + "\005PARFAULT" \ + "\004LPCANNXTP" \ + "\003CANNXTPP" \ + "\002PAGERCVD" \ + "\001LPCANAN" + +#define MII_1000TC_TESTMODE 0xe000 +#define MII_1000TC_CFG_EN 0x1000 +#define MII_1000TC_CFG_VAL 0x0800 +#define MII_1000TC_PORTTYPE 0x0400 +#define MII_1000TC_ADV_FULL 0x0200 +#define MII_1000TC_ADV_HALF 0x0100 + +#define MII_1000TC_BITS \ + "\020" \ + "\015CFG_EN" \ + "\014CFG_VAL" \ + "\013PORTTYPE" \ + "\012FULL" \ + "\011HALF" + +#define MII_1000TS_CFG_FAULT 0x8000 +#define MII_1000TS_CFG_MASTER 0x4000 +#define MII_1000TS_LOCALRXOK 0x2000 +#define MII_1000TS_REMOTERXOK 0x1000 +#define MII_1000TS_LP_FULL 0x0800 +#define MII_1000TS_LP_HALF 0x0400 + +#define MII_1000TS_BITS \ + "\020" \ + "\020CFG_FAULT" \ + "\017CFG_MASTER" \ + "\014CFG_LOCALRXOK" \ + "\013CFG_REMOTERXOK" \ + "\012LP_FULL" \ + "\011LP_HALF" + +#define MII_XSTATUS_1000BASEX_FD 0x8000 +#define MII_XSTATUS_1000BASEX 0x4000 +#define MII_XSTATUS_1000BASET_FD 0x2000 +#define MII_XSTATUS_1000BASET 0x1000 + +#define MII_XSTATUS_BITS \ + "\020" \ + "\0201000BASEX_FD" \ + "\0171000BASEX" \ + "\0161000BASET_FD" \ + "\0151000BASET" + +#define MII_READ_CMD(p, r) \ + ((6<<(18+5+5)) | ((p)<<(18+5)) | ((r)<<18)) + +#define MII_WRITE_CMD(p, r, v) \ + ((5<<(18+5+5)) | ((p)<<(18+5)) | ((r)<<18) | (2 << 16) | (v)) + +#endif /* _GEM_MII_H_ */ diff --git a/usr/src/uts/common/io/vnd/frameio.c b/usr/src/uts/common/io/vnd/frameio.c new file mode 100644 index 0000000000..e4e700fa12 --- /dev/null +++ b/usr/src/uts/common/io/vnd/frameio.c @@ -0,0 +1,464 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright (c) 2014, Joyent, Inc. All rights reserved. + */ + +/* + * Frame I/O utility functions + */ + +#include <sys/frameio.h> + +#include <sys/file.h> +#include <sys/types.h> +#include <sys/kmem.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/stream.h> +#include <sys/strsun.h> +#include <sys/sysmacros.h> +#include <sys/inttypes.h> + +static kmem_cache_t *frameio_cache; + +int +frameio_init(void) +{ + frameio_cache = kmem_cache_create("frameio_cache", + sizeof (frameio_t) + sizeof (framevec_t) * FRAMEIO_NVECS_MAX, + 0, NULL, NULL, NULL, NULL, NULL, 0); + if (frameio_cache == NULL) + return (1); + + return (0); +} + +void +frameio_fini(void) +{ + if (frameio_cache != NULL) + kmem_cache_destroy(frameio_cache); +} + +frameio_t * +frameio_alloc(int kmflags) +{ + return (kmem_cache_alloc(frameio_cache, kmflags)); +} + +void +frameio_free(frameio_t *fio) +{ + return (kmem_cache_free(frameio_cache, fio)); +} + +/* + * Ensure that we don't see any garbage in the framevecs that we're nominally + * supposed to work with. Specifically we want to make sure that the buflen and + * the address are not zero. + */ +static int +frameio_hdr_check_vecs(frameio_t *fio) +{ + int i; + for (i = 0; i < fio->fio_nvecs; i++) + if (fio->fio_vecs[i].fv_buf == NULL || + fio->fio_vecs[i].fv_buflen == 0) + return (EINVAL); + + return (0); +} + +/* + * We have to copy in framevec32_t's. To work around the data model issues and + * trying not to copy memory we first copy in the framevec32_t data into the + * standard fio_vec space. Next we work backwards copying a given framevec32_t + * to a temporaory framevec_t and then overwrite the frameio_t's data. Note that + * it is important that we do this in reverse so as to ensure that we don't + * clobber data as the framevec_t is larger than the framevec32_t. + */ +static int +frameio_hdr_copyin_ilp32(frameio_t *fio, const void *addr) +{ + framevec32_t *vec32p; + framevec_t fv; + int i; + + vec32p = (framevec32_t *)&fio->fio_vecs[0]; + + if (ddi_copyin(addr, vec32p, sizeof (framevec32_t) * fio->fio_nvecs, + 0) != 0) + return (EFAULT); + + for (i = fio->fio_nvecs - 1; i >= 0; i--) { + fv.fv_buf = (void *)(uintptr_t)vec32p[i].fv_buf; + fv.fv_buflen = vec32p[i].fv_buflen; + fv.fv_actlen = vec32p[i].fv_actlen; + fio->fio_vecs[i].fv_buf = fv.fv_buf; + fio->fio_vecs[i].fv_buflen = fv.fv_buflen; + fio->fio_vecs[i].fv_actlen = fv.fv_actlen; + } + + return (frameio_hdr_check_vecs(fio)); +} + +/* + * Copy in a frame io header into fio with space for up to nvecs. If the frameio + * contains more vectors than specified it will be ignored. mode should contain + * information about the datamodel. + */ +int +frameio_hdr_copyin(frameio_t *fio, int max_vecs, const void *addr, uint_t mode) +{ + int model = ddi_model_convert_from(mode & FMODELS); + int cpf = mode & FKIOCTL ? FKIOCTL : 0; + size_t fsize = model == DDI_MODEL_ILP32 ? + sizeof (frameio32_t) : sizeof (frameio_t); + + /* + * The start of the header is the same in all data models for the + * current verison. + */ + if (ddi_copyin(addr, fio, fsize, cpf) != 0) + return (EFAULT); + + if (fio->fio_version != FRAMEIO_VERSION_ONE) + return (EINVAL); + + if (fio->fio_nvecs > FRAMEIO_NVECS_MAX || fio->fio_nvecs == 0) + return (EINVAL); + + if (fio->fio_nvpf == 0) + return (EINVAL); + + if (fio->fio_nvecs % fio->fio_nvpf != 0) + return (EINVAL); + + if (fio->fio_nvecs > max_vecs) + return (EOVERFLOW); + + addr = (void *)((uintptr_t)addr + fsize); + if (model == DDI_MODEL_ILP32) { + if (cpf != 0) + return (EINVAL); + return (frameio_hdr_copyin_ilp32(fio, addr)); + } + + if (ddi_copyin(addr, &fio->fio_vecs[0], + sizeof (framevec_t) * fio->fio_nvecs, cpf) != 0) + return (EFAULT); + + return (frameio_hdr_check_vecs(fio)); +} + +static mblk_t * +frameio_allocb(size_t sz) +{ + mblk_t *mp; + + mp = allocb(sz, 0); + if (mp == NULL) + return (NULL); + + mp->b_datap->db_type = M_DATA; + return (mp); +} + +static int +framevec_mblk_read(framevec_t *fv, mblk_t **mpp, int cpf) +{ + mblk_t *mp; + cpf = cpf != 0 ? FKIOCTL : 0; + + mp = frameio_allocb(fv->fv_buflen); + + if (mp == NULL) { + freemsg(mp); + return (EAGAIN); + } + + if (ddi_copyin(fv->fv_buf, mp->b_wptr, fv->fv_buflen, + cpf) != 0) { + freemsg(mp); + return (EFAULT); + } + + mp->b_wptr += fv->fv_buflen; + *mpp = mp; + return (0); +} + +/* + * Read a set of frame vectors that make up a single message boundary and return + * that as a single message in *mpp that consists of multiple data parts. + */ +static int +frameio_mblk_read(frameio_t *fio, framevec_t *fv, mblk_t **mpp, int cpf) +{ + int nparts = fio->fio_nvpf; + int part, error; + mblk_t *mp; + + *mpp = NULL; + cpf = cpf != 0 ? FKIOCTL : 0; + + /* + * Construct the initial frame + */ + for (part = 0; part < nparts; part++) { + error = framevec_mblk_read(fv, &mp, cpf); + if (error != 0) { + freemsg(*mpp); + return (error); + } + + if (*mpp == NULL) + *mpp = mp; + else + linkb(*mpp, mp); + fv++; + } + + return (0); +} + +/* + * Read data from a series of frameio vectors into a message block chain. A + * given frameio request has a number of discrete messages divided into + * individual vectors based on fio->fio_nvcspframe. Each discrete message will + * be constructed into a message block chain pointed to by b_next. + * + * If we get an EAGAIN while trying to construct a given message block what we + * return depends on what else we've done so far. If we have succesfully + * completed at least one message then we free everything else we've done so + * far and return that. If no messages have been completed we return EAGAIN. If + * instead we encounter a different error, say EFAULT, then all of the fv_actlen + * entries values are undefined. + */ +int +frameio_mblk_chain_read(frameio_t *fio, mblk_t **mpp, int *nvecs, int cpf) +{ + int error = ENOTSUP; + int nframes = fio->fio_nvecs / fio->fio_nvpf; + int frame; + framevec_t *fv; + mblk_t *mp, *bmp = NULL; + + /* + * Protect against bogus kernel subsystems. + */ + VERIFY(fio->fio_nvecs > 0); + VERIFY(fio->fio_nvecs % fio->fio_nvpf == 0); + + *mpp = NULL; + cpf = cpf != 0 ? FKIOCTL : 0; + + fv = &fio->fio_vecs[0]; + for (frame = 0; frame < nframes; frame++) { + error = frameio_mblk_read(fio, fv, &mp, cpf); + if (error != 0) + goto failed; + + if (bmp != NULL) + bmp->b_next = mp; + else + *mpp = mp; + bmp = mp; + } + + *nvecs = nframes; + return (0); +failed: + /* + * On EAGAIN we've already taken care of making sure that we have no + * leftover messages, eg. they were never linked in. + */ + if (error == EAGAIN) { + if (frame != 0) + error = 0; + if (*nvecs != NULL) + *nvecs = frame; + ASSERT(*mpp != NULL); + } else { + for (mp = *mpp; mp != NULL; mp = bmp) { + bmp = mp->b_next; + freemsg(mp); + } + if (nvecs != NULL) + *nvecs = 0; + *mpp = NULL; + } + return (error); +} + +size_t +frameio_frame_length(frameio_t *fio, framevec_t *fv) +{ + int i; + size_t len = 0; + + for (i = 0; i < fio->fio_nvpf; i++, fv++) + len += fv->fv_buflen; + + return (len); +} + +/* + * Write a portion of an mblk to the current. + */ +static int +framevec_write_mblk_part(framevec_t *fv, mblk_t *mp, size_t len, size_t moff, + size_t foff, int cpf) +{ + ASSERT(len <= MBLKL(mp) - moff); + ASSERT(len <= fv->fv_buflen - fv->fv_actlen); + cpf = cpf != 0 ? FKIOCTL : 0; + + if (ddi_copyout(mp->b_rptr + moff, fv->fv_buf + foff, len, cpf) != 0) + return (EFAULT); + fv->fv_actlen += len; + + return (0); +} + +/* + * Because copying this out to the user might fail we don't want to update the + * b_rptr in case we need to copy it out again. + */ +static int +framevec_map_blk(frameio_t *fio, framevec_t *fv, mblk_t *mp, int cpf) +{ + int err; + size_t msize, blksize, len, moff, foff; + + msize = msgsize(mp); + if (msize > frameio_frame_length(fio, fv)) + return (EOVERFLOW); + + moff = 0; + foff = 0; + blksize = MBLKL(mp); + fv->fv_actlen = 0; + while (msize != 0) { + len = MIN(blksize, fv->fv_buflen - fv->fv_actlen); + err = framevec_write_mblk_part(fv, mp, len, moff, foff, cpf); + if (err != 0) + return (err); + + msize -= len; + blksize -= len; + moff += len; + foff += len; + + if (blksize == 0 && msize != 0) { + mp = mp->b_cont; + ASSERT(mp != NULL); + moff = 0; + blksize = MBLKL(mp); + } + + if (fv->fv_buflen == fv->fv_actlen && msize != 0) { + fv++; + fv->fv_actlen = 0; + foff = 0; + } + } + + return (0); +} + +int +frameio_mblk_chain_write(frameio_t *fio, frameio_write_mblk_map_t map, + mblk_t *mp, int *nwrite, int cpf) +{ + int mcount = 0; + int ret = 0; + + if (map != MAP_BLK_FRAME) + return (EINVAL); + + while (mp != NULL && mcount < fio->fio_nvecs) { + ret = framevec_map_blk(fio, &fio->fio_vecs[mcount], mp, cpf); + if (ret != 0) + break; + mcount += fio->fio_nvpf; + mp = mp->b_next; + } + + if (ret != 0 && mcount == 0) { + if (nwrite != NULL) + *nwrite = 0; + return (ret); + } + + if (nwrite != NULL) + *nwrite = mcount / fio->fio_nvpf; + + return (0); +} + +/* + * Copy out nframes worth of frameio header data back to userland. + */ +int +frameio_hdr_copyout(frameio_t *fio, int nframes, void *addr, uint_t mode) +{ + int i; + int model = ddi_model_convert_from(mode & FMODELS); + framevec32_t *vec32p; + framevec32_t f; + + if (fio->fio_nvecs / fio->fio_nvpf < nframes) + return (EINVAL); + + fio->fio_nvecs = nframes * fio->fio_nvpf; + + if (model == DDI_MODEL_NONE) { + if (ddi_copyout(fio, addr, + sizeof (frameio_t) + fio->fio_nvecs * sizeof (framevec_t), + mode & FKIOCTL) != 0) + return (EFAULT); + return (0); + } + + ASSERT(model == DDI_MODEL_ILP32); + + vec32p = (framevec32_t *)&fio->fio_vecs[0]; + for (i = 0; i < fio->fio_nvecs; i++) { + f.fv_buf = (caddr32_t)(uintptr_t)fio->fio_vecs[i].fv_buf; + if (fio->fio_vecs[i].fv_buflen > UINT_MAX || + fio->fio_vecs[i].fv_actlen > UINT_MAX) + return (EOVERFLOW); + f.fv_buflen = fio->fio_vecs[i].fv_buflen; + f.fv_actlen = fio->fio_vecs[i].fv_actlen; + vec32p[i].fv_buf = f.fv_buf; + vec32p[i].fv_buflen = f.fv_buflen; + vec32p[i].fv_actlen = f.fv_actlen; + } + + if (ddi_copyout(fio, addr, + sizeof (frameio32_t) + fio->fio_nvecs * sizeof (framevec32_t), + mode & FKIOCTL) != 0) + return (EFAULT); + return (0); +} + +void +frameio_mark_consumed(frameio_t *fio, int nframes) +{ + int i; + + ASSERT(fio->fio_nvecs / fio->fio_nvpf >= nframes); + for (i = 0; i < nframes * fio->fio_nvpf; i++) + fio->fio_vecs[i].fv_actlen = fio->fio_vecs[i].fv_buflen; +} diff --git a/usr/src/uts/common/io/vnd/vnd.c b/usr/src/uts/common/io/vnd/vnd.c new file mode 100644 index 0000000000..d62c80c1c6 --- /dev/null +++ b/usr/src/uts/common/io/vnd/vnd.c @@ -0,0 +1,5584 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright (c) 2014 Joyent, Inc. All rights reserved. + */ + +/* + * vnd - virtual (machine) networking datapath + * + * vnd's purpose is to provide a highly performant data path for Layer 2 network + * traffic and exist side by side an active IP netstack, each servicing + * different datalinks. vnd provides many of the same capabilities as the + * current TCP/IP stack does and some specific to layer two. Specifically: + * + * o Use of the DLD fastpath + * o Packet capture hooks + * o Ability to use hardware capabilities + * o Useful interfaces for handling multiple frames + * + * The following image shows where vnd fits into today's networking stack: + * + * +---------+----------+----------+ + * | libdlpi | libvnd | libsocket| + * +---------+----------+----------+ + * | · · VFS | + * | VFS · VFS +----------+ + * | · | sockfs | + * +---------+----------+----------+ + * | | VND | IP | + * | +----------+----------+ + * | DLD/DLS | + * +-------------------------------+ + * | MAC | + * +-------------------------------+ + * | GLDv3 | + * +-------------------------------+ + * + * ----------------------------------------- + * A Tale of Two Devices - DDI Device Basics + * ----------------------------------------- + * + * vnd presents itself to userland as a character device; however, it also is a + * STREAMS device so that it can interface with dld and the rest of the + * networking stack. Users never interface with the STREAMs devices directly and + * they are purely an implementation detail of vnd. Opening the STREAMS device + * require kcred and as such userland cannot interact with it or push it onto + * the stream head. + * + * The main vnd character device, /dev/vnd/ctl, is a self-cloning device. Every + * clone gets its own minor number; however, minor nodes are not created in the + * devices tree for these instances. In this state a user may do two different + * things. They may issue ioctls that affect global state or they may issue + * ioctls that try to attach it to a given datalink. Once a minor device has + * been attached to a datalink, all operations on it are scoped to that context, + * therefore subsequent global operations are not permitted. + * + * A given device can be linked into the /devices and /dev name space via a link + * ioctl. That ioctl causes a minor node to be created in /devices and then it + * will also appear under /dev/vnd/ due to vnd's sdev plugin. This is similar + * to, but simpler than, IP's persistence mechanism. + * + * --------------------- + * Binding to a datalink + * --------------------- + * + * Datalinks are backed by the dld (datalink device) and dls (datalink services) + * drivers. These drivers provide a STREAMS device for datalinks on the system + * which are exposed through /dev/net. Userland generally manipulates datalinks + * through libdlpi. When an IP interface is being plumbed up what actually + * happens is that someone does a dlpi_open(3DLPI) of the underlying datalink + * and then pushes on the ip STREAMS module with an I_PUSH ioctl. Modules may + * then can negotiate with dld and dls to obtain access to various capabilities + * and fast paths via a series of STREAMS messages. + * + * In vnd, we do the same thing, but we leave our STREAMS module as an + * implementation detail of the system. We don't want users to be able to + * arbitrarily push vnd STREAMS module onto any stream, so we explicitly require + * kcred to manipulate it. Thus, when a user issues a request to attach a + * datalink to a minor instance of the character device, that vnd minor instance + * itself does a layered open (ldi_open_by_name(9F)) of the specified datalink. + * vnd does that open using the passed in credentials from the ioctl, not kcred. + * This ensures that users who doesn't have permissions to open the device + * cannot. Once that's been opened, we push on the vnd streams module. + * + * Once the vnd STREAMS instance has been created for this device, eg. the + * I_PUSH ioctl returns, we explicitly send a STREAMS ioctl + * (VND_STRIOC_ASSOCIATE) to associate the vnd STREAMS and character devices. + * This association begins the STREAM device's initialization. We start up an + * asynchronous state machine that takes care of all the different aspects of + * plumbing up the device with dld and dls and enabling the MAC fast path. We + * need to guarantee to consumers of the character device that by the time their + * ioctl returns, the data path has been fully initialized. + * + * The state progression is fairly linear. There are two general steady states. + * The first is VND_S_ONLINE, which means that everything is jacked up and good + * to go. The alternative is VND_S_ZOMBIE, which means that the streams device + * encountered an error or we have finished tearing it down and the character + * device can clean it up. The following is our state progression and the + * meaning of each state: + * + * | + * | + * V + * +---------------+ + * | VNS_S_INITIAL | This is our initial state. Every + * +---------------+ vnd STREAMS device starts here. + * | While in this state, only dlpi + * | M_PROTO and M_IOCTL messages can be + * | sent or received. All STREAMS based + * | data messages are dropped. + * | We transition out of this state by + * | sending a DL_INFO_REQ to obtain + * | information about the underlying + * | link. + * v + * +-----------------+ + * +--<-| VNS_S_INFO_SENT | In this state, we verify and + * | +-----------------+ record information about the + * | | underlying device. If the device is + * | | not suitable, eg. not of type + * v | DL_ETHER, then we immediately + * | | become a ZOMBIE. To leave this + * | | state we request exclusive active + * | | access to the device via + * v | DL_EXCLUSIVE_REQ. + * | v + * | +----------------------+ + * +--<-| VNS_S_EXCLUSIVE_SENT | In this state, we verify whether + * | +----------------------+ or not we were able to obtain + * | | | exclusive access to the device. If + * | | | we were not able to, then we leave, + * v | | as that means that something like + * | | | IP is already plumbed up on top of + * | | | the datalink. We leave this state + * | | | by progressing through to the + * | | | appropriate DLPI primitive, either + * v | | DLPI_ATTACH_REQ or DLPI_BIND_REQ + * | | | depending on the style of the + * | | | datalink. + * | | v + * | | +-------------------+ + * +------ |--<-| VNS_S_ATTACH_SENT | In this state, we verify we were + * | | +-------------------+ able to perform a standard DLPI + * | | | attach and if so, go ahead and + * v | | send a DLPI_BIND_REQ. + * | v v + * | +-------------------+ + * +--<-| VNS_S_BIND_SENT | In this state we see the result of + * | +-------------------+ our attempt to bind to PPA 0 of the + * v | underlying device. Because we're + * | | trying to be a layer two datapath, + * | | the specific attachment point isn't + * | | too important as we're going to + * v | have to enable promiscuous mode. We + * | | transition out of this by sending + * | | our first of three promiscuous mode + * | | requests. + * v v + * | +------------------------+ + * +--<-| VNS_S_SAP_PROMISC_SENT | In this state we verify that we + * | +------------------------+ were able to enable promiscuous + * | | mode at the physical level. We + * | | transition out of this by enabling + * | | multicast and broadcast promiscuous + * v | mode. + * | v + * | +--------------------------+ + * +--<-| VNS_S_MULTI_PROMISC_SENT | In this state we verify that we + * | +--------------------------+ have enabled DL_PROMISC_MULTI and + * v | move onto the final promiscuous + * | | mode request. + * | v + * | +----------------------------+ + * +--<-| VNS_S_RX_ONLY_PROMISC_SENT | In this state we verify that we + * | +----------------------------+ enabled RX_ONLY promiscuous mode. + * | | We specifically do this as we don't + * v | want to receive our own traffic + * | | that we'll send out. We leave this + * | | state by requesting the set of + * | | dld/dls capabilities that we can + * v | process. + * | | + * | v + * | +--------------------+ + * +--<-| VNS_S_CAPAB_Q_SENT | We loop over the set of + * | +--------------------+ capabilities that dld advertised + * | | and enable the ones that currently + * v | support for use. See the section + * | | later on regarding capabilities + * | | for more information. We leave this + * | | state by sending an enable request. + * v v + * | +--------------------+ + * +--<-| VNS_S_CAPAB_E_SENT | Here we finish all capability + * | +--------------------+ initialization. Once finished, we + * | | transition to the next state. If + * v | the dld fast path is not available, + * | | we become a zombie. + * | v + * | +--------------+ + * +--<-| VNS_S_ONLINE | This is a vnd STREAMS device's + * | +--------------+ steady state. It will normally + * | | reside in this state while it is in + * | | active use. It will only transition + * v | to the next state when the STREAMS + * | | device is closed by the character + * | | device. In this state, all data + * | | flows over the dld fast path. + * | v + * | +---------------------+ + * +--<-| VNS_S_SHUTTING_DOWN | This vnd state takes care of + * | +---------------------+ disabling capabilities and then + * | | transitions to zombie state to + * v | indicate that it is finished. + * | v + * | +--------------+ + * +--->| VNS_S_ZOMBIE | In this state, the vnd STREAMS + * +--------------+ device is waiting to finished being + * reaped. + * + * If the stream association fails for any reason the state machine reaches + * VNS_S_ZOMBIE. A more detailed vnd_errno_t will propagate back through the + * STREAMS ioctl to the character device. That will fail the user ioctl and + * propagate the vnd_errno_t back to userland. If, on the other hand, the + * association succeeds, then the vnd STREAMS device will be fully plumbed up + * and ready to transmit and receive message blocks. Consumers will be able to + * start using the other cbops(9E) entry points once the attach has fully + * finished, which will occur after the original user attach ioctl to the + * character device returns. + * + * -------------------- + * General Architecture + * -------------------- + * + * There are several different devices and structures in the vnd driver. There + * is a per-netstack component, pieces related to the character device that + * consumers see, the internal STREAMS device state, and the data queues + * themselves. The following ASCII art picture describes their relationships and + * some of the major pieces of data that contain them. These are not exhaustive, + * eg. synchronization primitives are left out. + * + * +----------------+ +-----------------+ + * | global | | global | + * | device list | | netstack list | + * | vnd_dev_list | | vnd_nsd_list | + * +----------------+ +-----------------+ + * | | + * | v + * | +-------------------+ +-------------------+ + * | | per-netstack data | ---> | per-netstack data | --> ... + * | | vnd_pnsd_t | | vnd_pnsd_t | + * | | | +-------------------+ + * | | | + * | | nestackid_t ---+----> Netstack ID + * | | vnd_pnsd_flags_t -+----> Status flags + * | | zoneid_t ---+----> Zone ID for this netstack + * | | hook_family_t ---+----> VND IPv4 Hooks + * | | hook_family_t ---+----> VND IPv6 Hooks + * | | list_t ----+ | + * | +------------+------+ + * | | + * | v + * | +------------------+ +------------------+ + * | | character device | ---> | character device | -> ... + * +---------->| vnd_dev_t | | vnd_dev_t | + * | | +------------------+ + * | | + * | minor_t ---+--> device minor number + * | ldi_handle_t ---+--> handle to /dev/net/%datalink + * | vnd_dev_flags_t -+--> device flags, non blocking, etc. + * | char[] ---+--> name if linked + * | vnd_str_t * -+ | + * +--------------+---+ + * | + * v + * +-------------------------+ + * | STREAMS device | + * | vnd_str_t | + * | | + * | vnd_str_state_t ---+---> State machine state + * | gsqueue_t * ---+---> mblk_t Serialization queue + * | vnd_str_stat_t ---+---> per-device kstats + * | vnd_str_capab_t ---+----------------------------+ + * | vnd_data_queue_t ---+ | | + * | vnd_data_queue_t -+ | | v + * +-------------------+-+---+ +---------------------+ + * | | | Stream capabilities | + * | | | vnd_str_capab_t | + * | | | | + * | | supported caps <--+-- vnd_capab_flags_t | + * | | dld cap handle <--+-- void * | + * | | direct tx func <--+-- vnd_dld_tx_t | + * | | +---------------------+ + * | | + * +----------------+ +-------------+ + * | | + * v v + * +-------------------+ +-------------------+ + * | Read data queue | | Write data queue | + * | vnd_data_queue_t | | vnd_data_queue_t | + * | | | | + * | size_t ----+--> Current size | size_t ----+--> Current size + * | size_t ----+--> Max size | size_t ----+--> Max size + * | mblk_t * ----+--> Queue head | mblk_t * ----+--> Queue head + * | mblk_t * ----+--> Queue tail | mblk_t * ----+--> Queue tail + * +-------------------+ +-------------------+ + * + * + * Globally, we maintain two lists. One list contains all of the character + * device soft states. The other maintains a list of all our netstack soft + * states. Each netstack maintains a list of active devices that have been + * associated with a datalink in its netstack. + * + * Recall that a given minor instance of the character device exists in one of + * two modes. It can either be a cloned open of /dev/vnd/ctl, the control node, + * or it can be associated with a given datalink. When minor instances are in + * the former state, they do not exist in a given vnd_pnsd_t's list of devices. + * As part of attaching to a datalink, the given vnd_dev_t will be inserted into + * the appropriate vnd_pnsd_t. In addition, this will cause a STREAMS device, a + * vnd_str_t, to be created and associated to a vnd_dev_t. + * + * The character device, and its vnd_dev_t, is the interface to the rest of the + * system. The vnd_dev_t keeps track of various aspects like whether various + * operations, such as read, write and the frameio ioctls, are considered + * blocking or non-blocking in the O_NONBLOCK sense. It also is responsible for + * keeping track of things like the name of the device, if any, in /dev. The + * vnd_str_t, on the other hand manages aspects like buffer sizes and the actual + * data queues. However, ioctls that manipulate these properties all go through + * the vnd_dev_t to its associated vnd_str_t. + * + * Each of the STREAMS devices, the vnd_str_t, maintains two data queues. One + * for frames to transmit (write queue) and one for frames received (read + * queue). These data queues have a maximum size and attempting to add data + * beyond that maximum size will result in data being dropped. The sizes are + * configurable via ioctls VND_IOC_SETTXBUF, VND_IOC_SETRXBUF. Data either sits + * in those buffers or has a reservation in those buffers while they are in vnd + * and waiting to be consumed by the user or by mac. + * + * Finally, the vnd_str_t also has a vnd_str_capab_t which we use to manage the + * available, negotiated, and currently active features. + * + * ---------------------- + * Data Path and gsqueues + * ---------------------- + * + * There's a lot of plumbing in vnd to get to the point where we can send data, + * but vnd's bread and butter is the data path, so it's worth diving into it in + * more detail. Data enters and exits the system from two ends. + * + * The first end is the vnd consumer. This comes in the form of read and write + * system calls as well as the frame I/O ioctls. The read and write system calls + * operate on a single frame at a time. Think of a frame as a single message + * that has come in off the wire, which may itself comprise multiple mblk_t's + * linked together in the kernel. readv(2) and writev(2) have the same + * limitations as read(2) and write(2). We enforce this as the system is + * required to fill up every uio(9S) buffer before moving onto the next one. + * This means that if you have a MTU sized buffer and two frames come in which + * are less than half of the MTU they must fill up the given iovec. Even if we + * didn't want to do this, we have no way of informing the supplier of the + * iovecs that they were only partially filled or where one frame ends and + * another begins. That's life, as such we have frame I/O which solves this + * problem. It allows for multiple frames to be consumed as well as for frames + * to be broken down into multiple vector components. + * + * The second end is the mac direct calls. As part of negotiating capabilities + * via dld, we give mac a function of ours to call when packets are received + * [vnd_mac_input()] and a callback to indicate that flow has been restored + * [vnd_mac_flow_control()]. In turn, we also get a function pointer that we can + * transmit data with. As part of the contract with mac, mac is allowed to flow + * control us by returning a cookie to the transmit function. When that happens, + * all outbound traffic is halted until our callback function is called and we + * can schedule drains. + * + * It's worth looking at these in further detail. We'll start with the rx path. + * + * + * | + * * . . . packets from gld + * | + * v + * +-------------+ + * | mac | + * +-------------+ + * | + * v + * +-------------+ + * | dld | + * +-------------+ + * | + * * . . . dld direct callback + * | + * v + * +---------------+ + * | vnd_mac_input | + * +---------------+ + * | + * v + * +---------+ +-------------+ + * | dropped |<--*---------| vnd_hooks | + * | by | . +-------------+ + * | hooks | . drop probe | + * +---------+ kstat bump * . . . Do we have free + * | buffer space? + * | + * no . | . yes + * . + . + * +---*--+------*-------+ + * | | + * * . . drop probe * . . recv probe + * | kstat bump | kstat bump + * v | + * +---------+ * . . fire pollin + * | freemsg | v + * +---------+ +-----------------------+ + * | vnd_str_t`vns_dq_read | + * +-----------------------+ + * ^ ^ + * +----------+ | | +---------+ + * | read(9E) |-->-+ +--<--| frameio | + * +----------+ +---------+ + * + * The rx path is rather linear. Packets come into us from mac. We always run + * them through the various hooks, and if they come out of that, we inspect the + * read data queue. If there is not enough space for a packet, we drop it. + * Otherwise, we append it to the data queue, and fire read notifications + * targetting anyone polling or doing blocking I/O on this device. Those + * consumers then drain the head of the data queue. + * + * The tx path is more complicated due to mac flow control. After any call into + * mac, we may have to potentially suspend writes and buffer data for an + * arbitrary amount of time. As such, we need to carefully track the total + * amount of outstanding data so that we don't waste kernel memory. This is + * further complicated by the fact that mac will asynchronously tell us when our + * flow has been resumed. + * + * For data to be able to enter the system, it needs to be able to take a + * reservation from the write data queue. Once the reservation has been + * obtained, we enter the gsqueue so that we can actually append it. We use + * gsqueues (serialization queues) to ensure that packets are manipulated in + * order as we deal with the draining and appending packets. We also leverage + * its worker thread to help us do draining after mac has restorted our flow. + * + * The following image describes the flow: + * + * +-----------+ +--------------+ +-------------------------+ +------+ + * | write(9E) |-->| Space in the |--*--->| gsqueue_enter_one() |-->| Done | + * | frameio | | write queue? | . | +->vnd_squeue_tx_append | +------+ + * +-----------+ +--------------+ . +-------------------------+ + * | ^ . + * | | . reserve space from gsqueue + * | | | + * queue . . . * | space v + * full | * . . . avail +------------------------+ + * v | | vnd_squeue_tx_append() | + * +--------+ +------------+ +------------------------+ + * | EAGAIN |<--*------| Non-block? |<-+ | + * +--------+ . +------------+ | v + * . yes v | wait +--------------+ + * no . .* * . . for | append chain | + * +----+ space | to outgoing | + * | mblk chain | + * from gsqueue +--------------+ + * | | + * | +-------------------------------------------------+ + * | | + * | | yes . . . + * v v . + * +-----------------------+ +--------------+ . +------+ + * | vnd_squeue_tx_drain() |--->| mac blocked? |----*---->| Done | + * +-----------------------+ +--------------+ +------+ + * | | + * +---------------------------------|---------------------+ + * | | tx | + * | no . . * queue . . * + * | flow controlled . | empty * . fire pollout + * | . v | if mblk_t's + * +-------------+ . +---------------------+ | sent + * | set blocked |<----*------| vnd_squeue_tx_one() |--------^-------+ + * | flags | +---------------------+ | + * +-------------+ More data | | | More data | + * and limit ^ v * . . and limit ^ + * not reached . . * | | reached | + * +----+ | | + * v | + * +----------+ +-------------+ +---------------------------+ + * | mac flow |--------->| remove mac |--->| gsqueue_enter_one() with | + * | control | | block flags | | vnd_squeue_tx_drain() and | + * | callback | +-------------+ | GSQUEUE_FILL flag, iff | + * +----------+ | not already scheduled | + * +---------------------------+ + * + * The final path taken for a given write(9E)/frameio ioctl depends on whether + * or not the vnd_dev_t is non-blocking. That controls the initial path of + * trying to take a reservation in write data queue. If the device is in + * non-blocking mode, we'll return EAGAIN when there is not enough space + * available, otherwise, the calling thread blocks on the data queue. + * + * Today when we call into vnd_squeue_tx_drain() we will not try to drain the + * entire queue, as that could be quite large and we don't want to necessarily + * keep the thread that's doing the drain until it's been finished. Not only + * could more data be coming in, but the draining thread could be a userland + * thread that has more work to do. We have two limits today. There is an upper + * bound on the total amount of data and the total number of mblk_t chains. If + * we hit either limit, then we will schedule another drain in the gsqueue and + * go from there. + * + * It's worth taking some time to describe how we interact with gsqueues. vnd + * has a gsqueue_set_t for itself. It's important that it has its own set, as + * the profile of work that vnd does is different from other sub-systems in the + * kernel. When we open a STREAMS device in vnd_s_open, we get a random gsqueue. + * Unlike TCP/IP which uses an gsqueue for per TCP connection, we end up + * maintaining one for a given device. Because of that, we want to use a + * pseudo-random one to try and spread out the load, and picking one at random + * is likely to be just as good as any fancy algorithm we might come up with, + * especially as any two devices could have radically different transmit + * profiles. + * + * While some of the write path may seem complicated, it does allow us to + * maintain an important property. Once we have acknowledged a write(9E) or + * frameio ioctl, we will not drop the packet, excepting something like ipf via + * the firewall hooks. + * + * There is one other source of flow control that can exist in the system which + * is in the form of a barrier. The barrier is an internal mechanism used for + * ensuring that an gsqueue is drained for a given device. We use this as part + * of tearing down. Specifically we disable the write path so nothing new can be + * inserted into the gsqueue and then insert a barrier block. Once the barrier + * block comes out of the gsqueue, then we know nothing else in the gsqueue that + * could refer to the vnd_str_t, being destroyed, exists. + * + * --------------------- + * vnd, zones, netstacks + * --------------------- + * + * vnd devices are scoped to datalinks and datalinks are scoped to a netstack. + * Because of that, vnd is also a netstack module. It registers with the + * netstack sub-system and receives callbacks every time a netstack is created, + * being shutdown, and destroyed. The netstack callbacks drive the creation and + * destruction of the vnd_pnsd_t structures. + * + * Recall from the earlier architecture diagrams that every vnd device is scoped + * to a netstack and known about by a given vnd_pnsd_t. When that netstack is + * torn down, we also tear down any vnd devices that are hanging around. When + * the netstack is torn down, we know that any zones that are scoped to that + * netstack are being shut down and have no processes remaining. This is going + * to be the case whether they are shared or exclusive stack zones. We have to + * perform a careful dance. + * + * There are two different callbacks that happen on tear down, the first is a + * shutdown callback, the second is a destroy callback. When the shutdown + * callback is fired we need to prepare for the netstack to go away and ensure + * that nothing can continue to persist itself. + * + * More specifically, when we get notice of a stack being shutdown we first + * remove the netstack from the global netstack list to ensure that no one new + * can come in and find the netstack and get a reference to it. After that, we + * notify the neti hooks that they're going away. Once that's all done, we get + * to the heart of the matter. + * + * When shutting down there could be any number of outstanding contexts that + * have a reference on the vnd_pnsd_t and on the individual links. However, we + * know that no one new will be able to find the vnd_pnsd_t. To account for + * things that have existing references we mark the vnd_pnsd_t`vpnd_flags with + * VND_NS_CONDEMNED. This is checked by code paths that wish to append a device + * to the netstack's list. If this is set, then they must not append to it. + * Once this is set, we know that the netstack's list of devices can never grow, + * only shrink. + * + * Next, for each device we tag it with VND_D_ZONE_DYING. This indicates that + * the container for the device is being destroyed and that we should not allow + * additional references to the device to be created, whether via open, or + * linking. The presence of this bit also allows things like the list ioctl and + * sdev to know not to consider its existence. At the conclusion of this being + * set, we know that no one else should be able to obtain a new reference to the + * device. + * + * Once that has been set for all devices, we go through and remove any existing + * links that have been established in sdev. Because doing that may cause the + * final reference for the device to be dropped, which still has a reference to + * the netstack, we have to restart our walk due to dropped locks. We know that + * this walk will eventually complete because the device cannot be relinked and + * no new devices will be attached in this netstack due to VND_NS_CONDEMNED. + * Once that's finished, the shutdown callback returns. + * + * When we reach the destroy callback, we simply wait for references on the + * netstack to disappear. Because the zone has been shut down, all processes in + * it that have open references have been terminated and reaped. Any threads + * that are newly trying to reference it will fail. However, there is one thing + * that can halt this that we have no control over, which is the global zone + * holding open a reference to the device. In this case the zone halt will hang + * in vnd_stack_destroy. Once the last references is dropped we finish destroy + * the netinfo hooks and free the vnd_pnsd_t. + * + * ---- + * sdev + * ---- + * + * vnd registers a sdev plugin which allows it to dynamically fill out /dev/vnd + * for both the global and non-global zones. In any given zone we always supply + * a control node via /dev/vnd/ctl. This is the self-cloning node. Each zone + * will also have an entry per-link in that zone under /dev/vnd/%datalink, eg. + * if a link was named net0, there would be a /dev/vnd/net0. The global zone can + * also see every link for every zone, ala /dev/net, under + * /dev/vnd/%zonename/%datalink, eg. if a zone named 'turin' had a vnd device + * named net0, the global zone would have /dev/vnd/turin/net0. + * + * The sdev plugin has three interfaces that it supplies back to sdev. One is to + * validate that a given node is still valid. The next is a callback from sdev + * to say that it is no longer using the node. The third and final one is from + * sdev where it asks us to fill a directory. All of the heavy lifting is done + * in directory filling and in valiation. We opt not to maintain a reference on + * the device while there is an sdev node present. This makes the removal of + * nodes much simpler and most of the possible failure modes shouldn't cause any + * real problems. For example, the open path has to handle both dev_t's which no + * longer exist and which are no longer linked. + * + * ----- + * hooks + * ----- + * + * Like IP, vnd sends all L3 packets through its firewall hooks. Currently vnd + * provides these for L3 IP and IPv6 traffic. Each netstack provides these hooks + * in a minimal fashion. While we will allow traffic to be filtered through the + * hooks, we do not provide means for packet injection or additional inspection + * at this time. There are a total of four different events created: + * + * o IPv4 physical in + * o IPv4 physical out + * o IPv6 physical in + * o IPv6 physical out + * + * --------------- + * Synchronization + * --------------- + * + * To make our synchronization simpler, we've put more effort into making the + * metadata/setup paths do more work. That work allows the data paths to make + * assumptions around synchronization that simplify the general case. Each major + * structure, the vnd_pnsd_t, vnd_dev_t, vnd_str_t, and vnd_data_queue_t is + * annotated with the protection that its members receives. The following + * annotations are used: + * + * A Atomics; these values are only modified using atomics values. + * Currently this only applies to kstat values. + * E Existence; no lock is needed to access this member, it does not + * change while the structure is valid. + * GL Global Lock; these members are protected by the global + * vnd_dev_lock. + * L Locked; access to the member is controlled by a lock that is in + * the structure. + * NSL netstack lock; this member is protected by the containing + * netstack. This only applies to the vnd_dev_t`vdd_nslink. + * X This member is special, and is discussed in this section. + * + * In addition to locking, we also have reference counts on the vnd_dev_t and + * the vnd_pnsd_t. The reference counts describe the lifetimes of the structure. + * With rare exception, once a reference count is decremented, the consumer + * should not assume that the data is valid any more. The only exception to this + * is the case where we're removing an extant reference count from a link into + * /devices or /dev. Reference counts are obtained on these structures as a part + * of looking them up. + * + * # Global Lock Ordering + * ###################### + * + * The following is the order that you must take locks in vnd: + * + * 1) vnd`vnd_dev_lock + * 2) vnd_pnsd_t`vpnd_lock + * 3) vnd_dev_t`vnd_lock + * 4) vnd_str_t`vns_lock + * 5) vnd_data_queue_t`vdq_lock + * + * One must adhere to the following rules: + * + * o You must acquire a lower numbered lock before a high numbered lock. + * o It is NOT legal to hold two locks of the same level concurrently, eg. you + * can not hold two different vnd_dev_t's vnd_lock at the same time. + * o You may release locks in any order. + * o If you release a lock, you must honor the locking rules before acquiring + * it again. + * o You should not hold any locks when calling any of the rele functions. + * + * # Special Considerations + * ######################## + * + * While most of the locking is what's expected, it's worth going into the + * special nature that a few members hold. Today, only two structures have + * special considerations: the vnd_dev_t and the vnd_str_t. All members with + * special considerations have an additional annotation that describes how you + * should interact with it. + * + * vnd_dev_t: The vdd_nsd and vdd_cr are only valid when the minor node is + * attached or in the process of attaching. If the code path that goes through + * requires an attached vnd_dev_t, eg. the data path and tear down path, then it + * is always legal to dereference that member without a lock held. When they are + * added to the system, they should be done under the vdd_lock and done as part + * of setting the VND_D_ATTACH_INFLIGHT flag. These should not change during the + * lifetime of the vnd_dev_t. + * + * vnd_dev_t: The vdd_ldih is similar to the vdd_nsd and vdd_cr, except that it + * always exists as it is a part of the structure. The only time that it's valid + * to be using it is during the attach path with the VND_D_ATTACH_INFLIGHT flag + * set or during tear down. Outside of those paths which are naturally + * serialized, there is no explicit locking around the member. + * + * vnd_str_t: The vns_dev and vns_nsd work in similar ways. They are not + * initially set as part of creating the structure, but are set as part of + * responding to the association ioctl. Anything in the data path or metadata + * path that requires association may assume that they exist, as we do not kick + * off the state machine until they're set. + * + * vnd_str_t: The vns_drainblk and vns_barrierblk are similarly special. The + * members are designed to be used as part of various operations with the + * gsqueues. A lock isn't needed to use them, but to work with them, the + * appropriate flag in the vnd_str_t`vns_flags must have been set by the current + * thread. Otherwise, it is always fair game to refer to their addresses. Their + * contents are ignored by vnd, but some members are manipulated by the gsqueue + * subsystem. + */ + +#include <sys/conf.h> +#include <sys/devops.h> +#include <sys/modctl.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/errno.h> +#include <sys/open.h> +#include <sys/ddi.h> +#include <sys/ethernet.h> +#include <sys/stropts.h> +#include <sys/sunddi.h> +#include <sys/stream.h> +#include <sys/strsun.h> +#include <sys/ksynch.h> +#include <sys/taskq_impl.h> +#include <sys/sdt.h> +#include <sys/debug.h> +#include <sys/sysmacros.h> +#include <sys/dlpi.h> +#include <sys/cred.h> +#include <sys/id_space.h> +#include <sys/list.h> +#include <sys/ctype.h> +#include <sys/policy.h> +#include <sys/sunldi.h> +#include <sys/cred.h> +#include <sys/strsubr.h> +#include <sys/poll.h> +#include <sys/neti.h> +#include <sys/hook.h> +#include <sys/hook_event.h> +#include <sys/vlan.h> +#include <sys/dld.h> +#include <sys/mac_client.h> +#include <sys/netstack.h> +#include <sys/fs/sdev_plugin.h> +#include <sys/kstat.h> +#include <sys/atomic.h> +#include <sys/disp.h> +#include <sys/random.h> +#include <sys/gsqueue.h> + +#include <inet/ip.h> +#include <inet/ip6.h> + +#include <sys/vnd.h> + +/* + * Globals + */ +static dev_info_t *vnd_dip; +static taskq_t *vnd_taskq; +static kmem_cache_t *vnd_str_cache; +static kmem_cache_t *vnd_dev_cache; +static kmem_cache_t *vnd_pnsd_cache; +static id_space_t *vnd_minors; +static int vnd_list_init = 0; +static sdev_plugin_hdl_t vnd_sdev_hdl; +static gsqueue_set_t *vnd_sqset; + +static kmutex_t vnd_dev_lock; +static list_t vnd_dev_list; /* Protected by the vnd_dev_lock */ +static list_t vnd_nsd_list; /* Protected by the vnd_dev_lock */ + +/* + * STREAMs ioctls + * + * The STREAMs ioctls are internal to vnd. No one should be seeing them, as such + * they aren't a part of the header file. + */ +#define VND_STRIOC (('v' << 24) | ('n' << 16) | ('d' << 8) | 0x80) + +/* + * Private ioctl to associate a given streams instance with a minor instance of + * the character device. + */ +#define VND_STRIOC_ASSOCIATE (VND_STRIOC | 0x1) + +typedef struct vnd_strioc_associate { + minor_t vsa_minor; /* minor device node */ + netstackid_t vsa_nsid; /* netstack id */ + vnd_errno_t vsa_errno; /* errno */ +} vnd_strioc_associate_t; + +typedef enum vnd_strioc_state { + VSS_UNKNOWN = 0, + VSS_COPYIN = 1, + VSS_COPYOUT = 2, +} vnd_strioc_state_t; + +typedef struct vnd_strioc { + vnd_strioc_state_t vs_state; + caddr_t vs_addr; +} vnd_strioc_t; + +/* + * VND SQUEUE TAGS, start at 0x42 so we don't overlap with extent tags. Though + * really, overlap is at the end of the day, inevitable. + */ +#define VND_SQUEUE_TAG_TX_DRAIN 0x42 +#define VND_SQUEUE_TAG_MAC_FLOW_CONTROL 0x43 +#define VND_SQUEUE_TAG_VND_WRITE 0x44 +#define VND_SQUEUE_TAG_ND_FRAMEIO_WRITE 0x45 +#define VND_SQUEUE_TAG_STRBARRIER 0x46 + +/* + * vnd reserved names. These are names which are reserved by vnd and thus + * shouldn't be used by some external program. + */ +static char *vnd_reserved_names[] = { + "ctl", + "zone", + NULL +}; + +/* + * vnd's DTrace probe macros + * + * DTRACE_VND* are all for a stable provider. We also have an unstable internal + * set of probes for reference count manipulation. + */ +#define DTRACE_VND3(name, type1, arg1, type2, arg2, type3, arg3) \ + DTRACE_PROBE3(__vnd_##name, type1, arg1, type2, arg2, type3, arg3); + +#define DTRACE_VND4(name, type1, arg1, type2, arg2, type3, arg3, type4, arg4) \ + DTRACE_PROBE4(__vnd_##name, type1, arg1, type2, arg2, type3, arg3, \ + type4, arg4); + +#define DTRACE_VND5(name, type1, arg1, type2, arg2, type3, arg3, \ + type4, arg4, type5, arg5) \ + DTRACE_PROBE5(__vnd_##name, type1, arg1, type2, arg2, type3, arg3, \ + type4, arg4, type5, arg5); + +#define DTRACE_VND_REFINC(vdp) \ + DTRACE_PROBE2(vnd__ref__inc, vnd_dev_t *, vdp, int, vdp->vdd_ref); +#define DTRACE_VND_REFDEC(vdp) \ + DTRACE_PROBE2(vnd__ref__dec, vnd_dev_t *, vdp, int, vdp->vdd_ref); + + +/* + * Tunables + */ +size_t vnd_vdq_default_size = 1024 * 64; /* 64 KB */ +size_t vnd_vdq_hard_max = 1024 * 1024 * 4; /* 4 MB */ + +/* + * These numbers are designed as per-device tunables that are applied when a new + * vnd device is attached. They're a rough stab at what may be a reasonable + * amount of work to do in one burst in an squeue. + */ +size_t vnd_flush_burst_size = 1520 * 10; /* 10 1500 MTU packets */ +size_t vnd_flush_nburst = 10; /* 10 frames */ + +/* + * Constants related to our sdev plugins + */ +#define VND_SDEV_NAME "vnd" +#define VND_SDEV_ROOT "/dev/vnd" +#define VND_SDEV_ZROOT "/dev/vnd/zone" + +/* + * Statistic macros + */ +#define VND_STAT_INC(vsp, field, val) \ + atomic_add_64(&(vsp)->vns_ksdata.field.value.ui64, val) +#define VND_LATENCY_1MS 1000000 +#define VND_LATENCY_10MS 10000000 +#define VND_LATENCY_100MS 100000000 +#define VND_LATENCY_1S 1000000000 +#define VND_LATENCY_10S 10000000000 + +/* + * Constants for vnd hooks + */ +static uint8_t vnd_bcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +#define IPV4_MCAST_LEN 3 +static uint8_t vnd_ipv4_mcast[3] = { 0x01, 0x00, 0x5E }; +#define IPV6_MCAST_LEN 2 +static uint8_t vnd_ipv6_mcast[2] = { 0x33, 0x33 }; + +/* + * vnd internal data structures and types + */ + +struct vnd_str; +struct vnd_dev; +struct vnd_pnsd; + +/* + * As part of opening the device stream we need to properly communicate with our + * underlying stream. This is a bit of an asynchronous dance and we need to + * properly work with dld to get everything set up. We have to initiate the + * conversation with dld and as such we keep track of our state here. + */ +typedef enum vnd_str_state { + VNS_S_INITIAL = 0, + VNS_S_INFO_SENT, + VNS_S_EXCLUSIVE_SENT, + VNS_S_ATTACH_SENT, + VNS_S_BIND_SENT, + VNS_S_SAP_PROMISC_SENT, + VNS_S_MULTI_PROMISC_SENT, + VNS_S_RX_ONLY_PROMISC_SENT, + VNS_S_CAPAB_Q_SENT, + VNS_S_CAPAB_E_SENT, + VNS_S_ONLINE, + VNS_S_SHUTTING_DOWN, + VNS_S_ZOMBIE +} vnd_str_state_t; + +typedef enum vnd_str_flags { + VNS_F_NEED_ZONE = 0x1, + VNS_F_TASKQ_DISPATCHED = 0x2, + VNS_F_CONDEMNED = 0x4, + VNS_F_FLOW_CONTROLLED = 0x8, + VNS_F_DRAIN_SCHEDULED = 0x10, + VNS_F_BARRIER = 0x20, + VNS_F_BARRIER_DONE = 0x40 +} vnd_str_flags_t; + +typedef enum vnd_capab_flags { + VNS_C_HCKSUM = 0x1, + VNS_C_DLD = 0x2, + VNS_C_DIRECT = 0x4, + VNS_C_HCKSUM_BADVERS = 0x8 +} vnd_capab_flags_t; + +/* + * Definitions to interact with direct callbacks + */ +typedef void (*vnd_rx_t)(struct vnd_str *, mac_resource_t *, mblk_t *, + mac_header_info_t *); +typedef uintptr_t vnd_mac_cookie_t; +/* DLD Direct capability function */ +typedef int (*vnd_dld_cap_t)(void *, uint_t, void *, uint_t); +/* DLD Direct tx function */ +typedef vnd_mac_cookie_t (*vnd_dld_tx_t)(void *, mblk_t *, uint64_t, uint16_t); +/* DLD Direct function to set flow control callback */ +typedef void *(*vnd_dld_set_fcb_t)(void *, void (*)(void *, vnd_mac_cookie_t), + void *); +/* DLD Direct function to see if flow controlled still */ +typedef int (*vnd_dld_is_fc_t)(void *, vnd_mac_cookie_t); + +/* + * The vnd_str_capab_t is always protected by the vnd_str_t it's a member of. + */ +typedef struct vnd_str_capab { + vnd_capab_flags_t vsc_flags; + t_uscalar_t vsc_hcksum_opts; + vnd_dld_cap_t vsc_capab_f; + void *vsc_capab_hdl; + vnd_dld_tx_t vsc_tx_f; + void *vsc_tx_hdl; + vnd_dld_set_fcb_t vsc_set_fcb_f; + void *vsc_set_fcb_hdl; + vnd_dld_is_fc_t vsc_is_fc_f; + void *vsc_is_fc_hdl; + vnd_mac_cookie_t vsc_fc_cookie; + void *vsc_tx_fc_hdl; +} vnd_str_capab_t; + +/* + * The vnd_data_queue is a simple construct for storing a series of messages in + * a queue. + * + * See synchronization section of the big theory statement for member + * annotations. + */ +typedef struct vnd_data_queue { + struct vnd_str *vdq_vns; /* E */ + kmutex_t vdq_lock; + kcondvar_t vdq_ready; /* Uses vdq_lock */ + ssize_t vdq_max; /* L */ + ssize_t vdq_cur; /* L */ + mblk_t *vdq_head; /* L */ + mblk_t *vdq_tail; /* L */ +} vnd_data_queue_t; + +typedef struct vnd_str_stat { + kstat_named_t vks_rbytes; + kstat_named_t vks_rpackets; + kstat_named_t vks_obytes; + kstat_named_t vks_opackets; + kstat_named_t vks_nhookindrops; + kstat_named_t vks_nhookoutdrops; + kstat_named_t vks_ndlpidrops; + kstat_named_t vks_ndataindrops; + kstat_named_t vks_ndataoutdrops; + kstat_named_t vks_tdrops; + kstat_named_t vks_linkname; + kstat_named_t vks_zonename; + kstat_named_t vks_nmacflow; + kstat_named_t vks_tmacflow; + kstat_named_t vks_mac_flow_1ms; + kstat_named_t vks_mac_flow_10ms; + kstat_named_t vks_mac_flow_100ms; + kstat_named_t vks_mac_flow_1s; + kstat_named_t vks_mac_flow_10s; +} vnd_str_stat_t; + +/* + * vnd stream structure + * + * See synchronization section of the big theory statement for member + * annotations. + */ +typedef struct vnd_str { + kmutex_t vns_lock; + kcondvar_t vns_cancelcv; /* Uses vns_lock */ + kcondvar_t vns_barriercv; /* Uses vns_lock */ + kcondvar_t vns_stcv; /* Uses vns_lock */ + vnd_str_state_t vns_state; /* L */ + vnd_str_state_t vns_laststate; /* L */ + vnd_errno_t vns_errno; /* L */ + vnd_str_flags_t vns_flags; /* L */ + vnd_str_capab_t vns_caps; /* L */ + taskq_ent_t vns_tqe; /* L */ + vnd_data_queue_t vns_dq_read; /* E */ + vnd_data_queue_t vns_dq_write; /* E */ + mblk_t *vns_dlpi_inc; /* L */ + queue_t *vns_rq; /* E */ + queue_t *vns_wq; /* E */ + queue_t *vns_lrq; /* E */ + t_uscalar_t vns_dlpi_style; /* L */ + t_uscalar_t vns_minwrite; /* L */ + t_uscalar_t vns_maxwrite; /* L */ + hrtime_t vns_fclatch; /* L */ + hrtime_t vns_fcupdate; /* L */ + kstat_t *vns_kstat; /* E */ + gsqueue_t *vns_squeue; /* E */ + mblk_t vns_drainblk; /* E + X */ + mblk_t vns_barrierblk; /* E + X */ + vnd_str_stat_t vns_ksdata; /* A */ + size_t vns_nflush; /* L */ + size_t vns_bsize; /* L */ + struct vnd_dev *vns_dev; /* E + X */ + struct vnd_pnsd *vns_nsd; /* E + X */ +} vnd_str_t; + +typedef enum vnd_dev_flags { + VND_D_ATTACH_INFLIGHT = 0x001, + VND_D_ATTACHED = 0x002, + VND_D_LINK_INFLIGHT = 0x004, + VND_D_LINKED = 0x008, + VND_D_CONDEMNED = 0x010, + VND_D_ZONE_DYING = 0x020, + VND_D_OPENED = 0x040 +} vnd_dev_flags_t; + +/* + * This represents the data associated with a minor device instance. + * + * See synchronization section of the big theory statement for member + * annotations. + */ +typedef struct vnd_dev { + kmutex_t vdd_lock; + list_node_t vdd_link; /* GL */ + list_node_t vdd_nslink; /* NSL */ + int vdd_ref; /* L */ + vnd_dev_flags_t vdd_flags; /* L */ + minor_t vdd_minor; /* E */ + dev_t vdd_devid; /* E */ + ldi_ident_t vdd_ldiid; /* E */ + ldi_handle_t vdd_ldih; /* X */ + cred_t *vdd_cr; /* X */ + vnd_str_t *vdd_str; /* L */ + struct pollhead vdd_ph; /* E */ + struct vnd_pnsd *vdd_nsd; /* E + X */ + char vdd_datalink[VND_NAMELEN]; /* L */ + char vdd_lname[VND_NAMELEN]; /* L */ +} vnd_dev_t; + +typedef enum vnd_pnsd_flags { + VND_NS_CONDEMNED = 0x1 +} vnd_pnsd_flags_t; + +/* + * Per netstack data structure. + * + * See synchronization section of the big theory statement for member + * annotations. + */ +typedef struct vnd_pnsd { + list_node_t vpnd_link; /* protected by global dev lock */ + zoneid_t vpnd_zid; /* E */ + netstackid_t vpnd_nsid; /* E */ + boolean_t vpnd_hooked; /* E */ + net_handle_t vpnd_neti_v4; /* E */ + hook_family_t vpnd_family_v4; /* E */ + hook_event_t vpnd_event_in_v4; /* E */ + hook_event_t vpnd_event_out_v4; /* E */ + hook_event_token_t vpnd_token_in_v4; /* E */ + hook_event_token_t vpnd_token_out_v4; /* E */ + net_handle_t vpnd_neti_v6; /* E */ + hook_family_t vpnd_family_v6; /* E */ + hook_event_t vpnd_event_in_v6; /* E */ + hook_event_t vpnd_event_out_v6; /* E */ + hook_event_token_t vpnd_token_in_v6; /* E */ + hook_event_token_t vpnd_token_out_v6; /* E */ + kmutex_t vpnd_lock; /* Protects remaining members */ + kcondvar_t vpnd_ref_change; /* Uses vpnd_lock */ + int vpnd_ref; /* L */ + vnd_pnsd_flags_t vpnd_flags; /* L */ + list_t vpnd_dev_list; /* L */ +} vnd_pnsd_t; + +static void vnd_squeue_tx_drain(void *, mblk_t *, gsqueue_t *, void *); + +/* + * Drop function signature. + */ +typedef void (*vnd_dropper_f)(vnd_str_t *, mblk_t *, const char *); + +static void +vnd_drop_ctl(vnd_str_t *vsp, mblk_t *mp, const char *reason) +{ + DTRACE_VND4(drop__ctl, mblk_t *, mp, vnd_str_t *, vsp, mblk_t *, + mp, const char *, reason); + if (mp != NULL) { + freemsg(mp); + } + VND_STAT_INC(vsp, vks_ndlpidrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); +} + +static void +vnd_drop_in(vnd_str_t *vsp, mblk_t *mp, const char *reason) +{ + DTRACE_VND4(drop__in, mblk_t *, mp, vnd_str_t *, vsp, mblk_t *, + mp, const char *, reason); + if (mp != NULL) { + freemsg(mp); + } + VND_STAT_INC(vsp, vks_ndataindrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); +} + +static void +vnd_drop_out(vnd_str_t *vsp, mblk_t *mp, const char *reason) +{ + DTRACE_VND4(drop__out, mblk_t *, mp, vnd_str_t *, vsp, mblk_t *, + mp, const char *, reason); + if (mp != NULL) { + freemsg(mp); + } + VND_STAT_INC(vsp, vks_ndataoutdrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); +} + +static void +vnd_drop_hook_in(vnd_str_t *vsp, mblk_t *mp, const char *reason) +{ + DTRACE_VND4(drop__in, mblk_t *, mp, vnd_str_t *, vsp, mblk_t *, + mp, const char *, reason); + if (mp != NULL) { + freemsg(mp); + } + VND_STAT_INC(vsp, vks_nhookindrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); +} + +static void +vnd_drop_hook_out(vnd_str_t *vsp, mblk_t *mp, const char *reason) +{ + DTRACE_VND4(drop__out, mblk_t *, mp, vnd_str_t *, vsp, mblk_t *, + mp, const char *, reason); + if (mp != NULL) { + freemsg(mp); + } + VND_STAT_INC(vsp, vks_nhookoutdrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); +} + +static void +vnd_drop_panic(vnd_str_t *vsp, mblk_t *mp, const char *reason) +{ + panic("illegal vnd drop"); +} + +static void +vnd_mac_drop_input(vnd_str_t *vsp, mac_resource_t *unused, mblk_t *mp_chain, + mac_header_info_t *mhip) +{ + mblk_t *mp; + + while (mp_chain != NULL) { + mp = mp_chain; + mp_chain = mp->b_next; + vnd_drop_hook_in(vsp, mp, "stream not associated"); + } +} + +static vnd_pnsd_t * +vnd_nsd_lookup(netstackid_t nsid) +{ + vnd_pnsd_t *nsp; + + mutex_enter(&vnd_dev_lock); + for (nsp = list_head(&vnd_nsd_list); nsp != NULL; + nsp = list_next(&vnd_nsd_list, nsp)) { + if (nsp->vpnd_nsid == nsid) { + mutex_enter(&nsp->vpnd_lock); + VERIFY(nsp->vpnd_ref >= 0); + nsp->vpnd_ref++; + mutex_exit(&nsp->vpnd_lock); + break; + } + } + mutex_exit(&vnd_dev_lock); + return (nsp); +} + +static vnd_pnsd_t * +vnd_nsd_lookup_by_zid(zoneid_t zid) +{ + netstack_t *ns; + vnd_pnsd_t *nsp; + ns = netstack_find_by_zoneid(zid); + if (ns == NULL) + return (NULL); + nsp = vnd_nsd_lookup(ns->netstack_stackid); + netstack_rele(ns); + return (nsp); +} + +static vnd_pnsd_t * +vnd_nsd_lookup_by_zonename(char *zname) +{ + zone_t *zonep; + vnd_pnsd_t *nsp; + + zonep = zone_find_by_name(zname); + if (zonep == NULL) + return (NULL); + + nsp = vnd_nsd_lookup_by_zid(zonep->zone_id); + zone_rele(zonep); + return (nsp); +} + +static void +vnd_nsd_ref(vnd_pnsd_t *nsp) +{ + mutex_enter(&nsp->vpnd_lock); + /* + * This can only be used on something that has been obtained through + * some other means. As such, the caller should already have a reference + * before adding another one. This function should not be used as a + * means of creating the initial reference. + */ + VERIFY(nsp->vpnd_ref > 0); + nsp->vpnd_ref++; + mutex_exit(&nsp->vpnd_lock); + cv_broadcast(&nsp->vpnd_ref_change); +} + +static void +vnd_nsd_rele(vnd_pnsd_t *nsp) +{ + mutex_enter(&nsp->vpnd_lock); + VERIFY(nsp->vpnd_ref > 0); + nsp->vpnd_ref--; + mutex_exit(&nsp->vpnd_lock); + cv_broadcast(&nsp->vpnd_ref_change); +} + +static vnd_dev_t * +vnd_dev_lookup(minor_t m) +{ + vnd_dev_t *vdp; + mutex_enter(&vnd_dev_lock); + for (vdp = list_head(&vnd_dev_list); vdp != NULL; + vdp = list_next(&vnd_dev_list, vdp)) { + if (vdp->vdd_minor == m) { + mutex_enter(&vdp->vdd_lock); + VERIFY(vdp->vdd_ref > 0); + vdp->vdd_ref++; + DTRACE_VND_REFINC(vdp); + mutex_exit(&vdp->vdd_lock); + break; + } + } + mutex_exit(&vnd_dev_lock); + return (vdp); +} + +static void +vnd_dev_free(vnd_dev_t *vdp) +{ + /* + * When the STREAM exists we need to go through and make sure + * communication gets torn down. As part of closing the stream, we + * guarantee that nothing else should be able to enter the stream layer + * at this point. That means no one should be able to call + * read(),write() or one of the frameio ioctls. + */ + if (vdp->vdd_flags & VND_D_ATTACHED) { + ldi_close(vdp->vdd_ldih, FREAD | FWRITE, vdp->vdd_cr); + crfree(vdp->vdd_cr); + vdp->vdd_cr = NULL; + + /* + * We have to remove ourselves from our parents list now. It is + * really quite important that we have already set the condemend + * flag here so that our containing netstack basically knows + * that we're on the way down and knows not to wait for us. It's + * also important that we do that before we put a rele on the + * the device as that is the point at which it will check again. + */ + mutex_enter(&vdp->vdd_nsd->vpnd_lock); + list_remove(&vdp->vdd_nsd->vpnd_dev_list, vdp); + mutex_exit(&vdp->vdd_nsd->vpnd_lock); + vnd_nsd_rele(vdp->vdd_nsd); + vdp->vdd_nsd = NULL; + } + ASSERT(vdp->vdd_flags & VND_D_CONDEMNED); + id_free(vnd_minors, vdp->vdd_minor); + mutex_destroy(&vdp->vdd_lock); + kmem_cache_free(vnd_dev_cache, vdp); +} + +static void +vnd_dev_ref(vnd_dev_t *vdp) +{ + mutex_enter(&vdp->vdd_lock); + VERIFY(vdp->vdd_ref > 0); + vdp->vdd_ref++; + DTRACE_VND_REFINC(vdp); + mutex_exit(&vdp->vdd_lock); +} + +/* + * As part of releasing the hold on this we may tear down a given vnd_dev_t As + * such we need to make sure that we grab the list lock first before grabbing + * the vnd_dev_t's lock to ensure proper lock ordering. + */ +static void +vnd_dev_rele(vnd_dev_t *vdp) +{ + mutex_enter(&vnd_dev_lock); + mutex_enter(&vdp->vdd_lock); + VERIFY(vdp->vdd_ref > 0); + vdp->vdd_ref--; + DTRACE_VND_REFDEC(vdp); + if (vdp->vdd_ref > 0) { + mutex_exit(&vdp->vdd_lock); + mutex_exit(&vnd_dev_lock); + return; + } + + /* + * Now that we've removed this from the list, we can go ahead and + * drop the list lock. No one else can find this device and reference + * it. As its reference count is zero, it by definition does not have + * any remaining entries in /devices that could lead someone back to + * this. + */ + vdp->vdd_flags |= VND_D_CONDEMNED; + list_remove(&vnd_dev_list, vdp); + mutex_exit(&vdp->vdd_lock); + mutex_exit(&vnd_dev_lock); + + vnd_dev_free(vdp); +} + +/* + * Insert a mesage block chain if there's space, otherwise drop it. Return one + * so someone who was waiting for data would now end up having found it. eg. + * caller should consider a broadcast. + */ +static int +vnd_dq_push(vnd_data_queue_t *vqp, mblk_t *mp, boolean_t reserved, + vnd_dropper_f dropf) +{ + size_t msize; + + ASSERT(MUTEX_HELD(&vqp->vdq_lock)); + if (reserved == B_FALSE) { + msize = msgsize(mp); + if (vqp->vdq_cur + msize > vqp->vdq_max) { + dropf(vqp->vdq_vns, mp, "buffer full"); + return (0); + } + vqp->vdq_cur += msize; + } + + if (vqp->vdq_head == NULL) { + ASSERT(vqp->vdq_tail == NULL); + vqp->vdq_head = mp; + vqp->vdq_tail = mp; + } else { + vqp->vdq_tail->b_next = mp; + vqp->vdq_tail = mp; + } + + return (1); +} + +/* + * Remove a message message block chain. If the amount of space in the buffer + * has changed we return 1. We have no way of knowing whether or not there is + * enough space overall for a given writer who is blocked, so we always end up + * having to return true and thus tell consumers that they should consider + * signalling. + */ +static int +vnd_dq_pop(vnd_data_queue_t *vqp, mblk_t **mpp) +{ + size_t msize; + mblk_t *mp; + + ASSERT(MUTEX_HELD(&vqp->vdq_lock)); + ASSERT(mpp != NULL); + if (vqp->vdq_head == NULL) { + ASSERT(vqp->vdq_tail == NULL); + *mpp = NULL; + return (0); + } + + mp = vqp->vdq_head; + msize = msgsize(mp); + + vqp->vdq_cur -= msize; + if (mp->b_next == NULL) { + vqp->vdq_head = NULL; + vqp->vdq_tail = NULL; + /* + * We can't be certain that this is always going to be zero. + * Someone may have basically taken a reservation of space on + * the data queue, eg. claimed spae but not yet pushed it on + * yet. + */ + ASSERT(vqp->vdq_cur >= 0); + } else { + vqp->vdq_head = mp->b_next; + ASSERT(vqp->vdq_cur > 0); + } + mp->b_next = NULL; + *mpp = mp; + return (1); +} + +/* + * Reserve space in the queue. This will bump up the size of the queue and + * entitle the user to push something on later without bumping the space. + */ +static int +vnd_dq_reserve(vnd_data_queue_t *vqp, ssize_t size) +{ + ASSERT(MUTEX_HELD(&vqp->vdq_lock)); + ASSERT(size >= 0); + + if (size == 0) + return (0); + + if (size + vqp->vdq_cur > vqp->vdq_max) + return (0); + + vqp->vdq_cur += size; + return (1); +} + +static void +vnd_dq_unreserve(vnd_data_queue_t *vqp, ssize_t size) +{ + ASSERT(MUTEX_HELD(&vqp->vdq_lock)); + ASSERT(size > 0); + ASSERT(size <= vqp->vdq_cur); + + vqp->vdq_cur -= size; +} + +static void +vnd_dq_flush(vnd_data_queue_t *vqp, vnd_dropper_f dropf) +{ + mblk_t *mp, *next; + + mutex_enter(&vqp->vdq_lock); + for (mp = vqp->vdq_head; mp != NULL; mp = next) { + next = mp->b_next; + mp->b_next = NULL; + dropf(vqp->vdq_vns, mp, "vnd_dq_flush"); + } + vqp->vdq_cur = 0; + vqp->vdq_head = NULL; + vqp->vdq_tail = NULL; + mutex_exit(&vqp->vdq_lock); +} + +static boolean_t +vnd_dq_is_empty(vnd_data_queue_t *vqp) +{ + boolean_t ret; + + mutex_enter(&vqp->vdq_lock); + if (vqp->vdq_head == NULL) + ret = B_TRUE; + else + ret = B_FALSE; + mutex_exit(&vqp->vdq_lock); + + return (ret); +} + +/* + * Get a network uint16_t from the message and translate it into something the + * host understands. + */ +static int +vnd_mbc_getu16(mblk_t *mp, off_t off, uint16_t *out) +{ + size_t mpsize; + uint8_t *bp; + + mpsize = msgsize(mp); + /* Check for overflow */ + if (off + sizeof (uint16_t) > mpsize) + return (1); + + mpsize = MBLKL(mp); + while (off >= mpsize) { + mp = mp->b_cont; + off -= mpsize; + mpsize = MBLKL(mp); + } + + /* + * Data is in network order. Note the second byte of data might be in + * the next mp. + */ + bp = mp->b_rptr + off; + *out = *bp << 8; + if (off + 1 == mpsize) { + mp = mp->b_cont; + bp = mp->b_rptr; + } else { + bp++; + } + + *out |= *bp; + return (0); +} + +/* + * Given an mblk chain find the mblk and address of a particular offset. + */ +static int +vnd_mbc_getoffset(mblk_t *mp, off_t off, mblk_t **mpp, uintptr_t *offp) +{ + size_t mpsize; + + if (off >= msgsize(mp)) + return (1); + + mpsize = MBLKL(mp); + while (off >= mpsize) { + mp = mp->b_cont; + off -= mpsize; + mpsize = MBLKL(mp); + } + *mpp = mp; + *offp = (uintptr_t)mp->b_rptr + off; + + return (0); +} + +/* + * Fetch the destination mac address. Set *dstp to that mac address. If the data + * is not contiguous in the first mblk_t, fill in datap and set *dstp to it. + */ +static int +vnd_mbc_getdstmac(mblk_t *mp, uint8_t **dstpp, uint8_t *datap) +{ + int i; + + if (MBLKL(mp) >= ETHERADDRL) { + *dstpp = mp->b_rptr; + return (0); + } + + *dstpp = datap; + for (i = 0; i < ETHERADDRL; i += 2, datap += 2) { + if (vnd_mbc_getu16(mp, i, (uint16_t *)datap) != 0) + return (1); + } + + return (0); +} + +static int +vnd_hook(vnd_str_t *vsp, mblk_t **mpp, net_handle_t netiv4, hook_event_t hev4, + hook_event_token_t hetv4, net_handle_t netiv6, hook_event_t hev6, + hook_event_token_t hetv6, vnd_dropper_f hdrop, vnd_dropper_f ddrop) +{ + uint16_t etype; + int vlan = 0; + hook_pkt_event_t info; + size_t offset, mblen; + uint8_t *dstp; + uint8_t dstaddr[6]; + hook_event_t he; + hook_event_token_t het; + net_handle_t neti; + + /* + * Before we can ask if we're interested we have to do enough work to + * determine the ethertype. + */ + + /* Byte 12 is either the VLAN tag or the ethertype */ + if (vnd_mbc_getu16(*mpp, 12, &etype) != 0) { + ddrop(vsp, *mpp, "packet has incomplete ethernet header"); + *mpp = NULL; + return (1); + } + + if (etype == ETHERTYPE_VLAN) { + vlan = 1; + /* Actual ethertype is another four bytes in */ + if (vnd_mbc_getu16(*mpp, 16, &etype) != 0) { + ddrop(vsp, *mpp, + "packet has incomplete ethernet vlan header"); + *mpp = NULL; + return (1); + } + offset = sizeof (struct ether_vlan_header); + } else { + offset = sizeof (struct ether_header); + } + + /* + * At the moment we only hook on the kinds of things that the IP module + * would normally. + */ + if (etype != ETHERTYPE_IP && etype != ETHERTYPE_IPV6) + return (0); + + if (etype == ETHERTYPE_IP) { + neti = netiv4; + he = hev4; + het = hetv4; + } else { + neti = netiv6; + he = hev6; + het = hetv6; + } + + if (!he.he_interested) + return (0); + + + if (vnd_mbc_getdstmac(*mpp, &dstp, dstaddr) != 0) { + ddrop(vsp, *mpp, "packet has incomplete ethernet header"); + *mpp = NULL; + return (1); + } + + /* + * Now that we know we're interested, we have to do some additional + * sanity checking for IPF's sake, ala ip_check_length(). Specifically + * we need to check to make sure that the remaining packet size, + * excluding MAC, is at least the size of an IP header. + */ + mblen = msgsize(*mpp); + if ((etype == ETHERTYPE_IP && + mblen - offset < IP_SIMPLE_HDR_LENGTH) || + (etype == ETHERTYPE_IPV6 && mblen - offset < IPV6_HDR_LEN)) { + ddrop(vsp, *mpp, "packet has invalid IP header"); + *mpp = NULL; + return (1); + } + + info.hpe_protocol = neti; + info.hpe_ifp = (phy_if_t)vsp; + info.hpe_ofp = (phy_if_t)vsp; + info.hpe_mp = mpp; + info.hpe_flags = 0; + + if (bcmp(vnd_bcast_addr, dstp, ETHERADDRL) == 0) + info.hpe_flags |= HPE_BROADCAST; + else if (etype == ETHERTYPE_IP && + bcmp(vnd_ipv4_mcast, vnd_bcast_addr, IPV4_MCAST_LEN) == 0) + info.hpe_flags |= HPE_MULTICAST; + else if (etype == ETHERTYPE_IPV6 && + bcmp(vnd_ipv6_mcast, vnd_bcast_addr, IPV6_MCAST_LEN) == 0) + info.hpe_flags |= HPE_MULTICAST; + + if (vnd_mbc_getoffset(*mpp, offset, &info.hpe_mb, + (uintptr_t *)&info.hpe_hdr) != 0) { + ddrop(vsp, *mpp, "packet too small -- " + "unable to find payload"); + *mpp = NULL; + return (1); + } + + if (hook_run(neti->netd_hooks, het, (hook_data_t)&info) != 0) { + hdrop(vsp, *mpp, "drooped by hooks"); + return (1); + } + + return (0); +} + +/* + * This should not be used for DL_INFO_REQ. + */ +static mblk_t * +vnd_dlpi_alloc(size_t len, t_uscalar_t prim) +{ + mblk_t *mp; + mp = allocb(len, BPRI_MED); + if (mp == NULL) + return (NULL); + + mp->b_datap->db_type = M_PROTO; + mp->b_wptr = mp->b_rptr + len; + bzero(mp->b_rptr, len); + ((dl_unitdata_req_t *)mp->b_rptr)->dl_primitive = prim; + + return (mp); +} + +static void +vnd_dlpi_inc_push(vnd_str_t *vsp, mblk_t *mp) +{ + mblk_t **mpp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + ASSERT(mp->b_next == NULL); + mpp = &vsp->vns_dlpi_inc; + while (*mpp != NULL) + mpp = &((*mpp)->b_next); + *mpp = mp; +} + +static mblk_t * +vnd_dlpi_inc_pop(vnd_str_t *vsp) +{ + mblk_t *mp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vsp->vns_dlpi_inc; + if (mp != NULL) { + VERIFY(mp->b_next == NULL || mp->b_next != mp); + vsp->vns_dlpi_inc = mp->b_next; + mp->b_next = NULL; + } + return (mp); +} + +static int +vnd_st_sinfo(vnd_str_t *vsp) +{ + mblk_t *mp; + dl_info_req_t *dlir; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = allocb(MAX(sizeof (dl_info_req_t), sizeof (dl_info_ack_t)), + BPRI_HI); + if (mp == NULL) { + vsp->vns_errno = VND_E_NOMEM; + return (1); + } + vsp->vns_state = VNS_S_INFO_SENT; + cv_broadcast(&vsp->vns_stcv); + + mp->b_datap->db_type = M_PCPROTO; + dlir = (dl_info_req_t *)mp->b_rptr; + mp->b_wptr = (uchar_t *)&dlir[1]; + dlir->dl_primitive = DL_INFO_REQ; + putnext(vsp->vns_wq, mp); + + return (0); +} + +static int +vnd_st_info(vnd_str_t *vsp) +{ + dl_info_ack_t *dlia; + mblk_t *mp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_inc_pop(vsp); + dlia = (dl_info_ack_t *)mp->b_rptr; + vsp->vns_dlpi_style = dlia->dl_provider_style; + vsp->vns_minwrite = dlia->dl_min_sdu; + vsp->vns_maxwrite = dlia->dl_max_sdu; + + /* + * At this time we only support DL_ETHER devices. + */ + if (dlia->dl_mac_type != DL_ETHER) { + freemsg(mp); + vsp->vns_errno = VND_E_NOTETHER; + return (1); + } + + /* + * Because vnd operates on entire packets, we need to manually account + * for the ethernet header information. We add the size of the + * ether_vlan_header to account for this, regardless if it is using + * vlans or not. + */ + vsp->vns_maxwrite += sizeof (struct ether_vlan_header); + + freemsg(mp); + return (0); +} + +static int +vnd_st_sexclusive(vnd_str_t *vsp) +{ + mblk_t *mp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_alloc(sizeof (dl_attach_req_t), DL_EXCLUSIVE_REQ); + if (mp == NULL) { + vsp->vns_errno = VND_E_NOMEM; + return (1); + } + + vsp->vns_state = VNS_S_EXCLUSIVE_SENT; + cv_broadcast(&vsp->vns_stcv); + putnext(vsp->vns_wq, mp); + return (0); +} + +static int +vnd_st_exclusive(vnd_str_t *vsp) +{ + mblk_t *mp; + t_uscalar_t prim, cprim; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_inc_pop(vsp); + prim = ((dl_error_ack_t *)mp->b_rptr)->dl_primitive; + cprim = ((dl_ok_ack_t *)mp->b_rptr)->dl_correct_primitive; + + if (prim != DL_OK_ACK && prim != DL_ERROR_ACK) { + vnd_drop_ctl(vsp, mp, + "wrong dlpi primitive for vnd_st_exclusive"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (cprim != DL_EXCLUSIVE_REQ) { + vnd_drop_ctl(vsp, mp, + "vnd_st_exclusive: got ack/nack for wrong primitive"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (prim == DL_ERROR_ACK) + vsp->vns_errno = VND_E_DLEXCL; + + freemsg(mp); + return (prim == DL_ERROR_ACK); +} + +/* + * Send down a DLPI_ATTACH_REQ. + */ +static int +vnd_st_sattach(vnd_str_t *vsp) +{ + mblk_t *mp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_alloc(sizeof (dl_attach_req_t), DL_ATTACH_REQ); + if (mp == NULL) { + vsp->vns_errno = VND_E_NOMEM; + return (1); + } + + ((dl_attach_req_t *)mp->b_rptr)->dl_ppa = 0; + vsp->vns_state = VNS_S_ATTACH_SENT; + cv_broadcast(&vsp->vns_stcv); + putnext(vsp->vns_wq, mp); + + return (0); +} + +static int +vnd_st_attach(vnd_str_t *vsp) +{ + mblk_t *mp; + t_uscalar_t prim, cprim; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_inc_pop(vsp); + prim = ((dl_ok_ack_t *)mp->b_rptr)->dl_primitive; + cprim = ((dl_ok_ack_t *)mp->b_rptr)->dl_correct_primitive; + + + if (prim != DL_OK_ACK && prim != DL_ERROR_ACK) { + vnd_drop_ctl(vsp, mp, "vnd_st_attach: unknown primitive type"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (cprim != DL_ATTACH_REQ) { + vnd_drop_ctl(vsp, mp, + "vnd_st_attach: Got ack/nack for wrong primitive"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (prim == DL_ERROR_ACK) + vsp->vns_errno = VND_E_ATTACHFAIL; + + freemsg(mp); + return (prim == DL_ERROR_ACK); +} + +static int +vnd_st_sbind(vnd_str_t *vsp) +{ + mblk_t *mp; + dl_bind_req_t *dbrp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_alloc(sizeof (dl_bind_req_t) + sizeof (long), + DL_BIND_REQ); + if (mp == NULL) { + vsp->vns_errno = VND_E_NOMEM; + return (1); + } + dbrp = (dl_bind_req_t *)(mp->b_rptr); + dbrp->dl_sap = 0; + dbrp->dl_service_mode = DL_CLDLS; + + vsp->vns_state = VNS_S_BIND_SENT; + cv_broadcast(&vsp->vns_stcv); + putnext(vsp->vns_wq, mp); + + return (0); +} + +static int +vnd_st_bind(vnd_str_t *vsp) +{ + mblk_t *mp; + t_uscalar_t prim; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_inc_pop(vsp); + prim = ((dl_error_ack_t *)mp->b_rptr)->dl_primitive; + + if (prim != DL_BIND_ACK && prim != DL_ERROR_ACK) { + vnd_drop_ctl(vsp, mp, "wrong dlpi primitive for vnd_st_bind"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (prim == DL_ERROR_ACK) + vsp->vns_errno = VND_E_BINDFAIL; + + freemsg(mp); + return (prim == DL_ERROR_ACK); +} + +static int +vnd_st_spromisc(vnd_str_t *vsp, int type, vnd_str_state_t next) +{ + mblk_t *mp; + dl_promiscon_req_t *dprp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_alloc(sizeof (dl_promiscon_req_t), DL_PROMISCON_REQ); + if (mp == NULL) { + vsp->vns_errno = VND_E_NOMEM; + return (1); + } + + dprp = (dl_promiscon_req_t *)mp->b_rptr; + dprp->dl_level = type; + + vsp->vns_state = next; + cv_broadcast(&vsp->vns_stcv); + putnext(vsp->vns_wq, mp); + + return (0); +} + +static int +vnd_st_promisc(vnd_str_t *vsp) +{ + mblk_t *mp; + t_uscalar_t prim, cprim; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_inc_pop(vsp); + prim = ((dl_ok_ack_t *)mp->b_rptr)->dl_primitive; + cprim = ((dl_ok_ack_t *)mp->b_rptr)->dl_correct_primitive; + + if (prim != DL_OK_ACK && prim != DL_ERROR_ACK) { + vnd_drop_ctl(vsp, mp, + "wrong dlpi primitive for vnd_st_promisc"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (cprim != DL_PROMISCON_REQ) { + vnd_drop_ctl(vsp, mp, + "vnd_st_promisc: Got ack/nack for wrong primitive"); + vsp->vns_errno = VND_E_DLPIINVAL; + return (1); + } + + if (prim == DL_ERROR_ACK) + vsp->vns_errno = VND_E_PROMISCFAIL; + + freemsg(mp); + return (prim == DL_ERROR_ACK); +} + +static int +vnd_st_scapabq(vnd_str_t *vsp) +{ + mblk_t *mp; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + + mp = vnd_dlpi_alloc(sizeof (dl_capability_req_t), DL_CAPABILITY_REQ); + if (mp == NULL) { + vsp->vns_errno = VND_E_NOMEM; + return (1); + } + + vsp->vns_state = VNS_S_CAPAB_Q_SENT; + cv_broadcast(&vsp->vns_stcv); + putnext(vsp->vns_wq, mp); + + return (0); +} + +static void +vnd_mac_input(vnd_str_t *vsp, mac_resource_t *unused, mblk_t *mp_chain, + mac_header_info_t *mhip) +{ + int signal = 0; + mblk_t *mp; + vnd_pnsd_t *nsp = vsp->vns_nsd; + + ASSERT(vsp != NULL); + ASSERT(mp_chain != NULL); + + for (mp = mp_chain; mp != NULL; mp = mp_chain) { + uint16_t vid; + mp_chain = mp->b_next; + mp->b_next = NULL; + + /* + * If we were operating in a traditional dlpi context then we + * would have enabled DLIOCRAW and rather than the fast path we + * would come through dld_str_rx_raw. That function does two + * things that we have to consider doing ourselves. The first is + * that it adjusts the b_rptr back to account for dld bumping us + * past the mac header. It also tries to account for cases where + * mac provides an illusion of the mac header. Fortunately, dld + * only allows the fastpath when the media type is the same as + * the native type. Therefore all we have to do here is adjust + * the b_rptr. + */ + ASSERT(mp->b_rptr >= DB_BASE(mp) + mhip->mhi_hdrsize); + mp->b_rptr -= mhip->mhi_hdrsize; + vid = VLAN_ID(mhip->mhi_tci); + if (mhip->mhi_istagged && vid != VLAN_ID_NONE) { + bcopy(mp->b_rptr, mp->b_rptr + 4, 12); + mp->b_rptr += 4; + } + + if (nsp->vpnd_hooked && vnd_hook(vsp, &mp, nsp->vpnd_neti_v4, + nsp->vpnd_event_in_v4, nsp->vpnd_token_in_v4, + nsp->vpnd_neti_v6, nsp->vpnd_event_in_v6, + nsp->vpnd_token_in_v6, vnd_drop_hook_in, vnd_drop_in) != 0) + continue; + + VND_STAT_INC(vsp, vks_rpackets, 1); + VND_STAT_INC(vsp, vks_rbytes, msgsize(mp)); + DTRACE_VND5(recv, mblk_t *, mp, void *, NULL, void *, NULL, + vnd_str_t *, vsp, mblk_t *, mp); + mutex_enter(&vsp->vns_dq_read.vdq_lock); + signal |= vnd_dq_push(&vsp->vns_dq_read, mp, B_FALSE, + vnd_drop_in); + mutex_exit(&vsp->vns_dq_read.vdq_lock); + + } + + if (signal != 0) { + cv_broadcast(&vsp->vns_dq_read.vdq_ready); + pollwakeup(&vsp->vns_dev->vdd_ph, POLLIN | POLLRDNORM); + } + +} + +static void +vnd_mac_flow_control_stat(vnd_str_t *vsp, hrtime_t diff) +{ + VND_STAT_INC(vsp, vks_nmacflow, 1); + VND_STAT_INC(vsp, vks_tmacflow, diff); + if (diff >= VND_LATENCY_1MS) + VND_STAT_INC(vsp, vks_mac_flow_1ms, 1); + if (diff >= VND_LATENCY_10MS) + VND_STAT_INC(vsp, vks_mac_flow_10ms, 1); + if (diff >= VND_LATENCY_100MS) + VND_STAT_INC(vsp, vks_mac_flow_100ms, 1); + if (diff >= VND_LATENCY_1S) + VND_STAT_INC(vsp, vks_mac_flow_1s, 1); + if (diff >= VND_LATENCY_10S) + VND_STAT_INC(vsp, vks_mac_flow_10s, 1); +} + +/* + * This is a callback from MAC that indicates that we are allowed to send + * packets again. + */ +static void +vnd_mac_flow_control(void *arg, vnd_mac_cookie_t cookie) +{ + vnd_str_t *vsp = arg; + hrtime_t now, diff; + + mutex_enter(&vsp->vns_lock); + now = gethrtime(); + + /* + * Check for the case that we beat vnd_squeue_tx_one to the punch. + * There's also an additional case here that we got notified because + * we're sharing a device that ran out of tx descriptors, even though it + * wasn't because of us. + */ + if (!(vsp->vns_flags & VNS_F_FLOW_CONTROLLED)) { + vsp->vns_fcupdate = now; + mutex_exit(&vsp->vns_lock); + return; + } + + ASSERT(vsp->vns_flags & VNS_F_FLOW_CONTROLLED); + ASSERT(vsp->vns_caps.vsc_fc_cookie == cookie); + vsp->vns_flags &= ~VNS_F_FLOW_CONTROLLED; + vsp->vns_caps.vsc_fc_cookie = NULL; + diff = now - vsp->vns_fclatch; + vsp->vns_fclatch = 0; + DTRACE_VND3(flow__resumed, vnd_str_t *, vsp, uint64_t, + vsp->vns_dq_write.vdq_cur, uintptr_t, cookie); + /* + * If someone has asked to flush the squeue and thus inserted a barrier, + * than we shouldn't schedule a drain. + */ + if (!(vsp->vns_flags & (VNS_F_DRAIN_SCHEDULED | VNS_F_BARRIER))) { + vsp->vns_flags |= VNS_F_DRAIN_SCHEDULED; + gsqueue_enter_one(vsp->vns_squeue, &vsp->vns_drainblk, + vnd_squeue_tx_drain, vsp, GSQUEUE_FILL, + VND_SQUEUE_TAG_MAC_FLOW_CONTROL); + } + mutex_exit(&vsp->vns_lock); +} + +static void +vnd_mac_enter(vnd_str_t *vsp, mac_perim_handle_t *mphp) +{ + ASSERT(MUTEX_HELD(&vsp->vns_lock)); + VERIFY(vsp->vns_caps.vsc_capab_f(vsp->vns_caps.vsc_capab_hdl, + DLD_CAPAB_PERIM, mphp, DLD_ENABLE) == 0); +} + +static void +vnd_mac_exit(vnd_str_t *vsp, mac_perim_handle_t mph) +{ + ASSERT(MUTEX_HELD(&vsp->vns_lock)); + VERIFY(vsp->vns_caps.vsc_capab_f(vsp->vns_caps.vsc_capab_hdl, + DLD_CAPAB_PERIM, mph, DLD_DISABLE) == 0); +} + +static int +vnd_dld_cap_enable(vnd_str_t *vsp, vnd_rx_t rxfunc) +{ + int ret; + dld_capab_direct_t d; + mac_perim_handle_t mph; + vnd_str_capab_t *c = &vsp->vns_caps; + + bzero(&d, sizeof (d)); + d.di_rx_cf = (uintptr_t)rxfunc; + d.di_rx_ch = vsp; + d.di_flags = DI_DIRECT_RAW; + + vnd_mac_enter(vsp, &mph); + + /* + * If we're coming in here for a second pass, we need to make sure that + * we remove an existing flow control notification callback, otherwise + * we'll create a duplicate that will remain with garbage data. + */ + if (c->vsc_tx_fc_hdl != NULL) { + ASSERT(c->vsc_set_fcb_hdl != NULL); + (void) c->vsc_set_fcb_f(c->vsc_set_fcb_hdl, NULL, + c->vsc_tx_fc_hdl); + c->vsc_tx_fc_hdl = NULL; + } + + if (vsp->vns_caps.vsc_capab_f(c->vsc_capab_hdl, + DLD_CAPAB_DIRECT, &d, DLD_ENABLE) == 0) { + c->vsc_tx_f = (vnd_dld_tx_t)d.di_tx_df; + c->vsc_tx_hdl = d.di_tx_dh; + c->vsc_set_fcb_f = (vnd_dld_set_fcb_t)d.di_tx_cb_df; + c->vsc_set_fcb_hdl = d.di_tx_cb_dh; + c->vsc_is_fc_f = (vnd_dld_is_fc_t)d.di_tx_fctl_df; + c->vsc_is_fc_hdl = d.di_tx_fctl_dh; + c->vsc_tx_fc_hdl = c->vsc_set_fcb_f(c->vsc_set_fcb_hdl, + vnd_mac_flow_control, vsp); + c->vsc_flags |= VNS_C_DIRECT; + ret = 0; + } else { + vsp->vns_errno = VND_E_DIRECTFAIL; + ret = 1; + } + vnd_mac_exit(vsp, mph); + return (ret); +} + +static int +vnd_st_capabq(vnd_str_t *vsp) +{ + mblk_t *mp; + dl_capability_ack_t *cap; + dl_capability_sub_t *subp; + dl_capab_hcksum_t *hck; + dl_capab_dld_t *dld; + unsigned char *rp; + int ret = 0; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + mp = vnd_dlpi_inc_pop(vsp); + + rp = mp->b_rptr; + cap = (dl_capability_ack_t *)rp; + if (cap->dl_sub_length == 0) + goto done; + + /* Don't try to process something too big */ + if (sizeof (dl_capability_ack_t) + cap->dl_sub_length > MBLKL(mp)) { + VND_STAT_INC(vsp, vks_ndlpidrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); + vsp->vns_errno = VND_E_CAPACKINVAL; + ret = 1; + goto done; + } + + rp += cap->dl_sub_offset; + + while (cap->dl_sub_length > 0) { + subp = (dl_capability_sub_t *)rp; + /* Sanity check something crazy from down below */ + if (subp->dl_length + sizeof (dl_capability_sub_t) > + cap->dl_sub_length) { + VND_STAT_INC(vsp, vks_ndlpidrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); + vsp->vns_errno = VND_E_SUBCAPINVAL; + ret = 1; + goto done; + } + + switch (subp->dl_cap) { + case DL_CAPAB_HCKSUM: + hck = (dl_capab_hcksum_t *)(rp + + sizeof (dl_capability_sub_t)); + if (hck->hcksum_version != HCKSUM_CURRENT_VERSION) { + vsp->vns_caps.vsc_flags |= VNS_C_HCKSUM_BADVERS; + break; + } + if (dlcapabcheckqid(&hck->hcksum_mid, vsp->vns_lrq) != + B_TRUE) { + vsp->vns_errno = VND_E_CAPABPASS; + ret = 1; + goto done; + } + vsp->vns_caps.vsc_flags |= VNS_C_HCKSUM; + vsp->vns_caps.vsc_hcksum_opts = hck->hcksum_txflags; + break; + case DL_CAPAB_DLD: + dld = (dl_capab_dld_t *)(rp + + sizeof (dl_capability_sub_t)); + if (dld->dld_version != DLD_CURRENT_VERSION) { + vsp->vns_errno = VND_E_DLDBADVERS; + ret = 1; + goto done; + } + if (dlcapabcheckqid(&dld->dld_mid, vsp->vns_lrq) != + B_TRUE) { + vsp->vns_errno = VND_E_CAPABPASS; + ret = 1; + goto done; + } + vsp->vns_caps.vsc_flags |= VNS_C_DLD; + vsp->vns_caps.vsc_capab_f = + (vnd_dld_cap_t)dld->dld_capab; + vsp->vns_caps.vsc_capab_hdl = + (void *)dld->dld_capab_handle; + /* + * At this point in time, we have to set up a direct + * function that drops all input. This validates that + * we'll be able to set up direct input and that we can + * easily switch it earlier to the real data function + * when we've plumbed everything up. + */ + if (vnd_dld_cap_enable(vsp, vnd_mac_drop_input) != 0) { + /* vns_errno set by vnd_dld_cap_enable */ + ret = 1; + goto done; + } + break; + default: + /* Ignore unsupported cap */ + break; + } + + rp += sizeof (dl_capability_sub_t) + subp->dl_length; + cap->dl_sub_length -= sizeof (dl_capability_sub_t) + + subp->dl_length; + } + +done: + /* Make sure we enabled direct callbacks */ + if (ret == 0 && !(vsp->vns_caps.vsc_flags & VNS_C_DIRECT)) { + vsp->vns_errno = VND_E_DIRECTNOTSUP; + ret = 1; + } + + freemsg(mp); + return (ret); +} + +static void +vnd_st_sonline(vnd_str_t *vsp) +{ + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + vsp->vns_state = VNS_S_ONLINE; + cv_broadcast(&vsp->vns_stcv); +} + +static void +vnd_st_shutdown(vnd_str_t *vsp) +{ + mac_perim_handle_t mph; + vnd_str_capab_t *vsc = &vsp->vns_caps; + + VERIFY(MUTEX_HELD(&vsp->vns_lock)); + + /* + * At this point in time we know that there is no one transmitting as + * our final reference has been torn down and that vnd_s_close inserted + * a barrier to validate that everything is flushed. + */ + if (vsc->vsc_flags & VNS_C_DIRECT) { + vnd_mac_enter(vsp, &mph); + vsc->vsc_flags &= ~VNS_C_DIRECT; + (void) vsc->vsc_set_fcb_f(vsc->vsc_set_fcb_hdl, NULL, + vsc->vsc_tx_fc_hdl); + vsc->vsc_tx_fc_hdl = NULL; + (void) vsc->vsc_capab_f(vsc->vsc_capab_hdl, DLD_CAPAB_DIRECT, + NULL, DLD_DISABLE); + vnd_mac_exit(vsp, mph); + } + + /* + * We could send an unbind, but dld also does that for us. As we add + * more capabilities and the like, we should revisit this. + */ + vsp->vns_state = VNS_S_ZOMBIE; + cv_broadcast(&vsp->vns_stcv); +} + +/* + * Perform state transitions. This is a one way shot down the flow chart + * described in the big theory statement. + */ +static void +vnd_str_state_transition(void *arg) +{ + boolean_t died = B_FALSE; + vnd_str_t *vsp = arg; + mblk_t *mp; + + mutex_enter(&vsp->vns_lock); + if (vsp->vns_dlpi_inc == NULL && (vsp->vns_state != VNS_S_INITIAL && + vsp->vns_state != VNS_S_SHUTTING_DOWN)) { + mutex_exit(&vsp->vns_lock); + return; + } + DTRACE_PROBE2(vnd__state__transition, uintptr_t, vsp, + vnd_str_state_t, vsp->vns_state); + switch (vsp->vns_state) { + case VNS_S_INITIAL: + VERIFY(vsp->vns_dlpi_inc == NULL); + if (vnd_st_sinfo(vsp) != 0) + died = B_TRUE; + break; + case VNS_S_INFO_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_info(vsp) == 0) { + if (vnd_st_sexclusive(vsp) != 0) + died = B_TRUE; + } else { + died = B_TRUE; + } + break; + case VNS_S_EXCLUSIVE_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_exclusive(vsp) == 0) { + if (vsp->vns_dlpi_style == DL_STYLE2) { + if (vnd_st_sattach(vsp) != 0) + died = B_TRUE; + } else { + if (vnd_st_sbind(vsp) != 0) + died = B_TRUE; + } + } else { + died = B_TRUE; + } + break; + case VNS_S_ATTACH_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_attach(vsp) == 0) { + if (vnd_st_sbind(vsp) != 0) + died = B_TRUE; + } else { + died = B_TRUE; + } + break; + case VNS_S_BIND_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_bind(vsp) == 0) { + if (vnd_st_spromisc(vsp, DL_PROMISC_SAP, + VNS_S_SAP_PROMISC_SENT) != 0) + died = B_TRUE; + } else { + died = B_TRUE; + } + break; + case VNS_S_SAP_PROMISC_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_promisc(vsp) == 0) { + if (vnd_st_spromisc(vsp, DL_PROMISC_MULTI, + VNS_S_MULTI_PROMISC_SENT) != 0) + died = B_TRUE; + } else { + died = B_TRUE; + } + break; + case VNS_S_MULTI_PROMISC_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_promisc(vsp) == 0) { + if (vnd_st_spromisc(vsp, DL_PROMISC_RX_ONLY, + VNS_S_RX_ONLY_PROMISC_SENT) != 0) + died = B_TRUE; + } else { + died = B_TRUE; + } + break; + case VNS_S_RX_ONLY_PROMISC_SENT: + VERIFY(vsp->vns_dlpi_inc != NULL); + if (vnd_st_promisc(vsp) == 0) { + if (vnd_st_scapabq(vsp) != 0) + died = B_TRUE; + } else { + died = B_TRUE; + } + break; + case VNS_S_CAPAB_Q_SENT: + if (vnd_st_capabq(vsp) != 0) + died = B_TRUE; + else + vnd_st_sonline(vsp); + break; + case VNS_S_SHUTTING_DOWN: + vnd_st_shutdown(vsp); + break; + case VNS_S_ZOMBIE: + while ((mp = vnd_dlpi_inc_pop(vsp)) != NULL) + vnd_drop_ctl(vsp, mp, "vsp committed suicide"); + break; + default: + panic("vnd_str_t entered an unknown state"); + } + + if (died == B_TRUE) { + ASSERT(vsp->vns_errno != VND_E_SUCCESS); + vsp->vns_laststate = vsp->vns_state; + vsp->vns_state = VNS_S_ZOMBIE; + cv_broadcast(&vsp->vns_stcv); + } + + mutex_exit(&vsp->vns_lock); +} + +static void +vnd_dlpi_taskq_dispatch(void *arg) +{ + vnd_str_t *vsp = arg; + int run = 1; + + while (run != 0) { + vnd_str_state_transition(vsp); + mutex_enter(&vsp->vns_lock); + if (vsp->vns_flags & VNS_F_CONDEMNED || + vsp->vns_dlpi_inc == NULL) { + run = 0; + vsp->vns_flags &= ~VNS_F_TASKQ_DISPATCHED; + } + if (vsp->vns_flags & VNS_F_CONDEMNED) + cv_signal(&vsp->vns_cancelcv); + mutex_exit(&vsp->vns_lock); + } +} + +static int +vnd_neti_getifname(net_handle_t neti, phy_if_t phy, char *buf, const size_t len) +{ + return (-1); +} + +static int +vnd_neti_getmtu(net_handle_t neti, phy_if_t phy, lif_if_t ifdata) +{ + return (-1); +} + +static int +vnd_neti_getptmue(net_handle_t neti) +{ + return (-1); +} + +static int +vnd_neti_getlifaddr(net_handle_t neti, phy_if_t phy, lif_if_t ifdata, + size_t nelem, net_ifaddr_t type[], void *storage) +{ + return (-1); +} + +static int +vnd_neti_getlifzone(net_handle_t neti, phy_if_t phy, lif_if_t ifdata, + zoneid_t *zid) +{ + return (-1); +} + +static int +vnd_neti_getlifflags(net_handle_t neti, phy_if_t phy, lif_if_t ifdata, + uint64_t *flags) +{ + return (-1); +} + +static phy_if_t +vnd_neti_phygetnext(net_handle_t neti, phy_if_t phy) +{ + return (-1); +} + +static phy_if_t +vnd_neti_phylookup(net_handle_t neti, const char *name) +{ + return (-1); +} + +static lif_if_t +vnd_neti_lifgetnext(net_handle_t neti, phy_if_t phy, lif_if_t ifdata) +{ + return (-1); +} + +static int +vnd_neti_inject(net_handle_t neti, inject_t style, net_inject_t *packet) +{ + return (-1); +} + +static phy_if_t +vnd_neti_route(net_handle_t neti, struct sockaddr *address, + struct sockaddr *next) +{ + return ((phy_if_t)-1); +} + +static int +vnd_neti_ispchksum(net_handle_t neti, mblk_t *mp) +{ + return (-1); +} + +static int +vnd_neti_isvchksum(net_handle_t neti, mblk_t *mp) +{ + return (-1); +} + +static net_protocol_t vnd_neti_info_v4 = { + NETINFO_VERSION, + NHF_VND_INET, + vnd_neti_getifname, + vnd_neti_getmtu, + vnd_neti_getptmue, + vnd_neti_getlifaddr, + vnd_neti_getlifzone, + vnd_neti_getlifflags, + vnd_neti_phygetnext, + vnd_neti_phylookup, + vnd_neti_lifgetnext, + vnd_neti_inject, + vnd_neti_route, + vnd_neti_ispchksum, + vnd_neti_isvchksum +}; + +static net_protocol_t vnd_neti_info_v6 = { + NETINFO_VERSION, + NHF_VND_INET6, + vnd_neti_getifname, + vnd_neti_getmtu, + vnd_neti_getptmue, + vnd_neti_getlifaddr, + vnd_neti_getlifzone, + vnd_neti_getlifflags, + vnd_neti_phygetnext, + vnd_neti_phylookup, + vnd_neti_lifgetnext, + vnd_neti_inject, + vnd_neti_route, + vnd_neti_ispchksum, + vnd_neti_isvchksum +}; + + +static int +vnd_netinfo_init(vnd_pnsd_t *nsp) +{ + nsp->vpnd_neti_v4 = net_protocol_register(nsp->vpnd_nsid, + &vnd_neti_info_v4); + ASSERT(nsp->vpnd_neti_v4 != NULL); + + nsp->vpnd_neti_v6 = net_protocol_register(nsp->vpnd_nsid, + &vnd_neti_info_v6); + ASSERT(nsp->vpnd_neti_v6 != NULL); + + nsp->vpnd_family_v4.hf_version = HOOK_VERSION; + nsp->vpnd_family_v4.hf_name = "vnd_inet"; + + if (net_family_register(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4) != 0) { + net_protocol_unregister(nsp->vpnd_neti_v4); + net_protocol_unregister(nsp->vpnd_neti_v6); + cmn_err(CE_NOTE, "vnd_netinfo_init: net_family_register " + "failed for stack %d", nsp->vpnd_nsid); + return (1); + } + + nsp->vpnd_family_v6.hf_version = HOOK_VERSION; + nsp->vpnd_family_v6.hf_name = "vnd_inet6"; + + if (net_family_register(nsp->vpnd_neti_v6, &nsp->vpnd_family_v6) != 0) { + net_family_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4); + net_protocol_unregister(nsp->vpnd_neti_v4); + net_protocol_unregister(nsp->vpnd_neti_v6); + cmn_err(CE_NOTE, "vnd_netinfo_init: net_family_register " + "failed for stack %d", nsp->vpnd_nsid); + return (1); + } + + nsp->vpnd_event_in_v4.he_version = HOOK_VERSION; + nsp->vpnd_event_in_v4.he_name = NH_PHYSICAL_IN; + nsp->vpnd_event_in_v4.he_flags = 0; + nsp->vpnd_event_in_v4.he_interested = B_FALSE; + + nsp->vpnd_token_in_v4 = net_event_register(nsp->vpnd_neti_v4, + &nsp->vpnd_event_in_v4); + if (nsp->vpnd_token_in_v4 == NULL) { + net_family_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4); + net_family_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_family_v6); + net_protocol_unregister(nsp->vpnd_neti_v4); + net_protocol_unregister(nsp->vpnd_neti_v6); + cmn_err(CE_NOTE, "vnd_netinfo_init: net_event_register " + "failed for stack %d", nsp->vpnd_nsid); + return (1); + } + + nsp->vpnd_event_in_v6.he_version = HOOK_VERSION; + nsp->vpnd_event_in_v6.he_name = NH_PHYSICAL_IN; + nsp->vpnd_event_in_v6.he_flags = 0; + nsp->vpnd_event_in_v6.he_interested = B_FALSE; + + nsp->vpnd_token_in_v6 = net_event_register(nsp->vpnd_neti_v6, + &nsp->vpnd_event_in_v6); + if (nsp->vpnd_token_in_v6 == NULL) { + net_event_shutdown(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + net_event_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + net_family_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4); + net_family_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_family_v6); + net_protocol_unregister(nsp->vpnd_neti_v4); + net_protocol_unregister(nsp->vpnd_neti_v6); + cmn_err(CE_NOTE, "vnd_netinfo_init: net_event_register " + "failed for stack %d", nsp->vpnd_nsid); + return (1); + } + + nsp->vpnd_event_out_v4.he_version = HOOK_VERSION; + nsp->vpnd_event_out_v4.he_name = NH_PHYSICAL_OUT; + nsp->vpnd_event_out_v4.he_flags = 0; + nsp->vpnd_event_out_v4.he_interested = B_FALSE; + + nsp->vpnd_token_out_v4 = net_event_register(nsp->vpnd_neti_v4, + &nsp->vpnd_event_out_v4); + if (nsp->vpnd_token_out_v4 == NULL) { + net_event_shutdown(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + net_event_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + net_event_shutdown(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + net_event_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + net_family_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4); + net_family_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_family_v6); + net_protocol_unregister(nsp->vpnd_neti_v4); + net_protocol_unregister(nsp->vpnd_neti_v6); + cmn_err(CE_NOTE, "vnd_netinfo_init: net_event_register " + "failed for stack %d", nsp->vpnd_nsid); + return (1); + } + + nsp->vpnd_event_out_v6.he_version = HOOK_VERSION; + nsp->vpnd_event_out_v6.he_name = NH_PHYSICAL_OUT; + nsp->vpnd_event_out_v6.he_flags = 0; + nsp->vpnd_event_out_v6.he_interested = B_FALSE; + + nsp->vpnd_token_out_v6 = net_event_register(nsp->vpnd_neti_v6, + &nsp->vpnd_event_out_v6); + if (nsp->vpnd_token_out_v6 == NULL) { + net_event_shutdown(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + net_event_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + net_event_shutdown(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + net_event_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + net_event_shutdown(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + net_event_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + net_family_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4); + net_family_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_family_v6); + net_protocol_unregister(nsp->vpnd_neti_v4); + net_protocol_unregister(nsp->vpnd_neti_v6); + cmn_err(CE_NOTE, "vnd_netinfo_init: net_event_register " + "failed for stack %d", nsp->vpnd_nsid); + return (1); + } + + return (0); +} + +static void +vnd_netinfo_shutdown(vnd_pnsd_t *nsp) +{ + int ret; + + ret = net_event_shutdown(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + VERIFY(ret == 0); + ret = net_event_shutdown(nsp->vpnd_neti_v4, &nsp->vpnd_event_out_v4); + VERIFY(ret == 0); + ret = net_event_shutdown(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + VERIFY(ret == 0); + ret = net_event_shutdown(nsp->vpnd_neti_v6, &nsp->vpnd_event_out_v6); + VERIFY(ret == 0); +} + +static void +vnd_netinfo_fini(vnd_pnsd_t *nsp) +{ + int ret; + + ret = net_event_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_event_in_v4); + VERIFY(ret == 0); + ret = net_event_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_event_out_v4); + VERIFY(ret == 0); + ret = net_event_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_event_in_v6); + VERIFY(ret == 0); + ret = net_event_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_event_out_v6); + VERIFY(ret == 0); + ret = net_family_unregister(nsp->vpnd_neti_v4, &nsp->vpnd_family_v4); + VERIFY(ret == 0); + ret = net_family_unregister(nsp->vpnd_neti_v6, &nsp->vpnd_family_v6); + VERIFY(ret == 0); + ret = net_protocol_unregister(nsp->vpnd_neti_v4); + VERIFY(ret == 0); + ret = net_protocol_unregister(nsp->vpnd_neti_v6); + VERIFY(ret == 0); +} + +static void +vnd_strbarrier_cb(void *arg, mblk_t *bmp, gsqueue_t *gsp, void *dummy) +{ + vnd_str_t *vsp = arg; + + VERIFY(bmp == &vsp->vns_barrierblk); + mutex_enter(&vsp->vns_lock); + VERIFY(vsp->vns_flags & VNS_F_BARRIER); + VERIFY(!(vsp->vns_flags & VNS_F_BARRIER_DONE)); + vsp->vns_flags |= VNS_F_BARRIER_DONE; + mutex_exit(&vsp->vns_lock); + + /* + * For better or worse, we have to broadcast here as we could have a + * thread that's blocked for completion as well as one that's blocked + * waiting to do a barrier itself. + */ + cv_broadcast(&vsp->vns_barriercv); +} + +/* + * This is a data barrier for the stream while it is in fastpath mode. It blocks + * and ensures that there is nothing else in the squeue. + */ +static void +vnd_strbarrier(vnd_str_t *vsp) +{ + mutex_enter(&vsp->vns_lock); + while (vsp->vns_flags & VNS_F_BARRIER) + cv_wait(&vsp->vns_barriercv, &vsp->vns_lock); + vsp->vns_flags |= VNS_F_BARRIER; + mutex_exit(&vsp->vns_lock); + + gsqueue_enter_one(vsp->vns_squeue, &vsp->vns_barrierblk, + vnd_strbarrier_cb, vsp, GSQUEUE_PROCESS, VND_SQUEUE_TAG_STRBARRIER); + + mutex_enter(&vsp->vns_lock); + while (!(vsp->vns_flags & VNS_F_BARRIER_DONE)) + cv_wait(&vsp->vns_barriercv, &vsp->vns_lock); + vsp->vns_flags &= ~VNS_F_BARRIER; + vsp->vns_flags &= ~VNS_F_BARRIER_DONE; + mutex_exit(&vsp->vns_lock); + + /* + * We have to broadcast in case anyone is waiting for the barrier + * themselves. + */ + cv_broadcast(&vsp->vns_barriercv); +} + +/* + * Based on the type of message that we're dealing with we're going to want to + * do one of several things. Basically if it looks like it's something we know + * about, we should probably handle it in one of our transition threads. + * Otherwise, we should just simply putnext. + */ +static int +vnd_s_rput(queue_t *q, mblk_t *mp) +{ + t_uscalar_t prim; + int dispatch = 0; + vnd_str_t *vsp = q->q_ptr; + + switch (DB_TYPE(mp)) { + case M_PROTO: + case M_PCPROTO: + if (MBLKL(mp) < sizeof (t_uscalar_t)) { + vnd_drop_ctl(vsp, mp, "PROTO message too short"); + break; + } + + prim = ((union DL_primitives *)mp->b_rptr)->dl_primitive; + if (prim == DL_UNITDATA_REQ || prim == DL_UNITDATA_IND) { + vnd_drop_ctl(vsp, mp, + "recieved an unsupported dlpi DATA req"); + break; + } + + /* + * Enqueue the entry and fire off a taskq dispatch. + */ + mutex_enter(&vsp->vns_lock); + vnd_dlpi_inc_push(vsp, mp); + if (!(vsp->vns_flags & VNS_F_TASKQ_DISPATCHED)) { + dispatch = 1; + vsp->vns_flags |= VNS_F_TASKQ_DISPATCHED; + } + mutex_exit(&vsp->vns_lock); + if (dispatch != 0) + taskq_dispatch_ent(vnd_taskq, vnd_dlpi_taskq_dispatch, + vsp, 0, &vsp->vns_tqe); + break; + case M_DATA: + vnd_drop_in(vsp, mp, "M_DATA via put(9E)"); + break; + default: + putnext(vsp->vns_rq, mp); + } + return (0); +} + +static void +vnd_strioctl(queue_t *q, vnd_str_t *vsp, mblk_t *mp, struct iocblk *iocp) +{ + int error; + vnd_strioc_t *visp; + + if (iocp->ioc_cmd != VND_STRIOC_ASSOCIATE || + iocp->ioc_count != TRANSPARENT) { + error = EINVAL; + goto nak; + } + + /* + * All streams ioctls that we support must use kcred as a means to + * distinguish that this is a layered open by the kernel as opposed to + * one by a user who has done an I_PUSH of the module. + */ + if (iocp->ioc_cr != kcred) { + error = EPERM; + goto nak; + } + + if (mp->b_cont == NULL) { + error = EAGAIN; + goto nak; + } + + visp = kmem_alloc(sizeof (vnd_strioc_t), KM_SLEEP); + ASSERT(MBLKL(mp->b_cont) == sizeof (caddr_t)); + visp->vs_addr = *(caddr_t *)mp->b_cont->b_rptr; + visp->vs_state = VSS_COPYIN; + + mcopyin(mp, (void *)visp, sizeof (vnd_strioc_associate_t), NULL); + qreply(q, mp); + + return; + +nak: + if (mp->b_cont != NULL) { + freemsg(mp->b_cont); + mp->b_cont = NULL; + } + + iocp->ioc_error = error; + mp->b_datap->db_type = M_IOCNAK; + iocp->ioc_count = 0; + qreply(q, mp); +} + +static void +vnd_striocdata(queue_t *q, vnd_str_t *vsp, mblk_t *mp, struct copyresp *csp) +{ + int error; + vnd_str_state_t state; + struct copyreq *crp; + vnd_strioc_associate_t *vss; + vnd_dev_t *vdp = NULL; + vnd_pnsd_t *nsp = NULL; + char iname[2*VND_NAMELEN]; + zone_t *zone; + vnd_strioc_t *visp; + + visp = (vnd_strioc_t *)csp->cp_private; + + /* If it's not ours, it's not our problem */ + if (csp->cp_cmd != VND_STRIOC_ASSOCIATE) { + if (q->q_next != NULL) { + putnext(q, mp); + } else { + VND_STAT_INC(vsp, vks_ndlpidrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); + vnd_drop_ctl(vsp, mp, "uknown cmd for M_IOCDATA"); + } + kmem_free(visp, sizeof (vnd_strioc_t)); + return; + } + + /* The nak is already sent for us */ + if (csp->cp_rval != 0) { + vnd_drop_ctl(vsp, mp, "M_COPYIN failed"); + kmem_free(visp, sizeof (vnd_strioc_t)); + return; + } + + /* Data is sitting for us in b_cont */ + if (mp->b_cont == NULL || + MBLKL(mp->b_cont) != sizeof (vnd_strioc_associate_t)) { + kmem_free(visp, sizeof (vnd_strioc_t)); + miocnak(q, mp, 0, EINVAL); + qreply(q, mp); + return; + } + + vss = (vnd_strioc_associate_t *)mp->b_cont->b_rptr; + vdp = vnd_dev_lookup(vss->vsa_minor); + if (vdp == NULL) { + error = EIO; + vss->vsa_errno = VND_E_NODEV; + goto nak; + } + + nsp = vnd_nsd_lookup(vss->vsa_nsid); + if (nsp == NULL) { + error = EIO; + vss->vsa_errno = VND_E_NONETSTACK; + goto nak; + } + + mutex_enter(&vsp->vns_lock); + if (!(vsp->vns_flags & VNS_F_NEED_ZONE)) { + mutex_exit(&vsp->vns_lock); + error = EEXIST; + vss->vsa_errno = VND_E_ASSOCIATED; + goto nak; + } + + vsp->vns_nsd = nsp; + vsp->vns_flags &= ~VNS_F_NEED_ZONE; + vsp->vns_flags |= VNS_F_TASKQ_DISPATCHED; + mutex_exit(&vsp->vns_lock); + + taskq_dispatch_ent(vnd_taskq, vnd_dlpi_taskq_dispatch, vsp, 0, + &vsp->vns_tqe); + + + /* At this point we need to wait until we have transitioned to ONLINE */ + mutex_enter(&vsp->vns_lock); + while (vsp->vns_state != VNS_S_ONLINE && vsp->vns_state != VNS_S_ZOMBIE) + cv_wait(&vsp->vns_stcv, &vsp->vns_lock); + state = vsp->vns_state; + mutex_exit(&vsp->vns_lock); + + if (state == VNS_S_ZOMBIE) { + vss->vsa_errno = vsp->vns_errno; + error = EIO; + goto nak; + } + + mutex_enter(&vdp->vdd_lock); + mutex_enter(&vsp->vns_lock); + VERIFY(vdp->vdd_str == NULL); + /* + * Now initialize the remaining kstat properties and let's go ahead and + * create it. + */ + (void) snprintf(iname, sizeof (iname), "z%d_%d", + vdp->vdd_nsd->vpnd_zid, vdp->vdd_minor); + vsp->vns_kstat = kstat_create_zone("vnd", vdp->vdd_minor, iname, "net", + KSTAT_TYPE_NAMED, sizeof (vnd_str_stat_t) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL, GLOBAL_ZONEID); + if (vsp->vns_kstat == NULL) { + error = EIO; + vss->vsa_errno = VND_E_KSTATCREATE; + mutex_exit(&vsp->vns_lock); + mutex_exit(&vdp->vdd_lock); + goto nak; + } + vdp->vdd_str = vsp; + vsp->vns_dev = vdp; + + /* + * Now, it's time to do the las thing that can fail, changing out the + * input function. After this we know that we can receive data, so we + * should make sure that we're ready. + */ + if (vnd_dld_cap_enable(vsp, vnd_mac_input) != 0) { + error = EIO; + vss->vsa_errno = VND_E_DIRECTFAIL; + vdp->vdd_str = NULL; + vsp->vns_dev = NULL; + mutex_exit(&vsp->vns_lock); + mutex_exit(&vdp->vdd_lock); + goto nak; + } + + zone = zone_find_by_id(vdp->vdd_nsd->vpnd_zid); + ASSERT(zone != NULL); + vsp->vns_kstat->ks_data = &vsp->vns_ksdata; + /* Account for zone name */ + vsp->vns_kstat->ks_data_size += strlen(zone->zone_name) + 1; + /* Account for eventual link name */ + vsp->vns_kstat->ks_data_size += VND_NAMELEN; + kstat_named_setstr(&vsp->vns_ksdata.vks_zonename, zone->zone_name); + kstat_named_setstr(&vdp->vdd_str->vns_ksdata.vks_linkname, + vdp->vdd_lname); + zone_rele(zone); + kstat_install(vsp->vns_kstat); + + mutex_exit(&vsp->vns_lock); + mutex_exit(&vdp->vdd_lock); + + /* + * Note that the vnd_str_t does not keep a permanent hold on the + * vnd_pnsd_t. We leave that up to the vnd_dev_t as that's also what + * the nestack goes through to take care of everything. + */ + vss->vsa_errno = VND_E_SUCCESS; +nak: + if (vdp != NULL) + vnd_dev_rele(vdp); + if (nsp != NULL) + vnd_nsd_rele(nsp); + /* + * Change the copyin request to a copyout. Note that we can't use + * mcopyout here as it only works when the DB_TYPE is M_IOCTL. That's + * okay, as the copyin vs. copyout is basically the same. + */ + DB_TYPE(mp) = M_COPYOUT; + visp->vs_state = VSS_COPYOUT; + crp = (struct copyreq *)mp->b_rptr; + crp->cq_private = (void *)visp; + crp->cq_addr = visp->vs_addr; + crp->cq_size = sizeof (vnd_strioc_associate_t); + qreply(q, mp); +} + +static void +vnd_stroutdata(queue_t *q, vnd_str_t *vsp, mblk_t *mp, struct copyresp *csp) +{ + ASSERT(csp->cp_private != NULL); + kmem_free(csp->cp_private, sizeof (vnd_strioc_t)); + if (csp->cp_cmd != VND_STRIOC_ASSOCIATE) { + if (q->q_next != NULL) { + putnext(q, mp); + } else { + VND_STAT_INC(vsp, vks_ndlpidrops, 1); + VND_STAT_INC(vsp, vks_tdrops, 1); + vnd_drop_ctl(vsp, mp, "uknown cmd for M_IOCDATA"); + } + return; + } + + /* The nak is already sent for us */ + if (csp->cp_rval != 0) { + vnd_drop_ctl(vsp, mp, "M_COPYOUT failed"); + return; + } + + /* Ack and let's be done with it all */ + miocack(q, mp, 0, 0); +} + +static int +vnd_s_wput(queue_t *q, mblk_t *mp) +{ + vnd_str_t *vsp = q->q_ptr; + struct copyresp *crp; + vnd_strioc_state_t vstate; + vnd_strioc_t *visp; + + switch (DB_TYPE(mp)) { + case M_IOCTL: + vnd_strioctl(q, vsp, mp, (struct iocblk *)mp->b_rptr); + return (0); + case M_IOCDATA: + crp = (struct copyresp *)mp->b_rptr; + ASSERT(crp->cp_private != NULL); + visp = (vnd_strioc_t *)crp->cp_private; + vstate = visp->vs_state; + ASSERT(vstate == VSS_COPYIN || vstate == VSS_COPYOUT); + if (vstate == VSS_COPYIN) + vnd_striocdata(q, vsp, mp, + (struct copyresp *)mp->b_rptr); + else + vnd_stroutdata(q, vsp, mp, + (struct copyresp *)mp->b_rptr); + return (0); + default: + break; + } + if (q->q_next != NULL) + putnext(q, mp); + else + vnd_drop_ctl(vsp, mp, "!M_IOCTL in wput"); + + return (0); +} + +static int +vnd_s_open(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *credp) +{ + vnd_str_t *vsp; + uint_t rand; + + if (q->q_ptr != NULL) + return (EINVAL); + + if (!(sflag & MODOPEN)) + return (ENXIO); + + if (credp != kcred) + return (EPERM); + + vsp = kmem_cache_alloc(vnd_str_cache, KM_SLEEP); + bzero(vsp, sizeof (*vsp)); + mutex_init(&vsp->vns_lock, NULL, MUTEX_DRIVER, NULL); + cv_init(&vsp->vns_cancelcv, NULL, CV_DRIVER, NULL); + cv_init(&vsp->vns_barriercv, NULL, CV_DRIVER, NULL); + cv_init(&vsp->vns_stcv, NULL, CV_DRIVER, NULL); + vsp->vns_state = VNS_S_INITIAL; + + mutex_init(&vsp->vns_dq_read.vdq_lock, NULL, MUTEX_DRIVER, NULL); + mutex_init(&vsp->vns_dq_write.vdq_lock, NULL, MUTEX_DRIVER, NULL); + mutex_enter(&vnd_dev_lock); + vsp->vns_dq_read.vdq_max = vnd_vdq_default_size; + vsp->vns_dq_read.vdq_vns = vsp; + vsp->vns_dq_write.vdq_max = vnd_vdq_default_size; + vsp->vns_dq_write.vdq_vns = vsp; + mutex_exit(&vnd_dev_lock); + vsp->vns_rq = q; + vsp->vns_wq = WR(q); + q->q_ptr = WR(q)->q_ptr = vsp; + vsp->vns_flags = VNS_F_NEED_ZONE; + vsp->vns_nflush = vnd_flush_nburst; + vsp->vns_bsize = vnd_flush_burst_size; + + (void) random_get_pseudo_bytes((uint8_t *)&rand, sizeof (rand)); + vsp->vns_squeue = gsqueue_set_get(vnd_sqset, rand); + + /* + * We create our kstat and initialize all of its fields now, but we + * don't install it until we actually do the zone association so we can + * get everything. + */ + kstat_named_init(&vsp->vns_ksdata.vks_rbytes, "rbytes", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_rpackets, "rpackets", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_obytes, "obytes", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_opackets, "opackets", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_nhookindrops, "nhookindrops", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_nhookoutdrops, "nhookoutdrops", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_ndlpidrops, "ndlpidrops", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_ndataindrops, "ndataindrops", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_ndataoutdrops, "ndataoutdrops", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_tdrops, "total_drops", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_linkname, "linkname", + KSTAT_DATA_STRING); + kstat_named_init(&vsp->vns_ksdata.vks_zonename, "zonename", + KSTAT_DATA_STRING); + kstat_named_init(&vsp->vns_ksdata.vks_nmacflow, "flowcontrol_events", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_tmacflow, "flowcontrol_time", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_mac_flow_1ms, "flowcontrol_1ms", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_mac_flow_10ms, "flowcontrol_10ms", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_mac_flow_100ms, + "flowcontrol_100ms", KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_mac_flow_1s, "flowcontrol_1s", + KSTAT_DATA_UINT64); + kstat_named_init(&vsp->vns_ksdata.vks_mac_flow_10s, "flowcontrol_10s", + KSTAT_DATA_UINT64); + qprocson(q); + /* + * Now that we've called qprocson, grab the lower module for making sure + * that we don't have any pass through modules. + */ + vsp->vns_lrq = RD(vsp->vns_wq->q_next); + + return (0); +} + +static int +vnd_s_close(queue_t *q, int flag, cred_t *credp) +{ + vnd_str_t *vsp; + mblk_t *mp; + + VERIFY(WR(q)->q_next != NULL); + + vsp = q->q_ptr; + ASSERT(vsp != NULL); + + /* + * We need to transition ourselves down. This means that we have a few + * important different things to do in the process of tearing down our + * input and output buffers, making sure we've drained the current + * squeue, and disabling the fast path. Before we disable the fast path, + * we should make sure the squeue is drained. Because we're in streams + * close, we know that no packets can come into us from userland, but we + * can receive more. As such, the following is the exact order of things + * that we do: + * + * 1) flush the vns_dq_read + * 2) Insert the drain mblk + * 3) When it's been received, tear down the fast path by kicking + * off the state machine. + * 4) One final flush of both the vns_dq_read,vns_dq_write + */ + + vnd_dq_flush(&vsp->vns_dq_read, vnd_drop_in); + vnd_strbarrier(vsp); + mutex_enter(&vsp->vns_lock); + vsp->vns_state = VNS_S_SHUTTING_DOWN; + if (!(vsp->vns_flags & VNS_F_TASKQ_DISPATCHED)) { + vsp->vns_flags |= VNS_F_TASKQ_DISPATCHED; + taskq_dispatch_ent(vnd_taskq, vnd_dlpi_taskq_dispatch, vsp, + 0, &vsp->vns_tqe); + } + while (vsp->vns_state != VNS_S_ZOMBIE) + cv_wait(&vsp->vns_stcv, &vsp->vns_lock); + mutex_exit(&vsp->vns_lock); + + qprocsoff(q); + mutex_enter(&vsp->vns_lock); + vsp->vns_flags |= VNS_F_CONDEMNED; + while (vsp->vns_flags & VNS_F_TASKQ_DISPATCHED) + cv_wait(&vsp->vns_cancelcv, &vsp->vns_lock); + + while ((mp = vnd_dlpi_inc_pop(vsp)) != NULL) + vnd_drop_ctl(vsp, mp, "vnd_s_close"); + mutex_exit(&vsp->vns_lock); + + q->q_ptr = NULL; + vnd_dq_flush(&vsp->vns_dq_read, vnd_drop_in); + vnd_dq_flush(&vsp->vns_dq_write, vnd_drop_out); + mutex_destroy(&vsp->vns_dq_read.vdq_lock); + mutex_destroy(&vsp->vns_dq_write.vdq_lock); + + if (vsp->vns_kstat != NULL) + kstat_delete(vsp->vns_kstat); + mutex_destroy(&vsp->vns_lock); + cv_destroy(&vsp->vns_stcv); + cv_destroy(&vsp->vns_barriercv); + cv_destroy(&vsp->vns_cancelcv); + kmem_cache_free(vnd_str_cache, vsp); + + return (0); +} + +static vnd_mac_cookie_t +vnd_squeue_tx_one(vnd_str_t *vsp, mblk_t *mp) +{ + hrtime_t txtime; + vnd_mac_cookie_t vc; + + VND_STAT_INC(vsp, vks_opackets, 1); + VND_STAT_INC(vsp, vks_obytes, msgsize(mp)); + DTRACE_VND5(send, mblk_t *, mp, void *, NULL, void *, NULL, + vnd_str_t *, vsp, mblk_t *, mp); + /* Actually tx now */ + txtime = gethrtime(); + vc = vsp->vns_caps.vsc_tx_f(vsp->vns_caps.vsc_tx_hdl, + mp, 0, MAC_DROP_ON_NO_DESC); + + /* + * We need to check two different conditions before we immediately set + * the flow control lock. The first thing that we need to do is verify + * that this is an instance of hard flow control, so to say. The flow + * control callbacks won't always fire in cases where we still get a + * cookie returned. The explicit check for flow control will guarantee + * us that we'll get a subsequent notification callback. + * + * The second case comes about because we do not hold the + * vnd_str_t`vns_lock across calls to tx, we need to determine if a flow + * control notification already came across for us in a different thread + * calling vnd_mac_flow_control(). To deal with this, we record a + * timestamp every time that we change the flow control state. We grab + * txtime here before we transmit because that guarantees that the + * hrtime_t of the call to vnd_mac_flow_control() will be after txtime. + * + * If the flow control notification beat us to the punch, the value of + * vns_fcupdate will be larger than the value of txtime, and we should + * just record the statistics. However, if we didn't beat it to the + * punch (txtime > vns_fcupdate), then we know that it's safe to wait + * for a notification. + */ + if (vc != NULL) { + hrtime_t diff; + + if (vsp->vns_caps.vsc_is_fc_f(vsp->vns_caps.vsc_is_fc_hdl, + vc) == 0) + return (NULL); + mutex_enter(&vsp->vns_lock); + diff = vsp->vns_fcupdate - txtime; + if (diff > 0) { + mutex_exit(&vsp->vns_lock); + vnd_mac_flow_control_stat(vsp, diff); + return (NULL); + } + vsp->vns_flags |= VNS_F_FLOW_CONTROLLED; + vsp->vns_caps.vsc_fc_cookie = vc; + vsp->vns_fclatch = txtime; + vsp->vns_fcupdate = txtime; + DTRACE_VND3(flow__blocked, vnd_str_t *, vsp, + uint64_t, vsp->vns_dq_write.vdq_cur, uintptr_t, vc); + mutex_exit(&vsp->vns_lock); + } + + return (vc); +} + +static void +vnd_squeue_tx_drain(void *arg, mblk_t *drain_mp, gsqueue_t *gsp, void *dummy) +{ + mblk_t *mp; + int nmps; + size_t mptot, nflush, bsize; + boolean_t blocked, empty; + vnd_data_queue_t *vqp; + vnd_str_t *vsp = arg; + + mutex_enter(&vsp->vns_lock); + /* + * We either enter here via an squeue or via vnd_squeue_tx_append(). In + * the former case we need to mark that there is no longer an active + * user of the drain block. + */ + if (drain_mp != NULL) { + VERIFY(drain_mp == &vsp->vns_drainblk); + VERIFY(vsp->vns_flags & VNS_F_DRAIN_SCHEDULED); + vsp->vns_flags &= ~VNS_F_DRAIN_SCHEDULED; + } + + /* + * If we're still flow controlled or under a flush barrier, nothing to + * do. + */ + if (vsp->vns_flags & (VNS_F_FLOW_CONTROLLED | VNS_F_BARRIER)) { + mutex_exit(&vsp->vns_lock); + return; + } + + nflush = vsp->vns_nflush; + bsize = vsp->vns_bsize; + mutex_exit(&vsp->vns_lock); + + nmps = 0; + mptot = 0; + blocked = B_FALSE; + vqp = &vsp->vns_dq_write; + while (nmps < nflush && mptot <= bsize) { + mutex_enter(&vqp->vdq_lock); + if (vnd_dq_pop(vqp, &mp) == 0) { + mutex_exit(&vqp->vdq_lock); + break; + } + mutex_exit(&vqp->vdq_lock); + + nmps++; + mptot += msgsize(mp); + if (vnd_squeue_tx_one(vsp, mp) != NULL) { + blocked = B_TRUE; + break; + } + } + + empty = vnd_dq_is_empty(&vsp->vns_dq_write); + + /* + * If the queue is not empty, we're not blocked, and there isn't a drain + * scheduled, put it into the squeue with the drain block and + * GSQUEUE_FILL. + */ + if (blocked == B_FALSE && empty == B_FALSE) { + mutex_enter(&vsp->vns_lock); + if (!(vsp->vns_flags & VNS_F_DRAIN_SCHEDULED)) { + mblk_t *mp = &vsp->vns_drainblk; + vsp->vns_flags |= VNS_F_DRAIN_SCHEDULED; + gsqueue_enter_one(vsp->vns_squeue, + mp, vnd_squeue_tx_drain, vsp, + GSQUEUE_FILL, VND_SQUEUE_TAG_TX_DRAIN); + } + mutex_exit(&vsp->vns_lock); + } + + /* + * If we drained some amount of data, we need to signal the data queue. + */ + if (nmps > 0) { + cv_broadcast(&vsp->vns_dq_write.vdq_ready); + pollwakeup(&vsp->vns_dev->vdd_ph, POLLOUT); + } +} + +static void +vnd_squeue_tx_append(void *arg, mblk_t *mp, gsqueue_t *gsp, void *dummy) +{ + vnd_str_t *vsp = arg; + vnd_data_queue_t *vqp = &vsp->vns_dq_write; + vnd_pnsd_t *nsp = vsp->vns_nsd; + size_t len = msgsize(mp); + + /* + * Before we append this packet, we should run it through the firewall + * rules. + */ + if (nsp->vpnd_hooked && vnd_hook(vsp, &mp, nsp->vpnd_neti_v4, + nsp->vpnd_event_out_v4, nsp->vpnd_token_out_v4, nsp->vpnd_neti_v6, + nsp->vpnd_event_out_v6, nsp->vpnd_token_out_v6, vnd_drop_hook_out, + vnd_drop_out) != 0) { + /* + * Because we earlier reserved space for this packet and it's + * not making the cut, we need to go through and unreserve that + * space. Also note that the message block will likely be freed + * by the time we return from vnd_hook so we cannot rely on it. + */ + mutex_enter(&vqp->vdq_lock); + vnd_dq_unreserve(vqp, len); + mutex_exit(&vqp->vdq_lock); + return; + } + + /* + * We earlier reserved space for this packet. So for now simply append + * it and call drain. We know that no other drain can be going on right + * now thanks to the squeue. + */ + mutex_enter(&vqp->vdq_lock); + (void) vnd_dq_push(&vsp->vns_dq_write, mp, B_TRUE, vnd_drop_panic); + mutex_exit(&vqp->vdq_lock); + vnd_squeue_tx_drain(vsp, NULL, NULL, NULL); +} + +/* + * We need to see if this is a valid name of sorts for us. That means a few + * things. First off, we can't assume that what we've been given has actually + * been null terminated. More importantly, that it's a valid name as far as + * ddi_create_minor_node is concerned (that means no '@', '/', or ' '). We + * further constrain ourselves to simply alphanumeric characters and a few + * additional ones, ':', '-', and '_'. + */ +static int +vnd_validate_name(const char *buf, size_t buflen) +{ + int i, len; + + /* First make sure a null terminator exists */ + for (i = 0; i < buflen; i++) + if (buf[i] == '\0') + break; + len = i; + if (i == 0 || i == buflen) + return (0); + + for (i = 0; i < len; i++) + if (!isalnum(buf[i]) && buf[i] != ':' && buf[i] != '-' && + buf[i] != '_') + return (0); + + return (1); +} + +static int +vnd_ioctl_attach(vnd_dev_t *vdp, uintptr_t arg, cred_t *credp, int cpflag) +{ + vnd_ioc_attach_t via; + vnd_strioc_associate_t vss; + vnd_pnsd_t *nsp; + zone_t *zonep; + zoneid_t zid; + char buf[2*VND_NAMELEN]; + int ret, rp; + + if (secpolicy_net_config(credp, B_FALSE) != 0) + return (EPERM); + + if (secpolicy_net_rawaccess(credp) != 0) + return (EPERM); + + if (ddi_copyin((void *)arg, &via, sizeof (via), cpflag) != 0) + return (EFAULT); + via.via_errno = VND_E_SUCCESS; + + if (vnd_validate_name(via.via_name, VND_NAMELEN) == 0) { + via.via_errno = VND_E_BADNAME; + ret = EIO; + goto errcopyout; + } + + /* + * Only the global zone can request to create a device in a different + * zone. + */ + zid = crgetzoneid(credp); + if (zid != GLOBAL_ZONEID && via.via_zoneid != -1 && + zid != via.via_zoneid) { + via.via_errno = VND_E_PERM; + ret = EIO; + goto errcopyout; + } + + if (via.via_zoneid == -1) + via.via_zoneid = zid; + + /* + * Establish the name we'll use now. We want to be extra paranoid about + * the device we're opening so check that now. + */ + if (zid == GLOBAL_ZONEID && via.via_zoneid != zid) { + zonep = zone_find_by_id(via.via_zoneid); + if (zonep == NULL) { + via.via_errno = VND_E_NOZONE; + ret = EIO; + goto errcopyout; + } + if (snprintf(NULL, 0, "/dev/net/zone/%s/%s", zonep->zone_name, + via.via_name) >= sizeof (buf)) { + zone_rele(zonep); + via.via_errno = VND_E_BADNAME; + ret = EIO; + goto errcopyout; + } + (void) snprintf(buf, sizeof (buf), "/dev/net/zone/%s/%s", + zonep->zone_name, via.via_name); + zone_rele(zonep); + zonep = NULL; + } else { + if (snprintf(NULL, 0, "/dev/net/%s", via.via_name) >= + sizeof (buf)) { + via.via_errno = VND_E_BADNAME; + ret = EIO; + goto errcopyout; + } + (void) snprintf(buf, sizeof (buf), "/dev/net/%s", via.via_name); + } + + /* + * If our zone is dying then the netstack will have been removed from + * this list. + */ + nsp = vnd_nsd_lookup_by_zid(via.via_zoneid); + if (nsp == NULL) { + via.via_errno = VND_E_NOZONE; + ret = EIO; + goto errcopyout; + } + + /* + * Note we set the attached handle even though we haven't actually + * finished the process of attaching the ldi handle. + */ + mutex_enter(&vdp->vdd_lock); + if (vdp->vdd_flags & (VND_D_ATTACHED | VND_D_ATTACH_INFLIGHT)) { + mutex_exit(&vdp->vdd_lock); + vnd_nsd_rele(nsp); + via.via_errno = VND_E_ATTACHED; + ret = EIO; + goto errcopyout; + } + vdp->vdd_flags |= VND_D_ATTACH_INFLIGHT; + ASSERT(vdp->vdd_cr == NULL); + crhold(credp); + vdp->vdd_cr = credp; + ASSERT(vdp->vdd_nsd == NULL); + vdp->vdd_nsd = nsp; + mutex_exit(&vdp->vdd_lock); + + /* + * Place an additional hold on the vnd_pnsd_t as we go through and do + * all of the rest of our work. This will be the hold that we keep for + * as long as this thing is attached. + */ + vnd_nsd_ref(nsp); + + ret = ldi_open_by_name(buf, FREAD | FWRITE, vdp->vdd_cr, + &vdp->vdd_ldih, vdp->vdd_ldiid); + if (ret != 0) { + if (ret == ENODEV) + via.via_errno = VND_E_NODATALINK; + goto err; + } + + /* + * Unfortunately the I_PUSH interface doesn't allow us a way to detect + * whether or not we're coming in from a layered device. We really want + * to make sure that a normal user can't push on our streams module. + * Currently the only idea I have for this is to make sure that the + * credp is kcred which is really terrible. + */ + ret = ldi_ioctl(vdp->vdd_ldih, I_PUSH, (intptr_t)"vnd", FKIOCTL, + kcred, &rp); + if (ret != 0) { + rp = ldi_close(vdp->vdd_ldih, FREAD | FWRITE, vdp->vdd_cr); + VERIFY(rp == 0); + via.via_errno = VND_E_STRINIT; + ret = EIO; + goto err; + } + + vss.vsa_minor = vdp->vdd_minor; + vss.vsa_nsid = nsp->vpnd_nsid; + + ret = ldi_ioctl(vdp->vdd_ldih, VND_STRIOC_ASSOCIATE, (intptr_t)&vss, + FKIOCTL, kcred, &rp); + if (ret != 0 || vss.vsa_errno != VND_E_SUCCESS) { + rp = ldi_close(vdp->vdd_ldih, FREAD | FWRITE, vdp->vdd_cr); + VERIFY(rp == 0); + if (ret == 0) { + via.via_errno = vss.vsa_errno; + ret = EIO; + } + goto err; + } + + mutex_enter(&vdp->vdd_nsd->vpnd_lock); + + /* + * There's a chance that our netstack was condemned while we've had a + * hold on it. As such we need to check and if so, error out. + */ + if (vdp->vdd_nsd->vpnd_flags & VND_NS_CONDEMNED) { + mutex_exit(&vdp->vdd_nsd->vpnd_lock); + rp = ldi_close(vdp->vdd_ldih, FREAD | FWRITE, vdp->vdd_cr); + VERIFY(rp == 0); + ret = EIO; + via.via_errno = VND_E_NOZONE; + goto err; + } + + mutex_enter(&vdp->vdd_lock); + VERIFY(vdp->vdd_str != NULL); + vdp->vdd_flags &= ~VND_D_ATTACH_INFLIGHT; + vdp->vdd_flags |= VND_D_ATTACHED; + (void) strlcpy(vdp->vdd_datalink, via.via_name, + sizeof (vdp->vdd_datalink)); + list_insert_tail(&vdp->vdd_nsd->vpnd_dev_list, vdp); + mutex_exit(&vdp->vdd_lock); + mutex_exit(&vdp->vdd_nsd->vpnd_lock); + vnd_nsd_rele(nsp); + + return (0); + +err: + mutex_enter(&vdp->vdd_lock); + vdp->vdd_flags &= ~VND_D_ATTACH_INFLIGHT; + crfree(vdp->vdd_cr); + vdp->vdd_cr = NULL; + vdp->vdd_nsd = NULL; + mutex_exit(&vdp->vdd_lock); + + /* + * We have two holds to drop here. One for our original reference and + * one for the hold this operation would have represented. + */ + vnd_nsd_rele(nsp); + vnd_nsd_rele(nsp); +errcopyout: + if (ddi_copyout(&via, (void *)arg, sizeof (via), cpflag) != 0) + ret = EFAULT; + + return (ret); +} + +static int +vnd_ioctl_link(vnd_dev_t *vdp, intptr_t arg, cred_t *credp, int cpflag) +{ + int ret = 0; + vnd_ioc_link_t vil; + char mname[2*VND_NAMELEN]; + char **c; + vnd_dev_t *v; + zoneid_t zid; + + /* Not anyone can link something */ + if (secpolicy_net_config(credp, B_FALSE) != 0) + return (EPERM); + + if (ddi_copyin((void *)arg, &vil, sizeof (vil), cpflag) != 0) + return (EFAULT); + + if (vnd_validate_name(vil.vil_name, VND_NAMELEN) == 0) { + ret = EIO; + vil.vil_errno = VND_E_BADNAME; + goto errcopyout; + } + + c = vnd_reserved_names; + while (*c != NULL) { + if (strcmp(vil.vil_name, *c) == 0) { + ret = EIO; + vil.vil_errno = VND_E_BADNAME; + goto errcopyout; + } + c++; + } + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + ret = EIO; + vil.vil_errno = VND_E_NOTATTACHED; + goto errcopyout; + } + + if (vdp->vdd_flags & VND_D_ZONE_DYING) { + mutex_exit(&vdp->vdd_lock); + ret = EIO; + vil.vil_errno = VND_E_NOZONE; + goto errcopyout; + } + + if (vdp->vdd_flags & (VND_D_LINK_INFLIGHT | VND_D_LINKED)) { + mutex_exit(&vdp->vdd_lock); + ret = EIO; + vil.vil_errno = VND_E_LINKED; + goto errcopyout; + } + vdp->vdd_flags |= VND_D_LINK_INFLIGHT; + zid = vdp->vdd_nsd->vpnd_zid; + mutex_exit(&vdp->vdd_lock); + + if (snprintf(NULL, 0, "z%d:%s", zid, vil.vil_name) >= + sizeof (mname)) { + ret = EIO; + vil.vil_errno = VND_E_BADNAME; + goto errcopyout; + } + + mutex_enter(&vnd_dev_lock); + for (v = list_head(&vnd_dev_list); v != NULL; + v = list_next(&vnd_dev_list, v)) { + if (!(v->vdd_flags & VND_D_LINKED)) + continue; + + if (v->vdd_nsd->vpnd_zid == zid && + strcmp(v->vdd_lname, vil.vil_name) == 0) { + mutex_exit(&vnd_dev_lock); + ret = EIO; + vil.vil_errno = VND_E_LINKEXISTS; + goto error; + } + } + + /* + * We set the name and mark ourselves attached while holding the list + * lock to ensure that no other user can mistakingly find our name. + */ + (void) snprintf(mname, sizeof (mname), "z%d:%s", zid, + vil.vil_name); + mutex_enter(&vdp->vdd_lock); + + /* + * Because we dropped our lock, we need to double check whether or not + * the zone was marked as dying while we were here. If it hasn't, then + * it's safe for us to link it in. + */ + if (vdp->vdd_flags & VND_D_ZONE_DYING) { + mutex_exit(&vdp->vdd_lock); + mutex_exit(&vnd_dev_lock); + ret = EIO; + vil.vil_errno = VND_E_NOZONE; + goto error; + } + + (void) strlcpy(vdp->vdd_lname, vil.vil_name, sizeof (vdp->vdd_lname)); + if (ddi_create_minor_node(vnd_dip, mname, S_IFCHR, vdp->vdd_minor, + DDI_PSEUDO, 0) != DDI_SUCCESS) { + ret = EIO; + vil.vil_errno = VND_E_MINORNODE; + } else { + vdp->vdd_flags &= ~VND_D_LINK_INFLIGHT; + vdp->vdd_flags |= VND_D_LINKED; + kstat_named_setstr(&vdp->vdd_str->vns_ksdata.vks_linkname, + vdp->vdd_lname); + ret = 0; + } + mutex_exit(&vdp->vdd_lock); + mutex_exit(&vnd_dev_lock); + + if (ret == 0) { + /* + * Add a reference to represent that this device is linked into + * the file system name space to ensure that it doesn't + * disappear. + */ + vnd_dev_ref(vdp); + return (0); + } + +error: + mutex_enter(&vdp->vdd_lock); + vdp->vdd_flags &= ~VND_D_LINK_INFLIGHT; + vdp->vdd_lname[0] = '\0'; + mutex_exit(&vdp->vdd_lock); + +errcopyout: + if (ddi_copyout(&vil, (void *)arg, sizeof (vil), cpflag) != 0) + ret = EFAULT; + return (ret); +} + +/* + * Common unlink function. This is used both from the ioctl path and from the + * netstack shutdown path. The caller is required to hold the mutex on the + * vnd_dev_t, but they basically will have it relinquished for them. The only + * thing the caller is allowed to do afterward is to potentially rele the + * vnd_dev_t if they have their own hold. Note that only the ioctl path has its + * own hold. + */ +static void +vnd_dev_unlink(vnd_dev_t *vdp) +{ + char mname[2*VND_NAMELEN]; + + ASSERT(MUTEX_HELD(&vdp->vdd_lock)); + + (void) snprintf(mname, sizeof (mname), "z%d:%s", + vdp->vdd_nsd->vpnd_zid, vdp->vdd_lname); + ddi_remove_minor_node(vnd_dip, mname); + vdp->vdd_lname[0] = '\0'; + vdp->vdd_flags &= ~VND_D_LINKED; + kstat_named_setstr(&vdp->vdd_str->vns_ksdata.vks_linkname, + vdp->vdd_lname); + mutex_exit(&vdp->vdd_lock); + + /* + * This rele corresponds to the reference that we took in + * vnd_ioctl_link. + */ + vnd_dev_rele(vdp); +} + +static int +vnd_ioctl_unlink(vnd_dev_t *vdp, intptr_t arg, cred_t *credp, int cpflag) +{ + int ret; + zoneid_t zid; + vnd_ioc_unlink_t viu; + + /* Not anyone can unlink something */ + if (secpolicy_net_config(credp, B_FALSE) != 0) + return (EPERM); + + zid = crgetzoneid(credp); + + if (ddi_copyin((void *)arg, &viu, sizeof (viu), cpflag) != 0) + return (EFAULT); + + viu.viu_errno = VND_E_SUCCESS; + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_LINKED)) { + mutex_exit(&vdp->vdd_lock); + ret = EIO; + viu.viu_errno = VND_E_NOTLINKED; + goto err; + } + VERIFY(vdp->vdd_flags & VND_D_ATTACHED); + + if (zid != GLOBAL_ZONEID && zid != vdp->vdd_nsd->vpnd_zid) { + mutex_exit(&vdp->vdd_lock); + ret = EIO; + viu.viu_errno = VND_E_PERM; + goto err; + } + + /* vnd_dev_unlink releases the vdp mutex for us */ + vnd_dev_unlink(vdp); + ret = 0; +err: + if (ddi_copyout(&viu, (void *)arg, sizeof (viu), cpflag) != 0) + return (EFAULT); + + return (ret); +} + +static int +vnd_ioctl_setrxbuf(vnd_dev_t *vdp, intptr_t arg, int cpflag) +{ + int ret; + vnd_ioc_buf_t vib; + + if (ddi_copyin((void *)arg, &vib, sizeof (vib), cpflag) != 0) + return (EFAULT); + + mutex_enter(&vnd_dev_lock); + if (vib.vib_size > vnd_vdq_hard_max) { + mutex_exit(&vnd_dev_lock); + vib.vib_errno = VND_E_BUFTOOBIG; + ret = EIO; + goto err; + } + mutex_exit(&vnd_dev_lock); + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vib.vib_errno = VND_E_NOTATTACHED; + ret = EIO; + goto err; + } + + mutex_enter(&vdp->vdd_str->vns_lock); + if (vib.vib_size < vdp->vdd_str->vns_minwrite) { + mutex_exit(&vdp->vdd_str->vns_lock); + mutex_exit(&vdp->vdd_lock); + vib.vib_errno = VND_E_BUFTOOSMALL; + ret = EIO; + goto err; + } + + mutex_exit(&vdp->vdd_str->vns_lock); + mutex_enter(&vdp->vdd_str->vns_dq_read.vdq_lock); + vdp->vdd_str->vns_dq_read.vdq_max = vib.vib_size; + mutex_exit(&vdp->vdd_str->vns_dq_read.vdq_lock); + mutex_exit(&vdp->vdd_lock); + ret = 0; + +err: + if (ddi_copyout(&vib, (void *)arg, sizeof (vib), cpflag) != 0) + return (EFAULT); + + return (ret); +} + +static int +vnd_ioctl_getrxbuf(vnd_dev_t *vdp, intptr_t arg, int cpflag) +{ + int ret; + vnd_ioc_buf_t vib; + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vib.vib_errno = VND_E_NOTATTACHED; + ret = EIO; + goto err; + } + + mutex_enter(&vdp->vdd_str->vns_dq_read.vdq_lock); + vib.vib_size = vdp->vdd_str->vns_dq_read.vdq_max; + mutex_exit(&vdp->vdd_str->vns_dq_read.vdq_lock); + mutex_exit(&vdp->vdd_lock); + ret = 0; + +err: + if (ddi_copyout(&vib, (void *)arg, sizeof (vib), cpflag) != 0) + return (EFAULT); + + return (ret); +} + +static int +vnd_ioctl_getmaxbuf(vnd_dev_t *vdp, intptr_t arg, int cpflag) +{ + vnd_ioc_buf_t vib; + + mutex_enter(&vnd_dev_lock); + vib.vib_size = vnd_vdq_hard_max; + mutex_exit(&vnd_dev_lock); + + if (ddi_copyout(&vib, (void *)arg, sizeof (vib), cpflag) != 0) + return (EFAULT); + + return (0); +} + +static int +vnd_ioctl_gettxbuf(vnd_dev_t *vdp, intptr_t arg, int cpflag) +{ + int ret; + vnd_ioc_buf_t vib; + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vib.vib_errno = VND_E_NOTATTACHED; + ret = EIO; + goto err; + } + + mutex_enter(&vdp->vdd_str->vns_dq_write.vdq_lock); + vib.vib_size = vdp->vdd_str->vns_dq_write.vdq_max; + mutex_exit(&vdp->vdd_str->vns_dq_write.vdq_lock); + mutex_exit(&vdp->vdd_lock); + ret = 0; + +err: + if (ddi_copyout(&vib, (void *)arg, sizeof (vib), cpflag) != 0) + return (EFAULT); + + return (ret); +} + +static int +vnd_ioctl_settxbuf(vnd_dev_t *vdp, intptr_t arg, int cpflag) +{ + int ret; + vnd_ioc_buf_t vib; + + if (ddi_copyin((void *)arg, &vib, sizeof (vib), cpflag) != 0) + return (EFAULT); + + mutex_enter(&vnd_dev_lock); + if (vib.vib_size > vnd_vdq_hard_max) { + mutex_exit(&vnd_dev_lock); + vib.vib_errno = VND_E_BUFTOOBIG; + ret = EIO; + goto err; + } + mutex_exit(&vnd_dev_lock); + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vib.vib_errno = VND_E_NOTATTACHED; + ret = EIO; + goto err; + } + + mutex_enter(&vdp->vdd_str->vns_lock); + if (vib.vib_size < vdp->vdd_str->vns_minwrite) { + mutex_exit(&vdp->vdd_str->vns_lock); + mutex_exit(&vdp->vdd_lock); + vib.vib_errno = VND_E_BUFTOOSMALL; + ret = EIO; + goto err; + } + mutex_exit(&vdp->vdd_str->vns_lock); + + mutex_enter(&vdp->vdd_str->vns_dq_write.vdq_lock); + vdp->vdd_str->vns_dq_write.vdq_max = vib.vib_size; + mutex_exit(&vdp->vdd_str->vns_dq_write.vdq_lock); + mutex_exit(&vdp->vdd_lock); + ret = 0; + +err: + if (ddi_copyout(&vib, (void *)arg, sizeof (vib), cpflag) != 0) + return (EFAULT); + + return (ret); +} + +static int +vnd_ioctl_gettu(vnd_dev_t *vdp, intptr_t arg, int mode, boolean_t min) +{ + vnd_ioc_buf_t vib; + + vib.vib_errno = 0; + mutex_enter(&vdp->vdd_lock); + if (vdp->vdd_flags & VND_D_ATTACHED) { + mutex_enter(&vdp->vdd_str->vns_lock); + if (min == B_TRUE) + vib.vib_size = vdp->vdd_str->vns_minwrite; + else + vib.vib_size = vdp->vdd_str->vns_maxwrite; + mutex_exit(&vdp->vdd_str->vns_lock); + } else { + vib.vib_errno = VND_E_NOTATTACHED; + } + mutex_exit(&vdp->vdd_lock); + + if (ddi_copyout(&vib, (void *)arg, sizeof (vib), mode & FKIOCTL) != 0) + return (EFAULT); + + return (0); +} + +static int +vnd_frameio_read(vnd_dev_t *vdp, intptr_t addr, int mode) +{ + int ret, nonblock, nwrite; + frameio_t *fio; + vnd_data_queue_t *vqp; + mblk_t *mp; + + fio = frameio_alloc(KM_NOSLEEP | KM_NORMALPRI); + if (fio == NULL) + return (EAGAIN); + + ret = frameio_hdr_copyin(fio, FRAMEIO_NVECS_MAX, (const void *)addr, + mode); + if (ret != 0) { + frameio_free(fio); + return (ret); + } + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + frameio_free(fio); + return (ENXIO); + } + mutex_exit(&vdp->vdd_lock); + + nonblock = mode & (FNONBLOCK | FNDELAY); + + vqp = &vdp->vdd_str->vns_dq_read; + mutex_enter(&vqp->vdq_lock); + + /* Check empty case */ + if (vqp->vdq_cur == 0) { + if (nonblock != 0) { + mutex_exit(&vqp->vdq_lock); + frameio_free(fio); + return (EWOULDBLOCK); + } + while (vqp->vdq_cur == 0) { + if (cv_wait_sig(&vqp->vdq_ready, &vqp->vdq_lock) <= 0) { + mutex_exit(&vqp->vdq_lock); + frameio_free(fio); + return (EINTR); + } + } + } + + ret = frameio_mblk_chain_write(fio, MAP_BLK_FRAME, vqp->vdq_head, + &nwrite, mode & FKIOCTL); + if (ret != 0) { + mutex_exit(&vqp->vdq_lock); + frameio_free(fio); + return (ret); + } + + ret = frameio_hdr_copyout(fio, nwrite, (void *)addr, mode); + if (ret != 0) { + mutex_exit(&vqp->vdq_lock); + frameio_free(fio); + return (ret); + } + + while (nwrite > 0) { + (void) vnd_dq_pop(vqp, &mp); + freemsg(mp); + nwrite--; + } + mutex_exit(&vqp->vdq_lock); + frameio_free(fio); + + return (0); +} + +static int +vnd_frameio_write(vnd_dev_t *vdp, intptr_t addr, int mode) +{ + frameio_t *fio; + int ret, nonblock, nframes, i, nread; + size_t maxwrite, minwrite, total, flen; + mblk_t *mp_chain, *mp, *nmp; + vnd_data_queue_t *vqp; + + fio = frameio_alloc(KM_NOSLEEP | KM_NORMALPRI); + if (fio == NULL) + return (EAGAIN); + + ret = frameio_hdr_copyin(fio, FRAMEIO_NVECS_MAX, (void *)addr, mode); + if (ret != 0) { + frameio_free(fio); + return (ret); + } + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + frameio_free(fio); + return (ENXIO); + } + mutex_exit(&vdp->vdd_lock); + + nonblock = mode & (FNONBLOCK | FNDELAY); + + /* + * Make sure no single frame is larger than we can accept. + */ + mutex_enter(&vdp->vdd_str->vns_lock); + minwrite = vdp->vdd_str->vns_minwrite; + maxwrite = vdp->vdd_str->vns_maxwrite; + mutex_exit(&vdp->vdd_str->vns_lock); + + nframes = fio->fio_nvpf / fio->fio_nvecs; + total = 0; + for (i = 0; i < nframes; i++) { + flen = frameio_frame_length(fio, + &fio->fio_vecs[i*fio->fio_nvpf]); + if (flen < minwrite || flen > maxwrite) { + frameio_free(fio); + return (ERANGE); + } + total += flen; + } + + vqp = &vdp->vdd_str->vns_dq_write; + mutex_enter(&vqp->vdq_lock); + while (vnd_dq_reserve(vqp, total) == 0) { + if (nonblock != 0) { + frameio_free(fio); + mutex_exit(&vqp->vdq_lock); + return (EAGAIN); + } + if (cv_wait_sig(&vqp->vdq_ready, &vqp->vdq_lock) <= 0) { + mutex_exit(&vqp->vdq_lock); + frameio_free(fio); + return (EINTR); + } + } + mutex_exit(&vqp->vdq_lock); + + /* + * We've reserved our space, let's copyin and go from here. + */ + ret = frameio_mblk_chain_read(fio, &mp_chain, &nread, mode & FKIOCTL); + if (ret != 0) { + frameio_free(fio); + vnd_dq_unreserve(vqp, total); + cv_broadcast(&vqp->vdq_ready); + pollwakeup(&vdp->vdd_ph, POLLOUT); + return (ret); + } + + for (mp = mp_chain; mp != NULL; mp = nmp) { + nmp = mp->b_next; + mp->b_next = NULL; + gsqueue_enter_one(vdp->vdd_str->vns_squeue, mp, + vnd_squeue_tx_append, vdp->vdd_str, GSQUEUE_PROCESS, + VND_SQUEUE_TAG_VND_WRITE); + } + + /* + * Update the frameio structure to indicate that we wrote those frames. + */ + frameio_mark_consumed(fio, nread); + ret = frameio_hdr_copyout(fio, nread, (void *)addr, mode); + frameio_free(fio); + + return (ret); +} + +static int +vnd_ioctl_list_copy_info(vnd_dev_t *vdp, vnd_ioc_info_t *arg, int mode) +{ + const char *link; + uint32_t vers = 1; + ASSERT(MUTEX_HELD(&vdp->vdd_lock)); + + /* + * Copy all of the members out to userland. + */ + if (ddi_copyout(&vers, &arg->vii_version, sizeof (uint32_t), + mode & FKIOCTL) != 0) + return (EFAULT); + + if (vdp->vdd_flags & VND_D_LINKED) + link = vdp->vdd_lname; + else + link = "<anonymous>"; + if (ddi_copyout(link, arg->vii_name, sizeof (arg->vii_name), + mode & FKIOCTL) != 0) + return (EFAULT); + + if (ddi_copyout(vdp->vdd_datalink, arg->vii_datalink, + sizeof (arg->vii_datalink), mode & FKIOCTL) != 0) + return (EFAULT); + + if (ddi_copyout(&vdp->vdd_nsd->vpnd_zid, &arg->vii_zone, + sizeof (zoneid_t), mode & FKIOCTL) != 0) + return (EFAULT); + return (0); +} + +static int +vnd_ioctl_list(intptr_t arg, cred_t *credp, int mode) +{ + vnd_ioc_list_t vl; + vnd_ioc_list32_t vl32; + zoneid_t zid; + vnd_dev_t *vdp; + vnd_ioc_info_t *vip; + int found, cancopy, ret; + + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { + if (ddi_copyin((void *)arg, &vl32, sizeof (vnd_ioc_list32_t), + mode & FKIOCTL) != 0) + return (EFAULT); + vl.vl_nents = vl32.vl_nents; + vl.vl_actents = vl32.vl_actents; + vl.vl_ents = (void *)(uintptr_t)vl32.vl_ents; + } else { + if (ddi_copyin((void *)arg, &vl, sizeof (vnd_ioc_list_t), + mode & FKIOCTL) != 0) + return (EFAULT); + } + + cancopy = vl.vl_nents; + vip = vl.vl_ents; + found = 0; + zid = crgetzoneid(credp); + mutex_enter(&vnd_dev_lock); + for (vdp = list_head(&vnd_dev_list); vdp != NULL; + vdp = list_next(&vnd_dev_list, vdp)) { + mutex_enter(&vdp->vdd_lock); + if (vdp->vdd_flags & VND_D_ATTACHED && + !(vdp->vdd_flags & (VND_D_CONDEMNED | VND_D_ZONE_DYING)) && + (zid == GLOBAL_ZONEID || zid == vdp->vdd_nsd->vpnd_zid)) { + found++; + if (cancopy > 0) { + ret = vnd_ioctl_list_copy_info(vdp, vip, mode); + if (ret != 0) { + mutex_exit(&vdp->vdd_lock); + mutex_exit(&vnd_dev_lock); + return (ret); + } + cancopy--; + vip++; + } + } + mutex_exit(&vdp->vdd_lock); + } + mutex_exit(&vnd_dev_lock); + + if (ddi_copyout(&found, &((vnd_ioc_list_t *)arg)->vl_actents, + sizeof (uint_t), mode & FKIOCTL) != 0) + return (EFAULT); + + return (0); +} + + +static int +vnd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + int ret; + minor_t m; + vnd_dev_t *vdp; + + m = getminor(dev); + ASSERT(m != 0); + + /* + * Make sure no one has come in on an ioctl from the strioc case. + */ + if ((cmd & VND_STRIOC) == VND_STRIOC) + return (ENOTTY); + + /* + * Like close, seems like if this minor isn't found, it's a programmer + * error somehow. + */ + vdp = vnd_dev_lookup(m); + if (vdp == NULL) + return (ENXIO); + + switch (cmd) { + case VND_IOC_ATTACH: + if (!(mode & FWRITE)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_attach(vdp, arg, credp, mode); + break; + case VND_IOC_LINK: + if (!(mode & FWRITE)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_link(vdp, arg, credp, mode); + break; + case VND_IOC_UNLINK: + if (!(mode & FWRITE)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_unlink(vdp, arg, credp, mode); + break; + case VND_IOC_GETRXBUF: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_getrxbuf(vdp, arg, mode); + break; + case VND_IOC_SETRXBUF: + if (!(mode & FWRITE)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_setrxbuf(vdp, arg, mode); + break; + case VND_IOC_GETTXBUF: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_gettxbuf(vdp, arg, mode); + break; + case VND_IOC_SETTXBUF: + if (!(mode & FWRITE)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_settxbuf(vdp, arg, mode); + break; + case VND_IOC_GETMAXBUF: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + if (crgetzoneid(credp) != GLOBAL_ZONEID) { + ret = EPERM; + break; + } + ret = vnd_ioctl_getmaxbuf(vdp, arg, mode); + break; + case VND_IOC_GETMINTU: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_gettu(vdp, arg, mode, B_TRUE); + break; + case VND_IOC_GETMAXTU: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_gettu(vdp, arg, mode, B_FALSE); + break; + case VND_IOC_FRAMEIO_READ: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + ret = vnd_frameio_read(vdp, arg, mode); + break; + case VND_IOC_FRAMEIO_WRITE: + if (!(mode & FWRITE)) { + ret = EBADF; + break; + } + ret = vnd_frameio_write(vdp, arg, mode); + break; + case VND_IOC_LIST: + if (!(mode & FREAD)) { + ret = EBADF; + break; + } + ret = vnd_ioctl_list(arg, credp, mode); + break; + default: + ret = ENOTTY; + break; + } + + vnd_dev_rele(vdp); + return (ret); +} + +static int +vnd_open(dev_t *devp, int flag, int otyp, cred_t *credp) +{ + vnd_dev_t *vdp; + minor_t m; + zoneid_t zid; + + if (flag & (FEXCL | FNDELAY)) + return (ENOTSUP); + + if (otyp & OTYP_BLK) + return (ENOTSUP); + + zid = crgetzoneid(credp); + m = getminor(*devp); + + /* + * If we have an open of a non-zero instance then we need to look that + * up in our list of entries. + */ + if (m != 0) { + + /* + * We don't check for rawaccess globally as a user could be + * doing a list ioctl on the control node which doesn't require + * this privilege. + */ + if (secpolicy_net_rawaccess(credp) != 0) + return (EPERM); + + + vdp = vnd_dev_lookup(m); + if (vdp == NULL) + return (ENOENT); + + /* + * We need to check to make sure that the user is allowed to + * open this node. At this point it should be an attached handle + * as that's all we're allowed to access. + */ + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_LINKED)) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (ENOENT); + } + + if (vdp->vdd_flags & VND_D_ZONE_DYING) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (ENOENT); + } + + if (zid != GLOBAL_ZONEID && zid != vdp->vdd_nsd->vpnd_zid) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (ENOENT); + } + + if ((flag & FEXCL) && (vdp->vdd_flags & VND_D_OPENED)) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (EBUSY); + } + + if (!(vdp->vdd_flags & VND_D_OPENED)) { + vdp->vdd_flags |= VND_D_OPENED; + vdp->vdd_ref++; + DTRACE_VND_REFINC(vdp); + } + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + + return (0); + } + + if (flag & FEXCL) + return (ENOTSUP); + + /* + * We need to clone ourselves and set up new a state. + */ + vdp = kmem_cache_alloc(vnd_dev_cache, KM_SLEEP); + bzero(vdp, sizeof (vnd_dev_t)); + + if (ldi_ident_from_dev(*devp, &vdp->vdd_ldiid) != 0) { + kmem_cache_free(vnd_dev_cache, vdp); + return (EINVAL); + } + + vdp->vdd_minor = id_alloc(vnd_minors); + mutex_init(&vdp->vdd_lock, NULL, MUTEX_DRIVER, NULL); + list_link_init(&vdp->vdd_link); + vdp->vdd_ref = 1; + *devp = makedevice(getmajor(*devp), vdp->vdd_minor); + vdp->vdd_devid = *devp; + DTRACE_VND_REFINC(vdp); + vdp->vdd_flags |= VND_D_OPENED; + + mutex_enter(&vnd_dev_lock); + list_insert_head(&vnd_dev_list, vdp); + mutex_exit(&vnd_dev_lock); + + return (0); +} + +static int +vnd_close(dev_t dev, int flag, int otyp, cred_t *credp) +{ + minor_t m; + vnd_dev_t *vdp; + + m = getminor(dev); + if (m == 0) + return (ENXIO); + + vdp = vnd_dev_lookup(m); + if (vdp == NULL) + return (ENXIO); + + mutex_enter(&vdp->vdd_lock); + VERIFY(vdp->vdd_flags & VND_D_OPENED); + vdp->vdd_flags &= ~VND_D_OPENED; + mutex_exit(&vdp->vdd_lock); + + /* Remove the hold from the previous open. */ + vnd_dev_rele(vdp); + + /* And now from lookup */ + vnd_dev_rele(vdp); + return (0); +} + +static int +vnd_read(dev_t dev, struct uio *uiop, cred_t *credp) +{ + int nonblock, error = 0; + size_t mpsize; + vnd_dev_t *vdp; + vnd_data_queue_t *vqp; + mblk_t *mp = NULL; + offset_t u_loffset; + + /* + * If we have more than one uio we refuse to do anything. That's for + * frameio. + */ + if (uiop->uio_iovcnt > 1) + return (EINVAL); + + vdp = vnd_dev_lookup(getminor(dev)); + if (vdp == NULL) + return (ENXIO); + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (ENXIO); + } + mutex_exit(&vdp->vdd_lock); + nonblock = uiop->uio_fmode & (FNONBLOCK | FNDELAY); + + vqp = &vdp->vdd_str->vns_dq_read; + mutex_enter(&vqp->vdq_lock); + + /* Check empty case */ + if (vqp->vdq_cur == 0) { + if (nonblock != 0) { + error = EWOULDBLOCK; + goto err; + } + while (vqp->vdq_cur == 0) { + if (cv_wait_sig(&vqp->vdq_ready, &vqp->vdq_lock) <= 0) { + error = EINTR; + goto err; + } + } + } + + /* Ensure our buffer is big enough */ + mp = vqp->vdq_head; + ASSERT(mp != NULL); + mpsize = msgsize(mp); + if (mpsize > uiop->uio_resid) { + error = EOVERFLOW; + goto err; + } + + u_loffset = uiop->uio_loffset; + while (mp != NULL) { + if (uiomove(mp->b_rptr, MBLKL(mp), UIO_READ, uiop) != 0) { + error = EFAULT; + uiop->uio_loffset = u_loffset; + mp = NULL; + goto err; + } + mpsize -= MBLKL(mp); + mp = mp->b_cont; + } + ASSERT(mpsize == 0); + (void) vnd_dq_pop(vqp, &mp); + freemsg(mp); +err: + mutex_exit(&vqp->vdq_lock); + vnd_dev_rele(vdp); + + return (error); +} + +static int +vnd_write(dev_t dev, struct uio *uiop, cred_t *credp) +{ + int nonblock, error; + vnd_dev_t *vdp; + mblk_t *mp; + ssize_t iosize, origsize; + vnd_data_queue_t *vqp; + + if (uiop->uio_iovcnt > 1) + return (EINVAL); + + vdp = vnd_dev_lookup(getminor(dev)); + if (vdp == NULL) + return (ENXIO); + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (ENXIO); + } + mutex_exit(&vdp->vdd_lock); + nonblock = uiop->uio_fmode & (FNONBLOCK | FNDELAY); + + mutex_enter(&vdp->vdd_str->vns_lock); + if (uiop->uio_resid > vdp->vdd_str->vns_maxwrite || + uiop->uio_resid < vdp->vdd_str->vns_minwrite) { + mutex_exit(&vdp->vdd_str->vns_lock); + vnd_dev_rele(vdp); + return (ERANGE); + } + mutex_exit(&vdp->vdd_str->vns_lock); + VERIFY(vdp->vdd_str != NULL); + + /* + * Reserve space in the data queue if we can. If we can't, block or + * return EAGAIN. If we can, go and squeue_enter. + */ + vqp = &vdp->vdd_str->vns_dq_write; + mutex_enter(&vqp->vdq_lock); + while (vnd_dq_reserve(vqp, uiop->uio_resid) == 0) { + if (nonblock != 0) { + mutex_exit(&vqp->vdq_lock); + vnd_dev_rele(vdp); + return (EAGAIN); + } + if (cv_wait_sig(&vqp->vdq_ready, &vqp->vdq_lock) <= 0) { + mutex_exit(&vqp->vdq_lock); + vnd_dev_rele(vdp); + return (EINTR); + } + } + mutex_exit(&vqp->vdq_lock); + + /* + * Now that we've reserved the space, try to allocate kernel space for + * and copy in the block. To take care of all this we use the + * strmakedata subroutine for now. + */ + origsize = iosize = uiop->uio_resid; + error = strmakedata(&iosize, uiop, vdp->vdd_str->vns_wq->q_stream, 0, + &mp); + + /* + * strmakedata() will return an error or it may only consume a portion + * of the data. + */ + if (error != 0 || uiop->uio_resid != 0) { + vnd_dq_unreserve(vqp, origsize); + cv_broadcast(&vqp->vdq_ready); + pollwakeup(&vdp->vdd_ph, POLLOUT); + vnd_dev_rele(vdp); + return (ENOSR); + } + + gsqueue_enter_one(vdp->vdd_str->vns_squeue, mp, + vnd_squeue_tx_append, vdp->vdd_str, GSQUEUE_PROCESS, + VND_SQUEUE_TAG_VND_WRITE); + + vnd_dev_rele(vdp); + return (0); +} + +static int +vnd_chpoll(dev_t dev, short events, int anyyet, short *reventsp, + struct pollhead **phpp) +{ + int ready = 0; + vnd_dev_t *vdp; + vnd_data_queue_t *vqp; + + vdp = vnd_dev_lookup(getminor(dev)); + if (vdp == NULL) + return (ENXIO); + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_ATTACHED)) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (ENXIO); + } + mutex_exit(&vdp->vdd_lock); + + if ((events & POLLIN) || (events & POLLRDNORM)) { + vqp = &vdp->vdd_str->vns_dq_read; + mutex_enter(&vqp->vdq_lock); + if (vqp->vdq_head != NULL) + ready |= events & (POLLIN | POLLRDNORM); + mutex_exit(&vqp->vdq_lock); + } + + if (events & POLLOUT) { + vqp = &vdp->vdd_str->vns_dq_write; + mutex_enter(&vqp->vdq_lock); + if (vqp->vdq_cur != vqp->vdq_max) + ready |= POLLOUT; + mutex_exit(&vqp->vdq_lock); + } + + if (ready != 0) { + *reventsp = ready; + vnd_dev_rele(vdp); + return (0); + } + + *reventsp = 0; + if (!anyyet) + *phpp = &vdp->vdd_ph; + + vnd_dev_rele(vdp); + return (0); +} + +static void * +vnd_stack_init(netstackid_t stackid, netstack_t *ns) +{ + vnd_pnsd_t *nsp; + + nsp = kmem_cache_alloc(vnd_pnsd_cache, KM_SLEEP); + bzero(nsp, sizeof (*nsp)); + nsp->vpnd_nsid = stackid; + nsp->vpnd_zid = netstackid_to_zoneid(stackid); + nsp->vpnd_flags = 0; + mutex_init(&nsp->vpnd_lock, NULL, MUTEX_DRIVER, NULL); + list_create(&nsp->vpnd_dev_list, sizeof (vnd_dev_t), + offsetof(vnd_dev_t, vdd_nslink)); + if (vnd_netinfo_init(nsp) == 0) + nsp->vpnd_hooked = B_TRUE; + + mutex_enter(&vnd_dev_lock); + list_insert_tail(&vnd_nsd_list, nsp); + mutex_exit(&vnd_dev_lock); + + return (nsp); +} + +static void +vnd_stack_shutdown(netstackid_t stackid, void *arg) +{ + vnd_pnsd_t *nsp = arg; + vnd_dev_t *vdp; + + ASSERT(nsp != NULL); + /* + * After shut down no one should be able to find their way to this + * netstack again. + */ + mutex_enter(&vnd_dev_lock); + list_remove(&vnd_nsd_list, nsp); + mutex_exit(&vnd_dev_lock); + + /* + * Make sure hooks know that they're going away. + */ + if (nsp->vpnd_hooked == B_TRUE) + vnd_netinfo_shutdown(nsp); + + /* + * Now we need to go through and notify each zone that they are in + * teardown phase. See the big theory statement section on vnd, zones, + * netstacks, and sdev for more information about this. + */ + mutex_enter(&nsp->vpnd_lock); + nsp->vpnd_flags |= VND_NS_CONDEMNED; + for (vdp = list_head(&nsp->vpnd_dev_list); vdp != NULL; + vdp = list_next(&nsp->vpnd_dev_list, vdp)) { + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_CONDEMNED)) + vdp->vdd_flags |= VND_D_ZONE_DYING; + mutex_exit(&vdp->vdd_lock); + } + mutex_exit(&nsp->vpnd_lock); + + /* + * Next we remove all the links as we know nothing new can be added to + * the list and that none of the extent devices can obtain additional + * links. + */ +restart: + mutex_enter(&nsp->vpnd_lock); + for (vdp = list_head(&nsp->vpnd_dev_list); vdp != NULL; + vdp = list_next(&nsp->vpnd_dev_list, vdp)) { + mutex_enter(&vdp->vdd_lock); + if ((vdp->vdd_flags & VND_D_CONDEMNED) || + !(vdp->vdd_flags & VND_D_LINKED)) { + mutex_exit(&vdp->vdd_lock); + continue; + } + + /* + * We drop our lock here and restart afterwards. Note that as + * part of unlinking we end up doing a rele of the vnd_dev_t. If + * this is the final hold on the vnd_dev_t then it might try and + * remove itself. Our locking rules requires not to be holding + * any locks when we call any of the rele functions. + * + * Note that the unlink function requires holders to call into + * it with the vnd_dev_t->vdd_lock held and will take care of it + * for us. Because we don't have a hold on it, we're done at + * this point. + */ + mutex_exit(&nsp->vpnd_lock); + /* Forcibly unlink */ + vnd_dev_unlink(vdp); + goto restart; + } + mutex_exit(&nsp->vpnd_lock); +} + +static void +vnd_stack_destroy(netstackid_t stackid, void *arg) +{ + vnd_pnsd_t *nsp = arg; + + ASSERT(nsp != NULL); + + /* + * Now that we've unlinked everything we just have to hang out for + * it to finish exiting. Now that it's no longer the kernel itself + * that's doing this we just need to wait for our reference count to + * equal zero and then we're free. If the global zone is holding open a + * reference to a vnd device for another zone, that's bad, but there's + * nothing much we can do. See the section on 'vnd, zones, netstacks' in + * the big theory statement for more information. + */ + mutex_enter(&nsp->vpnd_lock); + while (nsp->vpnd_ref != 0) + cv_wait(&nsp->vpnd_ref_change, &nsp->vpnd_lock); + mutex_exit(&nsp->vpnd_lock); + + /* + * During shutdown we removed ourselves from the list and now we have no + * more references so we can safely say that there is nothing left and + * destroy everything that we had sitting around. + */ + if (nsp->vpnd_hooked == B_TRUE) + vnd_netinfo_fini(nsp); + + mutex_destroy(&nsp->vpnd_lock); + list_destroy(&nsp->vpnd_dev_list); + kmem_cache_free(vnd_pnsd_cache, nsp); +} + +/* + * Convert a node with a name of the form /dev/vnd/zone/%zonename and + * /dev/vnd/zone/%zonename/%linkname to the corresponding vnd netstack. + */ +static vnd_pnsd_t * +vnd_sdev_ctx_to_ns(sdev_ctx_t ctx) +{ + enum vtype vt; + const char *path = sdev_ctx_path(ctx); + char *zstart, *dup; + size_t duplen; + vnd_pnsd_t *nsp; + + vt = sdev_ctx_vtype(ctx); + ASSERT(strncmp(path, VND_SDEV_ZROOT, strlen(VND_SDEV_ZROOT)) == 0); + + if (vt == VDIR) { + zstart = strrchr(path, '/'); + ASSERT(zstart != NULL); + zstart++; + return (vnd_nsd_lookup_by_zonename(zstart)); + } + + ASSERT(vt == VCHR); + + dup = strdup(path); + duplen = strlen(dup) + 1; + zstart = strrchr(dup, '/'); + *zstart = '\0'; + zstart--; + zstart = strrchr(dup, '/'); + zstart++; + nsp = vnd_nsd_lookup_by_zonename(zstart); + kmem_free(dup, duplen); + + return (nsp); +} + +static sdev_plugin_validate_t +vnd_sdev_validate_dir(sdev_ctx_t ctx) +{ + vnd_pnsd_t *nsp; + + if (strcmp(sdev_ctx_path(ctx), VND_SDEV_ROOT) == 0) + return (SDEV_VTOR_VALID); + + if (strcmp(sdev_ctx_path(ctx), VND_SDEV_ZROOT) == 0) { + ASSERT(getzoneid() == GLOBAL_ZONEID); + ASSERT(sdev_ctx_flags(ctx) & SDEV_CTX_GLOBAL); + return (SDEV_VTOR_VALID); + } + + nsp = vnd_sdev_ctx_to_ns(ctx); + if (nsp == NULL) + return (SDEV_VTOR_INVALID); + vnd_nsd_rele(nsp); + + return (SDEV_VTOR_VALID); +} + +static sdev_plugin_validate_t +vnd_sdev_validate(sdev_ctx_t ctx) +{ + enum vtype vt; + dev_t dev; + vnd_dev_t *vdp; + + vt = sdev_ctx_vtype(ctx); + if (vt == VDIR) + return (vnd_sdev_validate_dir(ctx)); + ASSERT(vt == VCHR); + + if (strcmp("ctl", sdev_ctx_name(ctx)) == 0) + return (SDEV_VTOR_VALID); + + dev = (uintptr_t)sdev_ctx_vtype_data(ctx); + vdp = vnd_dev_lookup(getminor(dev)); + if (vdp == NULL) + return (SDEV_VTOR_STALE); + + mutex_enter(&vdp->vdd_lock); + if (!(vdp->vdd_flags & VND_D_LINKED) || + (vdp->vdd_flags & (VND_D_CONDEMNED | VND_D_ZONE_DYING))) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (SDEV_VTOR_STALE); + } + + if (strcmp(sdev_ctx_name(ctx), vdp->vdd_lname) != 0) { + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (SDEV_VTOR_STALE); + } + + mutex_exit(&vdp->vdd_lock); + vnd_dev_rele(vdp); + return (SDEV_VTOR_VALID); +} + +/* + * This function is a no-op. sdev never has holds on our devices as they can go + * away at any time and specfs has to deal with that fact. + */ +static void +vnd_sdev_inactive(sdev_ctx_t ctx) +{ +} + +static int +vnd_sdev_fillzone(vnd_pnsd_t *nsp, sdev_ctx_t ctx) +{ + int ret; + vnd_dev_t *vdp; + + mutex_enter(&nsp->vpnd_lock); + for (vdp = list_head(&nsp->vpnd_dev_list); vdp != NULL; + vdp = list_next(&nsp->vpnd_dev_list, vdp)) { + mutex_enter(&vdp->vdd_lock); + if ((vdp->vdd_flags & VND_D_LINKED) && + !(vdp->vdd_flags & (VND_D_CONDEMNED | VND_D_ZONE_DYING))) { + ret = sdev_plugin_mknod(ctx, vdp->vdd_lname, S_IFCHR, + vdp->vdd_devid); + if (ret != 0 && ret != EEXIST) { + mutex_exit(&vdp->vdd_lock); + mutex_exit(&nsp->vpnd_lock); + vnd_nsd_rele(nsp); + return (ret); + } + } + mutex_exit(&vdp->vdd_lock); + } + mutex_exit(&nsp->vpnd_lock); + + return (0); +} + +static int +vnd_sdev_filldir_root(sdev_ctx_t ctx) +{ + zoneid_t zid; + vnd_pnsd_t *nsp; + int ret; + + zid = getzoneid(); + nsp = vnd_nsd_lookup(zoneid_to_netstackid(zid)); + ASSERT(nsp != NULL); + ret = vnd_sdev_fillzone(nsp, ctx); + vnd_nsd_rele(nsp); + if (ret != 0) + return (ret); + + /* + * Checking the zone id is not sufficient as the global zone could be + * reaching down into a non-global zone's mounted /dev. + */ + if (zid == GLOBAL_ZONEID && (sdev_ctx_flags(ctx) & SDEV_CTX_GLOBAL)) { + ret = sdev_plugin_mkdir(ctx, "zone"); + if (ret != 0 && ret != EEXIST) + return (ret); + } + + /* + * Always add a reference to the control node. There's no need to + * reference it since it always exists and is always what we clone from. + */ + ret = sdev_plugin_mknod(ctx, "ctl", S_IFCHR, + makedevice(ddi_driver_major(vnd_dip), 0)); + if (ret != 0 && ret != EEXIST) + return (ret); + + return (0); +} + +static int +vnd_sdev_filldir_zroot(sdev_ctx_t ctx) +{ + int ret; + vnd_pnsd_t *nsp; + zone_t *zonep; + + ASSERT(getzoneid() == GLOBAL_ZONEID); + ASSERT(sdev_ctx_flags(ctx) & SDEV_CTX_GLOBAL); + + mutex_enter(&vnd_dev_lock); + for (nsp = list_head(&vnd_nsd_list); nsp != NULL; + nsp = list_next(&vnd_nsd_list, nsp)) { + mutex_enter(&nsp->vpnd_lock); + if (list_is_empty(&nsp->vpnd_dev_list)) { + mutex_exit(&nsp->vpnd_lock); + continue; + } + mutex_exit(&nsp->vpnd_lock); + zonep = zone_find_by_id(nsp->vpnd_zid); + /* + * This zone must be being torn down, so skip it. + */ + if (zonep == NULL) + continue; + ret = sdev_plugin_mkdir(ctx, zonep->zone_name); + zone_rele(zonep); + if (ret != 0 && ret != EEXIST) { + mutex_exit(&vnd_dev_lock); + return (ret); + } + } + mutex_exit(&vnd_dev_lock); + return (0); +} + +static int +vnd_sdev_filldir(sdev_ctx_t ctx) +{ + int ret; + vnd_pnsd_t *nsp; + + ASSERT(sdev_ctx_vtype(ctx) == VDIR); + if (strcmp(VND_SDEV_ROOT, sdev_ctx_path(ctx)) == 0) + return (vnd_sdev_filldir_root(ctx)); + + if (strcmp(VND_SDEV_ZROOT, sdev_ctx_path(ctx)) == 0) + return (vnd_sdev_filldir_zroot(ctx)); + + ASSERT(strncmp(VND_SDEV_ZROOT, sdev_ctx_path(ctx), + strlen(VND_SDEV_ZROOT)) == 0); + nsp = vnd_sdev_ctx_to_ns(ctx); + if (nsp == NULL) + return (0); + + ret = vnd_sdev_fillzone(nsp, ctx); + vnd_nsd_rele(nsp); + + return (ret); +} + +static sdev_plugin_ops_t vnd_sdev_ops = { + SDEV_PLUGIN_VERSION, + SDEV_PLUGIN_SUBDIR, + vnd_sdev_validate, + vnd_sdev_filldir, + vnd_sdev_inactive +}; + +static int +vnd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int errp = 0; + + if (cmd != DDI_ATTACH) + return (DDI_FAILURE); + + /* + * Only allow one instance. + */ + if (vnd_dip != NULL) + return (DDI_FAILURE); + + vnd_dip = dip; + if (ddi_create_minor_node(vnd_dip, "vnd", S_IFCHR, 0, DDI_PSEUDO, 0) != + DDI_SUCCESS) { + vnd_dip = NULL; + return (DDI_FAILURE); + } + + if (ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, + DDI_KERNEL_IOCTL, NULL, 0) != DDI_PROP_SUCCESS) { + ddi_remove_minor_node(vnd_dip, NULL); + vnd_dip = NULL; + return (DDI_FAILURE); + } + + vnd_sdev_hdl = sdev_plugin_register(VND_SDEV_NAME, &vnd_sdev_ops, + &errp); + if (vnd_sdev_hdl == NULL) { + ddi_remove_minor_node(vnd_dip, NULL); + ddi_prop_remove_all(vnd_dip); + vnd_dip = NULL; + return (DDI_FAILURE); + } + + vnd_sqset = gsqueue_set_create(GSQUEUE_DEFAULT_WAIT, + GSQUEUE_DEFAULT_PRIORITY); + + return (DDI_SUCCESS); +} + +static int +vnd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + if (cmd != DDI_DETACH) + return (DDI_FAILURE); + + mutex_enter(&vnd_dev_lock); + if (!list_is_empty(&vnd_dev_list)) { + mutex_exit(&vnd_dev_lock); + return (DDI_FAILURE); + } + mutex_exit(&vnd_dev_lock); + + return (DDI_FAILURE); +} + +static int +vnd_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) +{ + int error; + + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + *result = (void *)vnd_dip; + error = DDI_SUCCESS; + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)0; + error = DDI_SUCCESS; + break; + default: + error = DDI_FAILURE; + break; + } + return (error); +} + + + +static void +vnd_ddi_fini(void) +{ + netstack_unregister(NS_VND); + if (vnd_taskq != NULL) + taskq_destroy(vnd_taskq); + if (vnd_str_cache != NULL) + kmem_cache_destroy(vnd_str_cache); + if (vnd_dev_cache != NULL) + kmem_cache_destroy(vnd_dev_cache); + if (vnd_pnsd_cache != NULL) + kmem_cache_destroy(vnd_pnsd_cache); + if (vnd_minors != NULL) + id_space_destroy(vnd_minors); + if (vnd_list_init != 0) { + list_destroy(&vnd_nsd_list); + list_destroy(&vnd_dev_list); + mutex_destroy(&vnd_dev_lock); + vnd_list_init = 0; + } + frameio_fini(); +} + +static int +vnd_ddi_init(void) +{ + if (frameio_init() != 0) + return (DDI_FAILURE); + + vnd_str_cache = kmem_cache_create("vnd_str_cache", sizeof (vnd_str_t), + 0, NULL, NULL, NULL, NULL, NULL, 0); + if (vnd_str_cache == NULL) { + frameio_fini(); + return (DDI_FAILURE); + } + vnd_dev_cache = kmem_cache_create("vnd_dev_cache", sizeof (vnd_dev_t), + 0, NULL, NULL, NULL, NULL, NULL, 0); + if (vnd_dev_cache == NULL) { + kmem_cache_destroy(vnd_str_cache); + frameio_fini(); + return (DDI_FAILURE); + } + vnd_pnsd_cache = kmem_cache_create("vnd_pnsd_cache", + sizeof (vnd_pnsd_t), 0, NULL, NULL, NULL, NULL, NULL, 0); + if (vnd_pnsd_cache == NULL) { + kmem_cache_destroy(vnd_dev_cache); + kmem_cache_destroy(vnd_str_cache); + frameio_fini(); + return (DDI_FAILURE); + } + + vnd_taskq = taskq_create_instance("vnd", -1, 1, minclsyspri, 0, 0, 0); + if (vnd_taskq == NULL) { + kmem_cache_destroy(vnd_pnsd_cache); + kmem_cache_destroy(vnd_dev_cache); + kmem_cache_destroy(vnd_str_cache); + frameio_fini(); + return (DDI_FAILURE); + } + + vnd_minors = id_space_create("vnd_minors", 1, INT32_MAX); + if (vnd_minors == NULL) { + taskq_destroy(vnd_taskq); + kmem_cache_destroy(vnd_pnsd_cache); + kmem_cache_destroy(vnd_dev_cache); + kmem_cache_destroy(vnd_str_cache); + frameio_fini(); + return (DDI_FAILURE); + } + + mutex_init(&vnd_dev_lock, NULL, MUTEX_DRIVER, NULL); + list_create(&vnd_dev_list, sizeof (vnd_dev_t), + offsetof(vnd_dev_t, vdd_link)); + list_create(&vnd_nsd_list, sizeof (vnd_pnsd_t), + offsetof(vnd_pnsd_t, vpnd_link)); + vnd_list_init = 1; + + netstack_register(NS_VND, vnd_stack_init, vnd_stack_shutdown, + vnd_stack_destroy); + + return (DDI_SUCCESS); +} + +static struct module_info vnd_minfo = { + 0, /* module id */ + "vnd", /* module name */ + 1, /* smallest packet size */ + INFPSZ, /* largest packet size (infinite) */ + 1, /* high watermark */ + 0 /* low watermark */ +}; + +static struct qinit vnd_r_qinit = { + vnd_s_rput, + NULL, + vnd_s_open, + vnd_s_close, + NULL, + &vnd_minfo, + NULL +}; + +static struct qinit vnd_w_qinit = { + vnd_s_wput, + NULL, + NULL, + NULL, + NULL, + &vnd_minfo, + NULL +}; + +static struct streamtab vnd_strtab = { + &vnd_r_qinit, + &vnd_w_qinit, + NULL, + NULL +}; + + +static struct cb_ops vnd_cb_ops = { + vnd_open, /* open */ + vnd_close, /* close */ + nulldev, /* strategy */ + nulldev, /* print */ + nodev, /* dump */ + vnd_read, /* read */ + vnd_write, /* write */ + vnd_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + vnd_chpoll, /* poll */ + ddi_prop_op, /* cb_prop_op */ + NULL, /* streamtab */ + D_MP /* Driver compatibility flag */ +}; + +static struct dev_ops vnd_dev_ops = { + DEVO_REV, /* devo_rev */ + 0, /* refcnt */ + vnd_info, /* get_dev_info */ + nulldev, /* identify */ + nulldev, /* probe */ + vnd_attach, /* attach */ + vnd_detach, /* detach */ + nodev, /* reset */ + &vnd_cb_ops, /* driver operations */ + NULL, /* bus operations */ + nodev, /* dev power */ + ddi_quiesce_not_needed /* quiesce */ +}; + +static struct modldrv vnd_modldrv = { + &mod_driverops, + "Virtual Networking Datapath Driver", + &vnd_dev_ops +}; + +static struct fmodsw vnd_fmodfsw = { + "vnd", + &vnd_strtab, + D_NEW | D_MP +}; + +static struct modlstrmod vnd_modlstrmod = { + &mod_strmodops, + "Virtual Networking Datapath Driver", + &vnd_fmodfsw +}; + +static struct modlinkage vnd_modlinkage = { + MODREV_1, + &vnd_modldrv, + &vnd_modlstrmod, + NULL +}; + +int +_init(void) +{ + int error; + + /* + * We need to do all of our global initialization in init as opposed to + * attach and detach. The problem here is that because vnd can be used + * from a stream context while being detached, we can not rely on having + * run attach to create everything, alas. so it goes in _init, just like + * our friend ip. + */ + if ((error = vnd_ddi_init()) != DDI_SUCCESS) + return (error); + error = mod_install((&vnd_modlinkage)); + if (error != 0) + vnd_ddi_fini(); + return (error); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&vnd_modlinkage, modinfop)); +} + +int +_fini(void) +{ + int error; + + error = mod_remove(&vnd_modlinkage); + if (error == 0) + vnd_ddi_fini(); + return (error); +} diff --git a/usr/src/uts/common/io/vnd/vnd.conf b/usr/src/uts/common/io/vnd/vnd.conf new file mode 100644 index 0000000000..65872e1ddf --- /dev/null +++ b/usr/src/uts/common/io/vnd/vnd.conf @@ -0,0 +1,16 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2014, Joyent, Inc. All rights reserved. +# + +name="vnd" parent="pseudo" instance=0; diff --git a/usr/src/uts/common/io/vnic/vnic_dev.c b/usr/src/uts/common/io/vnic/vnic_dev.c index 4615f00e51..c183a9c4f5 100644 --- a/usr/src/uts/common/io/vnic/vnic_dev.c +++ b/usr/src/uts/common/io/vnic/vnic_dev.c @@ -52,6 +52,7 @@ #include <sys/vlan.h> #include <sys/vnic.h> #include <sys/vnic_impl.h> +#include <sys/mac_impl.h> #include <sys/mac_flow_impl.h> #include <inet/ip_impl.h> @@ -1059,6 +1060,18 @@ vnic_m_setprop(void *m_driver, const char *pr_name, mac_prop_id_t pr_num, err = mac_maxsdu_update(vn->vn_mh, mtu); break; } + case MAC_PROP_VN_PROMISC_FILTERED: { + boolean_t filtered; + + if (pr_valsize < sizeof (filtered)) { + err = EINVAL; + break; + } + + bcopy(pr_val, &filtered, sizeof (filtered)); + mac_set_promisc_filtered(vn->vn_mch, filtered); + break; + } case MAC_PROP_SECONDARY_ADDRS: { mac_secondary_addr_t msa; @@ -1080,8 +1093,14 @@ vnic_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, { vnic_t *vn = arg; int ret = 0; + boolean_t out; switch (pr_num) { + case MAC_PROP_VN_PROMISC_FILTERED: + out = mac_get_promisc_filtered(vn->vn_mch); + ASSERT(pr_valsize >= sizeof (boolean_t)); + bcopy(&out, pr_val, sizeof (boolean_t)); + break; case MAC_PROP_SECONDARY_ADDRS: ret = vnic_get_secondary_macs(vn, pr_valsize, pr_val); break; diff --git a/usr/src/uts/common/io/zfd.c b/usr/src/uts/common/io/zfd.c new file mode 100644 index 0000000000..f70115653f --- /dev/null +++ b/usr/src/uts/common/io/zfd.c @@ -0,0 +1,815 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 2014 Joyent, Inc. All rights reserved. + */ + +/* + * Zone File Descriptor Driver. + * + * This driver is derived from the zcons driver which is in turn derived from + * the pts/ptm drivers. The purpose is to expose file descriptors within the + * zone which are connected to zoneadmd and used for logging or an interactive + * connection to a process within the zone. + * + * Its implementation is straightforward. Each instance of the driver + * represents a global-zone/local-zone pair. Unlike the zcons device, zoneadmd + * uses these devices unidirectionally to provide stdin, stdout and stderr to + * the process within the zone. + * + * Instances of zfd are onlined as children of /pseudo/zfdnex@2/ by zoneadmd, + * using the devctl framework; thus the driver does not need to maintain any + * sort of "admin" node. + * + * The driver shuttles I/O from master side to slave side and back. In a break + * from the pts/ptm semantics, if one side is not open, I/O directed towards + * it will simply be discarded. This is so that if zoneadmd is not holding the + * master side fd open (i.e. it has died somehow), processes in the zone do not + * experience any errors and I/O to the fd does not cause the process to hang. + */ + +#include <sys/types.h> +#include <sys/cmn_err.h> +#include <sys/conf.h> +#include <sys/cred.h> +#include <sys/ddi.h> +#include <sys/debug.h> +#include <sys/devops.h> +#include <sys/errno.h> +#include <sys/file.h> +#include <sys/kstr.h> +#include <sys/modctl.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/stream.h> +#include <sys/stropts.h> +#include <sys/strsun.h> +#include <sys/sunddi.h> +#include <sys/sysmacros.h> +#include <sys/systm.h> +#include <sys/types.h> +#include <sys/zfd.h> +#include <sys/vnode.h> +#include <sys/fs/snode.h> +#include <sys/zone.h> + +static int zfd_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); +static int zfd_attach(dev_info_t *, ddi_attach_cmd_t); +static int zfd_detach(dev_info_t *, ddi_detach_cmd_t); + +static int zfd_open(queue_t *, dev_t *, int, int, cred_t *); +static int zfd_close(queue_t *, int, cred_t *); +static void zfd_wput(queue_t *, mblk_t *); +static void zfd_rsrv(queue_t *); +static void zfd_wsrv(queue_t *); + +/* + * The instance number is encoded in the dev_t in the minor number; the lowest + * bit of the minor number is used to track the master vs. slave side of the + * fd. The rest of the bits in the minor number are the instance. + */ +#define ZFD_MASTER_MINOR 0 +#define ZFD_SLAVE_MINOR 1 + +#define ZFD_INSTANCE(x) (getminor((x)) >> 1) +#define ZFD_NODE(x) (getminor((x)) & 0x01) + +/* + * This macro converts a zfd_state_t pointer to the associated slave minor + * node's dev_t. + */ +#define ZFD_STATE_TO_SLAVEDEV(x) \ + (makedevice(ddi_driver_major((x)->zfd_devinfo), \ + (minor_t)(ddi_get_instance((x)->zfd_devinfo) << 1 | ZFD_SLAVE_MINOR))) + +int zfd_debug = 0; +#define DBG(a) if (zfd_debug) cmn_err(CE_NOTE, a) +#define DBG1(a, b) if (zfd_debug) cmn_err(CE_NOTE, a, b) + +/* + * ZFD Pseudo Terminal Module: stream data structure definitions, + * based on zcons. + */ +static struct module_info zfd_info = { + 0x20FD, /* ZOFD - 8445 */ + "zfd", + 0, /* min packet size */ + INFPSZ, /* max packet size - infinity */ + 2048, /* high water */ + 128 /* low water */ +}; + +static struct qinit zfd_rinit = { + NULL, + (int (*)()) zfd_rsrv, + zfd_open, + zfd_close, + NULL, + &zfd_info, + NULL +}; + +static struct qinit zfd_winit = { + (int (*)()) zfd_wput, + (int (*)()) zfd_wsrv, + NULL, + NULL, + NULL, + &zfd_info, + NULL +}; + +static struct streamtab zfd_tab_info = { + &zfd_rinit, + &zfd_winit, + NULL, + NULL +}; + +#define ZFD_CONF_FLAG (D_MP | D_MTQPAIR | D_MTOUTPERIM | D_MTOCEXCL) + +/* + * this will define (struct cb_ops cb_zfd_ops) and (struct dev_ops zfd_ops) + */ +DDI_DEFINE_STREAM_OPS(zfd_ops, nulldev, nulldev, zfd_attach, zfd_detach, \ + nodev, zfd_getinfo, ZFD_CONF_FLAG, &zfd_tab_info, \ + ddi_quiesce_not_needed); + +/* + * Module linkage information for the kernel. + */ + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module (this is a pseudo driver) */ + "Zone FD driver", /* description of module */ + &zfd_ops /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + &modldrv, + NULL +}; + +typedef struct zfd_state { + dev_info_t *zfd_devinfo; + queue_t *zfd_master_rdq; + queue_t *zfd_slave_rdq; + vnode_t *zfd_slave_vnode; + int zfd_state; + int zfd_tty; +} zfd_state_t; + +#define ZFD_STATE_MOPEN 0x01 +#define ZFD_STATE_SOPEN 0x02 + +static void *zfd_soft_state; + +/* + * List of STREAMS modules that is pushed onto a slave instance after the + * ZFD_MAKETTY ioctl has been received. + */ +static char *zfd_mods[] = { + "ptem", + "ldterm", + "ttcompat", + NULL +}; + +int +_init(void) +{ + int err; + + if ((err = ddi_soft_state_init(&zfd_soft_state, sizeof (zfd_state_t), + 0)) != 0) { + return (err); + } + + if ((err = mod_install(&modlinkage)) != 0) + ddi_soft_state_fini(zfd_soft_state); + + return (err); +} + + +int +_fini(void) +{ + int err; + + if ((err = mod_remove(&modlinkage)) != 0) { + return (err); + } + + ddi_soft_state_fini(&zfd_soft_state); + return (0); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +static int +zfd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + zfd_state_t *zfds; + int instance; + char masternm[ZFD_NAME_LEN], slavenm[ZFD_NAME_LEN]; + + if (cmd != DDI_ATTACH) + return (DDI_FAILURE); + + instance = ddi_get_instance(dip); + if (ddi_soft_state_zalloc(zfd_soft_state, instance) != DDI_SUCCESS) + return (DDI_FAILURE); + + (void) snprintf(masternm, sizeof (masternm), "%s%d", ZFD_MASTER_NAME, + instance); + (void) snprintf(slavenm, sizeof (slavenm), "%s%d", ZFD_SLAVE_NAME, + instance); + + /* + * Create the master and slave minor nodes. + */ + if ((ddi_create_minor_node(dip, slavenm, S_IFCHR, + instance << 1 | ZFD_SLAVE_MINOR, DDI_PSEUDO, 0) == DDI_FAILURE) || + (ddi_create_minor_node(dip, masternm, S_IFCHR, + instance << 1 | ZFD_MASTER_MINOR, DDI_PSEUDO, 0) == DDI_FAILURE)) { + ddi_remove_minor_node(dip, NULL); + ddi_soft_state_free(zfd_soft_state, instance); + return (DDI_FAILURE); + } + + VERIFY((zfds = ddi_get_soft_state(zfd_soft_state, instance)) != NULL); + zfds->zfd_devinfo = dip; + zfds->zfd_tty = 0; + return (DDI_SUCCESS); +} + +static int +zfd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + zfd_state_t *zfds; + int instance; + + if (cmd != DDI_DETACH) + return (DDI_FAILURE); + + instance = ddi_get_instance(dip); + if ((zfds = ddi_get_soft_state(zfd_soft_state, instance)) == NULL) + return (DDI_FAILURE); + + if ((zfds->zfd_state & ZFD_STATE_MOPEN) || + (zfds->zfd_state & ZFD_STATE_SOPEN)) { + DBG1("zfd_detach: device (dip=%p) still open\n", (void *)dip); + return (DDI_FAILURE); + } + + ddi_remove_minor_node(dip, NULL); + ddi_soft_state_free(zfd_soft_state, instance); + + return (DDI_SUCCESS); +} + +/* + * zfd_getinfo() + * getinfo(9e) entrypoint. + */ +/*ARGSUSED*/ +static int +zfd_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) +{ + zfd_state_t *zfds; + int instance = ZFD_INSTANCE((dev_t)arg); + + switch (infocmd) { + case DDI_INFO_DEVT2DEVINFO: + if ((zfds = ddi_get_soft_state(zfd_soft_state, + instance)) == NULL) + return (DDI_FAILURE); + *result = zfds->zfd_devinfo; + return (DDI_SUCCESS); + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)(uintptr_t)instance; + return (DDI_SUCCESS); + } + return (DDI_FAILURE); +} + +/* + * Return the equivalent queue from the other side of the relationship. + * e.g.: given the slave's write queue, return the master's write queue. + */ +static queue_t * +zfd_switch(queue_t *qp) +{ + zfd_state_t *zfds = qp->q_ptr; + ASSERT(zfds != NULL); + + if (qp == zfds->zfd_master_rdq) + return (zfds->zfd_slave_rdq); + else if (OTHERQ(qp) == zfds->zfd_master_rdq && zfds->zfd_slave_rdq + != NULL) + return (OTHERQ(zfds->zfd_slave_rdq)); + else if (qp == zfds->zfd_slave_rdq) + return (zfds->zfd_master_rdq); + else if (OTHERQ(qp) == zfds->zfd_slave_rdq && zfds->zfd_master_rdq + != NULL) + return (OTHERQ(zfds->zfd_master_rdq)); + else + return (NULL); +} + +/* + * For debugging and outputting messages. Returns the name of the side of + * the relationship associated with this queue. + */ +static const char * +zfd_side(queue_t *qp) +{ + zfd_state_t *zfds = qp->q_ptr; + ASSERT(zfds != NULL); + + if (qp == zfds->zfd_master_rdq || + OTHERQ(qp) == zfds->zfd_master_rdq) { + return ("master"); + } + ASSERT(qp == zfds->zfd_slave_rdq || OTHERQ(qp) == zfds->zfd_slave_rdq); + return ("slave"); +} + +/*ARGSUSED*/ +static int +zfd_master_open(zfd_state_t *zfds, + queue_t *rqp, /* pointer to the read side queue */ + dev_t *devp, /* pointer to stream tail's dev */ + int oflag, /* the user open(2) supplied flags */ + int sflag, /* open state flag */ + cred_t *credp) /* credentials */ +{ + mblk_t *mop; + struct stroptions *sop; + + /* + * Enforce exclusivity on the master side; the only consumer should + * be the zoneadmd for the zone. + */ + if ((zfds->zfd_state & ZFD_STATE_MOPEN) != 0) + return (EBUSY); + + if ((mop = allocb(sizeof (struct stroptions), BPRI_MED)) == NULL) { + DBG("zfd_master_open(): mop allocation failed\n"); + return (ENOMEM); + } + + zfds->zfd_state |= ZFD_STATE_MOPEN; + + /* + * q_ptr stores driver private data; stash the soft state data on both + * read and write sides of the queue. + */ + WR(rqp)->q_ptr = rqp->q_ptr = zfds; + qprocson(rqp); + + /* + * Following qprocson(), the master side is fully plumbed into the + * STREAM and may send/receive messages. Setting zfds->zfd_master_rdq + * will allow the slave to send messages to us (the master). + * This cannot occur before qprocson() because the master is not + * ready to process them until that point. + */ + zfds->zfd_master_rdq = rqp; + + /* + * set up hi/lo water marks on stream head read queue and add + * controlling tty as needed. + */ + mop->b_datap->db_type = M_SETOPTS; + mop->b_wptr += sizeof (struct stroptions); + sop = (struct stroptions *)(void *)mop->b_rptr; + if (oflag & FNOCTTY) + sop->so_flags = SO_HIWAT | SO_LOWAT; + else + sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY; + sop->so_hiwat = 512; + sop->so_lowat = 256; + putnext(rqp, mop); + + return (0); +} + +/*ARGSUSED*/ +static int +zfd_slave_open(zfd_state_t *zfds, + queue_t *rqp, /* pointer to the read side queue */ + dev_t *devp, /* pointer to stream tail's dev */ + int oflag, /* the user open(2) supplied flags */ + int sflag, /* open state flag */ + cred_t *credp) /* credentials */ +{ + mblk_t *mop; + struct stroptions *sop; + /* + * The slave side can be opened as many times as needed. + */ + if ((zfds->zfd_state & ZFD_STATE_SOPEN) != 0) { + ASSERT((rqp != NULL) && (WR(rqp)->q_ptr == zfds)); + return (0); + } + + if (zfds->zfd_tty == 1) { + major_t major; + minor_t minor; + minor_t lastminor; + uint_t anchorindex; + + /* + * Set up sad(7D) so that the necessary STREAMS modules will + * be in place. A wrinkle is that 'ptem' must be anchored + * in place (see streamio(7i)) because we always want the + * fd to have terminal semantics. + */ + minor = + ddi_get_instance(zfds->zfd_devinfo) << 1 | ZFD_SLAVE_MINOR; + major = ddi_driver_major(zfds->zfd_devinfo); + lastminor = 0; + anchorindex = 1; + if (kstr_autopush(SET_AUTOPUSH, &major, &minor, &lastminor, + &anchorindex, zfd_mods) != 0) { + DBG("zfd_slave_open(): kstr_autopush() failed\n"); + return (EIO); + } + } + + if ((mop = allocb(sizeof (struct stroptions), BPRI_MED)) == NULL) { + DBG("zfd_slave_open(): mop allocation failed\n"); + return (ENOMEM); + } + + zfds->zfd_state |= ZFD_STATE_SOPEN; + + /* + * q_ptr stores driver private data; stash the soft state data on both + * read and write sides of the queue. + */ + WR(rqp)->q_ptr = rqp->q_ptr = zfds; + + qprocson(rqp); + + /* + * Must follow qprocson(), since we aren't ready to process until then. + */ + zfds->zfd_slave_rdq = rqp; + + /* + * set up hi/lo water marks on stream head read queue and add + * controlling tty as needed. + */ + mop->b_datap->db_type = M_SETOPTS; + mop->b_wptr += sizeof (struct stroptions); + sop = (struct stroptions *)(void *)mop->b_rptr; + sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY; + sop->so_hiwat = 512; + sop->so_lowat = 256; + putnext(rqp, mop); + + return (0); +} + +/* + * open(9e) entrypoint; checks sflag, and rejects anything unordinary. + */ +static int +zfd_open(queue_t *rqp, /* pointer to the read side queue */ + dev_t *devp, /* pointer to stream tail's dev */ + int oflag, /* the user open(2) supplied flags */ + int sflag, /* open state flag */ + cred_t *credp) /* credentials */ +{ + int instance = ZFD_INSTANCE(*devp); + int ret; + zfd_state_t *zfds; + + if (sflag != 0) + return (EINVAL); + + if ((zfds = ddi_get_soft_state(zfd_soft_state, instance)) == NULL) + return (ENXIO); + + switch (ZFD_NODE(*devp)) { + case ZFD_MASTER_MINOR: + ret = zfd_master_open(zfds, rqp, devp, oflag, sflag, credp); + break; + case ZFD_SLAVE_MINOR: + ret = zfd_slave_open(zfds, rqp, devp, oflag, sflag, credp); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +/* + * close(9e) entrypoint. + */ +/*ARGSUSED1*/ +static int +zfd_close(queue_t *rqp, int flag, cred_t *credp) +{ + queue_t *wqp; + mblk_t *bp; + zfd_state_t *zfds; + major_t major; + minor_t minor; + + zfds = (zfd_state_t *)rqp->q_ptr; + + if (rqp == zfds->zfd_master_rdq) { + DBG("Closing master side"); + + zfds->zfd_master_rdq = NULL; + zfds->zfd_state &= ~ZFD_STATE_MOPEN; + + /* + * qenable slave side write queue so that it can flush + * its messages as master's read queue is going away + */ + if (zfds->zfd_slave_rdq != NULL) { + qenable(WR(zfds->zfd_slave_rdq)); + } + + qprocsoff(rqp); + WR(rqp)->q_ptr = rqp->q_ptr = NULL; + + } else if (rqp == zfds->zfd_slave_rdq) { + + DBG("Closing slave side"); + zfds->zfd_state &= ~ZFD_STATE_SOPEN; + zfds->zfd_slave_rdq = NULL; + + wqp = WR(rqp); + while ((bp = getq(wqp)) != NULL) { + if (zfds->zfd_master_rdq != NULL) + putnext(zfds->zfd_master_rdq, bp); + else if (bp->b_datap->db_type == M_IOCTL) + miocnak(wqp, bp, 0, 0); + else + freemsg(bp); + } + + /* + * Qenable master side write queue so that it can flush its + * messages as slaves's read queue is going away. + */ + if (zfds->zfd_master_rdq != NULL) + qenable(WR(zfds->zfd_master_rdq)); + + qprocsoff(rqp); + WR(rqp)->q_ptr = rqp->q_ptr = NULL; + + if (zfds->zfd_tty == 1) { + /* + * Clear the sad configuration so that reopening + * doesn't fail to set up sad configuration. + */ + major = ddi_driver_major(zfds->zfd_devinfo); + minor = ddi_get_instance(zfds->zfd_devinfo) << 1 | + ZFD_SLAVE_MINOR; + (void) kstr_autopush(CLR_AUTOPUSH, &major, &minor, + NULL, NULL, NULL); + } + } + + return (0); +} + +static void +handle_mflush(queue_t *qp, mblk_t *mp) +{ + mblk_t *nmp; + DBG1("M_FLUSH on %s side", zfd_side(qp)); + + if (*mp->b_rptr & FLUSHW) { + DBG1("M_FLUSH, FLUSHW, %s side", zfd_side(qp)); + flushq(qp, FLUSHDATA); + *mp->b_rptr &= ~FLUSHW; + if ((*mp->b_rptr & FLUSHR) == 0) { + /* + * FLUSHW only. Change to FLUSHR and putnext other side, + * then we are done. + */ + *mp->b_rptr |= FLUSHR; + if (zfd_switch(RD(qp)) != NULL) { + putnext(zfd_switch(RD(qp)), mp); + return; + } + } else if ((zfd_switch(RD(qp)) != NULL) && + (nmp = copyb(mp)) != NULL) { + /* + * It is a FLUSHRW; we copy the mblk and send + * it to the other side, since we still need to use + * the mblk in FLUSHR processing, below. + */ + putnext(zfd_switch(RD(qp)), nmp); + } + } + + if (*mp->b_rptr & FLUSHR) { + DBG("qreply(qp) turning FLUSHR around\n"); + qreply(qp, mp); + return; + } + freemsg(mp); +} + +/* + * wput(9E) is symmetric for master and slave sides, so this handles both + * without splitting the codepath. (The only exception to this is the + * processing of zfd ioctls, which is restricted to the master side.) + * + * zfd_wput() looks at the other side; if there is no process holding that + * side open, it frees the message. This prevents processes from hanging + * if no one is holding open the fd. Otherwise, it putnext's high + * priority messages, putnext's normal messages if possible, and otherwise + * enqueues the messages; in the case that something is enqueued, wsrv(9E) + * will take care of eventually shuttling I/O to the other side. + */ +static void +zfd_wput(queue_t *qp, mblk_t *mp) +{ + unsigned char type = mp->b_datap->db_type; + zfd_state_t *zfds; + struct iocblk *iocbp; + + ASSERT(qp->q_ptr); + + DBG1("entering zfd_wput, %s side", zfd_side(qp)); + + /* + * Process zfd ioctl messages if qp is the master side's write queue. + */ + zfds = (zfd_state_t *)qp->q_ptr; + if (zfds->zfd_master_rdq != NULL && qp == WR(zfds->zfd_master_rdq) && + type == M_IOCTL) { + iocbp = (struct iocblk *)(void *)mp->b_rptr; + switch (iocbp->ioc_cmd) { + case ZFD_MAKETTY: + /* + * The process that passed the ioctl must be running in + * the global zone. + */ + if (crgetzoneid(iocbp->ioc_cr) != GLOBAL_ZONEID) { + miocack(qp, mp, 0, EINVAL); + return; + } + zfds->zfd_tty = 1; + miocack(qp, mp, 0, 0); + return; + default: + break; + } + } + + if (zfd_switch(RD(qp)) == NULL) { + DBG1("wput to %s side (no one listening)", zfd_side(qp)); + switch (type) { + case M_FLUSH: + handle_mflush(qp, mp); + break; + case M_IOCTL: + miocnak(qp, mp, 0, 0); + break; + default: + freemsg(mp); + break; + } + return; + } + + if (type >= QPCTL) { + DBG1("(hipri) wput, %s side", zfd_side(qp)); + switch (type) { + case M_READ: /* supposedly from ldterm? */ + DBG("zfd_wput: tossing M_READ\n"); + freemsg(mp); + break; + case M_FLUSH: + handle_mflush(qp, mp); + break; + default: + /* + * Put this to the other side. + */ + ASSERT(zfd_switch(RD(qp)) != NULL); + putnext(zfd_switch(RD(qp)), mp); + break; + } + DBG1("done (hipri) wput, %s side", zfd_side(qp)); + return; + } + + /* + * Only putnext if there isn't already something in the queue. + * otherwise things would wind up out of order. + */ + if (qp->q_first == NULL && + bcanputnext(RD(zfd_switch(qp)), mp->b_band)) { + DBG("wput: putting message to other side\n"); + putnext(RD(zfd_switch(qp)), mp); + } else { + DBG("wput: putting msg onto queue\n"); + (void) putq(qp, mp); + } + DBG1("done wput, %s side", zfd_side(qp)); +} + +/* + * rsrv(9E) is symmetric for master and slave, so zfd_rsrv() handles both + * without splitting up the codepath. + * + * Enable the write side of the partner. This triggers the partner to send + * messages queued on its write side to this queue's read side. + */ +static void +zfd_rsrv(queue_t *qp) +{ + zfd_state_t *zfds; + zfds = (zfd_state_t *)qp->q_ptr; + + /* + * Care must be taken here, as either of the master or slave side + * qptr could be NULL. + */ + ASSERT(qp == zfds->zfd_master_rdq || qp == zfds->zfd_slave_rdq); + if (zfd_switch(qp) == NULL) { + DBG("zfd_rsrv: other side isn't listening\n"); + return; + } + qenable(WR(zfd_switch(qp))); +} + +/* + * This routine is symmetric for master and slave, so it handles both without + * splitting up the codepath. + * + * If there are messages on this queue that can be sent to the other, send + * them via putnext(). Else, if queued messages cannot be sent, leave them + * on this queue. + */ +static void +zfd_wsrv(queue_t *qp) +{ + mblk_t *mp; + + DBG1("zfd_wsrv master (%s) side", zfd_side(qp)); + + /* + * Partner has no read queue, so take the data, and throw it away. + */ + if (zfd_switch(RD(qp)) == NULL) { + DBG("zfd_wsrv: other side isn't listening"); + while ((mp = getq(qp)) != NULL) { + if (mp->b_datap->db_type == M_IOCTL) + miocnak(qp, mp, 0, 0); + else + freemsg(mp); + } + flushq(qp, FLUSHALL); + return; + } + + /* + * while there are messages on this write queue... + */ + while ((mp = getq(qp)) != NULL) { + /* + * Due to the way zfd_wput is implemented, we should never + * see a control message here. + */ + ASSERT(mp->b_datap->db_type < QPCTL); + + if (bcanputnext(RD(zfd_switch(qp)), mp->b_band)) { + DBG("wsrv: send message to other side\n"); + putnext(RD(zfd_switch(qp)), mp); + } else { + DBG("wsrv: putting msg back on queue\n"); + (void) putbq(qp, mp); + break; + } + } +} |
