#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>
#include <sys/ethernet.h>
#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;
#define ROUNDUP2(x, y) (((x)+(y)-1) & ~((y)-1))
#define CHECK_AND_JUMP(err, label) if (err != USB_SUCCESS) goto label
#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
#define ONESEC (drv_usectohz(1*1000000))
struct chip_info {
int flags;
char *name;
int type;
};
#define CHIPTABLESIZE (sizeof (chiptbl_8150) / sizeof (struct chip_info))
struct urf_dev {
struct chip_info *chip;
uint8_t cr;
uint8_t tsr;
uint16_t rcr;
uint8_t txok_cnt;
};
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);
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 *);
static mblk_t *urf_tx_make_packet(struct usbgem_dev *, mblk_t *);
static mblk_t *urf_rx_make_packet(struct usbgem_dev *, mblk_t *);
#define OUTB(dp, p, v, errp, label) \
if ((*(errp) = usbgem_ctrl_out_val((dp), \
USB_DEV_REQ_HOST_TO_DEV \
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \
USB_REQ_SET_ADDRESS, \
(p), \
0, \
1, \
(v))) != USB_SUCCESS) goto label
#define OUTW(dp, p, v, errp, label) \
if ((*(errp) = usbgem_ctrl_out_val((dp), \
USB_DEV_REQ_HOST_TO_DEV \
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \
USB_REQ_SET_ADDRESS, \
(p), \
0, \
2, \
(v))) != USB_SUCCESS) goto label
#define OUTS(dp, p, buf, len, errp, label) \
if ((*(errp) = usbgem_ctrl_out((dp), \
USB_DEV_REQ_HOST_TO_DEV \
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \
USB_REQ_SET_ADDRESS, \
(p), \
0, \
(len), \
(buf), \
(len))) != USB_SUCCESS) goto label
#define IN(dp, p, vp, errp, label) \
if ((*(errp) = usbgem_ctrl_in_val((dp), \
USB_DEV_REQ_DEV_TO_HOST \
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \
USB_REQ_SET_ADDRESS, \
(p), \
0, \
sizeof ((*vp)), \
(vp))) != USB_SUCCESS) goto label
#define INS(dp, p, buf, len, errp, label) \
if ((*(errp) = usbgem_ctrl_in((dp), \
USB_DEV_REQ_DEV_TO_HOST \
| USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_RCPT_DEV, \
USB_REQ_SET_ADDRESS, \
(p), \
0, \
(len), \
(buf), \
(len))) != USB_SUCCESS) goto label
static int urf_ppa = 0;
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);
}
}
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);
}
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__));
lp->cr |= CR_RE | CR_TE;
OUTB(dp, CR, lp->cr, &err, usberr);
OUTB(dp, TCR, TCR_IFG_802_3, &err, usberr);
lp->rcr = RCR_TAIL | RCR_AER | RCR_AR;
OUTW(dp, RCR, lp->rcr, &err, usberr);
#ifdef notdef
err = urf_set_media(dp);
CHECK_AND_JUMP(err, usberr);
#endif
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;
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)
{
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
lp->rcr &= ~(RCR_AB | RCR_AD | RCR_AAM | RCR_AAP | RCR_AM);
OUTW(dp, RCR, lp->rcr, &err, usberr);
#else
OUTW(dp, RCR, lp->rcr | RCR_AAM | RCR_AAP, &err, usberr);
#endif
}
mode = RCR_AB
| RCR_AD;
bzero(mhash, sizeof (mhash));
if (dp->rxmode & RXMODE_PROMISC) {
mode |= RCR_AAM | RCR_AAP;
} else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 64/2) {
mode |= RCR_AAM;
} else if (dp->mc_count > 0) {
mode |= RCR_AM;
for (i = 0; i < dp->mc_count; i++) {
uint_t h;
h = dp->mc_list[i].hash >> (32 - 6);
mhash[h / 8] |= 1 << (h % 8);
}
}
lp->rcr |= mode;
OUTS(dp, IDR, dp->cur_addr.ether_addr_octet, ETHERADDRL, &err, usberr);
if (mode & RCR_AM) {
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__));
IN(dp, MSR, &old, &err, usberr);
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);
}
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) {
len = max(len, ETHERMIN);
if ((len & 0x3f) == 0) {
len++;
}
if ((new = allocb(len, 0)) == NULL) {
return (NULL);
}
new->b_wptr = new->b_rptr + len;
bp = new->b_rptr;
for (tp = mp; tp; tp = tp->b_cont) {
len = (uintptr_t)tp->b_wptr - (uintptr_t)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) {
dp->stats.runt++;
dp->stats.errrcv++;
return (NULL);
}
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));
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
if (len > ETHERMAX + ETHERFCSL) {
dp->stats.frame_too_long++;
dp->stats.errrcv++;
return (NULL);
} else if (len < ETHERMIN + ETHERFCSL) {
dp->stats.runt++;
dp->stats.errrcv++;
return (NULL);
}
#endif
mp->b_wptr -= ETHERFCSL;
return (mp);
}
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;
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:
;
}
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;
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) {
new = old | 0x04;
} else {
new = old & ~0x04;
}
if (new != old) {
OUTB(dp, CR, lp->cr | CR_WEPROM, &err, usberr);
IN(dp, URF_EEPROM_BASE + 8, ®, &err, usberr);
new = (new << 8) | reg;
OUTW(dp, URF_EEPROM_BASE + 8, new, &err, usberr);
OUTB(dp, CR, lp->cr, &err, usberr);
}
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;
}
}
cmn_err(CE_WARN, "%s: %s: failed to autoload: timeout",
dp->name, __func__);
goto usberr;
autoload_done:
INS(dp, IDR, dp->dev_addr.ether_addr_octet, ETHERADDRL, &err, usberr);
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) {
ugcp = kmem_zalloc(sizeof (*ugcp), KM_SLEEP);
(void) 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;
ugcp->usbgc_rx_header_len = 0;
ugcp->usbgc_rx_list_max = 64;
ugcp->usbgc_tx_timeout = USBGEM_TX_TIMEOUT;
ugcp->usbgc_tx_timeout_interval = ONESEC;
ugcp->usbgc_flow_control = FLOW_CONTROL_RX_PAUSE;
ugcp->usbgc_mii_link_watch_interval = ONESEC;
ugcp->usbgc_mii_an_watch_interval = ONESEC/5;
ugcp->usbgc_mii_reset_timeout = MII_RESET_TIMEOUT;
ugcp->usbgc_mii_an_timeout = MII_AN_TIMEOUT;
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_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
ugcp->usbgc_tx_make_packet = &urf_tx_make_packet;
ugcp->usbgc_rx_make_packet = &urf_rx_make_packet;
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;
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 = NULL;
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);
}
kmem_free(lp, sizeof (struct urf_dev));
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);
}
USBGEM_STREAM_OPS(urf_ops, urfattach, urfdetach);
static struct modldrv modldrv = {
&mod_driverops,
ident,
&urf_ops,
};
static struct modlinkage modlinkage = {
MODREV_1, &modldrv, NULL
};
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);
}
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));
}