hw/net/lan9118: Extract lan9118_phy

A very similar implementation of the same device exists in imx_fec. Prepare for
a common implementation by extracting a device model into its own files.

Some migration state has been moved into the new device model which breaks
migration compatibility for the following machines:
* smdkc210
* realview-*
* vexpress-*
* kzm
* mps2-*

While breaking migration ABI, fix the size of the MII registers to be 16 bit,
as defined by IEEE 802.3u.

Signed-off-by: Bernhard Beschow <shentey@gmail.com>
Tested-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-id: 20241102125724.532843-2-shentey@gmail.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Bernhard Beschow 2024-12-11 15:30:51 +00:00 committed by Peter Maydell
parent 97f2796a37
commit c0cf6b412e
5 changed files with 233 additions and 115 deletions

View file

@ -62,8 +62,12 @@ config VMXNET3_PCI
config SMC91C111
bool
config LAN9118_PHY
bool
config LAN9118
bool
select LAN9118_PHY
select PTIMER
config NE2000_ISA

View file

@ -16,6 +16,7 @@
#include "net/net.h"
#include "net/eth.h"
#include "hw/irq.h"
#include "hw/net/lan9118_phy.h"
#include "hw/net/lan9118.h"
#include "hw/ptimer.h"
#include "hw/qdev-properties.h"
@ -139,14 +140,6 @@ do { printf("lan9118: " fmt , ## __VA_ARGS__); } while (0)
#define MAC_CR_RXEN 0x00000004
#define MAC_CR_RESERVED 0x7f404213
#define PHY_INT_ENERGYON 0x80
#define PHY_INT_AUTONEG_COMPLETE 0x40
#define PHY_INT_FAULT 0x20
#define PHY_INT_DOWN 0x10
#define PHY_INT_AUTONEG_LP 0x08
#define PHY_INT_PARFAULT 0x04
#define PHY_INT_AUTONEG_PAGE 0x02
#define GPT_TIMER_EN 0x20000000
/*
@ -228,11 +221,8 @@ struct lan9118_state {
uint32_t mac_mii_data;
uint32_t mac_flow;
uint32_t phy_status;
uint32_t phy_control;
uint32_t phy_advertise;
uint32_t phy_int;
uint32_t phy_int_mask;
Lan9118PhyState mii;
IRQState mii_irq;
int32_t eeprom_writable;
uint8_t eeprom[128];
@ -274,8 +264,8 @@ struct lan9118_state {
static const VMStateDescription vmstate_lan9118 = {
.name = "lan9118",
.version_id = 2,
.minimum_version_id = 1,
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_PTIMER(timer, lan9118_state),
VMSTATE_UINT32(irq_cfg, lan9118_state),
@ -301,11 +291,6 @@ static const VMStateDescription vmstate_lan9118 = {
VMSTATE_UINT32(mac_mii_acc, lan9118_state),
VMSTATE_UINT32(mac_mii_data, lan9118_state),
VMSTATE_UINT32(mac_flow, lan9118_state),
VMSTATE_UINT32(phy_status, lan9118_state),
VMSTATE_UINT32(phy_control, lan9118_state),
VMSTATE_UINT32(phy_advertise, lan9118_state),
VMSTATE_UINT32(phy_int, lan9118_state),
VMSTATE_UINT32(phy_int_mask, lan9118_state),
VMSTATE_INT32(eeprom_writable, lan9118_state),
VMSTATE_UINT8_ARRAY(eeprom, lan9118_state, 128),
VMSTATE_INT32(tx_fifo_size, lan9118_state),
@ -385,9 +370,11 @@ static void lan9118_reload_eeprom(lan9118_state *s)
lan9118_mac_changed(s);
}
static void phy_update_irq(lan9118_state *s)
static void lan9118_update_irq(void *opaque, int n, int level)
{
if (s->phy_int & s->phy_int_mask) {
lan9118_state *s = opaque;
if (level) {
s->int_sts |= PHY_INT;
} else {
s->int_sts &= ~PHY_INT;
@ -395,33 +382,10 @@ static void phy_update_irq(lan9118_state *s)
lan9118_update(s);
}
static void phy_update_link(lan9118_state *s)
{
/* Autonegotiation status mirrors link status. */
if (qemu_get_queue(s->nic)->link_down) {
s->phy_status &= ~0x0024;
s->phy_int |= PHY_INT_DOWN;
} else {
s->phy_status |= 0x0024;
s->phy_int |= PHY_INT_ENERGYON;
s->phy_int |= PHY_INT_AUTONEG_COMPLETE;
}
phy_update_irq(s);
}
static void lan9118_set_link(NetClientState *nc)
{
phy_update_link(qemu_get_nic_opaque(nc));
}
static void phy_reset(lan9118_state *s)
{
s->phy_status = 0x7809;
s->phy_control = 0x3000;
s->phy_advertise = 0x01e1;
s->phy_int_mask = 0;
s->phy_int = 0;
phy_update_link(s);
lan9118_phy_update_link(&LAN9118(qemu_get_nic_opaque(nc))->mii,
nc->link_down);
}
static void lan9118_reset(DeviceState *d)
@ -478,8 +442,6 @@ static void lan9118_reset(DeviceState *d)
s->read_word_n = 0;
s->write_word_n = 0;
phy_reset(s);
s->eeprom_writable = 0;
lan9118_reload_eeprom(s);
}
@ -678,7 +640,7 @@ static void do_tx_packet(lan9118_state *s)
uint32_t status;
/* FIXME: Honor TX disable, and allow queueing of packets. */
if (s->phy_control & 0x4000) {
if (s->mii.control & 0x4000) {
/* This assumes the receive routine doesn't touch the VLANClient. */
qemu_receive_packet(qemu_get_queue(s->nic), s->txp->data, s->txp->len);
} else {
@ -834,68 +796,6 @@ static void tx_fifo_push(lan9118_state *s, uint32_t val)
}
}
static uint32_t do_phy_read(lan9118_state *s, int reg)
{
uint32_t val;
switch (reg) {
case 0: /* Basic Control */
return s->phy_control;
case 1: /* Basic Status */
return s->phy_status;
case 2: /* ID1 */
return 0x0007;
case 3: /* ID2 */
return 0xc0d1;
case 4: /* Auto-neg advertisement */
return s->phy_advertise;
case 5: /* Auto-neg Link Partner Ability */
return 0x0f71;
case 6: /* Auto-neg Expansion */
return 1;
/* TODO 17, 18, 27, 29, 30, 31 */
case 29: /* Interrupt source. */
val = s->phy_int;
s->phy_int = 0;
phy_update_irq(s);
return val;
case 30: /* Interrupt mask */
return s->phy_int_mask;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"do_phy_read: PHY read reg %d\n", reg);
return 0;
}
}
static void do_phy_write(lan9118_state *s, int reg, uint32_t val)
{
switch (reg) {
case 0: /* Basic Control */
if (val & 0x8000) {
phy_reset(s);
break;
}
s->phy_control = val & 0x7980;
/* Complete autonegotiation immediately. */
if (val & 0x1000) {
s->phy_status |= 0x0020;
}
break;
case 4: /* Auto-neg advertisement */
s->phy_advertise = (val & 0x2d7f) | 0x80;
break;
/* TODO 17, 18, 27, 31 */
case 30: /* Interrupt mask */
s->phy_int_mask = val & 0xff;
phy_update_irq(s);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"do_phy_write: PHY write reg %d = 0x%04x\n", reg, val);
}
}
static void do_mac_write(lan9118_state *s, int reg, uint32_t val)
{
switch (reg) {
@ -929,9 +829,9 @@ static void do_mac_write(lan9118_state *s, int reg, uint32_t val)
if (val & 2) {
DPRINTF("PHY write %d = 0x%04x\n",
(val >> 6) & 0x1f, s->mac_mii_data);
do_phy_write(s, (val >> 6) & 0x1f, s->mac_mii_data);
lan9118_phy_write(&s->mii, (val >> 6) & 0x1f, s->mac_mii_data);
} else {
s->mac_mii_data = do_phy_read(s, (val >> 6) & 0x1f);
s->mac_mii_data = lan9118_phy_read(&s->mii, (val >> 6) & 0x1f);
DPRINTF("PHY read %d = 0x%04x\n",
(val >> 6) & 0x1f, s->mac_mii_data);
}
@ -1126,7 +1026,7 @@ static void lan9118_writel(void *opaque, hwaddr offset,
break;
case CSR_PMT_CTRL:
if (val & 0x400) {
phy_reset(s);
lan9118_phy_reset(&s->mii);
}
s->pmt_ctrl &= ~0x34e;
s->pmt_ctrl |= (val & 0x34e);
@ -1373,6 +1273,13 @@ static void lan9118_realize(DeviceState *dev, Error **errp)
const MemoryRegionOps *mem_ops =
s->mode_16bit ? &lan9118_16bit_mem_ops : &lan9118_mem_ops;
qemu_init_irq(&s->mii_irq, lan9118_update_irq, s, 0);
object_initialize_child(OBJECT(s), "mii", &s->mii, TYPE_LAN9118_PHY);
if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(&s->mii), errp)) {
return;
}
qdev_connect_gpio_out(DEVICE(&s->mii), 0, &s->mii_irq);
memory_region_init_io(&s->mmio, OBJECT(dev), mem_ops, s,
"lan9118-mmio", 0x100);
sysbus_init_mmio(sbd, &s->mmio);

169
hw/net/lan9118_phy.c Normal file
View file

@ -0,0 +1,169 @@
/*
* SMSC LAN9118 PHY emulation
*
* Copyright (c) 2009 CodeSourcery, LLC.
* Written by Paul Brook
*
* This code is licensed under the GNU GPL v2
*
* Contributions after 2012-01-13 are licensed under the terms of the
* GNU GPL, version 2 or (at your option) any later version.
*/
#include "qemu/osdep.h"
#include "hw/net/lan9118_phy.h"
#include "hw/irq.h"
#include "hw/resettable.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#define PHY_INT_ENERGYON (1 << 7)
#define PHY_INT_AUTONEG_COMPLETE (1 << 6)
#define PHY_INT_FAULT (1 << 5)
#define PHY_INT_DOWN (1 << 4)
#define PHY_INT_AUTONEG_LP (1 << 3)
#define PHY_INT_PARFAULT (1 << 2)
#define PHY_INT_AUTONEG_PAGE (1 << 1)
static void lan9118_phy_update_irq(Lan9118PhyState *s)
{
qemu_set_irq(s->irq, !!(s->ints & s->int_mask));
}
uint16_t lan9118_phy_read(Lan9118PhyState *s, int reg)
{
uint16_t val;
switch (reg) {
case 0: /* Basic Control */
return s->control;
case 1: /* Basic Status */
return s->status;
case 2: /* ID1 */
return 0x0007;
case 3: /* ID2 */
return 0xc0d1;
case 4: /* Auto-neg advertisement */
return s->advertise;
case 5: /* Auto-neg Link Partner Ability */
return 0x0f71;
case 6: /* Auto-neg Expansion */
return 1;
/* TODO 17, 18, 27, 29, 30, 31 */
case 29: /* Interrupt source. */
val = s->ints;
s->ints = 0;
lan9118_phy_update_irq(s);
return val;
case 30: /* Interrupt mask */
return s->int_mask;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"lan9118_phy_read: PHY read reg %d\n", reg);
return 0;
}
}
void lan9118_phy_write(Lan9118PhyState *s, int reg, uint16_t val)
{
switch (reg) {
case 0: /* Basic Control */
if (val & 0x8000) {
lan9118_phy_reset(s);
break;
}
s->control = val & 0x7980;
/* Complete autonegotiation immediately. */
if (val & 0x1000) {
s->status |= 0x0020;
}
break;
case 4: /* Auto-neg advertisement */
s->advertise = (val & 0x2d7f) | 0x80;
break;
/* TODO 17, 18, 27, 31 */
case 30: /* Interrupt mask */
s->int_mask = val & 0xff;
lan9118_phy_update_irq(s);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"lan9118_phy_write: PHY write reg %d = 0x%04x\n", reg, val);
}
}
void lan9118_phy_update_link(Lan9118PhyState *s, bool link_down)
{
s->link_down = link_down;
/* Autonegotiation status mirrors link status. */
if (link_down) {
s->status &= ~0x0024;
s->ints |= PHY_INT_DOWN;
} else {
s->status |= 0x0024;
s->ints |= PHY_INT_ENERGYON;
s->ints |= PHY_INT_AUTONEG_COMPLETE;
}
lan9118_phy_update_irq(s);
}
void lan9118_phy_reset(Lan9118PhyState *s)
{
s->control = 0x3000;
s->status = 0x7809;
s->advertise = 0x01e1;
s->int_mask = 0;
s->ints = 0;
lan9118_phy_update_link(s, s->link_down);
}
static void lan9118_phy_reset_hold(Object *obj, ResetType type)
{
Lan9118PhyState *s = LAN9118_PHY(obj);
lan9118_phy_reset(s);
}
static void lan9118_phy_init(Object *obj)
{
Lan9118PhyState *s = LAN9118_PHY(obj);
qdev_init_gpio_out(DEVICE(s), &s->irq, 1);
}
static const VMStateDescription vmstate_lan9118_phy = {
.name = "lan9118-phy",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT16(control, Lan9118PhyState),
VMSTATE_UINT16(status, Lan9118PhyState),
VMSTATE_UINT16(advertise, Lan9118PhyState),
VMSTATE_UINT16(ints, Lan9118PhyState),
VMSTATE_UINT16(int_mask, Lan9118PhyState),
VMSTATE_BOOL(link_down, Lan9118PhyState),
VMSTATE_END_OF_LIST()
}
};
static void lan9118_phy_class_init(ObjectClass *klass, void *data)
{
ResettableClass *rc = RESETTABLE_CLASS(klass);
DeviceClass *dc = DEVICE_CLASS(klass);
rc->phases.hold = lan9118_phy_reset_hold;
dc->vmsd = &vmstate_lan9118_phy;
}
static const TypeInfo types[] = {
{
.name = TYPE_LAN9118_PHY,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(Lan9118PhyState),
.instance_init = lan9118_phy_init,
.class_init = lan9118_phy_class_init,
}
};
DEFINE_TYPES(types)

