* RFC: lowrisc_eth network model
@ 2025-05-21 16:01 Ben Dooks
2025-05-21 16:01 ` [RFC 1/3] hw/net: add lowrisc ethernet support Ben Dooks
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Ben Dooks @ 2025-05-21 16:01 UTC (permalink / raw)
To: nazar.kazakov, joseph.baker, fran.redondo, lawrence.hunter,
qemu-devel
Cc: ben.dooks
As part of the CVA6 project we're working on we did an
ethernet implementation as part of adding a machine to
emulate the FPGA build.
As part of this, we've done an MDIO bit-bang decoder
which might be useful (although we can get by with
just the ethernet MAC part)
^ permalink raw reply [flat|nested] 4+ messages in thread
* [RFC 1/3] hw/net: add lowrisc ethernet support
2025-05-21 16:01 RFC: lowrisc_eth network model Ben Dooks
@ 2025-05-21 16:01 ` Ben Dooks
2025-05-21 16:01 ` [RFC 2/3] hw/net: add bit-bang mdio decoding helper (mdio_bb) Ben Dooks
2025-05-21 16:01 ` [RFC 3/3] hw/net: lowrisc: initial mdio_bb work Ben Dooks
2 siblings, 0 replies; 4+ messages in thread
From: Ben Dooks @ 2025-05-21 16:01 UTC (permalink / raw)
To: nazar.kazakov, joseph.baker, fran.redondo, lawrence.hunter,
qemu-devel
Cc: ben.dooks
This is a model for the lowrisc_ethernet block, a simple
network driver found in the CVA6 CoreV APU.
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
---
note, squashed in "hw/net: lowrisc cleanups"
---
hw/net/Kconfig | 3 +
hw/net/lowrisc.c | 474 +++++++++++++++++++++++++++++++++++
hw/net/meson.build | 1 +
hw/net/trace-events | 10 +
include/hw/net/lowrisc_eth.h | 54 ++++
5 files changed, 542 insertions(+)
create mode 100644 hw/net/lowrisc.c
create mode 100644 include/hw/net/lowrisc_eth.h
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index 7f80218d10..790fe1ce60 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -109,6 +109,9 @@ config LASI_82596
bool
select I82596_COMMON
+config LOWRISC_ETH
+ bool
+
config SUNHME
bool
diff --git a/hw/net/lowrisc.c b/hw/net/lowrisc.c
new file mode 100644
index 0000000000..98177793e6
--- /dev/null
+++ b/hw/net/lowrisc.c
@@ -0,0 +1,474 @@
+/*
+ * QEMU LowRISC ethernet emulation
+ *
+ * Ben Dooks <ben.dooks@codethink.co.uk>
+ * Copyright (c) 2025 Codethink Ltd,
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/irq.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "net/eth.h"
+
+#include "hw/net/lowrisc_eth.h"
+#include "trace.h"
+
+/* address space is roughtly
+ * 0x0000..0x07ff - nothing here
+ * 0x8000..0x0880 - control and status registers
+ * 0x1000..0x4000 - transmission buffer(s) ?
+ * 0x4000..0x8000 - recive buffers
+ *
+ * Registers are bottom 32bits of each 64bit address, and the SRAMs for the
+ * transmit and receive buffers seem to be 64bit capable even if the code says
+ * they have 32bit data ports.
+ */
+REG32(MACLO, 0x800)
+
+REG32(MACHI, 0x808)
+ FIELD(MACHI, RX_LOOPBACK, 17, 1)
+ FIELD(MACHI, RX_ALL, 22, 1)
+ FIELD(MACHI, IRQ_EN, 23, 1)
+
+REG32(TPLR, 0x810)
+ FIELD(TPLR, FRAME_ADDR, 16, 12)
+ FIELD(TPLR, PACKET_LEN, 0, 12)
+ FIELD(TPLR, BUSY, 31, 1)
+
+REG32(TFCS, 0x0818)
+
+REG32(MDIOCTRL, 0x0820)
+ FIELD(MDIOCTRL, M_CLK, 0, 1)
+ FIELD(MDIOCTRL, M_DO, 1, 1)
+ FIELD(MDIOCTRL, M_OE, 2, 1) /* 0 = in, 1 = out */
+ FIELD(MDIOCTRL, M_DI, 3, 1)
+
+REG32(RFCS, 0x0828)
+
+REG32(RSR, 0x830)
+ FIELD(RSR, RECV_FIRST, 0, 4)
+ FIELD(RSR, RECV_NEXT, 4, 4)
+ FIELD(RSR, RECV_LAST, 8, 4)
+ FIELD(RSR, AVAIL, 12, 1)
+ FIELD(RSR, IRQ, 13, 1)
+
+REG32(RBAD, 0x0838)
+
+#define R_RPLR (0x0840) /* array of up to 16 registers */
+#define R_RPLR_END (R_RPLR + ((NR_RPLR-1) * 8))
+
+#define R_TXBUFF (0x1000)
+#define R_RXBUFF (0x4000)
+
+#define R_TXBUFF_END (R_TXBUFF + TX_BUFF_SZ - 1)
+#define R_RXBUFF_END (R_RXBUFF + RX_BUFF_SZ - 1)
+
+static uint32_t *find_register(LowriscEthState *s, hwaddr offset)
+{
+ if (offset >= R_RPLR && offset <= R_RPLR_END) {
+ offset -= R_RPLR;
+ offset /= 8;
+
+ return &s->r_rplr[offset];
+ }
+
+ switch (offset >> 2) {
+ case R_MACLO:
+ return &s->r_maclo;
+ case R_MACHI:
+ return &s->r_machi;
+ case R_MDIOCTRL:
+ return &s->r_mdioctrl;
+ case R_TPLR:
+ return &s->r_tplr;
+ case R_RFCS:
+ return &s->r_rfcs;
+ case R_RSR:
+ return &s->r_rsr;
+
+ default:
+ fprintf(stderr, "failed to find register for offset 0x%04x\n",
+ (unsigned)offset);
+ return NULL;
+ }
+
+ return NULL;
+}
+
+static void lowrisc_eth_update_irq(LowriscEthState *s)
+{
+ unsigned next, first;
+ uint32_t rsr = s->r_rsr;
+ bool set = false;
+
+ next = FIELD_EX32(rsr, RSR, RECV_NEXT);
+ first = FIELD_EX32(rsr, RSR, RECV_FIRST);
+
+ if (FIELD_EX32(s->r_machi, MACHI, IRQ_EN)) {
+ if (next != first) {
+ set = true;
+ }
+ }
+
+ /* update rsr for availability and irq-signalled state */
+ rsr = FIELD_DP32(rsr, RSR, AVAIL, (next != first) ? 1 : 0);
+ s->r_rsr = FIELD_DP32(rsr, RSR, IRQ, set ? 1 : 0);
+
+ trace_lowrisc_eth_irq(set, first, next,
+ FIELD_EX32(s->r_machi, MACHI, IRQ_EN));
+ qemu_set_irq(s->irq, set);
+}
+
+static ssize_t lowrisc_eth_receive(NetClientState *nc,
+ const uint8_t *buf, size_t size)
+{
+ LowriscEthState *s = qemu_get_nic_opaque(nc);
+ unsigned next, first, last;
+ unsigned index;
+ uint32_t rsr = s->r_rsr;
+ uint8_t *rxb;
+
+ last = FIELD_EX32(rsr, RSR, RECV_LAST);
+ next = FIELD_EX32(rsr, RSR, RECV_NEXT);
+ first = FIELD_EX32(rsr, RSR, RECV_FIRST);
+
+ trace_lowrisc_eth_rx((unsigned)size, first, next, last);
+
+ if (next == ((first + last) & 15)) {
+ /* we should not really get here, we're already full */
+ return -1;
+ }
+
+ if (is_multicast_ether_addr(buf) || is_broadcast_ether_addr(buf)) {
+ /* we're good for this packet */
+ } else if (FIELD_EX32(s->r_machi, MACHI, RX_ALL)) {
+ /* accepting everytihng, good here */
+ } else if (FIELD_EX32(s->r_machi, MACHI, RX_LOOPBACK)) {
+ /* should probably accept loopback packets...? */
+ } else {
+ /* check for destination being our MAC */
+ if (memcmp(buf, s->conf.macaddr.a, sizeof(s->conf.macaddr.a)) != 0) {
+ return size;
+ }
+ }
+
+ /* accepting the packet, work out which slot to put it in */
+ index = next & 7;
+ rxb = s->rx_buff + (index * RX_SZ);
+
+ trace_lowrisc_eth_rx_good((unsigned)size, index);
+
+ memcpy(rxb, buf, size);
+ //todo - add an actual FCS as it expects it in the rx buffer
+ s->r_rplr[index] = size + 4;
+
+ next = (next + 1) & 15;
+ s->r_rsr = FIELD_DP32(rsr, RSR, RECV_NEXT, next);
+
+ trace_lowrisc_eth_rx_upd_rsr(s->r_rsr);
+ lowrisc_eth_update_irq(s);
+
+ return size;
+}
+
+static bool lowrisc_eth_can_receive(NetClientState *nc)
+{
+ LowriscEthState *s = qemu_get_nic_opaque(nc);
+ unsigned next, first, last;
+ uint32_t rsr = s->r_rsr;
+ bool ok;
+
+ last = FIELD_EX32(rsr, RSR, RECV_LAST);
+ next = FIELD_EX32(rsr, RSR, RECV_NEXT);
+ first = FIELD_EX32(rsr, RSR, RECV_FIRST);
+ ok = next != ((first + last) & 15);
+
+ trace_lowrisc_eth_rx_check(first, next, first, ok);
+ return ok;
+}
+
+#define make_mac(__m, __b) (((uint32_t)(__m)) << (__b))
+
+static void lowrisc_eth_init_registers(LowriscEthState *s)
+{
+ const uint8_t *mac;
+
+ /* general register init */
+
+ s->r_tplr = 0;
+ s->r_rfcs = 0;
+ s->r_tplr = 0;
+ s->r_rsr = 0;
+ s->r_mdioctrl = FIELD_DP32(0x0, MDIOCTRL, M_DI, 1);
+ memset(&s->r_rplr, 0, sizeof(s->r_rplr));
+
+ /* init mac registers */
+
+ mac = &s->conf.macaddr.a[0];
+ s->r_maclo = make_mac(mac[5], 0) | make_mac(mac[4], 8) | make_mac(mac[3], 16) | make_mac(mac[2], 24);
+ s->r_machi = make_mac(mac[1], 0) | make_mac(mac[0], 8);
+
+ /* init the rx and tx buffres for now */
+ memset(&s->rx_buff, 0x44, sizeof(s->rx_buff));
+ memset(&s->tx_buff, 0x55, sizeof(s->tx_buff));
+}
+
+static void lowrisc_eth_reset(DeviceState *d)
+{
+ LowriscEthState *s = LOWRISC_ETH(d);
+
+ lowrisc_eth_init_registers(s);
+ lowrisc_eth_update_irq(s);
+}
+
+static uint64_t lowrisc_eth_read(void *opaque, hwaddr offset, unsigned size)
+{
+ LowriscEthState *s = opaque;
+ uint64_t retval = 0;
+
+ if (offset >= R_TXBUFF && offset <= R_TXBUFF_END) {
+ uint64_t *ptr = (uint64_t *)(s->tx_buff + offset - R_TXBUFF);
+ retval = *ptr;
+ } else if (offset >= R_RXBUFF && offset <= R_RXBUFF_END) {
+ uint64_t *ptr = (uint64_t *)(s->rx_buff + offset - R_RXBUFF);
+ retval = *ptr;
+ } else {
+ uint32_t *reg = find_register(s, offset);
+
+ if (reg) {
+ retval = *reg;
+ } else {
+ retval = ~(uint64_t)0x0;
+ }
+ }
+
+ /* note, there's nothing in the read path that would need updating
+ * the irq state, so no need to re-sync interrupts */
+
+ trace_lowrisc_eth_io_read(offset, retval);
+ return retval;
+}
+
+
+static void lowrisc_eth_update_mdioctrl(LowriscEthState *s, uint32_t val)
+{
+ /* since we're not implementing any sort of bit-banged MDIO, we just
+ * return the data input as high, which seems to be enough to allow
+ * the PHY link checks to work
+ */
+
+ s->r_mdioctrl = FIELD_DP32(s->r_mdioctrl, MDIOCTRL, M_DI, 1);
+}
+
+/* update tplr register, assume we're transmitting a packet */
+static void lowrisc_eth_update_tplr(LowriscEthState *s, uint32_t val)
+{
+ unsigned len = FIELD_EX32(val, TPLR, PACKET_LEN);
+
+ s->r_tplr = val | R_TPLR_BUSY_MASK;
+
+ trace_lowrisc_eth_tx(len);
+
+ if (FIELD_EX32(s->r_machi, MACHI, RX_LOOPBACK)) {
+ lowrisc_eth_receive(qemu_get_queue(s->nic), s->tx_buff, len);
+ } else {
+ qemu_send_packet(qemu_get_queue(s->nic), s->tx_buff, len);
+ }
+
+ /* clear busy as we are done now, no irq (oversight?) to be raised */
+ s->r_tplr &= ~R_TPLR_BUSY_MASK;
+}
+
+static void lowrisc_eth_update_mac(LowriscEthState *s)
+{
+ MACAddr addr;
+
+ /* if the maclo or machi registers change, then change qemu config */
+
+ addr.a[5] = s->r_maclo;
+ addr.a[4] = s->r_maclo >> 8;
+ addr.a[3] = s->r_maclo >> 16;
+ addr.a[2] = s->r_maclo >> 24;
+ addr.a[1] = s->r_machi;
+ addr.a[0] = s->r_machi >> 8;
+
+ if (memcmp(&addr, &s->conf.macaddr, sizeof(s->conf.macaddr)) != 0) {
+ s->conf.macaddr = addr;
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+ }
+}
+
+#define update_rsr(__s, __field, __val) \
+ do { __s->r_rsr= FIELD_DP32(__s->r_rsr, RSR, __field, __val); } while(0)
+
+static void lowrisc_eth_write(void *opaque, hwaddr offset, uint64_t val,
+ unsigned size)
+{
+ LowriscEthState *s = (LowriscEthState *)opaque;
+
+ trace_lowrisc_eth_io_write(offset, val);
+
+ if (offset >= R_TXBUFF && offset <= R_TXBUFF_END) {
+ uint8_t *ptr = s->tx_buff;
+
+ offset -= R_TXBUFF;
+ size = MIN(size, sizeof(s->tx_buff)-offset);
+ memcpy(ptr + offset, &val, size);
+ } else if (offset >= R_RXBUFF && offset <= R_RXBUFF_END) {
+ uint8_t *ptr = s->rx_buff;
+
+ offset -= R_RXBUFF;
+ size = MIN(size, sizeof(s->rx_buff)-offset);
+ memcpy(ptr + offset, &val, size);
+ } else {
+ uint32_t *reg = find_register(s, offset);
+
+ /* the core in cva6 may not fully check byte enables
+ * so just assume we're writing to the registers in full */
+
+ if (reg) {
+ switch (offset >> 2) {
+ case R_MACLO:
+ *reg = val;
+ lowrisc_eth_update_mac(s);
+ break;
+
+ case R_MACHI:
+ *reg = val;
+ lowrisc_eth_update_mac(s);
+ lowrisc_eth_update_irq(s);
+ break;
+
+ case R_RSR:
+ /* bits 3:0 of this write to the firstbuff field */
+ update_rsr(s, RECV_FIRST, val & 15);
+ lowrisc_eth_update_irq(s);
+ break;
+
+ case R_RFCS:
+ /* bits 3:0 of this write to the lastbuff field */
+ update_rsr(s, RECV_LAST, val & 15);
+ lowrisc_eth_update_irq(s);
+ break;
+
+ case R_TPLR:
+ *reg = val;
+ lowrisc_eth_update_tplr(s, val);
+ break;
+
+ case R_MDIOCTRL:
+ *reg = val;
+ lowrisc_eth_update_mdioctrl(s, val);
+ break;
+
+ default:
+ /* for now just assume anything else is just writable */
+ *reg = val;
+ }
+
+ return;
+ }
+ }
+}
+
+static const MemoryRegionOps lowrisc_eth_ops = {
+ .read = lowrisc_eth_read,
+ .write = lowrisc_eth_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ /* set max access size to 64bit, for any register it is only 64bit
+ * and tx/rx memory might be able to sub-write
+ */
+ .impl.max_access_size = 8,
+};
+
+static NetClientInfo net_lowrisc_eth_info = {
+ .type = NET_CLIENT_DRIVER_NIC,
+ .size = sizeof(NICState),
+ .can_receive = lowrisc_eth_can_receive,
+ .receive = lowrisc_eth_receive,
+ /* note, we do not currently have any way of signaling link status */
+};
+
+static void lowrisc_eth_realize(DeviceState *dev, Error **errp)
+{
+ LowriscEthState *s = LOWRISC_ETH(dev);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+ s->nic = qemu_new_nic(&net_lowrisc_eth_info, &s->conf,
+ object_get_typename(OBJECT(dev)), dev->id,
+ &dev->mem_reentrancy_guard, s);
+}
+
+static void lowrisc_eth_init(Object *obj)
+{
+ LowriscEthState *s = LOWRISC_ETH(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ lowrisc_eth_init_registers(s);
+ lowrisc_eth_update_irq(s);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &lowrisc_eth_ops, s,
+ "net", R_RXBUFF_END);
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+}
+
+static const VMStateDescription vmstate_lowrisc_eth = {
+ .name = "lowrisc_eth",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(r_maclo, LowriscEthState),
+ VMSTATE_UINT32(r_machi, LowriscEthState),
+ VMSTATE_UINT32(r_mdioctrl, LowriscEthState),
+ VMSTATE_UINT32(r_rfcs, LowriscEthState),
+ VMSTATE_UINT32(r_tplr, LowriscEthState),
+ VMSTATE_UINT32(r_rsr, LowriscEthState),
+ VMSTATE_UINT32_ARRAY(r_rplr, LowriscEthState, NR_RPLR),
+
+ /* might be overkill, but store rx and tx buffers */
+ VMSTATE_UINT8_ARRAY(tx_buff, LowriscEthState, TX_BUFF_SZ),
+ VMSTATE_UINT8_ARRAY(rx_buff, LowriscEthState, RX_BUFF_SZ),
+
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const Property lowrisc_eth_properties[] = {
+ DEFINE_NIC_PROPERTIES(LowriscEthState, conf),
+};
+
+static void lowrisc_eth_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = lowrisc_eth_realize;
+ device_class_set_props(dc, lowrisc_eth_properties);
+ dc->vmsd = &vmstate_lowrisc_eth;
+ device_class_set_legacy_reset(dc, lowrisc_eth_reset);
+}
+
+static const TypeInfo lowrisc_eth_info = {
+ .name = TYPE_LOWRISC_ETH,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LowriscEthState),
+ .instance_init = lowrisc_eth_init,
+ .class_init = lowrisc_eth_class_init,
+};
+
+static void lowrisc_eth_register_types(void)
+{
+ type_register_static(&lowrisc_eth_info);
+}
+
+type_init(lowrisc_eth_register_types)
diff --git a/hw/net/meson.build b/hw/net/meson.build
index e6759e26ca..79a65850fc 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -21,6 +21,7 @@ system_ss.add(when: 'CONFIG_SMC91C111', if_true: files('smc91c111.c'))
system_ss.add(when: 'CONFIG_LAN9118', if_true: files('lan9118.c'))
system_ss.add(when: 'CONFIG_LAN9118_PHY', if_true: files('lan9118_phy.c'))
system_ss.add(when: 'CONFIG_NE2000_ISA', if_true: files('ne2000-isa.c'))
+system_ss.add(when: 'CONFIG_LOWRISC_ETH', if_true: files('lowrisc.c'))
system_ss.add(when: 'CONFIG_OPENCORES_ETH', if_true: files('opencores_eth.c'))
system_ss.add(when: 'CONFIG_XGMAC', if_true: files('xgmac.c'))
system_ss.add(when: 'CONFIG_MIPSNET', if_true: files('mipsnet.c'))
diff --git a/hw/net/trace-events b/hw/net/trace-events
index 72b69c4a8b..acf1851eb4 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -20,6 +20,16 @@ lan9118_phy_reset(void) ""
lance_mem_readw(uint64_t addr, uint32_t ret) "addr=0x%"PRIx64"val=0x%04x"
lance_mem_writew(uint64_t addr, uint32_t val) "addr=0x%"PRIx64"val=0x%04x"
+# lowrisc.c
+lowrisc_eth_io_read(uint64_t addr, uint64_t val) "addr=0x%04"PRIx64" val=0x%016"PRIx64
+lowrisc_eth_io_write(uint64_t addr, uint64_t val) "addr=0x%04"PRIx64" val=0x%016"PRIx64
+lowrisc_eth_tx(unsigned length) "transmitting packet (%u bytes)"
+lowrisc_eth_irq(bool set, unsigned first, unsigned next, unsigned irqen) "set=%d first=%u next=%u irqen=%u"
+lowrisc_eth_rx(unsigned length, unsigned first, unsigned next, unsigned last) "rx length %u (%u,%u,%u)"
+lowrisc_eth_rx_good(unsigned length, unsigned index) "received %u bytes to index %u"
+lowrisc_eth_rx_check(unsigned first, unsigned next, unsigned last, bool ok) "%u,%u,%u -> %u"
+lowrisc_eth_rx_upd_rsr(uint64_t val) "RSR 0x%"PRIx64
+
# mipsnet.c
mipsnet_send(uint32_t size) "sending len=%u"
mipsnet_receive(uint32_t size) "receiving len=%u"
diff --git a/include/hw/net/lowrisc_eth.h b/include/hw/net/lowrisc_eth.h
new file mode 100644
index 0000000000..1f27d92ca8
--- /dev/null
+++ b/include/hw/net/lowrisc_eth.h
@@ -0,0 +1,54 @@
+/*
+ * QEMU LowRISC ethernet emulation
+ *
+ * Ben Dooks <ben.dooks@codethink.co.uk>
+ * Copyright (c) 2025 Codethink Ltd,
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#ifndef LOWRISC_ETH_H
+#define LOWRISC_ETH_H
+#include "qom/object.h"
+
+#define TYPE_LOWRISC_ETH "lowrisc_eth"
+OBJECT_DECLARE_SIMPLE_TYPE(LowriscEthState, LOWRISC_ETH)
+
+#include "net/net.h"
+#include "hw/sysbus.h"
+
+#define RX_SZ (2048)
+#define NR_RX_BUFFS (8)
+
+#define RX_BUFF_SZ (NR_RX_BUFFS * RX_SZ)
+#define TX_BUFF_SZ (0x1000)
+
+/* whilst the rx pointers are all 4 bit, the core only uses 3 bits for buffer index */
+#define NR_RPLR (8)
+
+struct LowriscEthState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ MemoryRegion iomem;
+ NICState *nic;
+ NICConf conf;
+ qemu_irq irq;
+
+ /* register states */
+ uint32_t r_maclo;
+ uint32_t r_machi;
+ uint32_t r_mdioctrl;
+ uint32_t r_rfcs;
+ uint32_t r_tplr;
+ uint32_t r_rsr;
+ uint32_t r_rplr[NR_RPLR];
+
+ /* packet buffers */
+ uint8_t rx_buff[RX_BUFF_SZ];
+ uint8_t tx_buff[TX_BUFF_SZ];
+};
+
+#endif /* LOWRISC_ETH_H */
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [RFC 2/3] hw/net: add bit-bang mdio decoding helper (mdio_bb)
2025-05-21 16:01 RFC: lowrisc_eth network model Ben Dooks
2025-05-21 16:01 ` [RFC 1/3] hw/net: add lowrisc ethernet support Ben Dooks
@ 2025-05-21 16:01 ` Ben Dooks
2025-05-21 16:01 ` [RFC 3/3] hw/net: lowrisc: initial mdio_bb work Ben Dooks
2 siblings, 0 replies; 4+ messages in thread
From: Ben Dooks @ 2025-05-21 16:01 UTC (permalink / raw)
To: nazar.kazakov, joseph.baker, fran.redondo, lawrence.hunter,
qemu-devel
Cc: ben.dooks
The mdio_bb allows PHY interfaces that are basically IO lines
that are twiddle by registers (although GPIO isn't supported
at this time, it would not be difficult to add)
The code processes the clock changes and issues a callback to
the driver when there is data to be transfered. It supports
both read and write, any other OP code is deemed invalid and
will abort the transfer.
todo:
- add proper vmstate helper for users
- get implementation fully tested with lowrisc_eth
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
---
notes:
- squashed in tracing and fixups
---
hw/net/Kconfig | 3 +
hw/net/mdio_bb.c | 167 +++++++++++++++++++++++++++++++++++++++
hw/net/meson.build | 1 +
hw/net/trace-events | 8 ++
include/hw/net/mdio_bb.h | 50 ++++++++++++
5 files changed, 229 insertions(+)
create mode 100644 hw/net/mdio_bb.c
create mode 100644 include/hw/net/mdio_bb.h
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index 790fe1ce60..3abee9130e 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -109,6 +109,9 @@ config LASI_82596
bool
select I82596_COMMON
+config MDIO_BB
+ bool
+
config LOWRISC_ETH
bool
diff --git a/hw/net/mdio_bb.c b/hw/net/mdio_bb.c
new file mode 100644
index 0000000000..945f32b7a8
--- /dev/null
+++ b/hw/net/mdio_bb.c
@@ -0,0 +1,167 @@
+/*
+ * QEMU MDIO bit-bang emulaiton
+ *
+ * Ben Dooks <ben.dooks@codethink.co.uk>
+ * Copyright (c) 2025 Codethink Ltd,
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/net/mdio_bb.h"
+
+#include "trace.h"
+
+void mdio_bb_init(struct mdio_bb *s)
+{
+ s->mdi = true;
+ s->mdo = true;
+ s->mdc = true;
+
+ s->opcode = 0;
+ s->bitcount = 0;
+ s->phy_reg_addr = 0;
+ s->phy_data = 0;
+ s->state = MDG_IDLE;
+}
+
+void mdio_bb_update(struct mdio_bb *s,
+ bool mdc, bool mdo)
+{
+ enum mdio_bb_state n_state = MDG_ILLEGAL;
+ bool rising = (!s->mdc && mdc);
+
+ s->mdc = mdc;
+ s->mdo = mdo;
+
+ /* work on rising edge of mdclk */
+ if (!rising)
+ return;
+
+ trace_mdio_bb_update(s->name, s->state, mdc, mdo);
+
+ switch (s->state) {
+ case MDG_IDLE:
+ /* if we get a '1' stick in idle,the pre-amble is 32 '1' bits */
+
+ if (!mdo) {
+ trace_mdio_bb_start(s->name);
+ n_state = MDG_START0;
+ } else {
+ n_state = MDG_IDLE;
+ }
+ break;
+
+ case MDG_START0:
+ if (!mdo) {
+ n_state = MDG_IDLE;
+ } else {
+ n_state = MDG_OP0;
+ };
+ break;
+
+ case MDG_OP0:
+ s->opcode = mdo << 1;
+ n_state = MDG_OP1;
+ break;
+
+ case MDG_OP1:
+ s->opcode |= mdo;
+ s->bitcount = 0;
+ s->phy_reg_addr = 0;
+
+ if (s->opcode == OP_READ || s->opcode == OP_WRITE) {
+ n_state = MDG_ADDR;
+ } else {
+ fprintf(stderr, "illegal MDIO op %02x\n", s->opcode);
+ n_state = MDG_ILLEGAL;
+ }
+ break;
+
+ case MDG_ADDR:
+ s->phy_reg_addr <<= 1;
+ s->phy_reg_addr |= mdo;
+ s->bitcount++;
+
+ if (s->bitcount == 10) {
+ n_state = MDG_TURN1;
+ } else {
+ n_state = MDG_ADDR;
+ }
+ break;
+
+ case MDG_TURN1:
+ n_state = MDG_TURN2;
+ break;
+
+ case MDG_TURN2:
+ s->bitcount = 15;
+
+ if (s->opcode == OP_READ) {
+ s->phy_data = (s->read)(s->param, s->phy_reg_addr);
+
+ trace_mdio_bb_read(s->name, s->phy_reg_addr >> 5,
+ s->phy_reg_addr & 0x1f, s->phy_data);
+ n_state = MDG_READ;
+ } else {
+ n_state = MDG_WRITE;
+ }
+ break;
+
+ case MDG_READ:
+ s->mdi = s->phy_data & (1 << s->bitcount) ? 1 : 0;
+
+ if (s->bitcount == 0) {
+ n_state = MDG_IDLE;
+ } else {
+ s->bitcount--;
+ n_state = MDG_READ;
+ }
+ break;
+
+ case MDG_WRITE:
+ /* writing data to the phy, mirror the mdi as the same as mdo in case
+ * it is being checked, otherwise collect bits and invoke the write when
+ * all the bits are received
+ */
+ s->mdi = mdo;
+
+ if (mdo) {
+ s->phy_data |= 1 << s->bitcount;
+ }
+
+ if (!s->bitcount) {
+ trace_mdio_bb_write(s->name, s->phy_reg_addr >> 5,
+ s->phy_reg_addr & 0x1f, s->phy_data);
+ (s->write)(s->param, s->phy_reg_addr, s->phy_data);
+ n_state = MDG_IDLE;
+ } else {
+ s->bitcount--;
+ n_state = MDG_WRITE;
+ }
+ break;
+
+ case MDG_ILLEGAL:
+ n_state = MDG_IDLE;
+ break;
+
+ default:
+ /* should not need a default state, but if so, illega. */
+ n_state = MDG_ILLEGAL;
+ }
+
+ if (n_state != MDG_ILLEGAL) {
+ trace_mdio_bb_new_state(s->name, s->state, n_state);
+ s->state = n_state;
+ } else {
+ /* encountered an illegal state. not much we can do here but go back
+ * into idle and hope that the reader is going to try and reset?
+ */
+
+ trace_mdio_bb_illegal_state(s->name, s->state, mdo);
+
+ fprintf(stderr, "mdio_bb: illegal next state from current %d (mdo %u)\n", s->state, mdo);
+ s->state = MDG_IDLE;
+ }
+}
diff --git a/hw/net/meson.build b/hw/net/meson.build
index 79a65850fc..fe34ee5c15 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -1,6 +1,7 @@
system_ss.add(when: 'CONFIG_DP8393X', if_true: files('dp8393x.c'))
system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen_nic.c'))
system_ss.add(when: 'CONFIG_NE2000_COMMON', if_true: files('ne2000.c'))
+system_ss.add(when: 'CONFIG_MDIO_BB', if_true: files('mdio_bb.c'))
# PCI network cards
system_ss.add(when: 'CONFIG_NE2000_PCI', if_true: files('ne2000-pci.c'))
diff --git a/hw/net/trace-events b/hw/net/trace-events
index acf1851eb4..e35324c4fd 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -312,6 +312,14 @@ igb_wrn_rx_desc_modes_not_supp(int desc_type) "Not supported descriptor type: %d
# igbvf.c
igbvf_wrn_io_addr_unknown(uint64_t addr) "IO unknown register 0x%"PRIx64
+# mdio_bb.c
+mdio_bb_update(const char *name, unsigned state, bool mdc, bool mdo) "(%s) state %u, mdc=%u mdo=%u"
+mdio_bb_start(const char *name) "(%s) starting transaction"
+mdio_bb_read(const char *name, unsigned phy, unsigned reg, unsigned data) "(%s) phy %d reg %u -> read %04x"
+mdio_bb_write(const char *name, unsigned phy, unsigned reg, unsigned data) "(%s) phy %d reg %u -> write %04x"
+mdio_bb_new_state(const char *name, unsigned prev, unsigned state) "(%s) state %u to new state %u"
+mdio_bb_illegal_state(const char *name, unsigned last, unsigned mdo) "(%s) illegal state from %u (mdo %u)"
+
# spapr_llan.c
spapr_vlan_get_rx_bd_from_pool_found(int pool, int32_t count, uint32_t rx_bufs) "pool=%d count=%"PRId32" rxbufs=%"PRIu32
spapr_vlan_get_rx_bd_from_page(int buf_ptr, uint64_t bd) "use_buf_ptr=%d bd=0x%016"PRIx64
diff --git a/include/hw/net/mdio_bb.h b/include/hw/net/mdio_bb.h
new file mode 100644
index 0000000000..d3f80267c9
--- /dev/null
+++ b/include/hw/net/mdio_bb.h
@@ -0,0 +1,50 @@
+/* MDIO GPIO based code
+ *
+ * * Ben Dooks <ben.dooks@codethink.co.uk>
+ * Copyright (c) 2025 Codethink Ltd,
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef MDIO_BB_H
+#define MDIO_BB_H
+
+ enum mdio_bb_state {
+ MDG_IDLE,
+ MDG_START0,
+ MDG_OP0,
+ MDG_OP1,
+ MDG_ADDR,
+ MDG_TURN1,
+ MDG_TURN2,
+ MDG_READ,
+ MDG_WRITE,
+ MDG_ILLEGAL,
+};
+
+#define OP_READ (2)
+#define OP_WRITE (1)
+
+struct mdio_bb {
+ const char *name;
+ void *param;
+ bool mdc, mdo, mdi;
+ unsigned opcode;
+ unsigned bitcount;
+ unsigned phy_reg_addr;
+ unsigned phy_data;
+ enum mdio_bb_state state;
+
+ /* addresses are supplied as addr[4:0] = reg, addr[10:5] = phy-addr */
+ /* read called to read from phy, so supply data to the MDIO bus */
+ unsigned (*read)(void *opaque, unsigned addr);
+ /* write called when data written to phy */
+ void (*write)(void *opaque, unsigned addr, unsigned data);
+};
+
+extern void mdio_bb_init(struct mdio_bb *bb);
+
+extern void mdio_bb_update(struct mdio_bb *s,
+ bool mdc, bool mdo);
+
+#endif /* MDIO_BB_H */
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [RFC 3/3] hw/net: lowrisc: initial mdio_bb work
2025-05-21 16:01 RFC: lowrisc_eth network model Ben Dooks
2025-05-21 16:01 ` [RFC 1/3] hw/net: add lowrisc ethernet support Ben Dooks
2025-05-21 16:01 ` [RFC 2/3] hw/net: add bit-bang mdio decoding helper (mdio_bb) Ben Dooks
@ 2025-05-21 16:01 ` Ben Dooks
2 siblings, 0 replies; 4+ messages in thread
From: Ben Dooks @ 2025-05-21 16:01 UTC (permalink / raw)
To: nazar.kazakov, joseph.baker, fran.redondo, lawrence.hunter,
qemu-devel
Cc: ben.dooks
Add initial implementation of PHY via bit-banged code.
This is to test the mdio_bb code and will need updating
for real board values.
---
hw/net/Kconfig | 1 +
hw/net/lowrisc.c | 42 +++++++++++++++++++++++++++++++-----
include/hw/net/lowrisc_eth.h | 4 ++++
3 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index 3abee9130e..0bc11e567a 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -114,6 +114,7 @@ config MDIO_BB
config LOWRISC_ETH
bool
+ select MDIO_BB
config SUNHME
bool
diff --git a/hw/net/lowrisc.c b/hw/net/lowrisc.c
index 98177793e6..d5cae83eb0 100644
--- a/hw/net/lowrisc.c
+++ b/hw/net/lowrisc.c
@@ -194,6 +194,32 @@ static bool lowrisc_eth_can_receive(NetClientState *nc)
return ok;
}
+static unsigned lowrisc_eth_phy_read(void *opaque, unsigned reg)
+{
+ unsigned phy = reg >> 5;
+
+ reg &= 0x1f;
+ if (phy == 1) {
+ switch (reg) {
+ case 0x00:
+ return 0xcafe;
+ case 0x01:
+ return 0xf00d;
+
+ default:
+ return 0xffff;
+ }
+ } else {
+ return reg;
+ }
+
+ return 0xffff;
+}
+
+static void lowrisc_eth_phy_write(void *opaque, unsigned reg, unsigned val)
+{
+}
+
#define make_mac(__m, __b) (((uint32_t)(__m)) << (__b))
static void lowrisc_eth_init_registers(LowriscEthState *s)
@@ -209,6 +235,13 @@ static void lowrisc_eth_init_registers(LowriscEthState *s)
s->r_mdioctrl = FIELD_DP32(0x0, MDIOCTRL, M_DI, 1);
memset(&s->r_rplr, 0, sizeof(s->r_rplr));
+ /* setup the mdio bus */
+ mdio_bb_init(&s->mdio_bb);
+ s->mdio_bb.name = "lowrisc_eth";
+ s->mdio_bb.param = s;
+ s->mdio_bb.read = lowrisc_eth_phy_read;
+ s->mdio_bb.write = lowrisc_eth_phy_write;
+
/* init mac registers */
mac = &s->conf.macaddr.a[0];
@@ -259,12 +292,11 @@ static uint64_t lowrisc_eth_read(void *opaque, hwaddr offset, unsigned size)
static void lowrisc_eth_update_mdioctrl(LowriscEthState *s, uint32_t val)
{
- /* since we're not implementing any sort of bit-banged MDIO, we just
- * return the data input as high, which seems to be enough to allow
- * the PHY link checks to work
- */
+ bool mdc = FIELD_EX32(val, MDIOCTRL, M_CLK);
+ bool mdo = FIELD_EX32(val, MDIOCTRL, M_DO);
- s->r_mdioctrl = FIELD_DP32(s->r_mdioctrl, MDIOCTRL, M_DI, 1);
+ mdio_bb_update(&s->mdio_bb, mdc, mdo);
+ s->r_mdioctrl = FIELD_DP32(val, MDIOCTRL, M_DI, s->mdio_bb.mdi);
}
/* update tplr register, assume we're transmitting a packet */
diff --git a/include/hw/net/lowrisc_eth.h b/include/hw/net/lowrisc_eth.h
index 1f27d92ca8..5c15549cc1 100644
--- a/include/hw/net/lowrisc_eth.h
+++ b/include/hw/net/lowrisc_eth.h
@@ -17,6 +17,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(LowriscEthState, LOWRISC_ETH)
#include "net/net.h"
#include "hw/sysbus.h"
+#include "hw/net/mdio_bb.h"
#define RX_SZ (2048)
#define NR_RX_BUFFS (8)
@@ -37,6 +38,9 @@ struct LowriscEthState {
NICConf conf;
qemu_irq irq;
+ /* the mdio bus */
+ struct mdio_bb mdio_bb;
+
/* register states */
uint32_t r_maclo;
uint32_t r_machi;
--
2.37.2.352.g3c44437643
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-05-21 16:03 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-21 16:01 RFC: lowrisc_eth network model Ben Dooks
2025-05-21 16:01 ` [RFC 1/3] hw/net: add lowrisc ethernet support Ben Dooks
2025-05-21 16:01 ` [RFC 2/3] hw/net: add bit-bang mdio decoding helper (mdio_bb) Ben Dooks
2025-05-21 16:01 ` [RFC 3/3] hw/net: lowrisc: initial mdio_bb work Ben Dooks
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).