iprb.c revision 0529d5c654f682ce87e4f74affd1c83c429c50e1
/*
* 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
*/
/*
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Intel Pro/100B Ethernet Driver
*/
#include <sys/ethernet.h>
#include <sys/mac_ether.h>
#include <sys/ethernet.h>
#include <sys/sysmacros.h>
#include "iprb.h"
#include "rcvbundl.h"
/*
* Intel has openly documented the programming interface for these
* parts in the "Intel 8255x 10/100 Mbps Ethernet Controller Family
* Open Source Software Developer Manual".
*
* While some open source systems have utilized many of the features
* of some models in this family (especially scatter gather and IP
* checksum support), we have elected to offer only the basic
* functionality. These are only 10/100 parts, and the additional
* complexity is not justified by the minimal performance benefit.
* KISS. So, we are only supporting the simple 82557 features.
*/
static void iprb_mii_notify(void *, link_state_t);
static int iprb_attach(dev_info_t *);
static int iprb_detach(dev_info_t *);
static int iprb_quiesce(dev_info_t *);
static int iprb_suspend(dev_info_t *);
static int iprb_resume(dev_info_t *);
static int iprb_m_start(void *);
static void iprb_m_stop(void *);
static int iprb_m_promisc(void *, boolean_t);
static int iprb_m_unicst(void *, const uint8_t *);
const void *);
void *);
static void iprb_m_propinfo(void *, const char *, mac_prop_id_t,
static void iprb_destroy(iprb_t *);
static int iprb_configure(iprb_t *);
static void iprb_identify(iprb_t *);
static void iprb_cmd_reclaim(iprb_t *);
static int iprb_cmd_ready(iprb_t *);
static int iprb_cmd_drain(iprb_t *);
static void iprb_rx_add(iprb_t *);
static void iprb_rx_init(iprb_t *);
static void iprb_periodic(void *);
static int iprb_add_intr(iprb_t *);
static void iprb_dma_free(iprb_dma_t *);
static int iprb_set_config(iprb_t *);
static int iprb_set_unicast(iprb_t *);
static int iprb_set_multicast(iprb_t *);
static int iprb_set_ucode(iprb_t *);
static void iprb_update_stats(iprb_t *);
static int iprb_start(iprb_t *);
static void iprb_error(iprb_t *, const char *, ...);
static mii_ops_t iprb_mii_ops = {
NULL, /* reset */
};
static mac_callbacks_t iprb_m_callbacks = {
NULL,
iprb_m_ioctl, /* mc_ioctl */
NULL, /* mc_getcapab */
NULL, /* mc_open */
NULL, /* mc_close */
};
/*
* Stream information
*/
static struct modldrv iprb_modldrv = {
&mod_driverops, /* drv_modops */
"Intel 8255x Ethernet", /* drv_linkinfo */
&iprb_devops /* drv_dev_ops */
};
static struct modlinkage iprb_modlinkage = {
MODREV_1, /* ml_rev */
};
static ddi_device_acc_attr_t acc_attr = {
};
static ddi_device_acc_attr_t buf_attr = {
};
/*
* The 8225x is a 32-bit addressing engine, but it can only address up
* to 31 bits on a single transaction. (Far less in reality it turns
* out.) Statistics buffers have to be 16-byte aligned, and as we
* allocate individual data pieces for other things, there is no
* compelling reason to use another attribute with support for less
* strict alignment.
*/
static ddi_dma_attr_t dma_attr = {
DMA_ATTR_V0, /* dma_attr_version */
0, /* dma_attr_addr_lo */
0xFFFFFFFFU, /* dma_attr_addr_hi */
0x7FFFFFFFU, /* dma_attr_count_max */
16, /* dma_attr_align */
0x100, /* dma_attr_burstsizes */
1, /* dma_attr_minxfer */
0xFFFFFFFFU, /* dma_attr_maxxfer */
0xFFFFFFFFU, /* dma_attr_seg */
1, /* dma_attr_sgllen */
1, /* dma_attr_granular */
0 /* dma_attr_flags */
};
#define DECL_UCODE(x) \
/*
* We don't bother allowing for tuning of the CPU saver algorithm.
* The ucode has reasonable defaults built-in. However, some variants
* apparently have bug fixes delivered via this ucode, so we still
* need to support the ucode upload.
*/
typedef struct {
} iprb_ucode_t;
#define UCODE(x) \
static const iprb_ucode_t iprb_ucode[] = {
{ 0 },
};
int
_init(void)
{
int rv;
}
return (rv);
}
int
_fini(void)
{
int rv;
}
return (rv);
}
int
{
}
int
{
uint16_t w;
int i;
/* we don't support high level interrupts, so we don't need cookies */
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/* Reset, but first go into idle state */
drv_usecwait(10);
drv_usecwait(10);
/*
* Precalculate watchdog times.
*/
/* Obtain our factory MAC address */
w = iprb_eeprom_read(ip, 0);
/*
* Generally, most devices we will ever see will
* already have fixed firmware. Since I can't verify
* the validity of the fix (no suitably downrev
* hardware), we'll just do our best to avoid it for
* devices that exhibit this behavior.
*/
/* EEPROM fix was already applied, assume safe. */
}
}
}
/* Determine whether we have an MII or a legacy 80c24 */
if ((w & 0x3f00) != 0x0600) {
return (DDI_FAILURE);
}
}
}
/* Allocate cmds and tx region */
for (i = 0; i < NUM_TX; i++) {
/* Command blocks */
return (DDI_FAILURE);
}
}
for (i = 0; i < NUM_TX; i++) {
/* Link the command blocks into a ring */
}
for (i = 0; i < NUM_RX; i++) {
/* Rx packet buffers */
return (DDI_FAILURE);
}
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
int
{
int actual;
return (DDI_FAILURE);
}
DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
&h->dmah) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
void
{
if (h->paddr != 0)
(void) ddi_dma_unbind_handle(h->dmah);
h->paddr = 0;
ddi_dma_mem_free(&h->acch);
ddi_dma_free_handle(&h->dmah);
}
void
{
int i;
/* shut down interrupts */
}
/* release DMA resources */
for (i = 0; i < NUM_TX; i++) {
}
for (i = 0; i < NUM_RX; i++) {
}
/* clean up the multicast list */
}
/* tear down register mappings */
/* clean the dip */
/* and finally toss the structure itself */
}
void
{
case 0x1229: /* 8255x family */
case 0x1030: /* Intel InBusiness */
} else {
}
break;
case 0x1209: /* Embedded 82559ER */
break;
case 0x2449: /* ICH2 */
case 0x1031: /* Pro/100 VE (ICH3) */
case 0x1032: /* Pro/100 VE (ICH3) */
case 0x1033: /* Pro/100 VM (ICH3) */
case 0x1034: /* Pro/100 VM (ICH3) */
case 0x1038: /* Pro/100 VM (ICH3) */
break;
default:
break;
}
/* Allow property override MWI support - not normally needed. */
}
}
void
{
uint16_t x;
while (mask) {
drv_usecwait(100);
drv_usecwait(100);
drv_usecwait(100);
mask >>= 1;
}
}
{
int mask;
uint16_t n;
/* if we don't know the address size yet call again to determine it */
(void) iprb_eeprom_read(ip, 0);
bits = 8;
}
/* enable the EEPROM chip select */
drv_usecwait(100);
/* send a read command */
n = 0;
drv_usecwait(100);
drv_usecwait(100);
drv_usecwait(100);
n++;
/* check the dummy 0 bit */
if (ip->eeprom_bits == 0) {
ip->eeprom_bits = n;
}
break;
}
}
if (n != ip->eeprom_bits) {
ip->eeprom_bits, n);
}
/* shift out a 16-bit word */
val = 0;
drv_usecwait(100);
drv_usecwait(100);
drv_usecwait(100);
}
/* and disable the eeprom */
drv_usecwait(100);
return (val);
}
int
{
/* wait for pending SCB commands to be accepted */
return (DDI_SUCCESS);
}
drv_usecwait(10);
}
return (DDI_FAILURE);
}
void
{
break;
}
} else {
}
}
}
int
{
for (int i = 1000000; i; i -= 10) {
return (DDI_SUCCESS);
drv_usecwait(10);
}
return (DDI_FAILURE);
}
int
{
/* If this command will consume the last CB, interrupt when done */
}
/* clear the status entry */
/* suspend upon completion of this new command */
/* clear the suspend flag from the last submitted command */
/*
* If the chip has a resume bug, then we need to try this as a work
* around. Some anecdotal evidence is that this will help solve
* the resume bug. Its a performance hit, but only if the EEPROM
* is not updated. (In theory we could do this only for 10Mbps HDX,
* but since it should just about never get used, we keep it simple.)
*/
return (DDI_FAILURE);
drv_usecwait(1);
}
/* wait for the SCB to be ready to accept a new command */
return (DDI_FAILURE);
/*
* Finally we can resume the CU. Note that if this the first
* command in the sequence (i.e. if the CU is IDLE), or if the
* CU is already busy working, then this CU resume command
* will not have any effect.
*/
return (DDI_SUCCESS);
}
{
return (NULL);
}
}
int
{
iprb_dma_t *cb;
return (DDI_FAILURE);
}
int
{
iprb_dma_t *cb;
int i;
list_t *l;
/*
* Only send the list if the total number of multicast
* address is nonzero and small enough to fit. We
* don't error out if it is too big, because in that
* case we will use the "allmulticast" support
* via iprb_set_config instead.
*/
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
}
}
int
{
iprb_dma_t *cb;
return (DDI_FAILURE);
}
}
int
{
iprb_dma_t *cb;
int i;
for (i = 0; iprb_ucode[i].length; i++) {
uc = &iprb_ucode[i];
break;
}
}
/* no matching firmware found, assume success */
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
}
}
int
{
return (DDI_FAILURE);
return (DDI_FAILURE);
return (DDI_FAILURE);
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
void
{
/* go idle */
drv_usecwait(50);
/* shut off device interrupts */
}
int
{
iprb_dma_t *cb;
/* Reset, but first go into idle state */
drv_usecwait(50);
drv_usecwait(10);
/* Reset pointers */
return (DDI_FAILURE);
return (DDI_FAILURE);
/* Send a NOP. This will be the first command seen by the device. */
return (DDI_FAILURE);
/* as that was the first command, go ahead and submit a CU start */
return (DDI_FAILURE);
/* Upload firmware. */
return (DDI_FAILURE);
/* Set up RFDs */
/* wait for the SCB */
(void) iprb_cmd_ready(ip);
/* Enable device interrupts */
return (DDI_SUCCESS);
}
void
{
int i;
/* Collect the hardware stats, but don't keep redoing it */
return;
return;
return;
for (i = 10000; i; i -= 10) {
/* yay stats are updated */
break;
}
drv_usecwait(10);
}
if (i == 0) {
return;
}
}
mblk_t *
{
iprb_dma_t *cb;
/* possibly reclaim some CBs */
/* flow control */
return (mp);
}
/* Generally this should never occur */
ip->macxmt_errs++;
return (NULL);
}
} else {
}
}
ip->macxmt_errs++;
}
return (NULL);
}
void
{
/* clear the suspend & EL bits from the previous RFD */
}
void
{
for (int i = 0; i < NUM_RX; i++)
}
mblk_t *
{
int i;
for (i = 0; i < NUM_RX; i++) {
break;
}
continue;
}
continue;
}
continue;
}
/* return it to the RFD list */
} else {
}
}
}
return (mplist);
}
int
{
(void) iprb_configure(ip);
return (0);
}
int
{
(void) iprb_configure(ip);
return (0);
}
int
{
if (add) {
return (ENOMEM);
}
list_insert_head(l, mc);
(void) iprb_configure(ip);
} else {
(void) iprb_configure(ip);
break;
}
}
if (mc)
}
return (0);
}
int
iprb_m_start(void *arg)
{
int rv;
if (rv == 0)
if (rv == 0) {
else
/* might be a lie. */
}
}
void
iprb_m_stop(void *arg)
{
} else {
}
}
}
int
{
return (0);
}
}
switch (stat) {
case MAC_STAT_IFSPEED:
}
break;
case ETHER_STAT_LINK_DUPLEX:
}
break;
case MAC_STAT_MULTIRCV:
break;
case MAC_STAT_BRDCSTRCV:
break;
case MAC_STAT_MULTIXMT:
break;
case MAC_STAT_BRDCSTXMT:
break;
case MAC_STAT_IPACKETS:
break;
case MAC_STAT_RBYTES:
break;
case MAC_STAT_OPACKETS:
break;
case MAC_STAT_OBYTES:
break;
case MAC_STAT_NORCVBUF:
break;
case MAC_STAT_COLLISIONS:
break;
case MAC_STAT_IERRORS:
break;
case MAC_STAT_OERRORS:
ip->macxmt_errs +
break;
case ETHER_STAT_ALIGN_ERRORS:
break;
case ETHER_STAT_FCS_ERRORS:
break;
case ETHER_STAT_DEFER_XMTS:
break;
break;
break;
break;
case ETHER_STAT_EX_COLLISIONS:
break;
case MAC_STAT_OVERFLOWS:
break;
case MAC_STAT_UNDERFLOWS:
break;
break;
break;
break;
case ETHER_STAT_MACXMT_ERRORS:
break;
case ETHER_STAT_MACRCV_ERRORS:
break;
default:
return (ENOTSUP);
}
return (0);
}
void
{
return;
}
switch (id) {
case MAC_PROP_DUPLEX:
case MAC_PROP_SPEED:
break;
}
}
int
void *val)
{
uint64_t x;
}
switch (id) {
case MAC_PROP_SPEED:
x = 10000000;
return (0);
case MAC_PROP_DUPLEX:
x = LINK_DUPLEX_UNKNOWN;
return (0);
}
return (ENOTSUP);
}
int
const void *val)
{
}
return (ENOTSUP);
}
mblk_t *
{
continue;
}
break;
}
}
return (mp);
}
void
{
return;
}
{
/*
* NB: we are guaranteed by the MII layer not to be suspended.
* Furthermore, we have an independent MII register.
*/
for (int i = 0; i < 100; i++) {
return (mdi & 0xffff);
}
drv_usecwait(1);
}
return (0xffff);
}
void
{
(data);
for (int i = 0; i < 100; i++) {
break;
}
}
void
{
}
{
return (DDI_INTR_UNCLAIMED);
}
if (sts == 0) {
/* No interrupt status! */
return (DDI_INTR_UNCLAIMED);
}
/* acknowledge the interrupts */
/* wait for the SCB */
(void) iprb_cmd_ready(ip);
}
}
if (mp) {
}
}
return (DDI_INTR_CLAIMED);
}
void
iprb_periodic(void *arg)
{
return;
}
/*
* If we haven't received a packet in a while, and if the link
* is up, then it might be a hung chip. This problem
* reportedly only occurs at 10 Mbps.
*/
}
/* update the statistics */
}
if (reset) {
/* We want to reconfigure */
}
}
}
int
{
/* Reset, but first go into idle state */
drv_usecwait(50);
drv_usecwait(10);
return (DDI_SUCCESS);
}
int
{
}
}
return (DDI_SUCCESS);
}
int
{
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
int
{
switch (cmd) {
case DDI_ATTACH:
return (iprb_attach(dip));
case DDI_RESUME:
return (iprb_resume(dip));
default:
return (DDI_FAILURE);
}
}
int
{
switch (cmd) {
case DDI_DETACH:
return (iprb_detach(dip));
case DDI_SUSPEND:
return (iprb_suspend(dip));
default:
return (DDI_FAILURE);
}
}
void
{
char buf[256];
}