View file

@ -19,6 +19,7 @@ system_ss.add(when: 'CONFIG_VMXNET3_PCI', if_true: files('vmxnet3.c'))
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_OPENCORES_ETH', if_true: files('opencores_eth.c'))
system_ss.add(when: 'CONFIG_XGMAC', if_true: files('xgmac.c'))

View file

@ -0,0 +1,37 @@
/*
* SMSC LAN9118 PHY emulation
*
* Copyright (c) 2009 CodeSourcery, LLC.
* Written by Paul Brook
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#ifndef HW_NET_LAN9118_PHY_H
#define HW_NET_LAN9118_PHY_H
#include "qom/object.h"
#include "hw/sysbus.h"
#define TYPE_LAN9118_PHY "lan9118-phy"
OBJECT_DECLARE_SIMPLE_TYPE(Lan9118PhyState, LAN9118_PHY)
typedef struct Lan9118PhyState {
SysBusDevice parent_obj;
uint16_t status;
uint16_t control;
uint16_t advertise;
uint16_t ints;
uint16_t int_mask;
qemu_irq irq;
bool link_down;
} Lan9118PhyState;
void lan9118_phy_update_link(Lan9118PhyState *s, bool link_down);
void lan9118_phy_reset(Lan9118PhyState *s);
uint16_t lan9118_phy_read(Lan9118PhyState *s, int reg);
void lan9118_phy_write(Lan9118PhyState *s, int reg, uint16_t val);
#endif