From 4e845d5d5e07db311e78eef33173e3a737afb9bd Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 31 Jan 2020 22:18:29 +0200 Subject: [PATCH] Backport more old code --- etc/make/amd64/platform.mk | 5 +- include/sys/amd64/hw/ahci.h | 32 ++ include/sys/amd64/hw/ata.h | 21 + include/sys/amd64/hw/pci/pci.h | 33 ++ include/sys/amd64/hw/pci/usb_uhci.h | 4 + sys/amd64/hw/pci/ahci.c | 604 ++++++++++++++++++++++++++++ sys/amd64/hw/pci/pci.c | 275 +++++++++++++ sys/amd64/hw/pci/pcidb.c | 65 +++ sys/amd64/hw/pci/rtl8139.c | 277 +++++++++++++ sys/amd64/hw/pci/usb.c | 24 ++ sys/amd64/hw/pci/usb_uhci.c | 494 +++++++++++++++++++++++ sys/amd64/kernel.c | 11 + 12 files changed, 1844 insertions(+), 1 deletion(-) create mode 100644 include/sys/amd64/hw/ahci.h create mode 100644 include/sys/amd64/hw/ata.h create mode 100644 include/sys/amd64/hw/pci/pci.h create mode 100644 include/sys/amd64/hw/pci/usb_uhci.h create mode 100644 sys/amd64/hw/pci/ahci.c create mode 100644 sys/amd64/hw/pci/pci.c create mode 100644 sys/amd64/hw/pci/pcidb.c create mode 100644 sys/amd64/hw/pci/rtl8139.c create mode 100644 sys/amd64/hw/pci/usb.c create mode 100644 sys/amd64/hw/pci/usb_uhci.c diff --git a/etc/make/amd64/platform.mk b/etc/make/amd64/platform.mk index 63fca37..a5ff352 100644 --- a/etc/make/amd64/platform.mk +++ b/etc/make/amd64/platform.mk @@ -46,7 +46,9 @@ OBJS+=$(O)/sys/amd64/hw/rs232.o \ $(O)/sys/amd64/syscall_s.o \ $(O)/sys/amd64/syscall.o \ $(O)/sys/amd64/binfmt_elf.o \ - $(O)/sys/amd64/hw/con.o + $(O)/sys/amd64/hw/con.o \ + $(O)/sys/amd64/hw/pci/pci.o \ + $(O)/sys/amd64/hw/pci/ahci.o kernel_LINKER=sys/amd64/link.ld kernel_LDFLAGS=-nostdlib \ @@ -88,6 +90,7 @@ kernel_OBJS=$(O)/sys/amd64/crti.o \ $(O)/sys/amd64/crtn.o DIRS+=$(O)/sys/amd64/image/boot/grub \ + $(O)/sys/amd64/hw/pci \ $(O)/sys/amd64/hw \ $(O)/sys/amd64/sys \ $(O)/sys/amd64/mm \ diff --git a/include/sys/amd64/hw/ahci.h b/include/sys/amd64/hw/ahci.h new file mode 100644 index 0000000..d34daad --- /dev/null +++ b/include/sys/amd64/hw/ahci.h @@ -0,0 +1,32 @@ +#pragma once + +struct ahci_registers; +struct pci_device; + +struct ahci_fis_reg_h2d { + uint8_t type; + uint8_t cmd_port; + uint8_t cmd; + uint8_t feature_low; + uint8_t lba0; + uint8_t lba1; + uint8_t lba2; + uint8_t device; + + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t feature_high; + + uint16_t count; + uint8_t icc; + uint8_t control; + + uint32_t __res0; +}; + +struct ahci_controller { + struct pci_device *pci_dev; + uintptr_t abar_phys; + struct ahci_registers *regs; +}; diff --git a/include/sys/amd64/hw/ata.h b/include/sys/amd64/hw/ata.h new file mode 100644 index 0000000..7afa19a --- /dev/null +++ b/include/sys/amd64/hw/ata.h @@ -0,0 +1,21 @@ +#pragma once + +#define ATA_CMD_IDENTIFY 0xEC +#define ATA_CMD_PACKET 0xA0 +#define ATA_CMD_READ_DMA_EX 0x25 +#define ATA_CMD_WRITE_DMA_EX 0x35 + +#define ATA_CMD_PACKET_IDENTIFY 0xA1 +#define ATAPI_CMD_READ_SECTORS 0xA8 + +#define ATA_SR_BUSY (1 << 7) +#define ATA_SR_DRQ (1 << 3) + +// Response to ATA_CMD_IDENTIFY +#define ATA_IDENT_DEVICE_TYPE 0x00 +#define ATA_IDENT_SERIAL 0x14 +#define ATA_IDENT_MODEL 0x36 +#define ATA_IDENT_CAPS 0x62 +#define ATA_IDENT_MAX_LBA 0x78 +#define ATA_IDENT_CMD_SETS 0xA4 +#define ATA_IDENT_MAX_LBAEXT 0xC8 diff --git a/include/sys/amd64/hw/pci/pci.h b/include/sys/amd64/hw/pci/pci.h new file mode 100644 index 0000000..7b7ebaf --- /dev/null +++ b/include/sys/amd64/hw/pci/pci.h @@ -0,0 +1,33 @@ +#pragma once +#include "sys/amd64/hw/irq.h" +#include "sys/types.h" + +#define PCI_PORT_CONFIG_ADDR 0xCF8 +#define PCI_PORT_CONFIG_DATA 0xCFC + +#define PCI_CONFIG_ID 0x00 +#define PCI_CONFIG_CMD 0x04 +#define PCI_CONFIG_CLASS 0x08 +#define PCI_CONFIG_INFO 0x0C +#define PCI_CONFIG_BAR(n) (0x10 + (n) * 4) +#define PCI_CONFIG_SUBSYSTEM 0x2C +#define PCI_CONFIG_CAPABILITIES 0x34 +#define PCI_CONFIG_IRQ 0x3C + +#define PCI_CONFIG_BRIDGE 0x18 + +#define PCI_ID(vnd, dev) (((uint32_t) (vnd)) | ((uint32_t) (dev) << 16)) + +struct pci_device; +typedef void (*pci_driver_func_t)(struct pci_device *dev); + +void pci_init(void); +void pci_add_root_bus(uint8_t n); + +uint32_t pci_config_read_dword(struct pci_device *dev, uint16_t off); +void pci_add_irq(struct pci_device *dev, irq_handler_func_t handler, void *ctx); + +void pci_add_class_driver(uint32_t full_class, pci_driver_func_t func); + +// pcidb.c +const char *pci_class_string(uint16_t full_class); diff --git a/include/sys/amd64/hw/pci/usb_uhci.h b/include/sys/amd64/hw/pci/usb_uhci.h new file mode 100644 index 0000000..68e7e27 --- /dev/null +++ b/include/sys/amd64/hw/pci/usb_uhci.h @@ -0,0 +1,4 @@ +#pragma once +#include "sys/amd64/hw/pci/pci.h" + +//void pci_usb_uhci_init(pci_addr_t addr); diff --git a/sys/amd64/hw/pci/ahci.c b/sys/amd64/hw/pci/ahci.c new file mode 100644 index 0000000..59ae01a --- /dev/null +++ b/sys/amd64/hw/pci/ahci.c @@ -0,0 +1,604 @@ +#include "sys/amd64/hw/pci/pci.h" +#include "sys/amd64/hw/ata.h" +#include "sys/amd64/mm/phys.h" +#include "sys/amd64/hw/ahci.h" +#include "sys/dev/blk.h" +#include "sys/dev/dev.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/errno.h" +#include "sys/debug.h" +#include "sys/heap.h" +#include "sys/attr.h" +#include "sys/mm.h" + +#define AHCI_SPIN_WAIT_MAX 10000000 +#define AHCI_PRD_MAX_SIZE 8192 + +#define AHCI_PORT_SIG_SATA 0x00000101 +#define AHCI_PORT_SIG_SATAPI 0xEB140101 + +#define AHCI_MAX_PORTS 32 +#define AHCI_CMD_LIST_SIZE 32 +#define AHCI_CMD_TABLE_ENTSZ 256 + +#define AHCI_PORT_IS_TFES (1 << 30) + +#define AHCI_PORT_SSTS_IPM_ACTIVE 0x01 +#define AHCI_PORT_SSTS_DET_CONNECTED 0x03 + +#define AHCI_FIS_REG_H2D 0x27 +// Tells the controller it's a command FIS +#define AHCI_FIS_REG_H2D_COMMAND (1 << 7) + +#define AHCI_PORT_CMD_LIST(port) \ + ((struct ahci_cmd_list *) MM_VIRTUALIZE((port)->clb | (((uintptr_t) (port)->clbu) << 32))) +#define AHCI_PORT_TABLE_ENTRY(list, n) \ + ((struct ahci_cmd_table *) MM_VIRTUALIZE((list)[n].ctba)) + +// Start +#define AHCI_PORT_CMD_ST (1 << 0) +// FIS receive enable +#define AHCI_PORT_CMD_FRE (1 << 4) +// FIS receive running +#define AHCI_PORT_CMD_FR (1 << 14) +// Command list running +#define AHCI_PORT_CMD_CR (1 << 15) + +struct ahci_port { + uint32_t clb; // 0x00 + uint32_t clbu; // 0x04 + uint32_t fb; // 0x08 + uint32_t fbu; // 0x0C + uint32_t is; // 0x10 + uint32_t ie; // 0x14 + uint32_t cmd; // 0x18 + uint32_t __res0; // 0x1C + uint32_t tfd; // 0x20 + uint32_t sig; // 0x24 + uint32_t ssts; // 0x28 + uint32_t sctl; // 0x2C + uint32_t serr; // 0x30 + uint32_t sact; // 0x34 + uint32_t ci; // 0x38 + uint32_t __res1[13];// 0x3C + uint32_t __vnd[4]; +}; + +struct ahci_registers { + // 0x00 - 0x1F - Generic Host Control + uint32_t cap; // 0x00 + uint32_t ghc; // 0x04 + uint32_t is; // 0x08 + uint32_t pi; // 0x0C + uint32_t vs; // 0x10 + uint32_t __unspec[3]; + // 0x20 - 0x9F - Reserved + uint32_t __res[32]; + // 0xA0 - 0xFF - Vendor specific registers + uint32_t __vnd[24]; + struct ahci_port port[32]; +}; + +// Command list entry +struct ahci_cmd_list { + uint16_t attr; + // PRD table length in entries + uint16_t prdtl; + // Byte count transferred + volatile uint32_t prdbc; + // Command table base address + uint64_t ctba; + uint32_t __res0[4]; +}; + +// Physical region descriptor entry +struct ahci_prd { + // Buffer address + uint64_t dba; + uint32_t __res0; + uint32_t dbc; +}; + +// Command table entry +struct ahci_cmd_table { + // Buffer for FIS to be sent to the device + union { + struct ahci_fis_reg_h2d fis_reg_h2d; + uint8_t __cmd_fis[64]; + }; + uint8_t acmd[16]; + uint8_t __res0[48]; + // Any number of entries can follow + struct ahci_prd prdt[0]; +}; + +//// Generic AHCI + +static void ahci_port_stop(struct ahci_port *port) { + // Disable command engine and FIS receive + port->cmd &= ~(AHCI_PORT_CMD_ST | AHCI_PORT_CMD_FRE); + + // Wait for controller to change status + while (port->cmd & (AHCI_PORT_CMD_CR | AHCI_PORT_CMD_FR)) { + asm ("pause"); + } +} + +static void ahci_port_start(struct ahci_port *port) { + // Wait while port may be processing any commands + while (port->cmd & AHCI_PORT_CMD_CR) { + asm ("pause"); + } + + // Enable command engine and FIS receive + port->cmd |= AHCI_PORT_CMD_FRE | AHCI_PORT_CMD_ST; +} + +static int ahci_port_cmd_alloc(struct ahci_port *port) { + uint32_t slots = (port->sact | port->ci); + for (int i = 0; i < AHCI_CMD_LIST_SIZE; ++i) { + if (!(slots & (1 << i))) { + return i; + } + } + return -1; +} + +static int ahci_port_alloc(struct ahci_port *port) { + // Disable port command engine first + ahci_port_stop(port); + + // AHCI port memory: + // * 1024 for command list + // * 256 - FIS receive buffer + // * 8192 - command table + // Total: + // 12288 bytes (page-aligned), 3 pages + + // Command list and FIS buffer + uintptr_t page0 = amd64_phys_alloc_page(); + if (page0 == MM_NADDR) { + kerror("Failed to allocate a page\n"); + return -ENOMEM; + } + + // Command table + uintptr_t page1 = amd64_phys_alloc_contiguous(2); + if (page1 == MM_NADDR) { + kerror("Failed to allocate 2 pages\n"); + amd64_phys_free(page0); + return -ENOMEM; + } + + // Cleanup the pages + memset((void *) MM_VIRTUALIZE(page0), 0, 0x1000); + memset((void *) MM_VIRTUALIZE(page1), 0, 0x2000); + + // Setup an empty command list with proper addresses + struct ahci_cmd_list *cmd_list = (struct ahci_cmd_list *) MM_VIRTUALIZE(page0); + for (size_t i = 0; i < AHCI_CMD_LIST_SIZE; ++i) { + cmd_list[i].prdtl = 8; + cmd_list[i].ctba = page1 + i * AHCI_CMD_TABLE_ENTSZ; + } + + // Configure the port to use allocated pages + port->clb = page0 & 0xFFFFFFFF; + port->clbu = page0 >> 32; + port->fb = (page0 + 0x400) & 0xFFFFFFFF; + port->fbu = (page0 + 0x400) >> 32; + + // Enable it again + ahci_port_start(port); + return 0; +} + +static int ahci_port_ata_cmd(struct ahci_port *port, uint8_t ata, uintptr_t lba, void *data, size_t len) { + // At least should be sector-aligned + _assert(len % 512 == 0); + + int cmd = ahci_port_cmd_alloc(port); + if (cmd < 0) { + return -EBUSY; + } + + struct ahci_cmd_list *list = AHCI_PORT_CMD_LIST(port); + struct ahci_cmd_list *list_entry = &list[cmd]; + struct ahci_cmd_table *table_entry = AHCI_PORT_TABLE_ENTRY(list, cmd); + uint32_t spin = 0; + + // TODO: support devices with different sector sizes + size_t nsect = (len + 511) / 512; + _assert((nsect & ~0xFFFF) == 0); + size_t prd_count = (len + AHCI_PRD_MAX_SIZE - 1) / AHCI_PRD_MAX_SIZE; + kdebug("Command requires %d PRDs\n", prd_count); + + // Setup command table entry + memset(table_entry, 0, sizeof(struct ahci_cmd_table) + sizeof(struct ahci_prd) * prd_count); + for (size_t i = 0, bytes_left = len; i < prd_count; ++i) { + size_t prd_size = MIN(AHCI_PRD_MAX_SIZE, len); + _assert(prd_size); + + uintptr_t prd_buf_phys = MM_PHYS(data) + i * AHCI_PRD_MAX_SIZE; + + table_entry->prdt[i].dba = prd_buf_phys; + table_entry->prdt[i].dbc = ((prd_size - 1) << 1) | 1; + + kdebug("PRD%d: %S at %p\n", i, prd_size, prd_buf_phys); + + if (i == prd_count - 1) { + // Mark last PRDT entry + table_entry->prdt[i].dbc |= 1 << 31; + } + + bytes_left -= prd_size; + } + + // Setup command list entry + // attr = FIS size in dwords + list_entry->attr = sizeof(struct ahci_fis_reg_h2d) / sizeof(uint32_t); + list_entry->prdtl = prd_count; + list_entry->prdbc = 0; + + // Setup FIS packet + struct ahci_fis_reg_h2d *fis = &table_entry->fis_reg_h2d; + memset(fis, 0, sizeof(struct ahci_fis_reg_h2d)); + fis->type = AHCI_FIS_REG_H2D; + fis->cmd = ata; + fis->cmd_port = AHCI_FIS_REG_H2D_COMMAND; + if (ata != ATA_CMD_IDENTIFY) { + fis->device = 1 << 6; // LBA mode + fis->lba0 = lba & 0xFF; + fis->lba1 = (lba >> 8) & 0xFF; + fis->lba2 = (lba >> 16) & 0xFF; + fis->lba3 = (lba >> 24) & 0xFF; + fis->lba4 = (lba >> 32) & 0xFF; + fis->lba5 = (lba >> 40) & 0xFF; + kdebug("nsect = %d\n", nsect); + fis->count = nsect & 0xFFFF; + } + + // Wait for port to be free for commands + while ((port->tfd & (ATA_SR_BUSY | ATA_SR_DRQ)) && spin < AHCI_SPIN_WAIT_MAX) { + ++spin; + } + + if (spin == AHCI_SPIN_WAIT_MAX) { + kerror("AHCI device hang\n"); + return -EIO; + } + + // Request HBA to execute the command + port->ci |= 1 << cmd; + + while (1) { + if (!(port->ci & (1 << cmd))) { + break; + } + + if (port->is & AHCI_PORT_IS_TFES) { + kerror("Device signalled TFE error\n"); + return -EIO; + } + } + + if (port->is & AHCI_PORT_IS_TFES) { + kerror("Device signalled TFE error\n"); + return -EIO; + } + + return 0; +} + +static int ahci_port_atapi_read(struct ahci_port *port, uint64_t lba, void *buf, size_t nsect) { + int cmd = ahci_port_cmd_alloc(port); + if (cmd < 0) { + return -EBUSY; + } + + struct ahci_cmd_list *list = AHCI_PORT_CMD_LIST(port); + struct ahci_cmd_list *list_entry = &list[cmd]; + struct ahci_cmd_table *table_entry = AHCI_PORT_TABLE_ENTRY(list, cmd); + uint32_t spin = 0; + + // TODO: support devices with different sector sizes + size_t len = nsect * 2048; + _assert((nsect & ~0xFFFF) == 0); + size_t prd_count = (len + AHCI_PRD_MAX_SIZE - 1) / AHCI_PRD_MAX_SIZE; + kdebug("Command requires %d PRDs\n", prd_count); + + // Setup command table entry + memset(table_entry, 0, sizeof(struct ahci_cmd_table) + sizeof(struct ahci_prd) * prd_count); + table_entry->acmd[0] = 0xA8; + table_entry->acmd[9] = nsect; + table_entry->acmd[5] = lba & 0xFF; + table_entry->acmd[4] = (lba >> 8) & 0xFF; + table_entry->acmd[3] = (lba >> 16) & 0xFF; + table_entry->acmd[2] = (lba >> 24) & 0xFF; + + for (size_t i = 0, bytes_left = len; i < prd_count; ++i) { + size_t prd_size = MIN(AHCI_PRD_MAX_SIZE, len); + _assert(prd_size); + + uintptr_t prd_buf_phys = MM_PHYS(buf) + i * AHCI_PRD_MAX_SIZE; + + table_entry->prdt[i].dba = prd_buf_phys; + table_entry->prdt[i].dbc = ((prd_size - 1) << 1) | 1; + + kdebug("PRD%d: %S at %p\n", i, prd_size, prd_buf_phys); + + if (i == prd_count - 1) { + // Mark last PRDT entry + table_entry->prdt[i].dbc |= 1 << 31; + } + + bytes_left -= prd_size; + } + + // Setup command list entry + // attr = FIS size in dwords + list_entry->attr = sizeof(struct ahci_fis_reg_h2d) / sizeof(uint32_t); + // This is ATAPI command + list_entry->attr |= (1 << 5); + list_entry->prdtl = 1; + list_entry->prdbc = 0; + + // Setup FIS packet + struct ahci_fis_reg_h2d *fis = &table_entry->fis_reg_h2d; + memset(fis, 0, sizeof(struct ahci_fis_reg_h2d)); + fis->type = AHCI_FIS_REG_H2D; + fis->cmd = ATA_CMD_PACKET; + fis->cmd_port = AHCI_FIS_REG_H2D_COMMAND; + + fis->device = 1 << 6; // LBA mode + fis->feature_low = 1; + fis->lba0 = lba & 0xFF; + fis->lba1 = (lba >> 8) & 0xFF; + fis->lba2 = (lba >> 16) & 0xFF; + fis->lba3 = (lba >> 24) & 0xFF; + fis->lba4 = (lba >> 32) & 0xFF; + fis->lba5 = (lba >> 40) & 0xFF; + fis->count = nsect & 0xFFFF; + + // Wait for port to be free for commands + while ((port->tfd & (ATA_SR_BUSY | ATA_SR_DRQ)) && spin < AHCI_SPIN_WAIT_MAX) { + ++spin; + } + + if (spin == AHCI_SPIN_WAIT_MAX) { + kerror("AHCI device hang\n"); + return -EIO; + } + + // Request HBA to execute the command + port->ci |= 1 << cmd; + + while (1) { + if (!(port->ci & (1 << cmd))) { + break; + } + + if (port->is & AHCI_PORT_IS_TFES) { + kerror("Device signalled TFE error\n"); + return -EIO; + } + } + + if (port->is & AHCI_PORT_IS_TFES) { + kerror("Device signalled TFE error\n"); + return -EIO; + } + + return 0; +} + +static int ahci_port_identify(struct ahci_port *port) { + char ident_buf[1024]; + size_t disk_size_lba; + char model_string[41]; + char serial_string[11]; + uint32_t cmd_sets; + int res, id_cmd; + + if (port->sig == AHCI_PORT_SIG_SATA) { + id_cmd = ATA_CMD_IDENTIFY; + } else { + id_cmd = ATA_CMD_PACKET_IDENTIFY; + } + + if ((res = ahci_port_ata_cmd(port, id_cmd, 0, ident_buf, sizeof(ident_buf))) != 0) { + kwarn("Disk identify failed: %s\n", kstrerror(res)); + return res; + } + + model_string[40] = 0; + serial_string[10] = 0; + cmd_sets = *(uint32_t *) (ident_buf + ATA_IDENT_CMD_SETS); + + for (size_t i = 0; i < 40; i += 2) { + model_string[i] = ident_buf[ATA_IDENT_MODEL + i + 1]; + model_string[i + 1] = ident_buf[ATA_IDENT_MODEL + i]; + } + for (size_t i = 39; i > 0; --i) { + if (model_string[i] != ' ') { + break; + } + model_string[i] = 0; + } + + for (size_t i = 0; i < 10; i += 2) { + serial_string[i] = ident_buf[ATA_IDENT_SERIAL + i + 1]; + serial_string[i + 1] = ident_buf[ATA_IDENT_SERIAL + i]; + } + for (size_t i = 9; i > 0; --i) { + if (serial_string[i] != ' ') { + break; + } + serial_string[i] = 0; + } + + if (cmd_sets & (1 << 26)) { + disk_size_lba = *(uint32_t *) (ident_buf + ATA_IDENT_MAX_LBAEXT); + } else { + disk_size_lba = *(uint32_t *) (ident_buf + ATA_IDENT_MAX_LBA); + } + + kdebug("%s drive: \"%s\", Serial: \"%s\", Capacity: %S\n", + id_cmd == ATA_CMD_IDENTIFY ? "SATA" : "SATAPI", + model_string, serial_string, disk_size_lba * 512); + + return 0; +} + +static ssize_t ahci_blk_write(struct blkdev *blk, const void *buf, size_t off, size_t len) { + if ((off % blk->block_size) != 0) { + panic("Misaligned AHCI device write: offset of %lu, block size is %lu\n", off, blk->block_size); + } + if ((len % blk->block_size) != 0) { + panic("Misaligned AHCI device write: size of %lu\n", len); + } + + kdebug("AHCI write %S at %p\n", len, off); + struct ahci_port *port = blk->dev_data; + _assert(port); + uintptr_t lba = (off / blk->block_size); + size_t n_blocks = len / blk->block_size; + int res; + + switch (port->sig) { + case AHCI_PORT_SIG_SATA: + if ((res = ahci_port_ata_cmd(port, ATA_CMD_WRITE_DMA_EX, lba, (void *) buf, len)) != 0) { + return res; + } + return len; + case AHCI_PORT_SIG_SATAPI: + return -EROFS; + default: + return -EIO; + } +} + +static ssize_t ahci_blk_read(struct blkdev *blk, void *buf, size_t off, size_t len) { + if ((off % blk->block_size) != 0) { + panic("Misaligned AHCI device read: offset of %lu, block size is %lu\n", off, blk->block_size); + } + if ((len % blk->block_size) != 0) { + panic("Misaligned AHCI device read: size of %lu\n", len); + } + + kdebug("AHCI read %S at %p\n", len, off); + struct ahci_port *port = blk->dev_data; + _assert(port); + uintptr_t lba = (off / blk->block_size); + size_t n_blocks = len / blk->block_size; + int res; + + switch (port->sig) { + case AHCI_PORT_SIG_SATA: + if ((res = ahci_port_ata_cmd(port, ATA_CMD_READ_DMA_EX, lba, buf, len)) != 0) { + return res; + } + return len; + case AHCI_PORT_SIG_SATAPI: + if ((res = ahci_port_atapi_read(port, lba, buf, n_blocks)) != 0) { + return res; + } + return len; + default: + return -EIO; + } +} + +static void ahci_port_add(struct ahci_port *port) { + struct blkdev *blk = kmalloc(sizeof(struct blkdev)); + _assert(blk); + uint32_t subclass; + + switch (port->sig) { + case AHCI_PORT_SIG_SATA: + blk->block_size = 512; + subclass = DEV_BLOCK_SDx; + break; + case AHCI_PORT_SIG_SATAPI: + blk->block_size = 2048; + subclass = DEV_BLOCK_CDx; + break; + default: + panic("Unhandled AHCI device signature: %08x\n", port->sig); + } + + blk->dev_data = port; + blk->read = ahci_blk_read; + blk->write = ahci_blk_write; + blk->flags = 0; + + _assert(dev_add(DEV_CLASS_BLOCK, subclass, blk, NULL) == 0); +} + +static void ahci_port_init(struct ahci_controller *ahci, struct ahci_port *port, int no) { + kinfo("Initializing port %d\n", no); + + // Allocate all the data structures required for port control + if (ahci_port_alloc(port) != 0) { + kwarn("Failed to allocate port data %d\n", no); + return; + } + + if (ahci_port_identify(port) != 0) { + kwarn("Failed to identify device at port %d\n", no); + return; + } + + // Register new block device + ahci_port_add(port); +} + +static void ahci_controller_init(struct ahci_controller *ahci) { + // Check controller version + kinfo("AHCI controller version is %02x.%02x\n", (ahci->regs->vs >> 16), (ahci->regs->vs & 0xFFFF)); + + // Check which ports are supported by AHCI controller + for (size_t i = 0; i < AHCI_MAX_PORTS; ++i) { + if (ahci->regs->pi & (1 << i)) { + // Get SATA connnection status + struct ahci_port *port = &ahci->regs->port[i]; + + uint32_t ssts = port->ssts; + uint8_t ipm = (ssts >> 8) & 0x0F; + uint8_t spd = (ssts >> 4) & 0x0F; + uint8_t det = ssts & 0x0F; + + if ((ipm != AHCI_PORT_SSTS_IPM_ACTIVE) || (det != AHCI_PORT_SSTS_DET_CONNECTED)) { + // Skip not connected or inactive ports + continue; + } + + if (port->sig == AHCI_PORT_SIG_SATA || port->sig == AHCI_PORT_SIG_SATAPI) { + ahci_port_init(ahci, port, i); + } + } + } +} + +//// PCI-specific + +static void pci_ahci_init(struct pci_device *pci_dev) { + // TODO: change pcie -> pci + uint32_t abar_phys = pci_config_read_dword(pci_dev, PCI_CONFIG_BAR(5)); + if (abar_phys & 1) { + kwarn("AHCI controller has ABAR in I/O space\n"); + return; + } + + struct ahci_controller *obj = kmalloc(sizeof(struct ahci_controller)); + + obj->pci_dev = pci_dev; + obj->abar_phys = abar_phys; + obj->regs = (void *) MM_VIRTUALIZE(abar_phys); + + ahci_controller_init(obj); +} + +static __init void ahci_register_class(void) { + pci_add_class_driver(0x010601, pci_ahci_init); +} diff --git a/sys/amd64/hw/pci/pci.c b/sys/amd64/hw/pci/pci.c new file mode 100644 index 0000000..10e5aec --- /dev/null +++ b/sys/amd64/hw/pci/pci.c @@ -0,0 +1,275 @@ +#include "sys/amd64/hw/pci/pci.h" +#include "sys/amd64/hw/ioapic.h" +#include "sys/amd64/hw/acpi.h" +#include "sys/amd64/hw/io.h" +#include "sys/assert.h" +#include "sys/panic.h" +#include "sys/debug.h" +#include "sys/heap.h" +#include "sys/mm.h" + +#define PCI_MAX_DRIVERS 64 + +#define pcie_config_read_dword(dev, off) \ + (*(uint32_t *) ((dev)->pcie_config + (off))) + +#define PCI_CAP_MSI_64 (1 << 7) +#define PCI_CAP_MSI_EN (1 << 0) +struct pci_cap_msi { + uint8_t cap_id; + uint8_t cap_link; + uint16_t message_control; + union { + struct { + uint32_t message_address; + uint16_t message_data; + } __attribute__((packed)) msi32; + struct { + uint64_t message_address; + uint16_t message_data; + } __attribute__((packed)) msi64; + }; +} __attribute__((packed)); + +struct pci_device { + // PCI address + uint8_t bus; + uint8_t dev; + uint8_t func; + + // PCIe addressing: segment group number and configuration space pointer + uint16_t pcie_segment_group; + void *pcie_config; + + // Interrupt resources + struct pci_cap_msi *msi; + int irq_pin; + + // For list + struct pci_device *next; +}; + +struct pci_driver { + pci_driver_func_t init_func; + uint32_t type; + uint32_t match; +}; + +static struct pci_device *g_pci_devices = NULL; +static struct pci_driver g_pci_drivers[PCI_MAX_DRIVERS] = {0}; +static size_t g_pci_driver_count; + +#define PCI_DRIVER_CLASS 1 +#define PCI_DRIVER_DEV 2 + +uint32_t pci_config_read_dword(struct pci_device *dev, uint16_t off) { + // TODO: check if device is really PCIe + return pcie_config_read_dword(dev, off); +} + +void pci_add_irq(struct pci_device *dev, irq_handler_func_t handler, void *ctx) { + if (dev->msi) { + uint8_t vector; + + if (irq_add_msi_handler(handler, ctx, &vector) != 0) { + panic("Failed to add MSI handler\n"); + } + + if (dev->msi->message_control & PCI_CAP_MSI_64) { + dev->msi->msi64.message_address = 0xFEE00000; + dev->msi->msi64.message_data = vector; + } else { + dev->msi->msi32.message_address = 0xFEE00000; + dev->msi->msi32.message_data = vector; + } + dev->msi->message_control |= PCI_CAP_MSI_EN; + } +} + +void pci_add_class_driver(uint32_t full_class, pci_driver_func_t func) { + if (g_pci_driver_count == PCI_MAX_DRIVERS) { + panic("Too many PCI drivers loaded\n"); + } + + struct pci_driver *driver = &g_pci_drivers[g_pci_driver_count++]; + + driver->init_func = func; + driver->type = PCI_DRIVER_CLASS; + driver->match = full_class & ~0xFF000000; +} + +void pci_add_root_bus(uint8_t n) { + kwarn("%s: %02x\n", __func__, n); +} + +static void pci_pick_driver(uint32_t device_id, uint32_t class_id, pci_driver_func_t *class_driver, pci_driver_func_t *dev_driver) { + pci_driver_func_t rclass = NULL, rdev = NULL; + for (size_t i = 0; i < g_pci_driver_count; ++i) { + if (rclass && rdev) { + break; + } + + if (g_pci_drivers[i].type == PCI_DRIVER_CLASS && !rclass) { + uint32_t match = g_pci_drivers[i].match; + // Check if prog_if has to be matched + // 0xFF means (match all prog. IF) + if ((match & 0xFF) == 0xFF) { + match &= ~0xFF; + class_id &= ~0xFF; + } + + if (match == class_id) { + rclass = g_pci_drivers[i].init_func; + continue; + } + } else if (g_pci_drivers[i].type == PCI_DRIVER_DEV && !rdev) { + if (device_id == g_pci_drivers[i].match) { + rdev = g_pci_drivers[i].init_func; + continue; + } + } + } + + *class_driver = rclass; + *dev_driver = rdev; +} + +static void pci_device_add(struct pci_device *dev) { + dev->next = g_pci_devices; + g_pci_devices = dev; +} + +static int pcie_device_setup(struct pci_device *dev) { + uint32_t class, caps_offset, irq_info; + uint32_t id; + uint8_t irq_pin; + + class = pcie_config_read_dword(dev, PCI_CONFIG_CLASS); + caps_offset = pcie_config_read_dword(dev, PCI_CONFIG_CAPABILITIES) & 0xFF; + irq_info = pcie_config_read_dword(dev, PCI_CONFIG_IRQ); + id = pcie_config_read_dword(dev, PCI_CONFIG_ID); + + kinfo("%02x:%02x:%02x:\n", dev->bus, dev->dev, dev->func); + kinfo(" Class %02x:%02x:%02x\n", (class >> 24), (class >> 16) & 0xFF, (class >> 8) & 0xFF); + kinfo(" Device %04x:%04x\n", id & 0xFFFF, (id >> 16) & 0xFFFF); + + irq_pin = (irq_info >> 8) & 0xFF; + if (irq_pin) { + dev->irq_pin = irq_pin - 1; + kinfo(" IRQ pin INT%c#\n", dev->irq_pin + 'A'); + } + + while (caps_offset) { + uint8_t *link = (uint8_t *) (dev->pcie_config + caps_offset); + + switch (link[0]) { + case 0x05: + kinfo(" * MSI capability\n"); + dev->msi = (struct pci_cap_msi *) link; + break; + case 0x10: + kinfo(" * PCIe capability\n"); + break; + default: + // Unknown capability + kinfo(" * Device capability: %02x\n", link[0]); + break; + } + + caps_offset = link[1]; + } + + pci_driver_func_t driver_class, driver_dev; + pci_pick_driver(id, class >> 8, &driver_class, &driver_dev); + + if (driver_dev) { + driver_dev(dev); + return 0; + } + + if (driver_class) { + driver_class(dev); + return 0; + } + + return -1; +} + +static void pcie_enumerate_device(uintptr_t base_address, uint16_t seg, uint8_t start_bus, uint8_t bus, uint8_t dev_no) { + void *cfg; + uint32_t id; + + for (uint8_t func = 0; func < 8; ++func) { + uint32_t d = ((uint32_t) (bus - start_bus) << 20) | ((uint32_t) dev_no << 15) | ((uint32_t) func << 12); + cfg = (void *) MM_VIRTUALIZE(base_address + d); + id = *(uint32_t *) cfg; + if ((id & 0xFFFF) == 0xFFFF) { + continue; + } + + struct pci_device *dev = kmalloc(sizeof(struct pci_device)); + _assert(dev); + dev->bus = bus; + dev->dev = dev_no; + dev->func = func; + + dev->pcie_segment_group = seg; + dev->pcie_config = cfg; + dev->msi = NULL; + dev->irq_pin = -1; + + pcie_device_setup(dev); + pci_device_add(dev); + } +} + +static void pcie_enumerate_bus(uintptr_t base_address, uint16_t seg, uint8_t start_bus, uint8_t bus) { + for (uint8_t dev = 0; dev < 32; ++dev) { + // Check if function 0 is present + void *cfg = (void *) MM_VIRTUALIZE(base_address + (((bus - start_bus) << 20) | (dev << 15))); + + if (((*(uint32_t *) cfg) & 0xFFFF) != 0xFFFF) { + uint32_t header = *(uint32_t *) (cfg + 0x0C); + header >>= 16; + header &= 0x7F; + + switch (header) { + case 0x00: + pcie_enumerate_device(base_address, seg, start_bus, bus, dev); + break; + default: + kwarn("Skipping unsupported header type: %02x\n", header); + break; + } + } + } +} + +static void pcie_enumerate_segment(uintptr_t base_address, uint16_t seg, uint8_t start_bus, uint8_t end_bus) { + for (uint16_t bus = start_bus; bus < end_bus; ++bus) { + pcie_enumerate_bus(base_address, seg, start_bus, bus); + } +} + +void pci_init(void) { + if (!acpi_mcfg) { + panic("TODO: legacy PCI\n"); + } + + uint32_t mcfg_entry_count = (acpi_mcfg->hdr.length - sizeof(struct acpi_header) - 8) / sizeof(struct acpi_mcfg_entry); + kinfo("MCFG has %u entries:\n", mcfg_entry_count); + for (uint32_t i = 0; i < mcfg_entry_count; ++i) { + kinfo("%u:\n", i); + kinfo(" Base address: %p\n", acpi_mcfg->entry[i].base_address); + kinfo(" Segment group #%u\n", acpi_mcfg->entry[i].pci_segment_group); + kinfo(" PCI buses: %02x-%02x\n", acpi_mcfg->entry[i].start_pci_bus, acpi_mcfg->entry[i].end_pci_bus); + } + + // Start enumerating buses specified in MCFG + for (uint32_t i = 0; i < mcfg_entry_count; ++i) { + pcie_enumerate_segment(acpi_mcfg->entry[i].base_address, + acpi_mcfg->entry[i].pci_segment_group, + acpi_mcfg->entry[i].start_pci_bus, + acpi_mcfg->entry[i].end_pci_bus); + } +} diff --git a/sys/amd64/hw/pci/pcidb.c b/sys/amd64/hw/pci/pcidb.c new file mode 100644 index 0000000..6ae4e06 --- /dev/null +++ b/sys/amd64/hw/pci/pcidb.c @@ -0,0 +1,65 @@ +#include "sys/amd64/hw/pci/pci.h" + +static const char *pci_class_code_strings[] = { + "Unclassified", + "Mass storage controller", + "Network controller", + "Display controller", + "Multimedia controller", + "Memory controller", + "Bridge device", + "Simple communication controller", + "Base system peripheral", + "Input device controller", + "Docking station", + "Processor", + "Serial bus controller", + "Wireless controller", + "Intelligent controller", + "Satellite communication controller", + "Encryption controller", + "Signal processing controller", + "Processing accelerator", + "Non-essential instrumentation" +}; + +const char *pci_class_string(uint16_t full_class) { + // Better descriptions for well-known classes + switch (full_class) { + // Mass storage + case 0x0101: + return "IDE controller"; + case 0x0106: + return "SATA controller"; + // Display controllers + case 0x0300: + return "VGA display controller"; + // Network controllers + case 0x200: + return "Ethernet controller"; + // Bridge devices + case 0x0600: + return "Host bridge"; + case 0x0601: + return "ISA bridge"; + // Serial bus controllers + case 0x0C03: + return "USB controller"; + case 0x0C05: + return "SMBus"; + default: + break; + } + + uint8_t class_code = full_class >> 8; + if (class_code == 0xFF) { + return "Unknown device"; + } + + if (class_code >= sizeof(pci_class_code_strings) / sizeof(pci_class_code_strings[0])) { + return "Reserved"; + } + + return pci_class_code_strings[class_code]; +} + diff --git a/sys/amd64/hw/pci/rtl8139.c b/sys/amd64/hw/pci/rtl8139.c new file mode 100644 index 0000000..95598df --- /dev/null +++ b/sys/amd64/hw/pci/rtl8139.c @@ -0,0 +1,277 @@ +#include "sys/amd64/hw/pci/pci.h" +#include "sys/amd64/mm/phys.h" +#include "sys/amd64/hw/irq.h" +#include "sys/amd64/hw/io.h" +#include "sys/net/eth.h" +#include "sys/net/netdev.h" +#include "sys/net/net.h" +#include "sys/assert.h" +#include "sys/string.h" +#include "sys/debug.h" +#include "sys/heap.h" +#include "sys/attr.h" +#include "sys/mm.h" + +#define RTL8139_IDR0 0x00 +#define RTL8139_MAR0 0x08 +#define RTL8139_TSD(n) (0x10 + (n) * 4) +#define RTL8139_TSAD(n) (0x20 + (n) * 4) +#define RTL8139_RBSTART 0x30 +#define RTL8139_ERBCR 0x34 +#define RTL8139_ERSR 0x36 +#define RTL8139_CR 0x37 +#define RTL8139_CAPR 0x38 +#define RTL8139_CBR 0x3A +#define RTL8139_IMR 0x3C +#define RTL8139_ISR 0x3E +#define RTL8139_TCR 0x40 +#define RTL8139_RCR 0x44 +#define RTL8139_TCTR 0x48 +#define RTL8139_MPC 0x4C +#define RTL8139_CONFIG0 0x51 +#define RTL8139_CONFIG1 0x52 +#define RTL8139_MSR 0x58 + +#define RTL8139_TSD_OWN (1 << 13) + +#define RTL8139_CR_RST (1 << 4) +#define RTL8139_CR_RE (1 << 3) +#define RTL8139_CR_TE (1 << 2) + +#define RTL8139_RCR_WRAP (1 << 7) +#define RTL8139_RCR_AB (1 << 0) +#define RTL8139_RCR_AM (1 << 1) +#define RTL8139_RCR_APM (1 << 2) +#define RTL8139_RCR_AAP (1 << 3) +#define RTL8139_RCR_ALL (RTL8139_RCR_AB | RTL8139_RCR_AM | RTL8139_RCR_APM | RTL8139_RCR_AAP) + +#define RTL8139_ISR_TER (1 << 3) +#define RTL8139_ISR_TOK (1 << 2) +#define RTL8139_ISR_RER (1 << 1) +#define RTL8139_ISR_ROK (1 << 0) + +#define RTL8139_IMR_TER (1 << 3) +#define RTL8139_IMR_TOK (1 << 2) +#define RTL8139_IMR_RER (1 << 1) +#define RTL8139_IMR_ROK (1 << 0) + +struct rtl8139 { + struct netdev net; + + uint32_t bar0; + // Receive buffer: 3 pages + uint32_t page0; + uint32_t tx_pages[4]; + size_t tx_sizes[4]; + // Bits: + // 0..3: TX_BUSY Set by _tx(), cleared by _handle_tx(), means that + // the descriptor cannot be allocated + uint32_t tx_flag; + uint32_t cbr_prev; + uint32_t tx_number; +}; + +static void rtl8139_handle_rx(struct rtl8139 *rtl); +static void rtl8139_handle_tx(struct rtl8139 *rtl, int succ); + +static uint32_t rtl8139_irq(void *ctx) { + struct rtl8139 *rtl = ctx; + uint16_t isr = inw(rtl->bar0 + RTL8139_ISR); + + if (isr) { + if (isr & RTL8139_ISR_ROK) { + // Got a packet + rtl8139_handle_rx(rtl); + } + if (isr & (RTL8139_ISR_TOK | RTL8139_ISR_TER)) { + rtl8139_handle_tx(rtl, isr & RTL8139_ISR_TOK); + } + + // Implementation of RTL8139 in qemu differs + // from the datasheet specification: + // we need to WRITE to the register to clear the interrupts + outw(rtl->bar0 + RTL8139_ISR, isr); + + return IRQ_HANDLED; + } + + return IRQ_UNHANDLED; +} + +static void rtl8139_handle_rx(struct rtl8139 *rtl) { + kdebug("Received a packet\n"); + uint16_t cbr = inw(rtl->bar0 + RTL8139_CBR); + size_t packet_size; + uint16_t packet_offset = rtl->cbr_prev; + + if (cbr > rtl->cbr_prev) { + packet_size = cbr - rtl->cbr_prev; + } else { + panic("TODO\n"); + } + rtl->cbr_prev = cbr; + + kdebug("Packet size: %u\n", packet_size); + + char *pack = (char *) MM_VIRTUALIZE(rtl->page0) + packet_offset; + struct eth_frame *eth_frame = (struct eth_frame *) &pack[4]; + + // First 4 octets - recv status and packet length + uint16_t recv_status = ((uint16_t *) pack)[0]; + uint16_t recv_length = ((uint16_t *) pack)[1]; + + kdebug("Recv status: %u\n", recv_status); + + //kdebug("Recv length = %u\n", recv_length); + //kdebug("Packet size = %u\n", packet_size); + //_assert(recv_length == (packet_size - 4)); + + eth_handle_frame(&rtl->net, eth_frame, recv_length); +} + +static void rtl8139_cmd_tx(struct rtl8139 *rtl) { + kdebug("HW TX %d\n", rtl->tx_number); + + uint8_t n = rtl->tx_number; + + ++rtl->tx_number; + if (rtl->tx_number == 4) { + rtl->tx_number = 0; + } + + outl(rtl->bar0 + RTL8139_TSD(n), rtl->tx_sizes[n]); +} + +static void rtl8139_handle_tx(struct rtl8139 *rtl, int s) { + uint8_t prev = 3; + if (rtl->tx_number != 0) { + prev = rtl->tx_number - 1; + } + + kdebug("%s Tx on %d\n", s ? "Successful" : "Failed", prev); + kdebug("Clearing TxD%d\n", prev); + rtl->tx_flag &= ~(1 << prev); + rtl->tx_sizes[prev] = 0; + + kdebug("TX Done!\n"); + + // If after transmission we reached a new entry and it's + // awaiting transmission - emit a Tx command + while (rtl->tx_flag & (1 << rtl->tx_number)) { + rtl8139_cmd_tx(rtl); + } +} + +static uint8_t rtl8139_alloc_tx(struct rtl8139 *rtl) { + for (uint8_t i = rtl->tx_number; i < 4; ++i) { + if (!(rtl->tx_flag & (1 << i))) { + return i; + } + } + for (uint8_t i = 0; i < rtl->tx_number; ++i) { + if (!(rtl->tx_flag & (1 << i))) { + return i; + } + } + return 0xFF; +} + +static int rtl8139_tx(struct netdev *net, const void *packet, size_t size) { + kdebug("Requested Tx\n"); + struct rtl8139 *rtl = (struct rtl8139 *) net; + + uint8_t index = rtl8139_alloc_tx(rtl); + // TODO: transmit queue for this? + assert(index != 0xFF, "No free TSADx\n"); + + void *page = (void *) MM_VIRTUALIZE(rtl->tx_pages[index]); + memcpy(page, packet, size); + + rtl->tx_sizes[index] = size; + + if (index == rtl->tx_number) { + // Can tx right now + rtl8139_cmd_tx(rtl); + } + + return 0; +} + +void pci_rtl8139_init(pci_addr_t addr) { + kdebug("Initializing RTL8139 at " PCI_FMTADDR "\n", PCI_VAADDR(addr)); + + struct rtl8139 *rtl = (struct rtl8139 *) kmalloc(sizeof(struct rtl8139)); + + uint32_t irq_config = pci_config_read_dword(addr, PCI_CONFIG_IRQ); + + // Enable PCI busmastering + uint32_t cmd = pci_config_read_dword(addr, PCI_CONFIG_CMD); + cmd |= 1 << 2; + pci_config_write_dword(addr, PCI_CONFIG_CMD, cmd); + + rtl->bar0 = pci_config_read_dword(addr, PCI_CONFIG_BAR(0)); + assert(rtl->bar0 & 1, "RTL8139 BAR0 is not in I/O space\n"); + rtl->bar0 &= ~0x3; + + // Board init + // Set LWAKE + LWPTN to active high + outb(rtl->bar0 + RTL8139_CONFIG1, 0); + + // Software reset + outb(rtl->bar0 + RTL8139_CR, RTL8139_CR_RST); + + // Wait for the board to clear RST bit + while (inb(rtl->bar0 + RTL8139_CR) & RTL8139_CR_RST); + + // Initialize rx buffer + rtl->page0 = amd64_phys_alloc_contiguous(3); + rtl->cbr_prev = 0; + assert(rtl->page0 != MM_NADDR, "Failed to allocate RxBuf\n"); + outl(rtl->bar0 + RTL8139_RBSTART, rtl->page0); + + // Initialize tx buffers + for (size_t i = 0; i < 4; ++i) { + rtl->tx_pages[i] = amd64_phys_alloc_page(); + rtl->tx_sizes[i] = 0; + assert(rtl->tx_pages[i] != MM_NADDR, "Failed to allocate TxBuf\n"); + outl(rtl->bar0 + RTL8139_TSAD(i), rtl->tx_pages[i]); + } + rtl->tx_flag = 0; + rtl->tx_number = 0; + + // Accept all + outl(rtl->bar0 + RTL8139_RCR, RTL8139_RCR_APM | RTL8139_RCR_AB | RTL8139_RCR_WRAP); + + // Enable Rx/Tx + outb(rtl->bar0 + RTL8139_CR, RTL8139_CR_TE | RTL8139_CR_RE); + + uint8_t irq_pin = (irq_config >> 8) & 0xFF; + if (irq_pin) { + kdebug("Uses INT%c# IRQ pin\n\n", 'A' + irq_pin - 1); + irq_add_pci_handler(addr, irq_pin - 1, rtl8139_irq, rtl); + } + + // Configure interrupt mask + outw(rtl->bar0 + RTL8139_IMR, RTL8139_IMR_ROK | RTL8139_IMR_RER | + RTL8139_IMR_TOK | RTL8139_IMR_TER); + // Clear ISR + outw(rtl->bar0 + RTL8139_ISR, 0); + + // After the reset, controller uses Tx pair 0 + rtl->tx_number = 0; + + // Setup a system network device + for (size_t i = 0; i < 6; ++i) { + rtl->net.hwaddr[i] = inb(rtl->bar0 + RTL8139_IDR0 + i); + } + kdebug("RTL8139 MAC: " MAC_FMT "\n", MAC_VA(rtl->net.hwaddr)); + rtl->net.tx = rtl8139_tx; + // TODO: call some kind of netdev_add to bind a name to the device + + // XXX: for testing + link_add_route_in(&rtl->net, 0x0002000A, 0x00FFFFFF, 0x0102000A); +} + +static __init void pci_rtl8139_register(void) { + pci_add_device_driver(PCI_ID(0x10EC, 0x8139), pci_rtl8139_init); +} diff --git a/sys/amd64/hw/pci/usb.c b/sys/amd64/hw/pci/usb.c new file mode 100644 index 0000000..0466a73 --- /dev/null +++ b/sys/amd64/hw/pci/usb.c @@ -0,0 +1,24 @@ +#include "sys/amd64/hw/pci/pci.h" +#include "sys/amd64/hw/pci/usb_uhci.h" +#include "sys/debug.h" +#include "sys/attr.h" + +//static void pci_usb_init(pci_addr_t addr) { +// uint32_t class = pci_config_read_dword(addr, PCI_CONFIG_CLASS); +// uint8_t prog_if = (class >> 8) & 0xFF; +// +// switch (prog_if) { +//#if defined(PCI_UHCI_ENABLE) +// case 0x00: +// pci_usb_uhci_init(addr); +// break; +//#endif +// default: +// kwarn("Unsupported USB controller type: %02x\n", prog_if); +// break; +// } +//} +// +//static __init void pci_register_usb(void) { +// pci_add_class_driver(0x0C03, pci_usb_init); +//} diff --git a/sys/amd64/hw/pci/usb_uhci.c b/sys/amd64/hw/pci/usb_uhci.c new file mode 100644 index 0000000..b18ff61 --- /dev/null +++ b/sys/amd64/hw/pci/usb_uhci.c @@ -0,0 +1,494 @@ +#include "sys/amd64/hw/pci/usb_uhci.h" +#include "sys/amd64/mm/phys.h" +#include "sys/amd64/hw/io.h" +#include "sys/usb/request.h" +#include "sys/usb/device.h" +#include "sys/usb/usb.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/debug.h" +#include "sys/heap.h" +#include "sys/mm.h" + +#define IO_USBCMD 0x00 +#define IO_USBSTS 0x02 +#define IO_USBINTR 0x04 +#define IO_FRNUM 0x06 +#define IO_FRBASEADD 0x08 +#define IO_SOFMOD 0x0C +#define IO_PORTSC1 0x10 +#define IO_PORTSC2 0x12 + +#define USBCMD_RUN (1 << 0) +#define USBCMD_HCRST (1 << 1) +#define USBCMD_GRST (1 << 2) +#define USBCMD_GSUS (1 << 3) +#define USBCMD_GRES (1 << 4) +#define USBCMD_SDBG (1 << 5) +#define USBCMD_CONF (1 << 6) +#define USBCMD_PMAX (1 << 7) + +#define USBSTS_INTR (1 << 0) +#define USBSTS_EINT (1 << 1) +#define USBSTS_RESD (1 << 2) +#define USBSTS_ESYS (1 << 3) +#define USBSTS_EPROC (1 << 4) +#define USBSTS_HALT (1 << 5) + +#define USBINTR_TCRC (1 << 0) +#define USBINTR_RES (1 << 1) +#define USBINTR_DONE (1 << 2) +#define USBINTR_SHORT (1 << 3) + +#define USBPORT_CONN (1 << 0) +#define USBPORT_CNCH (1 << 1) +#define USBPORT_PEN (1 << 2) +#define USBPORT_PENCH (1 << 3) +#define USBPORT_RESD (1 << 6) +#define USBPORT_LSPD (1 << 8) +#define USBPORT_RST (1 << 9) +#define USBPORT_SUSP (1 << 12) + +#define USB_FRAME_EMPTY (1 << 0) +#define USB_FRAME_QUEUE (1 << 1) +#define USB_FRAME_FULLQ (1 << 2) + +#define UHCI_MAX_QH 8 +#define UHCI_MAX_TD 32 + +#define UHCI_TD_USED (1 << 0) + +struct uhci_td { + uint32_t next_td; + uint32_t status; + uint32_t header; + uint32_t buffer; + union { + uint32_t __sys0[4]; + struct { + struct uhci_td *next; + uint32_t flags; + } system; + }; +} __attribute__((packed)); + +struct uhci_qh { + uint32_t head_td; + uint32_t element_td; + // System + uint32_t list_index; + struct usb_transfer *transfer; + struct uhci_qh *prev, *next; + struct uhci_td *head_real; + uint32_t used; + uintptr_t pad; +}; + +struct uhci { + struct usb_controller hc; + + uint32_t iobase; + uint32_t *frame_list; + + uintptr_t pool_page; + struct uhci_td *td_pool; + struct uhci_qh *qh_pool; + struct uhci_qh *qh_async; +}; + +static struct uhci_td *uhci_alloc_td(struct uhci *hc) { + for (size_t i = 0; i < UHCI_MAX_TD; ++i) { + if (!(hc->td_pool[i].system.flags & UHCI_TD_USED)) { + _assert(!(((uintptr_t) &hc->td_pool[i]) & 0xF)); + hc->td_pool[i].system.flags |= UHCI_TD_USED; + return &hc->td_pool[i]; + } + } + return NULL; +} + +static struct uhci_qh *uhci_alloc_qh(struct uhci *hc) { + for (size_t i = 0; i < UHCI_MAX_QH; ++i) { + if (!hc->qh_pool[i].used) { + _assert(!(((uintptr_t) &hc->qh_pool[i]) & 0xF)); + hc->qh_pool[i].used = 1; + return &hc->qh_pool[i]; + } + } + return NULL; +} + +/* static */ void uhci_data_init(struct uhci *data, uint32_t bar4) { + uintptr_t frame_list_page = amd64_phys_alloc_contiguous(2); + _assert(frame_list_page != MM_NADDR && frame_list_page < 0x100000000); + uintptr_t pool_page = amd64_phys_alloc_page(); + _assert(pool_page != MM_NADDR && pool_page < 0x100000000); + + data->iobase = bar4 & ~3; + + data->frame_list = (uint32_t *) MM_VIRTUALIZE(frame_list_page); + data->pool_page = pool_page; + data->td_pool = (struct uhci_td *) MM_VIRTUALIZE(pool_page); + data->qh_pool = (struct uhci_qh *) (MM_VIRTUALIZE(pool_page) + sizeof(struct uhci_td) * UHCI_MAX_TD); + + memset(data->td_pool, 0, sizeof(struct uhci_td) * UHCI_MAX_TD); + memset(data->qh_pool, 0, sizeof(struct uhci_qh) * UHCI_MAX_QH); + + data->qh_async = uhci_alloc_qh(data); + _assert(data->qh_async); + data->qh_async->head_td = USB_FRAME_EMPTY; + data->qh_async->element_td = USB_FRAME_EMPTY; + data->qh_async->transfer = NULL; + data->qh_async->prev = data->qh_async; + data->qh_async->next = data->qh_async; + + for (uint32_t i = 0; i < 2048; ++i) { + data->frame_list[i] = USB_FRAME_QUEUE | MM_PHYS(data->qh_async); + } +} + +static uint16_t uhci_port_reset(struct uhci *uhci, int port) { + uint16_t r; + uint16_t reg = uhci->iobase + port * 2 + IO_PORTSC1; + + r = inw(reg); + r |= USBPORT_RST; + outw(reg, r); + for (size_t i = 0; i < 10000000; ++i); + r = inw(reg); + r &= ~USBPORT_RST; + outw(reg, r); + + for (size_t i = 0; i < 10; ++i) { + for (size_t _ = 0; _ < 10000000; ++_); + + r = inw(reg); + + // Port is not connected + if (!(r & USBPORT_CONN)) { + break; + } + + if (r & (USBPORT_PENCH | USBPORT_CNCH)) { + r &= ~(USBPORT_PENCH | USBPORT_CNCH); + outw(reg, r); + continue; + } + + // Check if device is enabled + if (r & USBPORT_PEN) { + break; + } + + // Try to enable the port + r |= USBPORT_PEN; + outw(reg, r); + } + + return r; +} + +static void uhci_start_qh(struct uhci *hc, struct uhci_qh *qh) { + //kdebug("Assign QH: %p\n", qh); + struct uhci_qh *list = hc->qh_async; + _assert(list); + struct uhci_qh *end = hc->qh_async->prev; + _assert(end); + + qh->head_td = USB_FRAME_EMPTY; + end->head_td = (uint32_t) MM_PHYS(qh) | USB_FRAME_QUEUE; + qh->prev = end; + end->next = qh; + list->prev = qh; + qh->next = list; + + //kdebug("---- After add ----\n"); + //uhci_dumpq(hc); + //kdebug("---- After add ----\n"); +} + +static void uhci_remove_qh(struct uhci *hc, struct uhci_qh *qh) { + //kdebug("Unassign QH: %p\n", qh); + struct uhci_qh *list = hc->qh_async; + _assert(list); + struct uhci_qh *prev = qh->prev; + _assert(prev); + + prev->head_td = qh->head_td; + prev->next = list; + list->prev = prev; + + qh->next = NULL; + qh->prev = NULL; + + //kdebug("---- After remove ----\n"); + //uhci_dumpq(hc); + //kdebug("---- After remove ----\n"); +} + +static void uhci_free_td(struct uhci_td *td) { + //kdebug("Free TD: %p\n", td); + td->system.flags = 0; + td->next_td = USB_FRAME_EMPTY; + td->buffer = 0; +} + +static void uhci_free_qh(struct uhci_qh *qh) { + //kdebug("Free QH: %p\n", qh); + qh->transfer = 0; + qh->used = 0; + qh->head_real = NULL; + qh->head_td = 0; + qh->element_td = 0; +} + +static void uhci_process_qh(struct uhci *hc, struct uhci_qh *qh) { + struct usb_transfer *t = qh->transfer; + uint32_t elem_phys = qh->element_td & ~0xF; + + if (!elem_phys) { + t->flags = USB_TRANSFER_COMPLETE | USB_TRANSFER_SUCCESS; + } else { + struct uhci_td *td = (struct uhci_td *) MM_VIRTUALIZE(elem_phys); + + if (td->status & (1 << 22)) { + kwarn("Transfer descriptor is stalled\n"); + t->flags = USB_TRANSFER_COMPLETE; + } + } + + if (t->flags & USB_TRANSFER_COMPLETE) { + //kdebug("Transfer is complete\n"); + uhci_remove_qh(hc, qh); + + (void) uhci_free_td; + // Release TDs + uint32_t td_ptr = 1234; + struct uhci_td *td = qh->head_real; + _assert(td); + while (1) { + td_ptr = td->next_td; + + uhci_free_td(td); + + if (!(td_ptr & ~0xF) || (td_ptr & USB_FRAME_EMPTY)) { + break; + } + td = (struct uhci_td *) MM_VIRTUALIZE(td_ptr & ~0xF); + } + + uhci_free_qh(qh); + } +} + +static void uhci_wait_qh(struct uhci *hc, struct uhci_qh *qh) { + //kdebug("Wait for QH: %p\n", qh); + struct usb_transfer *t = qh->transfer; + while (!(t->flags & USB_TRANSFER_COMPLETE)) { + uhci_process_qh(hc, qh); + } + kdebug("Transfer is complete\n"); +} + +static void uhci_init_td(struct uhci_td *td, struct uhci_td *prev, uint8_t speed, uint8_t addr, uint8_t endp, uint16_t len, uint8_t packet_type, uint8_t toggle, void *buf) { + // Packet header: + // 0 .. 7 Packet type + // 0x69 IN + // 0xE1 OUT + // 0x2D SETUP + // 8 .. 14 Device address + // 15 .. 18 Endpoint address + // 19 Data toggle + // 20 Reserved (0) + // 21 .. 31 Maximum payload length - 1 + _assert((((uintptr_t) td) & 0xF) == 0); + len = (len - 1) & 0x7FF; + + if (prev) { + prev->next_td = (uint32_t) MM_PHYS(td) | USB_FRAME_FULLQ; + } + + td->header = packet_type | + (len << 21) | + (toggle << 19) | + (addr << 8) | + (endp << 15); + + // Status: + // 23 Active + // 26 Low speed + // 27 .. 28 Error counter + td->status = (1 << 23) | + (3 << 27); + + if (speed == USB_SPEED_LOW) { + td->status |= 1 << 26; + } + + td->buffer = MM_PHYS(buf); +} + +static void uhci_device_interrupt(struct usb_device *dev, struct usb_transfer *t) { + struct uhci *hc = dev->hc; + + uint8_t speed = dev->speed; + uint8_t addr = dev->address; + uint8_t endp = dev->desc_endpoint.address & 0xF; + + struct uhci_td *td = uhci_alloc_td(hc); + _assert(td); + + struct uhci_td *head = td; + struct uhci_td *prev = NULL; + + uint8_t toggle = dev->endpoint_toggle; + uint8_t packet_type = 0x69; + uint8_t packet_size = t->length; + + uhci_init_td(td, prev, speed, addr, endp, toggle, packet_type, packet_size, t->data); + + struct uhci_qh *qh = uhci_alloc_qh(hc); + _assert(qh); + qh->head_real = head; + qh->head_td = (uint32_t) MM_PHYS(head) | USB_FRAME_FULLQ; + qh->element_td = (uint32_t) MM_PHYS(head) | USB_FRAME_FULLQ; + qh->transfer = t; + + uhci_start_qh(hc, qh); +} + +static void uhci_device_control(struct usb_device *dev, struct usb_transfer *t) { + struct uhci *hc = dev->hc; + struct usb_request *req = t->request; + uint8_t max_len = sizeof(struct usb_request); + + struct uhci_td *td, *head, *prev; + struct uhci_qh *qh; + + td = uhci_alloc_td(hc); + _assert(td); + head = td; + + uint8_t addr = dev->address; + uint8_t endp = 0; + uint8_t speed = dev->speed; + uint16_t packet_size = sizeof(struct usb_request); + uint8_t packet_type = 0x2D; + uint8_t toggle = 0; + + // Setup first packet (SETUP) + uhci_init_td(td, NULL, speed, addr, endp, packet_size, packet_type, toggle, req); + prev = td; + + // Middle packets (IN) + size_t off = 0; + size_t rem = req->length; + packet_type = (req->type & USB_REQUEST_TYPE_D2H) ? 0x69 : 0xE1; + while (rem) { + packet_size = MIN(rem, dev->max_packet); + td = uhci_alloc_td(hc); + _assert(td); + + toggle ^= 1; + uhci_init_td(td, prev, speed, addr, endp, packet_size, packet_type, toggle, t->data + off); + + prev = td; + off += packet_size; + rem -= packet_size; + } + + // Ending packet (OUT) + td = uhci_alloc_td(hc); + _assert(td); + + packet_type = (req->type & USB_REQUEST_TYPE_D2H) ? 0xE1 : 0x69; + uhci_init_td(td, prev, speed, addr, endp, 0, packet_type, 1, NULL); + + qh = uhci_alloc_qh(hc); + _assert(qh); + qh->head_real = head; + qh->head_td = (uint32_t) MM_PHYS(head) | USB_FRAME_FULLQ; + qh->element_td = (uint32_t) MM_PHYS(head) | USB_FRAME_FULLQ; + qh->transfer = t; + + uhci_start_qh(hc, qh); + uhci_wait_qh(hc, qh); +} + +/* static */ void uhci_probe(struct uhci *uhci) { + uint16_t portsc; + + for (int port = 0; port < 2; ++port) { + portsc = uhci_port_reset(uhci, port); + + if (portsc & USBPORT_PEN) { + kdebug("Port %d is enabled\n", port); + + // Setup a new USB device + struct usb_device *dev = usb_device_create(); + dev->hc = uhci; + dev->max_packet = 8; + dev->hc_control = uhci_device_control; + dev->hc_interrupt = uhci_device_interrupt; + dev->address = 0; + dev->endpoint_toggle = 0; + + if (portsc & USBPORT_LSPD) { + dev->speed = USB_SPEED_LOW; + } else { + dev->speed = USB_SPEED_FULL; + } + + usb_device_init(dev); + } + } +} + +/* static */ void uhci_poll(struct usb_controller *_hc) { + struct uhci *hc = (struct uhci *) _hc; + + struct uhci_qh *qh = hc->qh_async; + struct uhci_qh *next; + while (qh) { + next = qh->next; + + if (qh->transfer) { + uhci_process_qh(hc, qh); + break; + } + + qh = next; + } +} + +//void pci_usb_uhci_init(pci_addr_t addr) { +//} +// uint32_t bar4; +// struct uhci *hc = kmalloc(sizeof(struct uhci)); +// _assert(hc); +// +// kdebug("Initializing USB UHCI at " PCI_FMTADDR "\n", PCI_VAADDR(addr)); +// +// bar4 = pci_config_read_dword(addr, PCI_CONFIG_BAR(4)); +// _assert(bar4 & 1); +// uhci_data_init(hc, bar4); +// kdebug("Base addr %p\n", hc->frame_list); +// +// // Disable IRQs +// outw(hc->iobase + IO_USBINTR, 0); +// // Setup frame lists +// outl(hc->iobase + IO_FRBASEADD, (uint32_t) MM_PHYS(hc->frame_list)); +// outw(hc->iobase + IO_FRNUM, 0); +// outw(hc->iobase + IO_SOFMOD, 0x40); +// // Clear status +// outw(hc->iobase + IO_USBSTS, 0xFFFF); +// // Enable controller +// outw(hc->iobase + IO_USBCMD, USBCMD_RUN); +// +// uhci_probe(hc); +// +// hc->hc.spec = USB_SPEC_UHCI; +// hc->hc.hc_poll = uhci_poll; +// +// usb_controller_add((struct usb_controller *) hc); +//} diff --git a/sys/amd64/kernel.c b/sys/amd64/kernel.c index 8e6677a..94ca2b2 100644 --- a/sys/amd64/kernel.c +++ b/sys/amd64/kernel.c @@ -1,5 +1,6 @@ #include "sys/amd64/loader/multiboot.h" #include "sys/amd64/asm/asm_irq.h" +#include "sys/amd64/hw/pci/pci.h" #include "sys/amd64/hw/rs232.h" #include "sys/amd64/smp/smp.h" #include "sys/amd64/syscall.h" @@ -124,6 +125,16 @@ void kernel_main(struct amd64_loader_data *data) { kinfo("yggdrasil " KERNEL_VERSION_STR "\n"); amd64_apic_init(); + rtc_init(); + // Setup system time + struct tm t; + rtc_read(&t); + system_boot_time = mktime(&t); + kinfo("Boot time: %04u-%02u-%02u %02u:%02u:%02u\n", + t.tm_year, t.tm_mon, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec); + + pci_init(); vfs_init(); tty_init();