2 * QEMU Intel i82596 (Apricot) emulation
4 * Copyright (c) 2019 Helge Deller <deller@gmx.de>
5 * This work is licensed under the GNU GPL license version 2 or later.
7 * This software was written to be compatible with the specification:
8 * https://www.intel.com/assets/pdf/general/82596ca.pdf
11 #include "qemu/osdep.h"
12 #include "qemu/timer.h"
16 #include "hw/qdev-properties.h"
17 #include "migration/vmstate.h"
18 #include "exec/address-spaces.h"
19 #include "qemu/module.h"
22 #include <zlib.h> /* for crc32 */
24 #if defined(ENABLE_DEBUG)
27 #define DBG(x) do { } while (0)
32 #define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m)
34 #define PKT_BUF_SZ 1536
37 #define ISCP_BUSY 0x0001
39 #define I596_NULL ((uint32_t)0xffffffff)
41 #define SCB_STATUS_CX 0x8000 /* CU finished command with I bit */
42 #define SCB_STATUS_FR 0x4000 /* RU finished receiving a frame */
43 #define SCB_STATUS_CNA 0x2000 /* CU left active state */
44 #define SCB_STATUS_RNR 0x1000 /* RU left active state */
46 #define SCB_COMMAND_ACK_MASK \
47 (SCB_STATUS_CX | SCB_STATUS_FR | SCB_STATUS_CNA | SCB_STATUS_RNR)
50 #define CU_SUSPENDED 1
54 #define RX_SUSPENDED 1
57 #define CMD_EOL 0x8000 /* The last command of the list, stop. */
58 #define CMD_SUSP 0x4000 /* Suspend after doing cmd. */
59 #define CMD_INTR 0x2000 /* Interrupt after doing cmd. */
61 #define CMD_FLEX 0x0008 /* Enable flexible memory model */
64 CmdNOp
= 0, CmdSASetup
= 1, CmdConfigure
= 2, CmdMulticastList
= 3,
65 CmdTx
= 4, CmdTDR
= 5, CmdDump
= 6, CmdDiagnose
= 7
68 #define STAT_C 0x8000 /* Set to 0 after execution */
69 #define STAT_B 0x4000 /* Command being executed */
70 #define STAT_OK 0x2000 /* Command executed ok */
71 #define STAT_A 0x1000 /* Command aborted */
73 #define I596_EOF 0x8000
74 #define SIZE_MASK 0x3fff
76 /* various flags in the chip config registers */
77 #define I596_PREFETCH (s->config[0] & 0x80)
78 #define I596_PROMISC (s->config[8] & 0x01)
79 #define I596_BC_DISABLE (s->config[8] & 0x02) /* broadcast disable */
80 #define I596_NOCRC_INS (s->config[8] & 0x08)
81 #define I596_CRCINM (s->config[11] & 0x04) /* CRC appended */
82 #define I596_MC_ALL (s->config[11] & 0x20)
83 #define I596_MULTIIA (s->config[13] & 0x40)
86 static uint8_t get_byte(uint32_t addr
)
88 return ldub_phys(&address_space_memory
, addr
);
91 static void set_byte(uint32_t addr
, uint8_t c
)
93 return stb_phys(&address_space_memory
, addr
, c
);
96 static uint16_t get_uint16(uint32_t addr
)
98 return lduw_be_phys(&address_space_memory
, addr
);
101 static void set_uint16(uint32_t addr
, uint16_t w
)
103 return stw_be_phys(&address_space_memory
, addr
, w
);
106 static uint32_t get_uint32(uint32_t addr
)
108 uint32_t lo
= lduw_be_phys(&address_space_memory
, addr
);
109 uint32_t hi
= lduw_be_phys(&address_space_memory
, addr
+ 2);
110 return (hi
<< 16) | lo
;
113 static void set_uint32(uint32_t addr
, uint32_t val
)
115 set_uint16(addr
, (uint16_t) val
);
116 set_uint16(addr
+ 2, val
>> 16);
120 struct qemu_ether_header
{
121 uint8_t ether_dhost
[6];
122 uint8_t ether_shost
[6];
126 #define PRINT_PKTHDR(txt, BUF) do { \
127 struct qemu_ether_header *hdr = (void *)(BUF); \
128 printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\
129 MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost), \
130 be16_to_cpu(hdr->ether_type)); \
133 static void i82596_transmit(I82596State
*s
, uint32_t addr
)
135 uint32_t tdb_p
; /* Transmit Buffer Descriptor */
137 /* TODO: Check flexible mode */
138 tdb_p
= get_uint32(addr
+ 8);
139 while (tdb_p
!= I596_NULL
) {
143 size
= get_uint16(tdb_p
);
144 len
= size
& SIZE_MASK
;
145 tba
= get_uint32(tdb_p
+ 8);
146 trace_i82596_transmit(len
, tba
);
149 assert(len
<= sizeof(s
->tx_buffer
));
150 address_space_read(&address_space_memory
, tba
,
151 MEMTXATTRS_UNSPECIFIED
, s
->tx_buffer
, len
);
152 DBG(PRINT_PKTHDR("Send", &s
->tx_buffer
));
153 DBG(printf("Sending %d bytes\n", len
));
154 qemu_send_packet(qemu_get_queue(s
->nic
), s
->tx_buffer
, len
);
157 /* was this the last package? */
158 if (size
& I596_EOF
) {
162 /* get next buffer pointer */
163 tdb_p
= get_uint32(tdb_p
+ 4);
167 static void set_individual_address(I82596State
*s
, uint32_t addr
)
172 nc
= qemu_get_queue(s
->nic
);
173 m
= s
->conf
.macaddr
.a
;
174 address_space_read(&address_space_memory
, addr
+ 8,
175 MEMTXATTRS_UNSPECIFIED
, m
, ETH_ALEN
);
176 qemu_format_nic_info_str(nc
, m
);
177 trace_i82596_new_mac(nc
->info_str
);
180 static void set_multicast_list(I82596State
*s
, uint32_t addr
)
182 uint16_t mc_count
, i
;
184 memset(&s
->mult
[0], 0, sizeof(s
->mult
));
185 mc_count
= get_uint16(addr
+ 8) / ETH_ALEN
;
187 if (mc_count
> MAX_MC_CNT
) {
188 mc_count
= MAX_MC_CNT
;
190 for (i
= 0; i
< mc_count
; i
++) {
191 uint8_t multicast_addr
[ETH_ALEN
];
192 address_space_read(&address_space_memory
, addr
+ i
* ETH_ALEN
,
193 MEMTXATTRS_UNSPECIFIED
, multicast_addr
, ETH_ALEN
);
194 DBG(printf("Add multicast entry " MAC_FMT
"\n",
195 MAC_ARG(multicast_addr
)));
196 unsigned mcast_idx
= (net_crc32(multicast_addr
, ETH_ALEN
) &
198 assert(mcast_idx
< 8 * sizeof(s
->mult
));
199 s
->mult
[mcast_idx
>> 3] |= (1 << (mcast_idx
& 7));
201 trace_i82596_set_multicast(mc_count
);
204 void i82596_set_link_status(NetClientState
*nc
)
206 I82596State
*d
= qemu_get_nic_opaque(nc
);
208 d
->lnkst
= nc
->link_down
? 0 : 0x8000;
211 static void update_scb_status(I82596State
*s
)
213 s
->scb_status
= (s
->scb_status
& 0xf000)
214 | (s
->cu_status
<< 8) | (s
->rx_status
<< 4);
215 set_uint16(s
->scb
, s
->scb_status
);
219 static void i82596_s_reset(I82596State
*s
)
221 trace_i82596_s_reset(s
);
224 s
->cu_status
= CU_IDLE
;
225 s
->rx_status
= RX_SUSPENDED
;
226 s
->cmd_p
= I596_NULL
;
227 s
->lnkst
= 0x8000; /* initial link state: up */
228 s
->ca
= s
->ca_active
= 0;
233 static void command_loop(I82596State
*s
)
239 DBG(printf("STARTING COMMAND LOOP cmd_p=%08x\n", s
->cmd_p
));
241 while (s
->cmd_p
!= I596_NULL
) {
244 set_uint16(s
->cmd_p
, status
);
245 status
= STAT_C
| STAT_OK
; /* update, but write later */
247 cmd
= get_uint16(s
->cmd_p
+ 2);
248 DBG(printf("Running command %04x at %08x\n", cmd
, s
->cmd_p
));
250 switch (cmd
& 0x07) {
254 set_individual_address(s
, s
->cmd_p
);
257 byte_cnt
= get_byte(s
->cmd_p
+ 8) & 0x0f;
258 byte_cnt
= MAX(byte_cnt
, 4);
259 byte_cnt
= MIN(byte_cnt
, sizeof(s
->config
));
260 /* copy byte_cnt max. */
261 address_space_read(&address_space_memory
, s
->cmd_p
+ 8,
262 MEMTXATTRS_UNSPECIFIED
, s
->config
, byte_cnt
);
263 /* config byte according to page 35ff */
264 s
->config
[2] &= 0x82; /* mask valid bits */
265 s
->config
[2] |= 0x40;
266 s
->config
[7] &= 0xf7; /* clear zero bit */
267 assert(I596_NOCRC_INS
== 0); /* do CRC insertion */
268 s
->config
[10] = MAX(s
->config
[10], 5); /* min frame length */
269 s
->config
[12] &= 0x40; /* only full duplex field valid */
270 s
->config
[13] |= 0x3f; /* set ones in byte 13 */
273 /* get signal LINK */
274 set_uint32(s
->cmd_p
+ 8, s
->lnkst
);
277 i82596_transmit(s
, s
->cmd_p
);
279 case CmdMulticastList
:
280 set_multicast_list(s
, s
->cmd_p
);
284 printf("FIXME Command %d !!\n", cmd
& 7);
285 g_assert_not_reached();
289 set_uint16(s
->cmd_p
, status
);
291 s
->cmd_p
= get_uint32(s
->cmd_p
+ 4); /* get link address */
292 DBG(printf("NEXT addr would be %08x\n", s
->cmd_p
));
294 s
->cmd_p
= I596_NULL
;
297 /* Stop when last command of the list. */
299 s
->cmd_p
= I596_NULL
;
301 /* Suspend after doing cmd? */
302 if (cmd
& CMD_SUSP
) {
303 s
->cu_status
= CU_SUSPENDED
;
304 printf("FIXME SUSPEND !!\n");
306 /* Interrupt after doing cmd? */
307 if (cmd
& CMD_INTR
) {
308 s
->scb_status
|= SCB_STATUS_CX
;
310 s
->scb_status
&= ~SCB_STATUS_CX
;
312 update_scb_status(s
);
314 /* Interrupt after doing cmd? */
315 if (cmd
& CMD_INTR
) {
319 if (s
->cu_status
!= CU_ACTIVE
) {
323 DBG(printf("FINISHED COMMAND LOOP\n"));
324 qemu_flush_queued_packets(qemu_get_queue(s
->nic
));
327 static void i82596_flush_queue_timer(void *opaque
)
329 I82596State
*s
= opaque
;
331 timer_del(s
->flush_queue_timer
);
332 qemu_flush_queued_packets(qemu_get_queue(s
->nic
));
333 timer_mod(s
->flush_queue_timer
,
334 qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL
) + 1000);
338 static void examine_scb(I82596State
*s
)
340 uint16_t command
, cuc
, ruc
;
342 /* get the scb command word */
343 command
= get_uint16(s
->scb
+ 2);
344 cuc
= (command
>> 8) & 0x7;
345 ruc
= (command
>> 4) & 0x7;
346 DBG(printf("MAIN COMMAND %04x cuc %02x ruc %02x\n", command
, cuc
, ruc
));
347 /* and clear the scb command word */
348 set_uint16(s
->scb
+ 2, 0);
350 s
->scb_status
&= ~(command
& SCB_COMMAND_ACK_MASK
);
353 case 0: /* no change */
355 case 1: /* CUC_START */
356 s
->cu_status
= CU_ACTIVE
;
358 case 4: /* CUC_ABORT */
359 s
->cu_status
= CU_SUSPENDED
;
360 s
->scb_status
|= SCB_STATUS_CNA
; /* CU left active state */
363 printf("WARNING: Unknown CUC %d!\n", cuc
);
367 case 0: /* no change */
369 case 1: /* RX_START */
370 case 2: /* RX_RESUME */
371 s
->rx_status
= RX_IDLE
;
373 timer_mod(s
->flush_queue_timer
, qemu_clock_get_ms(
374 QEMU_CLOCK_VIRTUAL
) + 1000);
377 case 3: /* RX_SUSPEND */
378 case 4: /* RX_ABORT */
379 s
->rx_status
= RX_SUSPENDED
;
380 s
->scb_status
|= SCB_STATUS_RNR
; /* RU left active state */
383 printf("WARNING: Unknown RUC %d!\n", ruc
);
386 if (command
& 0x80) { /* reset bit set? */
390 /* execute commands from SCBL */
391 if (s
->cu_status
!= CU_SUSPENDED
) {
392 if (s
->cmd_p
== I596_NULL
) {
393 s
->cmd_p
= get_uint32(s
->scb
+ 4);
397 /* update scb status */
398 update_scb_status(s
);
403 static void signal_ca(I82596State
*s
)
407 /* trace_i82596_channel_attention(s); */
409 /* CA after reset -> do init with new scp. */
410 s
->sysbus
= get_byte(s
->scp
+ 3); /* big endian */
411 DBG(printf("SYSBUS = %08x\n", s
->sysbus
));
412 if (((s
->sysbus
>> 1) & 0x03) != 2) {
413 printf("WARNING: NO LINEAR MODE !!\n");
415 if ((s
->sysbus
>> 7)) {
416 printf("WARNING: 32BIT LINMODE IN B-STEPPING NOT SUPPORTED !!\n");
418 iscp
= get_uint32(s
->scp
+ 8);
419 s
->scb
= get_uint32(iscp
+ 4);
420 set_byte(iscp
+ 1, 0); /* clear BUSY flag in iscp */
424 s
->ca
++; /* count ca() */
436 qemu_set_irq(s
->irq
, 1);
440 void i82596_ioport_writew(void *opaque
, uint32_t addr
, uint32_t val
)
442 I82596State
*s
= opaque
;
443 /* printf("i82596_ioport_writew addr=0x%08x val=0x%04x\n", addr, val); */
445 case PORT_RESET
: /* Reset */
457 uint32_t i82596_ioport_readw(void *opaque
, uint32_t addr
)
462 void i82596_h_reset(void *opaque
)
464 I82596State
*s
= opaque
;
469 bool i82596_can_receive(NetClientState
*nc
)
471 I82596State
*s
= qemu_get_nic_opaque(nc
);
473 if (s
->rx_status
== RX_SUSPENDED
) {
481 if (USE_TIMER
&& !timer_pending(s
->flush_queue_timer
)) {
488 ssize_t
i82596_receive(NetClientState
*nc
, const uint8_t *buf
, size_t sz
)
490 I82596State
*s
= qemu_get_nic_opaque(nc
);
493 uint16_t is_broadcast
= 0;
494 size_t len
= sz
; /* length of data for guest (including CRC) */
495 size_t bufsz
= sz
; /* length of data in buf */
498 static const uint8_t broadcast_macaddr
[6] = {
499 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
501 DBG(printf("i82596_receive() start\n"));
503 if (USE_TIMER
&& timer_pending(s
->flush_queue_timer
)) {
507 /* first check if receiver is enabled */
508 if (s
->rx_status
== RX_SUSPENDED
) {
509 trace_i82596_receive_analysis(">>> Receiving suspended");
514 trace_i82596_receive_analysis(">>> Link down");
518 /* Received frame smaller than configured "min frame len"? */
519 if (sz
< s
->config
[10]) {
520 printf("Received frame too small, %zu vs. %u bytes\n",
525 DBG(printf("Received %lu bytes\n", sz
));
529 /* promiscuous: receive all */
530 trace_i82596_receive_analysis(
531 ">>> packet received in promiscuous mode");
535 if (!memcmp(buf
, broadcast_macaddr
, 6)) {
536 /* broadcast address */
537 if (I596_BC_DISABLE
) {
538 trace_i82596_receive_analysis(">>> broadcast packet rejected");
543 trace_i82596_receive_analysis(">>> broadcast packet received");
546 } else if (buf
[0] & 0x01) {
549 trace_i82596_receive_analysis(">>> multicast packet rejected");
554 int mcast_idx
= (net_crc32(buf
, ETH_ALEN
) & BITS(7, 2)) >> 2;
555 assert(mcast_idx
< 8 * sizeof(s
->mult
));
557 if (!(s
->mult
[mcast_idx
>> 3] & (1 << (mcast_idx
& 7)))) {
558 trace_i82596_receive_analysis(">>> multicast address mismatch");
563 trace_i82596_receive_analysis(">>> multicast packet received");
566 } else if (!memcmp(s
->conf
.macaddr
.a
, buf
, 6)) {
569 trace_i82596_receive_analysis(
570 ">>> physical address matching packet received");
574 trace_i82596_receive_analysis(">>> unknown packet");
580 /* Calculate the ethernet checksum (4 bytes) */
582 crc
= cpu_to_be32(crc32(~0, buf
, sz
));
583 crc_ptr
= (uint8_t *) &crc
;
585 rfd_p
= get_uint32(s
->scb
+ 8); /* get Receive Frame Descriptor */
586 assert(rfd_p
&& rfd_p
!= I596_NULL
);
588 /* get first Receive Buffer Descriptor Address */
589 rbd
= get_uint32(rfd_p
+ 8);
590 assert(rbd
&& rbd
!= I596_NULL
);
592 trace_i82596_receive_packet(len
);
593 /* PRINT_PKTHDR("Receive", buf); */
596 uint16_t command
, status
;
599 command
= get_uint16(rfd_p
+ 2);
600 assert(command
& CMD_FLEX
); /* assert Flex Mode */
601 /* get first Receive Buffer Descriptor Address */
602 rbd
= get_uint32(rfd_p
+ 8);
603 assert(get_uint16(rfd_p
+ 14) == 0);
605 /* printf("Receive: rfd is %08x\n", rfd_p); */
608 uint16_t buffer_size
, num
;
610 size_t bufcount
, crccount
;
612 /* printf("Receive: rbd is %08x\n", rbd); */
613 buffer_size
= get_uint16(rbd
+ 12);
614 /* printf("buffer_size is 0x%x\n", buffer_size); */
615 assert(buffer_size
!= 0);
617 num
= buffer_size
& SIZE_MASK
;
621 rba
= get_uint32(rbd
+ 8);
622 /* printf("rba is 0x%x\n", rba); */
624 * Calculate how many bytes we want from buf[] and how many
627 if ((len
- num
) >= 4) {
628 /* The whole guest buffer, we haven't hit the CRC yet */
631 /* All that's left of buf[] */
634 crccount
= num
- bufcount
;
637 /* Still some of the actual data buffer to transfer */
638 assert(bufsz
>= bufcount
);
640 address_space_write(&address_space_memory
, rba
,
641 MEMTXATTRS_UNSPECIFIED
, buf
, bufcount
);
647 /* Write as much of the CRC as fits */
649 address_space_write(&address_space_memory
, rba
,
650 MEMTXATTRS_UNSPECIFIED
, crc_ptr
, crccount
);
656 num
|= 0x4000; /* set F BIT */
658 num
|= I596_EOF
; /* set EOF BIT */
660 set_uint16(rbd
+ 0, num
); /* write actual count with flags */
663 rbd
= get_uint32(rbd
+ 4);
664 /* printf("Next Receive: rbd is %08x\n", rbd); */
666 if (buffer_size
& I596_EOF
) /* last entry */
670 /* Housekeeping, see pg. 18 */
671 next_rfd
= get_uint32(rfd_p
+ 4);
672 set_uint32(next_rfd
+ 8, rbd
);
674 status
= STAT_C
| STAT_OK
| is_broadcast
;
675 set_uint16(rfd_p
, status
);
677 if (command
& CMD_SUSP
) { /* suspend after command? */
678 s
->rx_status
= RX_SUSPENDED
;
679 s
->scb_status
|= SCB_STATUS_RNR
; /* RU left active state */
682 if (command
& CMD_EOL
) /* was it last Frame Descriptor? */
690 s
->scb_status
|= SCB_STATUS_FR
; /* set "RU finished receiving frame" bit. */
691 update_scb_status(s
);
693 /* send IRQ that we received data */
694 qemu_set_irq(s
->irq
, 1);
695 /* s->send_irq = 1; */
698 DBG(printf("Checking:\n"));
699 rfd_p
= get_uint32(s
->scb
+ 8); /* get Receive Frame Descriptor */
700 DBG(printf("Next Receive: rfd is %08x\n", rfd_p
));
701 rfd_p
= get_uint32(rfd_p
+ 4); /* get Next Receive Frame Descriptor */
702 DBG(printf("Next Receive: rfd is %08x\n", rfd_p
));
703 /* get first Receive Buffer Descriptor Address */
704 rbd
= get_uint32(rfd_p
+ 8);
705 DBG(printf("Next Receive: rbd is %08x\n", rbd
));
712 const VMStateDescription vmstate_i82596
= {
715 .minimum_version_id
= 1,
716 .fields
= (const VMStateField
[]) {
717 VMSTATE_UINT16(lnkst
, I82596State
),
718 VMSTATE_TIMER_PTR(flush_queue_timer
, I82596State
),
719 VMSTATE_END_OF_LIST()
723 void i82596_common_init(DeviceState
*dev
, I82596State
*s
, NetClientInfo
*info
)
725 if (s
->conf
.macaddr
.a
[0] == 0) {
726 qemu_macaddr_default_if_unset(&s
->conf
.macaddr
);
728 s
->nic
= qemu_new_nic(info
, &s
->conf
, object_get_typename(OBJECT(dev
)),
729 dev
->id
, &dev
->mem_reentrancy_guard
, s
);
730 qemu_format_nic_info_str(qemu_get_queue(s
->nic
), s
->conf
.macaddr
.a
);
733 s
->flush_queue_timer
= timer_new_ns(QEMU_CLOCK_VIRTUAL
,
734 i82596_flush_queue_timer
, s
);
736 s
->lnkst
= 0x8000; /* initial link state: up */