1 |
/* |
/* |
2 |
* Cisco C7200 (Predator) AMD Am79c971 Module. |
* Cisco router simulation platform. |
3 |
* Copyright (C) 2006 Christophe Fillot. All rights reserved. |
* Copyright (C) 2006 Christophe Fillot. All rights reserved. |
4 |
* |
* |
5 |
* AMD Am79c971 FastEthernet chip emulation. |
* AMD Am79c971 FastEthernet chip emulation. |
15 |
#include <assert.h> |
#include <assert.h> |
16 |
|
|
17 |
#include "utils.h" |
#include "utils.h" |
18 |
#include "mips64.h" |
#include "cpu.h" |
19 |
|
#include "vm.h" |
20 |
#include "dynamips.h" |
#include "dynamips.h" |
21 |
#include "memory.h" |
#include "memory.h" |
22 |
#include "device.h" |
#include "device.h" |
30 |
#define DEBUG_BCR_REGS 0 |
#define DEBUG_BCR_REGS 0 |
31 |
#define DEBUG_PCI_REGS 0 |
#define DEBUG_PCI_REGS 0 |
32 |
#define DEBUG_ACCESS 0 |
#define DEBUG_ACCESS 0 |
33 |
#define DEBUG_TRANSMIT 0 |
#define DEBUG_TRANSMIT 1 |
34 |
#define DEBUG_RECEIVE 0 |
#define DEBUG_RECEIVE 1 |
35 |
#define DEBUG_UNKNOWN 0 |
#define DEBUG_UNKNOWN 0 |
36 |
|
|
37 |
/* AMD Am79c971 PCI vendor/product codes */ |
/* AMD Am79c971 PCI vendor/product codes */ |
126 |
struct am79c971_data { |
struct am79c971_data { |
127 |
char *name; |
char *name; |
128 |
|
|
129 |
|
/* Lock */ |
130 |
|
pthread_mutex_t lock; |
131 |
|
|
132 |
/* Interface type (10baseT or 100baseTX) */ |
/* Interface type (10baseT or 100baseTX) */ |
133 |
int type; |
int type; |
134 |
|
|
135 |
|
/* RX/TX clearing count */ |
136 |
|
int rx_tx_clear_count; |
137 |
|
|
138 |
/* Current RAP (Register Address Pointer) value */ |
/* Current RAP (Register Address Pointer) value */ |
139 |
m_uint8_t rap; |
m_uint8_t rap; |
140 |
|
|
178 |
/* Log an am79c971 message */ |
/* Log an am79c971 message */ |
179 |
#define AM79C971_LOG(d,msg...) vm_log((d)->vm,(d)->name,msg) |
#define AM79C971_LOG(d,msg...) vm_log((d)->vm,(d)->name,msg) |
180 |
|
|
181 |
|
/* Lock/Unlock primitives */ |
182 |
|
#define AM79C971_LOCK(d) pthread_mutex_lock(&(d)->lock) |
183 |
|
#define AM79C971_UNLOCK(d) pthread_mutex_unlock(&(d)->lock) |
184 |
|
|
185 |
static m_uint16_t mii_reg_values[32] = { |
static m_uint16_t mii_reg_values[32] = { |
186 |
0x1000, 0x782D, 0x2000, 0x5C01, 0x01E1, 0x0000, 0x0000, 0x0000, |
0x1000, 0x782D, 0x0013, 0x78E2, 0x01E1, 0xC9E1, 0x000F, 0x2001, |
187 |
|
0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, |
188 |
|
0x0104, 0x4780, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, |
189 |
|
0x0000, 0x0000, 0x00C8, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, |
190 |
|
|
191 |
|
#if 0 |
192 |
|
0x1000, 0x782D, 0x0013, 0x78e2, 0x01E1, 0xC9E1, 0x0000, 0x0000, |
193 |
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, |
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, |
194 |
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x8060, |
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x8060, |
195 |
0x8020, 0x0820, 0x0000, 0x3800, 0xA3B9, 0x0000, 0x0000, 0x0000, |
0x8023, 0x0820, 0x0000, 0x3800, 0xA3B9, 0x0000, 0x0000, 0x0000, |
196 |
|
#endif |
197 |
}; |
}; |
198 |
|
|
199 |
/* Read a MII register */ |
/* Read a MII register */ |
219 |
{ |
{ |
220 |
n_eth_hdr_t *hdr = (n_eth_hdr_t *)pkt; |
n_eth_hdr_t *hdr = (n_eth_hdr_t *)pkt; |
221 |
|
|
222 |
/* Ignore traffic sent by us */ |
/* Accept systematically frames if we are running in promiscuous mode */ |
|
if (!memcmp(&d->mac_addr,&hdr->saddr,N_ETH_ALEN)) |
|
|
return(FALSE); |
|
|
|
|
|
/* Accept systematically frames if we are running is promiscuous mode */ |
|
223 |
if (d->csr[15] & AM79C971_CSR15_PROM) |
if (d->csr[15] & AM79C971_CSR15_PROM) |
224 |
return(TRUE); |
return(TRUE); |
225 |
|
|
235 |
} |
} |
236 |
|
|
237 |
/* Update the Interrupt Flag bit of csr0 */ |
/* Update the Interrupt Flag bit of csr0 */ |
238 |
static void am79c971_update_intr_flag(struct am79c971_data *d) |
static void am79c971_update_irq_status(struct am79c971_data *d) |
239 |
{ |
{ |
240 |
m_uint32_t mask; |
m_uint32_t mask; |
241 |
|
|
242 |
mask = d->csr[3] & AM79C971_CSR3_IM_MASK; |
/* Bits set in CR3 disable the specified interrupts */ |
243 |
|
mask = AM79C971_CSR3_IM_MASK & ~(d->csr[3] & AM79C971_CSR3_IM_MASK); |
244 |
|
|
245 |
if (d->csr[0] & mask) |
if (d->csr[0] & mask) |
246 |
d->csr[0] |= AM79C971_CSR0_INTR; |
d->csr[0] |= AM79C971_CSR0_INTR; |
247 |
} |
else |
248 |
|
d->csr[0] &= ~AM79C971_CSR0_INTR; |
249 |
|
|
250 |
/* Trigger an interrupt */ |
if ((d->csr[0] & (AM79C971_CSR0_INTR|AM79C971_CSR0_IENA)) == |
251 |
static int am79c971_trigger_irq(struct am79c971_data *d) |
(AM79C971_CSR0_INTR|AM79C971_CSR0_IENA)) |
252 |
{ |
{ |
|
if (d->csr[0] & (AM79C971_CSR0_INTR|AM79C971_CSR0_IENA)) { |
|
253 |
pci_dev_trigger_irq(d->vm,d->pci_dev); |
pci_dev_trigger_irq(d->vm,d->pci_dev); |
254 |
return(TRUE); |
} else { |
255 |
|
pci_dev_clear_irq(d->vm,d->pci_dev); |
256 |
} |
} |
|
|
|
|
return(FALSE); |
|
257 |
} |
} |
258 |
|
|
259 |
/* Update RX/TX ON bits of csr0 */ |
/* Update RX/TX ON bits of csr0 */ |
341 |
/* Update RX/TX ON bits of csr0 since csr15 has been modified */ |
/* Update RX/TX ON bits of csr0 since csr15 has been modified */ |
342 |
am79c971_update_rx_tx_on_bits(d); |
am79c971_update_rx_tx_on_bits(d); |
343 |
AM79C971_LOG(d,"CSR0 = 0x%4.4x\n",d->csr[0]); |
AM79C971_LOG(d,"CSR0 = 0x%4.4x\n",d->csr[0]); |
|
|
|
|
am79c971_update_intr_flag(d); |
|
|
|
|
|
if (am79c971_trigger_irq(d)) |
|
|
AM79C971_LOG(d,"triggering IDON interrupt\n"); |
|
|
|
|
344 |
return(0); |
return(0); |
345 |
} |
} |
346 |
|
|
347 |
/* RDP (Register Data Port) access */ |
/* RDP (Register Data Port) access */ |
348 |
static void am79c971_rdp_access(cpu_mips_t *cpu,struct am79c971_data *d, |
static void am79c971_rdp_access(cpu_gen_t *cpu,struct am79c971_data *d, |
349 |
u_int op_type,m_uint64_t *data) |
u_int op_type,m_uint64_t *data) |
350 |
{ |
{ |
351 |
m_uint32_t mask; |
m_uint32_t mask; |
372 |
//AM79C971_LOG(d,"stopping interface!\n"); |
//AM79C971_LOG(d,"stopping interface!\n"); |
373 |
d->csr[0] = AM79C971_CSR0_STOP; |
d->csr[0] = AM79C971_CSR0_STOP; |
374 |
d->tx_pos = d->rx_pos = 0; |
d->tx_pos = d->rx_pos = 0; |
375 |
|
am79c971_update_irq_status(d); |
376 |
break; |
break; |
377 |
} |
} |
378 |
|
|
379 |
/* These bits are cleared when set to 1 */ |
/* These bits are cleared when set to 1 */ |
380 |
mask = AM79C971_CSR0_BABL | AM79C971_CSR0_CERR; |
mask = AM79C971_CSR0_BABL | AM79C971_CSR0_CERR; |
381 |
mask |= AM79C971_CSR0_MISS | AM79C971_CSR0_MERR; |
mask |= AM79C971_CSR0_MISS | AM79C971_CSR0_MERR; |
|
mask |= AM79C971_CSR0_RINT | AM79C971_CSR0_TINT; |
|
382 |
mask |= AM79C971_CSR0_IDON; |
mask |= AM79C971_CSR0_IDON; |
383 |
|
|
384 |
|
if (++d->rx_tx_clear_count == 3) { |
385 |
|
mask |= AM79C971_CSR0_RINT | AM79C971_CSR0_TINT; |
386 |
|
d->rx_tx_clear_count = 0; |
387 |
|
} |
388 |
|
|
389 |
d->csr[0] &= ~(*data & mask); |
d->csr[0] &= ~(*data & mask); |
390 |
|
|
391 |
/* Save the Interrupt Enable bit */ |
/* Save the Interrupt Enable bit */ |
405 |
d->csr[0] &= ~AM79C971_CSR0_STOP; |
d->csr[0] &= ~AM79C971_CSR0_STOP; |
406 |
am79c971_update_rx_tx_on_bits(d); |
am79c971_update_rx_tx_on_bits(d); |
407 |
} |
} |
408 |
|
|
409 |
|
/* Update IRQ status */ |
410 |
|
am79c971_update_irq_status(d); |
411 |
} |
} |
412 |
break; |
break; |
413 |
|
|
419 |
} else { |
} else { |
420 |
*data = (d->tx_l2len << 12) | (d->rx_l2len << 8); |
*data = (d->tx_l2len << 12) | (d->rx_l2len << 8); |
421 |
} |
} |
422 |
break; |
break; |
423 |
|
|
424 |
case 15: /* CSR15: Mode */ |
case 15: /* CSR15: Mode */ |
425 |
if (op_type == MTS_WRITE) { |
if (op_type == MTS_WRITE) { |
462 |
} |
} |
463 |
|
|
464 |
/* BDP (BCR Data Port) access */ |
/* BDP (BCR Data Port) access */ |
465 |
static void am79c971_bdp_access(cpu_mips_t *cpu,struct am79c971_data *d, |
static void am79c971_bdp_access(cpu_gen_t *cpu,struct am79c971_data *d, |
466 |
u_int op_type,m_uint64_t *data) |
u_int op_type,m_uint64_t *data) |
467 |
{ |
{ |
468 |
u_int mii_phy,mii_reg; |
u_int mii_phy,mii_reg; |
502 |
if (op_type == MTS_READ) { |
if (op_type == MTS_READ) { |
503 |
cpu_log(cpu,d->name,"read access to unknown BCR %d\n",d->rap); |
cpu_log(cpu,d->name,"read access to unknown BCR %d\n",d->rap); |
504 |
} else { |
} else { |
505 |
cpu_log(cpu,d->name,"write access to unknown BCR %d, value=0x%x\n", |
cpu_log(cpu,d->name, |
506 |
|
"write access to unknown BCR %d, value=0x%x\n", |
507 |
d->rap,*data); |
d->rap,*data); |
508 |
} |
} |
509 |
#endif |
#endif |
513 |
/* |
/* |
514 |
* dev_am79c971_access() |
* dev_am79c971_access() |
515 |
*/ |
*/ |
516 |
void *dev_am79c971_access(cpu_mips_t *cpu,struct vdevice *dev, |
void *dev_am79c971_access(cpu_gen_t *cpu,struct vdevice *dev, |
517 |
m_uint32_t offset,u_int op_size,u_int op_type, |
m_uint32_t offset,u_int op_size,u_int op_type, |
518 |
m_uint64_t *data) |
m_uint64_t *data) |
519 |
{ |
{ |
525 |
#if DEBUG_ACCESS |
#if DEBUG_ACCESS |
526 |
if (op_type == MTS_READ) { |
if (op_type == MTS_READ) { |
527 |
cpu_log(cpu,d->name,"read access to offset=0x%x, pc=0x%llx, size=%u\n", |
cpu_log(cpu,d->name,"read access to offset=0x%x, pc=0x%llx, size=%u\n", |
528 |
offset,cpu->pc,op_size); |
offset,cpu_get_pc(cpu),op_size); |
529 |
} else { |
} else { |
530 |
cpu_log(cpu,d->name,"write access to offset=0x%x, pc=0x%llx, " |
cpu_log(cpu,d->name,"write access to offset=0x%x, pc=0x%llx, " |
531 |
"val=0x%llx, size=%u\n",offset,cpu->pc,*data,op_size); |
"val=0x%llx, size=%u\n",offset,cpu_get_pc(cpu),*data,op_size); |
532 |
} |
} |
533 |
#endif |
#endif |
534 |
|
|
535 |
|
AM79C971_LOCK(d); |
536 |
|
|
537 |
switch(offset) { |
switch(offset) { |
538 |
case 0x14: /* RAP (Register Address Pointer) */ |
case 0x14: /* RAP (Register Address Pointer) */ |
539 |
if (op_type == MTS_WRITE) { |
if (op_type == MTS_WRITE) { |
552 |
break; |
break; |
553 |
} |
} |
554 |
|
|
555 |
|
AM79C971_UNLOCK(d); |
556 |
return NULL; |
return NULL; |
557 |
} |
} |
558 |
|
|
641 |
u_char *pkt_ptr = pkt; |
u_char *pkt_ptr = pkt; |
642 |
m_uint8_t sw_style; |
m_uint8_t sw_style; |
643 |
int i; |
int i; |
644 |
|
|
645 |
/* Truncate the packet if it is too big */ |
/* Truncate the packet if it is too big */ |
646 |
pkt_len = m_min(pkt_len,AM79C971_MAX_PKT_SIZE); |
pkt_len = m_min(pkt_len,AM79C971_MAX_PKT_SIZE); |
647 |
|
|
722 |
rxd0.rmd[1] |= AM79C971_RMD1_STP; |
rxd0.rmd[1] |= AM79C971_RMD1_STP; |
723 |
physmem_copy_u32_to_vm(d->vm,rx_start+4,rxd0.rmd[1]); |
physmem_copy_u32_to_vm(d->vm,rx_start+4,rxd0.rmd[1]); |
724 |
|
|
|
/* Generate RX interrupt */ |
|
725 |
d->csr[0] |= AM79C971_CSR0_RINT; |
d->csr[0] |= AM79C971_CSR0_RINT; |
726 |
am79c971_update_intr_flag(d); |
am79c971_update_irq_status(d); |
|
am79c971_trigger_irq(d); |
|
727 |
return(TRUE); |
return(TRUE); |
728 |
} |
} |
729 |
|
|
738 |
* Don't start receive if the RX ring address has not been set |
* Don't start receive if the RX ring address has not been set |
739 |
* and if RX ON is not set. |
* and if RX ON is not set. |
740 |
*/ |
*/ |
741 |
if ((d->rx_start == 0) || !(d->csr[0] & AM79C971_CSR0_TXON)) |
if ((d->rx_start == 0) || !(d->csr[0] & AM79C971_CSR0_RXON)) |
742 |
return(FALSE); |
return(FALSE); |
743 |
|
|
744 |
#if DEBUG_RECEIVE |
#if DEBUG_RECEIVE |
746 |
mem_dump(log_file,pkt,pkt_len); |
mem_dump(log_file,pkt,pkt_len); |
747 |
#endif |
#endif |
748 |
|
|
749 |
|
AM79C971_LOCK(d); |
750 |
|
|
751 |
/* |
/* |
752 |
* Receive only multicast/broadcast trafic + unicast traffic |
* Receive only multicast/broadcast trafic + unicast traffic |
753 |
* for this virtual machine. |
* for this virtual machine. |
754 |
*/ |
*/ |
755 |
hdr = (n_eth_hdr_t *)pkt; |
hdr = (n_eth_hdr_t *)pkt; |
756 |
|
|
757 |
if (am79c971_handle_mac_addr(d,pkt)) |
if (am79c971_handle_mac_addr(d,pkt)) |
758 |
am79c971_receive_pkt(d,pkt,pkt_len); |
am79c971_receive_pkt(d,pkt,pkt_len); |
759 |
|
|
760 |
|
AM79C971_UNLOCK(d); |
761 |
return(TRUE); |
return(TRUE); |
762 |
} |
} |
763 |
|
|
819 |
struct tx_desc txd0,ctxd,ntxd,*ptxd; |
struct tx_desc txd0,ctxd,ntxd,*ptxd; |
820 |
m_uint32_t tx_start,tx_current; |
m_uint32_t tx_start,tx_current; |
821 |
m_uint32_t clen,tot_len; |
m_uint32_t clen,tot_len; |
822 |
|
|
823 |
if ((d->tx_start == 0) || !(d->csr[0] & AM79C971_CSR0_TXON)) |
if ((d->tx_start == 0) || !(d->csr[0] & AM79C971_CSR0_TXON)) |
824 |
return(FALSE); |
return(FALSE); |
825 |
|
|
827 |
tx_start = tx_current = txdesc_get_current(d); |
tx_start = tx_current = txdesc_get_current(d); |
828 |
ptxd = &txd0; |
ptxd = &txd0; |
829 |
txdesc_read(d,tx_start,ptxd); |
txdesc_read(d,tx_start,ptxd); |
830 |
|
|
831 |
/* If we don't own the first descriptor, we cannot transmit */ |
/* If we don't own the first descriptor, we cannot transmit */ |
832 |
if (!(ptxd->tmd[1] & AM79C971_TMD1_OWN)) |
if (!(ptxd->tmd[1] & AM79C971_TMD1_OWN)) |
833 |
return(FALSE); |
return(FALSE); |
834 |
|
|
835 |
#if DEBUG_TRANSMIT |
#if DEBUG_TRANSMIT |
836 |
AM79C971_LOG(d,"am79c971_handle_txring: 1st desc: " |
AM79C971_LOG(d,"am79c971_handle_txring: 1st desc: " |
837 |
"tmd[0]=0x%x, tmd[1]=0x%x, tmd[2]=0x%x, tmd[3]=0x%x\n", |
"tmd[0]=0x%x, tmd[1]=0x%x, tmd[2]=0x%x, tmd[3]=0x%x\n", |
851 |
/* Copy packet data */ |
/* Copy packet data */ |
852 |
clen = ~((ptxd->tmd[1] & AM79C971_TMD1_LEN) - 1); |
clen = ~((ptxd->tmd[1] & AM79C971_TMD1_LEN) - 1); |
853 |
clen &= AM79C971_TMD1_LEN; |
clen &= AM79C971_TMD1_LEN; |
854 |
|
|
855 |
physmem_copy_from_vm(d->vm,pkt_ptr,ptxd->tmd[0],clen); |
physmem_copy_from_vm(d->vm,pkt_ptr,ptxd->tmd[0],clen); |
856 |
|
|
857 |
pkt_ptr += clen; |
pkt_ptr += clen; |
888 |
AM79C971_LOG(d,"sending packet of %u bytes\n",tot_len); |
AM79C971_LOG(d,"sending packet of %u bytes\n",tot_len); |
889 |
mem_dump(log_file,pkt,tot_len); |
mem_dump(log_file,pkt,tot_len); |
890 |
#endif |
#endif |
891 |
|
/* rewrite ISL header if required */ |
892 |
|
cisco_isl_rewrite(pkt,tot_len); |
893 |
|
|
894 |
/* send it on wire */ |
/* send it on wire */ |
895 |
netio_send(d->nio,pkt,tot_len); |
netio_send(d->nio,pkt,tot_len); |
896 |
} |
} |
901 |
|
|
902 |
/* Generate TX interrupt */ |
/* Generate TX interrupt */ |
903 |
d->csr[0] |= AM79C971_CSR0_TINT; |
d->csr[0] |= AM79C971_CSR0_TINT; |
904 |
am79c971_update_intr_flag(d); |
am79c971_update_irq_status(d); |
|
am79c971_trigger_irq(d); |
|
905 |
return(TRUE); |
return(TRUE); |
906 |
} |
} |
907 |
|
|
910 |
{ |
{ |
911 |
int i; |
int i; |
912 |
|
|
913 |
|
AM79C971_LOCK(d); |
914 |
|
|
915 |
for(i=0;i<AM79C971_TXRING_PASS_COUNT;i++) |
for(i=0;i<AM79C971_TXRING_PASS_COUNT;i++) |
916 |
if (!am79c971_handle_txring_single(d)) |
if (!am79c971_handle_txring_single(d)) |
917 |
break; |
break; |
918 |
|
|
919 |
|
AM79C971_UNLOCK(d); |
920 |
return(TRUE); |
return(TRUE); |
921 |
} |
} |
922 |
|
|
925 |
* |
* |
926 |
* Read a PCI register. |
* Read a PCI register. |
927 |
*/ |
*/ |
928 |
static m_uint32_t pci_am79c971_read(cpu_mips_t *cpu,struct pci_device *dev, |
static m_uint32_t pci_am79c971_read(cpu_gen_t *cpu,struct pci_device *dev, |
929 |
int reg) |
int reg) |
930 |
{ |
{ |
931 |
struct am79c971_data *d = dev->priv_data; |
struct am79c971_data *d = dev->priv_data; |
951 |
* |
* |
952 |
* Write a PCI register. |
* Write a PCI register. |
953 |
*/ |
*/ |
954 |
static void pci_am79c971_write(cpu_mips_t *cpu,struct pci_device *dev, |
static void pci_am79c971_write(cpu_gen_t *cpu,struct pci_device *dev, |
955 |
int reg,m_uint32_t value) |
int reg,m_uint32_t value) |
956 |
{ |
{ |
957 |
struct am79c971_data *d = dev->priv_data; |
struct am79c971_data *d = dev->priv_data; |
989 |
|
|
990 |
memset(d,0,sizeof(*d)); |
memset(d,0,sizeof(*d)); |
991 |
memcpy(d->mii_regs[0],mii_reg_values,sizeof(mii_reg_values)); |
memcpy(d->mii_regs[0],mii_reg_values,sizeof(mii_reg_values)); |
992 |
|
pthread_mutex_init(&d->lock,NULL); |
993 |
|
|
994 |
/* Add as PCI device */ |
/* Add as PCI device */ |
995 |
pci_dev = pci_dev_add(pci_bus,name, |
pci_dev = pci_dev_add(pci_bus,name, |