13 Commits

Author SHA1 Message Date
Travis Geiselbrecht
4839bb9689 [ahci][disk] start to parse the identify block to find disk size 2022-02-28 23:08:46 -08:00
Travis Geiselbrecht
1341158c5c [dev][block][ahci] only build on platforms with the VM enabled 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
e9079cb7ee [dev][block][ahci] continue if PxSIG is 0xfffffffff
This seems to happen if the device had not been frobbed by any firmware
prior to handing off to the system. Seems that a proper reset is in
order, but for now simply ignore it, assume it's a disk, and continue.
2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
f16fd2e9c6 WIP ahci get building on riscv which seems to not have a ffs() implementation in libgcc 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
28e8bf546e WIP [ahci] get building on 32bit arches 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
e8f0413e39 WIP ahci 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
2b83e01ad4 WIP ahci make sure mmio aperture is page aligned 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
000b94f1ac WIP ahci start to move logic into new disk class 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
aeed44352c WIP ahci
queing commands and using interrupts to handle them now
2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
269bb48e2c WIP ahci move some headers 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
d1ec4b9861 WIP ahci
read the first identify command from disk
2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
01f5e8dc67 WIP ahci 2022-02-27 19:42:40 -08:00
Travis Geiselbrecht
24af6fe93e WIP ahci 2022-02-27 19:42:40 -08:00
12 changed files with 1106 additions and 0 deletions

209
dev/block/ahci/ahci.cpp Normal file
View File

