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.do...@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.do...@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.do...@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