|
@@ -196,6 +196,31 @@ static const char *imx_eth_reg_name(IMXFECState *s, uint32_t index)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Versions of this device with more than one TX descriptor save the
|
|
|
+ * 2nd and 3rd descriptors in a subsection, to maintain migration
|
|
|
+ * compatibility with previous versions of the device that only
|
|
|
+ * supported a single descriptor.
|
|
|
+ */
|
|
|
+static bool imx_eth_is_multi_tx_ring(void *opaque)
|
|
|
+{
|
|
|
+ IMXFECState *s = IMX_FEC(opaque);
|
|
|
+
|
|
|
+ return s->tx_ring_num > 1;
|
|
|
+}
|
|
|
+
|
|
|
+static const VMStateDescription vmstate_imx_eth_txdescs = {
|
|
|
+ .name = "imx.fec/txdescs",
|
|
|
+ .version_id = 1,
|
|
|
+ .minimum_version_id = 1,
|
|
|
+ .needed = imx_eth_is_multi_tx_ring,
|
|
|
+ .fields = (VMStateField[]) {
|
|
|
+ VMSTATE_UINT32(tx_descriptor[1], IMXFECState),
|
|
|
+ VMSTATE_UINT32(tx_descriptor[2], IMXFECState),
|
|
|
+ VMSTATE_END_OF_LIST()
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
static const VMStateDescription vmstate_imx_eth = {
|
|
|
.name = TYPE_IMX_FEC,
|
|
|
.version_id = 2,
|
|
@@ -203,15 +228,18 @@ static const VMStateDescription vmstate_imx_eth = {
|
|
|
.fields = (VMStateField[]) {
|
|
|
VMSTATE_UINT32_ARRAY(regs, IMXFECState, ENET_MAX),
|
|
|
VMSTATE_UINT32(rx_descriptor, IMXFECState),
|
|
|
- VMSTATE_UINT32(tx_descriptor, IMXFECState),
|
|
|
-
|
|
|
+ VMSTATE_UINT32(tx_descriptor[0], IMXFECState),
|
|
|
VMSTATE_UINT32(phy_status, IMXFECState),
|
|
|
VMSTATE_UINT32(phy_control, IMXFECState),
|
|
|
VMSTATE_UINT32(phy_advertise, IMXFECState),
|
|
|
VMSTATE_UINT32(phy_int, IMXFECState),
|
|
|
VMSTATE_UINT32(phy_int_mask, IMXFECState),
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
- }
|
|
|
+ },
|
|
|
+ .subsections = (const VMStateDescription * []) {
|
|
|
+ &vmstate_imx_eth_txdescs,
|
|
|
+ NULL
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
#define PHY_INT_ENERGYON (1 << 7)
|
|
@@ -405,9 +433,8 @@ static void imx_eth_update(IMXFECState *s)
|
|
|
static void imx_fec_do_tx(IMXFECState *s)
|
|
|
{
|
|
|
int frame_size = 0, descnt = 0;
|
|
|
- uint8_t frame[ENET_MAX_FRAME_SIZE];
|
|
|
- uint8_t *ptr = frame;
|
|
|
- uint32_t addr = s->tx_descriptor;
|
|
|
+ uint8_t *ptr = s->frame;
|
|
|
+ uint32_t addr = s->tx_descriptor[0];
|
|
|
|
|
|
while (descnt++ < IMX_MAX_DESC) {
|
|
|
IMXFECBufDesc bd;
|
|
@@ -431,8 +458,8 @@ static void imx_fec_do_tx(IMXFECState *s)
|
|
|
frame_size += len;
|
|
|
if (bd.flags & ENET_BD_L) {
|
|
|
/* Last buffer in frame. */
|
|
|
- qemu_send_packet(qemu_get_queue(s->nic), frame, frame_size);
|
|
|
- ptr = frame;
|
|
|
+ qemu_send_packet(qemu_get_queue(s->nic), s->frame, frame_size);
|
|
|
+ ptr = s->frame;
|
|
|
frame_size = 0;
|
|
|
s->regs[ENET_EIR] |= ENET_INT_TXF;
|
|
|
}
|
|
@@ -448,17 +475,47 @@ static void imx_fec_do_tx(IMXFECState *s)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- s->tx_descriptor = addr;
|
|
|
+ s->tx_descriptor[0] = addr;
|
|
|
|
|
|
imx_eth_update(s);
|
|
|
}
|
|
|
|
|
|
-static void imx_enet_do_tx(IMXFECState *s)
|
|
|
+static void imx_enet_do_tx(IMXFECState *s, uint32_t index)
|
|
|
{
|
|
|
int frame_size = 0, descnt = 0;
|
|
|
- uint8_t frame[ENET_MAX_FRAME_SIZE];
|
|
|
- uint8_t *ptr = frame;
|
|
|
- uint32_t addr = s->tx_descriptor;
|
|
|
+
|
|
|
+ uint8_t *ptr = s->frame;
|
|
|
+ uint32_t addr, int_txb, int_txf, tdsr;
|
|
|
+ size_t ring;
|
|
|
+
|
|
|
+ switch (index) {
|
|
|
+ case ENET_TDAR:
|
|
|
+ ring = 0;
|
|
|
+ int_txb = ENET_INT_TXB;
|
|
|
+ int_txf = ENET_INT_TXF;
|
|
|
+ tdsr = ENET_TDSR;
|
|
|
+ break;
|
|
|
+ case ENET_TDAR1:
|
|
|
+ ring = 1;
|
|
|
+ int_txb = ENET_INT_TXB1;
|
|
|
+ int_txf = ENET_INT_TXF1;
|
|
|
+ tdsr = ENET_TDSR1;
|
|
|
+ break;
|
|
|
+ case ENET_TDAR2:
|
|
|
+ ring = 2;
|
|
|
+ int_txb = ENET_INT_TXB2;
|
|
|
+ int_txf = ENET_INT_TXF2;
|
|
|
+ tdsr = ENET_TDSR2;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
+ "%s: bogus value for index %x\n",
|
|
|
+ __func__, index);
|
|
|
+ abort();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ addr = s->tx_descriptor[ring];
|
|
|
|
|
|
while (descnt++ < IMX_MAX_DESC) {
|
|
|
IMXENETBufDesc bd;
|
|
@@ -482,13 +539,13 @@ static void imx_enet_do_tx(IMXFECState *s)
|
|
|
frame_size += len;
|
|
|
if (bd.flags & ENET_BD_L) {
|
|
|
if (bd.option & ENET_BD_PINS) {
|
|
|
- struct ip_header *ip_hd = PKT_GET_IP_HDR(frame);
|
|
|
+ struct ip_header *ip_hd = PKT_GET_IP_HDR(s->frame);
|
|
|
if (IP_HEADER_VERSION(ip_hd) == 4) {
|
|
|
- net_checksum_calculate(frame, frame_size);
|
|
|
+ net_checksum_calculate(s->frame, frame_size);
|
|
|
}
|
|
|
}
|
|
|
if (bd.option & ENET_BD_IINS) {
|
|
|
- struct ip_header *ip_hd = PKT_GET_IP_HDR(frame);
|
|
|
+ struct ip_header *ip_hd = PKT_GET_IP_HDR(s->frame);
|
|
|
/* We compute checksum only for IPv4 frames */
|
|
|
if (IP_HEADER_VERSION(ip_hd) == 4) {
|
|
|
uint16_t csum;
|
|
@@ -498,57 +555,59 @@ static void imx_enet_do_tx(IMXFECState *s)
|
|
|
}
|
|
|
}
|
|
|
/* Last buffer in frame. */
|
|
|
- qemu_send_packet(qemu_get_queue(s->nic), frame, len);
|
|
|
- ptr = frame;
|
|
|
+
|
|
|
+ qemu_send_packet(qemu_get_queue(s->nic), s->frame, frame_size);
|
|
|
+ ptr = s->frame;
|
|
|
+
|
|
|
frame_size = 0;
|
|
|
if (bd.option & ENET_BD_TX_INT) {
|
|
|
- s->regs[ENET_EIR] |= ENET_INT_TXF;
|
|
|
+ s->regs[ENET_EIR] |= int_txf;
|
|
|
}
|
|
|
}
|
|
|
if (bd.option & ENET_BD_TX_INT) {
|
|
|
- s->regs[ENET_EIR] |= ENET_INT_TXB;
|
|
|
+ s->regs[ENET_EIR] |= int_txb;
|
|
|
}
|
|
|
bd.flags &= ~ENET_BD_R;
|
|
|
/* Write back the modified descriptor. */
|
|
|
imx_enet_write_bd(&bd, addr);
|
|
|
/* Advance to the next descriptor. */
|
|
|
if ((bd.flags & ENET_BD_W) != 0) {
|
|
|
- addr = s->regs[ENET_TDSR];
|
|
|
+ addr = s->regs[tdsr];
|
|
|
} else {
|
|
|
addr += sizeof(bd);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- s->tx_descriptor = addr;
|
|
|
+ s->tx_descriptor[ring] = addr;
|
|
|
|
|
|
imx_eth_update(s);
|
|
|
}
|
|
|
|
|
|
-static void imx_eth_do_tx(IMXFECState *s)
|
|
|
+static void imx_eth_do_tx(IMXFECState *s, uint32_t index)
|
|
|
{
|
|
|
if (!s->is_fec && (s->regs[ENET_ECR] & ENET_ECR_EN1588)) {
|
|
|
- imx_enet_do_tx(s);
|
|
|
+ imx_enet_do_tx(s, index);
|
|
|
} else {
|
|
|
imx_fec_do_tx(s);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void imx_eth_enable_rx(IMXFECState *s)
|
|
|
+static void imx_eth_enable_rx(IMXFECState *s, bool flush)
|
|
|
{
|
|
|
IMXFECBufDesc bd;
|
|
|
- bool tmp;
|
|
|
+ bool rx_ring_full;
|
|
|
|
|
|
imx_fec_read_bd(&bd, s->rx_descriptor);
|
|
|
|
|
|
- tmp = ((bd.flags & ENET_BD_E) != 0);
|
|
|
+ rx_ring_full = !(bd.flags & ENET_BD_E);
|
|
|
|
|
|
- if (!tmp) {
|
|
|
+ if (rx_ring_full) {
|
|
|
FEC_PRINTF("RX buffer full\n");
|
|
|
- } else if (!s->regs[ENET_RDAR]) {
|
|
|
+ } else if (flush) {
|
|
|
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
|
|
}
|
|
|
|
|
|
- s->regs[ENET_RDAR] = tmp ? ENET_RDAR_RDAR : 0;
|
|
|
+ s->regs[ENET_RDAR] = rx_ring_full ? 0 : ENET_RDAR_RDAR;
|
|
|
}
|
|
|
|
|
|
static void imx_eth_reset(DeviceState *d)
|
|
@@ -585,7 +644,7 @@ static void imx_eth_reset(DeviceState *d)
|
|
|
}
|
|
|
|
|
|
s->rx_descriptor = 0;
|
|
|
- s->tx_descriptor = 0;
|
|
|
+ memset(s->tx_descriptor, 0, sizeof(s->tx_descriptor));
|
|
|
|
|
|
/* We also reset the PHY */
|
|
|
phy_reset(s);
|
|
@@ -791,6 +850,7 @@ static void imx_eth_write(void *opaque, hwaddr offset, uint64_t value,
|
|
|
unsigned size)
|
|
|
{
|
|
|
IMXFECState *s = IMX_FEC(opaque);
|
|
|
+ const bool single_tx_ring = !imx_eth_is_multi_tx_ring(s);
|
|
|
uint32_t index = offset >> 2;
|
|
|
|
|
|
FEC_PRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx_eth_reg_name(s, index),
|
|
@@ -807,16 +867,24 @@ static void imx_eth_write(void *opaque, hwaddr offset, uint64_t value,
|
|
|
if (s->regs[ENET_ECR] & ENET_ECR_ETHEREN) {
|
|
|
if (!s->regs[index]) {
|
|
|
s->regs[index] = ENET_RDAR_RDAR;
|
|
|
- imx_eth_enable_rx(s);
|
|
|
+ imx_eth_enable_rx(s, true);
|
|
|
}
|
|
|
} else {
|
|
|
s->regs[index] = 0;
|
|
|
}
|
|
|
break;
|
|
|
- case ENET_TDAR:
|
|
|
+ case ENET_TDAR1: /* FALLTHROUGH */
|
|
|
+ case ENET_TDAR2: /* FALLTHROUGH */
|
|
|
+ if (unlikely(single_tx_ring)) {
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
+ "[%s]%s: trying to access TDAR2 or TDAR1\n",
|
|
|
+ TYPE_IMX_FEC, __func__);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ case ENET_TDAR: /* FALLTHROUGH */
|
|
|
if (s->regs[ENET_ECR] & ENET_ECR_ETHEREN) {
|
|
|
s->regs[index] = ENET_TDAR_TDAR;
|
|
|
- imx_eth_do_tx(s);
|
|
|
+ imx_eth_do_tx(s, index);
|
|
|
}
|
|
|
s->regs[index] = 0;
|
|
|
break;
|
|
@@ -828,8 +896,12 @@ static void imx_eth_write(void *opaque, hwaddr offset, uint64_t value,
|
|
|
if ((s->regs[index] & ENET_ECR_ETHEREN) == 0) {
|
|
|
s->regs[ENET_RDAR] = 0;
|
|
|
s->rx_descriptor = s->regs[ENET_RDSR];
|
|
|
- s->regs[ENET_TDAR] = 0;
|
|
|
- s->tx_descriptor = s->regs[ENET_TDSR];
|
|
|
+ s->regs[ENET_TDAR] = 0;
|
|
|
+ s->regs[ENET_TDAR1] = 0;
|
|
|
+ s->regs[ENET_TDAR2] = 0;
|
|
|
+ s->tx_descriptor[0] = s->regs[ENET_TDSR];
|
|
|
+ s->tx_descriptor[1] = s->regs[ENET_TDSR1];
|
|
|
+ s->tx_descriptor[2] = s->regs[ENET_TDSR2];
|
|
|
}
|
|
|
break;
|
|
|
case ENET_MMFR:
|
|
@@ -907,7 +979,29 @@ static void imx_eth_write(void *opaque, hwaddr offset, uint64_t value,
|
|
|
} else {
|
|
|
s->regs[index] = value & ~7;
|
|
|
}
|
|
|
- s->tx_descriptor = s->regs[index];
|
|
|
+ s->tx_descriptor[0] = s->regs[index];
|
|
|
+ break;
|
|
|
+ case ENET_TDSR1:
|
|
|
+ if (unlikely(single_tx_ring)) {
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
+ "[%s]%s: trying to access TDSR1\n",
|
|
|
+ TYPE_IMX_FEC, __func__);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ s->regs[index] = value & ~7;
|
|
|
+ s->tx_descriptor[1] = s->regs[index];
|
|
|
+ break;
|
|
|
+ case ENET_TDSR2:
|
|
|
+ if (unlikely(single_tx_ring)) {
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
+ "[%s]%s: trying to access TDSR2\n",
|
|
|
+ TYPE_IMX_FEC, __func__);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ s->regs[index] = value & ~7;
|
|
|
+ s->tx_descriptor[2] = s->regs[index];
|
|
|
break;
|
|
|
case ENET_MRBR:
|
|
|
s->regs[index] = value & 0x00003ff0;
|
|
@@ -930,7 +1024,7 @@ static int imx_eth_can_receive(NetClientState *nc)
|
|
|
|
|
|
FEC_PRINTF("\n");
|
|
|
|
|
|
- return s->regs[ENET_RDAR] ? 1 : 0;
|
|
|
+ return !!s->regs[ENET_RDAR];
|
|
|
}
|
|
|
|
|
|
static ssize_t imx_fec_receive(NetClientState *nc, const uint8_t *buf,
|
|
@@ -1020,7 +1114,7 @@ static ssize_t imx_fec_receive(NetClientState *nc, const uint8_t *buf,
|
|
|
}
|
|
|
}
|
|
|
s->rx_descriptor = addr;
|
|
|
- imx_eth_enable_rx(s);
|
|
|
+ imx_eth_enable_rx(s, false);
|
|
|
imx_eth_update(s);
|
|
|
return len;
|
|
|
}
|
|
@@ -1037,6 +1131,7 @@ static ssize_t imx_enet_receive(NetClientState *nc, const uint8_t *buf,
|
|
|
uint8_t *crc_ptr;
|
|
|
unsigned int buf_len;
|
|
|
size_t size = len;
|
|
|
+ bool shift16 = s->regs[ENET_RACC] & ENET_RACC_SHIFT16;
|
|
|
|
|
|
FEC_PRINTF("len %d\n", (int)size);
|
|
|
|
|
@@ -1051,9 +1146,13 @@ static ssize_t imx_enet_receive(NetClientState *nc, const uint8_t *buf,
|
|
|
crc = cpu_to_be32(crc32(~0, buf, size));
|
|
|
crc_ptr = (uint8_t *) &crc;
|
|
|
|
|
|
- /* Huge frames are truncted. */
|
|
|
- if (size > ENET_MAX_FRAME_SIZE) {
|
|
|
- size = ENET_MAX_FRAME_SIZE;
|
|
|
+ if (shift16) {
|
|
|
+ size += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Huge frames are truncated. */
|
|
|
+ if (size > s->regs[ENET_FTRL]) {
|
|
|
+ size = s->regs[ENET_FTRL];
|
|
|
flags |= ENET_BD_TR | ENET_BD_LG;
|
|
|
}
|
|
|
|
|
@@ -1076,7 +1175,7 @@ static ssize_t imx_enet_receive(NetClientState *nc, const uint8_t *buf,
|
|
|
TYPE_IMX_FEC, __func__);
|
|
|
break;
|
|
|
}
|
|
|
- buf_len = (size <= s->regs[ENET_MRBR]) ? size : s->regs[ENET_MRBR];
|
|
|
+ buf_len = MIN(size, s->regs[ENET_MRBR]);
|
|
|
bd.length = buf_len;
|
|
|
size -= buf_len;
|
|
|
|
|
@@ -1087,6 +1186,24 @@ static ssize_t imx_enet_receive(NetClientState *nc, const uint8_t *buf,
|
|
|
buf_len += size - 4;
|
|
|
}
|
|
|
buf_addr = bd.data;
|
|
|
+
|
|
|
+ if (shift16) {
|
|
|
+ /*
|
|
|
+ * If SHIFT16 bit of ENETx_RACC register is set we need to
|
|
|
+ * align the payload to 4-byte boundary.
|
|
|
+ */
|
|
|
+ const uint8_t zeros[2] = { 0 };
|
|
|
+
|
|
|
+ dma_memory_write(&address_space_memory, buf_addr,
|
|
|
+ zeros, sizeof(zeros));
|
|
|
+
|
|
|
+ buf_addr += sizeof(zeros);
|
|
|
+ buf_len -= sizeof(zeros);
|
|
|
+
|
|
|
+ /* We only do this once per Ethernet frame */
|
|
|
+ shift16 = false;
|
|
|
+ }
|
|
|
+
|
|
|
dma_memory_write(&address_space_memory, buf_addr, buf, buf_len);
|
|
|
buf += buf_len;
|
|
|
if (size < 4) {
|
|
@@ -1116,7 +1233,7 @@ static ssize_t imx_enet_receive(NetClientState *nc, const uint8_t *buf,
|
|
|
}
|
|
|
}
|
|
|
s->rx_descriptor = addr;
|
|
|
- imx_eth_enable_rx(s);
|
|
|
+ imx_eth_enable_rx(s, false);
|
|
|
imx_eth_update(s);
|
|
|
return len;
|
|
|
}
|
|
@@ -1164,15 +1281,13 @@ static void imx_eth_realize(DeviceState *dev, Error **errp)
|
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
|
|
|
|
memory_region_init_io(&s->iomem, OBJECT(dev), &imx_eth_ops, s,
|
|
|
- TYPE_IMX_FEC, 0x400);
|
|
|
+ TYPE_IMX_FEC, FSL_IMX25_FEC_SIZE);
|
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
|
sysbus_init_irq(sbd, &s->irq[0]);
|
|
|
sysbus_init_irq(sbd, &s->irq[1]);
|
|
|
|
|
|
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
|
|
|
|
|
- s->conf.peers.ncs[0] = nd_table[0].netdev;
|
|
|
-
|
|
|
s->nic = qemu_new_nic(&imx_eth_net_info, &s->conf,
|
|
|
object_get_typename(OBJECT(dev)),
|
|
|
DEVICE(dev)->id, s);
|
|
@@ -1182,6 +1297,7 @@ static void imx_eth_realize(DeviceState *dev, Error **errp)
|
|
|
|
|
|
static Property imx_eth_properties[] = {
|
|
|
DEFINE_NIC_PROPERTIES(IMXFECState, conf),
|
|
|
+ DEFINE_PROP_UINT32("tx-ring-num", IMXFECState, tx_ring_num, 1),
|
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
|
};
|
|
|
|