@@ -0,0 +1,209 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "ahci.h"
#include <arch/atomic.h>
#include <dev/bus/pci.h>
#include <kernel/event.h>
#include <kernel/thread.h>
#include <kernel/vm.h>
#include <lk/bits.h>
#include <lk/cpp.h>
#include <lk/err.h>
#include <lk/init.h>
#include <lk/list.h>
#include <lk/trace.h>
#include <platform/interrupts.h>
#include <string.h>
#include <type_traits>
#include "ahci_hw.h"
#include "disk.h"
#include "port.h"
#define LOCAL_TRACE 1
volatile int ahci::global_count_= 0;
ahci::ahci() = default;
ahci::~ahci() = default;
status_t ahci::init_device(pci_location_t loc) {
char str[32];
loc_ = loc;
LTRACEF("pci location %s\n", pci_loc_string(loc_, str));
pci_bar_t bars[6];
status_t err = pci_bus_mgr_read_bars(loc_, bars);
if (err != NO_ERROR) return err;
LTRACEF("ahci BARS:\n");
if (LOCAL_TRACE) pci_dump_bars(bars, 6);
if (!bars[5].valid || !bars[5].addr) {
return ERR_NOT_FOUND;
}
// allocate a unit number
unit_ = atomic_add(&global_count_, 1);
// map bar 5, main memory mapped register interface, 4K
snprintf(str, sizeof(str), "ahci%d abar", unit_);
err = vmm_alloc_physical(vmm_get_kernel_aspace(), str, PAGE_ALIGN(bars[5].size), &abar_regs_, 0,
bars[5].addr, /* vmm_flags */ 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
LTRACEF("ABAR mapped to %p\n", abar_regs_);
pci_bus_mgr_enable_device(loc_);
LTRACEF("CAP %#x\n", read_reg(ahci_reg::CAP));
LTRACEF("PI %#x\n", read_reg(ahci_reg::PI));
// mask all irqs
write_reg(ahci_reg::GHC, read_reg(ahci_reg::GHC) & ~(1U << 1)); // clear GHC.IE
static auto irq_handler_wrapper = [](void *arg) -> handler_return {
ahci *a = (ahci *)arg;
return a->irq_handler();
};
// allocate an MSI interrupt
uint irq_base;
err = pci_bus_mgr_allocate_msi(loc_, 1, &irq_base);
if (err != NO_ERROR) {
// fall back to regular IRQs
err = pci_bus_mgr_allocate_irq(loc_, &irq_base);
if (err != NO_ERROR) {
printf("ahci: unable to allocate IRQ\n");
return err;
}
register_int_handler(irq_base, irq_handler_wrapper, this);
} else {
register_int_handler_msi(irq_base, irq_handler_wrapper, this, true);
}
LTRACEF("IRQ number %#x\n", irq_base);
unmask_interrupt(irq_base);
// enable interrupts
write_reg(ahci_reg::GHC, read_reg(ahci_reg::GHC) | (1U << 1)); // set GHC.IE
// probe every port marked implemented
uint32_t port_bitmap = read_reg(ahci_reg::PI);
size_t port_count = 0;
for (size_t port = 0; port < 32; port++) {
if ((port_bitmap & (1U << port)) == 0) {
// skip port not implemented
break;
}
port_count++;
ports_[port] = new ahci_port(*this, port);
auto *p = ports_[port];
ahci_disk *disk = nullptr;
err = p->probe(&disk);
if (err != NO_ERROR) {
continue;
}
DEBUG_ASSERT(disk);
// add the disk to a list for further processing
list_add_tail(&disk_list_, &disk->node_);
}
return NO_ERROR;
}
void ahci::disk_probe_worker() {
LTRACE_ENTRY;
ahci_disk *disk;
list_for_every_entry(&disk_list_, disk, ahci_disk, node_) {
disk->identify();
}
}
status_t ahci::start_disk_probe() {
auto probe_worker = [](void *arg) -> int {
ahci *a = (ahci *)arg;
a->disk_probe_worker();
return 0;
};
disk_probe_thread_ = thread_create("ahci disk probe", probe_worker, this, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
thread_resume(disk_probe_thread_);
return NO_ERROR;
}
handler_return ahci::irq_handler() {
LTRACE_ENTRY;
const auto orig_is = read_reg(ahci_reg::IS);
auto is = orig_is;
LTRACEF("is %#x\n", is);
// cycle through the ports that have interrupts queued
handler_return ret = INT_NO_RESCHEDULE;
while (is != 0) {
int port = __builtin_ctz(is);
LTRACEF("interrupt on port %d\n", port);
DEBUG_ASSERT(ports_[port] != nullptr);
if (ports_[port]->irq_handler() == INT_RESCHEDULE) {
ret = INT_RESCHEDULE;
}
is &= ~(1U<<port);
}
// clear the interrupt status
write_reg(ahci_reg::IS, orig_is);
LTRACE_EXIT;
return ret;
}
// hook called at init time to iterate through pci bus and find all of the ahci devices
static void ahci_init(uint level) {
LTRACE_ENTRY;
// probe pci to find a device
for (size_t i = 0; ; i++) {
pci_location_t loc;
status_t err = pci_bus_mgr_find_device_by_class(&loc, 0x1, 0x6, 0x1, i);
if (err != NO_ERROR) {
break;
}
// we maybe found one, create a new device and initialize it
auto a = new ahci;
err = a->init_device(loc);
if (err != NO_ERROR) {
char str[14];
printf("ahci: device at %s failed to initialize\n", pci_loc_string(loc, str));
delete a;
continue;
}
// set up any disks we've found
a->start_disk_probe();
}
}
LK_INIT_HOOK(ahci, &ahci_init, LK_INIT_LEVEL_PLATFORM + 1);

92
dev/block/ahci/ahci.h Normal file
View File

@@ -0,0 +1,92 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#pragma once
#include <dev/bus/pci.h>
#include <lk/cpp.h>
#include <lk/list.h>
#include <kernel/spinlock.h>
#include <kernel/thread.h>
#include "ahci_hw.h"
class ahci_port;
class ahci {
public:
ahci();
~ahci();
DISALLOW_COPY_ASSIGN_AND_MOVE(ahci);
int get_unit_num() const { return unit_; }
// initialize the device at passed in pci location.
// probe each of the active ports for disks and save
// a list of them for future probing.
status_t init_device(pci_location_t loc);
// start a thread and probe all of the disks found
status_t start_disk_probe();
private:
friend class ahci_port;
uint32_t read_reg(ahci_reg reg);
void write_reg(ahci_reg reg, uint32_t val);
uint32_t read_port_reg(uint port, ahci_port_reg reg);
void write_port_reg(uint port, ahci_port_reg reg, uint32_t val);
handler_return irq_handler();
void disk_probe_worker();
// counter of configured deices
static volatile int global_count_;
int unit_ = 0;
// main spinlock
spin_lock_t lock_ = SPIN_LOCK_INITIAL_VALUE;
// configuration
pci_location_t loc_ = {};
void *abar_regs_ = nullptr;
// array of ports
ahci_port *ports_[32] = {};
// list of disks we've found
thread_t *disk_probe_thread_ = nullptr;
list_node disk_list_ = LIST_INITIAL_VALUE(disk_list_);
};
inline uint32_t ahci::read_reg(ahci_reg reg) {
volatile uint32_t *r = (volatile uint32_t *)((uintptr_t)abar_regs_ + (size_t)reg);
return *r;
}
inline void ahci::write_reg(ahci_reg reg, uint32_t val) {
volatile uint32_t *r = (volatile uint32_t *)((uintptr_t)abar_regs_ + (size_t)reg);
*r = val;
}
inline uint32_t ahci::read_port_reg(uint port, ahci_port_reg reg) {
volatile uint32_t *r = (volatile uint32_t *)((uintptr_t)abar_regs_ + (size_t)reg + 0x100 + 0x80 * port);
return *r;
}
inline void ahci::write_port_reg(uint port, ahci_port_reg reg, uint32_t val) {
volatile uint32_t *r = (volatile uint32_t *)((uintptr_t)abar_regs_ + (size_t)reg + 0x100 + 0x80 * port);
*r = val;
}

99
dev/block/ahci/ahci_hw.h Normal file
View File

@@ -0,0 +1,99 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
// From serial-ata-ahci-spec-rev-1-3-1.pdf
#pragma once
#include <stdint.h>
// registers relative to the base of the ABAR
enum class ahci_reg {
CAP = 0x0, // capability
GHC = 0x4, // global HBA control
IS = 0x8, // interrupt status
PI = 0xc, // ports implemented
VS = 0x10, // version
CCC_CTL = 0x14, // command completion coalescing control
CCC_PORTS = 0x18, // command completion coalescing ports
EM_LOC = 0x1c, // enclosure management location
EM_CTL = 0x20, // enclosure management control
CAP2 = 0x24, // HBA capabilities extended
BOHC = 0x28, // BIOS/OS handoff control and status
// registers 0xa0 to 0xff are vendor specific
// port specific registers are enumerated below,
// repeated every 0x80 starting at 0x100
};
enum class ahci_port_reg {
PxCLB = 0x0,
PxCLBU = 0x4,
PxFB = 0x8,
PxFBU = 0xc,
PxIS = 0x10,
PxIE = 0x14,
PxCMD = 0x18,
PxTFD = 0x20,
PxSIG = 0x24,
PxSSTS = 0x28,
PxSCTL = 0x2c,
PxSERR = 0x30,
PxSACT = 0x34,
PxCI = 0x38,
PxSNTF = 0x3c,
PxFBS = 0x40,
PxDEVSLP = 0x44,
PxVS = 0x70,
};
// command header
struct ahci_cmd_header {
union {
uint32_t dw[8]; // raw 8 byte words
struct {
uint16_t cmd; // raw command bits
uint16_t prdtl; // physical region descriptor entry count
uint32_t prdbc; // physical region descriptor byte count
uint32_t ctba; // command table base address
uint32_t ctbau; // command table base address upper
};
};
};
static_assert(sizeof(ahci_cmd_header) == 0x20, "");
// physical region descriptor (PRDT entry)
struct ahci_prd {
union {
uint32_t dw[4]; // raw 4 byte words
struct {
uint32_t dba; // data base address
uint32_t dbau; // data base address upper
uint32_t _reserved;
uint32_t byte_count_ioc; // byte count [0:21], interrupt on completion [31]
};
};
};
static_assert(sizeof(ahci_prd) == 0x10, "");
struct ahci_cmd_table {
uint8_t cfis[64];
// offset 0x40
uint8_t acmd[16];
// offset 0x80
uint8_t _reserved[0x80 - 0x50];
ahci_prd pdrt[0];
};
static_assert(sizeof(ahci_cmd_table) == 0x80, "");

11
dev/block/ahci/ata.cpp Normal file
View File

@@ -0,0 +1,11 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "ata.h"
#include <lk/cpp.h>

22
dev/block/ahci/ata.h Normal file
View File

@@ -0,0 +1,22 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#pragma once
#include <stdint.h>
#include <hw/ata.h>
// ata helper routines
inline FIS_REG_H2D ata_cmd_identify() {
FIS_REG_H2D fis = {};
fis.fis_type = FIS_TYPE_REG_H2D;
fis.command = ATA_CMD_IDENTIFY;
fis.device = 0;
fis.c = 1;
return fis;
}

99
dev/block/ahci/disk.cpp Normal file
View File

@@ -0,0 +1,99 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "disk.h"
#include <lk/bits.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <stdint.h>
#include <string.h>
#include "ata.h"
#define LOCAL_TRACE 1
// offsets in the 256 word (2 byte word) IDENTIFY structure
enum ata_identify_words {
ATA_IDENTIFY_MODEL_NUMBER = 27, // 40 bytes
ATA_IDENTIFY_LOGICAL_SECTOR_COUNT_QWORD = 100, // 4 words of logical sector count
ATA_IDENTIFY_PHYS_TO_LOGICAL_SECTOR = 106, // phys size / logical size
ATA_IDENTIFY_LOGICAL_SECTOR_SIZE_DWORD = 117, // dword of logical sector size
};
ahci_disk::ahci_disk(ahci_port &p) : port_(p) {
}
ahci_disk::~ahci_disk() = default;
status_t ahci_disk::identify() {
LTRACE_ENTRY;
__ALIGNED(512) static uint16_t identify_data[256];
FIS_REG_H2D fis = ata_cmd_identify();
int slot;
auto err = port_.queue_command(&fis, sizeof(fis), identify_data, sizeof(identify_data), false, &slot);
if (err != NO_ERROR) {
return err;
}
// wait for it to complete
err = port_.wait_for_completion(slot);
if (err != NO_ERROR) {
return err;
}
LTRACEF("identify data:\n");
hexdump8(identify_data, sizeof(identify_data));
char model[20*2 + 1] = {};
for (auto i = 0; i < 20; i++) {
model[i * 2] = identify_data[ATA_IDENTIFY_MODEL_NUMBER + i] >> 8;
model[i * 2 + 1] = identify_data[ATA_IDENTIFY_MODEL_NUMBER + i] & 0xff;
}
LTRACEF("model '%s'\n", model);
// assumes LBA48
bool lba48 = identify_data[83] & (1 << 10);
if (!lba48) {
printf("AHCI: LBA48 required, aborting\n");
return ERR_NOT_SUPPORTED;
}
// sector count is 4 words at offset 100
uint64_t sector_count = identify_data[ATA_IDENTIFY_LOGICAL_SECTOR_COUNT_QWORD] |
((uint64_t)identify_data[ATA_IDENTIFY_LOGICAL_SECTOR_COUNT_QWORD + 1] << 16) |
((uint64_t)identify_data[ATA_IDENTIFY_LOGICAL_SECTOR_COUNT_QWORD + 2] << 32) |
((uint64_t)identify_data[ATA_IDENTIFY_LOGICAL_SECTOR_COUNT_QWORD + 3] << 48);
LTRACEF("logical sector count %#llx\n", sector_count);
// defaults to 512 bytes
uint32_t logical_sector_size = 512;
uint32_t physical_sector_size = 512;
auto phys_to_logical_sector = identify_data[ATA_IDENTIFY_PHYS_TO_LOGICAL_SECTOR];
//LTRACEF("phys size / logical size %#hx\n", identify_data[ATA_IDENTIFY_PHYS_TO_LOGICAL_SECTOR]);
if (BITS(phys_to_logical_sector, 15, 14) == (1 << 14)) { // word 106 has valid info
if (BIT(phys_to_logical_sector, 12)) {
// logical sector size is specified in word 117..118
logical_sector_size = identify_data[ATA_IDENTIFY_LOGICAL_SECTOR_SIZE_DWORD] |
((uint32_t)identify_data[ATA_IDENTIFY_LOGICAL_SECTOR_SIZE_DWORD + 1] << 16);
}
// bits 3:0 have physical sector size in power of 2 times logical size
physical_sector_size = (1U << BITS(phys_to_logical_sector, 3, 0)) * logical_sector_size;
}
LTRACEF("logical sector size %#x\n", logical_sector_size);
LTRACEF("physical sector size %#x\n", physical_sector_size);
LTRACEF("total size %#llx\n", sector_count * logical_sector_size);
return NO_ERROR;
}

27
dev/block/ahci/disk.h Normal file
View File

@@ -0,0 +1,27 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#pragma once
#include <lk/cpp.h>
#include <lk/list.h>
#include <sys/types.h>
#include "port.h"
class ahci_disk {
public:
ahci_disk(ahci_port &p);
~ahci_disk();
DISALLOW_COPY_ASSIGN_AND_MOVE(ahci_disk);
status_t identify();
list_node node_ = LIST_INITIAL_CLEARED_VALUE;
private:
ahci_port &port_;
};

271
dev/block/ahci/port.cpp Normal file
View File

@@ -0,0 +1,271 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "port.h"
#include <lk/bits.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <kernel/vm.h>
#include <kernel/thread.h>
#include <string.h>
#include "ata.h"
#include "disk.h"
#define LOCAL_TRACE 1
ahci_port::ahci_port(ahci &a, uint num) : ahci_(a), num_(num) {
for (auto &e : cmd_complete_event_) {
event_init(&e, false, EVENT_FLAG_AUTOUNSIGNAL);
}
}
ahci_port::~ahci_port() {
if (mem_region_) {
vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)mem_region_);
}
for (auto &e : cmd_complete_event_) {
event_destroy(&e);
}
}
status_t ahci_port::probe(ahci_disk **found_disk) {
// mask all IRQS on this port regardless if we want to use it
write_port_reg(ahci_port_reg::PxIE, 0);
// clear any pending bits
write_port_reg(ahci_port_reg::PxIS, 0xffffffff);
// check if drive is present
auto ssts = read_port_reg(ahci_port_reg::PxSSTS);
if (BITS(ssts, 3, 0) != 3) { // check SSTS.DET == 3 (device present and phy comm established)
return ERR_NOT_FOUND;
}
if (BITS_SHIFT(ssts, 11, 8) != 1) { // check SSTS.IPM == 1 (interface in active state)
return ERR_NOT_FOUND;
}
dprintf(INFO, "ahci%d port %u: ssts %#x device present and interface in active state\n",
ahci_.get_unit_num(), num_, ssts);
auto sig = read_port_reg(ahci_port_reg::PxSIG);
LTRACEF("port %u: sig %#x\n", num_, sig);
// if sig is all 1s then it hasn't been scanned yet, so assume it's a disk.
// otherwise if its 0x101 it's a disk
if (sig != 0xffffffff && sig != 0x101) {
TRACEF("skipping unhandled signature %#x\n", sig);
return ERR_NOT_FOUND;
}
LTRACEF("port %u: PxCLB %#x\n", num_, read_port_reg(ahci_port_reg::PxCLB));
LTRACEF("port %u: PxCMD %#x\n", num_, read_port_reg(ahci_port_reg::PxCMD));
// stop the port so we can reset addresses
auto cmd_reg = read_port_reg(ahci_port_reg::PxCMD);
cmd_reg &= ~((1<<4) | // clear CMD.FRE (fis receive enable)
(1<<0)); // clear CMD.ST (start)
write_port_reg(ahci_port_reg::PxCMD, cmd_reg);
// TODO: wait for CMD.FR to stop
// allocate a block of contiguous memory for
// 32 command list heads (32 * 0x20)
// a FIS struct (256 bytes)
// 32 command tables with 16 PRDTs per
const size_t size = (CMD_COUNT * sizeof(ahci_cmd_header)) + 256 +
(CMD_COUNT * CMD_TABLE_ENTRY_SIZE);
// allocate a contiguous block of ram
char str[32];
snprintf(str, sizeof(str), "ahci%d.%u cmd/fis", ahci_.get_unit_num(), num_);
status_t err = vmm_alloc_contiguous(vmm_get_kernel_aspace(), str, size,
(void **)&mem_region_, 0, /* vmm_flags */ 0,
ARCH_MMU_FLAG_UNCACHED_DEVICE);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
memset(mem_region_, 0, size);
mem_region_paddr_ = vaddr_to_paddr(mem_region_);
LTRACEF("cmd_list/fis mapped to %p, pa %#lx\n", mem_region_, mem_region_paddr_);
// carve up the pointers into this space
cmd_list_ = (volatile ahci_cmd_header *)mem_region_;
fis_ = (volatile uint8_t *)((uintptr_t)mem_region_ + 32 * sizeof(ahci_cmd_header));
cmd_table_ = (volatile ahci_cmd_table *)(fis_ + 256);
LTRACEF("command list at %p, FIS at %p, per command table at %p\n",
cmd_list_, fis_, cmd_table_);
// set the AHCI port to point to the command header and global fis
write_port_reg(ahci_port_reg::PxCLB, vaddr_to_paddr((void *)cmd_list_));
write_port_reg(ahci_port_reg::PxFB, vaddr_to_paddr((void *)fis_));
#if __INTPTR_WIDTH__ == 64
write_port_reg(ahci_port_reg::PxCLBU, vaddr_to_paddr((void *)cmd_list_) >> 32);
write_port_reg(ahci_port_reg::PxFBU, vaddr_to_paddr((void *)fis_) >> 32);
#else
write_port_reg(ahci_port_reg::PxCLBU, 0);
write_port_reg(ahci_port_reg::PxFBU, 0);
#endif
// set up the command headers
auto cmd_table_pa = vaddr_to_paddr((void *)cmd_table_);
for (auto i = 0; i < 32; i++) {
volatile auto *cmd = &cmd_list_[i];
// point the cmd header at the corresponding cmd table
cmd->ctba = (cmd_table_pa + sizeof(ahci_cmd_table) * i) & 0xffffffff;
#if __INTPTR_WIDTH__ == 64
cmd->ctbau = (cmd_table_pa + sizeof(ahci_cmd_table) * i) >> 32;
#else
cmd->ctbau = 0;
#endif
}
// restart the port
cmd_reg |= (1<<4); // set CMD.FRE (fis receive enable)
write_port_reg(ahci_port_reg::PxCMD, cmd_reg);
cmd_reg |= (1<<0); // set CMD.ST (start)
write_port_reg(ahci_port_reg::PxCMD, cmd_reg);
// unmask some irqs on this port
// TODO: unmask more if needed
uint32_t ie = (1U << 5) | // Descriptor Processed (DPS)
(1U << 3) | // Set device bits interrupt (SDBS)
(1U << 2) | // DMA setup FIS (DSS)
(1U << 1) | // PIO setup FIS (PSS)
(1U << 0); // Device to Host Register FIS (DHRS)
write_port_reg(ahci_port_reg::PxIE, ie);
// we found a disk above, create an object and pass it back
auto *disk = new ahci_disk(*this);
*found_disk = disk;
return NO_ERROR;
}
int ahci_port::find_free_cmdslot() {
uint32_t all_slots = read_port_reg(ahci_port_reg::PxSACT) |
read_port_reg(ahci_port_reg::PxCI);
LTRACEF("all_slots %#x\n", all_slots);
if (unlikely(all_slots == 0xffffffff)) {
// all slots are full
return -1;
}
int avail = __builtin_clz(~all_slots);
LTRACEF("avail %u\n", avail);
return avail;
}
status_t ahci_port::queue_command(const void *fis, size_t fis_len, void *buf, size_t buf_len, bool write, int *slot_out) {
LTRACEF("fis %p len %zu buf %p len %zu write %d\n", fis, fis_len, buf, buf_len, write);
DEBUG_ASSERT(fis);
DEBUG_ASSERT(fis_len > 0 && fis_len <= 64 && IS_ALIGNED(fis_len, 4));
DEBUG_ASSERT(buf || buf_len == 0);
AutoSpinLock guard(&lock_);
auto slot = find_free_cmdslot();
LTRACEF("slot %u\n", slot);
// clear interrupt status for this port
write_port_reg(ahci_port_reg::PxIS, 0xf);
auto *cmd_table = cmd_table_ptr(slot);
// set up physical descriptor of a run of memory
// XXX for now assume single run
auto *prdt = &cmd_table->pdrt[0];
auto buf_pa = vaddr_to_paddr(buf);
prdt->dba = buf_pa;
#if __INTPTR_WIDTH__ == 64
prdt->dbau = buf_pa >> 32;
#else
prdt->dbau = 0;
#endif
prdt->byte_count_ioc = (buf_len - 1) | (1U<<31); // byte count, interrupt on completion
// copy command into the command table
// TODO: replace with wordwise copy
memcpy((void *)cmd_table->cfis, fis, fis_len);
// set up the command header
auto *cmd = &cmd_list_[slot];
cmd->cmd = (fis_len / sizeof(uint32_t)) | (write ? (1<<6) : (0<<6)); // command fis size in words, read/write from device
cmd->prdtl = 1; // 1 prdt
//LTRACEF("cmd_table %p\n", cmd_table);
//hexdump((const void *)cmd_table, sizeof(*cmd_table) + CMD_TABLE_ENTRY_SIZE);
// TODO: barrier here
// rmb();
LTRACEF("IS %#x (before kick)\n", read_port_reg(ahci_port_reg::PxIS));
cmd_pending_ |= (1U << slot);
// kick the command
write_port_reg(ahci_port_reg::PxCI, (1U << slot)); // TODO: RMW?
*slot_out = slot;
return NO_ERROR;
}
status_t ahci_port::wait_for_completion(int slot) {
DEBUG_ASSERT(slot >= 0 && slot < (int)CMD_COUNT);
auto err = event_wait(&cmd_complete_event_[slot]);
return err;
}
handler_return ahci_port::irq_handler() {
LTRACE_ENTRY;
AutoSpinLockNoIrqSave guard(&lock_);
const auto raw_is = read_port_reg(ahci_port_reg::PxIS);
const auto is = raw_is & read_port_reg(ahci_port_reg::PxIE); // filter by things we're masking
LTRACEF("raw is %#x is %#x\n", raw_is, is);
// see if any commands completed
const auto ci = read_port_reg(ahci_port_reg::PxCI);
auto cmd_complete_bitmap = cmd_pending_ & ~ci;
LTRACEF("command complete bitmap %#x\n", cmd_complete_bitmap);
handler_return ret = INT_NO_RESCHEDULE;
while (cmd_complete_bitmap != 0) {
const size_t cmd_slot = __builtin_ctz(cmd_complete_bitmap);
DEBUG_ASSERT(cmd_slot < CMD_COUNT);
LTRACEF("slot %zu completed\n", cmd_slot);
// this slot completed
event_signal(&cmd_complete_event_[cmd_slot], false);
ret = INT_RESCHEDULE;
// mark the command as not pending anymore
cmd_pending_ &= ~(1U << cmd_slot);
// move to the next pending slot (if any)
cmd_complete_bitmap &= ~(1U << cmd_slot);
}
// ack everything for now
write_port_reg(ahci_port_reg::PxIS, is);
return ret;
}

