@@ -251,6 +251,21 @@ static bool bcm2838_genet_tdma_ring_active(BCM2838GenetState *s,
return active;
}
+static bool bcm2838_genet_rdma_ring_active(BCM2838GenetState *s,
+ unsigned int ring_index)
+{
+ uint32_t ring_mask = 1 << ring_index;
+ bool dma_en = s->regs.rdma.ctrl.fields.en == 1;
+ bool ring_en = (s->regs.rdma.ring_cfg.fields.en & ring_mask) != 0;
+ bool ring_buf_en = (s->regs.rdma.ctrl.fields.ring_buf_en & ring_mask) != 0;
+ bool active = dma_en && ring_en && ring_buf_en;
+
+ trace_bcm2838_genet_rx_dma_ring_active(ring_index,
+ active ? "active" : "halted");
+
+ return active;
+}
+
static void bcm2838_genet_tdma(BCM2838GenetState *s, hwaddr offset,
uint64_t value)
{
@@ -434,9 +449,212 @@ static const MemoryRegionOps bcm2838_genet_ops = {
.valid = {.min_access_size = sizeof(uint32_t)},
};
+static int32_t bcm2838_genet_filter(BCM2838GenetState *s, const void *buf,
+ size_t size)
+{
+ qemu_log_mask(LOG_UNIMP,
+ "Packet filtration with HFB isn't implemented yet");
+ return -1;
+}
+
+static int32_t bcm2838_genet_filter2ring(BCM2838GenetState *s,
+ uint32_t filter_idx)
+{
+ qemu_log_mask(LOG_UNIMP,
+ "Packet filtration with HFB isn't implemented yet");
+ return -1;
+}
+
+static bool is_packet_broadcast(const uint8_t *buf, size_t size)
+{
+ static const uint8_t bcst_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+ if (size < sizeof(bcst_addr)) {
+ return false;
+ }
+
+ return !memcmp(buf, bcst_addr, sizeof(bcst_addr));
+}
+
+static bool is_packet_multicast(const uint8_t *buf, size_t size)
+{
+ return !!(buf[0] & 0x01);
+}
+
+static ssize_t bcm2838_genet_rdma(BCM2838GenetState *s, uint32_t ring_idx,
+ const void *buf, size_t size)
+{
+ const size_t DESC_WORD_SIZE =
+ sizeof(BCM2838GenetRdmaDesc) / sizeof(uint32_t);
+
+ ssize_t len = 0;
+ BCM2838GenetRegsRdma *rdma = &s->regs.rdma;
+ BCM2838GenetRdmaRing *ring = &rdma->rings[ring_idx];
+ hwaddr write_index =
+ (ring->write_ptr + ((hwaddr)ring->write_ptr_hi << 32)) / DESC_WORD_SIZE;
+ BCM2838GenetRdmaDesc *desc = &rdma->descs[write_index];
+
+ const hwaddr START_INDEX =
+ (ring->start_addr + ((hwaddr)ring->start_addr_hi << 32))
+ / DESC_WORD_SIZE;
+ const hwaddr END_INDEX =
+ (ring->end_addr + ((hwaddr)ring->end_addr_hi << 32)) / DESC_WORD_SIZE;
+
+ if (!bcm2838_genet_rdma_ring_active(s, ring_idx)) {
+ return -1;
+ }
+
+ desc->length_status.fields.sop = 1;
+
+ while (len < size) {
+ size_t l = size - len;
+ size_t buf_size = ring->ring_buf_size & 0xffff;
+ uint8_t *dma_buf = s->rx_packet;
+ hwaddr dma_buf_addr =
+ desc->address_lo + ((hwaddr)desc->address_hi << 32);
+ MemTxResult mem_tx_result = MEMTX_OK;
+ uint8_t *frame_buf = dma_buf + sizeof(BCM2838GenetXmitStatus) + 2;
+ BCM2838GenetXmitStatus *xmit_status = (BCM2838GenetXmitStatus *)dma_buf;
+ struct iovec iov;
+ bool isip4, isip6;
+ size_t l3hdr_off, l4hdr_off, l5hdr_off;
+ eth_ip6_hdr_info ip6hdr_info;
+ eth_ip4_hdr_info ip4hdr_info;
+ eth_l4_hdr_info l4hdr_info;
+
+ if (l > ring->ring_buf_size) {
+ l = ring->ring_buf_size;
+ }
+
+ memcpy(frame_buf, buf + len, l);
+ iov.iov_base = frame_buf;
+ iov.iov_len = l;
+ eth_get_protocols(&iov, 1, 0,
+ &isip4, &isip6,
+ &l3hdr_off, &l4hdr_off, &l5hdr_off,
+ &ip6hdr_info, &ip4hdr_info, &l4hdr_info);
+
+ len += l;
+
+ desc->length_status.fields.eop = !!(len >= size);
+ desc->length_status.fields.buflength = l
+ + sizeof(BCM2838GenetXmitStatus) + 2;
+ if (s->regs.umac.cmd.fields.crc_fwd) {
+ desc->length_status.fields.buflength += 4;
+ }
+ desc->length_status.fields.broadcast =
+ !!is_packet_broadcast(frame_buf, l);
+ desc->length_status.fields.multicast =
+ !!is_packet_multicast(frame_buf, l);
+
+ xmit_status->rx_csum = 0;
+ if (isip4) {
+ xmit_status->rx_csum = ip4hdr_info.ip4_hdr.ip_sum;
+ }
+ xmit_status->length_status = desc->length_status.value;
+
+ mem_tx_result = address_space_write(&s->dma_as, dma_buf_addr,
+ MEMTXATTRS_UNSPECIFIED,
+ dma_buf, buf_size);
+ if (mem_tx_result != MEMTX_OK) {
+ desc->length_status.fields.rxerr = 1;
+ }
+
+ if (desc->length_status.fields.rxerr) {
+ break;
+ }
+
+ ++ring->prod_index.fields.index;
+ if (++write_index > END_INDEX) {
+ write_index = START_INDEX;
+ }
+ desc = &rdma->descs[write_index];
+ ring->write_ptr = write_index * DESC_WORD_SIZE;
+ ring->write_ptr_hi = ((hwaddr)write_index * DESC_WORD_SIZE) >> 32;
+ }
+
+ if (ring_idx == BCM2838_GENET_DMA_RING_DEFAULT) {
+ s->regs.intrl0.stat.fields.rxdma_mbdone = 1;
+ } else {
+ s->regs.intrl1.stat.fields.rx_intrs |= 1 << ring_idx;
+ }
+
+ return len;
+}
+
+static ssize_t bcm2838_genet_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ BCM2838GenetState *s = (BCM2838GenetState *)qemu_get_nic_opaque(nc);
+ ssize_t bytes_received = -1;
+ int32_t filter_index = -1;
+ int32_t ring_index = -1;
+
+ if (s->regs.rdma.ctrl.fields.en) {
+ filter_index = bcm2838_genet_filter(s, buf, size);
+
+ if (filter_index >= 0) {
+ ring_index = bcm2838_genet_filter2ring(s, filter_index);
+ } else {
+ ring_index = BCM2838_GENET_DMA_RING_CNT - 1;
+ }
+
+ if (size <= MAX_PACKET_SIZE) {
+ bytes_received = bcm2838_genet_rdma(s, ring_index, buf, size);
+ }
+ }
+
+ bcm2838_genet_set_irq_default(s);
+ bcm2838_genet_set_irq_prio(s);
+
+ return bytes_received;
+}
+
+static void bcm2838_genet_phy_update_link(BCM2838GenetState *s)
+{
+ bool qemu_link_down = qemu_get_queue(s->nic)->link_down != 0;
+
+ if (qemu_link_down && s->phy_regs.bmsr.fields.lstatus == 1) {
+ trace_bcm2838_genet_phy_update_link("down");
+
+ s->phy_regs.bmsr.fields.anegcomplete = 0;
+
+ s->phy_regs.bmsr.fields.lstatus = 0;
+ s->regs.intrl0.stat.fields.link_down = 1;
+ } else if (!qemu_link_down && s->phy_regs.bmsr.fields.lstatus == 0) {
+ trace_bcm2838_genet_phy_update_link("up");
+
+ /*
+ * Complete auto-negotiation (fixed link partner's abilities for now:
+ * 1Gbps with flow control)
+ */
+ s->phy_regs.stat1000.fields._1000half = 1;
+ s->phy_regs.stat1000.fields._1000full = 1;
+
+ s->phy_regs.lpa.fields.pause_cap = 1;
+ s->phy_regs.lpa.fields.pause_asym = 1;
+ s->phy_regs.lpa.fields.lpack = 1;
+
+ s->phy_regs.bmsr.fields.anegcomplete = 1;
+
+ s->phy_regs.bmsr.fields.lstatus = 1;
+ s->regs.intrl0.stat.fields.link_up = 1;
+ }
+
+ bcm2838_genet_set_irq_default(s);
+}
+static void bcm2838_genet_set_link(NetClientState *nc)
+{
+ BCM2838GenetState *s = qemu_get_nic_opaque(nc);
+
+ bcm2838_genet_phy_update_link(s);
+}
+
static NetClientInfo bcm2838_genet_client_info = {
.type = NET_CLIENT_DRIVER_NIC,
- .size = sizeof(NICState)
+ .size = sizeof(NICState),
+ .receive = bcm2838_genet_receive,
+ .link_status_changed = bcm2838_genet_set_link,
};
static void bcm2838_genet_realize(DeviceState *dev, Error **errp)
@@ -489,6 +707,8 @@ static void bcm2838_genet_phy_reset(BCM2838GenetState *s)
s->phy_aux_ctl_shd_regs.misc = 0x1E;
trace_bcm2838_genet_phy_reset("done");
+
+ bcm2838_genet_phy_update_link(s);
}
static void bcm2838_genet_reset(DeviceState *d)
@@ -726,6 +726,7 @@ struct BCM2838GenetState {
qemu_irq irq_prio;
uint8_t tx_packet[MAX_FRAME_SIZE];
+ uint8_t rx_packet[MAX_FRAME_SIZE];
};
#endif /* BCM2838_GENET_H */
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com> --- hw/net/bcm2838_genet.c | 222 ++++++++++++++++++++++++++++++++- include/hw/net/bcm2838_genet.h | 1 + 2 files changed, 222 insertions(+), 1 deletion(-)