import { LOG_NET } from './const.js' import { h, hex_dump } from './lib.js' import { dbg_assert, dbg_log } from './log.js' import { PCI } from './pci.js' import { BusConnector } from './bus.js' import { IO } from './io.js' // http://www.ethernut.de/pdf/8019asds.pdf // Minimal interface for the CPU fields Ne2k uses. interface Ne2kCpu { io: IO devices: { pci: PCI } } const NE2K_LOG_VERBOSE = false const NE2K_LOG_PACKETS = false const E8390_CMD = 0x00 /* The command register (for all pages) */ /* Page 0 register offsets. */ const _EN0_CLDALO = 0x01 /* Low byte of current local dma addr RD */ const EN0_STARTPG = 0x01 /* Starting page of ring bfr WR */ const _EN0_CLDAHI = 0x02 /* High byte of current local dma addr RD */ const EN0_STOPPG = 0x02 /* Ending page +1 of ring bfr WR */ const EN0_BOUNDARY = 0x03 /* Boundary page of ring bfr RD WR */ const EN0_TSR = 0x04 /* Transmit status reg RD */ const EN0_TPSR = 0x04 /* Transmit starting page WR */ const _EN0_NCR = 0x05 /* Number of collision reg RD */ const EN0_TCNTLO = 0x05 /* Low byte of tx byte count WR */ const _EN0_FIFO = 0x06 /* FIFO RD */ const EN0_TCNTHI = 0x06 /* High byte of tx byte count WR */ const EN0_ISR = 0x07 /* Interrupt status reg RD WR */ const _EN0_CRDALO = 0x08 /* low byte of current remote dma address RD */ const EN0_RSARLO = 0x08 /* Remote start address reg 0 */ const _EN0_CRDAHI = 0x09 /* high byte, current remote dma address RD */ const EN0_RSARHI = 0x09 /* Remote start address reg 1 */ const EN0_RCNTLO = 0x0a /* Remote byte count reg WR */ const EN0_RCNTHI = 0x0b /* Remote byte count reg WR */ const EN0_RSR = 0x0c /* rx status reg RD */ const EN0_RXCR = 0x0c /* RX configuration reg WR */ const EN0_TXCR = 0x0d /* TX configuration reg WR */ const EN0_COUNTER0 = 0x0d /* Rcv alignment error counter RD */ const EN0_DCFG = 0x0e /* Data configuration reg WR */ const EN0_COUNTER1 = 0x0e /* Rcv CRC error counter RD */ const EN0_IMR = 0x0f /* Interrupt mask reg WR */ const EN0_COUNTER2 = 0x0f /* Rcv missed frame error counter RD */ const NE_DATAPORT = 0x10 /* NatSemi-defined port window offset. */ const NE_RESET = 0x1f /* Issue a read to reset, a write to clear. */ /* Bits in EN0_ISR - Interrupt status register */ const ENISR_RX = 0x01 /* Receiver, no error */ const ENISR_TX = 0x02 /* Transmitter, no error */ const _ENISR_RX_ERR = 0x04 /* Receiver, with error */ const _ENISR_TX_ERR = 0x08 /* Transmitter, with error */ const _ENISR_OVER = 0x10 /* Receiver overwrote the ring */ const _ENISR_COUNTERS = 0x20 /* Counters need emptying */ const ENISR_RDC = 0x40 /* remote dma complete */ const ENISR_RESET = 0x80 /* Reset completed */ const _ENISR_ALL = 0x3f /* Interrupts we will enable */ const ENRSR_RXOK = 0x01 /* Received a good packet */ const START_PAGE = 0x40 const START_RX_PAGE = 0x40 + 12 const STOP_PAGE = 0x80 // Search and replace MAC addresses in ethernet, arp and dhcp packets. // Used after restoring an OS from memory dump, so that multiple instances of // that OS can run at the same time with different external MAC addresses. // Crude but seems to work. function translate_mac_address( packet: Uint8Array, search_mac: Uint8Array, replacement_mac: Uint8Array, ): void { if ( packet[0] === search_mac[0] && packet[1] === search_mac[1] && packet[2] === search_mac[2] && packet[3] === search_mac[3] && packet[4] === search_mac[4] && packet[5] === search_mac[5] ) { dbg_log('Replace mac in eth destination field', LOG_NET) packet[0] = replacement_mac[0] packet[1] = replacement_mac[1] packet[2] = replacement_mac[2] packet[3] = replacement_mac[3] packet[4] = replacement_mac[4] packet[5] = replacement_mac[5] } if ( packet[6 + 0] === search_mac[0] && packet[6 + 1] === search_mac[1] && packet[6 + 2] === search_mac[2] && packet[6 + 3] === search_mac[3] && packet[6 + 4] === search_mac[4] && packet[6 + 5] === search_mac[5] ) { dbg_log('Replace mac in eth source field', LOG_NET) packet[6 + 0] = replacement_mac[0] packet[6 + 1] = replacement_mac[1] packet[6 + 2] = replacement_mac[2] packet[6 + 3] = replacement_mac[3] packet[6 + 4] = replacement_mac[4] packet[6 + 5] = replacement_mac[5] } const ethertype = (packet[12] << 8) | packet[13] if (ethertype === 0x0800) { // ipv4 const ipv4_packet = packet.subarray(14) const ipv4_version = ipv4_packet[0] >> 4 if (ipv4_version !== 4) { dbg_log( 'Expected ipv4.version==4 but got: ' + ipv4_version, LOG_NET, ) return } const ipv4_ihl = ipv4_packet[0] & 0xf dbg_assert(ipv4_ihl === 5, 'TODO: ihl!=5') const ipv4_proto = ipv4_packet[9] if (ipv4_proto === 0x11) { // udp const udp_packet = ipv4_packet.subarray(5 * 4) const source_port = (udp_packet[0] << 8) | udp_packet[1] const destination_port = (udp_packet[2] << 8) | udp_packet[3] const checksum = (udp_packet[6] << 8) | udp_packet[7] dbg_log( 'udp srcport=' + source_port + ' dstport=' + destination_port + ' checksum=' + h(checksum, 4), LOG_NET, ) if (source_port === 67 || destination_port === 67) { // dhcp const dhcp_packet = udp_packet.subarray(8) const dhcp_magic = (dhcp_packet[0xec] << 24) | (dhcp_packet[0xed] << 16) | (dhcp_packet[0xee] << 8) | dhcp_packet[0xef] if (dhcp_magic !== 0x63825363) { dbg_log( "dhcp packet didn't match magic: " + h(dhcp_magic, 8), ) return } if ( dhcp_packet[28 + 0] === search_mac[0] && dhcp_packet[28 + 1] === search_mac[1] && dhcp_packet[28 + 2] === search_mac[2] && dhcp_packet[28 + 3] === search_mac[3] && dhcp_packet[28 + 4] === search_mac[4] && dhcp_packet[28 + 5] === search_mac[5] ) { dbg_log('Replace mac in dhcp.chaddr', LOG_NET) dhcp_packet[28 + 0] = replacement_mac[0] dhcp_packet[28 + 1] = replacement_mac[1] dhcp_packet[28 + 2] = replacement_mac[2] dhcp_packet[28 + 3] = replacement_mac[3] dhcp_packet[28 + 4] = replacement_mac[4] dhcp_packet[28 + 5] = replacement_mac[5] udp_packet[6] = udp_packet[7] = 0 // zero udp checksum } let offset = 0xf0 while (offset < dhcp_packet.length) { const dhcp_option_type = dhcp_packet[offset++] if (dhcp_option_type === 0xff) { break } const length = dhcp_packet[offset++] if ( dhcp_option_type === 0x3d && // client identifier dhcp_packet[offset + 0] === 0x01 && // ethernet dhcp_packet[offset + 1] === search_mac[0] && dhcp_packet[offset + 2] === search_mac[1] && dhcp_packet[offset + 3] === search_mac[2] && dhcp_packet[offset + 4] === search_mac[3] && dhcp_packet[offset + 5] === search_mac[4] && dhcp_packet[offset + 6] === search_mac[5] ) { dbg_log('Replace mac in dhcp.clientidentifier', LOG_NET) dhcp_packet[offset + 1] = replacement_mac[0] dhcp_packet[offset + 2] = replacement_mac[1] dhcp_packet[offset + 3] = replacement_mac[2] dhcp_packet[offset + 4] = replacement_mac[3] dhcp_packet[offset + 5] = replacement_mac[4] dhcp_packet[offset + 6] = replacement_mac[5] udp_packet[6] = udp_packet[7] = 0 // zero udp checksum } offset += length } } } else { // tcp, ... } } else if (ethertype === 0x0806) { // arp const arp_packet = packet.subarray(14) dbg_log( 'arp oper=' + arp_packet[7] + ' ' + format_mac(arp_packet.subarray(8, 8 + 6)) + ' ' + format_mac(arp_packet.subarray(18, 18 + 6)), LOG_NET, ) if ( arp_packet[8 + 0] === search_mac[0] && arp_packet[8 + 1] === search_mac[1] && arp_packet[8 + 2] === search_mac[2] && arp_packet[8 + 3] === search_mac[3] && arp_packet[8 + 4] === search_mac[4] && arp_packet[8 + 5] === search_mac[5] ) { dbg_log('Replace mac in arp.sha', LOG_NET) arp_packet[8 + 0] = replacement_mac[0] arp_packet[8 + 1] = replacement_mac[1] arp_packet[8 + 2] = replacement_mac[2] arp_packet[8 + 3] = replacement_mac[3] arp_packet[8 + 4] = replacement_mac[4] arp_packet[8 + 5] = replacement_mac[5] } } else { // TODO: ipv6, ... } } export function format_mac(mac: Uint8Array): string { return [ mac[0].toString(16).padStart(2, '0'), mac[1].toString(16).padStart(2, '0'), mac[2].toString(16).padStart(2, '0'), mac[3].toString(16).padStart(2, '0'), mac[4].toString(16).padStart(2, '0'), mac[5].toString(16).padStart(2, '0'), ].join(':') } function dump_packet(packet: Uint8Array, prefix: string): void { const ethertype = (packet[12] << 8) | (packet[13] << 0) if (ethertype === 0x0800) { const ipv4_packet = packet.subarray(14) const ipv4_len = (ipv4_packet[2] << 8) | ipv4_packet[3] const ipv4_proto = ipv4_packet[9] if (ipv4_proto === 0x11) { const udp_packet = ipv4_packet.subarray(5 * 4) const source_port = (udp_packet[0] << 8) | udp_packet[1] const destination_port = (udp_packet[2] << 8) | udp_packet[3] const checksum = (udp_packet[6] << 8) | udp_packet[7] if (source_port === 67 || destination_port === 67) { const dhcp_packet = udp_packet.subarray(8) const dhcp_chaddr = dhcp_packet.subarray(28, 28 + 6) dbg_log( prefix + ' len=' + packet.length + ' ethertype=' + h(ethertype) + ' ipv4.len=' + ipv4_len + ' ipv4.proto=' + h(packet[14 + 9]) + ' udp.srcport=' + source_port + ' udp.dstport=' + destination_port + ' udp.chksum=' + h(checksum, 4) + ' dhcp.chaddr=' + format_mac(dhcp_chaddr), ) } else { dbg_log( prefix + ' len=' + packet.length + ' ethertype=' + h(ethertype) + ' ipv4.len=' + ipv4_len + ' ipv4.proto=' + h(packet[14 + 9]) + ' udp.srcport=' + source_port + ' udp.dstport=' + destination_port + ' udp.chksum=' + h(checksum, 4), ) } } else if (ipv4_proto === 0x01) { // icmp, intentionally empty } else { dbg_log( prefix + ' len=' + packet.length + ' ethertype=' + h(ethertype) + ' ipv4.len=' + ipv4_len + ' ipv4.proto=' + h(packet[14 + 9]), ) } } else { const _arp_packet = packet.subarray(14) dbg_log( prefix + ' len=' + packet.length + ' ethertype=' + h(ethertype) + ' arp', ) } dbg_log(hex_dump(packet)) } export class Ne2k { name: string cpu: Ne2kCpu pci: PCI id: number preserve_mac_from_state_image: boolean mac_address_translation: boolean bus: BusConnector port: number pci_space: number[] pci_id: number pci_bars: { size: number }[] isr: number imr: number cr: number dcfg: number rcnt: number tcnt: number tpsr: number memory: Uint8Array rxcr: number txcr: number tsr: number mac: Uint8Array mar: Uint8Array mac_address_in_state: Uint8Array | null rsar: number pstart: number pstop: number curpg: number boundary: number constructor( cpu: Ne2kCpu, bus: BusConnector, preserve_mac_from_state_image: boolean, mac_address_translation: boolean, id?: number, ) { this.cpu = cpu this.pci = cpu.devices.pci this.id = id || 0 this.preserve_mac_from_state_image = preserve_mac_from_state_image this.mac_address_translation = mac_address_translation this.bus = bus this.bus.register( 'net' + this.id + '-receive', function (this: Ne2k, data: Uint8Array) { this.receive(data) }, this, ) this.port = 0x300 + 0x100 * this.id this.name = 'ne2k' const use_pci = true if (use_pci) { this.pci_space = [ 0xec, 0x10, 0x29, 0x80, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, (this.port & 0xff) | 1, this.port >> 8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x1a, 0x00, 0x11, 0x00, 0x00, 0xb8, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, ] this.pci_id = (this.id === 0 ? 0x05 : 0x07 + this.id) << 3 this.pci_bars = [ { size: 32, }, ] } else { this.pci_space = [] this.pci_id = 0 this.pci_bars = [] } this.isr = 0 this.imr = 0 // interrupt mask register this.cr = 1 this.dcfg = 0 this.rcnt = 0 this.tcnt = 0 this.tpsr = 0 this.memory = new Uint8Array(256 * 0x80) this.rxcr = 0 this.txcr = 0 this.tsr = 1 // mac address this.mac = new Uint8Array([ 0x00, 0x22, 0x15, (Math.random() * 255) | 0, (Math.random() * 255) | 0, (Math.random() * 255) | 0, ]) this.bus.send('net' + this.id + '-mac', format_mac(this.mac)) // multicast addresses this.mar = Uint8Array.of(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff) // Used for mac address translation // The mac the OS thinks it has this.mac_address_in_state = null for (let i = 0; i < 6; i++) { this.memory[i << 1] = this.memory[(i << 1) | 1] = this.mac[i] } // the PROM signature of 0x57, 0x57 is also doubled // resulting in setting the 4 bytes at the end, 28, 29, 30 and 31 to 0x57 this.memory[14 << 1] = this.memory[(14 << 1) | 1] = 0x57 this.memory[15 << 1] = this.memory[(15 << 1) | 1] = 0x57 dbg_log('Mac: ' + format_mac(this.mac), LOG_NET) this.rsar = 0 this.pstart = START_PAGE this.pstop = STOP_PAGE this.curpg = START_RX_PAGE this.boundary = START_RX_PAGE const io = cpu.io io.register_read( this.port | E8390_CMD, this, function (this: Ne2k): number { dbg_log('Read cmd', LOG_NET) return this.cr }, function (this: Ne2k): number { dbg_log('Read16 cmd', LOG_NET) return this.cr }, ) io.register_write( this.port | E8390_CMD, this, function (this: Ne2k, data_byte: number): void { this.cr = data_byte dbg_log( 'Write command: ' + h(data_byte, 2) + ' newpg=' + (this.cr >> 6) + ' txcr=' + h(this.txcr, 2), LOG_NET, ) if (this.cr & 1) { return } if (data_byte & 0x18 && this.rcnt === 0) { this.do_interrupt(ENISR_RDC) } if (data_byte & 4) { const start = this.tpsr << 8 let data: Uint8Array = this.memory.subarray( start, start + this.tcnt, ) if (NE2K_LOG_PACKETS) { dump_packet(data, 'send') } if (this.mac_address_in_state) { data = new Uint8Array(data) // make a copy translate_mac_address( data, this.mac_address_in_state, this.mac, ) } this.bus.send('net' + this.id + '-send', data) this.bus.send('eth-transmit-end', [data.length]) this.cr &= ~4 this.do_interrupt(ENISR_TX) dbg_log( 'Command: Transfer. length=' + h(data.byteLength), LOG_NET, ) } }, ) io.register_read( this.port | EN0_COUNTER0, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 1) { dbg_log('Read mar5', LOG_NET) return this.mar[5] } else { dbg_log('Read counter0 pg=' + pg, LOG_NET) return 0 } }, ) io.register_read( this.port | EN0_COUNTER1, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 1) { dbg_log('Read mar6', LOG_NET) return this.mar[6] } else { dbg_log('Read8 counter1 pg=' + pg, LOG_NET) return 0 } }, function (this: Ne2k): number { dbg_log('Read16 counter1 pg=' + this.get_page(), LOG_NET) // openbsd return 0 }, ) io.register_read( this.port | EN0_COUNTER2, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 1) { dbg_log('Read mar7', LOG_NET) return this.mar[7] } else { dbg_log('Read counter2 pg=' + pg, LOG_NET) return 0 } }, ) io.register_read( this.port | NE_RESET, this, function (this: Ne2k): number { dbg_log('Read reset', LOG_NET) this.do_interrupt(ENISR_RESET) return 0 }, ) io.register_write( this.port | NE_RESET, this, function (this: Ne2k, data_byte: number): void { dbg_log('Write reset: ' + h(data_byte, 2), LOG_NET) //this.isr &= ~ENISR_RESET; }, ) io.register_read( this.port | EN0_STARTPG, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { return this.pstart } else if (pg === 1) { dbg_log('Read pg1/01 (mac[0])', LOG_NET) return this.mac[0] } else if (pg === 2) { return this.pstart } else { dbg_log('Read pg' + pg + '/01') dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_STARTPG, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log('start page: ' + h(data_byte, 2), LOG_NET) this.pstart = data_byte } else if (pg === 1) { dbg_log('mac[0] = ' + h(data_byte), LOG_NET) this.mac[0] = data_byte } else if (pg === 3) { dbg_log( 'Unimplemented: Write pg3/01 (9346CR): ' + h(data_byte), LOG_NET, ) } else { dbg_log('Write pg' + pg + '/01: ' + h(data_byte), LOG_NET) dbg_assert(false) } }, ) io.register_read( this.port | EN0_STOPPG, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { return this.pstop } else if (pg === 1) { dbg_log('Read pg1/02 (mac[1])', LOG_NET) return this.mac[1] } else if (pg === 2) { return this.pstop } else { dbg_log('Read pg' + pg + '/02', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_STOPPG, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log('stop page: ' + h(data_byte, 2), LOG_NET) if (data_byte > this.memory.length >> 8) { data_byte = this.memory.length >> 8 dbg_log( 'XXX: Adjusting stop page to ' + h(data_byte), LOG_NET, ) } this.pstop = data_byte } else if (pg === 1) { dbg_log('mac[1] = ' + h(data_byte), LOG_NET) this.mac[1] = data_byte } else { dbg_log('Write pg' + pg + '/02: ' + h(data_byte), LOG_NET) dbg_assert(false) } }, ) io.register_read( this.port | EN0_ISR, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log('Read isr: ' + h(this.isr, 2), LOG_NET) return this.isr } else if (pg === 1) { dbg_log('Read curpg: ' + h(this.curpg, 2), LOG_NET) return this.curpg } else { dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_ISR, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { // acknowledge interrupts where bit is set dbg_log('Write isr: ' + h(data_byte, 2), LOG_NET) this.isr &= ~data_byte this.update_irq() } else if (pg === 1) { dbg_log('Write curpg: ' + h(data_byte, 2), LOG_NET) this.curpg = data_byte } else { dbg_assert(false) } }, ) io.register_write( this.port | EN0_TXCR, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { this.txcr = data_byte dbg_log('Write tx config: ' + h(data_byte, 2), LOG_NET) } else { dbg_log( 'Unimplemented: Write pg' + pg + '/0d ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_write( this.port | EN0_DCFG, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'Write data configuration: ' + h(data_byte, 2), LOG_NET, ) this.dcfg = data_byte } else { dbg_log( 'Unimplemented: Write pg' + pg + '/0e ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_read( this.port | EN0_RCNTLO, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log('Read pg0/0a', LOG_NET) return 0x50 } else if (pg === 1) { dbg_log('Read mar2', LOG_NET) return this.mar[2] } else { dbg_assert(false, 'TODO') return 0 } }, ) io.register_write( this.port | EN0_RCNTLO, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'Write remote byte count low: ' + h(data_byte, 2), LOG_NET, ) this.rcnt = (this.rcnt & 0xff00) | (data_byte & 0xff) } else { dbg_log( 'Unimplemented: Write pg' + pg + '/0a ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_read( this.port | EN0_RCNTHI, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log('Read pg0/0b', LOG_NET) return 0x43 } else if (pg === 1) { dbg_log('Read mar3', LOG_NET) return this.mar[3] } else { dbg_assert(false, 'TODO') return 0 } }, ) io.register_write( this.port | EN0_RCNTHI, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'Write remote byte count high: ' + h(data_byte, 2), LOG_NET, ) this.rcnt = (this.rcnt & 0xff) | ((data_byte << 8) & 0xff00) } else { dbg_log( 'Unimplemented: Write pg' + pg + '/0b ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_read( this.port | EN0_RSARLO, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log('Read remote start address low', LOG_NET) return this.rsar & 0xff } else if (pg === 1) { dbg_log('Read mar0', LOG_NET) return this.mar[0] } else { dbg_log('Unimplemented: Read pg' + pg + '/08', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_RSARLO, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'Write remote start address low: ' + h(data_byte, 2), LOG_NET, ) this.rsar = (this.rsar & 0xff00) | (data_byte & 0xff) } else { dbg_log( 'Unimplemented: Write pg' + pg + '/08 ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_read( this.port | EN0_RSARHI, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log('Read remote start address high', LOG_NET) return (this.rsar >> 8) & 0xff } else if (pg === 1) { dbg_log('Read mar1', LOG_NET) return this.mar[1] } else { dbg_log('Unimplemented: Read pg' + pg + '/09', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_RSARHI, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'Write remote start address low: ' + h(data_byte, 2), LOG_NET, ) this.rsar = (this.rsar & 0xff) | ((data_byte << 8) & 0xff00) } else { dbg_log( 'Unimplemented: Write pg' + pg + '/09 ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_write( this.port | EN0_IMR, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'Write interrupt mask register: ' + h(data_byte, 2) + ' isr=' + h(this.isr, 2), LOG_NET, ) this.imr = data_byte this.update_irq() } else { dbg_log( 'Unimplemented: Write pg' + pg + '/0f ' + h(data_byte, 2), LOG_NET, ) } }, ) io.register_read( this.port | EN0_BOUNDARY, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log('Read boundary: ' + h(this.boundary, 2), LOG_NET) return this.boundary } else if (pg === 1) { dbg_log('Read pg1/03 (mac[2])', LOG_NET) return this.mac[2] } else if (pg === 3) { dbg_log('Unimplemented: Read pg3/03 (CONFIG0)', LOG_NET) return 0 } else { dbg_log('Read pg' + pg + '/03', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_BOUNDARY, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log('Write boundary: ' + h(data_byte, 2), LOG_NET) this.boundary = data_byte } else if (pg === 1) { dbg_log('mac[2] = ' + h(data_byte), LOG_NET) this.mac[2] = data_byte } else { dbg_log('Write pg' + pg + '/03: ' + h(data_byte), LOG_NET) dbg_assert(false) } }, ) io.register_read( this.port | EN0_TSR, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { return this.tsr } else if (pg === 1) { dbg_log('Read pg1/04 (mac[3])', LOG_NET) return this.mac[3] } else { dbg_log('Read pg' + pg + '/04', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_TPSR, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log('Write tpsr: ' + h(data_byte, 2), LOG_NET) this.tpsr = data_byte } else if (pg === 1) { dbg_log('mac[3] = ' + h(data_byte), LOG_NET) this.mac[3] = data_byte } else { dbg_log('Write pg' + pg + '/04: ' + h(data_byte), LOG_NET) dbg_assert(false) } }, ) io.register_read( this.port | EN0_TCNTLO, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_log( 'Unimplemented: Read pg0/05 (NCR: Number of Collisions Register)', LOG_NET, ) return 0 } else if (pg === 1) { dbg_log('Read pg1/05 (mac[4])', LOG_NET) return this.mac[4] } else if (pg === 3) { dbg_log('Unimplemented: Read pg3/05 (CONFIG2)', LOG_NET) return 0 } else { dbg_log('Read pg' + pg + '/05', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_TCNTLO, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log('Write tcnt low: ' + h(data_byte, 2), LOG_NET) this.tcnt = (this.tcnt & ~0xff) | data_byte } else if (pg === 1) { dbg_log('mac[4] = ' + h(data_byte), LOG_NET) this.mac[4] = data_byte } else if (pg === 3) { dbg_log( 'Unimplemented: Write pg3/05 (CONFIG2): ' + h(data_byte), LOG_NET, ) } else { dbg_log('Write pg' + pg + '/05: ' + h(data_byte), LOG_NET) dbg_assert(false) } }, ) io.register_read( this.port | EN0_TCNTHI, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { dbg_assert(false, 'TODO') return 0 } else if (pg === 1) { dbg_log('Read pg1/06 (mac[5])', LOG_NET) return this.mac[5] } else if (pg === 3) { dbg_log('Unimplemented: Read pg3/06 (CONFIG3)', LOG_NET) return 0 } else { dbg_log('Read pg' + pg + '/06', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_TCNTHI, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log('Write tcnt high: ' + h(data_byte, 2), LOG_NET) this.tcnt = (this.tcnt & 0xff) | (data_byte << 8) } else if (pg === 1) { dbg_log('mac[5] = ' + h(data_byte), LOG_NET) this.mac[5] = data_byte } else if (pg === 3) { dbg_log( 'Unimplemented: Write pg3/06 (CONFIG3): ' + h(data_byte), LOG_NET, ) } else { dbg_log('Write pg' + pg + '/06: ' + h(data_byte), LOG_NET) dbg_assert(false) } }, ) io.register_read( this.port | EN0_RSR, this, function (this: Ne2k): number { const pg = this.get_page() if (pg === 0) { return 1 | (1 << 3) // receive status ok } else if (pg === 1) { dbg_log('Read mar4', LOG_NET) return this.mar[4] } else { dbg_log('Unimplemented: Read pg' + pg + '/0c', LOG_NET) dbg_assert(false) return 0 } }, ) io.register_write( this.port | EN0_RXCR, this, function (this: Ne2k, data_byte: number): void { const pg = this.get_page() if (pg === 0) { dbg_log( 'RX configuration reg write: ' + h(data_byte, 2), LOG_NET, ) this.rxcr = data_byte } else { dbg_log( 'Unimplemented: Write pg' + pg + '/0c: ' + h(data_byte), LOG_NET, ) } }, ) io.register_read( this.port | NE_DATAPORT | 0, this, this.data_port_read8, this.data_port_read16, this.data_port_read32, ) io.register_write( this.port | NE_DATAPORT | 0, this, this.data_port_write16, this.data_port_write16, this.data_port_write32, ) if (use_pci) { cpu.devices.pci.register_device(this) } } get_state(): (number | Uint8Array)[] { const state: (number | Uint8Array)[] = [] state[0] = this.isr state[1] = this.imr state[2] = this.cr state[3] = this.dcfg state[4] = this.rcnt state[5] = this.tcnt state[6] = this.tpsr state[7] = this.rsar state[8] = this.pstart state[9] = this.curpg state[10] = this.boundary state[11] = this.pstop state[12] = this.rxcr state[13] = this.txcr state[14] = this.tsr state[15] = this.mac state[16] = this.memory return state } set_state(state: any[]): void { this.isr = state[0] this.imr = state[1] this.cr = state[2] this.dcfg = state[3] this.rcnt = state[4] this.tcnt = state[5] this.tpsr = state[6] this.rsar = state[7] this.pstart = state[8] this.curpg = state[9] this.boundary = state[10] this.pstop = state[11] this.rxcr = state[12] this.txcr = state[13] this.tsr = state[14] if (this.preserve_mac_from_state_image) { this.mac = state[15] this.memory = state[16] } else if (this.mac_address_translation) { this.mac_address_in_state = state[15] this.memory = state[16] dbg_log( 'Using mac address translation' + ' guest_os_mac=' + format_mac(this.mac_address_in_state!) + ' real_mac=' + format_mac(this.mac), LOG_NET, ) } this.bus.send('net' + this.id + '-mac', format_mac(this.mac)) } do_interrupt(ir_mask: number): void { dbg_log('Do interrupt ' + h(ir_mask, 2), LOG_NET) this.isr |= ir_mask this.update_irq() } update_irq(): void { if (this.imr & this.isr) { this.pci.raise_irq(this.pci_id) } else { this.pci.lower_irq(this.pci_id) } } data_port_write(data_byte: number): void { if (NE2K_LOG_VERBOSE) { dbg_log( 'Write data port: data=' + h(data_byte & 0xff, 2) + ' rsar=' + h(this.rsar, 4) + ' rcnt=' + h(this.rcnt, 4), LOG_NET, ) } if ( this.rsar <= 0x10 || (this.rsar >= START_PAGE << 8 && this.rsar < STOP_PAGE << 8) ) { this.memory[this.rsar] = data_byte } this.rsar++ this.rcnt-- if (this.rsar >= this.pstop << 8) { this.rsar += (this.pstart - this.pstop) << 8 } if (this.rcnt === 0) { this.do_interrupt(ENISR_RDC) } } data_port_write16(data: number): void { this.data_port_write(data) if (this.dcfg & 1) { this.data_port_write(data >> 8) } } data_port_write32(data: number): void { this.data_port_write(data) this.data_port_write(data >> 8) this.data_port_write(data >> 16) this.data_port_write(data >> 24) } data_port_read(): number { let data = 0 if (this.rsar < STOP_PAGE << 8) { data = this.memory[this.rsar] } if (NE2K_LOG_VERBOSE) { dbg_log( 'Read data port: data=' + h(data, 2) + ' rsar=' + h(this.rsar, 4) + ' rcnt=' + h(this.rcnt, 4), LOG_NET, ) } this.rsar++ this.rcnt-- if (this.rsar >= this.pstop << 8) { this.rsar += (this.pstart - this.pstop) << 8 } if (this.rcnt === 0) { this.do_interrupt(ENISR_RDC) } return data } data_port_read8(): number { return this.data_port_read16() & 0xff } data_port_read16(): number { if (this.dcfg & 1) { return this.data_port_read() | (this.data_port_read() << 8) } else { return this.data_port_read() } } data_port_read32(): number { return ( this.data_port_read() | (this.data_port_read() << 8) | (this.data_port_read() << 16) | (this.data_port_read() << 24) ) } receive(data: Uint8Array): void { // called from the adapter when data is received over the network if (this.cr & 1) { // stop bit set return } if (NE2K_LOG_PACKETS) { dump_packet(data, 'receive') } this.bus.send('eth-receive-end', [data.length]) if (this.rxcr & 0x10) { // promiscuous } else if ( this.rxcr & 4 && data[0] === 0xff && data[1] === 0xff && data[2] === 0xff && data[3] === 0xff && data[4] === 0xff && data[5] === 0xff ) { // broadcast } else if (this.rxcr & 8 && (data[0] & 1) === 1) { // multicast // XXX return } else if ( data[0] === this.mac[0] && data[1] === this.mac[1] && data[2] === this.mac[2] && data[3] === this.mac[3] && data[4] === this.mac[4] && data[5] === this.mac[5] ) { // unicast match, intentionally empty } else { return } if (this.mac_address_in_state) { data = new Uint8Array(data) // make a copy translate_mac_address(data, this.mac, this.mac_address_in_state) } const packet_length = Math.max(60, data.length) const offset = this.curpg << 8 const total_length = packet_length + 4 const data_start = offset + 4 let next = this.curpg + 1 + (total_length >> 8) const end = offset + total_length const needed = 1 + (total_length >> 8) // boundary == curpg interpreted as ringbuffer empty const available = this.boundary > this.curpg ? this.boundary - this.curpg : this.pstop - this.curpg + this.boundary - this.pstart if ( available < needed && this.boundary !== 0 // XXX: ReactOS sets this to 0 initially and never updates it unless it receives a packet ) { dbg_log( 'Buffer full, dropping packet pstart=' + h(this.pstart) + ' pstop=' + h(this.pstop) + ' curpg=' + h(this.curpg) + ' needed=' + h(needed) + ' boundary=' + h(this.boundary) + ' available=' + h(available), LOG_NET, ) return } if (end > this.pstop << 8) { // Shouldn't happen because at this size it can't cross a page, // so we can skip filling with zeroes dbg_assert(data.length >= 60) const cut = (this.pstop << 8) - data_start dbg_assert(cut >= 0) this.memory.set(data.subarray(0, cut), data_start) this.memory.set(data.subarray(cut), this.pstart << 8) dbg_log('rcv cut=' + h(cut), LOG_NET) } else { this.memory.set(data, data_start) if (data.length < 60) { this.memory.fill(0, data_start + data.length, data_start + 60) } } if (next >= this.pstop) { next += this.pstart - this.pstop } // write packet header this.memory[offset] = ENRSR_RXOK // status this.memory[offset + 1] = next this.memory[offset + 2] = total_length this.memory[offset + 3] = total_length >> 8 this.curpg = next dbg_log( 'rcv offset=' + h(offset) + ' len=' + h(total_length) + ' next=' + h(next), LOG_NET, ) this.do_interrupt(ENISR_RX) } get_page(): number { return (this.cr >> 6) & 3 } }