/* 3Com 3C90xB/C EtherLink driver, by D.C. van Moolenbroek */ #include #include #include #include #include #include "3c90x.h" #define VERBOSE 0 /* verbose debugging output */ #define XLBC_FKEY 11 /* use Shift+Fn to dump statistics (0=off) */ #if VERBOSE #define XLBC_DEBUG(x) printf x #else #define XLBC_DEBUG(x) #endif static struct { char name[sizeof("3c90x#0")]; /* driver name */ int hook_id; /* IRQ hook ID */ uint8_t *base; /* base address of memory-mapped registers */ uint32_t size; /* size of memory-mapped register area */ uint16_t window; /* currently active register window */ uint16_t filter; /* packet receipt filter flags */ xlbc_pd_t *dpd_base; /* TX descriptor array, virtual address */ phys_bytes dpd_phys; /* TX descriptor array, physical address */ uint8_t *txb_base; /* transmission buffer, virtual address */ phys_bytes txb_phys; /* transmission buffer, physical address */ xlbc_pd_t *upd_base; /* RX descriptor array, virtual address */ phys_bytes upd_phys; /* RX descriptor array, physical address */ uint8_t *rxb_base; /* receipt buffers, virtual address */ phys_bytes rxb_phys; /* receipt buffers, physical address */ unsigned int dpd_tail; /* index of tail TX descriptor */ unsigned int dpd_used; /* number of in-use TX descriptors */ size_t txb_tail; /* index of tail TX byte in buffer */ size_t txb_used; /* number of in-use TX buffer bytes */ unsigned int upd_head; /* index of head RX descriptor */ eth_stat_t stat; /* statistics */ } state; enum xlbc_link_type { XLBC_LINK_DOWN, XLBC_LINK_UP, XLBC_LINK_UP_T_HD, XLBC_LINK_UP_T_FD, XLBC_LINK_UP_TX_HD, XLBC_LINK_UP_TX_FD }; #define XLBC_READ_8(off) (*(volatile uint8_t *)(state.base + (off))) #define XLBC_READ_16(off) (*(volatile uint16_t *)(state.base + (off))) #define XLBC_READ_32(off) (*(volatile uint32_t *)(state.base + (off))) #define XLBC_WRITE_8(off, val) \ (*(volatile uint8_t *)(state.base + (off)) = (val)) #define XLBC_WRITE_16(off, val) \ (*(volatile uint16_t *)(state.base + (off)) = (val)) #define XLBC_WRITE_32(off, val) \ (*(volatile uint32_t *)(state.base + (off)) = (val)) static int xlbc_init(unsigned int instance, ether_addr_t *addr); static void xlbc_stop(void); static void xlbc_mode(unsigned int mode); static ssize_t xlbc_recv(struct netdriver_data *data, size_t max); static int xlbc_send(struct netdriver_data *data, size_t size); static void xlbc_stat(eth_stat_t *stat); static void xlbc_intr(unsigned int mask); static void xlbc_other(const message *m_ptr, int ipc_status); static const struct netdriver xlbc_table = { .ndr_init = xlbc_init, .ndr_stop = xlbc_stop, .ndr_mode = xlbc_mode, .ndr_recv = xlbc_recv, .ndr_send = xlbc_send, .ndr_stat = xlbc_stat, .ndr_intr = xlbc_intr, .ndr_other = xlbc_other, }; /* * Find a matching PCI device. */ static int xlbc_probe(unsigned int skip) { uint16_t vid, did; int devind; #if VERBOSE const char *dname; #endif pci_init(); if (pci_first_dev(&devind, &vid, &did) <= 0) return -1; while (skip--) { if (pci_next_dev(&devind, &vid, &did) <= 0) return -1; } #if VERBOSE dname = pci_dev_name(vid, did); XLBC_DEBUG(("%s: found %s (%04x:%04x) at %s\n", state.name, dname ? dname : "", vid, did, pci_slot_name(devind))); #endif pci_reserve(devind); return devind; } /* * Issue a command to the command register. */ static void xlbc_issue_cmd(uint16_t cmd) { assert(!(XLBC_READ_16(XLBC_STATUS_REG) & XLBC_STATUS_IN_PROGRESS)); XLBC_WRITE_16(XLBC_CMD_REG, cmd); } /* * Wait for a command to be acknowledged. Return TRUE iff the command * completed within the timeout period. */ static int xlbc_wait_cmd(void) { spin_t spin; /* * The documentation implies that a timeout of 1ms is an upper bound * for all commands. */ SPIN_FOR(&spin, XLBC_CMD_TIMEOUT) { if (!(XLBC_READ_16(XLBC_STATUS_REG) & XLBC_STATUS_IN_PROGRESS)) return TRUE; } return FALSE; } /* * Reset the device to its initial state. Return TRUE iff successful. */ static int xlbc_reset(void) { (void)xlbc_wait_cmd(); xlbc_issue_cmd(XLBC_CMD_GLOBAL_RESET); /* * It appears that the "command in progress" bit may be cleared before * the reset has completed, resulting in strange behavior afterwards. * Thus, we wait for the maximum reset time (1ms) regardless first, and * only then start checking the command-in-progress bit. */ micro_delay(XLBC_RESET_DELAY); if (!xlbc_wait_cmd()) return FALSE; state.window = 0; return TRUE; } /* * Select a register window. */ static void xlbc_select_window(unsigned int window) { if (state.window == window) return; xlbc_issue_cmd(XLBC_CMD_SELECT_WINDOW | window); state.window = window; } /* * Read a word from the EEPROM. On failure, return a value with all bits set. */ static uint16_t xlbc_read_eeprom(unsigned int word) { spin_t spin; /* The B revision supports 64 EEPROM words only. */ assert(!(word & ~XLBC_EEPROM_CMD_ADDR)); xlbc_select_window(XLBC_EEPROM_WINDOW); assert(!(XLBC_READ_16(XLBC_EEPROM_CMD_REG) & XLBC_EEPROM_CMD_BUSY)); XLBC_WRITE_16(XLBC_EEPROM_CMD_REG, XLBC_EEPROM_CMD_READ | word); /* The documented maximum delay for reads is 162us. */ SPIN_FOR(&spin, XLBC_EEPROM_TIMEOUT) { if (!(XLBC_READ_16(XLBC_EEPROM_CMD_REG) & XLBC_EEPROM_CMD_BUSY)) return XLBC_READ_16(XLBC_EEPROM_DATA_REG); } return (uint16_t)-1; } /* * Obtain the preconfigured hardware address of the device. */ static void xlbc_get_hwaddr(ether_addr_t * addr) { uint16_t word[3]; /* TODO: allow overriding through environment variables */ word[0] = xlbc_read_eeprom(XLBC_EEPROM_WORD_OEM_ADDR0); word[1] = xlbc_read_eeprom(XLBC_EEPROM_WORD_OEM_ADDR1); word[2] = xlbc_read_eeprom(XLBC_EEPROM_WORD_OEM_ADDR2); addr->ea_addr[0] = word[0] >> 8; addr->ea_addr[1] = word[0] & 0xff; addr->ea_addr[2] = word[1] >> 8; addr->ea_addr[3] = word[1] & 0xff; addr->ea_addr[4] = word[2] >> 8; addr->ea_addr[5] = word[2] & 0xff; XLBC_DEBUG(("%s: MAC address %02x:%02x:%02x:%02x:%02x:%02x\n", state.name, addr->ea_addr[0], addr->ea_addr[1], addr->ea_addr[2], addr->ea_addr[3], addr->ea_addr[4], addr->ea_addr[5])); } /* * Configure the device to use the given hardware address. */ static void xlbc_set_hwaddr(ether_addr_t * addr) { xlbc_select_window(XLBC_STATION_WINDOW); /* Set station address. */ XLBC_WRITE_16(XLBC_STATION_ADDR0_REG, addr->ea_addr[0] | (addr->ea_addr[1] << 8)); XLBC_WRITE_16(XLBC_STATION_ADDR1_REG, addr->ea_addr[2] | (addr->ea_addr[3] << 8)); XLBC_WRITE_16(XLBC_STATION_ADDR2_REG, addr->ea_addr[4] | (addr->ea_addr[5] << 8)); /* Set station mask. */ XLBC_WRITE_16(XLBC_STATION_MASK0_REG, 0); XLBC_WRITE_16(XLBC_STATION_MASK1_REG, 0); XLBC_WRITE_16(XLBC_STATION_MASK2_REG, 0); } /* * Perform one-time initialization of various settings. */ static void xlbc_init_once(void) { uint16_t word; uint32_t dword; /* * Verify the presence of a 10BASE-T or 100BASE-TX port. Those are the * only port types that are supported and have been tested so far. */ xlbc_select_window(XLBC_MEDIA_OPT_WINDOW); word = XLBC_READ_16(XLBC_MEDIA_OPT_REG); if (!(word & (XLBC_MEDIA_OPT_BASE_TX | XLBC_MEDIA_OPT_10_BT))) panic("no 100BASE-TX or 10BASE-T port on device"); /* Initialize the device's internal configuration. */ xlbc_select_window(XLBC_CONFIG_WINDOW); word = XLBC_READ_16(XLBC_CONFIG_WORD1_REG); word = (word & ~XLBC_CONFIG_XCVR_MASK) | XLBC_CONFIG_XCVR_AUTO; XLBC_WRITE_16(XLBC_CONFIG_WORD1_REG, word); /* Disable alternate upload and download sequences. */ dword = XLBC_READ_32(XLBC_DMA_CTRL_REG); dword |= XLBC_DMA_CTRL_UP_NOALT | XLBC_DMA_CTRL_DN_NOALT; XLBC_WRITE_32(XLBC_DMA_CTRL_REG, dword); /* Specify in which status events we are interested. */ xlbc_issue_cmd(XLBC_CMD_IND_ENABLE | XLBC_STATUS_MASK); /* Enable statistics, including support for counters' upper bits. */ xlbc_select_window(XLBC_NET_DIAG_WINDOW); word = XLBC_READ_16(XLBC_NET_DIAG_REG); XLBC_WRITE_16(XLBC_NET_DIAG_REG, word | XLBC_NET_DIAG_UPPER); xlbc_issue_cmd(XLBC_CMD_STATS_ENABLE); } /* * Allocate memory for DMA. */ static void xlbc_alloc_dma(void) { /* Packet descriptors require 8-byte alignment. */ assert(!(sizeof(xlbc_pd_t) % 8)); /* * For packet transmission, we use one single circular buffer in which * we store packet data. We do not split packets in two when the * buffer wraps; instead we waste the trailing bytes and move on to the * start of the buffer. This allows us to use a single fragment for * each transmitted packet, thus keeping the descriptors small (16 * bytes). The descriptors themselves are allocated as a separate * array. There is obviously room for improvement here, but the * approach should be good enough. */ state.dpd_base = alloc_contig(XLBC_DPD_COUNT * sizeof(xlbc_pd_t), AC_ALIGN4K, &state.dpd_phys); state.txb_base = alloc_contig(XLBC_TXB_SIZE, 0, &state.txb_phys); if (state.dpd_base == NULL || state.txb_base == NULL) panic("unable to allocate memory for packet transmission"); /* * For packet receipt, we have a number of pairs of buffers and * corresponding descriptors. Each buffer is large enough to contain * an entire packet. We avoid wasting memory by allocating the buffers * in one go, at the cost of requiring a large contiguous area. The * descriptors are allocated as a separate array, thus matching the * scheme for transmission in terms of allocation strategy. Here, too, * there is clear room for improvement at the cost of extra complexity. */ state.upd_base = alloc_contig(XLBC_UPD_COUNT * sizeof(xlbc_pd_t), AC_ALIGN4K, &state.upd_phys); state.rxb_base = alloc_contig(XLBC_UPD_COUNT * XLBC_MAX_PKT_LEN, 0, &state.rxb_phys); if (state.upd_base == NULL || state.rxb_base == NULL) panic("unable to allocate memory for packet receipt"); } /* * Reset the transmitter. */ static void xlbc_reset_tx(void) { xlbc_issue_cmd(XLBC_CMD_TX_RESET); if (!xlbc_wait_cmd()) panic("timeout trying to reset transmitter"); state.dpd_tail = 0; state.dpd_used = 0; state.txb_tail = 0; state.txb_used = 0; xlbc_issue_cmd(XLBC_CMD_TX_ENABLE); } /* * Reset the receiver. */ static void xlbc_reset_rx(void) { unsigned int i; xlbc_issue_cmd(XLBC_CMD_RX_RESET); if (!xlbc_wait_cmd()) panic("timeout trying to reset receiver"); xlbc_issue_cmd(XLBC_CMD_SET_FILTER | state.filter); for (i = 0; i < XLBC_UPD_COUNT; i++) { state.upd_base[i].next = state.upd_phys + ((i + 1) % XLBC_UPD_COUNT) * sizeof(xlbc_pd_t); state.upd_base[i].flags = 0; state.upd_base[i].addr = state.rxb_phys + i * XLBC_MAX_PKT_LEN; state.upd_base[i].len = XLBC_LEN_LAST | XLBC_MAX_PKT_LEN; } XLBC_WRITE_32(XLBC_UP_LIST_PTR_REG, state.upd_phys); state.upd_head = 0; __insn_barrier(); xlbc_issue_cmd(XLBC_CMD_RX_ENABLE); } /* * Execute a MII read, write, or Z cycle. Stop the clock, wait, start the * clock, optionally change direction and/or data bits, and wait again. */ static uint16_t xlbc_mii_cycle(uint16_t val, uint16_t mask, uint16_t bits) { val &= ~XLBC_PHYS_MGMT_CLK; XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val); /* All the delays should be 200ns minimum. */ micro_delay(XLBC_MII_DELAY); /* The clock must be enabled separately from other bit updates. */ val |= XLBC_PHYS_MGMT_CLK; XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val); if (mask != 0) { val = (val & ~mask) | bits; XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val); } micro_delay(XLBC_MII_DELAY); return val; } /* * Read a MII register. */ static uint16_t xlbc_mii_read(uint16_t phy, uint16_t reg) { uint32_t dword; uint16_t val; int i; xlbc_select_window(XLBC_PHYS_MGMT_WINDOW); /* Set the direction to write. */ val = XLBC_READ_16(XLBC_PHYS_MGMT_REG) | XLBC_PHYS_MGMT_DIR; XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val); /* Execute write cycles to submit the preamble: PR=1..1 (32 bits) */ for (i = 0; i < 32; i++) val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA, XLBC_PHYS_MGMT_DATA); /* Execute write cycles to submit the rest of the read frame. */ /* ST=01 OP=10 PHYAD=aaaaa REGAD=rrrrr */ dword = 0x1800 | (phy << 5) | reg; for (i = 13; i >= 0; i--) val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA, ((dword >> i) & 1) ? XLBC_PHYS_MGMT_DATA : 0); /* Execute a Z cycle to set the direction to read. */ val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DIR, 0); dword = 0; /* Receive one status bit and 16 actual data bits. */ for (i = 16; i >= 0; i--) { (void)xlbc_mii_cycle(val, 0, 0); val = XLBC_READ_16(XLBC_PHYS_MGMT_REG); dword = (dword << 1) | !!(val & XLBC_PHYS_MGMT_DATA); micro_delay(XLBC_MII_DELAY); } /* Execute a Z cycle to terminate the read frame. */ (void)xlbc_mii_cycle(val, 0, 0); /* If the status bit was set, the results are invalid. */ if (dword & 0x10000) dword = 0xffff; return (uint16_t)dword; } /* * Write a MII register. */ static void xlbc_mii_write(uint16_t phy, uint16_t reg, uint16_t data) { uint32_t dword; uint16_t val; int i; xlbc_select_window(XLBC_PHYS_MGMT_WINDOW); /* Set the direction to write. */ val = XLBC_READ_16(XLBC_PHYS_MGMT_REG) | XLBC_PHYS_MGMT_DIR; XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val); /* Execute write cycles to submit the preamble: PR=1..1 (32 bits) */ for (i = 0; i < 32; i++) val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA, XLBC_PHYS_MGMT_DATA); /* Execute write cycles to submit the rest of the read frame. */ /* ST=01 OP=01 PHYAD=aaaaa REGAD=rrrrr TA=10 DATA=d..d (16 bits) */ dword = 0x50020000 | (phy << 23) | (reg << 18) | data; for (i = 31; i >= 0; i--) val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA, ((dword >> i) & 1) ? XLBC_PHYS_MGMT_DATA : 0); /* Execute a Z cycle to terminate the write frame. */ (void)xlbc_mii_cycle(val, 0, 0); } /* * Return a human-readable description for the given link type. */ #if VERBOSE || XLBC_FKEY static const char * xlbc_get_link_name(enum xlbc_link_type link_type) { switch (link_type) { case XLBC_LINK_DOWN: return "down"; case XLBC_LINK_UP: return "up"; case XLBC_LINK_UP_T_HD: return "up (10Mbps, half duplex)"; case XLBC_LINK_UP_T_FD: return "up (10Mbps, full duplex)"; case XLBC_LINK_UP_TX_HD: return "up (100Mbps, half duplex)"; case XLBC_LINK_UP_TX_FD: return "up (100Mbps, full duplex)"; default: return "(unknown)"; } } #endif /* VERBOSE || XLBC_FKEY */ /* * Determine the current link status, and return the resulting link type. */ static enum xlbc_link_type xlbc_get_link_type(void) { uint16_t status, control, mask; xlbc_select_window(XLBC_MEDIA_STS_WINDOW); if (!(XLBC_READ_16(XLBC_MEDIA_STS_REG) & XLBC_MEDIA_STS_LINK_DET)) return XLBC_LINK_DOWN; status = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_STATUS); if (!(status & XLBC_MII_STATUS_EXTCAP)) return XLBC_LINK_UP; if (!(status & XLBC_MII_STATUS_AUTONEG)) return XLBC_LINK_UP; /* Wait for auto-negotiation to complete first. */ if (!(status & XLBC_MII_STATUS_COMPLETE)) { control = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_CONTROL); control |= XLBC_MII_CONTROL_AUTONEG; xlbc_mii_write(XLBC_PHY_ADDR, XLBC_MII_CONTROL, control); SPIN_UNTIL(xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_STATUS) & XLBC_MII_STATUS_COMPLETE, XLBC_AUTONEG_TIMEOUT); status = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_STATUS); if (!(status & XLBC_MII_STATUS_COMPLETE)) return XLBC_LINK_UP; } /* The highest bit set in both registers is the selected link type. */ mask = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_AUTONEG_ADV) & xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_LP_ABILITY); if (mask & XLBC_MII_LINK_TX_FD) return XLBC_LINK_UP_TX_FD; if (mask & XLBC_MII_LINK_TX_HD) return XLBC_LINK_UP_TX_HD; if (mask & XLBC_MII_LINK_T_FD) return XLBC_LINK_UP_T_FD; if (mask & XLBC_MII_LINK_T_HD) return XLBC_LINK_UP_T_HD; return XLBC_LINK_UP; } /* * Set the duplex mode to full or half, based on the current link type. */ static void xlbc_set_duplex(enum xlbc_link_type link) { uint16_t word; int duplex; /* * If the link is down, do not change modes. In fact, the link may go * down as a result of the reset that is part of changing the mode. */ if (link == XLBC_LINK_DOWN) return; /* See if the desired duplex mode differs from the current mode. */ duplex = (link == XLBC_LINK_UP_T_FD || link == XLBC_LINK_UP_TX_FD); xlbc_select_window(XLBC_MAC_CTRL_WINDOW); word = XLBC_READ_16(XLBC_MAC_CTRL_REG); if (!!(word & XLBC_MAC_CTRL_ENA_FD) == duplex) return; /* already in the desired mode */ /* * Change duplex mode. Unfortunately, that also means we need to * reset the RX and TX engines. Fortunately, this should happen only * on a link change, so we're probably not doing much extra damage. * TODO: recovery for packets currently on the transmission queue. */ XLBC_DEBUG(("%s: %s full-duplex mode\n", state.name, duplex ? "setting" : "clearing")); XLBC_WRITE_16(XLBC_MAC_CTRL_REG, word ^ XLBC_MAC_CTRL_ENA_FD); xlbc_reset_rx(); xlbc_reset_tx(); } /* * The link status has changed. */ static void xlbc_link_event(void) { enum xlbc_link_type link_type; /* * The 3c90xB is documented to require a read from the internal * auto-negotiation expansion MII register in order to clear the link * event interrupt. The 3c90xC resets the link event interrupt as part * of automatic interrupt acknowledgment. */ (void)xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_AUTONEG_EXP); link_type = xlbc_get_link_type(); #if VERBOSE XLBC_DEBUG(("%s: link %s\n", state.name, xlbc_get_link_name(link_type))); #endif xlbc_set_duplex(link_type); } /* * Initialize the device. */ static void xlbc_init_hw(int devind, ether_addr_t * addr) { uint32_t bar; uint16_t cr; int r, io, irq; /* Map in the device's memory-mapped registers. */ if ((r = pci_get_bar(devind, PCI_BAR_2, &bar, &state.size, &io)) != OK) panic("unable to retrieve bar: %d", r); if (state.size < XLBC_MIN_REG_SIZE || io) panic("invalid register bar"); state.base = vm_map_phys(SELF, (void *)bar, state.size); if (state.base == MAP_FAILED) panic("unable to map in registers"); /* Reset the device to a known initial state. */ if (!xlbc_reset()) panic("unable to reset hardware"); /* Now that the device is reset, enable bus mastering if needed. */ cr = pci_attr_r8(devind, PCI_CR); if (!(cr & PCI_CR_MAST_EN)) pci_attr_w8(devind, PCI_CR, cr | PCI_CR_MAST_EN); /* Obtain and apply the hardware address. */ xlbc_get_hwaddr(addr); xlbc_set_hwaddr(addr); /* Perform various one-time initialization actions. */ xlbc_init_once(); /* Allocate memory for DMA. */ xlbc_alloc_dma(); /* Initialize the transmitter. */ xlbc_reset_tx(); /* Initialize the receiver. */ state.filter = XLBC_FILTER_STATION; xlbc_reset_rx(); /* Enable interrupts. */ irq = pci_attr_r8(devind, PCI_ILR); state.hook_id = 0; if ((r = sys_irqsetpolicy(irq, 0, &state.hook_id)) != OK) panic("unable to register IRQ: %d", r); if ((r = sys_irqenable(&state.hook_id)) != OK) panic("unable to enable IRQ: %d", r); xlbc_issue_cmd(XLBC_CMD_INT_ENABLE | XLBC_STATUS_MASK); /* * We will probably get a link event anyway, but trigger one now in * case that does not happen. The main purpose of this call is to * set the right duplex mode. */ xlbc_link_event(); } /* * Initialize the 3c90x driver and device. */ static int xlbc_init(unsigned int instance, ether_addr_t * addr) { int devind; #if XLBC_FKEY int fkeys, sfkeys; #endif memset(&state, 0, sizeof(state)); strlcpy(state.name, "3c90x#0", sizeof(state.name)); state.name[sizeof(state.name) - 2] += instance; /* Try to find a recognized device. */ if ((devind = xlbc_probe(instance)) < 0) return ENXIO; /* Initialize the device. */ xlbc_init_hw(devind, addr); #if XLBC_FKEY /* Register debug dump function key. */ fkeys = sfkeys = 0; bit_set(sfkeys, XLBC_FKEY); (void)fkey_map(&fkeys, &sfkeys); /* ignore failure */ #endif return OK; } /* * Stop the device. The main purpose is to stop any ongoing and future DMA. */ static void xlbc_stop(void) { /* A full reset ought to do it. */ (void)xlbc_reset(); } /* * Set packet receipt mode. */ static void xlbc_mode(unsigned int mode) { state.filter = XLBC_FILTER_STATION; if (mode & NDEV_MULTI) state.filter |= XLBC_FILTER_MULTI; if (mode & NDEV_BROAD) state.filter |= XLBC_FILTER_BROAD; if (mode & NDEV_PROMISC) state.filter |= XLBC_FILTER_PROMISC; xlbc_issue_cmd(XLBC_CMD_SET_FILTER | state.filter); } /* * Try to receive a packet. */ static ssize_t xlbc_recv(struct netdriver_data * data, size_t max) { uint32_t flags; uint8_t *ptr; unsigned int head; size_t len; head = state.upd_head; flags = *(volatile uint32_t *)&state.upd_base[head].flags; /* * The documentation implies, but does not state, that UP_COMPLETE is * set whenever UP_ERROR is. We rely exclusively on UP_COMPLETE. */ if (!(flags & XLBC_UP_COMPLETE)) return SUSPEND; if (flags & XLBC_UP_ERROR) { XLBC_DEBUG(("%s: received error\n", state.name)); state.stat.ets_recvErr++; if (flags & XLBC_UP_OVERRUN) state.stat.ets_fifoOver++; if (flags & XLBC_UP_ALIGN_ERR) state.stat.ets_frameAll++; if (flags & XLBC_UP_CRC_ERR) state.stat.ets_CRCerr++; len = 0; /* immediately move on to the next descriptor */ } else { len = flags & XLBC_UP_LEN; XLBC_DEBUG(("%s: received packet (size %zu)\n", state.name, len)); /* The device is supposed to not give us runt frames. */ assert(len >= XLBC_MIN_PKT_LEN); /* Truncate large packets. */ if (flags & XLBC_UP_OVERFLOW) len = XLBC_MAX_PKT_LEN; if (len > max) len = max; ptr = state.rxb_base + head * XLBC_MAX_PKT_LEN; netdriver_copyout(data, 0, ptr, len); } /* Mark the descriptor as ready for reuse. */ *(volatile uint32_t *)&state.upd_base[head].flags = 0; /* * At this point, the receive engine may have stalled as a result of * filling up all descriptors. Now that we have a free descriptor, we * can restart it. As per the documentation, we unstall blindly. */ xlbc_issue_cmd(XLBC_CMD_UP_UNSTALL); /* Advance to the next descriptor in our ring. */ state.upd_head = (head + 1) % XLBC_UPD_COUNT; return len; } /* * Return how much padding (if any) must be prepended to a packet of the given * size so that it does not have to be split due to wrapping. The given offset * is the starting point of the packet; this may be beyond the transmission * buffer size in the case that the current buffer contents already wrap. */ static size_t xlbc_pad_tx(size_t off, size_t size) { if (off < XLBC_TXB_SIZE && off + size >= XLBC_TXB_SIZE) return XLBC_TXB_SIZE - off; else return 0; } /* * Try to send a packet. */ static int xlbc_send(struct netdriver_data * data, size_t size) { size_t used, off, left; unsigned int head, last; uint32_t phys; /* We need a free transmission descriptor. */ if (state.dpd_used == XLBC_DPD_COUNT) return SUSPEND; /* * See if we can fit the packet in the circular transmission buffer. * The packet may not be broken up in two parts as the buffer wraps. */ used = state.txb_used; used += xlbc_pad_tx(state.txb_tail + used, size); left = XLBC_TXB_SIZE - used; if (left < size) return SUSPEND; XLBC_DEBUG(("%s: transmitting packet (size %zu)\n", state.name, size)); /* Copy in the packet. */ off = (state.txb_tail + used) % XLBC_TXB_SIZE; netdriver_copyin(data, 0, &state.txb_base[off], size); /* Set up a descriptor for the packet. */ head = (state.dpd_tail + state.dpd_used) % XLBC_DPD_COUNT; state.dpd_base[head].next = 0; state.dpd_base[head].flags = XLBC_DN_RNDUP_WORD | XLBC_DN_DN_INDICATE; state.dpd_base[head].addr = state.txb_phys + off; state.dpd_base[head].len = XLBC_LEN_LAST | size; phys = state.dpd_phys + head * sizeof(xlbc_pd_t); __insn_barrier(); /* We need to stall only if other packets were already pending. */ if (XLBC_READ_32(XLBC_DN_LIST_PTR_REG) != 0) { assert(state.dpd_used > 0); xlbc_issue_cmd(XLBC_CMD_DN_STALL); if (!xlbc_wait_cmd()) panic("timeout trying to stall downloads"); last = (state.dpd_tail + state.dpd_used - 1) % XLBC_DPD_COUNT; state.dpd_base[last].next = phys; /* Group interrupts a bit. This is a tradeoff. */ state.dpd_base[last].flags &= ~XLBC_DN_DN_INDICATE; if (XLBC_READ_32(XLBC_DN_LIST_PTR_REG) == 0) XLBC_WRITE_32(XLBC_DN_LIST_PTR_REG, phys); xlbc_issue_cmd(XLBC_CMD_DN_UNSTALL); } else XLBC_WRITE_32(XLBC_DN_LIST_PTR_REG, phys); /* Advance internal queue heads. */ state.dpd_used++; state.txb_used = used + size; assert(state.txb_used <= XLBC_TXB_SIZE); return OK; } /* * One or more packets have been downloaded. Free up the corresponding * descriptors for later reuse. */ static void xlbc_advance_tx(void) { uint32_t flags, len; while (state.dpd_used > 0) { flags = *(volatile uint32_t *) &state.dpd_base[state.dpd_tail].flags; if (!(flags & XLBC_DN_DN_COMPLETE)) break; XLBC_DEBUG(("%s: packet copied to transmitter\n", state.name)); len = state.dpd_base[state.dpd_tail].len & ~XLBC_LEN_LAST; state.dpd_tail = (state.dpd_tail + 1) % XLBC_DPD_COUNT; state.dpd_used--; len += xlbc_pad_tx(state.txb_tail, len); assert(state.txb_used >= len); state.txb_tail = (state.txb_tail + len) % XLBC_TXB_SIZE; state.txb_used -= len; } } /* * A transmission error has occurred. Restart, and if necessary even reset, * the transmitter. */ static void xlbc_recover_tx(void) { uint8_t status; int enable, reset; enable = reset = FALSE; while ((status = XLBC_READ_8(XLBC_TX_STATUS_REG)) & XLBC_TX_STATUS_COMPLETE) { XLBC_DEBUG(("%s: transmission error (0x%04x)\n", state.name, status)); /* This is an internal (non-packet) error status. */ if (status & XLBC_TX_STATUS_OVERFLOW) enable = TRUE; if (status & XLBC_TX_STATUS_MAX_COLL) { state.stat.ets_sendErr++; state.stat.ets_transAb++; enable = TRUE; } if (status & XLBC_TX_STATUS_UNDERRUN) { state.stat.ets_sendErr++; state.stat.ets_fifoUnder++; reset = TRUE; } if (status & XLBC_TX_STATUS_JABBER) { state.stat.ets_sendErr++; reset = TRUE; } XLBC_WRITE_8(XLBC_TX_STATUS_REG, status); } if (reset) { /* * Below is the documented Underrun Recovery procedure. We use * it for jabber errors as well, because there is no indication * that another procedure should be followed for that case. */ xlbc_issue_cmd(XLBC_CMD_DN_STALL); if (!xlbc_wait_cmd()) panic("download stall timeout during recovery"); SPIN_UNTIL(!(XLBC_READ_32(XLBC_DMA_CTRL_REG) & XLBC_DMA_CTRL_DN_INPROG), XLBC_CMD_TIMEOUT); xlbc_select_window(XLBC_MEDIA_STS_WINDOW); SPIN_UNTIL(!(XLBC_READ_16(XLBC_MEDIA_STS_REG) & XLBC_MEDIA_STS_TX_INPROG), XLBC_CMD_TIMEOUT); xlbc_issue_cmd(XLBC_CMD_TX_RESET); if (!xlbc_wait_cmd()) panic("transmitter reset timeout during recovery"); xlbc_issue_cmd(XLBC_CMD_TX_ENABLE); XLBC_WRITE_32(XLBC_DN_LIST_PTR_REG, state.dpd_phys + state.dpd_tail * sizeof(xlbc_pd_t)); XLBC_DEBUG(("%s: performed recovery\n", state.name)); } else if (enable) xlbc_issue_cmd(XLBC_CMD_TX_ENABLE); } /* * Update statistics. We read all registers, not just the ones we are * interested in, so as to limit the number of useless statistics interrupts. */ static void xlbc_update_stats(void) { uint8_t upper, up_rx, up_tx; xlbc_select_window(XLBC_STATS_WINDOW); state.stat.ets_carrSense += XLBC_READ_8(XLBC_CARRIER_LOST_REG); (void)XLBC_READ_8(XLBC_SQE_ERR_REG); state.stat.ets_collision += XLBC_READ_8(XLBC_MULTI_COLL_REG); state.stat.ets_collision += XLBC_READ_8(XLBC_SINGLE_COLL_REG); state.stat.ets_OWC += XLBC_READ_8(XLBC_LATE_COLL_REG); state.stat.ets_missedP += XLBC_READ_8(XLBC_RX_OVERRUNS_REG); state.stat.ets_transDef += XLBC_READ_8(XLBC_FRAMES_DEFERRED_REG); upper = XLBC_READ_8(XLBC_UPPER_FRAMES_REG); up_tx = ((upper & XLBC_UPPER_TX_MASK) >> XLBC_UPPER_TX_SHIFT) << 8; up_rx = ((upper & XLBC_UPPER_RX_MASK) >> XLBC_UPPER_RX_SHIFT) << 8; state.stat.ets_packetT += XLBC_READ_8(XLBC_FRAMES_XMIT_OK_REG) + up_tx; state.stat.ets_packetR += XLBC_READ_8(XLBC_FRAMES_RCVD_OK_REG) + up_rx; (void)XLBC_READ_16(XLBC_BYTES_RCVD_OK_REG); (void)XLBC_READ_16(XLBC_BYTES_XMIT_OK_REG); xlbc_select_window(XLBC_SSD_STATS_WINDOW); (void)XLBC_READ_8(XLBC_BAD_SSD_REG); } /* * Copy out statistics. */ static void xlbc_stat(eth_stat_t * stat) { xlbc_update_stats(); memcpy(stat, &state.stat, sizeof(*stat)); } /* * Process an interrupt. */ static void xlbc_intr(unsigned int __unused mask) { uint32_t val; int r; /* * Get interrupt mask. Acknowledge some interrupts, and disable all * interrupts as automatic side effect. The assumption is that any new * events are stored as indications which are then translated into * interrupts as soon as interrupts are reenabled, but this is not * documented explicitly. */ val = XLBC_READ_16(XLBC_STATUS_AUTO_REG); XLBC_DEBUG(("%s: interrupt (0x%04x)\n", state.name, val)); if (val & XLBC_STATUS_UP_COMPLETE) netdriver_recv(); if (val & (XLBC_STATUS_DN_COMPLETE | XLBC_STATUS_TX_COMPLETE)) xlbc_advance_tx(); if (val & XLBC_STATUS_TX_COMPLETE) xlbc_recover_tx(); if (val & XLBC_STATUS_HOST_ERROR) { /* * A catastrophic host error has occurred. Reset both the * transmitter and the receiver. This should be enough to * clear the host error, but may be overkill in the cases where * the error direction (TX or RX) can be clearly identified. * Since this entire condition is effectively untestable, we * do not even try to be smart about it. */ XLBC_DEBUG(("%s: host error, performing reset\n", state.name)); xlbc_reset_tx(); xlbc_reset_rx(); /* If this has not resolved the problem, restart the driver. */ if (XLBC_READ_16(XLBC_STATUS_REG) & XLBC_STATUS_HOST_ERROR) panic("host error not cleared"); } if (val & XLBC_STATUS_UPDATE_STATS) xlbc_update_stats(); if (val & XLBC_STATUS_LINK_EVENT) xlbc_link_event(); /* See if we should try to send more packets. */ if (val & (XLBC_STATUS_DN_COMPLETE | XLBC_STATUS_TX_COMPLETE | XLBC_STATUS_HOST_ERROR)) netdriver_send(); /* Reenable interrupts. */ if ((r = sys_irqenable(&state.hook_id)) != OK) panic("unable to reenable IRQ: %d", r); xlbc_issue_cmd(XLBC_CMD_INT_ENABLE | XLBC_STATUS_MASK); } /* * Dump statistics. */ #if XLBC_FKEY static void xlbc_dump(void) { enum xlbc_link_type link_type; link_type = xlbc_get_link_type(); xlbc_update_stats(); printf("\n"); printf("%s statistics:\n", state.name); printf("recvErr: %8ld\t", state.stat.ets_recvErr); printf("sendErr: %8ld\t", state.stat.ets_sendErr); printf("OVW: %8ld\n", state.stat.ets_OVW); printf("CRCerr: %8ld\t", state.stat.ets_CRCerr); printf("frameAll: %8ld\t", state.stat.ets_frameAll); printf("missedP: %8ld\n", state.stat.ets_missedP); printf("packetR: %8ld\t", state.stat.ets_packetR); printf("packetT: %8ld\t", state.stat.ets_packetT); printf("transDef: %8ld\n", state.stat.ets_transDef); printf("collision: %8ld\t", state.stat.ets_collision); printf("transAb: %8ld\t", state.stat.ets_transAb); printf("carrSense: %8ld\n", state.stat.ets_carrSense); printf("fifoUnder: %8ld\t", state.stat.ets_fifoUnder); printf("fifoOver: %8ld\t", state.stat.ets_fifoOver); printf("CDheartbeat: %8ld\n", state.stat.ets_CDheartbeat); printf("OWC: %8ld\t", state.stat.ets_OWC); printf("link: %s\n", xlbc_get_link_name(link_type)); } #endif /* XLBC_FKEY */ /* * Process miscellaneous messages. */ static void xlbc_other(const message * m_ptr, int ipc_status) { #if XLBC_FKEY int sfkeys; if (!is_ipc_notify(ipc_status) || m_ptr->m_source != TTY_PROC_NR) return; if (fkey_events(NULL, &sfkeys) == OK && bit_isset(sfkeys, XLBC_FKEY)) xlbc_dump(); #endif /* XLBC_FKEY */ } /* * The 3c90x ethernet driver. */ int main(int argc, char ** argv) { env_setargs(argc, argv); netdriver_task(&xlbc_table); return EXIT_SUCCESS; }