/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * SunOS MT STREAMS FEPS(SBus)/Cheerio(PCI) 10/100Mb Ethernet Device Driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hme_phy.h" #include "hme_mac.h" #include "hme.h" typedef void (*fptrv_t)(); typedef enum { NO_MSG = 0, AUTOCONFIG_MSG, DISPLAY_MSG, INIT_MSG, UNINIT_MSG, CONFIG_MSG, MII_MSG, FATAL_ERR_MSG, NFATAL_ERR_MSG, XCVR_MSG, NOXCVR_MSG, ERX_MSG, DDI_MSG, } msg_t; msg_t hme_debug_level = NO_MSG; static char *msg_string[] = { "NONE ", "AUTOCONFIG ", "DISPLAY " "INIT ", "UNINIT ", "CONFIG ", "MII ", "FATAL_ERR ", "NFATAL_ERR ", "XCVR ", "NOXCVR ", "ERX ", "DDI ", }; #define SEVERITY_NONE 0 #define SEVERITY_LOW 0 #define SEVERITY_MID 1 #define SEVERITY_HIGH 2 #define SEVERITY_UNKNOWN 99 #define FEPS_URUN_BUG #define HME_CODEVIOL_BUG #define KIOIP KSTAT_INTR_PTR(hmep->hme_intrstats) /* * The following variables are used for checking fixes in Sbus/FEPS 2.0 */ static int hme_urun_fix = 0; /* Bug fixed in Sbus/FEPS 2.0 */ /* * The following variables are used for configuring various features */ static int hme_64bit_enable = 1; /* Use 64-bit sbus transfers */ static int hme_reject_own = 1; /* Reject packets with own SA */ static int hme_ngu_enable = 0; /* Never Give Up mode */ char *hme_priv_prop[] = { "_ipg0", "_ipg1", "_ipg2", "_lance_mode", NULL }; static int hme_lance_mode = 1; /* to enable lance mode */ static int hme_ipg0 = 16; static int hme_ipg1 = 8; static int hme_ipg2 = 4; /* * The following parameters may be configured by the user. If they are not * configured by the user, the values will be based on the capabilities of * the transceiver. * The value "HME_NOTUSR" is ORed with the parameter value to indicate values * which are NOT configured by the user. */ #define HME_NOTUSR 0x0f000000 #define HME_MASK_1BIT 0x1 #define HME_MASK_5BIT 0x1f #define HME_MASK_8BIT 0xff /* * All strings used by hme messaging functions */ static char *no_xcvr_msg = "No transceiver found."; static char *burst_size_msg = "Could not identify the burst size"; static char *unk_rx_ringsz_msg = "Unknown receive RINGSZ"; static char *add_intr_fail_msg = "ddi_add_intr(9F) failed"; static char *mregs_4global_reg_fail_msg = "ddi_regs_map_setup(9F) for global reg failed"; static char *mregs_4etx_reg_fail_msg = "ddi_map_regs for etx reg failed"; static char *mregs_4erx_reg_fail_msg = "ddi_map_regs for erx reg failed"; static char *mregs_4bmac_reg_fail_msg = "ddi_map_regs for bmac reg failed"; static char *mregs_4mif_reg_fail_msg = "ddi_map_regs for mif reg failed"; static char *init_fail_gen_msg = "Failed to initialize hardware/driver"; static char *ddi_nregs_fail_msg = "ddi_dev_nregs failed(9F), returned %d"; static char *bad_num_regs_msg = "Invalid number of registers."; /* FATAL ERR msgs */ /* * Function prototypes. */ /* these two are global so that qfe can use them */ int hmeattach(dev_info_t *, ddi_attach_cmd_t); int hmedetach(dev_info_t *, ddi_detach_cmd_t); int hmequiesce(dev_info_t *); static boolean_t hmeinit_xfer_params(struct hme *); static uint_t hmestop(struct hme *); static void hmestatinit(struct hme *); static int hmeallocthings(struct hme *); static void hmefreethings(struct hme *); static int hmeallocbuf(struct hme *, hmebuf_t *, int); static int hmeallocbufs(struct hme *); static void hmefreebufs(struct hme *); static void hmeget_hm_rev_property(struct hme *); static boolean_t hmestart(struct hme *, mblk_t *); static uint_t hmeintr(caddr_t); static void hmereclaim(struct hme *); static int hmeinit(struct hme *); static void hmeuninit(struct hme *hmep); static mblk_t *hmeread(struct hme *, hmebuf_t *, uint32_t); static void hmesavecntrs(struct hme *); static void hme_fatal_err(struct hme *, uint_t); static void hme_nonfatal_err(struct hme *, uint_t); static int hmeburstsizes(struct hme *); static void send_bit(struct hme *, uint16_t); static uint16_t get_bit_std(uint8_t, struct hme *); static uint16_t hme_bb_mii_read(struct hme *, uint8_t, uint8_t); static void hme_bb_mii_write(struct hme *, uint8_t, uint8_t, uint16_t); static void hme_bb_force_idle(struct hme *); static uint16_t hme_mii_read(void *, uint8_t, uint8_t); static void hme_mii_write(void *, uint8_t, uint8_t, uint16_t); static void hme_setup_mac_address(struct hme *, dev_info_t *); static void hme_mii_notify(void *, link_state_t); static void hme_fault_msg(struct hme *, uint_t, msg_t, char *, ...); static void hme_check_acc_handle(char *, uint_t, struct hme *, ddi_acc_handle_t); /* * Nemo (GLDv3) Functions. */ static int hme_m_stat(void *, uint_t, uint64_t *); static int hme_m_start(void *); static void hme_m_stop(void *); static int hme_m_promisc(void *, boolean_t); static int hme_m_multicst(void *, boolean_t, const uint8_t *); static int hme_m_unicst(void *, const uint8_t *); static mblk_t *hme_m_tx(void *, mblk_t *); static boolean_t hme_m_getcapab(void *, mac_capab_t, void *); static int hme_m_getprop(void *, const char *, mac_prop_id_t, uint_t, void *); static void hme_m_propinfo(void *, const char *, mac_prop_id_t, mac_prop_info_handle_t); static int hme_m_setprop(void *, const char *, mac_prop_id_t, uint_t, const void *); static mii_ops_t hme_mii_ops = { MII_OPS_VERSION, hme_mii_read, hme_mii_write, hme_mii_notify, NULL }; static mac_callbacks_t hme_m_callbacks = { MC_GETCAPAB | MC_SETPROP | MC_GETPROP | MC_PROPINFO, hme_m_stat, hme_m_start, hme_m_stop, hme_m_promisc, hme_m_multicst, hme_m_unicst, hme_m_tx, NULL, NULL, hme_m_getcapab, NULL, NULL, hme_m_setprop, hme_m_getprop, hme_m_propinfo }; DDI_DEFINE_STREAM_OPS(hme_dev_ops, nulldev, nulldev, hmeattach, hmedetach, nodev, NULL, D_MP, NULL, hmequiesce); #define HME_FAULT_MSG1(p, s, t, f) \ hme_fault_msg((p), (s), (t), (f)); #define HME_FAULT_MSG2(p, s, t, f, a) \ hme_fault_msg((p), (s), (t), (f), (a)); #define HME_FAULT_MSG3(p, s, t, f, a, b) \ hme_fault_msg((p), (s), (t), (f), (a), (b)); #define HME_FAULT_MSG4(p, s, t, f, a, b, c) \ hme_fault_msg((p), (s), (t), (f), (a), (b), (c)); #define CHECK_MIFREG() \ hme_check_acc_handle(__FILE__, __LINE__, hmep, hmep->hme_mifregh) #define CHECK_ETXREG() \ hme_check_acc_handle(__FILE__, __LINE__, hmep, hmep->hme_etxregh) #define CHECK_ERXREG() \ hme_check_acc_handle(__FILE__, __LINE__, hmep, hmep->hme_erxregh) #define CHECK_MACREG() \ hme_check_acc_handle(__FILE__, __LINE__, hmep, hmep->hme_bmacregh) #define CHECK_GLOBREG() \ hme_check_acc_handle(__FILE__, __LINE__, hmep, hmep->hme_globregh) /* * Claim the device is ultra-capable of burst in the beginning. Use * the value returned by ddi_dma_burstsizes() to actually set the HME * global configuration register later. * * Sbus/FEPS supports burst sizes of 16, 32 and 64 bytes. Also, it supports * 32-bit and 64-bit Sbus transfers. Hence the dlim_burstsizes field contains * the the burstsizes in both the lo and hi words. */ #define HMELIMADDRLO ((uint64_t)0x00000000) #define HMELIMADDRHI ((uint64_t)0xffffffff) /* * Note that rx and tx data buffers can be arbitrarily aligned, but * that the descriptor rings need to be aligned on 2K boundaries, per * the spec. */ static ddi_dma_attr_t hme_dma_attr = { DMA_ATTR_V0, /* version number. */ (uint64_t)HMELIMADDRLO, /* low address */ (uint64_t)HMELIMADDRHI, /* high address */ (uint64_t)0x00ffffff, /* address counter max */ (uint64_t)HME_HMDALIGN, /* alignment */ (uint_t)0x00700070, /* dlim_burstsizes for 32 and 64 bit xfers */ (uint32_t)0x1, /* minimum transfer size */ (uint64_t)0x7fffffff, /* maximum transfer size */ (uint64_t)0x00ffffff, /* maximum segment size */ 1, /* scatter/gather list length */ 512, /* granularity */ 0 /* attribute flags */ }; static ddi_device_acc_attr_t hme_buf_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC, /* probably could allow merging & caching */ DDI_DEFAULT_ACC, }; static uchar_t pci_latency_timer = 0; /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ "Sun HME 10/100 Mb Ethernet", &hme_dev_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; /* <<<<<<<<<<<<<<<<<<<<<< Register operations >>>>>>>>>>>>>>>>>>>>> */ #define GET_MIFREG(reg) \ ddi_get32(hmep->hme_mifregh, (uint32_t *)&hmep->hme_mifregp->reg) #define PUT_MIFREG(reg, value) \ ddi_put32(hmep->hme_mifregh, (uint32_t *)&hmep->hme_mifregp->reg, value) #define GET_ETXREG(reg) \ ddi_get32(hmep->hme_etxregh, (uint32_t *)&hmep->hme_etxregp->reg) #define PUT_ETXREG(reg, value) \ ddi_put32(hmep->hme_etxregh, (uint32_t *)&hmep->hme_etxregp->reg, value) #define GET_ERXREG(reg) \ ddi_get32(hmep->hme_erxregh, (uint32_t *)&hmep->hme_erxregp->reg) #define PUT_ERXREG(reg, value) \ ddi_put32(hmep->hme_erxregh, (uint32_t *)&hmep->hme_erxregp->reg, value) #define GET_MACREG(reg) \ ddi_get32(hmep->hme_bmacregh, (uint32_t *)&hmep->hme_bmacregp->reg) #define PUT_MACREG(reg, value) \ ddi_put32(hmep->hme_bmacregh, \ (uint32_t *)&hmep->hme_bmacregp->reg, value) #define GET_GLOBREG(reg) \ ddi_get32(hmep->hme_globregh, (uint32_t *)&hmep->hme_globregp->reg) #define PUT_GLOBREG(reg, value) \ ddi_put32(hmep->hme_globregh, \ (uint32_t *)&hmep->hme_globregp->reg, value) #define PUT_TMD(ptr, paddr, len, flags) \ ddi_put32(hmep->hme_tmd_acch, &hmep->hme_tmdp[ptr].tmd_addr, paddr); \ ddi_put32(hmep->hme_tmd_acch, &hmep->hme_tmdp[ptr].tmd_flags, \ len | flags) #define GET_TMD_FLAGS(ptr) \ ddi_get32(hmep->hme_tmd_acch, &hmep->hme_tmdp[ptr].tmd_flags) #define PUT_RMD(ptr, paddr) \ ddi_put32(hmep->hme_rmd_acch, &hmep->hme_rmdp[ptr].rmd_addr, paddr); \ ddi_put32(hmep->hme_rmd_acch, &hmep->hme_rmdp[ptr].rmd_flags, \ (uint32_t)(HMEBUFSIZE << HMERMD_BUFSIZE_SHIFT) | HMERMD_OWN) #define GET_RMD_FLAGS(ptr) \ ddi_get32(hmep->hme_rmd_acch, &hmep->hme_rmdp[ptr].rmd_flags) #define GET_ROM8(offset) \ ddi_get8((hmep->hme_romh), (offset)) /* * Ether_copy is not endian-correct. Define an endian-correct version. */ #define ether_bcopy(a, b) (bcopy(a, b, 6)) /* * Ether-type is specifically big-endian, but data region is unknown endian */ #define get_ether_type(ptr) \ (((((uint8_t *)ptr)[12] << 8) | (((uint8_t *)ptr)[13]))) /* <<<<<<<<<<<<<<<<<<<<<< Configuration Parameters >>>>>>>>>>>>>>>>>>>>> */ #define BMAC_DEFAULT_JAMSIZE (0x04) /* jamsize equals 4 */ #define BMAC_LONG_JAMSIZE (0x10) /* jamsize equals 0x10 */ static int jamsize = BMAC_DEFAULT_JAMSIZE; /* * Calculate the bit in the multicast address filter that selects the given * address. */ static uint32_t hmeladrf_bit(const uint8_t *addr) { uint32_t crc; CRC32(crc, addr, ETHERADDRL, -1U, crc32_table); /* * Just want the 6 most significant bits. */ return (crc >> 26); } /* <<<<<<<<<<<<<<<<<<<<<<<< Bit Bang Operations >>>>>>>>>>>>>>>>>>>>>>>> */ static void send_bit(struct hme *hmep, uint16_t x) { PUT_MIFREG(mif_bbdata, x); PUT_MIFREG(mif_bbclk, HME_BBCLK_LOW); PUT_MIFREG(mif_bbclk, HME_BBCLK_HIGH); } /* * To read the MII register bits according to the IEEE Standard */ static uint16_t get_bit_std(uint8_t phyad, struct hme *hmep) { uint16_t x; PUT_MIFREG(mif_bbclk, HME_BBCLK_LOW); drv_usecwait(1); /* wait for >330 ns for stable data */ if (phyad == HME_INTERNAL_PHYAD) x = (GET_MIFREG(mif_cfg) & HME_MIF_CFGM0) ? 1 : 0; else x = (GET_MIFREG(mif_cfg) & HME_MIF_CFGM1) ? 1 : 0; PUT_MIFREG(mif_bbclk, HME_BBCLK_HIGH); return (x); } #define SEND_BIT(x) send_bit(hmep, x) #define GET_BIT_STD(phyad, x) x = get_bit_std(phyad, hmep) static void hme_bb_mii_write(struct hme *hmep, uint8_t phyad, uint8_t regad, uint16_t data) { int i; PUT_MIFREG(mif_bbopenb, 1); /* Enable the MII driver */ (void) hme_bb_force_idle(hmep); SEND_BIT(0); SEND_BIT(1); /* */ SEND_BIT(0); SEND_BIT(1); /* */ for (i = 4; i >= 0; i--) { /* */ SEND_BIT((phyad >> i) & 1); } for (i = 4; i >= 0; i--) { /* */ SEND_BIT((regad >> i) & 1); } SEND_BIT(1); SEND_BIT(0); /* */ for (i = 0xf; i >= 0; i--) { /* */ SEND_BIT((data >> i) & 1); } PUT_MIFREG(mif_bbopenb, 0); /* Disable the MII driver */ CHECK_MIFREG(); } /* Return 0 if OK, 1 if error (Transceiver does not talk management) */ static uint16_t hme_bb_mii_read(struct hme *hmep, uint8_t phyad, uint8_t regad) { int i; uint32_t x; uint16_t data = 0; PUT_MIFREG(mif_bbopenb, 1); /* Enable the MII driver */ (void) hme_bb_force_idle(hmep); SEND_BIT(0); SEND_BIT(1); /* */ SEND_BIT(1); SEND_BIT(0); /* */ for (i = 4; i >= 0; i--) { /* */ SEND_BIT((phyad >> i) & 1); } for (i = 4; i >= 0; i--) { /* */ SEND_BIT((regad >> i) & 1); } PUT_MIFREG(mif_bbopenb, 0); /* Disable the MII driver */ GET_BIT_STD(phyad, x); GET_BIT_STD(phyad, x); /* */ for (i = 0xf; i >= 0; i--) { /* */ GET_BIT_STD(phyad, x); data += (x << i); } /* * Kludge to get the Transceiver out of hung mode */ GET_BIT_STD(phyad, x); GET_BIT_STD(phyad, x); GET_BIT_STD(phyad, x); CHECK_MIFREG(); return (data); } static void hme_bb_force_idle(struct hme *hmep) { int i; for (i = 0; i < 33; i++) { SEND_BIT(1); } } /* <<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>> */ /* <<<<<<<<<<<<< Frame Register used for MII operations >>>>>>>>>>>>>>>>>>>> */ /* Return 0 if OK, 1 if error (Transceiver does not talk management) */ static uint16_t hme_mii_read(void *arg, uint8_t phyad, uint8_t regad) { struct hme *hmep = arg; uint32_t frame; uint32_t tmp_mif; uint32_t tmp_xif; tmp_mif = GET_MIFREG(mif_cfg); tmp_xif = GET_MACREG(xifc); switch (phyad) { case HME_EXTERNAL_PHYAD: PUT_MIFREG(mif_cfg, tmp_mif | HME_MIF_CFGPS); PUT_MACREG(xifc, tmp_xif | BMAC_XIFC_MIIBUFDIS); break; case HME_INTERNAL_PHYAD: PUT_MIFREG(mif_cfg, tmp_mif & ~(HME_MIF_CFGPS)); PUT_MACREG(xifc, tmp_xif & ~(BMAC_XIFC_MIIBUFDIS)); break; default: return (0xffff); } if (!hmep->hme_frame_enable) { frame = (hme_bb_mii_read(hmep, phyad, regad)); PUT_MACREG(xifc, tmp_xif); PUT_MIFREG(mif_cfg, tmp_mif); return (frame & 0xffff); } PUT_MIFREG(mif_frame, HME_MIF_FRREAD | (phyad << HME_MIF_FRPHYAD_SHIFT) | (regad << HME_MIF_FRREGAD_SHIFT)); /* * HMEDELAY((*framerp & HME_MIF_FRTA0), HMEMAXRSTDELAY); */ HMEDELAY((GET_MIFREG(mif_frame) & HME_MIF_FRTA0), 300); frame = GET_MIFREG(mif_frame); CHECK_MIFREG(); PUT_MACREG(xifc, tmp_xif); PUT_MIFREG(mif_cfg, tmp_mif); if ((frame & HME_MIF_FRTA0) == 0) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, MII_MSG, "MIF Read failure"); return (0xffff); } return ((uint16_t)(frame & HME_MIF_FRDATA)); } static void hme_mii_write(void *arg, uint8_t phyad, uint8_t regad, uint16_t data) { struct hme *hmep = arg; uint32_t frame; uint32_t tmp_mif; uint32_t tmp_xif; tmp_mif = GET_MIFREG(mif_cfg); tmp_xif = GET_MACREG(xifc); switch (phyad) { case HME_EXTERNAL_PHYAD: PUT_MIFREG(mif_cfg, tmp_mif | HME_MIF_CFGPS); PUT_MACREG(xifc, tmp_xif | BMAC_XIFC_MIIBUFDIS); break; case HME_INTERNAL_PHYAD: PUT_MIFREG(mif_cfg, tmp_mif & ~(HME_MIF_CFGPS)); PUT_MACREG(xifc, tmp_xif & ~(BMAC_XIFC_MIIBUFDIS)); break; default: return; } if (!hmep->hme_frame_enable) { hme_bb_mii_write(hmep, phyad, regad, data); PUT_MACREG(xifc, tmp_xif); PUT_MIFREG(mif_cfg, tmp_mif); return; } PUT_MIFREG(mif_frame, HME_MIF_FRWRITE | (phyad << HME_MIF_FRPHYAD_SHIFT) | (regad << HME_MIF_FRREGAD_SHIFT) | data); /* * HMEDELAY((*framerp & HME_MIF_FRTA0), HMEMAXRSTDELAY); */ HMEDELAY((GET_MIFREG(mif_frame) & HME_MIF_FRTA0), 300); frame = GET_MIFREG(mif_frame); PUT_MACREG(xifc, tmp_xif); PUT_MIFREG(mif_cfg, tmp_mif); CHECK_MIFREG(); if ((frame & HME_MIF_FRTA0) == 0) { HME_FAULT_MSG1(hmep, SEVERITY_MID, MII_MSG, "MIF Write failure"); } } static void hme_mii_notify(void *arg, link_state_t link) { struct hme *hmep = arg; if (link == LINK_STATE_UP) { (void) hmeinit(hmep); } mac_link_update(hmep->hme_mh, link); } /* <<<<<<<<<<<<<<<<<<<<<<<<<<< LOADABLE ENTRIES >>>>>>>>>>>>>>>>>>>>>>> */ int _init(void) { int status; mac_init_ops(&hme_dev_ops, "hme"); if ((status = mod_install(&modlinkage)) != 0) { mac_fini_ops(&hme_dev_ops); } return (status); } int _fini(void) { int status; if ((status = mod_remove(&modlinkage)) == 0) { mac_fini_ops(&hme_dev_ops); } return (status); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * ddi_dma_sync() a TMD or RMD descriptor. */ #define HMESYNCRMD(num, who) \ (void) ddi_dma_sync(hmep->hme_rmd_dmah, \ (num * sizeof (struct hme_rmd)), \ sizeof (struct hme_rmd), \ who) #define HMESYNCTMD(num, who) \ (void) ddi_dma_sync(hmep->hme_tmd_dmah, \ (num * sizeof (struct hme_tmd)), \ sizeof (struct hme_tmd), \ who) /* * Ethernet broadcast address definition. */ static struct ether_addr etherbroadcastaddr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* * MIB II broadcast/multicast packets */ #define IS_BROADCAST(pkt) (bcmp(pkt, ðerbroadcastaddr, ETHERADDRL) == 0) #define IS_MULTICAST(pkt) ((pkt[0] & 01) == 1) #define BUMP_InNUcast(hmep, pkt) \ if (IS_MULTICAST(pkt)) { \ if (IS_BROADCAST(pkt)) { \ hmep->hme_brdcstrcv++; \ } else { \ hmep->hme_multircv++; \ } \ } #define BUMP_OutNUcast(hmep, pkt) \ if (IS_MULTICAST(pkt)) { \ if (IS_BROADCAST(pkt)) { \ hmep->hme_brdcstxmt++; \ } else { \ hmep->hme_multixmt++; \ } \ } static int hme_create_prop_from_kw(dev_info_t *dip, char *vpdname, char *vpdstr) { char propstr[80]; int i, needprop = 0; struct ether_addr local_mac; if (strcmp(vpdname, "NA") == 0) { (void) strcpy(propstr, "local-mac-address"); needprop = 1; } else if (strcmp(vpdname, "Z0") == 0) { (void) strcpy(propstr, "model"); needprop = 1; } else if (strcmp(vpdname, "Z1") == 0) { (void) strcpy(propstr, "board-model"); needprop = 1; } if (needprop == 1) { if (strcmp(propstr, "local-mac-address") == 0) { for (i = 0; i < ETHERADDRL; i++) local_mac.ether_addr_octet[i] = (uchar_t)vpdstr[i]; if (ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, propstr, (char *)local_mac.ether_addr_octet, ETHERADDRL) != DDI_SUCCESS) { return (DDI_FAILURE); } } else { if (ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, propstr, vpdstr, strlen(vpdstr)+1) != DDI_SUCCESS) { return (DDI_FAILURE); } } } return (0); } /* * Get properties from old VPD * for PCI cards */ static int hme_get_oldvpd_props(dev_info_t *dip, int vpd_base) { struct hme *hmep; int vpd_start, vpd_len, kw_start, kw_len, kw_ptr; char kw_namestr[3]; char kw_fieldstr[256]; int i; hmep = ddi_get_driver_private(dip); vpd_start = vpd_base; if ((GET_ROM8(&hmep->hme_romp[vpd_start]) & 0xff) != 0x90) { return (1); /* error */ } else { vpd_len = 9; } /* Get local-mac-address */ kw_start = vpd_start + 3; /* Location of 1st keyword */ kw_ptr = kw_start; while ((kw_ptr - kw_start) < vpd_len) { /* Get all keywords */ kw_namestr[0] = GET_ROM8(&hmep->hme_romp[kw_ptr]); kw_namestr[1] = GET_ROM8(&hmep->hme_romp[kw_ptr+1]); kw_namestr[2] = '\0'; kw_len = (int)(GET_ROM8(&hmep->hme_romp[kw_ptr+2]) & 0xff); for (i = 0, kw_ptr += 3; i < kw_len; i++) kw_fieldstr[i] = GET_ROM8(&hmep->hme_romp[kw_ptr+i]); kw_fieldstr[i] = '\0'; if (hme_create_prop_from_kw(dip, kw_namestr, kw_fieldstr)) { return (DDI_FAILURE); } kw_ptr += kw_len; } /* next keyword */ if (ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, "model", "SUNW,cheerio", strlen("SUNW,cheerio")+1) != DDI_SUCCESS) { return (DDI_FAILURE); } return (0); } /* * Get properties from new VPD * for CompactPCI cards */ static int hme_get_newvpd_props(dev_info_t *dip, int vpd_base) { struct hme *hmep; int vpd_start, vpd_len, kw_start, kw_len, kw_ptr; char kw_namestr[3]; char kw_fieldstr[256]; int maxvpdsize, i; hmep = ddi_get_driver_private(dip); maxvpdsize = 1024; /* Real size not known until after it is read */ vpd_start = (int)((GET_ROM8(&(hmep->hme_romp[vpd_base+1])) & 0xff) | ((GET_ROM8(&hmep->hme_romp[vpd_base+2]) & 0xff) << 8)) +3; vpd_start = vpd_base + vpd_start; while (vpd_start < (vpd_base + maxvpdsize)) { /* Get all VPDs */ if ((GET_ROM8(&hmep->hme_romp[vpd_start]) & 0xff) != 0x90) { break; /* no VPD found */ } else { vpd_len = (int)((GET_ROM8(&hmep->hme_romp[vpd_start + 1]) & 0xff) | (GET_ROM8(&hmep->hme_romp[vpd_start + 2]) & 0xff) << 8); } /* Get all keywords in this VPD */ kw_start = vpd_start + 3; /* Location of 1st keyword */ kw_ptr = kw_start; while ((kw_ptr - kw_start) < vpd_len) { /* Get all keywords */ kw_namestr[0] = GET_ROM8(&hmep->hme_romp[kw_ptr]); kw_namestr[1] = GET_ROM8(&hmep->hme_romp[kw_ptr+1]); kw_namestr[2] = '\0'; kw_len = (int)(GET_ROM8(&hmep->hme_romp[kw_ptr+2]) & 0xff); for (i = 0, kw_ptr += 3; i < kw_len; i++) kw_fieldstr[i] = GET_ROM8(&hmep->hme_romp[kw_ptr+i]); kw_fieldstr[i] = '\0'; if (hme_create_prop_from_kw(dip, kw_namestr, kw_fieldstr)) { return (DDI_FAILURE); } kw_ptr += kw_len; } /* next keyword */ vpd_start += (vpd_len + 3); } /* next VPD */ return (0); } /* * Get properties from VPD */ static int hme_get_vpd_props(dev_info_t *dip) { struct hme *hmep; int v0, v1, vpd_base; int i, epromsrchlimit; hmep = ddi_get_driver_private(dip); v0 = (int)(GET_ROM8(&(hmep->hme_romp[0]))); v1 = (int)(GET_ROM8(&(hmep->hme_romp[1]))); v0 = ((v0 & 0xff) << 8 | v1); if ((v0 & 0xffff) != 0x55aa) { cmn_err(CE_NOTE, " Valid pci prom not found \n"); return (1); } epromsrchlimit = 4096; for (i = 2; i < epromsrchlimit; i++) { /* "PCIR" */ if (((GET_ROM8(&(hmep->hme_romp[i])) & 0xff) == 'P') && ((GET_ROM8(&(hmep->hme_romp[i+1])) & 0xff) == 'C') && ((GET_ROM8(&(hmep->hme_romp[i+2])) & 0xff) == 'I') && ((GET_ROM8(&(hmep->hme_romp[i+3])) & 0xff) == 'R')) { vpd_base = (int)((GET_ROM8(&(hmep->hme_romp[i+8])) & 0xff) | (GET_ROM8(&(hmep->hme_romp[i+9])) & 0xff) << 8); break; /* VPD pointer found */ } } /* No VPD found */ if (vpd_base == 0) { cmn_err(CE_NOTE, " Vital Product Data pointer not found \n"); return (1); } v0 = (int)(GET_ROM8(&(hmep->hme_romp[vpd_base]))); if (v0 == 0x82) { if (hme_get_newvpd_props(dip, vpd_base)) return (1); return (0); } else if (v0 == 0x90) { /* If we are are SUNW,qfe card, look for the Nth "NA" descr */ if ((GET_ROM8(&hmep->hme_romp[vpd_base + 12]) != 0x79) && GET_ROM8(&hmep->hme_romp[vpd_base + 4 * 12]) == 0x79) { vpd_base += hmep->hme_devno * 12; } if (hme_get_oldvpd_props(dip, vpd_base)) return (1); return (0); } else return (1); /* unknown start byte in VPD */ } /* * For x86, the BIOS doesn't map the PCI Rom register for the qfe * cards, so we have to extract it from the ebus bridge that is * function zero of the same device. This is a bit of an ugly hack. * (The ebus bridge leaves the entire ROM mapped at base address * register 0x10.) */ typedef struct { struct hme *hmep; dev_info_t *parent; uint8_t bus, dev; ddi_acc_handle_t acch; caddr_t romp; } ebus_rom_t; static int hme_mapebusrom(dev_info_t *dip, void *arg) { int *regs; unsigned nregs; int reg; ebus_rom_t *rom = arg; struct hme *hmep = rom->hmep; /* * We only want to look at our peers. Skip our parent. */ if (dip == rom->parent) { return (DDI_WALK_PRUNESIB); } if (ddi_get_parent(dip) != rom->parent) return (DDI_WALK_CONTINUE); if ((ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 0, "reg", ®s, &nregs)) != DDI_PROP_SUCCESS) { return (DDI_WALK_PRUNECHILD); } if (nregs < 1) { ddi_prop_free(regs); return (DDI_WALK_PRUNECHILD); } reg = regs[0]; ddi_prop_free(regs); /* * Look for function 0 on our bus and device. If the device doesn't * match, it might be an alternate peer, in which case we don't want * to examine any of its children. */ if ((PCI_REG_BUS_G(reg) != rom->bus) || (PCI_REG_DEV_G(reg) != rom->dev) || (PCI_REG_FUNC_G(reg) != 0)) { return (DDI_WALK_PRUNECHILD); } (void) ddi_regs_map_setup(dip, 1, &rom->romp, 0, 0, &hmep->hme_dev_attr, &rom->acch); /* * If we can't map the registers, the caller will notice that * the acch is NULL. */ return (DDI_WALK_TERMINATE); } static int hmeget_promebus(dev_info_t *dip) { ebus_rom_t rom; int *regs; unsigned nregs; struct hme *hmep; hmep = ddi_get_driver_private(dip); bzero(&rom, sizeof (rom)); /* * For x86, the BIOS doesn't map the PCI Rom register for the qfe * cards, so we have to extract it from the eBus bridge that is * function zero. This is a bit of an ugly hack. */ if ((ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 0, "reg", ®s, &nregs)) != DDI_PROP_SUCCESS) { return (DDI_FAILURE); } if (nregs < 5) { ddi_prop_free(regs); return (DDI_FAILURE); } rom.hmep = hmep; rom.bus = PCI_REG_BUS_G(regs[0]); rom.dev = PCI_REG_DEV_G(regs[0]); hmep->hme_devno = rom.dev; rom.parent = ddi_get_parent(dip); /* * The implementation of ddi_walk_devs says that we must not * be called during autoconfiguration. However, it turns out * that it is safe to call this during our attach routine, * because we are not a nexus device. * * Previously we rooted our search at our immediate parent, * but this triggered an assertion panic in debug kernels. */ ddi_walk_devs(ddi_root_node(), hme_mapebusrom, &rom); if (rom.acch) { hmep->hme_romh = rom.acch; hmep->hme_romp = (unsigned char *)rom.romp; return (DDI_SUCCESS); } return (DDI_FAILURE); } static int hmeget_promprops(dev_info_t *dip) { struct hme *hmep; int rom_bar; ddi_acc_handle_t cfg_handle; struct { uint16_t vendorid; uint16_t devid; uint16_t command; uint16_t status; uint32_t junk1; uint8_t cache_line; uint8_t latency; uint8_t header; uint8_t bist; uint32_t base; uint32_t base14; uint32_t base18; uint32_t base1c; uint32_t base20; uint32_t base24; uint32_t base28; uint32_t base2c; uint32_t base30; } *cfg_ptr; hmep = ddi_get_driver_private(dip); /* * map configuration space */ if (ddi_regs_map_setup(hmep->dip, 0, (caddr_t *)&cfg_ptr, 0, 0, &hmep->hme_dev_attr, &cfg_handle)) { return (DDI_FAILURE); } /* * Enable bus-master and memory accesses */ ddi_put16(cfg_handle, &cfg_ptr->command, PCI_COMM_SERR_ENABLE | PCI_COMM_PARITY_DETECT | PCI_COMM_MAE | PCI_COMM_ME); /* * Enable rom accesses */ rom_bar = ddi_get32(cfg_handle, &cfg_ptr->base30); ddi_put32(cfg_handle, &cfg_ptr->base30, rom_bar | 1); if ((ddi_regs_map_setup(dip, 2, (caddr_t *)&(hmep->hme_romp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_romh) != DDI_SUCCESS) && (hmeget_promebus(dip) != DDI_SUCCESS)) { if (cfg_ptr) ddi_regs_map_free(&cfg_handle); return (DDI_FAILURE); } else { if (hme_get_vpd_props(dip)) return (DDI_FAILURE); } if (hmep->hme_romp) ddi_regs_map_free(&hmep->hme_romh); if (cfg_ptr) ddi_regs_map_free(&cfg_handle); return (DDI_SUCCESS); } static void hmeget_hm_rev_property(struct hme *hmep) { int hm_rev; hm_rev = hmep->asic_rev; switch (hm_rev) { case HME_2P1_REVID: case HME_2P1_REVID_OBP: HME_FAULT_MSG2(hmep, SEVERITY_NONE, DISPLAY_MSG, "SBus 2.1 Found (Rev Id = %x)", hm_rev); hmep->hme_frame_enable = 1; break; case HME_2P0_REVID: HME_FAULT_MSG2(hmep, SEVERITY_NONE, DISPLAY_MSG, "SBus 2.0 Found (Rev Id = %x)", hm_rev); break; case HME_1C0_REVID: HME_FAULT_MSG2(hmep, SEVERITY_NONE, DISPLAY_MSG, "PCI IO 1.0 Found (Rev Id = %x)", hm_rev); break; default: HME_FAULT_MSG3(hmep, SEVERITY_NONE, DISPLAY_MSG, "%s (Rev Id = %x) Found", (hm_rev == HME_2C0_REVID) ? "PCI IO 2.0" : "Sbus", hm_rev); hmep->hme_frame_enable = 1; hmep->hme_lance_mode_enable = 1; hmep->hme_rxcv_enable = 1; break; } } /* * Interface exists: make available by filling in network interface * record. System will initialize the interface when it is ready * to accept packets. */ int hmeattach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct hme *hmep; mac_register_t *macp = NULL; int regno; int hm_rev = 0; int prop_len = sizeof (int); ddi_acc_handle_t cfg_handle; struct { uint16_t vendorid; uint16_t devid; uint16_t command; uint16_t status; uint8_t revid; uint8_t j1; uint16_t j2; } *cfg_ptr; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: if ((hmep = ddi_get_driver_private(dip)) == NULL) return (DDI_FAILURE); hmep->hme_flags &= ~HMESUSPENDED; mii_resume(hmep->hme_mii); if (hmep->hme_started) (void) hmeinit(hmep); return (DDI_SUCCESS); default: return (DDI_FAILURE); } /* * Allocate soft device data structure */ hmep = kmem_zalloc(sizeof (*hmep), KM_SLEEP); /* * Might as well set up elements of data structure */ hmep->dip = dip; hmep->instance = ddi_get_instance(dip); hmep->pagesize = ddi_ptob(dip, (ulong_t)1); /* IOMMU PSize */ /* * Might as well setup the driver private * structure as part of the dip. */ ddi_set_driver_private(dip, hmep); /* * Reject this device if it's in a slave-only slot. */ if (ddi_slaveonly(dip) == DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, "Dev not used - dev in slave only slot"); goto error_state; } /* * Map in the device registers. * * Reg # 0 is the Global register set * Reg # 1 is the ETX register set * Reg # 2 is the ERX register set * Reg # 3 is the BigMAC register set. * Reg # 4 is the MIF register set */ if (ddi_dev_nregs(dip, ®no) != (DDI_SUCCESS)) { HME_FAULT_MSG2(hmep, SEVERITY_HIGH, INIT_MSG, ddi_nregs_fail_msg, regno); goto error_state; } switch (regno) { case 5: hmep->hme_cheerio_mode = 0; break; case 2: case 3: /* for hot swap/plug, there will be 3 entries in "reg" prop */ hmep->hme_cheerio_mode = 1; break; default: HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, bad_num_regs_msg); goto error_state; } /* Initialize device attributes structure */ hmep->hme_dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; if (hmep->hme_cheerio_mode) hmep->hme_dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; else hmep->hme_dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_BE_ACC; hmep->hme_dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; if (hmep->hme_cheerio_mode) { uint8_t oldLT; uint8_t newLT = 0; dev_info_t *pdip; const char *pdrvname; /* * Map the PCI config space */ if (pci_config_setup(dip, &hmep->pci_config_handle) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, "pci_config_setup() failed.."); goto error_state; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&(hmep->hme_globregp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_globregh)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, mregs_4global_reg_fail_msg); goto error_unmap; } hmep->hme_etxregh = hmep->hme_erxregh = hmep->hme_bmacregh = hmep->hme_mifregh = hmep->hme_globregh; hmep->hme_etxregp = (void *)(((caddr_t)hmep->hme_globregp) + 0x2000); hmep->hme_erxregp = (void *)(((caddr_t)hmep->hme_globregp) + 0x4000); hmep->hme_bmacregp = (void *)(((caddr_t)hmep->hme_globregp) + 0x6000); hmep->hme_mifregp = (void *)(((caddr_t)hmep->hme_globregp) + 0x7000); /* * Get parent pci bridge info. */ pdip = ddi_get_parent(dip); pdrvname = ddi_driver_name(pdip); oldLT = pci_config_get8(hmep->pci_config_handle, PCI_CONF_LATENCY_TIMER); /* * Honor value set in /etc/system * "set hme:pci_latency_timer=0xYY" */ if (pci_latency_timer) newLT = pci_latency_timer; /* * Modify LT for simba */ else if (strcmp("simba", pdrvname) == 0) newLT = 0xf0; /* * Ensure minimum cheerio latency timer of 0x50 * Usually OBP or pci bridge should set this value * based on cheerio * min_grant * 8(33MHz) = 0x50 = 0xa * 0x8 * Some system set cheerio LT at 0x40 */ else if (oldLT < 0x40) newLT = 0x50; /* * Now program cheerio's pci latency timer with newLT */ if (newLT) pci_config_put8(hmep->pci_config_handle, PCI_CONF_LATENCY_TIMER, (uchar_t)newLT); } else { /* Map register sets */ if (ddi_regs_map_setup(dip, 0, (caddr_t *)&(hmep->hme_globregp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_globregh)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, mregs_4global_reg_fail_msg); goto error_state; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&(hmep->hme_etxregp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_etxregh)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, mregs_4etx_reg_fail_msg); goto error_unmap; } if (ddi_regs_map_setup(dip, 2, (caddr_t *)&(hmep->hme_erxregp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_erxregh)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, mregs_4erx_reg_fail_msg); goto error_unmap; } if (ddi_regs_map_setup(dip, 3, (caddr_t *)&(hmep->hme_bmacregp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_bmacregh)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, mregs_4bmac_reg_fail_msg); goto error_unmap; } if (ddi_regs_map_setup(dip, 4, (caddr_t *)&(hmep->hme_mifregp), 0, 0, &hmep->hme_dev_attr, &hmep->hme_mifregh)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, mregs_4mif_reg_fail_msg); goto error_unmap; } } /* Endif cheerio_mode */ /* * Based on the hm-rev, set some capabilities * Set up default capabilities for HM 2.0 */ hmep->hme_frame_enable = 0; hmep->hme_lance_mode_enable = 0; hmep->hme_rxcv_enable = 0; /* NEW routine to get the properties */ if (ddi_getlongprop_buf(DDI_DEV_T_ANY, hmep->dip, 0, "hm-rev", (caddr_t)&hm_rev, &prop_len) == DDI_PROP_SUCCESS) { hmep->asic_rev = hm_rev; hmeget_hm_rev_property(hmep); } else { /* * hm_rev property not found so, this is * case of hot insertion of card without interpreting fcode. * Get it from revid in config space after mapping it. */ if (ddi_regs_map_setup(hmep->dip, 0, (caddr_t *)&cfg_ptr, 0, 0, &hmep->hme_dev_attr, &cfg_handle)) { return (DDI_FAILURE); } /* * Since this is cheerio-based PCI card, we write 0xC in the * top 4 bits(4-7) of hm-rev and retain the bottom(0-3) bits * for Cheerio version(1.0 or 2.0 = 0xC0 or 0xC1) */ hm_rev = ddi_get8(cfg_handle, &cfg_ptr->revid); hm_rev = HME_1C0_REVID | (hm_rev & HME_REV_VERS_MASK); hmep->asic_rev = hm_rev; if (ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, "hm-rev", (caddr_t)&hm_rev, sizeof (hm_rev)) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, AUTOCONFIG_MSG, "ddi_prop_create error for hm_rev"); } ddi_regs_map_free(&cfg_handle); hmeget_hm_rev_property(hmep); /* get info via VPD */ if (hmeget_promprops(dip) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, AUTOCONFIG_MSG, "no promprops"); } } if (ddi_intr_hilevel(dip, 0)) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, NFATAL_ERR_MSG, " high-level interrupts are not supported"); goto error_unmap; } /* * Get intr. block cookie so that mutex locks can be initialized. */ if (ddi_get_iblock_cookie(dip, 0, &hmep->hme_cookie) != DDI_SUCCESS) goto error_unmap; /* * Initialize mutex's for this device. */ mutex_init(&hmep->hme_xmitlock, NULL, MUTEX_DRIVER, hmep->hme_cookie); mutex_init(&hmep->hme_intrlock, NULL, MUTEX_DRIVER, hmep->hme_cookie); /* * Quiesce the hardware. */ (void) hmestop(hmep); /* * Add interrupt to system */ if (ddi_add_intr(dip, 0, (ddi_iblock_cookie_t *)NULL, (ddi_idevice_cookie_t *)NULL, hmeintr, (caddr_t)hmep)) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, CONFIG_MSG, add_intr_fail_msg); goto error_mutex; } /* * Set up the ethernet mac address. */ hme_setup_mac_address(hmep, dip); if (!hmeinit_xfer_params(hmep)) goto error_intr; if (hmeburstsizes(hmep) == DDI_FAILURE) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, burst_size_msg); goto error_intr; } if (hmeallocthings(hmep) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, CONFIG_MSG, "resource allocation failed"); goto error_intr; } if (hmeallocbufs(hmep) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, CONFIG_MSG, "buffer allocation failed"); goto error_intr; } hmestatinit(hmep); /* our external (preferred) PHY is at address 0 */ (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, "first-phy", 0); hmep->hme_mii = mii_alloc(hmep, dip, &hme_mii_ops); if (hmep->hme_mii == NULL) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, CONFIG_MSG, "mii_alloc failed"); goto error_intr; } /* force a probe for the PHY */ mii_probe(hmep->hme_mii); if ((macp = mac_alloc(MAC_VERSION)) == NULL) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, CONFIG_MSG, "mac_alloc failed"); goto error_intr; } macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER; macp->m_driver = hmep; macp->m_dip = dip; macp->m_src_addr = hmep->hme_ouraddr.ether_addr_octet; macp->m_callbacks = &hme_m_callbacks; macp->m_min_sdu = 0; macp->m_max_sdu = ETHERMTU; macp->m_margin = VLAN_TAGSZ; macp->m_priv_props = hme_priv_prop; if (mac_register(macp, &hmep->hme_mh) != 0) { mac_free(macp); goto error_intr; } mac_free(macp); ddi_report_dev(dip); return (DDI_SUCCESS); /* * Failure Exit */ error_intr: if (hmep->hme_cookie) ddi_remove_intr(dip, 0, (ddi_iblock_cookie_t)0); if (hmep->hme_mii) mii_free(hmep->hme_mii); error_mutex: mutex_destroy(&hmep->hme_xmitlock); mutex_destroy(&hmep->hme_intrlock); error_unmap: if (hmep->hme_globregh) ddi_regs_map_free(&hmep->hme_globregh); if (hmep->hme_cheerio_mode == 0) { if (hmep->hme_etxregh) ddi_regs_map_free(&hmep->hme_etxregh); if (hmep->hme_erxregh) ddi_regs_map_free(&hmep->hme_erxregh); if (hmep->hme_bmacregh) ddi_regs_map_free(&hmep->hme_bmacregh); if (hmep->hme_mifregh) ddi_regs_map_free(&hmep->hme_mifregh); } else { if (hmep->pci_config_handle) (void) pci_config_teardown(&hmep->pci_config_handle); hmep->hme_etxregh = hmep->hme_erxregh = hmep->hme_bmacregh = hmep->hme_mifregh = hmep->hme_globregh = NULL; } error_state: hmefreethings(hmep); hmefreebufs(hmep); if (hmep) { kmem_free((caddr_t)hmep, sizeof (*hmep)); ddi_set_driver_private(dip, NULL); } return (DDI_FAILURE); } int hmedetach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct hme *hmep; if ((hmep = ddi_get_driver_private(dip)) == NULL) return (DDI_FAILURE); switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: mii_suspend(hmep->hme_mii); hmep->hme_flags |= HMESUSPENDED; hmeuninit(hmep); return (DDI_SUCCESS); default: return (DDI_FAILURE); } if (mac_unregister(hmep->hme_mh) != 0) { return (DDI_FAILURE); } /* * Make driver quiescent, we don't want to prevent the * detach on failure. Note that this should be redundant, * since mac_stop should already have called hmeuninit(). */ if (!(hmep->hme_flags & HMESUSPENDED)) { (void) hmestop(hmep); } if (hmep->hme_mii) mii_free(hmep->hme_mii); /* * Remove instance of the intr */ ddi_remove_intr(dip, 0, (ddi_iblock_cookie_t)0); /* * Unregister kstats. */ if (hmep->hme_ksp != NULL) kstat_delete(hmep->hme_ksp); if (hmep->hme_intrstats != NULL) kstat_delete(hmep->hme_intrstats); hmep->hme_ksp = NULL; hmep->hme_intrstats = NULL; /* * Destroy all mutexes and data structures allocated during * attach time. * * Note: at this time we should be the only thread accessing * the structures for this instance. */ if (hmep->hme_globregh) ddi_regs_map_free(&hmep->hme_globregh); if (hmep->hme_cheerio_mode == 0) { if (hmep->hme_etxregh) ddi_regs_map_free(&hmep->hme_etxregh); if (hmep->hme_erxregh) ddi_regs_map_free(&hmep->hme_erxregh); if (hmep->hme_bmacregh) ddi_regs_map_free(&hmep->hme_bmacregh); if (hmep->hme_mifregh) ddi_regs_map_free(&hmep->hme_mifregh); } else { if (hmep->pci_config_handle) (void) pci_config_teardown(&hmep->pci_config_handle); hmep->hme_etxregh = hmep->hme_erxregh = hmep->hme_bmacregh = hmep->hme_mifregh = hmep->hme_globregh = NULL; } mutex_destroy(&hmep->hme_xmitlock); mutex_destroy(&hmep->hme_intrlock); hmefreethings(hmep); hmefreebufs(hmep); ddi_set_driver_private(dip, NULL); kmem_free(hmep, sizeof (struct hme)); return (DDI_SUCCESS); } int hmequiesce(dev_info_t *dip) { struct hme *hmep; if ((hmep = ddi_get_driver_private(dip)) == NULL) return (DDI_FAILURE); (void) hmestop(hmep); return (DDI_SUCCESS); } static boolean_t hmeinit_xfer_params(struct hme *hmep) { int hme_ipg1_conf, hme_ipg2_conf; int hme_ipg0_conf, hme_lance_mode_conf; int prop_len = sizeof (int); dev_info_t *dip; dip = hmep->dip; /* * Set up the start-up values for user-configurable parameters * Get the values from the global variables first. * Use the MASK to limit the value to allowed maximum. */ hmep->hme_ipg1 = hme_ipg1 & HME_MASK_8BIT; hmep->hme_ipg2 = hme_ipg2 & HME_MASK_8BIT; hmep->hme_ipg0 = hme_ipg0 & HME_MASK_5BIT; /* * Get the parameter values configured in .conf file. */ if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, 0, "ipg1", (caddr_t)&hme_ipg1_conf, &prop_len) == DDI_PROP_SUCCESS) { hmep->hme_ipg1 = hme_ipg1_conf & HME_MASK_8BIT; } if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, 0, "ipg2", (caddr_t)&hme_ipg2_conf, &prop_len) == DDI_PROP_SUCCESS) { hmep->hme_ipg2 = hme_ipg2_conf & HME_MASK_8BIT; } if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, 0, "ipg0", (caddr_t)&hme_ipg0_conf, &prop_len) == DDI_PROP_SUCCESS) { hmep->hme_ipg0 = hme_ipg0_conf & HME_MASK_5BIT; } if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, 0, "lance_mode", (caddr_t)&hme_lance_mode_conf, &prop_len) == DDI_PROP_SUCCESS) { hmep->hme_lance_mode = hme_lance_mode_conf & HME_MASK_1BIT; } return (B_TRUE); } /* * Return 0 upon success, 1 on failure. */ static uint_t hmestop(struct hme *hmep) { /* * Disable the Tx dma engine. */ PUT_ETXREG(config, (GET_ETXREG(config) & ~HMET_CONFIG_TXDMA_EN)); HMEDELAY(((GET_ETXREG(state_mach) & 0x1f) == 0x1), HMEMAXRSTDELAY); /* * Disable the Rx dma engine. */ PUT_ERXREG(config, (GET_ERXREG(config) & ~HMER_CONFIG_RXDMA_EN)); HMEDELAY(((GET_ERXREG(state_mach) & 0x3f) == 0), HMEMAXRSTDELAY); /* * By this time all things should be quiet, so hit the * chip with a reset. */ PUT_GLOBREG(reset, HMEG_RESET_GLOBAL); HMEDELAY((GET_GLOBREG(reset) == 0), HMEMAXRSTDELAY); if (GET_GLOBREG(reset)) { return (1); } CHECK_GLOBREG(); return (0); } static int hmestat_kstat_update(kstat_t *ksp, int rw) { struct hme *hmep; struct hmekstat *hkp; hmep = (struct hme *)ksp->ks_private; hkp = (struct hmekstat *)ksp->ks_data; if (rw != KSTAT_READ) return (EACCES); /* * Update all the stats by reading all the counter registers. * Counter register stats are not updated till they overflow * and interrupt. */ mutex_enter(&hmep->hme_xmitlock); if (hmep->hme_flags & HMERUNNING) { hmereclaim(hmep); hmesavecntrs(hmep); } mutex_exit(&hmep->hme_xmitlock); hkp->hk_cvc.value.ul = hmep->hme_cvc; hkp->hk_lenerr.value.ul = hmep->hme_lenerr; hkp->hk_buff.value.ul = hmep->hme_buff; hkp->hk_missed.value.ul = hmep->hme_missed; hkp->hk_allocbfail.value.ul = hmep->hme_allocbfail; hkp->hk_babl.value.ul = hmep->hme_babl; hkp->hk_tmder.value.ul = hmep->hme_tmder; hkp->hk_txlaterr.value.ul = hmep->hme_txlaterr; hkp->hk_rxlaterr.value.ul = hmep->hme_rxlaterr; hkp->hk_slvparerr.value.ul = hmep->hme_slvparerr; hkp->hk_txparerr.value.ul = hmep->hme_txparerr; hkp->hk_rxparerr.value.ul = hmep->hme_rxparerr; hkp->hk_slverrack.value.ul = hmep->hme_slverrack; hkp->hk_txerrack.value.ul = hmep->hme_txerrack; hkp->hk_rxerrack.value.ul = hmep->hme_rxerrack; hkp->hk_txtagerr.value.ul = hmep->hme_txtagerr; hkp->hk_rxtagerr.value.ul = hmep->hme_rxtagerr; hkp->hk_eoperr.value.ul = hmep->hme_eoperr; hkp->hk_notmds.value.ul = hmep->hme_notmds; hkp->hk_notbufs.value.ul = hmep->hme_notbufs; hkp->hk_norbufs.value.ul = hmep->hme_norbufs; /* * Debug kstats */ hkp->hk_inits.value.ul = hmep->inits; hkp->hk_phyfail.value.ul = hmep->phyfail; /* * xcvr kstats */ hkp->hk_asic_rev.value.ul = hmep->asic_rev; return (0); } static void hmestatinit(struct hme *hmep) { struct kstat *ksp; struct hmekstat *hkp; const char *driver; int instance; char buf[16]; instance = hmep->instance; driver = ddi_driver_name(hmep->dip); if ((ksp = kstat_create(driver, instance, "driver_info", "net", KSTAT_TYPE_NAMED, sizeof (struct hmekstat) / sizeof (kstat_named_t), 0)) == NULL) { HME_FAULT_MSG1(hmep, SEVERITY_UNKNOWN, INIT_MSG, "kstat_create failed"); return; } (void) snprintf(buf, sizeof (buf), "%sc%d", driver, instance); hmep->hme_intrstats = kstat_create(driver, instance, buf, "controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT); if (hmep->hme_intrstats) kstat_install(hmep->hme_intrstats); hmep->hme_ksp = ksp; hkp = (struct hmekstat *)ksp->ks_data; kstat_named_init(&hkp->hk_cvc, "code_violations", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_lenerr, "len_errors", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_buff, "buff", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_missed, "missed", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_nocanput, "nocanput", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_allocbfail, "allocbfail", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_babl, "babble", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_tmder, "tmd_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_txlaterr, "tx_late_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_rxlaterr, "rx_late_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_slvparerr, "slv_parity_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_txparerr, "tx_parity_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_rxparerr, "rx_parity_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_slverrack, "slv_error_ack", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_txerrack, "tx_error_ack", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_rxerrack, "rx_error_ack", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_txtagerr, "tx_tag_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_rxtagerr, "rx_tag_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_eoperr, "eop_error", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_notmds, "no_tmds", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_notbufs, "no_tbufs", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_norbufs, "no_rbufs", KSTAT_DATA_ULONG); /* * Debugging kstats */ kstat_named_init(&hkp->hk_inits, "inits", KSTAT_DATA_ULONG); kstat_named_init(&hkp->hk_phyfail, "phy_failures", KSTAT_DATA_ULONG); /* * xcvr kstats */ kstat_named_init(&hkp->hk_asic_rev, "asic_rev", KSTAT_DATA_ULONG); ksp->ks_update = hmestat_kstat_update; ksp->ks_private = (void *) hmep; kstat_install(ksp); } int hme_m_getprop(void *arg, const char *name, mac_prop_id_t num, uint_t sz, void *val) { struct hme *hmep = arg; int value; int rv; rv = mii_m_getprop(hmep->hme_mii, name, num, sz, val); if (rv != ENOTSUP) return (rv); switch (num) { case MAC_PROP_PRIVATE: break; default: return (ENOTSUP); } if (strcmp(name, "_ipg0") == 0) { value = hmep->hme_ipg0; } else if (strcmp(name, "_ipg1") == 0) { value = hmep->hme_ipg1; } else if (strcmp(name, "_ipg2") == 0) { value = hmep->hme_ipg2; } else if (strcmp(name, "_lance_mode") == 0) { value = hmep->hme_lance_mode; } else { return (ENOTSUP); } (void) snprintf(val, sz, "%d", value); return (0); } static void hme_m_propinfo(void *arg, const char *name, mac_prop_id_t num, mac_prop_info_handle_t mph) { struct hme *hmep = arg; mii_m_propinfo(hmep->hme_mii, name, num, mph); switch (num) { case MAC_PROP_PRIVATE: { char valstr[64]; int default_val; if (strcmp(name, "_ipg0") == 0) { default_val = hme_ipg0; } else if (strcmp(name, "_ipg1") == 0) { default_val = hme_ipg1; } else if (strcmp(name, "_ipg2") == 0) { default_val = hme_ipg2; } if (strcmp(name, "_lance_mode") == 0) { default_val = hme_lance_mode; } else { return; } (void) snprintf(valstr, sizeof (valstr), "%d", default_val); mac_prop_info_set_default_str(mph, valstr); break; } } } int hme_m_setprop(void *arg, const char *name, mac_prop_id_t num, uint_t sz, const void *val) { struct hme *hmep = arg; int rv; long lval; boolean_t init = B_FALSE; rv = mii_m_setprop(hmep->hme_mii, name, num, sz, val); if (rv != ENOTSUP) return (rv); rv = 0; switch (num) { case MAC_PROP_PRIVATE: break; default: return (ENOTSUP); } (void) ddi_strtol(val, NULL, 0, &lval); if (strcmp(name, "_ipg1") == 0) { if ((lval >= 0) && (lval <= 255)) { hmep->hme_ipg1 = lval & 0xff; init = B_TRUE; } else { return (EINVAL); } } else if (strcmp(name, "_ipg2") == 0) { if ((lval >= 0) && (lval <= 255)) { hmep->hme_ipg2 = lval & 0xff; init = B_TRUE; } else { return (EINVAL); } } else if (strcmp(name, "_ipg0") == 0) { if ((lval >= 0) && (lval <= 31)) { hmep->hme_ipg0 = lval & 0xff; init = B_TRUE; } else { return (EINVAL); } } else if (strcmp(name, "_lance_mode") == 0) { if ((lval >= 0) && (lval <= 1)) { hmep->hme_lance_mode = lval & 0xff; init = B_TRUE; } else { return (EINVAL); } } else { rv = ENOTSUP; } if (init) { (void) hmeinit(hmep); } return (rv); } /*ARGSUSED*/ static boolean_t hme_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) { switch (cap) { case MAC_CAPAB_HCKSUM: *(uint32_t *)cap_data = HCKSUM_INET_PARTIAL; return (B_TRUE); default: return (B_FALSE); } } static int hme_m_promisc(void *arg, boolean_t on) { struct hme *hmep = arg; hmep->hme_promisc = on; (void) hmeinit(hmep); return (0); } static int hme_m_unicst(void *arg, const uint8_t *macaddr) { struct hme *hmep = arg; /* * Set new interface local address and re-init device. * This is destructive to any other streams attached * to this device. */ mutex_enter(&hmep->hme_intrlock); bcopy(macaddr, &hmep->hme_ouraddr, ETHERADDRL); mutex_exit(&hmep->hme_intrlock); (void) hmeinit(hmep); return (0); } static int hme_m_multicst(void *arg, boolean_t add, const uint8_t *macaddr) { struct hme *hmep = arg; uint32_t ladrf_bit; boolean_t doinit = B_FALSE; /* * If this address's bit was not already set in the local address * filter, add it and re-initialize the Hardware. */ ladrf_bit = hmeladrf_bit(macaddr); mutex_enter(&hmep->hme_intrlock); if (add) { hmep->hme_ladrf_refcnt[ladrf_bit]++; if (hmep->hme_ladrf_refcnt[ladrf_bit] == 1) { hmep->hme_ladrf[ladrf_bit >> 4] |= 1 << (ladrf_bit & 0xf); hmep->hme_multi++; doinit = B_TRUE; } } else { hmep->hme_ladrf_refcnt[ladrf_bit]--; if (hmep->hme_ladrf_refcnt[ladrf_bit] == 0) { hmep->hme_ladrf[ladrf_bit >> 4] &= ~(1 << (ladrf_bit & 0xf)); doinit = B_TRUE; } } mutex_exit(&hmep->hme_intrlock); if (doinit) { (void) hmeinit(hmep); } return (0); } static int hme_m_start(void *arg) { struct hme *hmep = arg; if (hmeinit(hmep) != 0) { /* initialization failed -- really want DL_INITFAILED */ return (EIO); } else { hmep->hme_started = B_TRUE; mii_start(hmep->hme_mii); return (0); } } static void hme_m_stop(void *arg) { struct hme *hmep = arg; mii_stop(hmep->hme_mii); hmep->hme_started = B_FALSE; hmeuninit(hmep); } static int hme_m_stat(void *arg, uint_t stat, uint64_t *val) { struct hme *hmep = arg; mutex_enter(&hmep->hme_xmitlock); if (hmep->hme_flags & HMERUNNING) { hmereclaim(hmep); hmesavecntrs(hmep); } mutex_exit(&hmep->hme_xmitlock); if (mii_m_getstat(hmep->hme_mii, stat, val) == 0) { return (0); } switch (stat) { case MAC_STAT_IPACKETS: *val = hmep->hme_ipackets; break; case MAC_STAT_RBYTES: *val = hmep->hme_rbytes; break; case MAC_STAT_IERRORS: *val = hmep->hme_ierrors; break; case MAC_STAT_OPACKETS: *val = hmep->hme_opackets; break; case MAC_STAT_OBYTES: *val = hmep->hme_obytes; break; case MAC_STAT_OERRORS: *val = hmep->hme_oerrors; break; case MAC_STAT_MULTIRCV: *val = hmep->hme_multircv; break; case MAC_STAT_MULTIXMT: *val = hmep->hme_multixmt; break; case MAC_STAT_BRDCSTRCV: *val = hmep->hme_brdcstrcv; break; case MAC_STAT_BRDCSTXMT: *val = hmep->hme_brdcstxmt; break; case MAC_STAT_UNDERFLOWS: *val = hmep->hme_uflo; break; case MAC_STAT_OVERFLOWS: *val = hmep->hme_oflo; break; case MAC_STAT_COLLISIONS: *val = hmep->hme_coll; break; case MAC_STAT_NORCVBUF: *val = hmep->hme_norcvbuf; break; case MAC_STAT_NOXMTBUF: *val = hmep->hme_noxmtbuf; break; case ETHER_STAT_LINK_DUPLEX: *val = hmep->hme_duplex; break; case ETHER_STAT_ALIGN_ERRORS: *val = hmep->hme_align_errors; break; case ETHER_STAT_FCS_ERRORS: *val = hmep->hme_fcs_errors; break; case ETHER_STAT_EX_COLLISIONS: *val = hmep->hme_excol; break; case ETHER_STAT_DEFER_XMTS: *val = hmep->hme_defer_xmts; break; case ETHER_STAT_SQE_ERRORS: *val = hmep->hme_sqe_errors; break; case ETHER_STAT_FIRST_COLLISIONS: *val = hmep->hme_fstcol; break; case ETHER_STAT_TX_LATE_COLLISIONS: *val = hmep->hme_tlcol; break; case ETHER_STAT_TOOLONG_ERRORS: *val = hmep->hme_toolong_errors; break; case ETHER_STAT_TOOSHORT_ERRORS: *val = hmep->hme_runt; break; case ETHER_STAT_CARRIER_ERRORS: *val = hmep->hme_carrier_errors; break; default: return (EINVAL); } return (0); } static mblk_t * hme_m_tx(void *arg, mblk_t *mp) { struct hme *hmep = arg; mblk_t *next; while (mp != NULL) { next = mp->b_next; mp->b_next = NULL; if (!hmestart(hmep, mp)) { mp->b_next = next; break; } mp = next; } return (mp); } /* * Software IP checksum, for the edge cases that the * hardware can't handle. See hmestart for more info. */ static uint16_t hme_cksum(void *data, int len) { uint16_t *words = data; int i, nwords = len / 2; uint32_t sum = 0; /* just add up the words */ for (i = 0; i < nwords; i++) { sum += *words++; } /* pick up residual byte ... assume even half-word allocations */ if (len % 2) { sum += (*words & htons(0xff00)); } sum = (sum >> 16) + (sum & 0xffff); sum = (sum >> 16) + (sum & 0xffff); return (~(sum & 0xffff)); } static boolean_t hmestart(struct hme *hmep, mblk_t *mp) { uint32_t len; boolean_t retval = B_TRUE; hmebuf_t *tbuf; uint32_t txptr; uint32_t csflags = 0; uint32_t flags; uint32_t start_offset; uint32_t stuff_offset; mac_hcksum_get(mp, &start_offset, &stuff_offset, NULL, NULL, &flags); if (flags & HCK_PARTIALCKSUM) { if (get_ether_type(mp->b_rptr) == ETHERTYPE_VLAN) { start_offset += sizeof (struct ether_header) + 4; stuff_offset += sizeof (struct ether_header) + 4; } else { start_offset += sizeof (struct ether_header); stuff_offset += sizeof (struct ether_header); } csflags = HMETMD_CSENABL | (start_offset << HMETMD_CSSTART_SHIFT) | (stuff_offset << HMETMD_CSSTUFF_SHIFT); } mutex_enter(&hmep->hme_xmitlock); if (hmep->hme_flags & HMESUSPENDED) { hmep->hme_carrier_errors++; hmep->hme_oerrors++; goto bad; } if (hmep->hme_txindex != hmep->hme_txreclaim) { hmereclaim(hmep); } if ((hmep->hme_txindex - HME_TMDMAX) == hmep->hme_txreclaim) goto notmds; txptr = hmep->hme_txindex % HME_TMDMAX; tbuf = &hmep->hme_tbuf[txptr]; /* * Note that for checksum offload, the hardware cannot * generate correct checksums if the packet is smaller than * 64-bytes. In such a case, we bcopy the packet and use * a software checksum. */ len = msgsize(mp); if (len < 64) { /* zero fill the padding */ bzero(tbuf->kaddr, 64); } mcopymsg(mp, tbuf->kaddr); if ((csflags != 0) && ((len < 64) || (start_offset > HMETMD_CSSTART_MAX) || (stuff_offset > HMETMD_CSSTUFF_MAX))) { uint16_t sum; sum = hme_cksum(tbuf->kaddr + start_offset, len - start_offset); bcopy(&sum, tbuf->kaddr + stuff_offset, sizeof (sum)); csflags = 0; } if (ddi_dma_sync(tbuf->dmah, 0, len, DDI_DMA_SYNC_FORDEV) == DDI_FAILURE) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, DDI_MSG, "ddi_dma_sync failed"); } /* * update MIB II statistics */ BUMP_OutNUcast(hmep, tbuf->kaddr); PUT_TMD(txptr, tbuf->paddr, len, HMETMD_OWN | HMETMD_SOP | HMETMD_EOP | csflags); HMESYNCTMD(txptr, DDI_DMA_SYNC_FORDEV); hmep->hme_txindex++; PUT_ETXREG(txpend, HMET_TXPEND_TDMD); CHECK_ETXREG(); mutex_exit(&hmep->hme_xmitlock); hmep->hme_starts++; return (B_TRUE); bad: mutex_exit(&hmep->hme_xmitlock); freemsg(mp); return (B_TRUE); notmds: hmep->hme_notmds++; hmep->hme_wantw = B_TRUE; hmereclaim(hmep); retval = B_FALSE; done: mutex_exit(&hmep->hme_xmitlock); return (retval); } /* * Initialize channel. * Return 0 on success, nonzero on error. * * The recommended sequence for initialization is: * 1. Issue a Global Reset command to the Ethernet Channel. * 2. Poll the Global_Reset bits until the execution of the reset has been * completed. * 2(a). Use the MIF Frame/Output register to reset the transceiver. * Poll Register 0 to till the Resetbit is 0. * 2(b). Use the MIF Frame/Output register to set the PHY in in Normal-Op, * 100Mbps and Non-Isolated mode. The main point here is to bring the * PHY out of Isolate mode so that it can generate the rx_clk and tx_clk * to the MII interface so that the Bigmac core can correctly reset * upon a software reset. * 2(c). Issue another Global Reset command to the Ethernet Channel and poll * the Global_Reset bits till completion. * 3. Set up all the data structures in the host memory. * 4. Program the TX_MAC registers/counters (excluding the TX_MAC Configuration * Register). * 5. Program the RX_MAC registers/counters (excluding the RX_MAC Configuration * Register). * 6. Program the Transmit Descriptor Ring Base Address in the ETX. * 7. Program the Receive Descriptor Ring Base Address in the ERX. * 8. Program the Global Configuration and the Global Interrupt Mask Registers. * 9. Program the ETX Configuration register (enable the Transmit DMA channel). * 10. Program the ERX Configuration register (enable the Receive DMA channel). * 11. Program the XIF Configuration Register (enable the XIF). * 12. Program the RX_MAC Configuration Register (Enable the RX_MAC). * 13. Program the TX_MAC Configuration Register (Enable the TX_MAC). */ #ifdef FEPS_URUN_BUG static int hme_palen = 32; #endif static int hmeinit(struct hme *hmep) { uint32_t i; int ret; boolean_t fdx; int phyad; /* * Lock sequence: * hme_intrlock, hme_xmitlock. */ mutex_enter(&hmep->hme_intrlock); /* * Don't touch the hardware if we are suspended. But don't * fail either. Some time later we may be resumed, and then * we'll be back here to program the device using the settings * in the soft state. */ if (hmep->hme_flags & HMESUSPENDED) { mutex_exit(&hmep->hme_intrlock); return (0); } /* * This should prevent us from clearing any interrupts that * may occur by temporarily stopping interrupts from occurring * for a short time. We need to update the interrupt mask * later in this function. */ PUT_GLOBREG(intmask, ~HMEG_MASK_MIF_INTR); /* * Rearranged the mutex acquisition order to solve the deadlock * situation as described in bug ID 4065896. */ mutex_enter(&hmep->hme_xmitlock); hmep->hme_flags = 0; hmep->hme_wantw = B_FALSE; if (hmep->inits) hmesavecntrs(hmep); /* * Perform Global reset of the Sbus/FEPS ENET channel. */ (void) hmestop(hmep); /* * Clear all descriptors. */ bzero(hmep->hme_rmdp, HME_RMDMAX * sizeof (struct hme_rmd)); bzero(hmep->hme_tmdp, HME_TMDMAX * sizeof (struct hme_tmd)); /* * Hang out receive buffers. */ for (i = 0; i < HME_RMDMAX; i++) { PUT_RMD(i, hmep->hme_rbuf[i].paddr); } /* * DMA sync descriptors. */ (void) ddi_dma_sync(hmep->hme_rmd_dmah, 0, 0, DDI_DMA_SYNC_FORDEV); (void) ddi_dma_sync(hmep->hme_tmd_dmah, 0, 0, DDI_DMA_SYNC_FORDEV); /* * Reset RMD and TMD 'walking' pointers. */ hmep->hme_rxindex = 0; hmep->hme_txindex = hmep->hme_txreclaim = 0; /* * This is the right place to initialize MIF !!! */ PUT_MIFREG(mif_imask, HME_MIF_INTMASK); /* mask all interrupts */ if (!hmep->hme_frame_enable) PUT_MIFREG(mif_cfg, GET_MIFREG(mif_cfg) | HME_MIF_CFGBB); else PUT_MIFREG(mif_cfg, GET_MIFREG(mif_cfg) & ~HME_MIF_CFGBB); /* enable frame mode */ /* * Depending on the transceiver detected, select the source * of the clocks for the MAC. Without the clocks, TX_MAC does * not reset. When the Global Reset is issued to the Sbus/FEPS * ASIC, it selects Internal by default. */ switch ((phyad = mii_get_addr(hmep->hme_mii))) { case -1: HME_FAULT_MSG1(hmep, SEVERITY_HIGH, XCVR_MSG, no_xcvr_msg); goto init_fail; /* abort initialization */ case HME_INTERNAL_PHYAD: PUT_MACREG(xifc, 0); break; case HME_EXTERNAL_PHYAD: /* Isolate the Int. xcvr */ PUT_MACREG(xifc, BMAC_XIFC_MIIBUFDIS); break; } hmep->inits++; /* * Initialize BigMAC registers. * First set the tx enable bit in tx config reg to 0 and poll on * it till it turns to 0. Same for rx config, hash and address * filter reg. * Here is the sequence per the spec. * MADD2 - MAC Address 2 * MADD1 - MAC Address 1 * MADD0 - MAC Address 0 * HASH3, HASH2, HASH1, HASH0 for group address * AFR2, AFR1, AFR0 and AFMR for address filter mask * Program RXMIN and RXMAX for packet length if not 802.3 * RXCFG - Rx config for not stripping CRC * XXX Anything else to hme configured in RXCFG * IPG1, IPG2, ALIMIT, SLOT, PALEN, PAPAT, TXSFD, JAM, TXMAX, TXMIN * if not 802.3 compliant * XIF register for speed selection * MASK - Interrupt mask * Set bit 0 of TXCFG * Set bit 0 of RXCFG */ /* * Initialize the TX_MAC registers * Initialization of jamsize to work around rx crc bug */ PUT_MACREG(jam, jamsize); #ifdef FEPS_URUN_BUG if (hme_urun_fix) PUT_MACREG(palen, hme_palen); #endif PUT_MACREG(ipg1, hmep->hme_ipg1); PUT_MACREG(ipg2, hmep->hme_ipg2); PUT_MACREG(rseed, ((hmep->hme_ouraddr.ether_addr_octet[0] << 8) & 0x3) | hmep->hme_ouraddr.ether_addr_octet[1]); /* Initialize the RX_MAC registers */ /* * Program BigMAC with local individual ethernet address. */ PUT_MACREG(madd2, (hmep->hme_ouraddr.ether_addr_octet[4] << 8) | hmep->hme_ouraddr.ether_addr_octet[5]); PUT_MACREG(madd1, (hmep->hme_ouraddr.ether_addr_octet[2] << 8) | hmep->hme_ouraddr.ether_addr_octet[3]); PUT_MACREG(madd0, (hmep->hme_ouraddr.ether_addr_octet[0] << 8) | hmep->hme_ouraddr.ether_addr_octet[1]); /* * Set up multicast address filter by passing all multicast * addresses through a crc generator, and then using the * low order 6 bits as a index into the 64 bit logical * address filter. The high order three bits select the word, * while the rest of the bits select the bit within the word. */ PUT_MACREG(hash0, hmep->hme_ladrf[0]); PUT_MACREG(hash1, hmep->hme_ladrf[1]); PUT_MACREG(hash2, hmep->hme_ladrf[2]); PUT_MACREG(hash3, hmep->hme_ladrf[3]); /* * Configure parameters to support VLAN. (VLAN encapsulation adds * four bytes.) */ PUT_MACREG(txmax, ETHERMAX + ETHERFCSL + 4); PUT_MACREG(rxmax, ETHERMAX + ETHERFCSL + 4); /* * Initialize HME Global registers, ETX registers and ERX registers. */ PUT_ETXREG(txring, hmep->hme_tmd_paddr); PUT_ERXREG(rxring, hmep->hme_rmd_paddr); /* * ERX registers can be written only if they have even no. of bits set. * So, if the value written is not read back, set the lsb and write * again. * static int hme_erx_fix = 1; : Use the fix for erx bug */ { uint32_t temp; temp = hmep->hme_rmd_paddr; if (GET_ERXREG(rxring) != temp) PUT_ERXREG(rxring, (temp | 4)); } PUT_GLOBREG(config, (hmep->hme_config | (hmep->hme_64bit_xfer << HMEG_CONFIG_64BIT_SHIFT))); /* * Significant performance improvements can be achieved by * disabling transmit interrupt. Thus TMD's are reclaimed only * when we run out of them in hmestart(). */ PUT_GLOBREG(intmask, HMEG_MASK_INTR | HMEG_MASK_TINT | HMEG_MASK_TX_ALL); PUT_ETXREG(txring_size, ((HME_TMDMAX -1)>> HMET_RINGSZ_SHIFT)); PUT_ETXREG(config, (GET_ETXREG(config) | HMET_CONFIG_TXDMA_EN | HMET_CONFIG_TXFIFOTH)); /* get the rxring size bits */ switch (HME_RMDMAX) { case 32: i = HMER_CONFIG_RXRINGSZ32; break; case 64: i = HMER_CONFIG_RXRINGSZ64; break; case 128: i = HMER_CONFIG_RXRINGSZ128; break; case 256: i = HMER_CONFIG_RXRINGSZ256; break; default: HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, unk_rx_ringsz_msg); goto init_fail; } i |= (HME_FSTBYTE_OFFSET << HMER_CONFIG_FBO_SHIFT) | HMER_CONFIG_RXDMA_EN; /* h/w checks start offset in half words */ i |= ((sizeof (struct ether_header) / 2) << HMER_RX_CSSTART_SHIFT); PUT_ERXREG(config, i); /* * Bug related to the parity handling in ERX. When erxp-config is * read back. * Sbus/FEPS drives the parity bit. This value is used while * writing again. * This fixes the RECV problem in SS5. * static int hme_erx_fix = 1; : Use the fix for erx bug */ { uint32_t temp; temp = GET_ERXREG(config); PUT_ERXREG(config, i); if (GET_ERXREG(config) != i) HME_FAULT_MSG4(hmep, SEVERITY_UNKNOWN, ERX_MSG, "error:temp = %x erxp->config = %x, should be %x", temp, GET_ERXREG(config), i); } /* * Set up the rxconfig, txconfig and seed register without enabling * them the former two at this time * * BigMAC strips the CRC bytes by default. Since this is * contrary to other pieces of hardware, this bit needs to * enabled to tell BigMAC not to strip the CRC bytes. * Do not filter this node's own packets. */ if (hme_reject_own) { PUT_MACREG(rxcfg, ((hmep->hme_promisc ? BMAC_RXCFG_PROMIS : 0) | BMAC_RXCFG_MYOWN | BMAC_RXCFG_HASH)); } else { PUT_MACREG(rxcfg, ((hmep->hme_promisc ? BMAC_RXCFG_PROMIS : 0) | BMAC_RXCFG_HASH)); } drv_usecwait(10); /* wait after setting Hash Enable bit */ fdx = (mii_get_duplex(hmep->hme_mii) == LINK_DUPLEX_FULL); if (hme_ngu_enable) PUT_MACREG(txcfg, (fdx ? BMAC_TXCFG_FDX : 0) | BMAC_TXCFG_NGU); else PUT_MACREG(txcfg, (fdx ? BMAC_TXCFG_FDX: 0)); i = 0; if ((hmep->hme_lance_mode) && (hmep->hme_lance_mode_enable)) i = ((hmep->hme_ipg0 & HME_MASK_5BIT) << BMAC_XIFC_IPG0_SHIFT) | BMAC_XIFC_LANCE_ENAB; if (phyad == HME_INTERNAL_PHYAD) PUT_MACREG(xifc, i | (BMAC_XIFC_ENAB)); else PUT_MACREG(xifc, i | (BMAC_XIFC_ENAB | BMAC_XIFC_MIIBUFDIS)); PUT_MACREG(rxcfg, GET_MACREG(rxcfg) | BMAC_RXCFG_ENAB); PUT_MACREG(txcfg, GET_MACREG(txcfg) | BMAC_TXCFG_ENAB); hmep->hme_flags |= (HMERUNNING | HMEINITIALIZED); /* * Update the interrupt mask : this will re-allow interrupts to occur */ PUT_GLOBREG(intmask, HMEG_MASK_INTR); mac_tx_update(hmep->hme_mh); init_fail: /* * Release the locks in reverse order */ mutex_exit(&hmep->hme_xmitlock); mutex_exit(&hmep->hme_intrlock); ret = !(hmep->hme_flags & HMERUNNING); if (ret) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, init_fail_gen_msg); } /* * Hardware checks. */ CHECK_GLOBREG(); CHECK_MIFREG(); CHECK_MACREG(); CHECK_ERXREG(); CHECK_ETXREG(); init_exit: return (ret); } /* * Calculate the dvma burstsize by setting up a dvma temporarily. Return * 0 as burstsize upon failure as it signifies no burst size. * Requests for 64-bit transfer setup, if the platform supports it. * NOTE: Do not use ddi_dma_alloc_handle(9f) then ddi_dma_burstsize(9f), * sun4u Ultra-2 incorrectly returns a 32bit transfer. */ static int hmeburstsizes(struct hme *hmep) { int burstsizes; ddi_dma_handle_t handle; if (ddi_dma_alloc_handle(hmep->dip, &hme_dma_attr, DDI_DMA_DONTWAIT, NULL, &handle)) { return (0); } hmep->hme_burstsizes = burstsizes = ddi_dma_burstsizes(handle); ddi_dma_free_handle(&handle); /* * Use user-configurable parameter for enabling 64-bit transfers */ burstsizes = (hmep->hme_burstsizes >> 16); if (burstsizes) hmep->hme_64bit_xfer = hme_64bit_enable; /* user config value */ else burstsizes = hmep->hme_burstsizes; if (hmep->hme_cheerio_mode) hmep->hme_64bit_xfer = 0; /* Disable for cheerio */ if (burstsizes & 0x40) hmep->hme_config = HMEG_CONFIG_BURST64; else if (burstsizes & 0x20) hmep->hme_config = HMEG_CONFIG_BURST32; else hmep->hme_config = HMEG_CONFIG_BURST16; return (DDI_SUCCESS); } static int hmeallocbuf(struct hme *hmep, hmebuf_t *buf, int dir) { ddi_dma_cookie_t dmac; size_t len; unsigned ccnt; if (ddi_dma_alloc_handle(hmep->dip, &hme_dma_attr, DDI_DMA_DONTWAIT, NULL, &buf->dmah) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate buf dma handle - failed"); return (DDI_FAILURE); } if (ddi_dma_mem_alloc(buf->dmah, ROUNDUP(HMEBUFSIZE, 512), &hme_buf_attr, DDI_DMA_STREAMING, DDI_DMA_DONTWAIT, NULL, &buf->kaddr, &len, &buf->acch) != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate buf memory - failed"); return (DDI_FAILURE); } if (ddi_dma_addr_bind_handle(buf->dmah, NULL, buf->kaddr, len, dir | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &dmac, &ccnt) != DDI_DMA_MAPPED) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot map buf for dma - failed"); return (DDI_FAILURE); } buf->paddr = dmac.dmac_address; /* apparently they don't handle multiple cookies */ if (ccnt > 1) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "too many buf dma cookies"); return (DDI_FAILURE); } return (DDI_SUCCESS); } static int hmeallocbufs(struct hme *hmep) { hmep->hme_tbuf = kmem_zalloc(HME_TMDMAX * sizeof (hmebuf_t), KM_SLEEP); hmep->hme_rbuf = kmem_zalloc(HME_RMDMAX * sizeof (hmebuf_t), KM_SLEEP); /* Alloc RX buffers. */ for (int i = 0; i < HME_RMDMAX; i++) { if (hmeallocbuf(hmep, &hmep->hme_rbuf[i], DDI_DMA_READ) != DDI_SUCCESS) { return (DDI_FAILURE); } } /* Alloc TX buffers. */ for (int i = 0; i < HME_TMDMAX; i++) { if (hmeallocbuf(hmep, &hmep->hme_tbuf[i], DDI_DMA_WRITE) != DDI_SUCCESS) { return (DDI_FAILURE); } } return (DDI_SUCCESS); } static void hmefreebufs(struct hme *hmep) { int i; if (hmep->hme_rbuf == NULL) return; /* * Free and unload pending xmit and recv buffers. * Maintaining the 1-to-1 ordered sequence of * We have written the routine to be idempotent. */ for (i = 0; i < HME_TMDMAX; i++) { hmebuf_t *tbuf = &hmep->hme_tbuf[i]; if (tbuf->paddr) { (void) ddi_dma_unbind_handle(tbuf->dmah); } if (tbuf->kaddr) { ddi_dma_mem_free(&tbuf->acch); } if (tbuf->dmah) { ddi_dma_free_handle(&tbuf->dmah); } } for (i = 0; i < HME_RMDMAX; i++) { hmebuf_t *rbuf = &hmep->hme_rbuf[i]; if (rbuf->paddr) { (void) ddi_dma_unbind_handle(rbuf->dmah); } if (rbuf->kaddr) { ddi_dma_mem_free(&rbuf->acch); } if (rbuf->dmah) { ddi_dma_free_handle(&rbuf->dmah); } } kmem_free(hmep->hme_rbuf, HME_RMDMAX * sizeof (hmebuf_t)); kmem_free(hmep->hme_tbuf, HME_TMDMAX * sizeof (hmebuf_t)); } /* * Un-initialize (STOP) HME channel. */ static void hmeuninit(struct hme *hmep) { /* * Allow up to 'HMEDRAINTIME' for pending xmit's to complete. */ HMEDELAY((hmep->hme_txindex == hmep->hme_txreclaim), HMEDRAINTIME); mutex_enter(&hmep->hme_intrlock); mutex_enter(&hmep->hme_xmitlock); hmep->hme_flags &= ~HMERUNNING; (void) hmestop(hmep); mutex_exit(&hmep->hme_xmitlock); mutex_exit(&hmep->hme_intrlock); } /* * Allocate CONSISTENT memory for rmds and tmds with appropriate alignment and * map it in IO space. Allocate space for transmit and receive ddi_dma_handle * structures to use the DMA interface. */ static int hmeallocthings(struct hme *hmep) { int size; int rval; size_t real_len; uint_t cookiec; ddi_dma_cookie_t dmac; dev_info_t *dip = hmep->dip; /* * Allocate the TMD and RMD descriptors and extra for page alignment. */ rval = ddi_dma_alloc_handle(dip, &hme_dma_attr, DDI_DMA_DONTWAIT, NULL, &hmep->hme_rmd_dmah); if (rval != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate rmd handle - failed"); return (DDI_FAILURE); } size = HME_RMDMAX * sizeof (struct hme_rmd); rval = ddi_dma_mem_alloc(hmep->hme_rmd_dmah, size, &hmep->hme_dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &hmep->hme_rmd_kaddr, &real_len, &hmep->hme_rmd_acch); if (rval != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate rmd dma mem - failed"); return (DDI_FAILURE); } hmep->hme_rmdp = (void *)(hmep->hme_rmd_kaddr); rval = ddi_dma_addr_bind_handle(hmep->hme_rmd_dmah, NULL, hmep->hme_rmd_kaddr, size, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &dmac, &cookiec); if (rval != DDI_DMA_MAPPED) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate rmd dma - failed"); return (DDI_FAILURE); } hmep->hme_rmd_paddr = dmac.dmac_address; if (cookiec != 1) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "too many rmd cookies - failed"); return (DDI_FAILURE); } rval = ddi_dma_alloc_handle(dip, &hme_dma_attr, DDI_DMA_DONTWAIT, NULL, &hmep->hme_tmd_dmah); if (rval != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate tmd handle - failed"); return (DDI_FAILURE); } size = HME_TMDMAX * sizeof (struct hme_rmd); rval = ddi_dma_mem_alloc(hmep->hme_tmd_dmah, size, &hmep->hme_dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &hmep->hme_tmd_kaddr, &real_len, &hmep->hme_tmd_acch); if (rval != DDI_SUCCESS) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate tmd dma mem - failed"); return (DDI_FAILURE); } hmep->hme_tmdp = (void *)(hmep->hme_tmd_kaddr); rval = ddi_dma_addr_bind_handle(hmep->hme_tmd_dmah, NULL, hmep->hme_tmd_kaddr, size, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &dmac, &cookiec); if (rval != DDI_DMA_MAPPED) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "cannot allocate tmd dma - failed"); return (DDI_FAILURE); } hmep->hme_tmd_paddr = dmac.dmac_address; if (cookiec != 1) { HME_FAULT_MSG1(hmep, SEVERITY_HIGH, INIT_MSG, "too many tmd cookies - failed"); return (DDI_FAILURE); } return (DDI_SUCCESS); } static void hmefreethings(struct hme *hmep) { if (hmep->hme_rmd_paddr) { (void) ddi_dma_unbind_handle(hmep->hme_rmd_dmah); hmep->hme_rmd_paddr = 0; } if (hmep->hme_rmd_acch) ddi_dma_mem_free(&hmep->hme_rmd_acch); if (hmep->hme_rmd_dmah) ddi_dma_free_handle(&hmep->hme_rmd_dmah); if (hmep->hme_tmd_paddr) { (void) ddi_dma_unbind_handle(hmep->hme_tmd_dmah); hmep->hme_tmd_paddr = 0; } if (hmep->hme_tmd_acch) ddi_dma_mem_free(&hmep->hme_tmd_acch); if (hmep->hme_tmd_dmah) ddi_dma_free_handle(&hmep->hme_tmd_dmah); } /* * First check to see if it our device interrupting. */ static uint_t hmeintr(caddr_t arg) { struct hme *hmep = (void *)arg; uint32_t hmesbits; uint32_t serviced = DDI_INTR_UNCLAIMED; uint32_t num_reads = 0; uint32_t rflags; mblk_t *mp, *head, **tail; head = NULL; tail = &head; mutex_enter(&hmep->hme_intrlock); /* * The status register auto-clears on read except for * MIF Interrupt bit */ hmesbits = GET_GLOBREG(status); CHECK_GLOBREG(); /* * Note: TINT is sometimes enabled in thr hmereclaim() */ /* * Bugid 1227832 - to handle spurious interrupts on fusion systems. * Claim the first interrupt after initialization */ if (hmep->hme_flags & HMEINITIALIZED) { hmep->hme_flags &= ~HMEINITIALIZED; serviced = DDI_INTR_CLAIMED; } if ((hmesbits & (HMEG_STATUS_INTR | HMEG_STATUS_TINT)) == 0) { /* No interesting interrupt */ if (hmep->hme_intrstats) { if (serviced == DDI_INTR_UNCLAIMED) KIOIP->intrs[KSTAT_INTR_SPURIOUS]++; else KIOIP->intrs[KSTAT_INTR_HARD]++; } mutex_exit(&hmep->hme_intrlock); return (serviced); } serviced = DDI_INTR_CLAIMED; if (!(hmep->hme_flags & HMERUNNING)) { if (hmep->hme_intrstats) KIOIP->intrs[KSTAT_INTR_HARD]++; mutex_exit(&hmep->hme_intrlock); hmeuninit(hmep); return (serviced); } if (hmesbits & (HMEG_STATUS_FATAL_ERR | HMEG_STATUS_NONFATAL_ERR)) { if (hmesbits & HMEG_STATUS_FATAL_ERR) { if (hmep->hme_intrstats) KIOIP->intrs[KSTAT_INTR_HARD]++; hme_fatal_err(hmep, hmesbits); mutex_exit(&hmep->hme_intrlock); (void) hmeinit(hmep); return (serviced); } hme_nonfatal_err(hmep, hmesbits); } if (hmesbits & (HMEG_STATUS_TX_ALL | HMEG_STATUS_TINT)) { mutex_enter(&hmep->hme_xmitlock); hmereclaim(hmep); mutex_exit(&hmep->hme_xmitlock); } if (hmesbits & HMEG_STATUS_RINT) { /* * This dummy PIO is required to flush the SBus * Bridge buffers in QFE. */ (void) GET_GLOBREG(config); /* * Loop through each RMD no more than once. */ while (num_reads++ < HME_RMDMAX) { hmebuf_t *rbuf; int rxptr; rxptr = hmep->hme_rxindex % HME_RMDMAX; HMESYNCRMD(rxptr, DDI_DMA_SYNC_FORKERNEL); rflags = GET_RMD_FLAGS(rxptr); if (rflags & HMERMD_OWN) { /* * Chip still owns it. We're done. */ break; } /* * Retrieve the packet. */ rbuf = &hmep->hme_rbuf[rxptr]; mp = hmeread(hmep, rbuf, rflags); /* * Return ownership of the RMD. */ PUT_RMD(rxptr, rbuf->paddr); HMESYNCRMD(rxptr, DDI_DMA_SYNC_FORDEV); if (mp != NULL) { *tail = mp; tail = &mp->b_next; } /* * Advance to the next RMD. */ hmep->hme_rxindex++; } } if (hmep->hme_intrstats) KIOIP->intrs[KSTAT_INTR_HARD]++; mutex_exit(&hmep->hme_intrlock); if (head != NULL) mac_rx(hmep->hme_mh, NULL, head); return (serviced); } /* * Transmit completion reclaiming. */ static void hmereclaim(struct hme *hmep) { boolean_t reclaimed = B_FALSE; /* * Loop through each TMD. */ while (hmep->hme_txindex > hmep->hme_txreclaim) { int reclaim; uint32_t flags; reclaim = hmep->hme_txreclaim % HME_TMDMAX; HMESYNCTMD(reclaim, DDI_DMA_SYNC_FORKERNEL); flags = GET_TMD_FLAGS(reclaim); if (flags & HMETMD_OWN) { /* * Chip still owns it. We're done. */ break; } /* * Count a chained packet only once. */ if (flags & HMETMD_SOP) { hmep->hme_opackets++; } /* * MIB II */ hmep->hme_obytes += flags & HMETMD_BUFSIZE; reclaimed = B_TRUE; hmep->hme_txreclaim++; } if (reclaimed) { /* * we could reclaim some TMDs so turn off interrupts */ if (hmep->hme_wantw) { PUT_GLOBREG(intmask, HMEG_MASK_INTR | HMEG_MASK_TINT | HMEG_MASK_TX_ALL); hmep->hme_wantw = B_FALSE; mac_tx_update(hmep->hme_mh); } } else { /* * enable TINTS: so that even if there is no further activity * hmereclaim will get called */ if (hmep->hme_wantw) PUT_GLOBREG(intmask, GET_GLOBREG(intmask) & ~HMEG_MASK_TX_ALL); } CHECK_GLOBREG(); } /* * Handle interrupts for fatal errors * Need reinitialization of the ENET channel. */ static void hme_fatal_err(struct hme *hmep, uint_t hmesbits) { if (hmesbits & HMEG_STATUS_SLV_PAR_ERR) { hmep->hme_slvparerr++; } if (hmesbits & HMEG_STATUS_SLV_ERR_ACK) { hmep->hme_slverrack++; } if (hmesbits & HMEG_STATUS_TX_TAG_ERR) { hmep->hme_txtagerr++; hmep->hme_oerrors++; } if (hmesbits & HMEG_STATUS_TX_PAR_ERR) { hmep->hme_txparerr++; hmep->hme_oerrors++; } if (hmesbits & HMEG_STATUS_TX_LATE_ERR) { hmep->hme_txlaterr++; hmep->hme_oerrors++; } if (hmesbits & HMEG_STATUS_TX_ERR_ACK) { hmep->hme_txerrack++; hmep->hme_oerrors++; } if (hmesbits & HMEG_STATUS_EOP_ERR) { hmep->hme_eoperr++; } if (hmesbits & HMEG_STATUS_RX_TAG_ERR) { hmep->hme_rxtagerr++; hmep->hme_ierrors++; } if (hmesbits & HMEG_STATUS_RX_PAR_ERR) { hmep->hme_rxparerr++; hmep->hme_ierrors++; } if (hmesbits & HMEG_STATUS_RX_LATE_ERR) { hmep->hme_rxlaterr++; hmep->hme_ierrors++; } if (hmesbits & HMEG_STATUS_RX_ERR_ACK) { hmep->hme_rxerrack++; hmep->hme_ierrors++; } } /* * Handle interrupts regarding non-fatal errors. */ static void hme_nonfatal_err(struct hme *hmep, uint_t hmesbits) { if (hmesbits & HMEG_STATUS_RX_DROP) { hmep->hme_missed++; hmep->hme_ierrors++; } if (hmesbits & HMEG_STATUS_DEFTIMR_EXP) { hmep->hme_defer_xmts++; } if (hmesbits & HMEG_STATUS_FSTCOLC_EXP) { hmep->hme_fstcol += 256; } if (hmesbits & HMEG_STATUS_LATCOLC_EXP) { hmep->hme_tlcol += 256; hmep->hme_oerrors += 256; } if (hmesbits & HMEG_STATUS_EXCOLC_EXP) { hmep->hme_excol += 256; hmep->hme_oerrors += 256; } if (hmesbits & HMEG_STATUS_NRMCOLC_EXP) { hmep->hme_coll += 256; } if (hmesbits & HMEG_STATUS_MXPKTSZ_ERR) { hmep->hme_babl++; hmep->hme_oerrors++; } /* * This error is fatal and the board needs to * be reinitialized. Comments? */ if (hmesbits & HMEG_STATUS_TXFIFO_UNDR) { hmep->hme_uflo++; hmep->hme_oerrors++; } if (hmesbits & HMEG_STATUS_SQE_TST_ERR) { hmep->hme_sqe_errors++; } if (hmesbits & HMEG_STATUS_RCV_CNT_EXP) { if (hmep->hme_rxcv_enable) { hmep->hme_cvc += 256; } } if (hmesbits & HMEG_STATUS_RXFIFO_OVFL) { hmep->hme_oflo++; hmep->hme_ierrors++; } if (hmesbits & HMEG_STATUS_LEN_CNT_EXP) { hmep->hme_lenerr += 256; hmep->hme_ierrors += 256; } if (hmesbits & HMEG_STATUS_ALN_CNT_EXP) { hmep->hme_align_errors += 256; hmep->hme_ierrors += 256; } if (hmesbits & HMEG_STATUS_CRC_CNT_EXP) { hmep->hme_fcs_errors += 256; hmep->hme_ierrors += 256; } } static mblk_t * hmeread(struct hme *hmep, hmebuf_t *rbuf, uint32_t rflags) { mblk_t *bp; uint32_t len; t_uscalar_t type; len = (rflags & HMERMD_BUFSIZE) >> HMERMD_BUFSIZE_SHIFT; /* * Check for short packet * and check for overflow packet also. The processing is the * same for both the cases - reuse the buffer. Update the Buffer * overflow counter. */ if ((len < ETHERMIN) || (rflags & HMERMD_OVFLOW) || (len > (ETHERMAX + 4))) { if (len < ETHERMIN) hmep->hme_runt++; else { hmep->hme_buff++; hmep->hme_toolong_errors++; } hmep->hme_ierrors++; return (NULL); } /* * Sync the received buffer before looking at it. */ (void) ddi_dma_sync(rbuf->dmah, 0, 0, DDI_DMA_SYNC_FORKERNEL); /* * copy the packet data and then recycle the descriptor. */ if ((bp = allocb(len + HME_FSTBYTE_OFFSET, BPRI_HI)) == NULL) { hmep->hme_allocbfail++; hmep->hme_norcvbuf++; return (NULL); } bcopy(rbuf->kaddr, bp->b_rptr, len + HME_FSTBYTE_OFFSET); hmep->hme_ipackets++; /* Add the First Byte offset to the b_rptr and copy */ bp->b_rptr += HME_FSTBYTE_OFFSET; bp->b_wptr = bp->b_rptr + len; /* * update MIB II statistics */ BUMP_InNUcast(hmep, bp->b_rptr); hmep->hme_rbytes += len; type = get_ether_type(bp->b_rptr); /* * TCP partial checksum in hardware */ if (type == ETHERTYPE_IP || type == ETHERTYPE_IPV6) { uint16_t cksum = ~rflags & HMERMD_CKSUM; uint_t end = len - sizeof (struct ether_header); mac_hcksum_set(bp, 0, 0, end, htons(cksum), HCK_PARTIALCKSUM); } return (bp); } /*VARARGS*/ static void hme_fault_msg(struct hme *hmep, uint_t severity, msg_t type, char *fmt, ...) { char msg_buffer[255]; va_list ap; va_start(ap, fmt); (void) vsnprintf(msg_buffer, sizeof (msg_buffer), fmt, ap); if (hmep == NULL) { cmn_err(CE_NOTE, "hme : %s", msg_buffer); } else if (type == DISPLAY_MSG) { cmn_err(CE_CONT, "?%s%d : %s\n", ddi_driver_name(hmep->dip), hmep->instance, msg_buffer); } else if (severity == SEVERITY_HIGH) { cmn_err(CE_WARN, "%s%d : %s, SEVERITY_HIGH, %s\n", ddi_driver_name(hmep->dip), hmep->instance, msg_buffer, msg_string[type]); } else { cmn_err(CE_CONT, "%s%d : %s\n", ddi_driver_name(hmep->dip), hmep->instance, msg_buffer); } va_end(ap); } /* * if this is the first init do not bother to save the * counters. They should be 0, but do not count on it. */ static void hmesavecntrs(struct hme *hmep) { uint32_t fecnt, aecnt, lecnt, rxcv; uint32_t ltcnt, excnt; /* XXX What all gets added in ierrors and oerrors? */ fecnt = GET_MACREG(fecnt); PUT_MACREG(fecnt, 0); aecnt = GET_MACREG(aecnt); hmep->hme_align_errors += aecnt; PUT_MACREG(aecnt, 0); lecnt = GET_MACREG(lecnt); hmep->hme_lenerr += lecnt; PUT_MACREG(lecnt, 0); rxcv = GET_MACREG(rxcv); #ifdef HME_CODEVIOL_BUG /* * Ignore rxcv errors for Sbus/FEPS 2.1 or earlier */ if (!hmep->hme_rxcv_enable) { rxcv = 0; } #endif hmep->hme_cvc += rxcv; PUT_MACREG(rxcv, 0); ltcnt = GET_MACREG(ltcnt); hmep->hme_tlcol += ltcnt; PUT_MACREG(ltcnt, 0); excnt = GET_MACREG(excnt); hmep->hme_excol += excnt; PUT_MACREG(excnt, 0); hmep->hme_fcs_errors += fecnt; hmep->hme_ierrors += (fecnt + aecnt + lecnt); hmep->hme_oerrors += (ltcnt + excnt); hmep->hme_coll += (GET_MACREG(nccnt) + ltcnt); PUT_MACREG(nccnt, 0); CHECK_MACREG(); } /* * To set up the mac address for the network interface: * The adapter card may support a local mac address which is published * in a device node property "local-mac-address". This mac address is * treated as the factory-installed mac address for DLPI interface. * If the adapter firmware has used the device for diskless boot * operation it publishes a property called "mac-address" for use by * inetboot and the device driver. * If "mac-address" is not found, the system options property * "local-mac-address" is used to select the mac-address. If this option * is set to "true", and "local-mac-address" has been found, then * local-mac-address is used; otherwise the system mac address is used * by calling the "localetheraddr()" function. */ static void hme_setup_mac_address(struct hme *hmep, dev_info_t *dip) { char *prop; int prop_len = sizeof (int); hmep->hme_addrflags = 0; /* * Check if it is an adapter with its own local mac address * If it is present, save it as the "factory-address" * for this adapter. */ if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "local-mac-address", (caddr_t)&prop, &prop_len) == DDI_PROP_SUCCESS) { if (prop_len == ETHERADDRL) { hmep->hme_addrflags = HME_FACTADDR_PRESENT; ether_bcopy(prop, &hmep->hme_factaddr); HME_FAULT_MSG2(hmep, SEVERITY_NONE, DISPLAY_MSG, "Local Ethernet address = %s", ether_sprintf(&hmep->hme_factaddr)); } kmem_free(prop, prop_len); } /* * Check if the adapter has published "mac-address" property. * If it is present, use it as the mac address for this device. */ if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "mac-address", (caddr_t)&prop, &prop_len) == DDI_PROP_SUCCESS) { if (prop_len >= ETHERADDRL) { ether_bcopy(prop, &hmep->hme_ouraddr); kmem_free(prop, prop_len); return; } kmem_free(prop, prop_len); } #ifdef __sparc /* * On sparc, we might be able to use the mac address from the * system. However, on all other systems, we need to use the * address from the PROM. */ if (ddi_getlongprop(DDI_DEV_T_ANY, dip, 0, "local-mac-address?", (caddr_t)&prop, &prop_len) == DDI_PROP_SUCCESS) { if ((strncmp("true", prop, prop_len) == 0) && (hmep->hme_addrflags & HME_FACTADDR_PRESENT)) { hmep->hme_addrflags |= HME_FACTADDR_USE; ether_bcopy(&hmep->hme_factaddr, &hmep->hme_ouraddr); kmem_free(prop, prop_len); HME_FAULT_MSG1(hmep, SEVERITY_NONE, DISPLAY_MSG, "Using local MAC address"); return; } kmem_free(prop, prop_len); } /* * Get the system ethernet address. */ (void) localetheraddr((struct ether_addr *)NULL, &hmep->hme_ouraddr); #else ether_bcopy(&hmep->hme_factaddr, &hmep->hme_ouraddr); #endif } /* ARGSUSED */ static void hme_check_acc_handle(char *file, uint_t line, struct hme *hmep, ddi_acc_handle_t handle) { }