git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22959 a95241bf-73f2-0310-859d-f6bbb57e9c96
349 lines
9.5 KiB
C++
349 lines
9.5 KiB
C++
/*
|
|
* Copyright 2007, Marcus Overhagen. All rights reserved.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
#include "ahci_controller.h"
|
|
#include "util.h"
|
|
|
|
#include <KernelExport.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <new>
|
|
|
|
#define TRACE(a...) dprintf("\33[34mahci:\33[0m " a)
|
|
#define FLOW(a...) dprintf("ahci: " a)
|
|
|
|
|
|
AHCIController::AHCIController(device_node_handle node, pci_device_info *device)
|
|
: fNode(node)
|
|
, fPCIDevice(device)
|
|
, fPCIVendorID(0xffff)
|
|
, fPCIDeviceID(0xffff)
|
|
, fFlags(0)
|
|
, fCommandSlotCount(0)
|
|
, fPortCountMax(0)
|
|
, fPortCountAvail(0)
|
|
, fPortImplementedMask(0)
|
|
, fIRQ(0)
|
|
, 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;
|
|
|
|
if (gPCI->get_pci_info(fPCIDevice, &pciInfo) < B_OK) {
|
|
dprintf("AHCIController::Init ERROR: getting PCI info failed!\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
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 = gPCI->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 = gPCI->read_pci_config(fPCIDevice, capabilityOffset, 4);
|
|
satacr1 = gPCI->read_pci_config(fPCIDevice, capabilityOffset + 4, 4);
|
|
TRACE("satacr0 = 0x%08lx, satacr1 = 0x%08lx\n", satacr0, satacr1);
|
|
}
|
|
|
|
uint16 pcicmd = gPCI->read_pci_config(fPCIDevice, PCI_command, 2);
|
|
TRACE("pcicmd old 0x%04x\n", pcicmd);
|
|
pcicmd = PCI_command_master | PCI_command_memory | (pcicmd & ~PCI_command_io);
|
|
TRACE("pcicmd new 0x%04x\n", pcicmd);
|
|
gPCI->write_pci_config(fPCIDevice, PCI_command, 2, pcicmd);
|
|
|
|
if (fPCIVendorID == PCI_VENDOR_JMICRON) {
|
|
uint32 ctrl = gPCI->read_pci_config(fPCIDevice, PCI_JMICRON_CONTROLLER_CONTROL_1, 4);
|
|
TRACE("Jmicron controller control 1 old 0x%08lx\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%08lx\n", ctrl);
|
|
gPCI->write_pci_config(fPCIDevice, PCI_JMICRON_CONTROLLER_CONTROL_1, 4, ctrl);
|
|
}
|
|
|
|
fIRQ = pciInfo.u.h0.interrupt_line;
|
|
if (fIRQ == 0 || fIRQ == 0xff) {
|
|
TRACE("PCI IRQ not assigned\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
void *addr = (void *)pciInfo.u.h0.base_registers[5];
|
|
size_t size = pciInfo.u.h0.base_register_sizes[5];
|
|
|
|
TRACE("registers at %p, size %#lx\n", addr, size);
|
|
if (!addr) {
|
|
TRACE("PCI base address register 5 not assigned\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
fRegsArea = map_mem((void **)&fRegs, addr, size, 0, "AHCI HBA regs");
|
|
if (fRegsArea < B_OK) {
|
|
TRACE("mapping registers failed\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
if (ResetController() < B_OK) {
|
|
TRACE("controller reset failed\n");
|
|
goto err;
|
|
}
|
|
|
|
fCommandSlotCount = 1 + ((fRegs->cap >> CAP_NCS_SHIFT) & CAP_NCS_MASK);
|
|
fPortCountMax = 1 + ((fRegs->cap >> CAP_NP_SHIFT) & CAP_NP_MASK);
|
|
|
|
fPortImplementedMask = fRegs->pi;
|
|
if (fPortImplementedMask == 0) {
|
|
fPortImplementedMask = 0xffffffff >> (32 - fPortCountMax);
|
|
TRACE("ports-implemented mask is zero, using 0x%lx instead.\n", fPortImplementedMask);
|
|
}
|
|
|
|
fPortCountAvail = count_bits_set(fPortImplementedMask);
|
|
|
|
TRACE("cap: Interface Speed Support: generation %lu\n", (fRegs->cap >> CAP_ISS_SHIFT) & CAP_ISS_MASK);
|
|
TRACE("cap: Number of Command Slots: %d (raw %#lx)\n", fCommandSlotCount, (fRegs->cap >> CAP_NCS_SHIFT) & CAP_NCS_MASK);
|
|
TRACE("cap: Number of Ports: %d (raw %#lx)\n", fPortCountMax, (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: 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");
|
|
TRACE("ghc: AHCI Enable: %s\n", (fRegs->ghc & GHC_AE) ? "yes" : "no");
|
|
TRACE("Ports Implemented Mask: %#08lx\n", fPortImplementedMask);
|
|
TRACE("Number of Available Ports: %d\n", fPortCountAvail);
|
|
TRACE("AHCI Version %lu.%lu\n", fRegs->vs >> 16, fRegs->vs & 0xff);
|
|
TRACE("Interrupt %u\n", 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 <= fPortCountMax; i++) {
|
|
if (fPortImplementedMask & (1 << i)) {
|
|
fPort[i] = new (std::nothrow)AHCIPort(this, i);
|
|
if (!fPort[i]) {
|
|
TRACE("out of memory creating port %d", i);
|
|
break;
|
|
}
|
|
status_t status = fPort[i]->Init1();
|
|
if (status < B_OK) {
|
|
TRACE("init-1 port %d failed", i);
|
|
delete fPort[i];
|
|
fPort[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// enable interrupts
|
|
fRegs->ghc |= GHC_IE;
|
|
FlushPostedWrites();
|
|
|
|
for (int i = 0; i <= fPortCountMax; i++) {
|
|
if (fPort[i]) {
|
|
status_t status = fPort[i]->Init2();
|
|
if (status < B_OK) {
|
|
TRACE("init-2 port %d failed", 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 <= fPortCountMax; 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);
|
|
|
|
|
|
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;
|
|
|
|
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 = 1 + ((fRegs->cap >> CAP_NP_SHIFT) & CAP_NP_MASK);
|
|
if (portCount > 8)
|
|
panic("Intel AHCI: too many SATA ports! Please report at http://dev.haiku-os.org");
|
|
uint16 pcs = gPCI->read_pci_config(fPCIDevice, 0x92, 2);
|
|
pcs |= (0xff >> (8 - portCount));
|
|
gPCI->write_pci_config(fPCIDevice, 0x92, 2, pcs);
|
|
}
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
int32
|
|
AHCIController::Interrupt(void *data)
|
|
{
|
|
AHCIController *self = (AHCIController *)data;
|
|
uint32 int_stat = self->fRegs->is & self->fPortImplementedMask;
|
|
if (int_stat == 0)
|
|
return B_UNHANDLED_INTERRUPT;
|
|
|
|
for (int i = 0; i < self->fPortCountMax; i++) {
|
|
if (int_stat & (1 << i)) {
|
|
if (self->fPort[i]) {
|
|
self->fPort[i]->Interrupt();
|
|
} else {
|
|
FLOW("interrupt on non-existent port %d\n", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// clear interrupts
|
|
self->fRegs->is = int_stat;
|
|
|
|
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;
|
|
|
|
return fPort[targetID]->ScsiGetRestrictions(isATAPI, noAutoSense, maxBlocks);
|
|
}
|