/* * Copyright (c) 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * disk/ahci/hba.cpp * Driver for the Advanced Host Controller Interface. */ #include #include #include #include #include #include #include #include #include #include #include #include "../node.h" #include "ahci.h" #include "hba.h" #include "port.h" #include "registers.h" namespace Sortix { namespace AHCI { static inline void ahci_hba_flush(volatile struct hba_regs* hba_regs) { (void) hba_regs->ghc; } static unsigned long AllocateDiskNumber() { static unsigned long next_disk_number = 0; return InterlockedIncrement(&next_disk_number).o; } HBA::HBA(uint32_t devaddr) { this->devaddr = devaddr; memset(&mmio_alloc, 0, sizeof(mmio_alloc)); memset(&ports, 0, sizeof(ports)); interrupt_registered = false; mmio_alloced = false; } HBA::~HBA() { // TODO: Destroy all the ports? if ( interrupt_registered ) Interrupt::UnregisterHandler(interrupt_index, &interrupt_registration); if ( mmio_alloced ) UnmapPCIBar(&mmio_alloc); } void HBA::LogF(const char* format, ...) { // TODO: Print this line in an atomic manner. Log::PrintF("ahci: pci 0x%X: ", devaddr); va_list ap; va_start(ap, format); Log::PrintFV(format, ap); va_end(ap); Log::PrintF("\n"); } bool HBA::Reset() { #if 0 // We probably don't want to do this? // Indicate that system software is AHCI aware. regs->ghc = regs->ghc | GHC_AE; regs->ghc = regs->ghc | GHC_HR; ahci_hba_flush(regs); while ( (regs->ghc & GHC_HR) ) kthread_yield(); regs->ghc = regs->ghc | GHC_AE; ahci_hba_flush(regs); // TODO: Set port enable bits in Port Control and Status on Intel // controllers. #endif return true; } void HBA::OnInterrupt() { uint32_t is = regs->is; if ( !is ) return; for ( size_t i = 0; i < sizeof(ports) / sizeof(ports[0]); i++ ) { if ( !(is & (1U << i)) ) continue; if ( ports[i] ) ports[i]->OnInterrupt(); } regs->is = is; } void HBA__OnInterrupt(struct interrupt_context*, void* context) { ((HBA*) context)->OnInterrupt(); } bool HBA::Initialize(Ref dev, const char* devpath) { pcibar_t mmio_bar = PCI::GetBAR(devaddr, 5); if ( mmio_bar.size() < sizeof(struct hba_regs) && /* or || ? */ mmio_bar.size() < 1024 ) { LogF("error: register area is too small"); return errno = EINVAL, false; } if ( !MapPCIBAR(&mmio_alloc, mmio_bar, 0) ) { LogF("error: registers could not be mapped: %m"); return false; } regs = (volatile struct hba_regs*) mmio_alloc.from; if ( 4 < sizeof(void*) && !(regs->cap & CAP_S64A) ) { LogF("error: controller doesn't support 64-bit addressing"); return errno = EINVAL, false; } // TODO: Take control from BIOS? #if 0 // TODO: Is it bad to do a hard reset? if ( !Reset() ) { LogF("error: could not reset"); return false; } #else regs->ghc = regs->ghc | GHC_AE; #endif regs->ghc = regs->ghc & ~GHC_IE; uint32_t ports_implemented = regs->pi; uint32_t num_ports = CAP_NCS(regs->cap); uint32_t actual_ports = 0; for ( uint32_t i = 0; i < num_ports; i++ ) { if ( !(ports_implemented & (1 << i)) ) continue; actual_ports++; if ( regs->ports[i].pxcmd & (PXCMD_ST | PXCMD_CR | PXCMD_FRE | PXCMD_FR) ) { if ( regs->ports[i].pxcmd & (PXCMD_ST | PXCMD_CR) ) { regs->ports[i].pxcmd = regs->ports[i].pxcmd & ~PXCMD_ST; if ( !WaitClear(®s->ports[i].pxcmd, PXCMD_CR, true, 500) ) { LogF("error: port %u: " "timeout waiting for PXCMD_CR to clear", i); ports_implemented &= ~(1 << i); continue; } } if ( regs->ports[i].pxcmd & (PXCMD_FRE | PXCMD_FR) ) { regs->ports[i].pxcmd = regs->ports[i].pxcmd & ~PXCMD_FRE; if ( !WaitClear(®s->ports[i].pxcmd, PXCMD_FR, true, 500) ) { LogF("error: port %u: " "timeout waiting for PXCMD_FR to clear", i); ports_implemented &= ~(1 << i); continue; } } } regs->ports[i].pxis = regs->ports[i].pxis; regs->ports[i].pxserr = regs->ports[i].pxserr; } regs->is = regs->is; for ( uint32_t i = 0; i < num_ports; i++ ) { if ( !(ports_implemented & (1 << i)) ) continue; if ( !(ports[i] = new Port(this, i)) ) { LogF("error: failed to allocate port %u", i); continue; } if ( !ports[i]->Initialize() ) { delete ports[i]; ports[i] = NULL; continue; } } interrupt_index = Interrupt::IRQ0 + PCI::Read8(devaddr, PCIFIELD_INTERRUPT_LINE); interrupt_registration.handler = HBA__OnInterrupt; interrupt_registration.context = this; Interrupt::RegisterHandler(interrupt_index, &interrupt_registration); interrupt_registered = true; // Enable interrupts. regs->ghc = regs->ghc | GHC_IE; ahci_hba_flush(regs); for ( uint32_t i = 0; i < num_ports; i++ ) { if ( ports[i] && !ports[i]->FinishInitialize() ) { //ports[i]->Destroy(); // TODO: WARNING: FIXME: Shut down the port here, see the function // ahci_port_destroy in the OS `kiwi'! // TODO: Unsafe with respect to interrupt handler. delete ports[i]; ports[i] = NULL; continue; } } for ( uint32_t i = 0; i < num_ports; i++ ) { if ( !ports[i] ) continue; unsigned long number = AllocateDiskNumber(); char name[4 + sizeof(unsigned long) * 3]; snprintf(name, sizeof(name), "ahci%lu", number); Ref node(new PortNode(ports[i], 0, 0, 0660, dev->dev, 0)); if ( !node ) PanicF("Unable to allocate memory for %s/%s inode", devpath, name); ioctx_t ctx; SetupKernelIOCtx(&ctx); if ( LinkInodeInDir(&ctx, dev, name, node) != 0 ) PanicF("Unable to link %s/%s to AHCI driver inode", devpath, name); } return true; } } // namespace AHCI } // namespace Sortix