75
dev/block/ahci/port.h Normal file
View File

@@ -0,0 +1,75 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#pragma once
#include <lk/cpp.h>
#include <sys/types.h>
#include <kernel/spinlock.h>
#include <kernel/event.h>
#include "ahci.h"
#include "ahci_hw.h"
class ahci_disk;
// per port AHCI object
class ahci_port {
public:
ahci_port(ahci &a, uint num);
~ahci_port();
DISALLOW_COPY_ASSIGN_AND_MOVE(ahci_port);
handler_return irq_handler();
status_t probe(ahci_disk **found_disk);
status_t queue_command(const void *fis, size_t fis_len, void *buf, size_t buf_len, bool write, int *slot_out);
status_t wait_for_completion(int slot);
private:
uint32_t read_port_reg(ahci_port_reg reg);
void write_port_reg(ahci_port_reg reg, uint32_t val);
int find_free_cmdslot();
volatile ahci_cmd_table *cmd_table_ptr(uint cmd_slot);
// constants
static const size_t CMD_COUNT = 32; // number of active command slots
static const size_t PRD_PER_CMD = 16; // physical descriptors per command slot
static const size_t CMD_TABLE_ENTRY_SIZE = sizeof(ahci_cmd_table) + sizeof(ahci_prd) * PRD_PER_CMD;
// members
ahci &ahci_;
uint num_;
// per port spinlock
spin_lock_t lock_ = SPIN_LOCK_INITIAL_VALUE;
// pending command bitmap
uint32_t cmd_pending_ = 0;
event cmd_complete_event_[CMD_COUNT];
void *mem_region_ = nullptr;
paddr_t mem_region_paddr_ = 0;
volatile ahci_cmd_header *cmd_list_ = nullptr;
volatile uint8_t *fis_ = nullptr;
volatile ahci_cmd_table *cmd_table_ = nullptr;
};
inline uint32_t ahci_port::read_port_reg(ahci_port_reg reg) {
return ahci_.read_port_reg(num_, reg);
}
inline void ahci_port::write_port_reg(ahci_port_reg reg, uint32_t val) {
ahci_.write_port_reg(num_, reg, val);
}
inline volatile ahci_cmd_table *ahci_port::cmd_table_ptr(uint cmd_slot) {
return (volatile ahci_cmd_table *)((uintptr_t)cmd_table_+ CMD_TABLE_ENTRY_SIZE * cmd_slot);
}

