/* * Copyright 2007-2009, Marcus Overhagen. All rights reserved. * Distributed under the terms of the MIT License. */ #include "ahci_controller.h" #include "util.h" #include #include #include #include #include #define TRACE(a...) dprintf("ahci: " a) #define FLOW(a...) dprintf("ahci: " a) AHCIController::AHCIController(device_node *node, pci_device_module_info *pciModule, pci_device *device) : fNode(node), fPCI(pciModule), fPCIDevice(device), fPCIVendorID(0xffff), fPCIDeviceID(0xffff), fFlags(0), fCommandSlotCount(0), fPortCount(0), fPortImplementedMask(0), fIRQ(0), fUseMSI(false), fInstanceCheck(-1) { memset(fPort, 0, sizeof(fPort)); ASSERT(sizeof(ahci_port) == 128); ASSERT(sizeof(ahci_hba) == 4352); ASSERT(sizeof(fis) == 256); ASSERT(sizeof(command_list_entry) == 32); ASSERT(sizeof(command_table) == 128); ASSERT(sizeof(prd) == 16); } AHCIController::~AHCIController() { } status_t AHCIController::Init() { pci_info pciInfo; fPCI->get_pci_info(fPCIDevice, &pciInfo); fPCIVendorID = pciInfo.vendor_id; fPCIDeviceID = pciInfo.device_id; TRACE("AHCIController::Init %u:%u:%u vendor %04x, device %04x\n", pciInfo.bus, pciInfo.device, pciInfo.function, fPCIVendorID, fPCIDeviceID); // --- Instance check workaround begin char sName[32]; snprintf(sName, sizeof(sName), "ahci-inst-%u-%u-%u", pciInfo.bus, pciInfo.device, pciInfo.function); if (find_port(sName) >= 0) { dprintf("AHCIController::Init ERROR: an instance for object %u:%u:%u already exists\n", pciInfo.bus, pciInfo.device, pciInfo.function); return B_ERROR; } fInstanceCheck = create_port(1, sName); // --- Instance check workaround end get_device_info(fPCIVendorID, fPCIDeviceID, NULL, &fFlags); uchar capabilityOffset; status_t res = fPCI->find_pci_capability(fPCIDevice, PCI_cap_id_sata, &capabilityOffset); if (res == B_OK) { uint32 satacr0; uint32 satacr1; TRACE("PCI SATA capability found at offset 0x%x\n", capabilityOffset); satacr0 = fPCI->read_pci_config(fPCIDevice, capabilityOffset, 4); satacr1 = fPCI->read_pci_config(fPCIDevice, capabilityOffset + 4, 4); TRACE("satacr0 = 0x%08" B_PRIx32 ", satacr1 = 0x%08" B_PRIx32 "\n", satacr0, satacr1); } uint16 pcicmd = fPCI->read_pci_config(fPCIDevice, PCI_command, 2); TRACE("pcicmd old 0x%04x\n", pcicmd); pcicmd &= ~(PCI_command_io | PCI_command_int_disable); pcicmd |= PCI_command_master | PCI_command_memory; TRACE("pcicmd new 0x%04x\n", pcicmd); fPCI->write_pci_config(fPCIDevice, PCI_command, 2, pcicmd); if (fPCIVendorID == PCI_VENDOR_JMICRON) { uint32 ctrl = fPCI->read_pci_config(fPCIDevice, PCI_JMICRON_CONTROLLER_CONTROL_1, 4); TRACE("Jmicron controller control 1 old 0x%08" B_PRIx32 "\n", ctrl); ctrl &= ~((1 << 9) | (1 << 12) | (1 << 14)); // disable SFF 8038i emulation ctrl |= (1 << 8) | (1 << 13) | (1 << 15); // enable AHCI controller TRACE("Jmicron controller control 1 new 0x%08" B_PRIx32 "\n", ctrl); fPCI->write_pci_config(fPCIDevice, PCI_JMICRON_CONTROLLER_CONTROL_1, 4, ctrl); } fIRQ = pciInfo.u.h0.interrupt_line; if (fPCI->get_msi_count(fPCIDevice) >= 1) { uint8 vector; if (fPCI->configure_msi(fPCIDevice, 1, &vector) == B_OK && fPCI->enable_msi(fPCIDevice) == B_OK) { TRACE("using MSI vector %u\n", vector); fIRQ = vector; fUseMSI = true; } else { TRACE("couldn't use MSI\n"); } } if (fIRQ == 0 || fIRQ == 0xff) { TRACE("Error: PCI IRQ not assigned\n"); return B_ERROR; } phys_addr_t addr = pciInfo.u.h0.base_registers[5]; size_t size = pciInfo.u.h0.base_register_sizes[5]; TRACE("registers at %#" B_PRIxPHYSADDR ", size %#" B_PRIxSIZE "\n", addr, size); if (addr == 0) { TRACE("PCI base address register 5 not assigned\n"); return B_ERROR; } fRegsArea = map_mem((void **)&fRegs, addr, size, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, "AHCI HBA regs"); if (fRegsArea < B_OK) { TRACE("mapping registers failed\n"); return B_ERROR; } // make sure interrupts are disabled fRegs->ghc &= ~GHC_IE; FlushPostedWrites(); if (ResetController() < B_OK) { TRACE("controller reset failed\n"); goto err; } fCommandSlotCount = 1 + ((fRegs->cap >> CAP_NCS_SHIFT) & CAP_NCS_MASK); fPortCount = 1 + ((fRegs->cap >> CAP_NP_SHIFT) & CAP_NP_MASK); fPortImplementedMask = fRegs->pi; // reported mask of implemented ports is sometimes empty if (fPortImplementedMask == 0) { fPortImplementedMask = 0xffffffff >> (32 - fPortCount); TRACE("ports-implemented mask is zero, using 0x%" B_PRIx32 " instead.\n", fPortImplementedMask); } // reported number of ports is sometimes too small int highestPort; highestPort = fls(fPortImplementedMask); // 1-based, 1 to 32 if (fPortCount < highestPort) { TRACE("reported number of ports is wrong, using %d instead.\n", highestPort); fPortCount = highestPort; } TRACE("cap: Interface Speed Support: generation %" B_PRIu32 "\n", (fRegs->cap >> CAP_ISS_SHIFT) & CAP_ISS_MASK); TRACE("cap: Number of Command Slots: %d (raw %#" B_PRIx32 ")\n", fCommandSlotCount, (fRegs->cap >> CAP_NCS_SHIFT) & CAP_NCS_MASK); TRACE("cap: Number of Ports: %d (raw %#" B_PRIx32 ")\n", fPortCount, (fRegs->cap >> CAP_NP_SHIFT) & CAP_NP_MASK); TRACE("cap: Supports Port Multiplier: %s\n", (fRegs->cap & CAP_SPM) ? "yes" : "no"); TRACE("cap: Supports External SATA: %s\n", (fRegs->cap & CAP_SXS) ? "yes" : "no"); TRACE("cap: Enclosure Management Supported: %s\n", (fRegs->cap & CAP_EMS) ? "yes" : "no"); TRACE("cap: FIS-based Switching Control: %s\n", (fRegs->cap & CAP_FBSS) ? "yes" : "no"); TRACE("cap: Supports Command List Override: %s\n", (fRegs->cap & CAP_SCLO) ? "yes" : "no"); TRACE("cap: Supports Staggered Spin-up: %s\n", (fRegs->cap & CAP_SSS) ? "yes" : "no"); TRACE("cap: Supports Mechanical Presence Switch: %s\n", (fRegs->cap & CAP_SMPS) ? "yes" : "no"); TRACE("cap: Supports 64-bit Addressing: %s\n", (fRegs->cap & CAP_S64A) ? "yes" : "no"); TRACE("cap: Supports Native Command Queuing: %s\n", (fRegs->cap & CAP_SNCQ) ? "yes" : "no"); TRACE("cap: Supports SNotification Register: %s\n", (fRegs->cap & CAP_SSNTF) ? "yes" : "no"); TRACE("cap: Supports Command List Override: %s\n", (fRegs->cap & CAP_SCLO) ? "yes" : "no"); TRACE("cap: Supports AHCI mode only: %s\n", (fRegs->cap & CAP_SAM) ? "yes" : "no"); if (fRegs->vs >= 0x00010200) { TRACE("cap2: DevSleep Entrance from Slumber Only: %s\n", (fRegs->cap2 & CAP2_DESO) ? "yes" : "no"); TRACE("cap2: Supports Aggressive Device Sleep Management: %s\n", (fRegs->cap2 & CAP2_SADM) ? "yes" : "no"); TRACE("cap2: Supports Device Sleep: %s\n", (fRegs->cap2 & CAP2_SDS) ? "yes" : "no"); TRACE("cap2: Automatic Partial to Slumber Transitions: %s\n", (fRegs->cap2 & CAP2_APST) ? "yes" : "no"); TRACE("cap2: NVMHCI Present: %s\n", (fRegs->cap2 & CAP2_NVMP) ? "yes" : "no"); TRACE("cap2: BIOS/OS Handoff: %s\n", (fRegs->cap2 & CAP2_BOH) ? "yes" : "no"); } TRACE("ghc: AHCI Enable: %s\n", (fRegs->ghc & GHC_AE) ? "yes" : "no"); TRACE("Ports Implemented Mask: %#08" B_PRIx32 " Number of Available Ports:" " %d\n", fPortImplementedMask, count_bits_set(fPortImplementedMask)); TRACE("AHCI Version %02" B_PRIx32 "%02" B_PRIx32 ".%02" B_PRIx32 ".%02" B_PRIx32 " Interrupt %u\n", fRegs->vs >> 24, (fRegs->vs >> 16) & 0xff, (fRegs->vs >> 8) & 0xff, fRegs->vs & 0xff, fIRQ); // setup interrupt handler if (install_io_interrupt_handler(fIRQ, Interrupt, this, 0) < B_OK) { TRACE("can't install interrupt handler\n"); goto err; } for (int i = 0; i < fPortCount; i++) { if (fPortImplementedMask & (1 << i)) { fPort[i] = new (std::nothrow)AHCIPort(this, i); if (!fPort[i]) { TRACE("out of memory creating port %d\n", i); break; } status_t status = fPort[i]->Init1(); if (status < B_OK) { TRACE("init-1 port %d failed\n", i); delete fPort[i]; fPort[i] = NULL; } } } // clear any pending interrupts uint32 interruptsPending; interruptsPending = fRegs->is; fRegs->is = interruptsPending; FlushPostedWrites(); // enable interrupts fRegs->ghc |= GHC_IE; FlushPostedWrites(); for (int i = 0; i < fPortCount; i++) { if (fPort[i]) { status_t status = fPort[i]->Init2(); if (status < B_OK) { TRACE("init-2 port %d failed\n", i); fPort[i]->Uninit(); delete fPort[i]; fPort[i] = NULL; } } } return B_OK; err: delete_area(fRegsArea); return B_ERROR; } void AHCIController::Uninit() { TRACE("AHCIController::Uninit\n"); for (int i = 0; i < fPortCount; i++) { if (fPort[i]) { fPort[i]->Uninit(); delete fPort[i]; } } // disable interrupts fRegs->ghc &= ~GHC_IE; FlushPostedWrites(); // clear pending interrupts fRegs->is = 0xffffffff; FlushPostedWrites(); // well... remove_io_interrupt_handler(fIRQ, Interrupt, this); if (fUseMSI) { fPCI->disable_msi(fPCIDevice); fPCI->unconfigure_msi(fPCIDevice); } delete_area(fRegsArea); // --- Instance check workaround begin delete_port(fInstanceCheck); // --- Instance check workaround end } status_t AHCIController::ResetController() { uint32 saveCaps = fRegs->cap & (CAP_SMPS | CAP_SSS | CAP_SPM | CAP_EMS | CAP_SXS); uint32 savePI = fRegs->pi; // AHCI 1.3: Software may perform an HBA reset prior to initializing the controller // by setting GHC.AE to ‘1’ and then setting GHC.HR to ‘1’ if desired. fRegs->ghc |= GHC_AE; FlushPostedWrites(); fRegs->ghc |= GHC_HR; FlushPostedWrites(); if (wait_until_clear(&fRegs->ghc, GHC_HR, 1000000) < B_OK) return B_TIMED_OUT; fRegs->ghc |= GHC_AE; FlushPostedWrites(); fRegs->cap |= saveCaps; fRegs->pi = savePI; FlushPostedWrites(); if (fPCIVendorID == PCI_VENDOR_INTEL) { // Intel PCS—Port Control and Status // SATA port enable bits must be set int portCount = std::max(fls(fRegs->pi), 1 + (int)((fRegs->cap >> CAP_NP_SHIFT) & CAP_NP_MASK)); if (portCount > 8) { // TODO: fix this when specification available TRACE("don't know how to enable SATA ports 9 to %d\n", portCount); portCount = 8; } uint16 pcs = fPCI->read_pci_config(fPCIDevice, 0x92, 2); pcs |= (0xff >> (8 - portCount)); fPCI->write_pci_config(fPCIDevice, 0x92, 2, pcs); } return B_OK; } int32 AHCIController::Interrupt(void *data) { AHCIController *self = (AHCIController *)data; uint32 interruptPending = self->fRegs->is & self->fPortImplementedMask; if (interruptPending == 0) return B_UNHANDLED_INTERRUPT; for (int i = 0; i < self->fPortCount; i++) { if (interruptPending & (1 << i)) { if (self->fPort[i]) { self->fPort[i]->Interrupt(); } else { FLOW("interrupt on non-existent port %d\n", i); } } } // clear pending interrupts self->fRegs->is = interruptPending; return B_INVOKE_SCHEDULER; } void AHCIController::ExecuteRequest(scsi_ccb *request) { if (request->target_lun || !fPort[request->target_id]) { request->subsys_status = SCSI_DEV_NOT_THERE; gSCSI->finished(request, 1); return; } fPort[request->target_id]->ScsiExecuteRequest(request); } uchar AHCIController::AbortRequest(scsi_ccb *request) { if (request->target_lun || !fPort[request->target_id]) return SCSI_DEV_NOT_THERE; return fPort[request->target_id]->ScsiAbortRequest(request); } uchar AHCIController::TerminateRequest(scsi_ccb *request) { if (request->target_lun || !fPort[request->target_id]) return SCSI_DEV_NOT_THERE; return fPort[request->target_id]->ScsiTerminateRequest(request); } uchar AHCIController::ResetDevice(uchar targetID, uchar targetLUN) { if (targetLUN || !fPort[targetID]) return SCSI_DEV_NOT_THERE; return fPort[targetID]->ScsiResetDevice(); } void AHCIController::GetRestrictions(uchar targetID, bool *isATAPI, bool *noAutoSense, uint32 *maxBlocks) { if (!fPort[targetID]) return; fPort[targetID]->ScsiGetRestrictions(isATAPI, noAutoSense, maxBlocks); }