Masa Murayama wrote:
> My pc doesn't hang after I modified re.
> The configuration of my pc is core2quad intel cpu with 2GB memory.
> It doesn't hang with both of four core configuration and sigle core
> configuration.
> I did bi-directional netperf test. It ran without hang.
>
> Would you give me the latest re.c ?
Certainly! I've attached the updated file.
Steve
/*
* Copyright (c) 2009 Steven Stallion. 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 copyright owner nor the names of any
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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/errno.h>
#include <sys/cmn_err.h>
#include <sys/varargs.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/ddi_intr.h>
#include <sys/sunddi.h>
#include <sys/kstat.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/pci.h>
#include <sys/ethernet.h>
#include <sys/crc32.h>
#include <sys/vlan.h>
#include <sys/mac.h>
#include <sys/mac_ether.h>
#include <sys/mac_provider.h>
#include "re.h"
/* Loadable module entry points */
static int re_attach(dev_info_t *, ddi_attach_cmd_t);
static int re_detach(dev_info_t *, ddi_detach_cmd_t);
static int re_quiesce(dev_info_t *);
/* MAC entry points */
static int re_m_stat(void *, uint_t, uint64_t *);
static int re_m_start(void *);
static void re_m_stop(void *);
static int re_m_promisc(void *, boolean_t);
static int re_m_multicst(void *, boolean_t, const uint8_t *);
static int re_m_unicst(void *, const uint8_t *);
static mblk_t *re_m_tx(void *, mblk_t *);
static int re_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
const void *);
static int re_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
uint_t, void *, uint_t *);
/* ISR/periodic entry points */
static uint_t re_intr(caddr_t, caddr_t);
static void re_portmon(void *);
/* Additional kstat entry points */
static int re_regs_update(kstat_t *ksp, int rw);
/* Support functions */
static int re_reset(re_t *);
static int re_init(re_t *);
static inline void re_init_dcr(re_t *);
static inline void re_init_ring(re_t *);
static inline void re_init_mac(re_t *);
static inline void re_init_phy(re_t *);
static int re_start(re_t *);
static void re_stop(re_t *);
static void re_restart(re_t *);
static int re_suspend(re_t *);
static int re_resume(re_t *);
static mblk_t *re_recv(re_t *);
static inline boolean_t re_recv_next(re_t *, re_hdr_t *);
static void re_recv_err(re_t *);
static int re_send(re_t *, mblk_t *);
static inline void re_send_pkt(re_t *);
static void re_send_err(re_t *);
static inline void re_intr_clear(re_t *);
static void re_intr_enable(re_t *);
static void re_intr_disable(re_t *);
static link_state_t re_link_test(re_t *);
static inline int re_gethdr(re_t *, re_hdr_t *);
static void re_getmode(re_t *, re_mode_t *);
static void re_getaddr(re_t *, uint8_t *);
static void re_setaddr(re_t *, uint8_t *);
static void re_setmcast(re_t *, uint8_t *);
static inline void re_setpage(re_t *, uint8_t);
static void re_read_prom(re_t *, uint8_t *, uint8_t, uint8_t);
static inline void re_rdma_clear(re_t *);
static void re_rdma_abort(re_t *);
static void re_rdma_init(re_t *, uint8_t, uint16_t, uint16_t);
static void re_rdma_get(re_t *, uint8_t *, uint16_t, uint16_t);
static int re_rdma_put(re_t *, uint8_t *, uint16_t, uint16_t);
static void re_dprintf(dev_info_t *, int, const char *, ...);
#ifdef DEBUG
#define re_debug(dip, ...) \
re_dprintf((dip), CE_NOTE, __VA_ARGS__)
#else
#define re_debug(dip, ...) /*EMPTY*/
#endif
#define re_error(dip, ...) \
re_dprintf((dip), CE_WARN, __VA_ARGS__)
extern struct mod_ops mod_driverops;
DDI_DEFINE_STREAM_OPS(re_ops, nulldev, nulldev, re_attach, re_detach,
nodev, NULL, D_MP, NULL, re_quiesce);
static struct modldrv modldrv = {
&mod_driverops, /* drv_modops */
"Realtek Ethernet", /* drv_linkinfo */
&re_ops /* drv_dev_ops */
};
static struct modlinkage modlinkage = {
MODREV_1, /* ml_rev */
{ &modldrv, NULL } /* ml_linkage */
};
static ddi_device_acc_attr_t re_acc_attr = {
DDI_DEVICE_ATTR_V0, /* devacc_attr_version */
DDI_NEVERSWAP_ACC, /* devacc_attr_endian_flags */
DDI_STRICTORDER_ACC /* devacc_attr_dataorder */
};
static mac_callbacks_t re_m_callbacks = {
MC_SETPROP | MC_GETPROP, /* mc_callbacks */
re_m_stat, /* mc_getstat */
re_m_start, /* mc_start */
re_m_stop, /* mc_stop */
re_m_promisc, /* mc_setpromisc */
re_m_multicst, /* mc_multicst */
re_m_unicst, /* mc_unicst */
re_m_tx, /* mc_tx */
NULL, /* mc_ioctl */
NULL, /* mc_getcapab */
NULL, /* mc_open */
NULL, /* mc_close */
re_m_setprop, /* mc_setprop */
re_m_getprop /* mc_getprop */
};
static uint8_t re_broadcast[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static uint8_t re_mcast_promisc[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static re_regs_kstat_t re_regs_kstats[] = {
{ "reg_cr", REG_PAGE_0, CR_REG },
{ "reg_clda0", REG_PAGE_0, CLDA0_REG },
{ "reg_clda1", REG_PAGE_0, CLDA1_REG },
{ "reg_bnry", REG_PAGE_0, BNRY_REG },
{ "reg_tsr", REG_PAGE_0, TSR_REG },
{ "reg_fifo", REG_PAGE_0, FIFO_REG },
{ "reg_isr", REG_PAGE_0, ISR_REG },
{ "reg_crda0", REG_PAGE_0, CRDA0_REG },
{ "reg_crda1", REG_PAGE_0, CRDA1_REG },
{ "reg_rsr", REG_PAGE_0, RSR_REG },
{ "reg_curr", REG_PAGE_1, CURR_REG },
{ "reg_pstart", REG_PAGE_2, PSTART_REG },
{ "reg_pstop", REG_PAGE_2, PSTOP_REG },
{ "reg_tpsr", REG_PAGE_2, TPSR_REG },
{ "reg_rcr", REG_PAGE_2, RCR_REG },
{ "reg_tcr", REG_PAGE_2, TCR_REG },
{ "reg_dcr", REG_PAGE_2, DCR_REG },
{ "reg_imr", REG_PAGE_2, IMR_REG }
};
static size_t re_regs_kstats_len =
sizeof (re_regs_kstats) / sizeof (re_regs_kstat_t);
/*
* Loadable module entry points.
*/
int
_init(void)
{
int error;
mac_init_ops(&re_ops, "re");
if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
mac_fini_ops(&re_ops);
}
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
mac_fini_ops(&re_ops);
}
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
re_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
ddi_acc_handle_t pci;
int types;
int count;
int actual;
re_t *rep;
mac_register_t *macp;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
rep = ddi_get_driver_private(dip);
return (re_resume(rep));
default:
return (DDI_FAILURE);
}
/*
* PCI configuration.
*/
if (pci_config_setup(dip, &pci) != DDI_SUCCESS) {
re_error(dip, "unable to allocate bus resources!");
return (DDI_FAILURE);
}
pci_config_put16(pci, PCI_CONF_COMM,
pci_config_get16(pci, PCI_CONF_COMM) | PCI_COMM_IO);
pci_config_teardown(&pci);
if (ddi_intr_get_supported_types(dip, &types)
!= DDI_SUCCESS || !(types & DDI_INTR_TYPE_FIXED)) {
re_error(dip, "fixed interrupts not supported!");
return (DDI_FAILURE);
}
if (ddi_intr_get_nintrs(dip, DDI_INTR_TYPE_FIXED, &count)
!= DDI_SUCCESS || count != 1) {
re_error(dip, "no fixed interrupts available!");
return (DDI_FAILURE);
}
/*
* Initialize soft state.
*/
rep = kmem_zalloc(sizeof (re_t), KM_SLEEP);
ddi_set_driver_private(dip, rep);
rep->re_dip = dip;
rep->re_htable = kmem_zalloc(sizeof (ddi_intr_handle_t), KM_SLEEP);
if (ddi_intr_alloc(dip, rep->re_htable, DDI_INTR_TYPE_FIXED, 0,
count, &actual, 0) != DDI_SUCCESS || actual != count) {
re_error(dip, "unable to allocate interrupt!");
goto failure;
}
if (ddi_intr_get_pri(rep->re_htable[0], &rep->re_intr_pri)
!= DDI_SUCCESS) {
re_error(dip, "unable to get interrupt priority!");
goto failure;
}
if (rep->re_intr_pri >= ddi_intr_get_hilevel_pri()) {
re_error(dip, "high-level interrupts not supported!");
goto failure;
}
mutex_init(&rep->re_txlock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(rep->re_intr_pri));
/*
* Map device registers.
*/
if (ddi_regs_map_setup(dip, 1, &rep->re_regs, 0, 0,
&re_acc_attr, &rep->re_regs_acch) != DDI_SUCCESS) {
re_error(dip, "unable to setup register map!");
goto failure;
}
/*
* Initialize additional kstats.
*/
if ((rep->re_regs_ksp = kstat_create("re", ddi_get_instance(dip),
"regs", "misc", KSTAT_TYPE_NAMED, re_regs_kstats_len, 0)) == NULL) {
re_error(dip, "unable to create regs kstat!");
goto failure;
}
rep->re_regs_ksp->ks_private = rep;
rep->re_regs_ksp->ks_update = re_regs_update;
/* Initialize named kstats */
for (int i = 0; i < re_regs_kstats_len; ++i) {
kstat_named_init((kstat_named_t *)rep->re_regs_ksp->ks_data + i,
re_regs_kstats[i].ks_name, KSTAT_DATA_ULONG);
}
kstat_install(rep->re_regs_ksp);
/*
* Default properties.
*/
rep->re_legacy_mode = ddi_prop_get_int(DDI_DEV_T_ANY,
rep->re_dip, 0, "legacy-mode", 0);
rep->re_adv_pause_cap = ddi_prop_get_int(DDI_DEV_T_ANY,
rep->re_dip, 0, "adv_pause_cap", 1);
rep->re_adv_10fdx_cap = ddi_prop_get_int(DDI_DEV_T_ANY,
rep->re_dip, 0, "adv_10fdx_cap", 1);
rep->re_adv_10hdx_cap = ddi_prop_get_int(DDI_DEV_T_ANY,
rep->re_dip, 0, "adv_10hdx_cap", 1);
/*
* Initialize device.
*/
mutex_enter(&rep->re_txlock);
if (re_reset(rep) != DDI_SUCCESS) {
mutex_exit(&rep->re_txlock);
goto failure;
}
/* Determine operating mode */
re_getmode(rep, &rep->re_mode);
/* Use station address as default */
re_getaddr(rep, rep->re_curraddr);
mutex_exit(&rep->re_txlock);
/*
* Enable interrupt handler.
*/
if (ddi_intr_add_handler(rep->re_htable[0], re_intr, rep, NULL)
!= DDI_SUCCESS) {
re_error(dip, "unable to add interrupt handler!");
goto failure;
}
if (ddi_intr_enable(rep->re_htable[0]) != DDI_SUCCESS) {
re_error(dip, "unable to enable interrupt handler!");
goto failure;
}
/*
* Register with MAC.
*/
if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
re_error(dip, "unable to allocate mac resources!");
goto failure;
}
macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
macp->m_driver = rep;
macp->m_dip = dip;
macp->m_src_addr = rep->re_curraddr;
macp->m_callbacks = &re_m_callbacks;
macp->m_min_sdu = 0;
macp->m_max_sdu = ETHERMTU;
macp->m_margin = VLAN_TAGSZ;
if (mac_register(macp, &rep->re_mh) != 0) {
re_error(dip, "unable to register with mac!");
goto failure;
}
mac_free(macp);
return (DDI_SUCCESS);
failure:
if (macp != NULL) {
mac_free(macp);
}
if (rep->re_htable != NULL) {
if (rep->re_htable[0] != NULL) {
(void) ddi_intr_disable(rep->re_htable[0]);
(void) ddi_intr_remove_handler(rep->re_htable[0]);
(void) ddi_intr_free(rep->re_htable[0]);
}
kmem_free(rep->re_htable, sizeof (ddi_intr_handle_t));
}
mutex_destroy(&rep->re_txlock);
if (rep->re_regs_ksp != NULL) {
kstat_delete(rep->re_regs_ksp);
}
if (rep->re_regs_acch != NULL) {
ddi_regs_map_free(&rep->re_regs_acch);
}
kmem_free(rep, sizeof (re_t));
return (DDI_FAILURE);
}
int
re_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
re_t *rep = ddi_get_driver_private(dip);
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (re_suspend(rep));
default:
return (DDI_FAILURE);
}
if (mac_unregister(rep->re_mh) != 0) {
re_error(dip, "unable to unregister from mac!");
return (DDI_FAILURE);
}
(void) ddi_intr_disable(rep->re_htable[0]);
(void) ddi_intr_remove_handler(rep->re_htable[0]);
(void) ddi_intr_free(rep->re_htable[0]);
kmem_free(rep->re_htable, sizeof (ddi_intr_handle_t));
mutex_destroy(&rep->re_txlock);
kstat_delete(rep->re_regs_ksp);
ddi_regs_map_free(&rep->re_regs_acch);
kmem_free(rep, sizeof (re_t));
return (DDI_SUCCESS);
}
int
re_quiesce(dev_info_t *dip)
{
re_t *rep = ddi_get_driver_private(dip);
/* Hard reset device */
ddi_put8(rep->re_regs_acch, RESET8(rep),
ddi_get8(rep->re_regs_acch, RESET8(rep)));
return (DDI_SUCCESS);
}
/*
* MAC entry points.
*/
int
re_m_stat(void *arg, uint_t stat, uint64_t *val)
{
re_t *rep = arg;
switch (stat) {
case MAC_STAT_IFSPEED:
*val = IFSPEED(rep->re_speed);
break;
case MAC_STAT_MULTIRCV:
*val = rep->re_multircv;
break;
case MAC_STAT_BRDCSTRCV:
*val = rep->re_brdcstrcv;
break;
case MAC_STAT_MULTIXMT:
*val = rep->re_multixmt;
break;
case MAC_STAT_BRDCSTXMT:
*val = rep->re_brdcstxmt;
break;
case MAC_STAT_NORCVBUF:
*val = rep->re_norcvbuf;
break;
case MAC_STAT_IERRORS:
*val = rep->re_ierrors;
break;
case MAC_STAT_NOXMTBUF:
*val = rep->re_noxmtbuf;
break;
case MAC_STAT_OERRORS:
*val = rep->re_oerrors;
break;
case MAC_STAT_COLLISIONS:
*val = rep->re_collisions;
break;
case MAC_STAT_RBYTES:
*val = rep->re_rbytes;
break;
case MAC_STAT_IPACKETS:
*val = rep->re_ipackets;
break;
case MAC_STAT_OBYTES:
*val = rep->re_obytes;
break;
case MAC_STAT_OPACKETS:
*val = rep->re_opackets;
break;
case MAC_STAT_UNDERFLOWS:
*val = rep->re_uflo;
break;
case MAC_STAT_OVERFLOWS:
*val = rep->re_oflo;
break;
case ETHER_STAT_ALIGN_ERRORS:
*val = rep->re_align_errors;
break;
case ETHER_STAT_FCS_ERRORS:
*val = rep->re_fcs_errors;
break;
case ETHER_STAT_FIRST_COLLISIONS:
*val = rep->re_first_collisions;
break;
case ETHER_STAT_SQE_ERRORS:
*val = rep->re_sqe_errors;
break;
case ETHER_STAT_TX_LATE_COLLISIONS:
*val = rep->re_tx_late_collisions;
break;
case ETHER_STAT_EX_COLLISIONS:
*val = rep->re_ex_collisions;
break;
case ETHER_STAT_CARRIER_ERRORS:
*val = rep->re_carrier_errors;
break;
case ETHER_STAT_CAP_10FDX:
*val = rep->re_10fdx_cap;
break;
case ETHER_STAT_CAP_10HDX:
*val = rep->re_10hdx_cap;
break;
case ETHER_STAT_CAP_PAUSE:
*val = rep->re_pause_cap;
break;
case ETHER_STAT_ADV_CAP_10FDX:
*val = rep->re_adv_10fdx_cap;
break;
case ETHER_STAT_ADV_CAP_10HDX:
*val = rep->re_adv_10hdx_cap;
break;
case ETHER_STAT_ADV_CAP_PAUSE:
*val = rep->re_adv_pause_cap;
break;
case ETHER_STAT_LINK_PAUSE:
*val = rep->re_flowctrl;
break;
case ETHER_STAT_LINK_DUPLEX:
*val = rep->re_duplex;
break;
case ETHER_STAT_TOOLONG_ERRORS:
*val = rep->re_toolong_errors;
break;
case ETHER_STAT_TOOSHORT_ERRORS:
*val = rep->re_runt_errors;
break;
case ETHER_STAT_JABBER_ERRORS:
*val = rep->re_jabber_errors;
break;
default:
return (ENOTSUP);
}
return (0);
}
int
re_m_start(void *arg)
{
re_t *rep = arg;
int error;
mutex_enter(&rep->re_txlock);
if ((error = re_start(rep)) != DDI_SUCCESS) {
return (error);
}
rep->re_portmon = ddi_periodic_add(re_portmon,
rep, PORTMON_INTERVAL, DDI_IPL_0);
rep->re_state = RE_STATE_UP;
mutex_exit(&rep->re_txlock);
return (0);
}
void
re_m_stop(void *arg)
{
re_t *rep = arg;
mutex_enter(&rep->re_txlock);
ddi_periodic_delete(rep->re_portmon);
re_stop(rep);
mac_link_update(rep->re_mh, LINK_STATE_UNKNOWN);
rep->re_state = RE_STATE_DOWN;
mutex_exit(&rep->re_txlock);
}
int
re_m_promisc(void *arg, boolean_t on)
{
re_t *rep = arg;
mutex_enter(&rep->re_txlock);
rep->re_promisc = on;
if (rep->re_state & RE_STATE_SUSPENDED) {
mutex_exit(&rep->re_txlock);
return (0);
}
if (rep->re_state & RE_STATE_UP) {
/*
* The device must be restarted for changes
* to take effect. This will destroy remaining
* data in the rx and tx buffers.
*/
re_restart(rep);
}
mutex_exit(&rep->re_txlock);
return (0);
}
int
re_m_multicst(void *arg, boolean_t add, const uint8_t *macaddr)
{
re_t *rep = arg;
uint32_t crc;
int index;
uint8_t bit;
uint8_t *val;
mutex_enter(&rep->re_txlock);
CRC32(crc, macaddr, ETHERADDRL, -1U, crc32_table);
crc >>= MCHASH_LATCH;
index = crc / MCHASHSZ;
bit = BIT(crc % MCHASHSZ);
val = &rep->re_mcast[index];
if (add) {
*val |= bit;
} else {
*val ^= bit;
}
if (rep->re_promisc || rep->re_state & RE_STATE_SUSPENDED) {
mutex_exit(&rep->re_txlock);
return (0);
}
if (rep->re_state & RE_STATE_UP) {
/*
* The device must be restarted for changes
* to take effect. This will destroy remaining
* data in the rx and tx buffers.
*/
re_restart(rep);
}
mutex_exit(&rep->re_txlock);
return (0);
}
int
re_m_unicst(void *arg, const uint8_t *macaddr)
{
re_t *rep = arg;
mutex_enter(&rep->re_txlock);
bcopy(macaddr, rep->re_curraddr, ETHERADDRL);
if (rep->re_state & RE_STATE_SUSPENDED) {
mutex_exit(&rep->re_txlock);
return (0);
}
if (rep->re_state & RE_STATE_UP) {
/*
* The device must be restarted for changes
* to take effect. This will destroy remaining
* data in the rx and tx buffers.
*/
re_restart(rep);
}
mutex_exit(&rep->re_txlock);
return (0);
}
mblk_t *
re_m_tx(void *arg, mblk_t *mp)
{
re_t *rep = arg;
mblk_t *next;
mutex_enter(&rep->re_txlock);
while (mp != NULL) {
next = mp->b_next;
mp->b_next = NULL;
if (re_send(rep, mp) != DDI_SUCCESS) {
rep->re_tx_update = B_TRUE;
mp->b_next = next;
break;
}
mp = next;
}
mutex_exit(&rep->re_txlock);
return (mp);
}
/*ARGSUSED*/
int
re_m_setprop(void *barg, const char *pr_name, mac_prop_id_t pr_num,
uint_t pr_valsize, const void *pr_val)
{
re_t *rep = barg;
int error = 0;
mutex_enter(&rep->re_txlock);
switch (pr_num) {
case MAC_PROP_FLOWCTRL: {
link_flowctrl_t val;
if (!rep->re_pause_cap || !rep->re_adv_10fdx_cap) {
error = ENOTSUP;
break;
}
if (pr_valsize < sizeof (link_flowctrl_t)) {
error = EINVAL;
break;
}
bcopy(pr_val, &val, sizeof (link_flowctrl_t));
switch (val) {
case LINK_FLOWCTRL_BI:
rep->re_adv_pause_cap = 1;
break;
case LINK_FLOWCTRL_NONE:
rep->re_adv_pause_cap = 0;
break;
default:
error = ENOTSUP;
}
if (error == 0) {
rep->re_flowctrl = val;
}
break;
}
case MAC_PROP_EN_10FDX_CAP:
if (!rep->re_10fdx_cap) {
error = ENOTSUP;
break;
}
rep->re_adv_10fdx_cap = *(uint8_t *)pr_val;
if (!rep->re_adv_10fdx_cap) {
rep->re_adv_pause_cap = 0;
}
break;
case MAC_PROP_EN_10HDX_CAP:
if (!rep->re_10hdx_cap) {
error = ENOTSUP;
break;
}
rep->re_adv_10hdx_cap = *(uint8_t *)pr_val;
break;
default:
error = ENOTSUP;
}
if (error == 0 && rep->re_state == RE_STATE_UP) {
/*
* The device must be restarted for changes
* to take effect. This will destroy remaining
* data in the rx and tx buffers.
*/
re_restart(rep);
}
mutex_exit(&rep->re_txlock);
return (error);
}
/*ARGSUSED*/
int
re_m_getprop(void *barg, const char *pr_name, mac_prop_id_t pr_num,
uint_t pr_flags, uint_t pr_valsize, void *pr_val, uint_t *perm)
{
re_t *rep = barg;
boolean_t is_default;
if (pr_valsize == 0) {
return (EINVAL);
}
*perm = MAC_PROP_PERM_RW;
is_default = pr_flags & MAC_PROP_DEFAULT;
switch (pr_num) {
case MAC_PROP_DUPLEX: {
link_duplex_t val;
*perm = MAC_PROP_PERM_READ;
if (pr_valsize < sizeof (link_duplex_t)) {
return (EINVAL);
}
if (is_default) {
val = (rep->re_10fdx_cap ?
LINK_DUPLEX_FULL : LINK_DUPLEX_HALF);
} else {
val = rep->re_duplex;
}
bcopy(&val, pr_val, sizeof (link_duplex_t));
break;
}
case MAC_PROP_SPEED: {
uint64_t val = IFSPEED(rep->re_speed);
*perm = MAC_PROP_PERM_READ;
if (pr_valsize < sizeof (uint64_t)) {
return (EINVAL);
}
bcopy(&val, pr_val, sizeof (uint64_t));
break;
}
case MAC_PROP_MTU: {
uint32_t val = ETHERMTU;
*perm = MAC_PROP_PERM_READ;
if (pr_valsize < sizeof (uint32_t)) {
return (EINVAL);
}
bcopy(&val, pr_val, sizeof (uint32_t));
break;
}
case MAC_PROP_FLOWCTRL: {
link_flowctrl_t val;
if (pr_valsize < sizeof (link_flowctrl_t)) {
return (EINVAL);
}
if (is_default) {
val = (rep->re_pause_cap ?
LINK_FLOWCTRL_BI : LINK_FLOWCTRL_NONE);
} else {
val = rep->re_flowctrl;
}
bcopy(&val, pr_val, sizeof (link_flowctrl_t));
break;
}
case MAC_PROP_ADV_10FDX_CAP:
*perm = MAC_PROP_PERM_READ;
/*FALLTHRU*/
case MAC_PROP_EN_10FDX_CAP:
*(uint8_t *)pr_val = (is_default ?
rep->re_10fdx_cap : rep->re_adv_10fdx_cap);
break;
case MAC_PROP_ADV_10HDX_CAP:
*perm = MAC_PROP_PERM_READ;
/*FALLTHRU*/
case MAC_PROP_EN_10HDX_CAP:
*(uint8_t *)pr_val = (is_default ?
rep->re_10hdx_cap : rep->re_adv_10hdx_cap);
break;
default:
return (ENOTSUP);
}
return (0);
}
/*
* ISR/periodic entry points.
*/
/*ARGSUSED*/
uint_t
re_intr(caddr_t arg1, caddr_t arg2)
{
re_t *rep = (void *)arg1;
uint8_t status;
mblk_t *mp = NULL;
mutex_enter(&rep->re_txlock);
if (rep->re_state != RE_STATE_UP) {
mutex_exit(&rep->re_txlock);
return (DDI_INTR_UNCLAIMED);
}
re_setpage(rep, REG_PAGE_0);
if ((status = REG_READ(rep, ISR_REG)) == 0) {
mutex_exit(&rep->re_txlock);
return (DDI_INTR_UNCLAIMED);
}
/* Clear interrupt status */
REG_WRITE(rep, ISR_REG, status);
if (status & ISR_OVW) {
re_error(rep->re_dip, "rx overflow; resetting!");
re_restart(rep);
} else if (status & (ISR_PTX | ISR_TXE)) {
rep->re_tx_busy = B_FALSE;
if (rep->re_tx_update) {
mac_tx_update(rep->re_mh);
rep->re_tx_update = B_FALSE;
}
if (status & ISR_TXE) {
re_send_err(rep);
}
} else if (status & ISR_PRX) {
mp = re_recv(rep);
} if (status & ISR_RXE) {
re_recv_err(rep);
}
mutex_exit(&rep->re_txlock);
/* Never submit chain within txlock! */
if (mp != NULL) {
mac_rx(rep->re_mh, NULL, mp);
}
return (DDI_INTR_CLAIMED);
}
void
re_portmon(void *arg)
{
re_t *rep = arg;
link_state_t state;
mutex_enter(&rep->re_txlock);
state = re_link_test(rep);
if (rep->re_link_state != state) {
mac_link_update(rep->re_mh,
(rep->re_link_state = state));
}
mutex_exit(&rep->re_txlock);
}
/*
* Additional kstat entry points.
*/
int
re_regs_update(kstat_t *ksp, int rw)
{
re_t *rep = ksp->ks_private;
if (rw == KSTAT_WRITE) {
return (EACCES);
}
mutex_enter(&rep->re_txlock);
/*
* Reading from these registers should never
* produce side-effects.
*/
for (int i = 0; i < re_regs_kstats_len; ++i) {
re_regs_kstat_t *rksp = &re_regs_kstats[i];
kstat_named_t *knp = (kstat_named_t *)ksp->ks_data + i;
re_setpage(rep, rksp->ks_reg_page);
knp->value.ul = REG_READ(rep, rksp->ks_reg);
}
mutex_exit(&rep->re_txlock);
return (0);
}
/*
* Support functions.
*/
int
re_reset(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
/* Reset soft state */
rep->re_duplex = LINK_DUPLEX_UNKNOWN;
rep->re_flowctrl = LINK_FLOWCTRL_NONE;
rep->re_speed = RE_SPEED_UNKNOWN;
rep->re_tx_busy = B_FALSE;
rep->re_tx_update = B_FALSE;
/* Soft reset device */
REG_WRITE(rep, CR_REG, CR_RD_ABRT | CR_STP);
/* Wait for device to settle */
for (int i = 0; i < ISR_DELAY_CYCLES; ++i) {
if (REG_READ(rep, ISR_REG) & ISR_RST) {
rep->re_init = B_FALSE;
return (DDI_SUCCESS);
}
drv_usecwait(ISR_DELAY);
}
re_error(rep->re_dip, "reset timed out!");
return (DDI_FAILURE);
}
int
re_init(re_t *rep)
{
int error;
ASSERT(mutex_owned(&rep->re_txlock));
if ((error = re_reset(rep)) != DDI_SUCCESS) {
return (error);
}
re_init_dcr(rep);
re_rdma_clear(rep);
re_init_mac(rep);
re_init_ring(rep);
re_intr_enable(rep);
re_setaddr(rep, rep->re_curraddr);
if (rep->re_promisc) {
re_setmcast(rep, re_mcast_promisc);
} else {
re_setmcast(rep, rep->re_mcast);
}
re_init_phy(rep);
rep->re_init = B_TRUE;
return (DDI_SUCCESS);
}
inline void
re_init_dcr(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
REG_WRITE(rep, DCR_REG, DCR_WTS | DCR_LS | DCR_FT_8);
}
inline void
re_init_ring(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
rep->re_rx_next = PSTART + 1;
REG_WRITE(rep, PSTART_REG, PSTART);
REG_WRITE(rep, PSTOP_REG, PSTOP);
REG_WRITE(rep, BNRY_REG, PSTART);
re_setpage(rep, REG_PAGE_1);
REG_WRITE(rep, CURR_REG, rep->re_rx_next);
}
inline void
re_init_mac(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
REG_WRITE(rep, RCR_REG, RCR_AB | RCR_AM | RCR_AR |
(rep->re_promisc ? RCR_PRO : 0));
/* Put into loopback */
REG_WRITE(rep, TCR_REG, TCR_LB_1);
}
inline void
re_init_phy(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
/* Assume all modes supported */
rep->re_pause_cap = 1;
rep->re_10fdx_cap = 1;
rep->re_10hdx_cap = 1;
/* 10Mbps only device */
rep->re_speed = RE_SPEED_10;
re_setpage(rep, REG_PAGE_3);
/* Enable PHY auto-detection */
REG_WRITE_BITS(rep, CONFIG2_REG, CONFIG2_PL, CONFIG2_PL_AUTO);
if (rep->re_mode & RE_MODE_RTL8029AS) {
REG_WRITE(rep, CR9346_REG, CR9346_EEM_ENABLE);
if (rep->re_adv_10fdx_cap) {
REG_WRITE_BIT(rep, CONFIG3_REG, CONFIG3_FUDUP);
rep->re_duplex = LINK_DUPLEX_FULL;
if (rep->re_adv_pause_cap) {
REG_WRITE_BIT(rep, CONFIG2_REG, CONFIG2_FCE);
rep->re_flowctrl = LINK_FLOWCTRL_BI;
goto done;
}
} else {
REG_CLEAR_BIT(rep, CONFIG3_REG, CONFIG3_FUDUP);
rep->re_duplex = LINK_DUPLEX_HALF;
}
REG_CLEAR_BIT(rep, CONFIG2_REG, CONFIG2_FCE);
rep->re_flowctrl = LINK_FLOWCTRL_NONE;
} else { /* RE_MODE_LEGACY */
rep->re_duplex = LINK_DUPLEX_HALF;
rep->re_flowctrl = LINK_FLOWCTRL_NONE;
/* Disable unsupported modes */
rep->re_pause_cap = rep->re_adv_pause_cap = 0;
rep->re_10fdx_cap = rep->re_adv_10fdx_cap = 0;
}
done:
REG_WRITE(rep, CR_REG, CR_RD_ABRT | CR_STA);
}
int
re_start(re_t *rep)
{
int error;
ASSERT(mutex_owned(&rep->re_txlock));
if ((error = re_init(rep)) != DDI_SUCCESS) {
return (error);
}
/* Take out of loopback */
REG_WRITE(rep, TCR_REG, TCR_LB_0);
return (DDI_SUCCESS);
}
void
re_stop(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_intr_disable(rep);
(void) re_reset(rep);
}
void
re_restart(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_stop(rep);
(void) re_start(rep);
}
int
re_suspend(re_t *rep)
{
mutex_enter(&rep->re_txlock);
if (rep->re_state & RE_STATE_UP) {
re_stop(rep);
}
rep->re_state |= RE_STATE_SUSPENDED;
mutex_exit(&rep->re_txlock);
return (DDI_SUCCESS);
}
int
re_resume(re_t *rep)
{
mutex_enter(&rep->re_txlock);
rep->re_state ^= RE_STATE_SUSPENDED;
if (rep->re_state & RE_STATE_UP) {
re_start(rep);
}
mutex_exit(&rep->re_txlock);
return (DDI_SUCCESS);
}
mblk_t *
re_recv(re_t *rep)
{
mblk_t *chain = NULL;
mblk_t **mpp = &chain;
ASSERT(mutex_owned(&rep->re_txlock));
re_hdr_t hdr;
do {
uint16_t len;
mblk_t *mp;
if (re_gethdr(rep, &hdr) != DDI_SUCCESS) {
re_debug(rep->re_dip, "spurious rx interrupt!");
return (chain);
}
len = hdr.h_len - ETHERFCSL;
if (len < ETHERMIN) {
rep->re_ierrors++;
rep->re_runt_errors++;
continue;
}
if (len > ETHERMAX + VLAN_TAGSZ) {
rep->re_ierrors++;
rep->re_toolong_errors++;
continue;
}
/* Align on word boundary */
mp = allocb(PAD32(len), 0);
if (mp == NULL) {
rep->re_ierrors++;
rep->re_norcvbuf++;
continue;
}
mp->b_wptr = mp->b_rptr + len;
re_rdma_get(rep, mp->b_rptr, hdr.h_data, len);
rep->re_ipackets++;
rep->re_rbytes += len;
if (hdr.h_status & RSR_PHY) {
if (bcmp(mp->b_rptr, re_broadcast, ETHERADDRL) == 0) {
rep->re_brdcstrcv++;
} else {
rep->re_multircv++;
}
}
*mpp = mp;
mpp = &mp->b_next;
} while (re_recv_next(rep, &hdr));
return (chain);
}
inline boolean_t
re_recv_next(re_t *rep, re_hdr_t *hp)
{
uint8_t bnry;
ASSERT(mutex_owned(&rep->re_txlock));
/* Update next packet pointer */
rep->re_rx_next = hp->h_next;
/* Update boundary pointer */
bnry = rep->re_rx_next - 1;
if (bnry < PSTART) {
bnry = PSTOP - 1;
}
REG_WRITE(rep, BNRY_REG, bnry);
re_setpage(rep, REG_PAGE_1);
/* Are more packets are available? */
return (REG_READ(rep, CURR_REG) != rep->re_rx_next);
}
void
re_recv_err(re_t *rep)
{
uint8_t status;
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_0);
if ((status = REG_READ(rep, RSR_REG)) == 0) {
return;
}
rep->re_ierrors++;
if (status & RSR_CRC) {
rep->re_fcs_errors +=
REG_READ(rep, CNTR1_REG);
}
if (status & RSR_FAE) {
rep->re_align_errors +=
REG_READ(rep, CNTR0_REG);
}
if (status & RSR_FO) {
rep->re_oflo++;
}
if (status & RSR_MPA) {
rep->re_norcvbuf +=
REG_READ(rep, CNTR2_REG);
}
if (status & RSR_DFR) {
rep->re_jabber_errors++;
}
}
int
re_send(re_t *rep, mblk_t *mp)
{
uint16_t len;
int error;
ASSERT(mutex_owned(&rep->re_txlock));
len = msgsize(mp);
if (len > ETHERMAX + VLAN_TAGSZ) {
rep->re_oerrors++;
rep->re_toolong_errors++;
freemsg(mp);
return (DDI_SUCCESS);
}
if (rep->re_tx_busy) {
rep->re_noxmtbuf++;
return (DDI_FAILURE);
}
mcopymsg(mp, rep->re_tx_buf);
/*
* Packets must contain at least ETHERMIN octets.
* Padded octets are zeroed out prior to issuing
* the remote write.
*/
rep->re_tx_len = max(ETHERMIN, len);
if (rep->re_tx_len > len) {
bzero(rep->re_tx_buf + len, rep->re_tx_len - len);
}
if ((error = re_rdma_put(rep, rep->re_tx_buf, BTOP(TXPSTART),
rep->re_tx_len)) != DDI_SUCCESS) {
return (error);
}
rep->re_opackets++;
rep->re_obytes += rep->re_tx_len;
if (*rep->re_tx_buf & 0x01) {
if (bcmp(rep->re_tx_buf, re_broadcast, ETHERADDRL) == 0) {
rep->re_brdcstxmt++;
} else {
rep->re_multixmt++;
}
}
/* Attempt to send immediately */
re_send_pkt(rep);
return (DDI_SUCCESS);
}
inline void
re_send_pkt(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
rep->re_tx_busy = B_TRUE;
REG_WRITE(rep, TPSR_REG, TXPSTART);
REG_WRITE(rep, TBCR0_REG, rep->re_tx_len);
REG_WRITE(rep, TBCR1_REG, rep->re_tx_len >> 8);
REG_WRITE(rep, CR_REG, CR_TXP | CR_STA);
}
void
re_send_err(re_t *rep)
{
uint8_t status;
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_0);
if ((status = REG_READ(rep, TSR_REG)) == 0) {
return;
}
rep->re_oerrors++;
if (status & TSR_COL) {
uint8_t ncr = REG_READ(rep, NCR_REG);
rep->re_collisions += ncr;
if (ncr == 1) {
rep->re_first_collisions++;
}
}
if (status & TSR_ABT) {
rep->re_ex_collisions++;
}
if (status & TSR_CRS) {
rep->re_carrier_errors++;
}
if (status & TSR_FU) {
rep->re_uflo++;
}
if (status & TSR_CDH) {
rep->re_sqe_errors++;
}
if (status & TSR_OWC) {
rep->re_tx_late_collisions++;
}
}
inline void
re_intr_clear(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
REG_WRITE(rep, ISR_REG, 0xff);
}
void
re_intr_enable(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_0);
re_intr_clear(rep);
REG_WRITE(rep, IMR_REG, IMR_OVWE |
IMR_PRXE | IMR_RXEE | IMR_PTXE | IMR_TXEE);
}
void
re_intr_disable(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_0);
REG_CLEAR(rep, IMR_REG);
re_intr_clear(rep);
}
link_state_t
re_link_test(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_3);
/*
* If PHY auto-detection is enabled, the BNC bit
* indicates 10BASE-T link state. If 10BASE2 media
* is used, LINK_STATE_DOWN will always be reported.
*/
return (REG_READ(rep, CONFIG0_REG) & CONFIG0_BNC ?
LINK_STATE_DOWN : LINK_STATE_UP);
}
inline int
re_gethdr(re_t *rep, re_hdr_t *hp)
{
uint32_t val;
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_1);
/* Is next packet available? */
if (REG_READ(rep, CURR_REG) == rep->re_rx_next) {
return (DDI_FAILURE);
}
hp->h_base = BTOP(rep->re_rx_next);
hp->h_data = hp->h_base + RXHDRSZ;
/*
* The rx header comprises the first 4 bytes of
* the initial rx buffer page.
*/
re_rdma_init(rep, CR_RD_RR, hp->h_base, RXHDRSZ);
val = ddi_get32(rep->re_regs_acch, DATA32(rep));
hp->h_status = (uint8_t)val;
hp->h_next = (uint8_t)(val >> 8);
hp->h_len = (uint16_t)(val >> 16);
return (DDI_SUCCESS);
}
void
re_getmode(re_t *rep, re_mode_t *mode)
{
ASSERT(mutex_owned(&rep->re_txlock));
if (!rep->re_legacy_mode) {
re_setpage(rep, REG_PAGE_3);
if (REG_READ(rep, ASID0_REG) == 0x29 &&
REG_READ(rep, ASID1_REG) == 0x80) {
*mode = RE_MODE_RTL8029AS;
return;
}
}
*mode = RE_MODE_LEGACY;
re_debug(rep->re_dip, "found legacy mode device");
}
void
re_getaddr(re_t *rep, uint8_t *macaddr)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_read_prom(rep, macaddr, PROM_ETHER, ETHERADDRL);
re_debug(rep->re_dip,
"station address is %02x:%02x:%02x:%02x:%02x:%02x",
macaddr[0], macaddr[1], macaddr[2], macaddr[3],
macaddr[4], macaddr[5]);
}
void
re_setaddr(re_t *rep, uint8_t *macaddr)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_1);
REG_WRITE(rep, PAR0_REG, macaddr[0]);
REG_WRITE(rep, PAR1_REG, macaddr[1]);
REG_WRITE(rep, PAR2_REG, macaddr[2]);
REG_WRITE(rep, PAR3_REG, macaddr[3]);
REG_WRITE(rep, PAR4_REG, macaddr[4]);
REG_WRITE(rep, PAR5_REG, macaddr[5]);
}
void
re_setmcast(re_t *rep, uint8_t *mcast)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_1);
REG_WRITE(rep, MAR0_REG, mcast[0]);
REG_WRITE(rep, MAR1_REG, mcast[1]);
REG_WRITE(rep, MAR2_REG, mcast[2]);
REG_WRITE(rep, MAR3_REG, mcast[3]);
REG_WRITE(rep, MAR4_REG, mcast[4]);
REG_WRITE(rep, MAR5_REG, mcast[5]);
REG_WRITE(rep, MAR6_REG, mcast[6]);
REG_WRITE(rep, MAR7_REG, mcast[7]);
}
inline void
re_setpage(re_t *rep, uint8_t reg_page)
{
ASSERT(mutex_owned(&rep->re_txlock));
REG_WRITE(rep, CR_REG, reg_page | CR_RD_ABRT |
(rep->re_init ? CR_STA : CR_STP));
}
void
re_read_prom(re_t *rep, uint8_t *buf, uint8_t off, uint8_t len)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_rdma_abort(rep);
re_init_dcr(rep);
re_intr_disable(rep);
/*
* The PROM should always be read using byte-wide
* access regardless of the DCR configuration.
*/
re_rdma_init(rep, CR_RD_RR, off, len);
ddi_rep_get8(rep->re_regs_acch, buf, DATA8(rep),
len, DDI_DEV_NO_AUTOINCR);
}
inline void
re_rdma_clear(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
REG_CLEAR(rep, RBCR0_REG);
REG_CLEAR(rep, RBCR1_REG);
}
void
re_rdma_abort(re_t *rep)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_setpage(rep, REG_PAGE_0);
REG_WRITE(rep, CR_REG, CR_RD_ABRT |
(rep->re_init ? CR_STA : CR_STP));
}
void
re_rdma_init(re_t *rep, uint8_t cmd, uint16_t addr, uint16_t len)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_rdma_abort(rep);
REG_WRITE(rep, RBCR0_REG, len);
REG_WRITE(rep, RBCR1_REG, len >> 8);
REG_WRITE(rep, RSAR0_REG, addr);
REG_WRITE(rep, RSAR1_REG, addr >> 8);
REG_WRITE(rep, CR_REG, cmd |
(rep->re_init ? CR_STA : CR_STP));
}
void
re_rdma_get(re_t *rep, uint8_t *buf, uint16_t addr, uint16_t len)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_rdma_init(rep, CR_RD_RR, addr, len);
ddi_rep_get32(rep->re_regs_acch, (uint32_t *)buf, DATA32(rep),
PAD32(len) / sizeof (uint32_t), DDI_DEV_NO_AUTOINCR);
}
int
re_rdma_put(re_t *rep, uint8_t *buf, uint16_t addr, uint16_t len)
{
ASSERT(mutex_owned(&rep->re_txlock));
re_rdma_init(rep, CR_RD_RW, addr, len);
ddi_rep_put32(rep->re_regs_acch, (uint32_t *)buf, DATA32(rep),
PAD32(len) / sizeof (uint32_t), DDI_DEV_NO_AUTOINCR);
/*
* Remote writes occur in bursts to the FIFO. To
* avoid potential corruption, interrupt status
* is polled for completion before returning.
*/
for (int i = 0; i < ISR_DELAY_CYCLES; ++i) {
if (REG_READ(rep, ISR_REG) & ISR_RDC) {
/* Clear RDMA complete status */
REG_WRITE(rep, ISR_REG, ISR_RDC);
return (DDI_SUCCESS);
}
drv_usecwait(ISR_DELAY);
}
re_error(rep->re_dip, "write timed out!");
return (DDI_FAILURE);
}
void
re_dprintf(dev_info_t *dip, int level, const char *format, ...)
{
va_list ap;
char buf[255];
va_start(ap, format);
(void) vsnprintf(buf, sizeof (buf), format, ap);
cmn_err(level, "!%s%d %s", ddi_driver_name(dip),
ddi_get_instance(dip), buf);
va_end(ap);
}
_______________________________________________
driver-discuss mailing list
[email protected]
http://mail.opensolaris.org/mailman/listinfo/driver-discuss