mirror of https://github.com/minexew/Shrine.git
44 changed files with 5722 additions and 5 deletions
@ -0,0 +1,410 @@
|
||||
// vim: set ft=c: |
||||
|
||||
#include "::/Adam/HwSupp/Pci" |
||||
|
||||
// Significantly based on http://wiki.osdev.org/AMD_PCNET |
||||
|
||||
#define PCNET_DEVICE_ID 0x2000 |
||||
#define PCNET_VENDOR_ID 0x1022 |
||||
|
||||
#define PCNET_COMMAND_IOEN (1<<0) |
||||
#define PCNET_COMMAND_MEMEN (1<<1) |
||||
#define PCNET_COMMAND_BMEN (1<<2) |
||||
#define PCNET_COMMAND_SCYCEN (1<<3) |
||||
#define PCNET_COMMAND_MWIEN (1<<4) |
||||
#define PCNET_COMMAND_VGASNOOP (1<<5) |
||||
#define PCNET_COMMAND_PERREN (1<<6) |
||||
#define PCNET_COMMAND_ADSTEP (1<<7) |
||||
#define PCNET_COMMAND_SERREN (1<<8) |
||||
#define PCNET_COMMAND_FBTBEN (1<<9) |
||||
|
||||
#define PCNET_STATUS_FBTBC (1<<7) |
||||
#define PCNET_STATUS_DATAPERR (1<<8) |
||||
#define PCNET_STATUS_DEVSEL_BIT 9 |
||||
#define PCNET_STATUS_STABORT (1<<11) |
||||
#define PCNET_STATUS_RTABORT (1<<12) |
||||
#define PCNET_STATUS_RMABORT (1<<13) |
||||
#define PCNET_STATUS_SERR (1<<14) |
||||
#define PCNET_STATUS_PERR (1<<15) |
||||
|
||||
#define PCNET_WD_RESET 0x14 |
||||
|
||||
#define PCNET_DW_RDP 0x10 |
||||
#define PCNET_DW_RAP 0x14 |
||||
#define PCNET_DW_RESET 0x18 |
||||
|
||||
#define PCNET_CSR0_INIT (1<<0) |
||||
#define PCNET_CSR0_STRT (1<<1) |
||||
#define PCNET_CSR0_STOP (1<<2) |
||||
#define PCNET_CSR0_IENA (1<<6) |
||||
#define PCNET_CSR0_IDON (1<<8) |
||||
#define PCNET_CSR0_TINT (1<<9) |
||||
#define PCNET_CSR0_RINT (1<<10) |
||||
|
||||
#define PCNET_CSR3_BSWP (1<<2) |
||||
#define PCNET_CSR3_IDONM (1<<8) |
||||
#define PCNET_CSR3_TINTM (1<<9) |
||||
#define PCNET_CSR3_RINTM (1<<10) |
||||
|
||||
#define PCNET_CSR4_TXSTRT (1<<3) |
||||
#define PCNET_CSR4_ASTRP_RCV (1<<10) |
||||
#define PCNET_CSR4_APAD_XMT (1<<11) |
||||
|
||||
#define PCNET_TXFIFO_FULL (-1) |
||||
|
||||
// TODO: this should be configurable |
||||
#define PCNET_NUM_RX_LOG2 5 |
||||
#define PCNET_NUM_TX_LOG2 3 |
||||
|
||||
#define rx_buffer_count (1<<PCNET_NUM_RX_LOG2) |
||||
#define tx_buffer_count (1<<PCNET_NUM_TX_LOG2) |
||||
|
||||
// Including SrcAddr, DstAddr, EtherType |
||||
// Where does this even belong? NetFifo defines for now. |
||||
//#define ETHERNET_FRAME_SIZE 1548 |
||||
|
||||
#define PCNET_DE_SIZE 16 |
||||
|
||||
class CPCNetBufferSetup { |
||||
U16 mode; |
||||
U8 rlen; |
||||
U8 tlen; |
||||
U8 mac[6]; |
||||
U16 reserved; |
||||
U8 ladr[8]; |
||||
U32 rxbuf; |
||||
U32 txbuf; |
||||
}; |
||||
|
||||
// Card I/O base |
||||
I64 pcnet_iob = 0; |
||||
|
||||
U8 my_mac[6]; |
||||
|
||||
// Current Rx/Tx buffer |
||||
I64 rx_buffer_ptr = 0; |
||||
I64 tx_buffer_ptr = 0; |
||||
|
||||
// Rx/Tx descriptor ring buffers, PCNET_DE_SIZE each |
||||
// _phys are uncached |
||||
U8* rdes_phys; |
||||
U8* tdes_phys; |
||||
U8* rdes; |
||||
U8* tdes; |
||||
|
||||
U32 rx_buffers; // physical address of actual receive buffers (< 4 GiB) |
||||
U32 tx_buffers; // physical address of actual transmit buffers (< 4 GiB) |
||||
|
||||
static U0 writeRAP32(U32 val) { |
||||
OutU32(pcnet_iob + PCNET_DW_RAP, val); |
||||
} |
||||
|
||||
static U32 readCSR32(U32 csr_no) { |
||||
writeRAP32(csr_no); |
||||
return InU32(pcnet_iob + PCNET_DW_RDP); |
||||
} |
||||
|
||||
static U0 writeCSR32(U32 csr_no, U32 val) { |
||||
writeRAP32(csr_no); |
||||
OutU32(pcnet_iob + PCNET_DW_RDP, val); |
||||
} |
||||
|
||||
static U0 PCNetReset() { |
||||
InU32(pcnet_iob + PCNET_DW_RESET); |
||||
InU16(pcnet_iob + PCNET_WD_RESET); |
||||
} |
||||
|
||||
// does the driver own the particular buffer? |
||||
static I64 driverOwns(U8 *des, I64 idx) |
||||
{ |
||||
return (des[PCNET_DE_SIZE * idx + 7] & 0x80) == 0; |
||||
} |
||||
|
||||
static U0 PCNetReadEeprom(I64 offset, U8* buffer, I64 count) { |
||||
while (count) { |
||||
*buffer = InU32(pcnet_iob + offset); |
||||
offset++; |
||||
buffer++; |
||||
count--; |
||||
} |
||||
} |
||||
|
||||
static I64 PCNetRxPacket(U8** buffer_out, U16* length_out) { |
||||
I64 index = rx_buffer_ptr; |
||||
|
||||
// packet length is given by bytes 8 and 9 of the descriptor |
||||
// (no need to negate it unlike BCNT above) |
||||
U16* p16 = &rdes[index * PCNET_DE_SIZE + 8]; |
||||
U16 length = *p16; |
||||
|
||||
// increment rx_buffer_ptr; |
||||
rx_buffer_ptr = (rx_buffer_ptr + 1) & (rx_buffer_count - 1); |
||||
|
||||
*buffer_out = rx_buffers + index * ETHERNET_FRAME_SIZE; |
||||
*length_out = length; |
||||
return index; |
||||
} |
||||
|
||||
static I64 PCNetReleaseRxPacket(I64 index) { |
||||
rdes[index * PCNET_DE_SIZE + 7] = 0x80; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static I64 PCNetAllocTxPacket(U8** buffer_out, I64 length, I64 flags) { |
||||
// FIXME: validate length |
||||
flags = flags; |
||||
|
||||
if (!driverOwns(tdes, tx_buffer_ptr)) { |
||||
return PCNET_TXFIFO_FULL; |
||||
} |
||||
|
||||
I64 index = tx_buffer_ptr; |
||||
|
||||
// set the STP bit in the descriptor entry (signals this is the first |
||||
// frame in a split packet - we only support single frames) |
||||
tdes[index * PCNET_DE_SIZE + 7] |= 0x2; |
||||
|
||||
// similarly, set the ENP bit to state this is also the end of a packet |
||||
tdes[index * PCNET_DE_SIZE + 7] |= 0x1; |
||||
|
||||
// set the BCNT member to be 0xf000 OR'd with the first 12 bits of the |
||||
// two's complement of the length of the packet |
||||
U16 bcnt = (-length); |
||||
bcnt &= 0xfff; |
||||
bcnt |= 0xf000; |
||||
U16* p16 = &tdes[index * PCNET_DE_SIZE + 4]; |
||||
*p16 = bcnt; |
||||
|
||||
tx_buffer_ptr = (tx_buffer_ptr + 1) & (tx_buffer_count - 1); |
||||
|
||||
*buffer_out = tx_buffers + index * ETHERNET_FRAME_SIZE; |
||||
return index; |
||||
} |
||||
|
||||
static I64 PCNetFinishTxPacket(I64 index) { |
||||
// finally, flip the ownership bit back to the card |
||||
tdes[index * PCNET_DE_SIZE + 7] |= 0x80; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static U0 PCNetInitDE(U32 buf_addr, U8 *des, I64 idx, I64 is_tx) { |
||||
MemSet(&des[idx * PCNET_DE_SIZE], PCNET_DE_SIZE, 0); |
||||
|
||||
// first 4 bytes are the physical address of the actual buffer |
||||
U32* p32 = &des[idx * PCNET_DE_SIZE]; |
||||
*p32 = buf_addr + idx * ETHERNET_FRAME_SIZE; |
||||
|
||||
// next 2 bytes are 0xf000 OR'd with the first 12 bits of the 2s complement of the length |
||||
U16 bcnt = (-ETHERNET_FRAME_SIZE); |
||||
bcnt &= 0x0fff; |
||||
bcnt |= 0xf000; |
||||
|
||||
U16* p16 = &des[idx * PCNET_DE_SIZE + 4]; |
||||
*p16 = bcnt; |
||||
|
||||
// finally, set ownership bit - transmit buffers are owned by us, receive buffers by the card |
||||
if (!is_tx) |
||||
des[idx * PCNET_DE_SIZE + 7] = 0x80; |
||||
} |
||||
|
||||
static I64 PCNetAllocBuffers() { |
||||
I64 i; |
||||
|
||||
I64 rdes_size = PCNET_DE_SIZE * rx_buffer_count; |
||||
I64 tdes_size = PCNET_DE_SIZE * tx_buffer_count; |
||||
|
||||
rdes_phys = MAlloc(rdes_size, Fs->code_heap); |
||||
tdes_phys = MAlloc(tdes_size, Fs->code_heap); |
||||
|
||||
if (rdes_phys + rdes_size > 0x100000000 || tdes_phys + tdes_size > 0x100000000) { |
||||
"$FG,4$PCNetAllocBuffers: rdes_phys=%08Xh tdes_phys=%08Xh\n$FG$", rdes_phys, tdes_phys; |
||||
return -1; |
||||
} |
||||
|
||||
rdes = rdes_phys + dev.uncached_alias; |
||||
tdes = tdes_phys + dev.uncached_alias; |
||||
|
||||
I64 rx_buffers_size = ETHERNET_FRAME_SIZE * rx_buffer_count; |
||||
I64 tx_buffers_size = ETHERNET_FRAME_SIZE * tx_buffer_count; |
||||
|
||||
// TODO: shouldn't these be uncached as well? |
||||
rx_buffers = MAlloc(rx_buffers_size, Fs->code_heap); |
||||
tx_buffers = MAlloc(tx_buffers_size, Fs->code_heap); |
||||
|
||||
if (rx_buffers + rx_buffers_size > 0x100000000 || tx_buffers + tx_buffers_size > 0x100000000) { |
||||
"$FG,4$PCNetAllocBuffers: rx_buffers=%08Xh tx_buffers=%08Xh\n$FG$", rx_buffers, tx_buffers; |
||||
return -1; |
||||
} |
||||
|
||||
for (i = 0; i < rx_buffer_count; i++) |
||||
PCNetInitDE(rx_buffers, rdes, i, FALSE); |
||||
|
||||
for (i = 0; i < tx_buffer_count; i++) |
||||
PCNetInitDE(tx_buffers, tdes, i, TRUE); |
||||
|
||||
//"rdes: %08Xh\ttdes: %08X\n", rdes, tdes; |
||||
//"rbuf: %08Xh\ttbuf: %08X\n", rx_buffers, tx_buffers; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
interrupt U0 PCNetIrq() { |
||||
U32 csr0 = readCSR32(0); |
||||
|
||||
while (driverOwns(rdes, rx_buffer_ptr)) { |
||||
//if (csr0 & PCNET_CSR0_RINT) { |
||||
//"Int reason %08X\n", csr0; |
||||
|
||||
U8* buffer; |
||||
U16 length; |
||||
I64 index = PCNetRxPacket(&buffer, &length); |
||||
|
||||
if (index >= 0) { |
||||
//"[%d] Rx %d B\n", index, length; |
||||
NetFifoPushCopy(buffer, length); |
||||
PCNetReleaseRxPacket(index); |
||||
} |
||||
|
||||
writeCSR32(0, csr0 | PCNET_CSR0_RINT); |
||||
} |
||||
|
||||
*(dev.uncached_alias + LAPIC_EOI)(U32*) = 0; |
||||
} |
||||
|
||||
static U0 PCNetInit(I64 bus, I64 dev_, I64 fun) { |
||||
CPciDevInfo info; |
||||
|
||||
PciGetDevInfo(&info, bus, dev_, fun); |
||||
//PciDumpInfo(&info); |
||||
|
||||
if (info.vendor_id != PCNET_VENDOR_ID || info.device_id != PCNET_DEVICE_ID) |
||||
throw; |
||||
|
||||
U16 config = PCNET_COMMAND_IOEN | PCNET_COMMAND_BMEN; |
||||
PCIWriteU16(bus, dev_, fun, PCI_REG_COMMAND, config); |
||||
|
||||
config = PCIReadU16(bus, dev_, fun, PCI_REG_COMMAND); |
||||
|
||||
pcnet_iob = (PCIReadU32(bus, dev_, fun, PCI_REG_BAR0) & ~(0x0000001f)); |
||||
//U32 membase = (PCIReadU32(bus, dev_, fun, PCI_REG_BAR1) & ~(0x0000001f)); |
||||
//"PCNet iobase: %016Xh\n", pcnet_iob; |
||||
//"PCNet membase: %08Xh\n", membase; |
||||
|
||||
// Reset the card to get into defined state |
||||
PCNetReset(); |
||||
Sleep(1); |
||||
|
||||
// Enter 32-bit mode |
||||
OutU32(pcnet_iob + PCNET_DW_RDP, 0); |
||||
|
||||
// SWSTYLE |
||||
U32 csr58 = readCSR32(58); |
||||
csr58 &= 0xfff0; |
||||
csr58 |= 2; |
||||
writeCSR32(58, csr58); |
||||
|
||||
PCNetReadEeprom(0, my_mac, 6); |
||||
|
||||
if (PCNetAllocBuffers() < 0) |
||||
return; |
||||
|
||||
U8* setup = MAlloc(sizeof(CPCNetBufferSetup), Fs->code_heap); |
||||
CPCNetBufferSetup* u_setup = setup + dev.uncached_alias; |
||||
|
||||
u_setup->mode = 0; // see CSR15 in spec |
||||
u_setup->rlen = (PCNET_NUM_RX_LOG2 << 4); |
||||
u_setup->tlen = (PCNET_NUM_TX_LOG2 << 4); |
||||
MemCpy(u_setup->mac, my_mac, 6); |
||||
"PCNet MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", |
||||
u_setup->mac[0], u_setup->mac[1], u_setup->mac[2], u_setup->mac[3], u_setup->mac[4], u_setup->mac[5]; |
||||
u_setup->reserved = 0; |
||||
MemSet(u_setup->ladr, 0, 8); |
||||
u_setup->rxbuf = rdes_phys; |
||||
u_setup->txbuf = tdes_phys; |
||||
|
||||
U32 p_setup = setup; |
||||
writeCSR32(1, p_setup & 0xffff); |
||||
writeCSR32(2, p_setup >> 16); |
||||
|
||||
U32 csr3 = readCSR32(3); |
||||
csr3 &= ~PCNET_CSR3_BSWP; // disable big-endian |
||||
csr3 &= ~PCNET_CSR3_RINTM; // enable Rx interruot |
||||
csr3 |= PCNET_CSR3_IDONM; // mask-out Init Done Interrupt |
||||
csr3 |= PCNET_CSR3_TINTM; // mask-out Tx interrupt |
||||
writeCSR32(3, csr3); |
||||
|
||||
U32 csr4 = readCSR32(4); |
||||
csr4 |= PCNET_CSR4_APAD_XMT; // auto pad transmit |
||||
writeCSR32(4, csr4); |
||||
|
||||
// Upload configuration |
||||
writeCSR32(0, readCSR32(0) | PCNET_CSR0_INIT | PCNET_CSR0_IENA); |
||||
|
||||
while (!(readCSR32(0) & PCNET_CSR0_IDON)) { |
||||
Yield; |
||||
} |
||||
|
||||
// Exit config mode |
||||
U32 csr0 = readCSR32(0); |
||||
csr0 &= ~(PCNET_CSR0_INIT | PCNET_CSR0_STOP); |
||||
csr0 |= PCNET_CSR0_STRT; |
||||
writeCSR32(0, csr0); |
||||
|
||||
// Init interrupt |
||||
//IntEntrySet(info.interrupt_line, &PCNetIrq, IDTET_IRQ); |
||||
IntEntrySet(0x40, &PCNetIrq, IDTET_IRQ); |
||||
IntEntrySet(0x41, &PCNetIrq, IDTET_IRQ); |
||||
IntEntrySet(0x42, &PCNetIrq, IDTET_IRQ); |
||||
IntEntrySet(0x43, &PCNetIrq, IDTET_IRQ); |
||||
PciRerouteInterrupts(0x40); |
||||
|
||||
Sleep(100); |
||||
|
||||
Free(setup); |
||||
} |
||||
|
||||
I64 EthernetFrameAlloc(U8** buffer_out, U8* src_addr, U8* dst_addr, U16 ethertype, I64 length, I64 flags) { |
||||
U8* frame; |
||||
|
||||
// APAD_XMT doesn't seem to work in VirtualBox, so we have to pad the frame ourselves |
||||
if (length < 46) |
||||
length = 46; |
||||
|
||||
I64 index = PCNetAllocTxPacket(&frame, 14 + length, flags); |
||||
|
||||
if (index < 0) |
||||
return index; |
||||
|
||||
MemCpy(frame + 0, dst_addr, 6); |
||||
MemCpy(frame + 6, src_addr, 6); |
||||
frame[12] = (ethertype >> 8); |
||||
frame[13] = (ethertype & 0xff); |
||||
|
||||
*buffer_out = frame + 14; |
||||
return index; |
||||
} |
||||
|
||||
I64 EthernetFrameFinish(I64 index) { |
||||
return PCNetFinishTxPacket(index); |
||||
} |
||||
|
||||
U8* EthernetGetAddress() { |
||||
return my_mac; |
||||
} |
||||
|
||||
I64 EthernetInit() { |
||||
I64 b, d, f; |
||||
|
||||
if (pcnet_iob != 0) |
||||
return 0; |
||||
|
||||
if (PciFindByID(PCNET_VENDOR_ID, PCNET_DEVICE_ID, &b, &d, &f)) { |
||||
//"PCNet @ %d:%d:%d\n", b, d, f; |
||||
PCNetInit(b, d, f); |
||||
return 0; |
||||
} |
||||
|
||||
return -1; |
||||
} |
@ -0,0 +1,119 @@
|
||||
// vim: set ft=c: |
||||
|
||||
#define PCI_REG_VENDOR_ID 0x00 |
||||
#define PCI_REG_DEVICE_ID 0x02 |
||||
#define PCI_REG_COMMAND 0x04 |
||||
#define PCI_REG_STATUS 0x06 |
||||
#define PCI_REG_REVISION_ID 0x08 |
||||
#define PCI_REG_PROG_IF 0x09 |
||||
#define PCI_REG_SUBCLASS 0x0a |
||||
#define PCI_REG_CLASS 0x0b |
||||
#define PCI_REG_CACHE_LINE_SIZE 0x0c |
||||
#define PCI_REG_LATENCY_TIMER 0x0d |
||||
#define PCI_REG_HEADER_TYPE 0x0e |
||||
#define PCI_REG_BIST 0x0f |
||||
#define PCI_REG_BAR0 0x10 |
||||
#define PCI_REG_BAR1 0x14 |
||||
#define PCI_REG_BAR2 0x18 |
||||
#define PCI_REG_BAR3 0x1c |
||||
#define PCI_REG_BAR4 0x20 |
||||
#define PCI_REG_BAR5 0x24 |
||||
|
||||
#define PCI_REG_INTERRUPT_LINE 0x3C |
||||
|
||||
class CPciDevInfo { |
||||
U16 vendor_id, device_id; |
||||
U16 command, status; |
||||
U8 class_, subclass, prog_if, revision_id; |
||||
U8 cache_line_size, latency_timer, header_type, bist; |
||||
U32 bar[6]; |
||||
|
||||
// ...a lot of trash comes here... |
||||
|
||||
U8 interrupt_line; |
||||
}; |
||||
|
||||
U0 PciDumpInfo(CPciDevInfo* info) { |
||||
I64 i; |
||||
|
||||
"vendor_id=%04Xh\tdevice_id=%04Xh\n", info->vendor_id, info->device_id; |
||||
"command=%04Xh\tstatus=%04Xh\n", info->command, info->status; |
||||
"revision_id=%02Xh\tprog_if=%02Xh\n", info->revision_id, info->prog_if; |
||||
"subclass=%02Xh\tclass_=%02Xh\n", info->subclass, info->class_; |
||||
"cache_line_size=%02Xh\tlatency_timer=%02Xh\n", info->cache_line_size, info->latency_timer; |
||||
"header_type=%02Xh\tbist=%02Xh\n", info->header_type, info->bist; |
||||
|
||||
for (i = 0; i < 6; i++) |
||||
"BAR[%d]=%08X\n", i, info->bar[i]; |
||||
|
||||
"interrupt_line=%02Xh\n", info->interrupt_line; |
||||
} |
||||
|
||||
Bool PciFindByID(U16 vendor_id, U16 device_id, I64* bus_out, I64* dev_out, I64* fun_out) { |
||||
I64 vendor, b, d, f, timeout = 32 * 8 * 2; |
||||
|
||||
if (dev.pci_head.next != &dev.pci_head) |
||||
return FALSE; |
||||
|
||||
for (b = 0; b < sys_pci_busses; b++) { |
||||
for (d = 0; d < 32; d++) { |
||||
for (f = 0; f < 8; f++) { |
||||
vendor = PCIReadU16(b, d, f, PCI_REG_VENDOR_ID); |
||||
|
||||
if (vendor != 0xFFFF) { |
||||
if (vendor == vendor_id && PCIReadU16(b, d, f, PCI_REG_DEVICE_ID) == device_id) { |
||||
*bus_out = b; |
||||
*dev_out = d; |
||||
*fun_out = f; |
||||
return TRUE; |
||||
} |
||||
timeout = 32 * 8 * 2; |
||||
} |
||||
else if (sys_pci_busses == 256 && --timeout <= 0) { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return FALSE; |
||||
} |
||||
|
||||
U0 PciGetDevInfo(CPciDevInfo* info_out, I64 bus, I64 dev, I64 fun) { |
||||
// TODO: do a bunch of PCIReadU32 in a loop instead |
||||
info_out->vendor_id = PCIReadU16(bus, dev, fun, PCI_REG_VENDOR_ID); |
||||
info_out->device_id = PCIReadU16(bus, dev, fun, PCI_REG_DEVICE_ID); |
||||
info_out->command = PCIReadU16(bus, dev, fun, PCI_REG_COMMAND); |
||||
info_out->status = PCIReadU16(bus, dev, fun, PCI_REG_STATUS); |
||||
info_out->revision_id = PCIReadU8(bus, dev, fun, PCI_REG_REVISION_ID); |
||||
info_out->prog_if = PCIReadU8(bus, dev, fun, PCI_REG_PROG_IF); |
||||
info_out->subclass = PCIReadU8(bus, dev, fun, PCI_REG_SUBCLASS); |
||||
info_out->class_ = PCIReadU8(bus, dev, fun, PCI_REG_CLASS); |
||||
info_out->cache_line_size = PCIReadU8(bus, dev, fun, PCI_REG_CACHE_LINE_SIZE); |
||||
info_out->latency_timer = PCIReadU8(bus, dev, fun, PCI_REG_LATENCY_TIMER); |
||||
info_out->header_type = PCIReadU8(bus, dev, fun, PCI_REG_HEADER_TYPE); |
||||
info_out->bist = PCIReadU8(bus, dev, fun, PCI_REG_BIST); |
||||
info_out->bar[0] = PCIReadU32(bus, dev, fun, PCI_REG_BAR0); |
||||
info_out->bar[1] = PCIReadU32(bus, dev, fun, PCI_REG_BAR1); |
||||
info_out->bar[2] = PCIReadU32(bus, dev, fun, PCI_REG_BAR2); |
||||
info_out->bar[3] = PCIReadU32(bus, dev, fun, PCI_REG_BAR3); |
||||
info_out->bar[4] = PCIReadU32(bus, dev, fun, PCI_REG_BAR4); |
||||
info_out->bar[5] = PCIReadU32(bus, dev, fun, PCI_REG_BAR5); |
||||
|
||||
info_out->interrupt_line = PCIReadU8(bus, dev, fun, PCI_REG_INTERRUPT_LINE); |
||||
} |
||||
|
||||
#define INT_DEST_CPU 0 |
||||
|
||||
U0 PciRerouteInterrupts(I64 base) { |
||||
I64 i; |
||||
U8* da = dev.uncached_alias + IOAPIC_REG; |
||||
U32* _d = dev.uncached_alias + IOAPIC_DATA; |
||||
|
||||
for (i = 0; i < 4; i++) { |
||||
*da = IOREDTAB + i * 2 + 1; |
||||
*_d = dev.mp_apic_ids[INT_DEST_CPU] << 24; |
||||
*da = IOREDTAB + i * 2; |
||||
*_d = 0x4000 + base + i; |
||||
} |
||||
} |
@ -0,0 +1,125 @@
|
||||
// vim: set ft=c: |
||||
|
||||
// Not a Network Layer protocol, but it is encapsulated in L2 frames, which makes it L3 for our purposes |
||||
|
||||
#define ARP_REQUEST 0x01 |
||||
#define ARP_REPLY 0x02 |
||||
|
||||
class CArpHeader { |
||||
U16 htype; |
||||
U16 ptype; |
||||
U8 hlen; |
||||
U8 plen; |
||||
U16 oper; |
||||
U8 sha[6]; |
||||
U32 spa; |
||||
U8 tha[6]; |
||||
U32 tpa; |
||||
}; |
||||
|
||||
class CArpCacheEntry { |
||||
CArpCacheEntry* next; |
||||
U32 ip; |
||||
U8 mac[6]; |
||||
}; |
||||
|
||||
// Stored in network order |
||||
static U32 arp_my_ipv4_n = 0; |
||||
|
||||
// TODO: use a Hash table |
||||
static CArpCacheEntry* arp_cache = NULL; |
||||
|
||||
// IPs are in network order |
||||
I64 ArpSend(U16 oper, U8* dest_mac, U8* sender_mac, U32 sender_ip_n, U8* target_mac, U32 target_ip_n) { |
||||
U8* frame; |
||||
|
||||
I64 index = EthernetFrameAlloc(&frame, sender_mac, dest_mac, |
||||
ETHERTYPE_ARP, sizeof(CArpHeader), 0); |
||||
|
||||
if (index < 0) |
||||
return index; |
||||
|
||||
CArpHeader* hdr = frame; |
||||
hdr->htype = htons(1); |
||||
hdr->ptype = htons(ETHERTYPE_IPV4); |
||||
hdr->hlen = 6; |
||||
hdr->plen = 4; |
||||
hdr->oper = htons(oper); |
||||
MemCpy(hdr->sha, sender_mac, 6); |
||||
hdr->spa = sender_ip_n; |
||||
MemCpy(hdr->tha, target_mac, 6); |
||||
hdr->tpa = target_ip_n; |
||||
|
||||
return EthernetFrameFinish(index); |
||||
} |
||||
|
||||
U0 ArpSetIPv4Address(U32 addr) { |
||||
arp_my_ipv4_n = htonl(addr); |
||||
|
||||
// Broadcast our new address |
||||
ArpSend(ARP_REPLY, eth_broadcast, EthernetGetAddress(), arp_my_ipv4_n, eth_null, arp_my_ipv4_n); |
||||
} |
||||
|
||||
CArpCacheEntry* ArpCacheFindByIP(U32 ip) { |
||||
CArpCacheEntry* e = arp_cache; |
||||
|
||||
while (e) { |
||||
if (e->ip == ip) |
||||
return e; |
||||
e = e->next; |
||||
} |
||||
|
||||
return e; |
||||
} |
||||
|
||||
CArpCacheEntry* ArpCachePut(U32 ip, U8* mac) { |
||||
CArpCacheEntry* e = ArpCacheFindByIP(ip); |
||||
|
||||
if (!e) { |
||||
//"ARP: add entry for %08X\n", ip; |
||||
e = MAlloc(sizeof(CArpCacheEntry)); |
||||
e->next = arp_cache; |
||||
e->ip = ip; |
||||
MemCpy(e->mac, mac, 6); |
||||
arp_cache = e; |
||||
} |
||||
// FIXME: else replace! |
||||
|
||||
return e; |
||||
} |
||||
|
||||
I64 ArpHandler(CEthFrame* eth_frame) { |
||||
if (eth_frame->ethertype != ETHERTYPE_ARP) |
||||
return -1; |
||||
|
||||
if (eth_frame->length < sizeof(CArpHeader)) |
||||
return -1; |
||||
|
||||
CArpHeader* hdr = eth_frame->data; |
||||
U16 oper = ntohs(hdr->oper); |
||||
|
||||
//"ARP: htype %d, ptype %d, hlen %d, plen %d, oper %d\n", |
||||
// ntohs(hdr->htype), ntohs(hdr->ptype), hdr->hlen, hdr->plen, oper; |
||||
//" spa %08X, tpa %08X\n", ntohl(hdr->spa), ntohl(hdr->tpa); |
||||
|
||||
if (ntohs(hdr->htype) != 1 || ntohs(hdr->ptype) != ETHERTYPE_IPV4 |
||||
|| hdr->hlen != 6 || hdr->plen != 4) |
||||
return -1; |
||||
|
||||
if (oper == ARP_REQUEST) { |
||||
// Not too sure about this line, but it seems necessary in WiFi networks, |
||||
// because the wireless device won't hear our Ethernet broadcast when we Request |
||||
//ArpCachePut(ntohl(hdr->spa), hdr->sha); |
||||
|
||||
if (hdr->tpa == arp_my_ipv4_n) { |
||||
ArpSend(ARP_REPLY, hdr->sha, EthernetGetAddress(), arp_my_ipv4_n, hdr->sha, hdr->spa); |
||||
} |
||||
} |
||||
else if (oper == ARP_REPLY) { |
||||
ArpCachePut(ntohl(hdr->spa), hdr->sha); |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
RegisterL3Protocol(ETHERTYPE_ARP, &ArpHandler); |
@ -0,0 +1,275 @@
|
||||
// vim: set ft=c: |
||||
|
||||
#include "::/Adam/Net/Socket" |
||||
|
||||
#define BOOTREQUEST 0x01 |
||||
#define BOOTREPLY 0x02 |
||||
|
||||
#define HTYPE_ETHERNET 0x01 |
||||
|
||||
#define HLEN_ETHERNET 6 |
||||
|
||||
#define DHCP_OPTION_SUBNET_MASK 1 |
||||
#define DHCP_OPTION_ROUTER 3 |
||||
#define DHCP_OPTION_DNS 6 |
||||
#define DHCP_OPTION_DOMAIN_NAME 15 |
||||
#define DHCP_OPTION_REQUESTED_IP 50 |
||||
#define DHCP_OPTION_MSGTYPE 53 |
||||
#define DHCP_OPTION_SERVER_ID 54 |
||||
#define DHCP_OPTION_PARAMLIST 55 |
||||
|
||||
#define DHCP_COOKIE 0x63825363 |
||||
#define DHCP_MSGTYPE_DISCOVER 0x01 |
||||
#define DHCP_MSGTYPE_OFFER 0x02 |
||||
#define DHCP_MSGTYPE_REQUEST 0x03 |
||||
#define DHCP_MSGTYPE_ACK 0x05 |
||||
|
||||
class CDhcpHeader { |
||||
U8 op; |
||||
U8 htype; |
||||
U8 hlen; |
||||
U8 hops; |
||||
U32 xid; |
||||
U16 secs; |
||||
U16 flags; |
||||
U32 ciaddr; |
||||
U32 yiaddr; |
||||
U32 siaddr; |
||||
U32 giaddr; |
||||
U8 chaddr[16]; |
||||
U8 sname[64]; |
||||
U8 file[128]; |
||||
}; |
||||
|
||||
class CDhcpDiscoverOptions { |
||||
U32 cookie; |
||||
// DHCP Message Type |
||||
U8 dmt_type; |
||||
U8 dmt_length; |
||||
U8 dmt; |
||||
// DHCP Parameter Request List |
||||
U8 prl_type; |
||||
U8 prl_length; |
||||
U8 prl[4]; |
||||
|
||||
U8 end; |
||||
}; |
||||
|
||||
class CDhcpRequestOptions { |
||||
U32 cookie; |
||||
// DHCP Message Type |
||||
U8 dmt_type; |
||||
U8 dmt_length; |
||||
U8 dmt; |
||||
// DHCP Requested IP |
||||
U8 requested_ip_type; |
||||
U8 requested_ip_length; |
||||
U32 requested_ip; |
||||
// DHCP Server Identifier |
||||
U8 server_id_type; |
||||
U8 server_id_length; |
||||
U32 server_id; |
||||
|
||||
U8 end; |
||||
}; |
||||
|
||||
U32 DhcpBeginTransaction() { |
||||
return RandU32(); |
||||
} |
||||
|
||||
I64 DhcpSendDiscover(U32 xid) { |
||||
U8* frame; |
||||
I64 index = UdpPacketAlloc(&frame, 0x00000000, 68, 0xffffffff, 67, |
||||
sizeof(CDhcpHeader) + sizeof(CDhcpDiscoverOptions)); |
||||
|
||||
if (index < 0) |
||||
return index; |
||||
|
||||
CDhcpHeader* dhcp = frame; |
||||
MemSet(dhcp, 0, sizeof(CDhcpHeader)); |
||||
dhcp->op = BOOTREQUEST; |
||||
dhcp->htype = HTYPE_ETHERNET; |
||||
dhcp->hlen = HLEN_ETHERNET; |
||||
dhcp->hops = 0; |
||||
dhcp->xid = htonl(xid); |
||||
dhcp->secs = 0; |
||||
dhcp->flags = htons(0x8000); |
||||
dhcp->ciaddr = 0; |
||||
dhcp->yiaddr = 0; |
||||
dhcp->siaddr = 0; |
||||
dhcp->giaddr = 0; |
||||
MemCpy(dhcp->chaddr, EthernetGetAddress(), 6); |
||||
|
||||
CDhcpDiscoverOptions* opts = frame + sizeof(CDhcpHeader); |
||||
opts->cookie = htonl(DHCP_COOKIE); |
||||
opts->dmt_type = DHCP_OPTION_MSGTYPE; |
||||
opts->dmt_length = 1; |
||||
opts->dmt = DHCP_MSGTYPE_DISCOVER; |
||||
opts->prl_type = DHCP_OPTION_PARAMLIST; |
||||
opts->prl_length = 4; |
||||
opts->prl[0] = DHCP_OPTION_SUBNET_MASK; |
||||
opts->prl[1] = DHCP_OPTION_ROUTER; |
||||
opts->prl[2] = DHCP_OPTION_DNS; |
||||
opts->prl[3] = DHCP_OPTION_DOMAIN_NAME; |
||||
opts->end = 0xff; |
||||
|
||||
return UdpPacketFinish(index); |
||||
} |
||||
|
||||
I64 DhcpSendRequest(U32 xid, U32 requested_ip, U32 siaddr) { |
||||
U8* frame; |
||||
I64 index = UdpPacketAlloc(&frame, 0x00000000, 68, 0xffffffff, 67, |
||||
sizeof(CDhcpHeader) + sizeof(CDhcpRequestOptions)); |
||||
|
||||
if (index < 0) |
||||
return index; |
||||
|
||||
CDhcpHeader* dhcp = frame; |
||||
MemSet(dhcp, 0, sizeof(CDhcpHeader)); |
||||
dhcp->op = BOOTREQUEST; |
||||
dhcp->htype = HTYPE_ETHERNET; |
||||
dhcp->hlen = HLEN_ETHERNET; |
||||
dhcp->hops = 0; |
||||
dhcp->xid = htonl(xid); |
||||
dhcp->secs = 0; |
||||
dhcp->flags = htons(0x0000); |
||||
dhcp->ciaddr = 0; |
||||
dhcp->yiaddr = 0; |
||||
dhcp->siaddr = htonl(siaddr); |
||||
dhcp->giaddr = 0; |
||||
MemCpy(dhcp->chaddr, EthernetGetAddress(), 6); |
||||
|
||||
CDhcpRequestOptions* opts = frame + sizeof(CDhcpHeader); |
||||
opts->cookie = htonl(DHCP_COOKIE); |
||||
opts->dmt_type = DHCP_OPTION_MSGTYPE; |
||||
opts->dmt_length = 1; |
||||
opts->dmt = DHCP_MSGTYPE_REQUEST; |
||||
opts->requested_ip_type = DHCP_OPTION_REQUESTED_IP; |
||||
opts->requested_ip_length = 4; |
||||
opts->requested_ip = htonl(requested_ip); |
||||
opts->server_id_type = DHCP_OPTION_SERVER_ID; |
||||
opts->server_id_length = 4; |
||||
opts->server_id = htonl(siaddr); |
||||
opts->end = 0xff; |
||||
|
||||
return UdpPacketFinish(index); |
||||
} |
||||
|
||||
I64 DhcpParseBegin(U8** data_inout, I64* length_inout, CDhcpHeader** hdr_out) { |
||||
U8* data = *data_inout; |
||||
I64 length = *length_inout; |
||||
|
||||
if (length < sizeof(CDhcpHeader) + 4) { |
||||
//"DhcpParseBegin: too short\n"; |
||||
return -1; |
||||
} |
||||
|
||||
U32* p_cookie = data + sizeof(CDhcpHeader); |
||||
|
||||
if (ntohl(*p_cookie) != DHCP_COOKIE) { |
||||
//"DhcpParseBegin: cookie %08Xh != %08Xh\n", ntohl(*p_cookie), DHCP_COOKIE; |
||||
return -1; |
||||
} |
||||
|
||||
*hdr_out = data; |
||||
*data_inout = data + (sizeof(CDhcpHeader) + 4); |
||||
*length_inout = length - (sizeof(CDhcpHeader) + 4); |
||||
return 0; |
||||
} |
||||
|
||||
I64 DhcpParseOption(U8** data_inout, I64* length_inout, U8* type_out, U8* value_length_out, U8** value_out) { |
||||
U8* data = *data_inout; |
||||
I64 length = *length_inout; |
||||
|
||||
if (length < 2 || length < 2 + data[1]) { |
||||
//"DhcpParseOption: too short\n"; |
||||
return -1; |
||||
} |
||||
|
||||
if (data[0] == 0xff) |
||||
return 0; |
||||
|
||||
*type_out = data[0]; |
||||
*value_length_out = data[1]; |
||||
*value_out = data + 2; |
||||
|
||||
*data_inout = data + (2 + *value_length_out); |
||||
*length_inout = length - (2 + *value_length_out); |
||||
return data[0]; |
||||
} |
||||
|
||||
I64 DhcpParseOffer(U32 xid, U8* data, I64 length, U32* yiaddr_out, |
||||
U32* dns_ip_out, U32* router_ip_out, U32* subnet_mask_out) { |
||||
CDhcpHeader* hdr; |
||||
I64 error = DhcpParseBegin(&data, &length, &hdr); |
||||
if (error < 0) return error; |
||||
|
||||
if (ntohl(hdr->xid) != xid) |
||||
return -1; |
||||
|
||||
Bool have_type = FALSE; |
||||
Bool have_dns = FALSE; |
||||
Bool have_router = FALSE; |
||||
Bool have_subnet = FALSE; |
||||
|
||||
while (length) { |
||||
U8 type, value_length; |
||||
U8* value; |
||||
|
||||
error = DhcpParseOption(&data, &length, &type, &value_length, &value); |
||||
//"%d, %02Xh, %d, %02Xh...\n", error, type, value_length, value[0]; |
||||
if (error < 0) return error; |
||||
if (error == 0) break; |
||||
|
||||
if (type == DHCP_OPTION_MSGTYPE && value_length == 1 && value[0] == DHCP_MSGTYPE_OFFER) |
||||
have_type = TRUE; |
||||
|
||||
if (type == DHCP_OPTION_DNS && value_length == 4) { |
||||
*dns_ip_out = ntohl(*(value(U32*))); |
||||
have_dns = TRUE; |
||||
} |
||||
|
||||
if (type == DHCP_OPTION_ROUTER && value_length == 4) { |
||||
*router_ip_out = ntohl(*(value(U32*))); |
||||
have_router = TRUE; |
||||
} |
||||
|
||||
if (type == DHCP_OPTION_SUBNET_MASK && value_length == 4) { |
||||
*subnet_mask_out = ntohl(*(value(U32*))); |
||||
have_subnet = TRUE; |
||||
} |
||||
} |
||||
|
||||
//"DhcpParseOffer: end %d %d %d %d\n", have_type, have_dns, have_subnet, have_router; |
||||
|
||||
if (have_type && have_dns && have_subnet && have_router) { |
||||
*yiaddr_out = ntohl(hdr->yiaddr); |
||||
return 0; |
||||
} |
||||
else |
||||
return -1; |
||||
} |
||||
|
||||
I64 DhcpParseAck(U32 xid, U8* data, I64 length) { |
||||
CDhcpHeader* hdr; |
||||
I64 error = DhcpParseBegin(&data, &length, &hdr); |
||||
if (error < 0) return error; |
||||
|
||||
if (ntohl(hdr->xid) != xid) |
||||
return -1; |
||||
|
||||
while (length) { |
||||
U8 type, value_length; |
||||
U8* value; |
||||
|
||||
error = DhcpParseOption(&data, &length, &type, &value_length, &value); |
||||
//"%d, %02Xh, %d, %02Xh...\n", error, type, value_length, value[0]; |
||||
if (error < 0) return error; |
||||
if (error == 0) break; |
||||
|
||||
if (type == DHCP_OPTION_MSGTYPE && value_length == 1 && value[0] == DHCP_MSGTYPE_ACK) |
||||
return 0; |
||||
} |
||||
|
||||
return -1; |
||||
} |
@ -0,0 +1,538 @@
|
||||
// vim: set ft=c: |
||||
|
||||
#define DNS_RCODE_NO_ERROR 0 |
||||
#define DNS_RCODE_FORMAT_ERROR 1 |
||||
#define DNS_RCODE_SERVER_FAILURE 2 |
||||
#define DNS_RCODE_NAME_ERROR 3 |
||||
#define DNS_RCODE_NOT_IMPLEMENTED 5 |
||||
#define DNS_RCODE_REFUSED 6 |
||||
|
||||
#define DNS_FLAG_RA 0x0080 |
||||
#define DNS_FLAG_RD 0x0100 |
||||
#define DNS_FLAG_TC 0x0200 |
||||
#define DNS_FLAG_AA 0x0400 |
||||
|
||||
#define DNS_OP_QUERY 0 |
||||
#define DNS_OP_IQUERY 1 |
||||
#define DNS_OP_STATUS 2 |
||||
|
||||
#define DNS_FLAG_QR 0x8000 |
||||
|
||||
// http://www.freesoft.org/CIE/RFC/1035/14.htm |
||||
#define DNS_TYPE_A 1 |
||||
#define DNS_TYPE_NS 2 |
||||
#define DNS_TYPE_CNAME 5 |
||||
#define DNS_TYPE_PTR 12 |
||||
#define DNS_TYPE_MX 15 |
||||
#define DNS_TYPE_TXT 16 |
||||
|
||||
// http://www.freesoft.org/CIE/RFC/1035/16.htm |
||||
#define DNS_CLASS_IN 1 |
||||
|
||||
#define DNS_TIMEOUT 5000 |
||||
#define DNS_MAX_RETRIES 3 |
||||
|
||||
class CDnsCacheEntry { |
||||
CDnsCacheEntry* next; |
||||
U8* hostname; |
||||
addrinfo info; |
||||
// TODO: honor TTL |
||||
}; |
||||
|
||||
class CDnsHeader { |
||||
U16 id; |
||||
U16 flags; |
||||
U16 qdcount; |
||||
U16 ancount; |
||||
U16 nscount; |
||||
U16 arcount; |
||||
}; |
||||
|
||||
class CDnsDomainName { |
||||
U8** labels; |
||||
I64 num_labels; |
||||
} |
||||
|
||||
class CDnsQuestion { |
||||
CDnsQuestion* next; |
||||
|
||||
CDnsDomainName qname; |
||||
U16 qtype; |
||||
U16 qclass; |
||||
}; |
||||
|
||||
class CDnsRR { |
||||
CDnsRR* next; |
||||
|
||||
CDnsDomainName name; |
||||
U16 type; |
||||
U16 class_; |
||||
U32 ttl; |
||||
U16 rdlength; |
||||
U8* rdata; |
||||
}; |
||||
|
||||
// TODO: use a Hash table |
||||
static CDnsCacheEntry* dns_cache = NULL; |
||||
|
||||
static U32 dns_ip = 0; |
||||
|
||||
static CDnsCacheEntry* DnsCacheFind(U8* hostname) { |
||||
CDnsCacheEntry* e = dns_cache; |
||||
|
||||
while (e) { |
||||
if (!StrCmp(e->hostname, hostname)) |
||||
return e; |
||||
|
||||
e = e->next; |
||||
} |
||||
|
||||
return e; |
||||
} |
||||
|
||||
static CDnsCacheEntry* DnsCachePut(U8* hostname, addrinfo* info) { |
||||
CDnsCacheEntry* e = DnsCacheFind(hostname); |
||||
|
||||
if (!e) { |
||||
e = MAlloc(sizeof(CDnsCacheEntry)); |
||||
e->next = dns_cache; |
||||
e->hostname = StrNew(hostname); |
||||
AddrInfoCopy(&e->info, info); |
||||
|
||||
dns_cache = e; |
||||
} |
||||
|
||||
return e; |
||||
} |
||||
|
||||
static I64 DnsCalcQuestionSize(CDnsQuestion* question) { |
||||
I64 size = 0; |
||||
I64 i; |
||||
for (i = 0; i < question->qname.num_labels; i++) { |
||||
size += 1 + StrLen(question->qname.labels[i]); |
||||
} |
||||
return size + 1 + 4; |
||||
} |
||||
|
||||
static U0 DnsSerializeQuestion(U8* buf, CDnsQuestion* question) { |
||||
I64 i; |
||||
|
||||
for (i = 0; i < question->qname.num_labels; i++) { |
||||
U8* label = question->qname.labels[i]; |
||||
*(buf++) = StrLen(label); |
||||
|
||||
while (*label) |
||||
*(buf++) = *(label++); |
||||
} |
||||
|
||||
*(buf++) = 0; |
||||
*(buf++) = (question->qtype >> 8); |
||||
*(buf++) = (question->qtype & 0xff); |
||||
*(buf++) = (question->qclass >> 8); |
||||
*(buf++) = (question->qclass & 0xff); |
||||
} |
||||
|
||||
static I64 DnsSendQuestion(U16 id, U16 local_port, CDnsQuestion* question) { |
||||
if (!dns_ip) |
||||
return -1; |
||||
|
||||
U8* frame; |
||||
I64 index = UdpPacketAlloc(&frame, IPv4GetAddress(), local_port, dns_ip, 53, |
||||
sizeof(CDnsHeader) + DnsCalcQuestionSize(question)); |
||||
|
||||
if (index < 0) |
||||
return index; |
||||
|
||||
U16 flags = (DNS_OP_QUERY << 11) | DNS_FLAG_RD; |
||||
|
||||
CDnsHeader* hdr = frame; |
||||
hdr->id = htons(id); |
||||
hdr->flags = htons(flags); |
||||
hdr->qdcount = htons(1); |
||||
hdr->ancount = 0; |
||||
hdr->nscount = 0; |
||||
hdr->arcount = 0; |
||||
|
||||
DnsSerializeQuestion(frame + sizeof(CDnsHeader), question); |
||||
|
||||
return UdpPacketFinish(index); |
||||
} |
||||
|
||||
static I64 DnsParseDomainName(U8* packet_data, I64 packet_length, |
||||
U8** data_inout, I64* length_inout, CDnsDomainName* name_out) { |
||||
U8* data = *data_inout; |
||||
I64 length = *length_inout; |
||||
Bool jump_taken = FALSE; |
||||
|
||||
if (length < 1) { |
||||
//"DnsParseDomainName: EOF\n"; |
||||
return -1; |
||||
} |
||||
|
||||
name_out->labels = MAlloc(16 * sizeof(U8*)); |
||||
name_out->num_labels = 0; |
||||
|
||||
U8* name_buf = MAlloc(256); |
||||
name_out->labels[0] = name_buf; |
||||
|
||||
while (length) { |
||||
I64 label_len = *(data++); |
||||
length--; |
||||
|
||||
if (label_len == 0) { |
||||
break; |
||||
} |
||||
else if (label_len >= 192) { |
||||
label_len &= 0x3f; |
||||
|
||||
if (!jump_taken) { |
||||
*data_inout = data + 1; |
||||
*length_inout = length - 1; |
||||
jump_taken = TRUE; |
||||
} |
||||
|
||||
//"jmp %d\n", ((label_len << 8) | *data); |
||||
|
||||
data = packet_data + ((label_len << 8) | *data); |
||||
length = packet_data + packet_length - data; |
||||
} |
||||
else { |
||||
if (length < label_len) return -1; |
||||
|
||||
MemCpy(name_buf, data, label_len); |
||||
data += label_len; |
||||
length -= label_len; |
||||
|
||||
name_buf[label_len] = 0; |
||||
//"%d bytes => %s\n", label_len, name_buf; |
||||
name_out->labels[name_out->num_labels++] = name_buf; |
||||
|
||||
name_buf += label_len + 1; |
||||
} |
||||
} |
||||
|
||||
if (!jump_taken) { |
||||
*data_inout = data; |
||||
*length_inout = length; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static I64 DnsParseQuestion(U8* packet_data, I64 packet_length, |
||||
U8** data_inout, I64* length_inout, CDnsQuestion* question_out) { |
||||
I64 error = DnsParseDomainName(packet_data, packet_length, |
||||
data_inout, length_inout, &question_out->qname); |
||||
|
||||
if (error < 0) |
||||
return error; |
||||
|
||||
U8* data = *data_inout; |
||||
I64 length = *length_inout; |
||||
|
||||
if (length < 4) |
||||
return -1; |
||||
|
||||
question_out->next = NULL; |
||||
question_out->qtype = (data[1] << 8) | data[0]; |
||||
question_out->qclass = (data[3] << 8) | data[2]; |
||||
|
||||
//"DnsParseQuestion: qtype %d, qclass %d\n", ntohs(question_out->qtype), ntohs(question_out->qclass); |
||||
|
||||
*data_inout = data + 4; |
||||
*length_inout = length - 4; |
||||
return 0; |
||||
} |
||||
|
||||
static I64 DnsParseRR(U8* packet_data, I64 packet_length, |
||||
U8** data_inout, I64* length_inout, CDnsRR* rr_out) { |
||||
I64 error = DnsParseDomainName(packet_data, packet_length, |
||||
data_inout, length_inout, &rr_out->name); |
||||
|
||||
if (error < 0) |
||||
return error; |
||||
|
||||
U8* data = *data_inout; |
||||
I64 length = *length_inout; |
||||
|
||||
if (length < 10) |
||||
return -1; |
||||
|
||||
rr_out->next = NULL; |
||||
MemCpy(&rr_out->type, data, 10); |
||||
|
||||
I64 record_length = 10 + ntohs(rr_out->rdlength); |
||||
|
||||
if (length < record_length) |
||||
return -1; |
||||
|
||||
rr_out->rdata = data + 10; |
||||
|
||||
//"DnsParseRR: type %d, class %d\n, ttl %d, rdlength %d\n", |
||||
// ntohs(rr_out->type), ntohs(rr_out->class_), ntohl(rr_out->ttl), ntohs(rr_out->rdlength); |
||||
|
||||
*data_inout = data + record_length; |
||||
*length_inout = length - record_length; |
||||
return 0; |
||||
} |
||||
|
||||
static I64 DnsParseResponse(U16 id, U8* data, I64 length, |
||||
CDnsHeader** hdr_out, CDnsQuestion** questions_out, |
||||
CDnsRR** answers_out) { |
||||
U8* packet_data = data; |
||||
I64 packet_length = length; |
||||
|
||||
if (length < sizeof(CDnsHeader)) { |
||||
//"DnsParseResponse: too short\n"; |
||||
return -1; |
||||
} |
||||
|
||||
CDnsHeader* hdr = data; |
||||
data += sizeof(CDnsHeader); |
||||
|
||||
if (id != 0 && ntohs(hdr->id) != id) { |
||||
//"DnsParseResponse: id %04Xh != %04Xh\n", ntohs(hdr->id), id; |
||||
return -1; |
||||
} |
||||
|
||||
I64 i; |
||||
|
||||
for (i = 0; i < htons(hdr->qdcount); i++) { |
||||
CDnsQuestion* question = MAlloc(sizeof(CDnsQuestion)); |
||||
if (DnsParseQuestion(packet_data, packet_length, &data, &length, question) < 0) |
||||
return -1; |
||||
|
||||
question->next = *questions_out; |
||||
*questions_out = question; |
||||
} |
||||
|
||||
for (i = 0; i < htons(hdr->ancount); i++) { |
||||
CDnsRR* answer = MAlloc(sizeof(CDnsRR)); |
||||
if (DnsParseRR(packet_data, packet_length, &data, &length, answer) < 0) |
||||
return -1; |
||||
|
||||
answer->next = *answers_out; |
||||
*answers_out = answer; |
||||
} |
||||
|
||||
*hdr_out = hdr; |
||||
return 0; |
||||
} |
||||
|
||||
static U0 DnsBuildQuestion(CDnsQuestion* question, U8* name) { |
||||
question->next = NULL; |
||||
question->qname.labels = MAlloc(16 * sizeof(U8*)); |
||||
question->qname.labels[0] = 0; |
||||
question->qname.num_labels = 0; |
||||
question->qtype = DNS_TYPE_A; |
||||
question->qclass = DNS_CLASS_IN; |
||||
|
||||
U8* copy = StrNew(name); |
||||
|
||||
while (*copy) { |
||||
question->qname.labels[question->qname.num_labels++] = copy; |
||||
U8* dot = StrFirstOcc(copy, "."); |
||||
|
||||
if (dot) { |
||||
*dot = 0; |
||||
copy = dot + 1; |
||||
} |
||||
else |
||||
break; |
||||
} |
||||
} |
||||
|
||||
static U0 DnsFreeQuestion(CDnsQuestion* question) { |
||||
Free(question->qname.labels[0]); |
||||
} |
||||
|
||||
static U0 DnsFreeRR(CDnsRR* rr) { |
||||
Free(rr->name.labels[0]); |
||||
} |
||||
|
||||
static U0 DnsFreeQuestionChain(CDnsQuestion* questions) { |
||||
while (questions) { |
||||
CDnsQuestion* next = questions->next; |
||||
DnsFreeQuestion(questions); |
||||
Free(questions); |
||||
questions = next; |
||||
} |
||||
} |
||||
|
||||
static U0 DnsFreeRRChain(CDnsRR* rrs) { |
||||
while (rrs) { |
||||
CDnsQuestion* next = rrs->next; |
||||
DnsFreeRR(rrs); |
||||
Free(rrs); |
||||
rrs = next; |
||||
} |
||||
} |
||||
|
||||
static I64 DnsRunQuery(I64 sock, U8* name, U16 port, addrinfo** res_out) { |
||||
I64 retries = 0; |
||||
I64 timeout = DNS_TIMEOUT; |
||||
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO_MS, &timeout, sizeof(timeout)) < 0) { |
||||
"$FG,6$DnsRunQuery: setsockopt failed\n$FG$"; |
||||
} |
||||
|
||||
U16 local_port = RandU16(); |
||||
|
||||
sockaddr_in addr; |
||||
addr.sin_family = AF_INET; |
||||
addr.sin_port = htons(local_port); |
||||
addr.sin_addr.s_addr = INADDR_ANY; |
||||
|
||||
if (bind(sock, &addr, sizeof(addr)) < 0) { |
||||
"$FG,4$DnsRunQuery: failed to bind\n$FG$"; |
||||
return -1; |
||||
} |
||||
|
||||
U8 buffer[2048]; |
||||
|
||||
I64 count; |
||||
sockaddr_in addr_in; |
||||
|
||||
U16 id = RandU16(); |
||||
I64 error = 0; |
||||
|
||||
CDnsQuestion question; |
||||
DnsBuildQuestion(&question, name); |
||||
|
||||
while (1) { |
||||
error = DnsSendQuestion(id, local_port, &question); |
||||
if (error < 0) return error; |
||||
|
||||
count = recvfrom(sock, buffer, sizeof(buffer), 0, &addr_in, sizeof(addr_in)); |
||||
|
||||
if (count > 0) { |
||||
//"Try parse response\n"; |
||||
CDnsHeader* hdr = NULL; |
||||
CDnsQuestion* questions = NULL; |
||||
CDnsRR* answers = NULL; |
||||
|
||||
error = DnsParseResponse(id, buffer, count, &hdr, &questions, &answers); |
||||
|
||||
if (error >= 0) { |
||||
Bool have = FALSE; |
||||
|
||||
// Look for a suitable A-record in the answer |
||||
CDnsRR* answer = answers; |
||||
while (answer) { |
||||
// TODO: if there are multiple acceptable answers, |
||||
// we should pick one at random -- not just the first one |
||||
if (htons(answer->type) == DNS_TYPE_A |
||||
&& htons(answer->class_) == DNS_CLASS_IN |
||||
&& htons(answer->rdlength) == 4) { |
||||
addrinfo* res = MAlloc(sizeof(addrinfo)); |
||||
res->ai_flags = 0; |
||||
res->ai_family = AF_INET; |
||||
res->ai_socktype = 0; |
||||
res->ai_protocol = 0; |
||||
res->ai_addrlen = sizeof(sockaddr_in); |
||||
res->ai_addr = MAlloc(sizeof(sockaddr_in)); |
||||
res->ai_canonname = NULL; |
||||
res->ai_next = NULL; |
||||
|
||||
sockaddr_in* sa = res->ai_addr; |
||||
sa->sin_family = AF_INET; |
||||
sa->sin_port = port; |
||||
MemCpy(&sa->sin_addr.s_addr, answers->rdata, 4); |
||||
|
||||
DnsCachePut(name, res); |
||||
*res_out = res; |
||||
have = TRUE; |
||||
break; |
||||
} |
||||
|
||||
answer = answer->next; |
||||
} |
||||
|
||||
DnsFreeQuestionChain(questions); |
||||
DnsFreeRRChain(answers); |
||||
|
||||
if (have) |
||||
break; |
||||
|
||||
// At this point we could try iterative resolution, |
||||
// but all end-user DNS servers would have tried that already |
||||
|
||||
"$FG,6$DnsParseResponse: no suitable answer in reply\n$FG$"; |
||||
error = -1; |
||||
} |
||||
else { |
||||
"$FG,6$DnsParseResponse: error %d\n$FG$", error; |
||||
} |
||||
} |
||||
|
||||
if (++retries == DNS_MAX_RETRIES) { |
||||
"$FG,4$DnsRunQuery: max retries reached\n$FG$"; |
||||
error = -1; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
DnsFreeQuestion(&question); |
||||
return error; |
||||
} |
||||
|
||||
I64 DnsGetaddrinfo(U8* node, U8* service, addrinfo* hints, addrinfo** res) { |
||||
no_warn service; |
||||
no_warn hints; |
||||
|
||||
CDnsCacheEntry* cached = DnsCacheFind(node); |
||||
|
||||
if (cached) { |
||||
*res = MAlloc(sizeof(addrinfo)); |
||||
AddrInfoCopy(*res, &cached->info); |
||||
(*res)->ai_flags |= AI_CACHED; |
||||
return 0; |
||||
} |
||||
|
||||
I64 sock = socket(AF_INET, SOCK_DGRAM); |
||||
I64 error = 0; |
||||
|
||||
if (sock >= 0) { |
||||
// TODO: service should be parsed as int, specifying port number |
||||
error = DnsRunQuery(sock, node, 0, res); |
||||
|
||||
close(sock); |
||||
} |
||||
else |
||||
error = -1; |
||||
|
||||
return error; |
||||
} |
||||
|
||||
U0 DnsSetResolverIPv4(U32 ip) { |
||||
dns_ip = ip; |
||||
} |
||||
|
||||
public U0 Host(U8* hostname) { |
||||
addrinfo* res = NULL; |
||||
I64 error = getaddrinfo(hostname, NULL, NULL, &res); |
||||
|
||||
if (error < 0) { |
||||
"$FG,4$getaddrinfo: error %d\n", error; |
||||
} |
||||
else { |
||||
addrinfo* curr = res; |
||||
while (curr) { |
||||
"flags %04Xh, family %d, socktype %d, proto %d, addrlen %d, addr %s\n", |
||||
curr->ai_flags, curr->ai_family, curr->ai_socktype, curr->ai_protocol, curr->ai_addrlen, |
||||
inet_ntoa((curr->ai_addr(sockaddr_in*))->sin_addr); |
||||
curr = curr->ai_next; |
||||
} |
||||
} |
||||
|
||||
freeaddrinfo(res); |
||||
} |
||||
|
||||
U0 DnsInit() { |
||||
static CAddrResolver dns_addr_resolver; |
||||
dns_addr_resolver.getaddrinfo = &DnsGetaddrinfo; |
||||
|
||||
socket_addr_resolver = &dns_addr_resolver; |
||||
} |
||||
|
||||
DnsInit; |
@ -0,0 +1,55 @@
|
||||
// vim: set ft=c: |
||||
|
||||
class CEthFrame { |
||||
U8 source_addr[6]; |
||||
U8 padding[2]; |
||||
U8 dest_addr[6]; |
||||
U16 ethertype; |
||||
|
||||
U8* data; |
||||
I64 length; |
||||
}; |
||||
|
||||
class CL3Protocol { |
||||
CL3Protocol* next; |
||||
|
||||
U16 ethertype; |
||||
U8 padding[6]; |
||||
|
||||
I64 (*handler)(CEthFrame* frame); |
||||
}; |
||||
|
||||
static CL3Protocol* l3_protocols = NULL; |
||||
|
||||
U8 eth_null[6] = {0, 0, 0, 0, 0, 0}; |
||||
U8 eth_broadcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
||||
|
||||
I64 EthernetFrameParse(CEthFrame* frame_out, U8* frame, U16 length) { |
||||
// FIXME: check length |
||||
// TODO: MemCpy has high overhead, get rid of it |
||||
MemCpy(frame_out->dest_addr, frame, 6); |
||||
MemCpy(frame_out->source_addr, frame + 6, 6); |
||||
frame_out->ethertype = frame[13] | (frame[12] << 8); |
||||
|
||||
/*"Rx dst: %02X:%02X:%02X:%02X:%02X:%02X\n", |
||||
frame_out->dest_addr[0], frame_out->dest_addr[1], frame_out->dest_addr[2], frame_out->dest_addr[3], frame_out->dest_addr[4], frame_out->dest_addr[5]; |
||||
|
||||
"Rx src: %02X:%02X:%02X:%02X:%02X:%02X\n", |
||||
frame_out->source_addr[0], frame_out->source_addr[1], frame_out->source_addr[2], frame_out->source_addr[3], frame_out->source_addr[4], frame_out->source_addr[5]; |
||||
|
||||
"Rx ethertype: %02X\n", frame_out->ethertype;*/ |
||||
|
||||
frame_out->data = frame + 14; |
||||
frame_out->length = length - 14 - 4; // ?? |
||||
return 0; |
||||
} |
||||
|
||||
U0 RegisterL3Protocol(U16 ethertype, I64 (*handler)(CEthFrame* frame)) { |
||||
CL3Protocol* p = MAlloc(sizeof(CL3Protocol)); |
||||
|
||||
p->next = l3_protocols; |
||||
p->ethertype = ethertype; |
||||
p->handler = handler; |
||||
|
||||
l3_protocols = p; |
||||
} |
@ -0,0 +1,197 @@
|
||||
// vim: set ft=cpp: |
||||
|
||||
#include "::/Adam/Net/Socket" |
||||
#include "::/Adam/Net/UrlParse" |
||||
|
||||
#define HTTP_ECONNECT (-101) |
||||
#define HTTP_EPROTOCOL (-102) |
||||
#define HTTP_EREQUEST (-103) |
||||
#define HTTP_EREDIRECT (-104) |
||||
#define HTTP_EEOF (-105) |
||||
#define HTTP_ECONTENTLENGTH (-106) |
||||
|
||||
#define HTTP_MAX_REDIRECTS 5 |
||||
#define HTTP_USER_AGENT "SnailNet ($TX+CX,"TempleOS",D="DD_OS_NAME_VERSION"$)" |
||||
|
||||
/** |
||||
* @param len_out (required) requires the content length, or -1 if unspecified |
||||
* @return socket (>= 0) on success, error code on failure |
||||
*/ |
||||
I64 HttpOpenGet(U8* host, U16 port = 0, U8* path, I64* len_out, |
||||
I64 allowed_redirects = HTTP_MAX_REDIRECTS) { |
||||
U8 line[256]; |
||||
I64 error = 0; |
||||
|
||||
if (!port) |
||||
port = 80; |
||||
|
||||
// Should this be done here though? |
||||
if (*path == 0) |
||||
path = "/"; |
||||
|
||||
//"Connect(%s:%d)\n", host, port; |
||||
I64 sock = create_connection(host, port); |
||||
//"create_connection: %d\n", sock; |
||||
if (sock >= 0) { |
||||
StrPrint(line, "GET %s HTTP/1.0\r\n", path); |
||||
sendString(sock, line, 0); |
||||
StrPrint(line, "Host: %s\r\n", host); |
||||
sendString(sock, line, 0); |
||||
sendString(sock, "User-Agent: " HTTP_USER_AGENT "\r\n", 0); |
||||
sendString(sock, "\r\n", 0); |
||||
|
||||
Bool haveHTTP = FALSE; |
||||
U8* location = NULL; |
||||
|
||||
I64 code = -1; |
||||
|
||||
*len_out = -1; |
||||
|
||||
while (1) { |
||||
error = recvLine(sock, line, sizeof(line), 0); |
||||
|
||||
if (error < 0) { |
||||
break; |
||||
} |
||||
else if (error == 0) { |
||||
if (!haveHTTP) |
||||
error = HTTP_EPROTOCOL; |
||||
break; |
||||
} |
||||
|
||||
U8* delim; |
||||
|
||||
//"%s\n", line; |
||||
if (!haveHTTP) { |
||||
delim = StrFirstOcc(line, " "); |
||||
if (delim && StrNCmp(line, "HTTP/", 5) == 0) { |
||||
code = Str2I64(delim + 1); |
||||
|
||||
if (code >= 200 && code <= 399) { |
||||
haveHTTP = TRUE; |
||||
} |
||||
else { |
||||
error = HTTP_EREQUEST; |
||||
break; |
||||
} |
||||
} |
||||
else { |
||||
error = HTTP_EREQUEST; |
||||
break; |
||||
} |
||||
} |
||||
else { |
||||
delim = StrFirstOcc(line, ":"); |
||||
|
||||
if (!delim) { |
||||
error = HTTP_EPROTOCOL; |
||||
break; |
||||
} |
||||
|
||||
*delim = 0; |
||||
|
||||
do { delim++; } |
||||
while (*delim == ' '); |
||||
|
||||
//"%s=%s\n", line, delim; |
||||
if (!StrCmp(line, "Content-Length")) { |
||||
StrScan(delim, "%d", len_out); |
||||
} |
||||
else if (!StrCmp(line, "Location")) { |
||||
// This will leak on malformed response |
||||
location = StrNew(delim); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// HTTP Code 3xx -- Redirection |
||||
if (!error && code >= 300 && code <= 399) { |
||||
if (allowed_redirects > 0) { |
||||
CUrl curl; |
||||
UrlInit(&curl); |
||||
|
||||
if (UrlParse(location, &curl)) { |
||||
if (!StrCmp(curl.protocol, "http")) { |
||||
close(sock); |
||||
sock = HttpOpenGet(curl.host, curl.port, curl.path, len_out, allowed_redirects - 1); |
||||
|
||||
if (sock < 0) |
||||
error = sock; |
||||
} |
||||
else |
||||
error = HTTP_EPROTOCOL; |
||||
} |
||||
else |
||||
error = HTTP_EREDIRECT; |
||||
|
||||
UrlFree(&curl); |
||||
} |
||||
else { |
||||
error = HTTP_EREDIRECT; |
||||
} |
||||
} |
||||
|
||||
Free(location); |
||||
} |
||||
else |
||||
error = HTTP_ECONNECT; |
||||
|
||||
if (error) { |
||||
close(sock); |
||||
return error; |
||||
} |
||||
else { |
||||
return sock; |
||||
} |
||||
} |
||||
|
||||
I64 HttpGet(U8* host, U16 port = 0, U8* path, U8** data_out = NULL, I64* len_out = NULL) { |
||||
I64 error = 0; |
||||
I64 len = 0; |
||||
I64 sock = HttpOpenGet(host, port, path, &len); |
||||
|
||||
if (sock > 0) { |
||||
if (len >= 0) { |
||||