20
dev/block/ahci/rules.mk Normal file
View File

@@ -0,0 +1,20 @@
LOCAL_DIR := $(GET_LOCAL_DIR)
# At the moment, this can only be built with hardware MMU available.
ifeq (true,$(call TOBOOL,$(WITH_KERNEL_VM)))
MODULE := $(LOCAL_DIR)
MODULE_SRCS += $(LOCAL_DIR)/ahci.cpp
MODULE_SRCS += $(LOCAL_DIR)/ata.cpp
MODULE_SRCS += $(LOCAL_DIR)/disk.cpp
MODULE_SRCS += $(LOCAL_DIR)/port.cpp
MODULE_DEPS += dev/bus/pci
MODULE_DEPS += lib/bio
MODULE_CPPFLAGS += -Wno-invalid-offsetof
include make/module.mk
endif # WITH_KERNEL_VM

View File

@@ -2,4 +2,5 @@
# #
MODULES += dev/bus/pci MODULES += dev/bus/pci
MODULES += dev/block/ahci
MODULES += dev/net/e1000 MODULES += dev/net/e1000

180
dev/include/hw/ata.h Normal file
View File

@@ -0,0 +1,180 @@
//
// Copyright (c) 2022 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#pragma once
#include <stdint.h>
#include <assert.h>
// portions from https://wiki.osdev.org/AHCI
//
// TODO: reconsider use of bitfields, endian
typedef enum {
FIS_TYPE_REG_H2D = 0x27, // Register FIS - host to device
FIS_TYPE_REG_D2H = 0x34, // Register FIS - device to host
FIS_TYPE_DMA_ACT = 0x39, // DMA activate FIS - device to host
FIS_TYPE_DMA_SETUP = 0x41, // DMA setup FIS - bidirectional
FIS_TYPE_DATA = 0x46, // Data FIS - bidirectional
FIS_TYPE_BIST = 0x58, // BIST activate FIS - bidirectional
FIS_TYPE_PIO_SETUP = 0x5F, // PIO setup FIS - device to host
FIS_TYPE_DEV_BITS = 0xA1, // Set device bits FIS - device to host
} FIS_TYPE;
typedef enum {
ATA_CMD_IDENTIFY = 0xec,
ATA_CMD_PACKET = 0xa0,
ATA_CMD_PACKET_IDENTIFY = 0xa1,
} ATA_CMD;
typedef struct tagFIS_REG_H2D
{
// DWORD 0
uint8_t fis_type; // FIS_TYPE_REG_H2D
uint8_t pmport:4; // Port multiplier
uint8_t rsv0:3; // Reserved
uint8_t c:1; // 1: Command, 0: Control
uint8_t command; // Command register
uint8_t featurel; // Feature register, 7:0
// DWORD 1
uint8_t lba0; // LBA low register, 7:0
uint8_t lba1; // LBA mid register, 15:8
uint8_t lba2; // LBA high register, 23:16
uint8_t device; // Device register
// DWORD 2
uint8_t lba3; // LBA register, 31:24
uint8_t lba4; // LBA register, 39:32
uint8_t lba5; // LBA register, 47:40
uint8_t featureh; // Feature register, 15:8
// DWORD 3
uint8_t countl; // Count register, 7:0
uint8_t counth; // Count register, 15:8
uint8_t icc; // Isochronous command completion
uint8_t control; // Control register
// DWORD 4
uint8_t rsv1[4]; // Reserved
} FIS_REG_H2D;
typedef struct tagFIS_REG_D2H
{
// DWORD 0
uint8_t fis_type; // FIS_TYPE_REG_D2H
uint8_t pmport:4; // Port multiplier
uint8_t rsv0:2; // Reserved
uint8_t i:1; // Interrupt bit
uint8_t rsv1:1; // Reserved
uint8_t status; // Status register
uint8_t error; // Error register
// DWORD 1
uint8_t lba0; // LBA low register, 7:0
uint8_t lba1; // LBA mid register, 15:8
uint8_t lba2; // LBA high register, 23:16
uint8_t device; // Device register
// DWORD 2
uint8_t lba3; // LBA register, 31:24
uint8_t lba4; // LBA register, 39:32
uint8_t lba5; // LBA register, 47:40
uint8_t rsv2; // Reserved
// DWORD 3
uint8_t countl; // Count register, 7:0
uint8_t counth; // Count register, 15:8
uint8_t rsv3[2]; // Reserved
// DWORD 4
uint8_t rsv4[4]; // Reserved
} FIS_REG_D2H;
typedef struct tagFIS_DATA
{
// DWORD 0
uint8_t fis_type; // FIS_TYPE_DATA
uint8_t pmport:4; // Port multiplier
uint8_t rsv0:4; // Reserved
uint8_t rsv1[2]; // Reserved
// DWORD 1 ~ N
uint32_t data[1]; // Payload
} FIS_DATA;
typedef struct tagFIS_PIO_SETUP
{
// DWORD 0
uint8_t fis_type; // FIS_TYPE_PIO_SETUP
uint8_t pmport:4; // Port multiplier
uint8_t rsv0:1; // Reserved
uint8_t d:1; // Data transfer direction, 1 - device to host
uint8_t i:1; // Interrupt bit
uint8_t rsv1:1;
uint8_t status; // Status register
uint8_t error; // Error register
// DWORD 1
uint8_t lba0; // LBA low register, 7:0
uint8_t lba1; // LBA mid register, 15:8
uint8_t lba2; // LBA high register, 23:16
uint8_t device; // Device register
// DWORD 2
uint8_t lba3; // LBA register, 31:24
uint8_t lba4; // LBA register, 39:32
uint8_t lba5; // LBA register, 47:40
uint8_t rsv2; // Reserved
// DWORD 3
uint8_t countl; // Count register, 7:0
uint8_t counth; // Count register, 15:8
uint8_t rsv3; // Reserved
uint8_t e_status; // New value of status register
// DWORD 4
uint16_t tc; // Transfer count
uint8_t rsv4[2]; // Reserved
} FIS_PIO_SETUP;
typedef struct tagFIS_DMA_SETUP
{
// DWORD 0
uint8_t fis_type; // FIS_TYPE_DMA_SETUP
uint8_t pmport:4; // Port multiplier
uint8_t rsv0:1; // Reserved
uint8_t d:1; // Data transfer direction, 1 - device to host
uint8_t i:1; // Interrupt bit
uint8_t a:1; // Auto-activate. Specifies if DMA Activate FIS is needed
uint8_t rsved[2]; // Reserved
// DWORD 1&2
uint64_t DMAbufferID; // DMA Buffer Identifier. Used to Identify DMA buffer in host memory.
// SATA Spec says host specific and not in Spec. Trying AHCI spec might work.
// DWORD 3
uint32_t rsvd; // Reserved
// DWORD 4
uint32_t DMAbufOffset; // Byte offset into buffer. First 2 bits must be 0
// DWORD 5
uint32_t TransferCount; // Number of bytes to transfer. Bit 0 must be 0
// DWORD 6
uint32_t resvd; // Reserved
} FIS_DMA_SETUP;