diff --git a/build.zig b/build.zig index 3ff102735..ba25ed953 100644 --- a/build.zig +++ b/build.zig @@ -46,6 +46,10 @@ const DriverClass = struct { const Gpu = enum { virtio, }; + + const Pcie = enum { + starfive, + }; }; const util_src = [_][]const u8{ @@ -248,7 +252,32 @@ fn addNetworkDriver( }); driver.addIncludePath(net_config_include); driver.addIncludePath(b.path(b.fmt("drivers/network/{s}/", .{ @tagName(class) }))); + driver.linkLibrary(util); + + return driver; +} + +fn addPcieDriver( + b: *std.Build, + util: *std.Build.Step.Compile, + class: DriverClass.Pcie, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) *std.Build.Step.Compile { + const driver = addPd(b, .{ + .name = b.fmt("driver_pcie_{s}.elf", .{@tagName(class)}), + .target = target, + .optimize = optimize, + .strip = false, + }); + const source = b.fmt("drivers/pcie/{s}/pcie.c", .{@tagName(class)}); + driver.addCSourceFile(.{ .file = b.path(source), .flags = &.{"-Werror"} }); + driver.addIncludePath(b.path(b.fmt("drivers/pcie/{s}/", .{@tagName(class)}))); driver.addIncludePath(b.path("include")); + + driver.addCSourceFile(.{ .file = b.path("drivers/nvme/nvme.c"), .flags = &.{"-Werror"} }); + driver.addIncludePath(b.path("drivers/nvme/")); + driver.linkLibrary(util); return driver; @@ -534,4 +563,10 @@ pub fn build(b: *std.Build) void { net_copy.linkLibrary(util); net_copy.linkLibrary(util_putchar_debug); b.installArtifact(net_copy); + + inline for (std.meta.fields(DriverClass.Pcie)) |class| { + const driver = addPcieDriver(b, util, @enumFromInt(class.value), target, optimize); + driver.linkLibrary(util_putchar_debug); + b.installArtifact(driver); + } } diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c new file mode 100644 index 000000000..c352b1e2a --- /dev/null +++ b/drivers/nvme/nvme.c @@ -0,0 +1,280 @@ +#include + +#include "nvme.h" +#include "nvme_queue.h" + +#define DEBUG_DRIVER +#ifdef DEBUG_DRIVER +#include "nvme_debug.h" +#endif + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +#error "code assumes little endian CPU as NVMe/PCIe is little endian" +#endif + +volatile nvme_controller_t *nvme_controller; +nvme_submission_queue_entry_t *nvme_asq_region; +nvme_completion_queue_entry_t *nvme_acq_region; +uintptr_t nvme_asq_region_paddr; +uintptr_t nvme_acq_region_paddr; +nvme_submission_queue_entry_t *nvme_io_sq_region; +nvme_completion_queue_entry_t *nvme_io_cq_region; +uintptr_t nvme_io_sq_region_paddr; +uintptr_t nvme_io_cq_region_paddr; +#define NVME_ADMIN_QUEUE_SIZE 0x1000 +#define NVME_IO_QUEUE_SIZE 0x1000 + +static nvme_queue_info_t admin_queue; +static nvme_queue_info_t io_queue; + +uintptr_t data_region_paddr; +volatile uint8_t *data_region; + +#define NVME_ASQ_CAPACITY (NVME_ADMIN_QUEUE_SIZE / sizeof(nvme_submission_queue_entry_t)) +#define NVME_ACQ_CAPACITY (NVME_ADMIN_QUEUE_SIZE / sizeof(nvme_completion_queue_entry_t)) +_Static_assert(NVME_ASQ_CAPACITY <= 0x1000, "capacity of ASQ must be <=4096 (entries)"); +_Static_assert(NVME_ACQ_CAPACITY <= 0x1000, "capacity of ACQ must be <=4096 (entries)"); +#define NVME_IO_SQ_CAPACITY (NVME_IO_QUEUE_SIZE / sizeof(nvme_submission_queue_entry_t)) +#define NVME_IO_CQ_CAPACITY (NVME_IO_QUEUE_SIZE / sizeof(nvme_completion_queue_entry_t)) +// §3.3.3.1 +_Static_assert(NVME_IO_SQ_CAPACITY <= 0x10000, "capacity of IO SQ must be <=65536 (entries)"); +_Static_assert(NVME_IO_CQ_CAPACITY <= 0x10000, "capacity of IO CQ must be <=65536 (entries)"); + +void nvme_irq_mask(void) +{ + /* [NVMe-Transport-PCIe-1.1] 3.5.1.1 Differences between Pin Based and MSI Interrupts + > Pin-based and single MSI only use one interrupt vector. + > Multiple MSI may use up to 32 interrupt vectors. + + [NVMe-2.1] 3.1.4.10 Admin Completion Queue Base Address + > This queue is always associated with interrupt vector 0. + */ + + /* For now -- we mask out every interrupt vector) */ + nvme_controller->intms = 0xffffffff; +} + +void nvme_irq_unmask(void) +{ + /* [NVMe-Transport-PCIe-1.1] 3.5.1.1 Differences between Pin Based and MSI Interrupts + > Pin-based and single MSI only use one interrupt vector. + > Multiple MSI may use up to 32 interrupt vectors. + + [NVMe-2.1] 3.1.4.10 Admin Completion Queue Base Address + > This queue is always associated with interrupt vector 0. + */ + + /* For now -- we mask in only vector 0, as it's the only one */ + nvme_controller->intmc = 0xffffffff; +} + +/* [NVMe-2.1] 3.5.1 Memory-based Controller Initialization (PCIe) */ +void nvme_controller_init() +{ + LOG_NVME("CAP: %016lx\n", nvme_controller->cap); + // TODO: alignment 32-bit. + // LOG_NVME("VS: major: %u, minor: %u, tertiary: %u\n", nvme_controller->vs.mjr, nvme_controller->vs.mnr, + // nvme_controller->vs.ter); + LOG_NVME("CC: %08x\n", nvme_controller->cc); + + nvme_controller->cc &= ~NVME_CC_EN; + + // 1. Wait for CSTS.RDY to become '0' (i.e. not ready) + int i = 100; + while (nvme_controller->csts & NVME_CSTS_RDY && i != 0) i--; + if (i == 0) { + sddf_dprintf("time out\n"); + return; + } + + // 2. Configure Admin Queue(s); + nvme_queues_init(&admin_queue, /* y */ 0, nvme_controller, nvme_asq_region, NVME_ASQ_CAPACITY, nvme_acq_region, + NVME_ACQ_CAPACITY); + nvme_irq_mask(); + assert(nvme_asq_region_paddr != 0x0); + assert(nvme_acq_region_paddr != 0x0); + nvme_controller->asq = nvme_asq_region_paddr; + nvme_controller->acq = nvme_acq_region_paddr; + nvme_controller->aqa &= ~(NVME_AQA_ACQS_MASK | NVME_AQA_ASQS_MASK); + nvme_controller->aqa |= ((NVME_ASQ_CAPACITY - 1) << NVME_AQA_ASQS_SHIFT) + | ((NVME_ACQ_CAPACITY - 1) << NVME_AQA_ACQS_SHIFT); + + // 3. Initialise Command Support Sets. + nvme_controller->cc &= ~(NVME_CC_CSS_MASK); + if (nvme_controller->cap & NVME_CAP_NOIOCSS) { + nvme_controller->cc |= 0b111 << NVME_CC_CSS_SHIFT; + } else if (nvme_controller->cap & NVME_CAP_IOCSS) { + nvme_controller->cc |= 0b110 << NVME_CC_CSS_SHIFT; + } else if (nvme_controller->cap & NVME_CAP_NCSS) { + nvme_controller->cc |= 0b000 << NVME_CC_CSS_SHIFT; + } + +#if defined(CONFIG_PLAT_QEMU_RISCV_VIRT) || defined(CONFIG_PLAT_QEMU_ARM_VIRT) + /* + QEMU deviates from the NVMe specification: + https://gitlab.com/qemu-project/qemu/-/issues/1691 + */ + nvme_controller->cc &= ~(NVME_CC_CSS_MASK); + nvme_controller->cc |= 0b000 << NVME_CC_CSS_SHIFT; +#endif + + // 4a. Arbitration Mechanism (TODO) + // 4b. Memory Page Size + // TODO: Check CAP.MPSMAX/CAP.MPSMIN fields + nvme_controller->cc &= ~NVME_CC_MPS_MASK; + /* n.b. page size = 2 ^ (12 + MPS) */ + uint8_t page_size_log2 = 12; /* all architectures we care about have page size 2^12. */ + nvme_controller->cc |= ((page_size_log2 - 12) << NVME_CC_MPS_SHIFT) & NVME_CC_MPS_MASK; + + // TODO: See initialisation note under §4.2.4; fine since already that way. + + // 5. Enable the controller + nvme_controller->cc |= NVME_CC_EN; + + // 6. Wait for ready + LOG_NVME("waiting ready...\n"); + while (!(nvme_controller->csts & NVME_CSTS_RDY)); + LOG_NVME("\tdone\n"); + + // 7. Send the Identify Controller command (Identify with CNS = 01h); §5.1.13 + // TODO: What do we actually need this for???? + // sudo nvme admin-passthru /dev/nvme0 --opcode=0x06 --cdw10=0x0001 --data-len=4096 -r -s + nvme_completion_queue_entry_t entry; + entry = nvme_queue_submit_and_consume_poll(&admin_queue, &(nvme_submission_queue_entry_t){ + .cdw0 = /* CID */ (0b1111 << 16) | /* PSDT */ 0 | /* FUSE */ 0 | /* OPC */ 0x6, + .cdw10 = /* CNTID[31:16] */ 0x0 | /* CNS */ 0x01, + .prp2 = 0, + .prp1 = data_region_paddr, /* TEMP */ + }); + + assert((entry.phase_tag_and_status & _MASK(1, 15)) == 0x0); // §4.2.3 Status Field + + // 8. The host determines any I/O Command Set specific configuration information + // TODO: Why??? + + // 9. Determine the number of I/O Submission Queues and I/O Completion Queues + // supported using the Set Features command with the Number of Queues feature identifier. + // After determining the number of I/O Queues, the NVMe Transport specific interrupt registers + // (e.g., MSI and/or MSI-X registers) should be configured + // TODO: interrupts. & don't ignore # but we always use one, so. + uint16_t io_queue_id = 1; + assert(nvme_io_sq_region != 0x0); + assert(nvme_io_cq_region != 0x0); + assert(nvme_io_sq_region_paddr != 0x0); + assert(nvme_io_cq_region_paddr != 0x0); + nvme_queues_init(&io_queue, io_queue_id, nvme_controller, nvme_io_sq_region, NVME_IO_SQ_CAPACITY, nvme_io_cq_region, + NVME_IO_CQ_CAPACITY); + + // §3.3.1.1 Queue Seutp & Initialization + // => Configures the size of the I/O Submission Queues (CC.IOSQES) and I/O Completion Queues (CC.IOCQES) + // nvme_controller->cc &= ~(NVME_CC_IOCQES_MASK | NVME_CC_IOSQES_MASK); + /* n.b. CQ/SQ entry sizes are specified as 2^n; i.e. 2^4 = 16 and 2^6 = 64. */ + nvme_controller->cc |= (4 << NVME_CC_IOCQES_SHIFT) | (6 << NVME_CC_IOSQES_SHIFT); + + // 10. Allocate the appropriate number of I/O Completion Queues [...] + // The I/O Completion Queues are allocated using the Create I/O Completion Queue command. + // §5.2.1 + entry = nvme_queue_submit_and_consume_poll(&admin_queue, &(nvme_submission_queue_entry_t){ + .cdw0 = /* CID */ (0b1010 << 16) | /* PSDT */ 0 | /* FUSE */ 0 | /* OPC */ 0x5, + .cdw10 = /* QSIZE */ ((NVME_IO_CQ_CAPACITY - 1U) << 16) | /* QID */ io_queue_id, + .cdw11 = /* IV */ (0x0 << 16) | /* IEN */ 1 << 1 | /* PC */ 0x1, + .prp2 = 0, + .prp1 = nvme_io_cq_region_paddr, + }); + + assert((entry.phase_tag_and_status & _MASK(1, 15)) == 0x0); // §4.2.3 Status Field + + // 11. Allocate the appropriate number of I/O Submission Queues [...] + // The I/O Submission Queues are allocated using the Create I/O Submission Queue command. + // §5.2.2 + entry = nvme_queue_submit_and_consume_poll(&admin_queue, &(nvme_submission_queue_entry_t){ + .cdw0 = /* CID */ (0b1110 << 16) | /* PSDT */ 0 | /* FUSE */ 0 | /* OPC */ 0x1, + .cdw10 = /* QSIZE */ ((NVME_IO_SQ_CAPACITY - 1U) << 16) | /* QID */ io_queue_id, + .cdw11 = /* CQID */ (io_queue_id << 16) | /* QPRIO */ (0b00 << 1) | /* PC */ 0b1, + .cdw12 = 0, + .prp2 = 0, + .prp1 = nvme_io_sq_region_paddr, + }); + + assert((entry.phase_tag_and_status & _MASK(1, 15)) == 0x0); // §4.2.3 Status Field + + // 12. To enable asynchronous notification of optional events, the host should issue a Set Features + // command specifying the events to enable. To enable asynchronous notification of events, the host + // should submit an appropriate number of Asynchronous Event Request commands. This step may + // be done at any point after the controller signals that the controller is ready (i.e., CSTS.RDY is set to ‘1’). + // TODO: ??? + + nvme_irq_unmask(); +} + +void nvme_continue(int z); +void nvme_init() +{ + LOG_NVME("Starting NVME initialisation... (%s)\n", microkit_name); + + // We should do a Function Level Reset as defined by [PCIe-2.0] spec §6.6.2 + + // https://github.com/bootreer/vroom/blob/d8bbe9db2b1cfdfc38eec31f3b48f5eb167879a9/src/nvme.rs#L220 + + nvme_controller_init(); + LOG_NVME("NVME initialised\n"); + + /* TODO: Don't send via this */ + nvme_continue(0); +} + +#define NUMBER_BLOCKS 1 +void nvme_continue(int z) +{ + if (z == 0) { + /* [NVMe-CommandSet-1.1] 3.3.4 Read command */ + nvme_queue_submit(&io_queue, &(nvme_submission_queue_entry_t){ + .cdw0 = /* CID */ (0b1011 << 16) | /* PSDT */ 0 | /* FUSE */ 0 | /* OPC */ 0x2, + .nsid = 0x1, // TOOD: Why is NSID 1 now ???? + .cdw10 = /* SLBA[31:00] */ 0x0, + .cdw11 = /* SLBA[63:32] */ 0x0, + .cdw12 = /* LR */ (0b1U << 31) | /* others */ 0 | /* NLB */ (NUMBER_BLOCKS - 1), + .prp2 = 0x0, + .prp1 = data_region_paddr, + }); + } else if (z == 1) { + sddf_dprintf("doing nothing :P -- should get another IRQ \n"); + /* + So this works fine on QEMU AArch64... + but on QEMU RISCV level interrupts only get triggerred once + ... this caused issues in linux + ... https://www.mail-archive.com/qemu-devel@nongnu.org/msg931360.html + */ + } else if (z == 2) { + nvme_completion_queue_entry_t cq_entry; + int ret = nvme_queue_consume(&io_queue, &cq_entry); + assert(ret == 0); + assert((cq_entry.phase_tag_and_status & _MASK(1, 15)) == 0x0); // §4.2.3 Status Field + + for (int i = 0; i < 8; i++) { + LOG_NVME("Data [%02x]: %02x\n", i, data_region[i]); + } + + for (int i = 0; i < 4096; i++) { + data_region[i] = data_region[i] ^ 0xbb; + } + + /* [NVMe-CommandSet-1.1] ??????? write */ + nvme_queue_submit(&io_queue, &(nvme_submission_queue_entry_t){ + .cdw0 = /* CID */ (0b1101 << 16) | /* PSDT */ 0 | /* FUSE */ 0 | /* OPC */ 0x1, + .nsid = 0x1, // TOOD: Why is NSID 1 now ???? + .cdw10 = /* SLBA[31:00] */ 0x0, + .cdw11 = /* SLBA[63:32] */ 0x0, + .cdw12 = /* LR */ (0b1U << 31) | /* others */ 0 | /* NLB */ (NUMBER_BLOCKS - 1), + .prp2 = 0x0, + .prp1 = data_region_paddr, + }); + } else if (z == 3) { + nvme_completion_queue_entry_t cq_entry; + int ret = nvme_queue_consume(&io_queue, &cq_entry); + assert(ret == 0); + assert((cq_entry.phase_tag_and_status & _MASK(1, 15)) == 0x0); // §4.2.3 Status Field + + LOG_NVME("Got response for write!\n"); + } +} diff --git a/drivers/nvme/nvme.h b/drivers/nvme/nvme.h new file mode 100644 index 000000000..1a1f5618f --- /dev/null +++ b/drivers/nvme/nvme.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include + +#include + +/* + References: + + [NVMe-2.1] NVM Express Base Specification Revision 2.1 (Aug 5, 2024) + https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-Revision-2.1-2024.08.05-Ratified.pdf + + [NVMEe-Transport-PCIe-1.1] NVMe over PCIe Transport Specification, Revision 1.1 (Aug 5, 2024) + https://nvmexpress.org/wp-content/uploads/NVM-Express-PCI-Express-Transport-Specification-Revision-1.1-2024.08.05-Ratified.pdf + + [NVMe-CommandSet-1.1] NVM Command Set Specification, Revision 1.1 (Aug 5, 2024) + https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-Revision-1.1-2024.08.05-Ratified.pdf +*/ + +/* [NVMe-2.1] Section 3.1.4 Controller Properties + RO = Read Only + RW = Read Write + RWC = Read/Write '1' to clear + RWS = Read/Write '1' to set +*/ +typedef struct nvme_controller { + uint64_t cap; /* Controller Capabilities (RO) */ + struct vs { + uint8_t ter; /* Tertiary Version */ + uint8_t mnr; /* Minor Version */ + uint16_t mjr; /* Major Version */ + } vs; /* Version (RO) */ + uint32_t intms; /* Interrupt Mask Set (RWS) */ + uint32_t intmc; /* Interrupt Mask Clear (RWC) */ + uint32_t cc; /* Controller Configuration (RW) */ + uint32_t _reserved; + uint32_t csts; /* Controller Status */ + uint32_t nssr; /* NVM Subsystem Reset (optional) */ + uint32_t aqa; /* Admin Queue Attributes */ + uint64_t asq; /* Admin Submission Queue Base Address */ + uint64_t acq; /* Admin Completion Queue Base Address */ + + uint32_t cmbloc; /* Controller Memory Buffer Location (optional) */ + uint32_t cmbsz; /* Controller Memory Buffer Size (optional) */ + uint32_t bpinfo; /* Boot Partition Information (optional) */ + uint32_t bprsel; /* Boot Partition Read Select (optional) */ + uint64_t bpmbl; /* Boot Partition Memory Buffer Location (optional) */ + uint64_t cmbmsc; /* Controller Memory Buffer Memory Space Control (optional) */ + uint32_t cmbsts; /* Controller Memory Buffer Status (optional) */ + uint32_t cmbebs; /* Controller Memory Buffer Elasticity Buffer Size (optional) */ + uint32_t cmbswtp; /* Controller Memory Buffer Sustained Write Throughput (optional) */ + uint32_t nssd; /* NVM Subsystem Shutdown (optional) */ + + uint32_t crto; /* Controller Ready Timeouts */ + uint32_t _reserved2; +} nvme_controller_t; + +_Static_assert(offsetof(nvme_controller_t, _reserved2) == 0x6C, "nvme_controller_t must match spec layout"); + +#define _LEN(start, end) ((end - start) + 1) +#define _MASK(start, end) ((BIT(_LEN(start, end)) - 1) << (start)) + +/* [NVMe-2.1] 3.1.4.1 Offset 0h: CAP – Controller Capabilities */ +#define NVME_CAP_NOIOCSS BIT(37 + 7) /* No I/O Command Set Support */ +#define NVME_CAP_IOCSS BIT(37 + 6) /* I/O Command Set Support */ +#define NVME_CAP_NCSS BIT(37 + 0) /* NVM Command Set Support */ +#define NVME_CAP_DSTRD_SHIFT 32 /* Doorbell Stride (2 ^ (2 + DSTRD)) */ +#define NVME_CAP_DSTRD_MASK _MASK(32, 35) /* Doorbell Stride (2 ^ (2 + DSTRD)) */ + +/* [NVMe-2.1] 3.1.4.5 Offset 14h: CC – Controller Configuration */ +#define NVME_CC_IOCQES_SHIFT 20 /* I/O Completion Queue Entry Size */ +#define NVME_CC_IOCQES_MASK _MASK(20, 23) /* I/O Completion Queue Entry Size */ +#define NVME_CC_IOSQES_SHIFT 16 /* I/O Submission Queue Entry Size */ +#define NVME_CC_IOSQES_MASK _MASK(16, 19) /* I/O Submission Queue Entry Size */ +#define NVME_CC_MPS_SHIFT 7 /* Host Memory Page Size */ +#define NVME_CC_MPS_MASK _MASK(7, 10) /* Host Memory Page Size */ +#define NVME_CC_CSS_SHIFT 4 /* I/O Command Set Selected */ +#define NVME_CC_CSS_MASK _MASK(4, 6) /* I/O Command Set Selected */ +#define NVME_CC_EN BIT(0) /* Controller Enable */ + +/* [NVMe-2.1] 3.1.4.6 Offset 1Ch: CSTS – Controller Status */ +#define NVME_CSTS_RDY BIT(0) /* Controller Ready (RO) */ + +/* [NVMe-2.1] 3.1.4.7 Offset 20h: NSSR – NVM Subsystem Reset */ +#define NVME_NSSRC_VALUE (0x4E564D65) /* NVM Subsystem Reset Control - Reset value */ + +/* [NVMe-2.1] 3.1.4.8 Offset 24h: AQA – Admin Queue Attributes */ +#define NVME_AQA_ACQS_SHIFT 16 /* Admin Completion Queue Size (#entries) */ +#define NVME_AQA_ACQS_MASK _MASK(16, 27) /* Admin Completion Queue Size (#entries) */ +#define NVME_AQA_ASQS_SHIFT 0 /* Admin Submission Queue Size (#entries) */ +#define NVME_AQA_ASQS_MASK _MASK(0, 11) /* Admin Submission Queue Size (#entries) */ + +/** + * Queue Structures + */ + +/* [NVMe-2.1] 4.1 Submission Queue Entry */ +typedef struct nvme_submission_queue_entry { + // TODO: split out to opcode etc (figure 91??) + + uint32_t cdw0; /* Command Dword 0 (common) */ + uint32_t nsid; /* Namespace Identifier */ + uint32_t cdw2; /* Command Dword 2 (command-specific) */ + uint32_t cdw3; /* Command Dword 3 (command-specific) */ + uint64_t mptr; /* Metadata Pointer */ + uint64_t prp1; /* Data Pointer - PRP Entry 1 */ + uint64_t prp2; /* Data Pointer - PRP Entry 2 */ + uint32_t cdw10; /* Command Dword 10 (command-specific) */ + uint32_t cdw11; /* Command Dword 11 (command-specific) */ + uint32_t cdw12; /* Command Dword 12 (command-specific) */ + uint32_t cdw13; /* Command Dword 13 (command-specific) */ + uint32_t cdw14; /* Command Dword 14 (command-specific) */ + uint32_t cdw15; /* Command Dword 15 (command-specific) */ +} nvme_submission_queue_entry_t; +_Static_assert(sizeof(nvme_submission_queue_entry_t) == 64, "Each Common Command Format command is 64 bytes in size."); + +/* [NVMe-2.1] 4.2 Completion Queue Entry */ +typedef struct nvme_completion_queue_entry { + uint32_t cdw0; /* Command Dword 0 (command-specific) */ + uint32_t cdw1; /* Command Dword 1 (command-specific) */ + uint16_t sqhd; /* Submission Queue Head Pointer */ + uint16_t sqid; /* Submission Queue ID */ + uint16_t cid; /* Command Identifier */ + uint16_t phase_tag_and_status; /* Phase Tag (P) and Status */ +} nvme_completion_queue_entry_t; +_Static_assert(sizeof(nvme_completion_queue_entry_t) == 16, + "The Common Completion Queue Entry Layout is 16 bytes in size"); + +/** + * Below here is NVMe PCIe Transport Specific Properties. + */ + +#define NVME_PCIE_SQT_MASK _MASK(0, 15) /* Submission Queue Tail*/ +#define NVME_PCIE_CQH_MASK _MASK(0, 15) /* Completion Queue Head */ + +#define NVME_PCIE_DOORBELL_OFFSET(i, DSTRD) (0x1000 + ((i) * (4 << (DSTRD)))) diff --git a/drivers/nvme/nvme_debug.h b/drivers/nvme/nvme_debug.h new file mode 100644 index 000000000..c0c1e03f0 --- /dev/null +++ b/drivers/nvme/nvme_debug.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include "nvme.h" +#include "nvme_queue.h" +#include + +// clang-format off +#define LOG_NVME(...) do{ sddf_dprintf("NVME|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +// clang-format on + +static void nvme_debug_dump_controller_regs(volatile nvme_controller_t *nvme_controller) +{ + LOG_NVME("CAP: %016lx\n", nvme_controller->cap); + LOG_NVME("VS: major: %u, minor: %u, tertiary: %u\n", nvme_controller->vs.mjr, nvme_controller->vs.mnr, + nvme_controller->vs.ter); + LOG_NVME("INTMS: %08x\n", nvme_controller->intms); + LOG_NVME("INTMC: %08x\n", nvme_controller->intmc); + LOG_NVME("CC: %08x\n", nvme_controller->cc); + LOG_NVME("CSTS: %08x\n", nvme_controller->csts); + LOG_NVME("AQA: %08x\n", nvme_controller->aqa); + LOG_NVME("ASQ: %016lx\n", nvme_controller->asq); + LOG_NVME("ACQ: %016lx\n", nvme_controller->acq); +} + +static nvme_completion_queue_entry_t nvme_queue_submit_and_consume_poll(nvme_queue_info_t *queue, + nvme_submission_queue_entry_t *entry) +{ + nvme_queue_submit(queue, entry); + + nvme_completion_queue_entry_t response; + int i = 0; + while (true) { + int ret = nvme_queue_consume(queue, &response); + if (ret == 0) { + LOG_NVME("received a response for submission with CDW0: %x\n", entry->cdw0); + return response; + } + + if (i % 100 == 0) { + LOG_NVME("waiting for response to submission with CDW0: %x\n", entry->cdw0); + } + } +} + +// TODO: tidy up. +/* 5.1.12.1.2 Error Information (Log Page Identifier 01h) */ +typedef struct { + uint64_t ecnt; /* Error Count; unique ID for this error. (retained across power off) */ + uint16_t sqid; /* Submission Queue ID */ + uint16_t cid; /* Command ID */ + uint16_t sts; /* Status Info (from the completion queue entry - Status + Phase) */ + uint16_t pel; /* Parameter Error location (!!!!) */ + uint64_t lba; /* Logical Block Address */ + uint32_t nsid; + uint8_t vsia; /* vendor specific info available */ + uint8_t trtype; /* transport type */ + uint8_t csi; /* command set indiciator (valid if version > 0x1)*/ + uint8_t opc; /* opcode (valid if version > 0x1)*/ + uint64_t csinfo; /* command specific information (if specified in the command) */ + uint16_t ttsi; /* transport type specification information */ + uint8_t _reserved[20]; + uint8_t lpver; /* log page version */ +} nvme_error_information_log_page_t; +_Static_assert(sizeof(nvme_error_information_log_page_t) == 64, "should be 64 bytes."); + +static void nvme_debug_get_error_information_log_page(nvme_queue_info_t *admin_queue, uint64_t data_paddr, + volatile void *data) +{ + LOG_NVME("!!! LOG PAGE !!!\n"); + nvme_completion_queue_entry_t entry; + entry = nvme_queue_submit_and_consume_poll( + admin_queue, &(nvme_submission_queue_entry_t) { + .cdw0 = /* CID */ (0b1001 << 16) | /* PSDT */ 0 | /* FUSE */ 0 | /* OPC */ 0x2, + .prp2 = 0, + .prp1 = data_paddr, + .cdw10 = /* NUMDL*/ (0x100 << 16) | /* LID*/ 0x01, + .cdw11 = 0x0, + .cdw12 = 0x0, + }); + + assert((entry.phase_tag_and_status & _MASK(1, 15)) == 0x0); // §4.2.3 Status Field + + volatile nvme_error_information_log_page_t *errors = data; + for (int i = 0; i < 2; i++) { + LOG_NVME("Error 0x%lx\n", errors[i].ecnt); + // These produce Store/AMO faults, NEAR THE BEGINNING of this function??? TODO??? + LOG_NVME("\tSQID: 0x%x\n", errors[i].sqid); + LOG_NVME("\t CID: 0x%x\n", errors[i].cid); + LOG_NVME("\t STS: 0x%x\n", errors[i].sts); + LOG_NVME("\t PEL: 0x%x\n", errors[i].pel); + LOG_NVME("\t(elided)\n"); + } +} diff --git a/drivers/nvme/nvme_queue.h b/drivers/nvme/nvme_queue.h new file mode 100644 index 000000000..911c5cce5 --- /dev/null +++ b/drivers/nvme/nvme_queue.h @@ -0,0 +1,84 @@ +#pragma once + +#include "nvme.h" + +typedef struct nvme_queue_info { + struct { + nvme_submission_queue_entry_t *queue; + uint16_t capacity; + uint16_t tail; + volatile uint32_t *doorbell; + } submission; + + struct { + nvme_completion_queue_entry_t *queue; + uint16_t capacity; + uint16_t head; + volatile uint32_t *doorbell; + _Bool phase; + } completion; + +} nvme_queue_info_t; + + +// y is the submission queue index +static inline void nvme_queues_init(nvme_queue_info_t *queue, uint16_t y, volatile nvme_controller_t *nvme_controller, + nvme_submission_queue_entry_t *submission_queue, uint16_t submission_capacity, + nvme_completion_queue_entry_t *completion_queue, uint16_t completion_capacity) +{ + uint8_t DSTRD = (nvme_controller->cap & NVME_CAP_DSTRD_MASK) >> NVME_CAP_DSTRD_SHIFT; + + /* [NVMEe-Transport-PCIe-1.1] 3.1.2.1 SQyTDBL & 3.1.2.2 CQyHDBL + Note: "The host should not read the doorbell registers" + */ + volatile uint32_t *submission_doorbell = (void *)nvme_controller + NVME_PCIE_DOORBELL_OFFSET(2 * y, DSTRD); + volatile uint32_t *completion_doorbell = (void *)nvme_controller + NVME_PCIE_DOORBELL_OFFSET(2 * y + 1, DSTRD); + + *queue = (nvme_queue_info_t){ + .submission = { + .queue = submission_queue, + .capacity = submission_capacity, + .tail = 0, + .doorbell = submission_doorbell, + }, + .completion = { + .queue = completion_queue, + .capacity = completion_capacity, + .head = 0, + .doorbell = completion_doorbell, + .phase = 0, + }, + }; +} + +static inline void nvme_queue_submit(nvme_queue_info_t *queue, nvme_submission_queue_entry_t *entry) +{ + queue->submission.queue[queue->submission.tail] = *entry; + + // todo: overflow. todo: full queue. + queue->submission.tail++; + + *queue->submission.doorbell = queue->submission.tail; +} + +static inline int nvme_queue_consume(nvme_queue_info_t *queue, nvme_completion_queue_entry_t *entry) +{ + /* if the head is not new */ + nvme_completion_queue_entry_t *cq_head_entry = &queue->completion.queue[queue->completion.head]; +// 4.2.41 phase tag + if ((cq_head_entry->phase_tag_and_status & BIT(0)) == queue->completion.phase) { + return -1; + } + + *entry = *cq_head_entry; + + queue->completion.head++; /* TODO: wrapping */ + // if (cq_head == length) { + // cq_head = 0; + // phase ^= 1; /* flip phase */ + // } + + *queue->completion.doorbell = queue->completion.head; + + return 0; +} diff --git a/drivers/pcie/starfive/pcie.c b/drivers/pcie/starfive/pcie.c new file mode 100644 index 000000000..4a518f539 --- /dev/null +++ b/drivers/pcie/starfive/pcie.c @@ -0,0 +1,343 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +#include "pcie.h" + +// TODO: Separate components or something. +void nvme_init(); + +// void *pcie_regs; + +/* This is the PCI Enhanced Configuration Access Mechanism, + See [PCIe-2.0] §7.22 +*/ +uintptr_t pcie_config; +#if defined(CONFIG_PLAT_ROCKPRO64) +#define PCIE_CONFIG_SIZE (0x1000000 - 0x800000) +#elif defined(CONFIG_PLAT_IMX8MM_EVK) +#define PCIE_CONFIG_SIZE 0x10000 +#elif defined(CONFIG_PLAT_STAR64) +#define PCIE_CONFIG_SIZE 0x1000000 +#elif defined(CONFIG_PLAT_QEMU_RISCV_VIRT) || defined(CONFIG_PLAT_QEMU_ARM_VIRT) +#define PCIE_CONFIG_SIZE 0x1000000 +#else +#error "unknown pcie config region size" +#endif + +/* bus between [0, 256) + device between [0, 31) + function between [0, 8) +*/ +uintptr_t get_bdf_offset(uint8_t bus, uint8_t device, uint8_t function) +{ + /* [PCIe-2.0] Table 7-1 Enhanced Configuration Address Mapping */ + + uintptr_t offset = ((uint32_t)bus << 20) | ((uint32_t)device << 15) | ((uint32_t)function << 12); + + assert(offset % 4096 == 0); /* check page aligned */ + + return offset; +} + +static bool found_nvme = false; +static uint8_t nvme_bus; +static uint8_t nvme_device; +static uint8_t nvme_function; +uintptr_t nvme_controller_paddr; + +void device_print(uint8_t bus, uint8_t device, uint8_t function) +{ + uintptr_t offset = get_bdf_offset(bus, device, function); + assert(offset < PCIE_CONFIG_SIZE); + void *config_base = (void *)(pcie_config + offset); + volatile pcie_header_t *header = (pcie_header_t *)config_base; + + if (header->vendor_id == PCIE_VENDOR_INVALID) { + return; + } + +#if defined(CONFIG_PLAT_ROCKPRO64) + // https://github.com/torvalds/linux/commit/d84c572de1a360501d2e439ac632126f5facf59d + if (bus == 0 && device > 0) { + return; + } +#endif + +#if defined(CONFIG_PLAT_STAR64) + /* + See: plda_pcie_addr_valid() in U-Boot + https://lore.kernel.org/u-boot/20230423105859.125764-2-minda.chen@starfivetech.com/ + + In the secondary bus of host bridge, can only access bus device 0; + all other devices are duplicates of device 0. + + @todo there appears to be some way to change the secondary bus number? + see e.g. plda_pcie_config_write() + so it might not always be bus number 1. + */ + if (bus == 1 && device > 0) { + return; + } +#endif + + sddf_dprintf("\nB.D:F: %02x:%02x.%01x\n", bus, device, function); + sddf_dprintf("vendor ID: 0x%04x\n", header->vendor_id); + sddf_dprintf("device ID: 0x%04x\n", header->device_id); + sddf_dprintf("command register: 0x%04x\n", header->command); + sddf_dprintf("status register: 0x%04x\n", header->status); + sddf_dprintf("revision ID: 0x%02x\n", header->revision_id); + sddf_dprintf("base-class code: 0x%02x | sub-class code: 0x%02x\n", header->base_class_code, header->subclass_code); + + // rockchip is ????? +#if !defined(CONFIG_PLAT_ROCKPRO64) + // enable bus mastering... and memory accesses + header->command |= BIT(2) | BIT(1); +#endif + sddf_dprintf("enabled bus master and memory access\n"); + if (header->base_class_code == 0x1 && header->subclass_code == 0x8 && !found_nvme) { + sddf_dprintf("FOUND NVME!!!\n"); + found_nvme = true; + nvme_bus = bus; + nvme_device = device; + nvme_function = function; + + // TODO: hacky + assert(nvme_controller_paddr != 0); + volatile pcie_header_type0_t *type0_header = (pcie_header_type0_t *)config_base; + header->command &= ~BIT(1); + type0_header->base_address_registers[0] = nvme_controller_paddr; + type0_header->base_address_registers[1] = 0x0; + header->command |= BIT(1); + } + sddf_dprintf("header type: 0x%02x\n", header->header_type); + + sddf_dprintf("\thas multi-functions: %s\n", + header->header_type & PCIE_HEADER_TYPE_HAS_MULTI_FUNCTIONS ? "yes" : "no"); + sddf_dprintf("\tlayout variant: 0x%02lx\n", header->header_type & PCIE_HEADER_TYPE_LAYOUT_MASK); + + + if ((header->header_type & PCIE_HEADER_TYPE_LAYOUT_MASK) == PCIE_HEADER_TYPE_GENERAL) { + volatile pcie_header_type0_t *type0_header = (pcie_header_type0_t *)config_base; + for (int i = 0; i < 6; i++) { + uint32_t bar = type0_header->base_address_registers[i]; + sddf_dprintf("BAR%01d raw val: %08x\n", i, bar); + if (bar == 0) { + sddf_dprintf("\tunimplemented\n"); + continue; + } + + if (bar & BIT(0)) { + sddf_dprintf("\tbase address for I/O\n"); + sddf_dprintf("\taddress: 0x%08lx\n", bar & ~(BIT(0) | BIT(1))); + } else { + sddf_dprintf("\tbase address for memory\n"); + sddf_dprintf("\ttype: "); + switch ((bar & (BIT(1) | BIT(2))) >> 1) { + case 0b00: + sddf_dprintf("32-bit space\n"); + sddf_dprintf("\tfull address: 0x%08lx\n", bar & ~(BIT(4) - 1)); + break; + + case 0b10: + sddf_dprintf("64-bit space\n"); + if (i >= 5) { + sddf_dprintf("\tspecified 64-bit in the last slot, ignoring..."); + continue; + } + + uint32_t bar_upper = type0_header->base_address_registers[i + 1]; + + sddf_dprintf("\tfull address: 0x%08x_%08lx\n", bar_upper, bar & ~(BIT(4) - 1)); + + /* [PCI-3.0] 6.2.5.1 Address Maps (Implementation Note) p227*/ + + // Decode (I/O or memory) of a register is disabled via the command register before sizing a Base Address register. + header->command &= ~(BIT(1)); + + // calculate size. + type0_header->base_address_registers[i] = 0xffffffff; + type0_header->base_address_registers[i + 1] = 0xffffffff; + + // read back + uint32_t size_lower = type0_header->base_address_registers[i]; + uint32_t size_upper = type0_header->base_address_registers[i + 1]; + uint64_t size_readback = ((uint64_t)size_upper << 32) | (size_lower); + // calculation can be done from the 32-bit value read by first clearing encoding information bits + // (bit 0 for I/O, bits 0-3 formemory), inverting all 32 bits (logical NOT), then incrementing by 1 + size_readback &= ~(BIT(3) | BIT(2) | BIT(1) | BIT(0)); + size_readback = ~size_readback; + size_readback += 1; + + sddf_dprintf("\tsize: 0x%lx\n", size_readback); + + // The original value in the Base Address register is restored before re-enabling + // decode in the command register of the device. + type0_header->base_address_registers[i] = bar; + type0_header->base_address_registers[i + 1] = bar_upper; + header->command |= BIT(1); + + i += 1; // skip one slot. + break; + + default: + sddf_dprintf("reserved\n"); + } + sddf_dprintf("\tprefetchable: %s\n", bar & BIT(3) ? "yes" : "no"); + } + } + } +} + +void print_pci_info(uint8_t bus, uint8_t device, uint8_t function, bool to_mask) +{ + uintptr_t offset = get_bdf_offset(bus, device, function); + assert(offset < PCIE_CONFIG_SIZE); + void *config_base = (void *)(pcie_config + offset); + volatile pcie_header_t *header = (pcie_header_t *)config_base; + + if (header->vendor_id == PCIE_VENDOR_INVALID) return; + + sddf_dprintf("\nB.D:F: %02x:%02x.%01x\n", bus, device, function); + sddf_dprintf("ACK: command register: 0x%04x\n", header->command); + sddf_dprintf("ACK: status register: 0x%04x\n", header->status); + sddf_dprintf("\t(PIN-based) interrupt disable: %s\n", (header->command & BIT(10)) ? "is disabled" : "is not disabled"); + sddf_dprintf("\t(PIN-based) interrupt status: %s\n", (header->status & BIT(3)) ? "asserted" : "none"); + + return; + + /* mask out the interrupt... */ + // https://elixir.bootlin.com/linux/v5.18-rc4/source/drivers/pci/pci.c#L4595 + if (to_mask) { + header->command |= BIT(10); + } else { + header->command &= ~BIT(10); + } + + // we only access IRQ PIn and Line an Capiblites which are actually comon (TODO) + // assert((header->header_type & PCIE_HEADER_TYPE_LAYOUT_MASK) == PCIE_HEADER_TYPE_GENERAL); + volatile pcie_header_type0_t *type0_header = (pcie_header_type0_t *)config_base; + // 0xff (255) means "unknown" or "no connection" on interrupt line + // interrupt pin: 0 = no, 1 => INTA#, 2 => INTB#, 3 => INTC#, 4 => INTD# + sddf_printf("\t(PIN-based) interrupt line: %02x, interrupt pin: %02x\n", type0_header->interrupt_line, type0_header->interrupt_pin); + + if (!(header->status & BIT(4))) { + sddf_dprintf("no capabilities??\n"); + }; /* assert capabilities list status exists */ + sddf_dprintf("start capabilities PTR: %u\n", type0_header->capabilities_pointer); + + /* ach capability +must be DWORD aligned. The bottom two bits of all pointers (including the initial pointer +at 34h) are reserved and must be implemented as 00b although software must mask them to +allow for future uses of these bits. A pointer value of 00h is used to indicate the last +capability in the list.*/ + uint8_t current_ptr = type0_header->capabilities_pointer & ~0b11; + /* process capabilities list to get MSI */ + while (current_ptr != 0) { + volatile uint8_t *capability = (void *)(config_base + current_ptr); + /* 6.8.1 of the pci spec */ + uint8_t cap_id = capability[0]; + sddf_dprintf("cap ptr: %x, id: %x\n", current_ptr, cap_id); + uint8_t PCIE_CAPABILITY_ID_MSI = 0x05; /* #define this. */ + uint8_t PCIE_CAPABILITY_ID_MSIX = 0x11; /* #define this. */ + uint8_t PCIE_CAPABILITY_ID_PM = 0x1; /* #define this. */ + if (cap_id == PCIE_CAPABILITY_ID_MSI) { + uint16_t message_control = capability[2] | ((uint16_t)capability[3] << 8); + sddf_dprintf("\tmessage control(msi): %x\n", message_control); + } else if (cap_id == PCIE_CAPABILITY_ID_MSIX) { + uint16_t message_control = capability[2] | ((uint16_t)capability[3] << 8); + sddf_dprintf("\tmessage control(msix): %x\n", message_control); + } else if (cap_id == PCIE_CAPABILITY_ID_PM) { + // https://lekensteyn.nl/files/docs/PCI_Power_Management_12.pdf¸ §3.2 + // not this... + uint16_t control = capability[4] | ((uint16_t)capability[5] << 8); + sddf_dprintf("\tpower management ctrl: %x\n", control); + } else if (cap_id == 0x10 /* PCI Express */) { + sddf_dprintf("\troot control(pcie): %x\n", *(uint16_t *)&(capability[0x1C])); + sddf_dprintf("\troot status(pcie): %x\n", *(uint32_t *)&(capability[0x20])); + } else { + sddf_dprintf("\tunknown\n"); + } + + current_ptr = (capability[1] & ~0b11); + } +} + +void init() +{ + sddf_dprintf("pcie driver starting!\n"); + +#if 1 + for (uint8_t bus = 0; bus <= 255; bus++) { + for (uint8_t device = 0; device < 32; device++) { + // for (uint8_t function = 0; function < 8; function++) { + for (uint8_t function = 0; function < 1; function++) { + uintptr_t offset = get_bdf_offset(bus, device, function); + if (offset >= PCIE_CONFIG_SIZE) { + goto out; + } + + /* TODO: This also configures BARs for the NVMe devices... */ + /* That is not printing. */ + device_print(bus, device, function); + } + } + } + +out: + sddf_dprintf("\n\nPCIE_ENUM_COMPLETE\n"); +#endif + + assert(found_nvme); + print_pci_info(nvme_bus, nvme_device, nvme_function, false); + nvme_init(); +} + +void nvme_continue(int z); +void notified(microkit_channel ch) +{ + static int i = 1; + sddf_dprintf("\n===============================\n"); + sddf_dprintf("notified on ch: %u\n", ch); + + if (i <= 3 /* keep i nsync with nvme_continue */) { + /* this shows asserted on all platforms, as you would expect */ + print_pci_info(nvme_bus, nvme_device, nvme_function, /* don't ack? */ false); + + sddf_dprintf("\nacking (%u)\n", i); + /* this usually acknowledges the CQ doorbell, causing interrupts to disappear + but for the second time we intentionally making the level interrupt fire + again by not acking it. + + on QEMU RISCV we see "should get an IRQ" and that's the last message + we see -> but [continues] + */ + nvme_continue(i); + /* TODO: we should use NVMe INTMS/INTMC while this is happening + because that PCI virtual INTx line is shared + howveer since we don't share it, meh! */ + microkit_irq_ack(ch); + + /* on all platforms this shows interrupt status: none after the 3rd + ack when we stop doing anything. + + [...] on QEMU RISCV this shows "asserted" yet we don't get further IRQs + + AND!!! on Star64 RISCV this shows "none" but we still get interrupts!! + */ + print_pci_info(nvme_bus, nvme_device, nvme_function, /* don't ack? */ false); + } else if (i < 10) { + sddf_dprintf("\nYou should not see this message -- trying to ACK again\n"); + microkit_irq_ack(ch); + } else { + sddf_dprintf("Stopping IRQ ACKing\n"); + } + + i++; +} diff --git a/drivers/pcie/starfive/pcie.h b/drivers/pcie/starfive/pcie.h new file mode 100644 index 000000000..0599ed74b --- /dev/null +++ b/drivers/pcie/starfive/pcie.h @@ -0,0 +1,85 @@ +#pragma once + +/* + References: + + [PCIe-2.0] PCI Express 2.0 Base Specification Revision 0.9 (Sep 11, 2006). + https://community.intel.com/cipcp26785/attachments/cipcp26785/fpga-intellectual-property/8220/1/PCI_Express_Base_Specification_v20.pdf + + [PCI-3.0] PCI Local Bus Specification Revision 3.0 (Feb 3, 2004) + https://lekensteyn.nl/files/docs/PCI_SPEV_V3_0.pdf +*/ + +/* [PCIe-2.0] §7.5 PCI-Compatible Configuration Registers + [PCI-3.0] §6.1 Configuration Space Organization + + All of these registers are read-only. +*/ +typedef struct pcie_header { + /* This field identifies the manufacturer of the device. + FFFFh is an invalid value for Vendor ID. */ + uint16_t vendor_id; + /* This field identifies the particular device. + This identifier is allocated by the vendor.*/ + uint16_t device_id; + /* Provides coarse control over a device's ability to generate and respond to PCI cycles. + See [PCI-3.0] §6.2.2 Device Control and [PCIe-2.0] §7.5.1.1 */ + uint16_t command; + /* The Status register is used to record status information for PCI bus related events. + See [PCI-3.0] §6.2.3 Device Status and [PCIe-2.0] §7.5.1.2 */ + uint16_t status; + /* + This register specifies a device specific revision identifier. The value + is chosen by the vendor. Zero is an acceptable value. This field + should be viewed as a vendor defined extension to the Device ID. + */ + uint8_t revision_id; + /* A specific register-level programming interface (if any). */ + uint8_t class_code_programming_interface; + /* sub-class code which identifies more specifically the function of the device*/ + uint8_t subclass_code; + /* Broadly classifies the type of function the device performs. */ + uint8_t base_class_code; + /* This field is implemented by PCI Express devices as a read-write field + for legacy compatibility purposes but has no effect on any PCI Express + device behavior. */ + uint8_t cacheline_size; + /* The Latency Timer does not apply to PCI Express. */ + uint8_t latency_timer; + /* This byte identifies the layout of the second part of the predefined header. */ + uint8_t header_type; + /* Built-in Self Test. Optional. */ + uint8_t bist; +} __attribute__((packed)) pcie_header_t; + +_Static_assert(sizeof(pcie_header_t) == 16, "PCI Common Configuration Space Header must be 16 bytes"); + +#define PCIE_VENDOR_INVALID 0xffff + +#define PCIE_HEADER_TYPE_HAS_MULTI_FUNCTIONS BIT(7) + +#define PCIE_HEADER_TYPE_LAYOUT_MASK (BIT(7) - 1) +#define PCIE_HEADER_TYPE_GENERAL 0x00 +#define PCIE_HEADER_TYPE_PCI_PCI_BRIDGE 0x01 + +/* [PCIe-2.0] §7.5.2 Type 0 Configuration Space Header */ +typedef struct pcie_header_type0 { + pcie_header_t common_header; + /* [PCI-3.0] 6.2.5. Base Addresses + Base Address Registers (BAR) can be 32-bit (1 slot) or 64-bit (2 slots). + */ + uint32_t base_address_registers[6]; + /* Points to the Card Information Structure (CIS) for a CardBus card. */ + uint32_t cardbus_cis_pointer; + uint16_t subsystem_vendor_id; + uint16_t subsystem_id; + uint32_t expansion_rom_base_address; + uint8_t capabilities_pointer; + uint8_t _reserved[7]; + uint8_t interrupt_line; + uint8_t interrupt_pin; + uint8_t min_gnt; + uint8_t max_lat; +} __attribute__((packed)) pcie_header_type0_t; + +_Static_assert(sizeof(pcie_header_type0_t) == 64, "Type 0 Configuration Space Header must be 64 bytes"); diff --git a/examples/pcie/board/imx8mm_evk/pcie.system b/examples/pcie/board/imx8mm_evk/pcie.system new file mode 100644 index 000000000..cd4fe8c28 --- /dev/null +++ b/examples/pcie/board/imx8mm_evk/pcie.system @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/pcie/board/qemu_virt_aarch64/pcie.system b/examples/pcie/board/qemu_virt_aarch64/pcie.system new file mode 100644 index 000000000..754e7fd77 --- /dev/null +++ b/examples/pcie/board/qemu_virt_aarch64/pcie.system @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/pcie/board/qemu_virt_riscv64/pcie.system b/examples/pcie/board/qemu_virt_riscv64/pcie.system new file mode 100644 index 000000000..5f8200059 --- /dev/null +++ b/examples/pcie/board/qemu_virt_riscv64/pcie.system @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/pcie/board/rockpro64/pcie.system b/examples/pcie/board/rockpro64/pcie.system new file mode 100644 index 000000000..613612478 --- /dev/null +++ b/examples/pcie/board/rockpro64/pcie.system @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/pcie/board/star64/pcie.system b/examples/pcie/board/star64/pcie.system new file mode 100644 index 000000000..dbf5f6c62 --- /dev/null +++ b/examples/pcie/board/star64/pcie.system @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/pcie/build.zig b/examples/pcie/build.zig new file mode 100644 index 000000000..46fd17fb9 --- /dev/null +++ b/examples/pcie/build.zig @@ -0,0 +1,222 @@ +// +// Copyright 2024, UNSW +// SPDX-License-Identifier: BSD-2-Clause +// +const std = @import("std"); + +const MicrokitBoard = enum { + star64, // interrupts dodgy + rockpro64, // not working + imx8mm_evk, + qemu_virt_aarch64, // works + qemu_virt_riscv64, // interrupts dodgy +}; + +const Target = struct { + board: MicrokitBoard, + zig_target: std.Target.Query, +}; + +const targets = [_]Target{ + .{ + .board = MicrokitBoard.star64, + .zig_target = std.Target.Query{ + .cpu_arch = .riscv64, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.baseline_rv64 }, + .os_tag = .freestanding, + .abi = .none, + }, + }, + .{ + .board = MicrokitBoard.rockpro64, + .zig_target = std.Target.Query{ + .cpu_arch = .aarch64, + .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.cortex_a53 }, +// .cpu_features_add = std.Target.aarch64.featureSet(&[_]std.Target.aarch64.Feature{ .strict_align }), + .os_tag = .freestanding, + .abi = .none, + }, + }, + .{ + .board = MicrokitBoard.imx8mm_evk, + .zig_target = std.Target.Query{ + .cpu_arch = .aarch64, + .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.cortex_a53 }, + .cpu_features_add = std.Target.aarch64.featureSet(&[_]std.Target.aarch64.Feature{ .strict_align }), + .os_tag = .freestanding, + .abi = .none, + }, + }, + .{ + .board = MicrokitBoard.qemu_virt_aarch64, + .zig_target = std.Target.Query{ + .cpu_arch = .aarch64, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_a53 }, + .os_tag = .freestanding, + .abi = .none, + }, + }, + .{ + .board = MicrokitBoard.qemu_virt_riscv64, + .zig_target = std.Target.Query{ + .cpu_arch = .riscv64, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.baseline_rv64 }, + .os_tag = .freestanding, + .abi = .none, + }, + }, +}; + +fn findTarget(board: MicrokitBoard) std.Target.Query { + for (targets) |target| { + if (board == target.board) { + return target.zig_target; + } + } + + std.log.err("Board '{}' is not supported\n", .{board}); + std.posix.exit(1); +} + +const ConfigOptions = enum { debug, release, benchmark }; + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + + // Getting the path to the Microkit SDK before doing anything else + const microkit_sdk_arg = b.option([]const u8, "sdk", "Path to Microkit SDK"); + if (microkit_sdk_arg == null) { + std.log.err("Missing -Dsdk=/path/to/sdk argument being passed\n", .{}); + std.posix.exit(1); + } + const microkit_sdk = microkit_sdk_arg.?; + + const microkit_config_option = b.option(ConfigOptions, "config", "Microkit config to build for") orelse ConfigOptions.debug; + const microkit_config = @tagName(microkit_config_option); + + // Get the Microkit SDK board we want to target + const microkit_board_option = b.option(MicrokitBoard, "board", "Microkit board to target"); + + if (microkit_board_option == null) { + std.log.err("Missing -Dboard= argument being passed\n", .{}); + std.posix.exit(1); + } + const target = b.resolveTargetQuery(findTarget(microkit_board_option.?)); + const microkit_board = @tagName(microkit_board_option.?); + + const microkit_board_dir = b.fmt("{s}/board/{s}/{s}", .{ microkit_sdk, microkit_board, microkit_config }); + const microkit_tool = b.fmt("{s}/bin/microkit", .{microkit_sdk}); + const libmicrokit = b.fmt("{s}/lib/libmicrokit.a", .{microkit_board_dir}); + const libmicrokit_include = b.fmt("{s}/include", .{microkit_board_dir}); + const libmicrokit_linker_script = b.fmt("{s}/lib/microkit.ld", .{microkit_board_dir}); + + const sddf_dep = b.dependency("sddf", .{ + .target = target, + .optimize = optimize, + .libmicrokit = @as([]const u8, libmicrokit), + .libmicrokit_include = @as([]const u8, libmicrokit_include), + .libmicrokit_linker_script = @as([]const u8, libmicrokit_linker_script), + }); + + const driver_class = switch (microkit_board_option.?) { + .star64 => "starfive", + .rockpro64 => "starfive", // hack + .imx8mm_evk => "starfive", // hack + .qemu_virt_aarch64 => "starfive", // hack + .qemu_virt_riscv64 => "starfive", // hack + }; + + const driver = sddf_dep.artifact(b.fmt("driver_pcie_{s}.elf", .{driver_class})); + // This is required because the SDF file is expecting a different name to the artifact we + // are dealing with. + const driver_install = b.addInstallArtifact(driver, .{ .dest_sub_path = "pcie_driver.elf" }); + + const client = b.addExecutable(.{ + .name = "client.elf", + .target = target, + .optimize = optimize, + .strip = false, + }); + + client.addCSourceFile(.{ .file = b.path("client.c") }); + client.addIncludePath(sddf_dep.path("include")); + client.linkLibrary(sddf_dep.artifact("util")); + client.linkLibrary(sddf_dep.artifact("util_putchar_debug")); + + client.addIncludePath(.{ .cwd_relative = libmicrokit_include }); + client.addObjectFile(.{ .cwd_relative = libmicrokit }); + client.setLinkerScriptPath(.{ .cwd_relative = libmicrokit_linker_script }); + + b.installArtifact(client); + + const system_description_path = b.fmt("board/{s}/pcie.system", .{microkit_board}); + const final_image_dest = b.getInstallPath(.bin, "./loader.img"); + const microkit_tool_cmd = b.addSystemCommand(&[_][]const u8{ microkit_tool, system_description_path, "--search-path", b.getInstallPath(.bin, ""), "--board", microkit_board, "--config", microkit_config, "-o", final_image_dest, "-r", b.getInstallPath(.prefix, "./report.txt") }); + microkit_tool_cmd.setEnvironmentVariable("MICROKIT_SDK", microkit_sdk); + microkit_tool_cmd.step.dependOn(b.getInstallStep()); + microkit_tool_cmd.step.dependOn(&driver_install.step); + const microkit_step = b.step("microkit", "Compile and build the final bootable image"); + microkit_step.dependOn(µkit_tool_cmd.step); + b.default_step = microkit_step; + + if (std.mem.eql(u8, microkit_board, "qemu_virt_aarch64")) { + const create_disk_cmd = b.addSystemCommand(&[_][]const u8{ + "bash", "../blk/mkvirtdisk", + }); + const disk = create_disk_cmd.addOutputFileArg("disk"); + create_disk_cmd.addArgs(&[_][]const u8{ + "1", "512", b.fmt("{}", .{ 1024 * 1024 * 16 }), + }); + const disk_install = b.addInstallFile(disk, "disk"); + disk_install.step.dependOn(&create_disk_cmd.step); + + const qemu_cmd = b.addSystemCommand(&[_][]const u8{ + "qemu-system-aarch64", + "-machine", "virt,virtualization=on,highmem=off,secure=off", + "-cpu", "cortex-a53", + "-serial", "mon:stdio", + "-device", b.fmt("loader,file={s},addr=0x70000000,cpu-num=0", .{final_image_dest}), + "-m", "size=2G", + "-nographic", + "-d", "guest_errors", + "-drive", b.fmt("file={s},if=none,format=raw,id=hd", .{ b.getInstallPath(.prefix, "disk") }), + "-device", "nvme,serial=deadbeef,drive=hd", + // "--trace", "pci*", + // "--trace", "pci_nvme*", + // "--trace", "nvme*", + }); + qemu_cmd.step.dependOn(b.default_step); + qemu_cmd.step.dependOn(&disk_install.step); + const simulate_step = b.step("qemu", "Simulate the image using QEMU"); + simulate_step.dependOn(&qemu_cmd.step); + } else if (std.mem.eql(u8, microkit_board, "qemu_virt_riscv64")) { + const create_disk_cmd = b.addSystemCommand(&[_][]const u8{ + "bash", "../blk/mkvirtdisk", + }); + const disk = create_disk_cmd.addOutputFileArg("disk"); + create_disk_cmd.addArgs(&[_][]const u8{ + "1", "512", b.fmt("{}", .{ 1024 * 1024 * 16 }), + }); + const disk_install = b.addInstallFile(disk, "disk"); + disk_install.step.dependOn(&create_disk_cmd.step); + + const qemu_cmd = b.addSystemCommand(&[_][]const u8{ + "qemu-system-riscv64", + "-machine", "virt", + "-serial", "mon:stdio", + "-kernel", final_image_dest, + "-m", "size=2G", + "-nographic", + "-d", "guest_errors", + "-drive", b.fmt("file={s},if=none,format=raw,id=hd", .{ b.getInstallPath(.prefix, "disk") }), + "-device", "nvme,serial=deadbeef,drive=hd", + // "--trace", "pci*", + // "--trace", "pci_nvme*", + // "--trace", "nvme*", + }); + qemu_cmd.step.dependOn(b.default_step); + qemu_cmd.step.dependOn(&disk_install.step); + const simulate_step = b.step("qemu", "Simulate the image using QEMU"); + simulate_step.dependOn(&qemu_cmd.step); + } +} diff --git a/examples/pcie/build.zig.zon b/examples/pcie/build.zig.zon new file mode 100644 index 000000000..450929c7d --- /dev/null +++ b/examples/pcie/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = "sddf_timer_example", + .version = "1.0.0", + + .dependencies = .{ + .sddf = .{ + .path = "../../" + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + } +} + diff --git a/examples/pcie/client.c b/examples/pcie/client.c new file mode 100644 index 000000000..9379e6a07 --- /dev/null +++ b/examples/pcie/client.c @@ -0,0 +1,18 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +void notified(microkit_channel ch) +{ + +} + +void init(void) +{ + sddf_dprintf("Hello from client\n"); +} diff --git a/examples/pcie/qemu.dts b/examples/pcie/qemu.dts new file mode 100644 index 000000000..1942c1a3d --- /dev/null +++ b/examples/pcie/qemu.dts @@ -0,0 +1,208 @@ +/dts-v1/; + +/ { + #address-cells = <0x02>; + #size-cells = <0x02>; + compatible = "riscv-virtio"; + model = "riscv-virtio,qemu"; + + poweroff { + value = <0x5555>; + offset = <0x00>; + regmap = <0x04>; + compatible = "syscon-poweroff"; + }; + + reboot { + value = <0x7777>; + offset = <0x00>; + regmap = <0x04>; + compatible = "syscon-reboot"; + }; + + platform-bus@4000000 { + interrupt-parent = <0x03>; + ranges = <0x00 0x00 0x4000000 0x2000000>; + #address-cells = <0x01>; + #size-cells = <0x01>; + compatible = "qemu,platform\0simple-bus"; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00 0x80000000 0x00 0x80000000>; + }; + + cpus { + #address-cells = <0x01>; + #size-cells = <0x00>; + timebase-frequency = <0x989680>; + + cpu@0 { + phandle = <0x01>; + device_type = "cpu"; + reg = <0x00>; + status = "okay"; + compatible = "riscv"; + riscv,cboz-block-size = <0x40>; + riscv,cbom-block-size = <0x40>; + riscv,isa = "rv64imafdch_zicbom_zicboz_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_sstc_svadu"; + mmu-type = "riscv,sv57"; + + interrupt-controller { + #interrupt-cells = <0x01>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + phandle = <0x02>; + }; + }; + + cpu-map { + + cluster0 { + + core0 { + cpu = <0x01>; + }; + }; + }; + }; + + pmu { + riscv,event-to-mhpmcounters = <0x01 0x01 0x7fff9 0x02 0x02 0x7fffc 0x10019 0x10019 0x7fff8 0x1001b 0x1001b 0x7fff8 0x10021 0x10021 0x7fff8>; + compatible = "riscv,pmu"; + }; + + fw-cfg@10100000 { + dma-coherent; + reg = <0x00 0x10100000 0x00 0x18>; + compatible = "qemu,fw-cfg-mmio"; + }; + + flash@20000000 { + bank-width = <0x04>; + reg = <0x00 0x20000000 0x00 0x2000000 0x00 0x22000000 0x00 0x2000000>; + compatible = "cfi-flash"; + }; + + chosen { + stdout-path = "/soc/serial@10000000"; + rng-seed = <0xeacbf6e6 0x149b4d83 0xa4525e73 0x367c75e 0xccb35a12 0x4172ecef 0x1b52148f 0x887a0864>; + }; + + soc { + #address-cells = <0x02>; + #size-cells = <0x02>; + compatible = "simple-bus"; + ranges; + + rtc@101000 { + interrupts = <0x0b>; + interrupt-parent = <0x03>; + reg = <0x00 0x101000 0x00 0x1000>; + compatible = "google,goldfish-rtc"; + }; + + serial@10000000 { + interrupts = <0x0a>; + interrupt-parent = <0x03>; + clock-frequency = "\08@"; + reg = <0x00 0x10000000 0x00 0x100>; + compatible = "ns16550a"; + }; + + test@100000 { + phandle = <0x04>; + reg = <0x00 0x100000 0x00 0x1000>; + compatible = "sifive,test1\0sifive,test0\0syscon"; + }; + + pci@30000000 { + interrupt-map-mask = <0x1800 0x00 0x00 0x07>; + interrupt-map = <0x00 0x00 0x00 0x01 0x03 0x20 0x00 0x00 0x00 0x02 0x03 0x21 0x00 0x00 0x00 0x03 0x03 0x22 0x00 0x00 0x00 0x04 0x03 0x23 0x800 0x00 0x00 0x01 0x03 0x21 0x800 0x00 0x00 0x02 0x03 0x22 0x800 0x00 0x00 0x03 0x03 0x23 0x800 0x00 0x00 0x04 0x03 0x20 0x1000 0x00 0x00 0x01 0x03 0x22 0x1000 0x00 0x00 0x02 0x03 0x23 0x1000 0x00 0x00 0x03 0x03 0x20 0x1000 0x00 0x00 0x04 0x03 0x21 0x1800 0x00 0x00 0x01 0x03 0x23 0x1800 0x00 0x00 0x02 0x03 0x20 0x1800 0x00 0x00 0x03 0x03 0x21 0x1800 0x00 0x00 0x04 0x03 0x22>; + ranges = <0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000 0x3000000 0x04 0x00 0x04 0x00 0x04 0x00>; + reg = <0x00 0x30000000 0x00 0x10000000>; + dma-coherent; + bus-range = <0x00 0xff>; + linux,pci-domain = <0x00>; + device_type = "pci"; + compatible = "pci-host-ecam-generic"; + #size-cells = <0x02>; + #interrupt-cells = <0x01>; + #address-cells = <0x03>; + }; + + virtio_mmio@10008000 { + interrupts = <0x08>; + interrupt-parent = <0x03>; + reg = <0x00 0x10008000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10007000 { + interrupts = <0x07>; + interrupt-parent = <0x03>; + reg = <0x00 0x10007000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10006000 { + interrupts = <0x06>; + interrupt-parent = <0x03>; + reg = <0x00 0x10006000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10005000 { + interrupts = <0x05>; + interrupt-parent = <0x03>; + reg = <0x00 0x10005000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10004000 { + interrupts = <0x04>; + interrupt-parent = <0x03>; + reg = <0x00 0x10004000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10003000 { + interrupts = <0x03>; + interrupt-parent = <0x03>; + reg = <0x00 0x10003000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10002000 { + interrupts = <0x02>; + interrupt-parent = <0x03>; + reg = <0x00 0x10002000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10001000 { + interrupts = <0x01>; + interrupt-parent = <0x03>; + reg = <0x00 0x10001000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + plic@c000000 { + phandle = <0x03>; + riscv,ndev = <0x5f>; + reg = <0x00 0xc000000 0x00 0x600000>; + interrupts-extended = <0x02 0x0b 0x02 0x09>; + interrupt-controller; + compatible = "sifive,plic-1.0.0\0riscv,plic0"; + #address-cells = <0x00>; + #interrupt-cells = <0x01>; + }; + + clint@2000000 { + interrupts-extended = <0x02 0x03 0x02 0x07>; + reg = <0x00 0x2000000 0x00 0x10000>; + compatible = "sifive,clint0\0riscv,clint0"; + }; + }; +}; diff --git a/examples/pcie/qemu_arm.dts b/examples/pcie/qemu_arm.dts new file mode 100644 index 000000000..57e20a4b1 --- /dev/null +++ b/examples/pcie/qemu_arm.dts @@ -0,0 +1,389 @@ +/dts-v1/; + +/ { + interrupt-parent = <0x8002>; + model = "linux,dummy-virt"; + #size-cells = <0x02>; + #address-cells = <0x02>; + compatible = "linux,dummy-virt"; + + psci { + migrate = <0xc4000005>; + cpu_on = <0xc4000003>; + cpu_off = <0x84000002>; + cpu_suspend = <0xc4000001>; + method = "smc"; + compatible = "arm,psci-1.0\0arm,psci-0.2\0arm,psci"; + }; + + memory@40000000 { + reg = <0x00 0x40000000 0x00 0x80000000>; + device_type = "memory"; + }; + + platform-bus@c000000 { + interrupt-parent = <0x8002>; + ranges = <0x00 0x00 0xc000000 0x2000000>; + #address-cells = <0x01>; + #size-cells = <0x01>; + compatible = "qemu,platform\0simple-bus"; + }; + + fw-cfg@9020000 { + dma-coherent; + reg = <0x00 0x9020000 0x00 0x18>; + compatible = "qemu,fw-cfg-mmio"; + }; + + virtio_mmio@a000000 { + dma-coherent; + interrupts = <0x00 0x10 0x01>; + reg = <0x00 0xa000000 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000200 { + dma-coherent; + interrupts = <0x00 0x11 0x01>; + reg = <0x00 0xa000200 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000400 { + dma-coherent; + interrupts = <0x00 0x12 0x01>; + reg = <0x00 0xa000400 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000600 { + dma-coherent; + interrupts = <0x00 0x13 0x01>; + reg = <0x00 0xa000600 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000800 { + dma-coherent; + interrupts = <0x00 0x14 0x01>; + reg = <0x00 0xa000800 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000a00 { + dma-coherent; + interrupts = <0x00 0x15 0x01>; + reg = <0x00 0xa000a00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000c00 { + dma-coherent; + interrupts = <0x00 0x16 0x01>; + reg = <0x00 0xa000c00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a000e00 { + dma-coherent; + interrupts = <0x00 0x17 0x01>; + reg = <0x00 0xa000e00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001000 { + dma-coherent; + interrupts = <0x00 0x18 0x01>; + reg = <0x00 0xa001000 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001200 { + dma-coherent; + interrupts = <0x00 0x19 0x01>; + reg = <0x00 0xa001200 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001400 { + dma-coherent; + interrupts = <0x00 0x1a 0x01>; + reg = <0x00 0xa001400 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001600 { + dma-coherent; + interrupts = <0x00 0x1b 0x01>; + reg = <0x00 0xa001600 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001800 { + dma-coherent; + interrupts = <0x00 0x1c 0x01>; + reg = <0x00 0xa001800 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001a00 { + dma-coherent; + interrupts = <0x00 0x1d 0x01>; + reg = <0x00 0xa001a00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001c00 { + dma-coherent; + interrupts = <0x00 0x1e 0x01>; + reg = <0x00 0xa001c00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a001e00 { + dma-coherent; + interrupts = <0x00 0x1f 0x01>; + reg = <0x00 0xa001e00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002000 { + dma-coherent; + interrupts = <0x00 0x20 0x01>; + reg = <0x00 0xa002000 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002200 { + dma-coherent; + interrupts = <0x00 0x21 0x01>; + reg = <0x00 0xa002200 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002400 { + dma-coherent; + interrupts = <0x00 0x22 0x01>; + reg = <0x00 0xa002400 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002600 { + dma-coherent; + interrupts = <0x00 0x23 0x01>; + reg = <0x00 0xa002600 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002800 { + dma-coherent; + interrupts = <0x00 0x24 0x01>; + reg = <0x00 0xa002800 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002a00 { + dma-coherent; + interrupts = <0x00 0x25 0x01>; + reg = <0x00 0xa002a00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002c00 { + dma-coherent; + interrupts = <0x00 0x26 0x01>; + reg = <0x00 0xa002c00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a002e00 { + dma-coherent; + interrupts = <0x00 0x27 0x01>; + reg = <0x00 0xa002e00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003000 { + dma-coherent; + interrupts = <0x00 0x28 0x01>; + reg = <0x00 0xa003000 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003200 { + dma-coherent; + interrupts = <0x00 0x29 0x01>; + reg = <0x00 0xa003200 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003400 { + dma-coherent; + interrupts = <0x00 0x2a 0x01>; + reg = <0x00 0xa003400 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003600 { + dma-coherent; + interrupts = <0x00 0x2b 0x01>; + reg = <0x00 0xa003600 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003800 { + dma-coherent; + interrupts = <0x00 0x2c 0x01>; + reg = <0x00 0xa003800 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003a00 { + dma-coherent; + interrupts = <0x00 0x2d 0x01>; + reg = <0x00 0xa003a00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003c00 { + dma-coherent; + interrupts = <0x00 0x2e 0x01>; + reg = <0x00 0xa003c00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@a003e00 { + dma-coherent; + interrupts = <0x00 0x2f 0x01>; + reg = <0x00 0xa003e00 0x00 0x200>; + compatible = "virtio,mmio"; + }; + + gpio-keys { + compatible = "gpio-keys"; + + poweroff { + gpios = <0x8004 0x03 0x00>; + linux,code = <0x74>; + label = "GPIO Key Poweroff"; + }; + }; + + pl061@9030000 { + phandle = <0x8004>; + clock-names = "apb_pclk"; + clocks = <0x8000>; + interrupts = <0x00 0x07 0x04>; + gpio-controller; + #gpio-cells = <0x02>; + compatible = "arm,pl061\0arm,primecell"; + reg = <0x00 0x9030000 0x00 0x1000>; + }; + + pcie@10000000 { + interrupt-map-mask = <0x1800 0x00 0x00 0x07>; + interrupt-map = <0x00 0x00 0x00 0x01 0x8002 0x00 0x00 0x00 0x03 0x04 0x00 0x00 0x00 0x02 0x8002 0x00 0x00 0x00 0x04 0x04 0x00 0x00 0x00 0x03 0x8002 0x00 0x00 0x00 0x05 0x04 0x00 0x00 0x00 0x04 0x8002 0x00 0x00 0x00 0x06 0x04 0x800 0x00 0x00 0x01 0x8002 0x00 0x00 0x00 0x04 0x04 0x800 0x00 0x00 0x02 0x8002 0x00 0x00 0x00 0x05 0x04 0x800 0x00 0x00 0x03 0x8002 0x00 0x00 0x00 0x06 0x04 0x800 0x00 0x00 0x04 0x8002 0x00 0x00 0x00 0x03 0x04 0x1000 0x00 0x00 0x01 0x8002 0x00 0x00 0x00 0x05 0x04 0x1000 0x00 0x00 0x02 0x8002 0x00 0x00 0x00 0x06 0x04 0x1000 0x00 0x00 0x03 0x8002 0x00 0x00 0x00 0x03 0x04 0x1000 0x00 0x00 0x04 0x8002 0x00 0x00 0x00 0x04 0x04 0x1800 0x00 0x00 0x01 0x8002 0x00 0x00 0x00 0x06 0x04 0x1800 0x00 0x00 0x02 0x8002 0x00 0x00 0x00 0x03 0x04 0x1800 0x00 0x00 0x03 0x8002 0x00 0x00 0x00 0x04 0x04 0x1800 0x00 0x00 0x04 0x8002 0x00 0x00 0x00 0x05 0x04>; + #interrupt-cells = <0x01>; + ranges = <0x1000000 0x00 0x00 0x00 0x3eff0000 0x00 0x10000 0x2000000 0x00 0x10000000 0x00 0x10000000 0x00 0x2eff0000>; + reg = <0x00 0x3f000000 0x00 0x1000000>; + msi-map = <0x00 0x8003 0x00 0x10000>; + dma-coherent; + bus-range = <0x00 0x0f>; + linux,pci-domain = <0x00>; + #size-cells = <0x02>; + #address-cells = <0x03>; + device_type = "pci"; + compatible = "pci-host-ecam-generic"; + }; + + pl031@9010000 { + clock-names = "apb_pclk"; + clocks = <0x8000>; + interrupts = <0x00 0x02 0x04>; + reg = <0x00 0x9010000 0x00 0x1000>; + compatible = "arm,pl031\0arm,primecell"; + }; + + pl011@9000000 { + clock-names = "uartclk\0apb_pclk"; + clocks = <0x8000 0x8000>; + interrupts = <0x00 0x01 0x04>; + reg = <0x00 0x9000000 0x00 0x1000>; + compatible = "arm,pl011\0arm,primecell"; + }; + + pmu { + interrupts = <0x01 0x07 0x104>; + compatible = "arm,armv8-pmuv3"; + }; + + intc@8000000 { + phandle = <0x8002>; + interrupts = <0x01 0x09 0x04>; + reg = <0x00 0x8000000 0x00 0x10000 0x00 0x8010000 0x00 0x10000 0x00 0x8030000 0x00 0x10000 0x00 0x8040000 0x00 0x10000>; + compatible = "arm,cortex-a15-gic"; + ranges; + #size-cells = <0x02>; + #address-cells = <0x02>; + interrupt-controller; + #interrupt-cells = <0x03>; + + v2m@8020000 { + phandle = <0x8003>; + reg = <0x00 0x8020000 0x00 0x1000>; + msi-controller; + compatible = "arm,gic-v2m-frame"; + }; + }; + + flash@0 { + bank-width = <0x04>; + reg = <0x00 0x00 0x00 0x4000000 0x00 0x4000000 0x00 0x4000000>; + compatible = "cfi-flash"; + }; + + cpus { + #size-cells = <0x00>; + #address-cells = <0x01>; + + cpu-map { + + socket0 { + + cluster0 { + + core0 { + cpu = <0x8001>; + }; + }; + }; + }; + + cpu@0 { + phandle = <0x8001>; + reg = <0x00>; + compatible = "arm,cortex-a53"; + device_type = "cpu"; + }; + }; + + timer { + interrupts = <0x01 0x0d 0x104 0x01 0x0e 0x104 0x01 0x0b 0x104 0x01 0x0a 0x104>; + always-on; + compatible = "arm,armv8-timer\0arm,armv7-timer"; + }; + + apb-pclk { + phandle = <0x8000>; + clock-output-names = "clk24mhz"; + clock-frequency = <0x16e3600>; + #clock-cells = <0x00>; + compatible = "fixed-clock"; + }; + + chosen { + stdout-path = "/pl011@9000000"; + rng-seed = <0x39809366 0xe7fc0a9d 0x6c78703e 0xe7eba96c 0x94a9b477 0xfec74fb6 0xe647b61c 0xd80d1762>; + kaslr-seed = <0x2044fc98 0x9c6ba958>; + }; +};