summaryrefslogtreecommitdiff
path: root/sys/dev/phy
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/phy')
-rw-r--r--sys/dev/phy/e1000.c358
-rw-r--r--sys/dev/phy/rtl.c (renamed from sys/dev/phy/rt8139.c)210
2 files changed, 527 insertions, 41 deletions
diff --git a/sys/dev/phy/e1000.c b/sys/dev/phy/e1000.c
new file mode 100644
index 0000000..95efe6d
--- /dev/null
+++ b/sys/dev/phy/e1000.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/driver.h>
+#include <sys/errno.h>
+#include <sys/syslog.h>
+#include <sys/mmio.h>
+#include <dev/phy/e1000regs.h>
+#include <dev/pci/pci.h>
+#include <dev/pci/pciregs.h>
+#include <dev/timer.h>
+#include <net/if_var.h>
+#include <string.h>
+
+#define pr_trace(fmt, ...) kprintf("e1000: " fmt, ##__VA_ARGS__)
+#define pr_error(...) pr_trace(__VA_ARGS__)
+
+#define E1000_VENDOR 0x8086
+#define E1000_DEVICE 0x100E
+#define E1000_TIMEOUT 500 /* In msec */
+
+static struct timer tmr;
+static struct pci_device *e1000;
+static struct netif netif;
+
+struct e1000_nic {
+ void *vap;
+ uint8_t has_eeprom : 1;
+ uint16_t eeprom_size;
+ uint16_t io_port;
+};
+
+static int
+e1000_poll_reg(volatile uint32_t *reg, uint32_t bits, bool pollset)
+{
+ size_t usec_start, usec;
+ size_t elapsed_msec;
+ uint32_t val;
+ bool tmp;
+
+ usec_start = tmr.get_time_usec();
+
+ for (;;) {
+ val = mmio_read32(reg);
+ tmp = (pollset) ? ISSET(val, bits) : !ISSET(val, bits);
+
+ usec = tmr.get_time_usec();
+ elapsed_msec = (usec - usec_start) / 1000;
+
+ /* If tmp is set, the register updated in time */
+ if (tmp) {
+ break;
+ }
+
+ /* Exit with an error if we timeout */
+ if (elapsed_msec > E1000_TIMEOUT) {
+ return -ETIME;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Query information about any EEPROMs for diagnostic
+ * purposes.
+ *
+ * TODO: Some wacky older chips don't show their presence
+ * too easily, we could fallback to microwire / SPI
+ * bit banging to see if it responds to us manually
+ * clocking a dummy read operation in.
+ */
+static void
+eeprom_query(struct e1000_nic *np)
+{
+ uint16_t size_bits = 1024;
+ uint32_t eecd, *eecd_p;
+ const char *typestr = "microwire";
+
+ eecd_p = PTR_OFFSET(np->vap, E1000_EECD);
+
+ /*
+ * First we should check if there is an EEPROM
+ * on-board as if not, there is nothing we can do
+ * here.
+ */
+ eecd = mmio_read32(eecd_p);
+ if (!ISSET(eecd, E1000_EECD_PRES)) {
+ return;
+ }
+
+ np->has_eeprom = 1;
+ if (ISSET(eecd, E1000_EECD_TYPE)) {
+ typestr = "SPI";
+ }
+ if (ISSET(eecd, E1000_EECD_SIZE)) {
+ size_bits = 4096;
+ }
+
+ np->eeprom_size = size_bits;
+ pr_trace("%d-bit %s EEPROM detected\n", size_bits, typestr);
+}
+
+/*
+ * If there is no EEPROM, we can still read
+ * the MAC address through the Receive address
+ * registers
+ *
+ * XXX: This is typically only used as a fallback.
+ *
+ * Returns a less than zero value if an ethernet
+ * address is not found, which would be kind of
+ * not good.
+ *
+ * @np: NIC descriptor
+ * @addr: Pointer to MAC address data
+ */
+static int
+e1000_read_recvaddr(struct e1000_nic *np, struct netif_addr *addr)
+{
+ const uint32_t RECVADDR_OFF = 0x5400;
+ uint32_t tmp;
+ uint32_t *dword_p;
+
+ dword_p = PTR_OFFSET(np->vap, RECVADDR_OFF);
+
+ if (dword_p[0] == 0) {
+ pr_error("bad hwaddr in recvaddr\n");
+ return -ENOTSUP;
+ }
+
+ /* DWORD 0 */
+ tmp = mmio_read32(&dword_p[0]);
+ addr->data[0] = tmp & 0xFF;
+ addr->data[1] = (tmp >> 8) & 0xFF;
+ addr->data[2] = (tmp >> 16) & 0xFF;
+ addr->data[3] = (tmp >> 24) & 0xFF;
+
+ /* DWORD 1 */
+ tmp = mmio_read32(&dword_p[1]);
+ addr->data[4] = tmp & 0xFF;
+ addr->data[5] = (tmp >> 8) & 0xFF;
+ return 0;
+}
+
+/*
+ * Read 16-bytes from the NIC's on-board EEPROM.
+ *
+ * XXX: This should only be used if the caller is
+ * certain that the NIC has an EEPROM
+ *
+ * @addr: EEPROM address to read from
+ *
+ * A returned value of 0xFFFF should be seen as invalid.
+ */
+static uint16_t
+eeprom_readw(struct e1000_nic *np, uint8_t addr)
+{
+ uint32_t eerd, *eerd_p;
+ int error;
+
+ if (!np->has_eeprom) {
+ pr_error("e1000_read_eeprom: EEPROM not present\n");
+ return 0xFFFF;
+ }
+
+ eerd_p = PTR_OFFSET(np->vap, E1000_EERD);
+ eerd = (addr << 8) | E1000_EERD_START;
+ mmio_write32(eerd_p, eerd);
+
+ error = e1000_poll_reg(eerd_p, E1000_EERD_DONE, true);
+ if (error < 0) {
+ pr_error("e1000_read_eeprom: timeout\n");
+ return 0xFFFF;
+ }
+
+ eerd = mmio_read32(eerd_p);
+ return (eerd >> 16) & 0xFFFF;
+}
+
+/*
+ * Read the MAC address from the NICs EEPROM.
+ *
+ * XXX: This should usually work, however if the NIC does
+ * not have an on-board EEPROM, this will fail. In such
+ * cases, e1000_read_recvaddr() can be called instead.
+ *
+ * @np: NIC descriptor
+ * @addr: Pointer to MAC address data
+ */
+static int
+e1000_read_macaddr(struct e1000_nic *np, struct netif_addr *addr)
+{
+ uint16_t eeprom_word;
+
+ if (!np->has_eeprom) {
+ pr_trace("EEPROM not present, trying recvaddr\n");
+ return e1000_read_recvaddr(np, addr);
+ }
+
+ /* Word 0 */
+ eeprom_word = eeprom_readw(np, E1000_HWADDR0);
+ addr->data[0] = (eeprom_word & 0xFF);
+ addr->data[1] = (eeprom_word >> 8) & 0xFF;
+
+ /* Word 1 */
+ eeprom_word = eeprom_readw(np, E1000_HWADDR1);
+ addr->data[2] = (eeprom_word & 0xFF);
+ addr->data[3] = (eeprom_word >> 8) & 0xFF;
+
+ /* Word 2 */
+ eeprom_word = eeprom_readw(np, E1000_HWADDR2);
+ addr->data[4] = (eeprom_word & 0xFF);
+ addr->data[5] = (eeprom_word >> 8) & 0xFF;
+ return 0;
+}
+
+/*
+ * Reset the entire E1000
+ */
+static int
+e1000_reset(struct e1000_nic *np)
+{
+ uint32_t ctl, *ctl_p;
+ int error;
+
+ ctl_p = PTR_OFFSET(np->vap, E1000_CTL);
+ ctl = mmio_read32(&ctl_p);
+ ctl |= E1000_CTL_RST;
+ mmio_write32(&ctl_p, ctl);
+
+ error = e1000_poll_reg(ctl_p, E1000_CTL_RST, false);
+ if (error < 0) {
+ pr_error("reset timeout\n");
+ return error;
+ }
+
+ return 0;
+}
+
+/*
+ * Initialize an E1000(e) chip
+ */
+static int
+e1000_chip_init(struct e1000_nic *np)
+{
+ struct netif_addr *addr = &netif.addr;
+ int error;
+
+ /*
+ * To ensure that BIOS/UEFI or whatever firmware got us
+ * here didn't fuck anything up in the process or at the
+ * very least, put the controller in a seemingly alright
+ * state that gives us a suprise screwing in the future,
+ * we'll reset everything to its default startup state.
+ *
+ * Better safe than sorry...
+ */
+ if ((error = e1000_reset(np)) < 0) {
+ return error;
+ }
+
+ eeprom_query(np);
+ if ((error = e1000_read_macaddr(np, addr)) < 0) {
+ return error;
+ }
+
+ pr_trace("MAC address: %x:%x:%x:%x:%x:%x\n",
+ (uint64_t)addr->data[0], (uint64_t)addr->data[1],
+ (uint64_t)addr->data[2], (uint64_t)addr->data[3],
+ (uint64_t)addr->data[4], (uint64_t)addr->data[5]);
+
+ return 0;
+}
+
+/*
+ * Enables PCI specific bits like bus mastering (for DMA)
+ * as well as MMIO.
+ */
+static void
+e1000_init_pci(void)
+{
+ uint32_t tmp;
+
+ tmp = pci_readl(e1000, PCIREG_CMDSTATUS);
+ tmp |= (PCI_BUS_MASTERING | PCI_MEM_SPACE);
+ pci_writel(e1000, PCIREG_CMDSTATUS, tmp);
+}
+
+static int
+e1000_init(void)
+{
+ struct pci_lookup lookup;
+ struct e1000_nic nic;
+ int status;
+
+ lookup.vendor_id = E1000_VENDOR;
+ lookup.device_id = E1000_DEVICE;
+ e1000 = pci_get_device(lookup, PCI_DEVICE_ID | PCI_VENDOR_ID);
+ if (e1000 == NULL) {
+ return -ENODEV;
+ }
+
+ /* Get a GP timer */
+ if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) {
+ pr_error("failed to fetch general purpose timer\n");
+ return -ENODEV;
+ }
+
+ /* We need msleep() */
+ if (tmr.msleep == NULL) {
+ pr_error("general purpose timer has no msleep()\n");
+ return -ENODEV;
+ }
+
+ memset(&nic, 0, sizeof(nic));
+ pr_trace("e1000 at pci%d:%x.%x.%d\n",
+ e1000->bus, e1000->device_id, e1000->func,
+ e1000->slot);
+
+ if ((status = pci_map_bar(e1000, 0, &nic.vap)) != 0) {
+ pr_error("failed to map BAR0\n");
+ return status;
+ }
+
+ e1000_init_pci();
+ e1000_chip_init(&nic);
+ return 0;
+}
+
+DRIVER_EXPORT(e1000_init);
diff --git a/sys/dev/phy/rt8139.c b/sys/dev/phy/rtl.c
index e2f87e1..691f767 100644
--- a/sys/dev/phy/rt8139.c
+++ b/sys/dev/phy/rtl.c
@@ -30,29 +30,30 @@
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/syslog.h>
+#include <sys/spinlock.h>
#include <sys/driver.h>
+#include <sys/device.h>
#include <dev/pci/pci.h>
-#include <dev/phy/rt8139.h>
+#include <dev/phy/rtl.h>
#include <dev/timer.h>
#include <dev/pci/pciregs.h>
+#include <net/netbuf.h>
+#include <net/if_var.h>
#include <vm/physmem.h>
+#include <vm/dynalloc.h>
#include <vm/vm.h>
#include <machine/pio.h>
+#include <machine/intr.h>
#include <string.h>
-/* TODO: Make this smoother */
-#if defined(__x86_64__)
-#include <machine/intr.h>
-#include <machine/ioapic.h>
-#include <machine/lapic.h>
-#include <machine/idt.h>
-#endif
+#define IFNAME "rt0"
-#define pr_trace(fmt, ...) kprintf("rt8139: " fmt, ##__VA_ARGS__)
+#define pr_trace(fmt, ...) kprintf("rt81xx: " fmt, ##__VA_ARGS__)
#define pr_error(...) pr_trace(__VA_ARGS__)
#define RX_BUF_SIZE 3 /* In pages */
#define RX_REAL_BUF_SIZE 8192 /* In bytes */
+#define TXQ_ENTRIES 4
#define RX_PTR_MASK (~3)
@@ -63,11 +64,26 @@
#define HAVE_PIO 0
#endif /* _MACHINE_HAVE_PIO */
+static struct spinlock netif_lock;
+static struct netbuf netif_buf[TXQ_ENTRIES];
static struct pci_device *dev;
+static struct netif netif;
static struct timer tmr;
+static uint32_t tx_ptr = 0;
+static uint32_t netif_enq_ptr = 0;
static uint16_t ioport;
static paddr_t rxbuf, txbuf;
+/* TXAD regs */
+static uint16_t tsads[TXQ_ENTRIES] = {
+ RT_TXAD_N(0), RT_TXAD_N(4),
+ RT_TXAD_N(8), RT_TXAD_N(12)
+};
+static uint16_t tsds[TXQ_ENTRIES] = {
+ RT_TXSTATUS_N(0), RT_TXSTATUS_N(4),
+ RT_TXSTATUS_N(8), RT_TXSTATUS_N(8)
+};
+
/*
* Write to an RTL8139 register
*
@@ -156,53 +172,112 @@ rt_poll(uint8_t reg, uint8_t size, uint32_t bits, bool pollset)
return val;
}
-#if defined(__x86_64__)
-__isr static void
-rt8139_pin_irq(void *sp)
+static int
+rt_tx(void *packet, size_t len)
+{
+ static uint32_t tx_ptr = 0;
+ void *tx_data;
+ paddr_t tx_pa;
+
+ tx_data = dynalloc(len);
+ if (tx_data == NULL) {
+ return -ENOMEM;
+ }
+
+ memcpy(tx_data, packet, len);
+ tx_pa = VIRT_TO_PHYS(tx_data);
+ rt_write(tsads[tx_ptr], 4, tx_pa);
+ rt_write(tsds[tx_ptr++], 4, len);
+ if (tx_ptr > TXQ_ENTRIES - 1) {
+ tx_ptr = 0;
+ }
+ return 0;
+}
+
+static void
+__rt81xx_tx_start(struct netif *nifp)
+{
+ struct netbuf *dest;
+ int error;
+
+ for (int i = 0; i < netif_enq_ptr; ++i) {
+ dest = &netif_buf[i];
+ error = rt_tx(dest->data, dest->len);
+ if (error < 0) {
+ pr_error("tx_start fail @queue %d (errno=%d)\n", i, error);
+ }
+ }
+}
+
+static void
+rt81xx_tx_start(struct netif *nifp)
+{
+ spinlock_acquire(&netif_lock);
+ __rt81xx_tx_start(nifp);
+ spinlock_release(&netif_lock);
+}
+
+static int
+rt81xx_tx_enq(struct netif *nifp, struct netbuf *nbp, void *data)
+{
+ struct netbuf *dest;
+
+ spinlock_acquire(&netif_lock);
+ dest = &netif_buf[netif_enq_ptr++];
+ memcpy(dest, nbp, sizeof(*dest));
+
+ if (netif_enq_ptr > TXQ_ENTRIES - 1) {
+ __rt81xx_tx_start(nifp);
+ netif_enq_ptr = 0;
+ }
+ spinlock_release(&netif_lock);
+ return 0;
+}
+
+static int
+rt81xx_intr(void *sp)
{
- static uint32_t packet_ptr = 0;
uint16_t len;
uint16_t *p;
uint16_t status;
status = rt_read(RT_INTRSTATUS, 2);
- p = (uint16_t *)(rxbuf + packet_ptr);
+ p = (uint16_t *)(rxbuf + tx_ptr);
len = *(p + 1); /* Length after header */
p += 2; /* Points to data now */
- if (status & RT_TOK) {
- return;
+ if (!ISSET(status, RT_TOK | RT_ROK)) {
+ return 0;
+ }
+
+ if (ISSET(status, RT_TOK)) {
+ pr_trace("sent packet\n");
+ return 1;
}
/* Update rxbuf offset in CAPR */
- packet_ptr = (packet_ptr + len + 4 + 3) & RX_PTR_MASK;
- if (packet_ptr > RX_REAL_BUF_SIZE) {
- packet_ptr -= RX_REAL_BUF_SIZE;
+ tx_ptr = (tx_ptr + len + 4 + 3) & RX_PTR_MASK;
+ if (tx_ptr > RX_REAL_BUF_SIZE) {
+ tx_ptr -= RX_REAL_BUF_SIZE;
}
- rt_write(RT_RXBUFTAIL, 2, packet_ptr - 0x10);
+ rt_write(RT_RXBUFTAIL, 2, tx_ptr - 0x10);
rt_write(RT_INTRSTATUS, 2, RT_ACKW);
- lapic_eoi();
+ return 1; /* handled */
}
static int
-rtl8139_irq_init(void)
+rt81xx_irq_init(void)
{
- int vec;
+ struct intr_hand ih;
- vec = intr_alloc_vector("rt8139", IPL_BIO);
- if (vec < 0) {
- return vec;
+ ih.func = rt81xx_intr;
+ ih.priority = IPL_BIO;
+ ih.irq = dev->irq_line;
+ if (intr_register("rt81xx", &ih) == NULL) {
+ return -EIO;
}
-
- /* Map interrupt vector to IRQ */
- idt_set_desc(vec, IDT_INT_GATE, ISR(rt8139_pin_irq), 0);
- ioapic_set_vec(dev->irq_line, vec);
- ioapic_irq_unmask(dev->irq_line);
return 0;
}
-#else
-#define rtl8139_irq_init(...) -ENOTSUP
-#endif
static void
rt_init_pci(void)
@@ -218,6 +293,11 @@ rt_init_pci(void)
static int
rt_init_mac(void)
{
+ struct netif_addr *addr = &netif.addr;
+ uint8_t conf;
+ uint32_t tmp;
+ int error;
+
/*
* First step is ensuring the MAC is in known
* and consistent state by resetting it. God
@@ -226,7 +306,52 @@ rt_init_mac(void)
ioport = dev->bar[0] & ~1;
pr_trace("resetting MAC...\n");
rt_write(RT_CHIPCMD, 1, RT_RST);
- rt_poll(RT_CHIPCMD, 1, RT_RST, 0);
+ error = rt_poll(RT_CHIPCMD, 1, RT_RST, 0);
+ if (error < 0) {
+ pr_error("RTL8139 reset timeout\n");
+ return error;
+ }
+
+ /*
+ * Tell the RTL8139 to load config data from
+ * the 93C46. This is done by clearing EEM1
+ * and setting EEM0. This whole process should
+ * take roughly 2 milliseconds.
+ *
+ * XXX: EEPROM autoloads *should* happen during a hardware
+ * reset but some cards might not follow spec so force
+ * it.
+ */
+ conf = rt_read(RT_CFG9346, 1);
+ conf &= ~RT_EEM1;
+ conf |= RT_EEM0;
+ rt_write(RT_CFG9346, 1, conf);
+
+ /* MAC address dword 0 */
+ tmp = rt_read(RT_IDR0, 4);
+ addr->data[0] = tmp & 0xFF;
+ addr->data[1] = (tmp >> 8) & 0xFF;
+ addr->data[2] = (tmp >> 16) & 0xFF;
+ addr->data[3] = (tmp >> 24) & 0xFF;
+
+ /* MAC address word 1 */
+ tmp = rt_read(RT_IDR2, 4);
+ addr->data[4] = (tmp >> 16) & 0xFF;
+ addr->data[5] = (tmp >> 24) & 0xFF;
+
+ pr_trace("MAC address: %x:%x:%x:%x:%x:%x\n",
+ (uint64_t)addr->data[0], (uint64_t)addr->data[1],
+ (uint64_t)addr->data[2], (uint64_t)addr->data[3],
+ (uint64_t)addr->data[4], (uint64_t)addr->data[5]);
+
+ /*
+ * Alright, now we don't want those EEM bits
+ * sticking lopsided so lets put the RTL8139
+ * back into normal operation...
+ */
+ conf = rt_read(RT_CFG9346, 1);
+ conf &= ~(RT_EEM1 | RT_EEM0);
+ rt_write(RT_CFG9346, 1, conf);
rxbuf = vm_alloc_frame(RX_BUF_SIZE);
txbuf = vm_alloc_frame(RX_BUF_SIZE);
@@ -241,6 +366,11 @@ rt_init_mac(void)
return -ENOMEM;
}
+ memcpy(netif.name, IFNAME, strlen(IFNAME) + 1);
+ netif.tx_enq = rt81xx_tx_enq;
+ netif.tx_start = rt81xx_tx_start;
+ netif_add(&netif);
+
/*
* Configure the chip:
*
@@ -258,19 +388,17 @@ rt_init_mac(void)
* - Enable interrupts through ROK/TOK
* - Enable RX state machines
*
- * TODO: Support TX
- *
*/
- rtl8139_irq_init();
+ rt81xx_irq_init();
rt_write(RT_RXBUF, 4, rxbuf);
rt_write(RT_RXCONFIG, 4, RT_AB | RT_AM | RT_APM | RT_AAP);
rt_write(RT_INTRMASK, 2, RT_ROK | RT_TOK);
- rt_write(RT_CHIPCMD, 1, RT_RE);
+ rt_write(RT_CHIPCMD, 1, RT_RE | RT_TE);
return 0;
}
static int
-rt813l_init(void)
+rt81xx_init(void)
{
struct pci_lookup lookup;
@@ -312,4 +440,4 @@ rt813l_init(void)
return rt_init_mac();
}
-DRIVER_EXPORT(rt813l_init);
+DRIVER_DEFER(rt81xx_init);