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"
15 #include "sysemu/sysemu.h"
17 #include "hw/qdev-properties.h"
18 #include "migration/vmstate.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 */
47 #define CU_SUSPENDED 1
51 #define RX_SUSPENDED 1
54 #define CMD_EOL 0x8000 /* The last command of the list, stop. */
55 #define CMD_SUSP 0x4000 /* Suspend after doing cmd. */
56 #define CMD_INTR 0x2000 /* Interrupt after doing cmd. */
58 #define CMD_FLEX 0x0008 /* Enable flexible memory model */
61 CmdNOp
= 0, CmdSASetup
= 1, CmdConfigure
= 2, CmdMulticastList
= 3,
62 CmdTx
= 4, CmdTDR
= 5, CmdDump
= 6, CmdDiagnose
= 7
65 #define STAT_C 0x8000 /* Set to 0 after execution */
66 #define STAT_B 0x4000 /* Command being executed */
67 #define STAT_OK 0x2000 /* Command executed ok */
68 #define STAT_A 0x1000 /* Command aborted */
70 #define I596_EOF 0x8000
71 #define SIZE_MASK 0x3fff
73 #define ETHER_TYPE_LEN 2
74 #define VLAN_TCI_LEN 2
75 #define VLAN_HLEN (ETHER_TYPE_LEN + VLAN_TCI_LEN)
77 /* various flags in the chip config registers */
78 #define I596_PREFETCH (s->config[0] & 0x80)
79 #define I596_PROMISC (s->config[8] & 0x01)
80 #define I596_BC_DISABLE (s->config[8] & 0x02) /* broadcast disable */
81 #define I596_NOCRC_INS (s->config[8] & 0x08)
82 #define I596_CRCINM (s->config[11] & 0x04) /* CRC appended */
83 #define I596_MC_ALL (s->config[11] & 0x20)
84 #define I596_MULTIIA (s->config[13] & 0x40)
87 static uint8_t get_byte(uint32_t addr
)
89 return ldub_phys(&address_space_memory
, addr
);
92 static void set_byte(uint32_t addr
, uint8_t c
)
94 return stb_phys(&address_space_memory
, addr
, c
);
97 static uint16_t get_uint16(uint32_t addr
)
99 return lduw_be_phys(&address_space_memory
, addr
);
102 static void set_uint16(uint32_t addr
, uint16_t w
)
104 return stw_be_phys(&address_space_memory
, addr
, w
);
107 static uint32_t get_uint32(uint32_t addr
)
109 uint32_t lo
= lduw_be_phys(&address_space_memory
, addr
);
110 uint32_t hi
= lduw_be_phys(&address_space_memory
, addr
+ 2);
111 return (hi
<< 16) | lo
;
114 static void set_uint32(uint32_t addr
, uint32_t val
)
116 set_uint16(addr
, (uint16_t) val
);
117 set_uint16(addr
+ 2, val
>> 16);
121 struct qemu_ether_header
{
122 uint8_t ether_dhost
[6];
123 uint8_t ether_shost
[6];
127 #define PRINT_PKTHDR(txt, BUF) do { \
128 struct qemu_ether_header *hdr = (void *)(BUF); \
129 printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\
130 MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost), \
131 be16_to_cpu(hdr->ether_type)); \
134 static void i82596_transmit(I82596State
*s
, uint32_t addr
)
136 uint32_t tdb_p
; /* Transmit Buffer Descriptor */
138 /* TODO: Check flexible mode */
139 tdb_p
= get_uint32(addr
+ 8);
140 while (tdb_p
!= I596_NULL
) {
144 size
= get_uint16(tdb_p
);
145 len
= size
& SIZE_MASK
;
146 tba
= get_uint32(tdb_p
+ 8);
147 trace_i82596_transmit(len
, tba
);
150 assert(len
<= sizeof(s
->tx_buffer
));
151 address_space_rw(&address_space_memory
, tba
,
152 MEMTXATTRS_UNSPECIFIED
, s
->tx_buffer
, len
, 0);
153 DBG(PRINT_PKTHDR("Send", &s
->tx_buffer
));
154 DBG(printf("Sending %d bytes\n", len
));
155 qemu_send_packet(qemu_get_queue(s
->nic
), s
->tx_buffer
, len
);
158 /* was this the last package? */
159 if (size
& I596_EOF
) {
163 /* get next buffer pointer */
164 tdb_p
= get_uint32(tdb_p
+ 4);
168 static void set_individual_address(I82596State
*s
, uint32_t addr
)
173 nc
= qemu_get_queue(s
->nic
);
174 m
= s
->conf
.macaddr
.a
;
175 address_space_rw(&address_space_memory
, addr
+ 8,
176 MEMTXATTRS_UNSPECIFIED
, m
, ETH_ALEN
, 0);
177 qemu_format_nic_info_str(nc
, m
);
178 trace_i82596_new_mac(nc
->info_str
);
181 static void set_multicast_list(I82596State
*s
, uint32_t addr
)
183 uint16_t mc_count
, i
;
185 memset(&s
->mult
[0], 0, sizeof(s
->mult
));
186 mc_count
= get_uint16(addr
+ 8) / ETH_ALEN
;
188 if (mc_count
> MAX_MC_CNT
) {
189 mc_count
= MAX_MC_CNT
;
191 for (i
= 0; i
< mc_count
; i
++) {
192 uint8_t multicast_addr
[ETH_ALEN
];
193 address_space_rw(&address_space_memory
,
194 addr
+ i
* ETH_ALEN
, MEMTXATTRS_UNSPECIFIED
,
195 multicast_addr
, ETH_ALEN
, 0);
196 DBG(printf("Add multicast entry " MAC_FMT
"\n",
197 MAC_ARG(multicast_addr
)));
198 unsigned mcast_idx
= (net_crc32(multicast_addr
, ETH_ALEN
) &
200 assert(mcast_idx
< 8 * sizeof(s
->mult
));
201 s
->mult
[mcast_idx
>> 3] |= (1 << (mcast_idx
& 7));
203 trace_i82596_set_multicast(mc_count
);
206 void i82596_set_link_status(NetClientState
*nc
)
208 I82596State
*d
= qemu_get_nic_opaque(nc
);
210 d
->lnkst
= nc
->link_down
? 0 : 0x8000;
213 static void update_scb_status(I82596State
*s
)
215 s
->scb_status
= (s
->scb_status
& 0xf000)
216 | (s
->cu_status
<< 8) | (s
->rx_status
<< 4);
217 set_uint16(s
->scb
, s
->scb_status
);
221 static void i82596_s_reset(I82596State
*s
)
223 trace_i82596_s_reset(s
);
226 s
->cu_status
= CU_IDLE
;
227 s
->rx_status
= RX_SUSPENDED
;
228 s
->cmd_p
= I596_NULL
;
229 s
->lnkst
= 0x8000; /* initial link state: up */
230 s
->ca
= s
->ca_active
= 0;
235 static void command_loop(I82596State
*s
)
241 DBG(printf("STARTING COMMAND LOOP cmd_p=%08x\n", s
->cmd_p
));
243 while (s
->cmd_p
!= I596_NULL
) {
246 set_uint16(s
->cmd_p
, status
);
247 status
= STAT_C
| STAT_OK
; /* update, but write later */
249 cmd
= get_uint16(s
->cmd_p
+ 2);
250 DBG(printf("Running command %04x at %08x\n", cmd
, s
->cmd_p
));
252 switch (cmd
& 0x07) {
256 set_individual_address(s
, s
->cmd_p
);
259 byte_cnt
= get_byte(s
->cmd_p
+ 8) & 0x0f;
260 byte_cnt
= MAX(byte_cnt
, 4);
261 byte_cnt
= MIN(byte_cnt
, sizeof(s
->config
));
262 /* copy byte_cnt max. */
263 address_space_rw(&address_space_memory
, s
->cmd_p
+ 8,
264 MEMTXATTRS_UNSPECIFIED
, s
->config
, byte_cnt
, 0);
265 /* config byte according to page 35ff */
266 s
->config
[2] &= 0x82; /* mask valid bits */
267 s
->config
[2] |= 0x40;
268 s
->config
[7] &= 0xf7; /* clear zero bit */
269 assert(I596_NOCRC_INS
== 0); /* do CRC insertion */
270 s
->config
[10] = MAX(s
->config
[10], 5); /* min frame length */
271 s
->config
[12] &= 0x40; /* only full duplex field valid */
272 s
->config
[13] |= 0x3f; /* set ones in byte 13 */
275 /* get signal LINK */
276 set_uint32(s
->cmd_p
+ 8, s
->lnkst
);
279 i82596_transmit(s
, s
->cmd_p
);
281 case CmdMulticastList
:
282 set_multicast_list(s
, s
->cmd_p
);
286 printf("FIXME Command %d !!\n", cmd
& 7);
291 set_uint16(s
->cmd_p
, status
);
293 s
->cmd_p
= get_uint32(s
->cmd_p
+ 4); /* get link address */
294 DBG(printf("NEXT addr would be %08x\n", s
->cmd_p
));
296 s
->cmd_p
= I596_NULL
;
299 /* Stop when last command of the list. */
301 s
->cmd_p
= I596_NULL
;
303 /* Suspend after doing cmd? */
304 if (cmd
& CMD_SUSP
) {
305 s
->cu_status
= CU_SUSPENDED
;
306 printf("FIXME SUSPEND !!\n");
308 /* Interrupt after doing cmd? */
309 if (cmd
& CMD_INTR
) {
310 s
->scb_status
|= SCB_STATUS_CX
;
312 s
->scb_status
&= ~SCB_STATUS_CX
;
314 update_scb_status(s
);
316 /* Interrupt after doing cmd? */
317 if (cmd
& CMD_INTR
) {
321 if (s
->cu_status
!= CU_ACTIVE
) {
325 DBG(printf("FINISHED COMMAND LOOP\n"));
326 qemu_flush_queued_packets(qemu_get_queue(s
->nic
));
329 static void i82596_flush_queue_timer(void *opaque
)
331 I82596State
*s
= opaque
;
333 timer_del(s
->flush_queue_timer
);
334 qemu_flush_queued_packets(qemu_get_queue(s
->nic
));
335 timer_mod(s
->flush_queue_timer
,
336 qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL
) + 1000);
340 static void examine_scb(I82596State
*s
)
342 uint16_t command
, cuc
, ruc
;
344 /* get the scb command word */
345 command
= get_uint16(s
->scb
+ 2);
346 cuc
= (command
>> 8) & 0x7;
347 ruc
= (command
>> 4) & 0x7;
348 DBG(printf("MAIN COMMAND %04x cuc %02x ruc %02x\n", command
, cuc
, ruc
));
349 /* and clear the scb command word */
350 set_uint16(s
->scb
+ 2, 0);
352 if (command
& BIT(31)) /* ACK-CX */
353 s
->scb_status
&= ~SCB_STATUS_CX
;
354 if (command
& BIT(30)) /*ACK-FR */
355 s
->scb_status
&= ~SCB_STATUS_FR
;
356 if (command
& BIT(29)) /*ACK-CNA */
357 s
->scb_status
&= ~SCB_STATUS_CNA
;
358 if (command
& BIT(28)) /*ACK-RNR */
359 s
->scb_status
&= ~SCB_STATUS_RNR
;
362 case 0: /* no change */
364 case 1: /* CUC_START */
365 s
->cu_status
= CU_ACTIVE
;
367 case 4: /* CUC_ABORT */
368 s
->cu_status
= CU_SUSPENDED
;
369 s
->scb_status
|= SCB_STATUS_CNA
; /* CU left active state */
372 printf("WARNING: Unknown CUC %d!\n", cuc
);
376 case 0: /* no change */
378 case 1: /* RX_START */
379 case 2: /* RX_RESUME */
380 s
->rx_status
= RX_IDLE
;
382 timer_mod(s
->flush_queue_timer
, qemu_clock_get_ms(
383 QEMU_CLOCK_VIRTUAL
) + 1000);
386 case 3: /* RX_SUSPEND */
387 case 4: /* RX_ABORT */
388 s
->rx_status
= RX_SUSPENDED
;
389 s
->scb_status
|= SCB_STATUS_RNR
; /* RU left active state */
392 printf("WARNING: Unknown RUC %d!\n", ruc
);
395 if (command
& 0x80) { /* reset bit set? */
399 /* execute commands from SCBL */
400 if (s
->cu_status
!= CU_SUSPENDED
) {
401 if (s
->cmd_p
== I596_NULL
) {
402 s
->cmd_p
= get_uint32(s
->scb
+ 4);
406 /* update scb status */
407 update_scb_status(s
);
412 static void signal_ca(I82596State
*s
)
416 /* trace_i82596_channel_attention(s); */
418 /* CA after reset -> do init with new scp. */
419 s
->sysbus
= get_byte(s
->scp
+ 3); /* big endian */
420 DBG(printf("SYSBUS = %08x\n", s
->sysbus
));
421 if (((s
->sysbus
>> 1) & 0x03) != 2) {
422 printf("WARNING: NO LINEAR MODE !!\n");
424 if ((s
->sysbus
>> 7)) {
425 printf("WARNING: 32BIT LINMODE IN B-STEPPING NOT SUPPORTED !!\n");
427 iscp
= get_uint32(s
->scp
+ 8);
428 s
->scb
= get_uint32(iscp
+ 4);
429 set_byte(iscp
+ 1, 0); /* clear BUSY flag in iscp */
433 s
->ca
++; /* count ca() */
445 qemu_set_irq(s
->irq
, 1);
449 void i82596_ioport_writew(void *opaque
, uint32_t addr
, uint32_t val
)
451 I82596State
*s
= opaque
;
452 /* printf("i82596_ioport_writew addr=0x%08x val=0x%04x\n", addr, val); */
454 case PORT_RESET
: /* Reset */
466 uint32_t i82596_ioport_readw(void *opaque
, uint32_t addr
)
471 void i82596_h_reset(void *opaque
)
473 I82596State
*s
= opaque
;
478 int i82596_can_receive(NetClientState
*nc
)
480 I82596State
*s
= qemu_get_nic_opaque(nc
);
482 if (s
->rx_status
== RX_SUSPENDED
) {
490 if (USE_TIMER
&& !timer_pending(s
->flush_queue_timer
)) {
497 #define MIN_BUF_SIZE 60
499 ssize_t
i82596_receive(NetClientState
*nc
, const uint8_t *buf
, size_t sz
)
501 I82596State
*s
= qemu_get_nic_opaque(nc
);
504 uint16_t is_broadcast
= 0;
508 uint8_t buf1
[MIN_BUF_SIZE
+ VLAN_HLEN
];
509 static const uint8_t broadcast_macaddr
[6] = {
510 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
512 DBG(printf("i82596_receive() start\n"));
514 if (USE_TIMER
&& timer_pending(s
->flush_queue_timer
)) {
518 /* first check if receiver is enabled */
519 if (s
->rx_status
== RX_SUSPENDED
) {
520 trace_i82596_receive_analysis(">>> Receiving suspended");
525 trace_i82596_receive_analysis(">>> Link down");
529 /* Received frame smaller than configured "min frame len"? */
530 if (sz
< s
->config
[10]) {
531 printf("Received frame too small, %zu vs. %u bytes\n",
536 DBG(printf("Received %lu bytes\n", sz
));
540 /* promiscuous: receive all */
541 trace_i82596_receive_analysis(
542 ">>> packet received in promiscuous mode");
546 if (!memcmp(buf
, broadcast_macaddr
, 6)) {
547 /* broadcast address */
548 if (I596_BC_DISABLE
) {
549 trace_i82596_receive_analysis(">>> broadcast packet rejected");
554 trace_i82596_receive_analysis(">>> broadcast packet received");
557 } else if (buf
[0] & 0x01) {
560 trace_i82596_receive_analysis(">>> multicast packet rejected");
565 int mcast_idx
= (net_crc32(buf
, ETH_ALEN
) & BITS(7, 2)) >> 2;
566 assert(mcast_idx
< 8 * sizeof(s
->mult
));
568 if (!(s
->mult
[mcast_idx
>> 3] & (1 << (mcast_idx
& 7)))) {
569 trace_i82596_receive_analysis(">>> multicast address mismatch");
574 trace_i82596_receive_analysis(">>> multicast packet received");
577 } else if (!memcmp(s
->conf
.macaddr
.a
, buf
, 6)) {
580 trace_i82596_receive_analysis(
581 ">>> physical address matching packet received");
585 trace_i82596_receive_analysis(">>> unknown packet");
591 /* if too small buffer, then expand it */
592 if (len
< MIN_BUF_SIZE
+ VLAN_HLEN
) {
593 memcpy(buf1
, buf
, len
);
594 memset(buf1
+ len
, 0, MIN_BUF_SIZE
+ VLAN_HLEN
- len
);
596 if (len
< MIN_BUF_SIZE
) {
601 /* Calculate the ethernet checksum (4 bytes) */
603 crc
= cpu_to_be32(crc32(~0, buf
, sz
));
604 crc_ptr
= (uint8_t *) &crc
;
606 rfd_p
= get_uint32(s
->scb
+ 8); /* get Receive Frame Descriptor */
607 assert(rfd_p
&& rfd_p
!= I596_NULL
);
609 /* get first Receive Buffer Descriptor Address */
610 rbd
= get_uint32(rfd_p
+ 8);
611 assert(rbd
&& rbd
!= I596_NULL
);
613 trace_i82596_receive_packet(len
);
614 /* PRINT_PKTHDR("Receive", buf); */
617 uint16_t command
, status
;
620 command
= get_uint16(rfd_p
+ 2);
621 assert(command
& CMD_FLEX
); /* assert Flex Mode */
622 /* get first Receive Buffer Descriptor Address */
623 rbd
= get_uint32(rfd_p
+ 8);
624 assert(get_uint16(rfd_p
+ 14) == 0);
626 /* printf("Receive: rfd is %08x\n", rfd_p); */
629 uint16_t buffer_size
, num
;
632 /* printf("Receive: rbd is %08x\n", rbd); */
633 buffer_size
= get_uint16(rbd
+ 12);
634 /* printf("buffer_size is 0x%x\n", buffer_size); */
635 assert(buffer_size
!= 0);
637 num
= buffer_size
& SIZE_MASK
;
641 rba
= get_uint32(rbd
+ 8);
642 /* printf("rba is 0x%x\n", rba); */
643 address_space_rw(&address_space_memory
, rba
,
644 MEMTXATTRS_UNSPECIFIED
, (void *)buf
, num
, 1);
648 if (len
== 0) { /* copy crc */
649 address_space_rw(&address_space_memory
, rba
- 4,
650 MEMTXATTRS_UNSPECIFIED
, crc_ptr
, 4, 1);
653 num
|= 0x4000; /* set F BIT */
655 num
|= I596_EOF
; /* set EOF BIT */
657 set_uint16(rbd
+ 0, num
); /* write actual count with flags */
660 rbd
= get_uint32(rbd
+ 4);
661 /* printf("Next Receive: rbd is %08x\n", rbd); */
663 if (buffer_size
& I596_EOF
) /* last entry */
667 /* Housekeeping, see pg. 18 */
668 next_rfd
= get_uint32(rfd_p
+ 4);
669 set_uint32(next_rfd
+ 8, rbd
);
671 status
= STAT_C
| STAT_OK
| is_broadcast
;
672 set_uint16(rfd_p
, status
);
674 if (command
& CMD_SUSP
) { /* suspend after command? */
675 s
->rx_status
= RX_SUSPENDED
;
676 s
->scb_status
|= SCB_STATUS_RNR
; /* RU left active state */
679 if (command
& CMD_EOL
) /* was it last Frame Descriptor? */
687 s
->scb_status
|= SCB_STATUS_FR
; /* set "RU finished receiving frame" bit. */
688 update_scb_status(s
);
690 /* send IRQ that we received data */
691 qemu_set_irq(s
->irq
, 1);
692 /* s->send_irq = 1; */
695 DBG(printf("Checking:\n"));
696 rfd_p
= get_uint32(s
->scb
+ 8); /* get Receive Frame Descriptor */
697 DBG(printf("Next Receive: rfd is %08x\n", rfd_p
));
698 rfd_p
= get_uint32(rfd_p
+ 4); /* get Next Receive Frame Descriptor */
699 DBG(printf("Next Receive: rfd is %08x\n", rfd_p
));
700 /* get first Receive Buffer Descriptor Address */
701 rbd
= get_uint32(rfd_p
+ 8);
702 DBG(printf("Next Receive: rbd is %08x\n", rbd
));
709 const VMStateDescription vmstate_i82596
= {
712 .minimum_version_id
= 1,
713 .fields
= (VMStateField
[]) {
714 VMSTATE_UINT16(lnkst
, I82596State
),
715 VMSTATE_TIMER_PTR(flush_queue_timer
, I82596State
),
716 VMSTATE_END_OF_LIST()
720 void i82596_common_init(DeviceState
*dev
, I82596State
*s
, NetClientInfo
*info
)
722 if (s
->conf
.macaddr
.a
[0] == 0) {
723 qemu_macaddr_default_if_unset(&s
->conf
.macaddr
);
725 s
->nic
= qemu_new_nic(info
, &s
->conf
, object_get_typename(OBJECT(dev
)),
727 qemu_format_nic_info_str(qemu_get_queue(s
->nic
), s
->conf
.macaddr
.a
);
730 s
->flush_queue_timer
= timer_new_ns(QEMU_CLOCK_VIRTUAL
,
731 i82596_flush_queue_timer
, s
);
733 s
->lnkst
= 0x8000; /* initial link state: up */