wolfBoot/src/x86/ahci.c

469 lines
16 KiB
C

/* ahci.c
*
* Copyright (C) 2023 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*
*/
/**
* @file ahci.c
*
* @brief AHCI (Advanced Host Controller Interface) Implementation.
*
* This file contains the implementation of the AHCI (Advanced Host Controller
* Interface) driver. It includes functions to enable and disable the AHCI
* controller, detect SATA disks, and initialize ATA drives for detected disks.
*/
#ifndef AHCI_H_
#define AHCI_H_
#include <stdint.h>
#include <x86/common.h>
#include <printf.h>
#include <pci.h>
#include <x86/ahci.h>
#include <x86/ata.h>
#include <string.h>
#define AHCI_ABAR_OFFSET 0x24
#ifdef TARGET_x86_fsp_qemu
#define SATA_BASE 0x02200000
#elif TARGET_kontron_vx3060_s2
#define SATA_BASE 0x02200000
#endif /* TARGET_qemu_fsp */
#define HBA_FIS_BASE (SATA_BASE + 0x100)
#define HBA_CLB_BASE (SATA_BASE + 0x1000)
#define HBA_TBL_BASE (SATA_BASE + 0x200000)
#define HBA_FIS_SIZE 0x100
#define HBA_CLB_SIZE 0x400
#define HBA_TBL_SIZE 0x800
#define HBA_FIS_PORT_SIZE 0x80
#define PCI_REG_PCS 0x92
#define PCI_REG_CLK 0x94
#define PCI_REG_PCS_PORT_ENABLE_MASK 0x3f
#define PCI_REG_PCS_OOB 1 << 15
#define PCI_REG_MAP 0x90
#define PCI_REG_MAP_AHCI_MODE (0x1 << 6)
#define PCI_REG_MAP_ALL_PORTS (0x1 << 5)
#ifdef DEBUG_AHCI
#define AHCI_DEBUG_PRINTF(...) wolfBoot_printf(__VA_ARGS__)
#else
#define AHCI_DEBUG_PRINTF(...) do {} while(0)
#endif /* DEBUG_AHCI */
/**
* @brief Sets the AHCI Base Address Register (ABAR) for the given device.
*
* @param bus The PCI bus number of the AHCI device.
* @param dev The PCI device number of the AHCI device.
* @param fun The PCI function number of the AHCI device.
* @param addr The address to set as the ABAR.
*/
static inline void ahci_set_bar(uint32_t bus, uint32_t dev,
uint32_t func, uint32_t addr)
{
pci_config_write32(bus, dev, func, AHCI_ABAR_OFFSET, addr);
}
/**
* @brief Initializes the SATA controller for the given device.
*
* This function initializes the SATA controller for the specified AHCI device
* and detects connected SATA disks. It sets up the necessary registers and
* configurations for the controller to function properly.
*
* @param bus The PCI bus number of the AHCI device.
* @param dev The PCI device number of the AHCI device.
* @param fun The PCI function number of the AHCI device.
* @return 0 on success, or a negative value on failure.
*/
int init_sata_controller(uint32_t bus, uint32_t dev, uint32_t fun)
{
uint16_t reg16;
uint32_t reg;
reg16 = pci_config_read16(bus, dev, fun, PCI_REG_PCS);
/* enable all ports */
reg16 |= 0x3f;
reg16 |= PCI_REG_PCS_OOB;
pci_config_write16(bus, dev, fun,
PCI_REG_PCS, reg16);
reg = pci_config_read32(bus, dev, fun, PCI_REG_CLK);
reg |= 0x193;
pci_config_write32(bus, dev, fun, PCI_REG_CLK, reg);
wolfBoot_printf("Device detected: %x\r\n",
reg16 & ~PCI_REG_PCS_PORT_ENABLE_MASK);
return 0;
}
/**
* @brief Enables the AHCI controller for the given device.
*
* This function enables the AHCI controller for the specified AHCI device
* and returns the AHCI Base Address Register (ABAR) for accessing AHCI registers.
*
* @param bus The PCI bus number of the AHCI device.
* @param dev The PCI device number of the AHCI device.
* @param fun The PCI function number of the AHCI device.
* @return The ABAR address on success, or 0 on failure.
*/
uint32_t ahci_enable(uint32_t bus, uint32_t dev, uint32_t fun)
{
uint16_t reg16;
uint32_t iobar;
uint32_t reg;
uint32_t bar;
AHCI_DEBUG_PRINTF("ahci: enabling %x:%x.%x\r\n", bus, dev, fun);
reg = pci_config_read32(bus, dev, fun, PCI_COMMAND_OFFSET);
bar = pci_config_read32(bus, dev, fun, AHCI_ABAR_OFFSET);
AHCI_DEBUG_PRINTF("PCI BAR: %08x\r\n", bar);
iobar = pci_config_read32(bus, dev, fun, AHCI_AIDPBA_OFFSET);
AHCI_DEBUG_PRINTF("PCI I/O space: %08x\r\n", iobar);
reg |= PCI_COMMAND_BUS_MASTER;
reg |= PCI_COMMAND_MEM_SPACE;
pci_config_write32(bus, dev, fun, PCI_COMMAND_OFFSET, reg);
reg = pci_config_read32(bus, dev, fun, PCI_INTR_OFFSET);
AHCI_DEBUG_PRINTF("Interrupt pin for AHCI controller: %02x\r\n",
(reg >> 8) & 0xFF);
pci_config_write32(bus, dev, fun, PCI_INTR_OFFSET,
(reg & 0xFFFFFF00 | 0x0a));
AHCI_DEBUG_PRINTF("Setting interrupt line: 0x0A\r\n");
return bar;
}
/**
* @brief Dumps the status of the specified AHCI port.
*
* This function dumps the status of the AHCI port with the given index.
* It prints the status of various port registers for debugging purposes.
*
* @param base The AHCI Base Address Register (ABAR) for accessing AHCI registers.
* @param i The index of the AHCI port to dump status for.
*/
void ahci_dump_port(uint32_t base, int i)
{
uint32_t cmd, ci, is, tfd, serr, ssst;
cmd = mmio_read32(AHCI_PxCMD(base, i));
ci = mmio_read32(AHCI_PxCI(base, i));
is = mmio_read32(AHCI_PxIS(base, i));
tfd = mmio_read32(AHCI_PxTFD(base, i));
serr = mmio_read32(AHCI_PxSERR(base, i));
ssst = mmio_read32(AHCI_PxSSTS(base, i));
AHCI_DEBUG_PRINTF("%d: cmd:0x%x ci:0x%x is: 0x%x tfd: 0x%x serr: 0x%x ssst: 0x%x\r\n",
i, cmd, ci, is, tfd, serr, ssst);
}
/**
* @brief Enables SATA ports and detects connected SATA disks.
*
* This function enables SATA ports in the AHCI controller and detects connected SATA disks.
* It initializes the ATA drives for the detected disks.
*
* @param base The AHCI Base Address Register (ABAR) for accessing AHCI registers.
*/
void sata_enable(uint32_t base) {
volatile uint32_t count;
uint32_t cap, ports_impl;
uint8_t sata_only;
uint8_t cap_sud;
uint32_t n_ports;
uint32_t i, j;
uint64_t data64;
uint32_t data;
uint32_t reg;
mmio_or32(AHCI_HBA_GHC(base), HBA_GHC_AE);
/* Wait until enabled. */
while ((mmio_read32(AHCI_HBA_GHC(base)) & HBA_GHC_AE) == 0)
;
AHCI_DEBUG_PRINTF("AHCI memory mapped at %08x\r\n", base);
/* Resetting the controller */
mmio_or32(AHCI_HBA_GHC(base), HBA_GHC_HR | HBA_GHC_IE);
/* Wait until reset is complete */
while ((mmio_read32(AHCI_HBA_GHC(base)) & HBA_GHC_HR) != 0)
;
/* Wait until enabled. */
if ((mmio_read32(AHCI_HBA_GHC(base)) & HBA_GHC_AE) == 0)
mmio_or32(AHCI_HBA_GHC(base), HBA_GHC_AE);;
AHCI_DEBUG_PRINTF("AHCI reset complete.\r\n");
cap = mmio_read32(AHCI_HBA_CAP(base));
n_ports = (cap & 0x1F) + 1;
sata_only = (cap & AHCI_CAP_SAM);
cap_sud = (cap & AHCI_CAP_SSS);
if (!sata_only)
mmio_or32(AHCI_HBA_GHC(base), HBA_GHC_AE);
ports_impl = mmio_read32(AHCI_HBA_PI(base));
/* Clear global HBA IS */
reg = mmio_read32(AHCI_HBA_IS(base));
mmio_write32(AHCI_HBA_IS(base), reg);
AHCI_DEBUG_PRINTF("AHCI HBA: Cleared IS\r\n");
AHCI_DEBUG_PRINTF("AHCI: %d ports\r\n", n_ports);
for (i = 0; i < AHCI_MAX_PORTS; i++) {
if ((ports_impl & (1 << i)) != 0) {
uint32_t reg;
uint32_t ssts = mmio_read32(AHCI_PxSSTS(base, i));
uint8_t ipm = (ssts >> 8) & 0xFF;
uint8_t det = ssts & 0x0F;
volatile struct hba_cmd_header *hdr;
data = mmio_read32(AHCI_PxCMD(base, i));
/* Detect POD */
if ((data & AHCI_PORT_CMD_CPD) != 0) {
AHCI_DEBUG_PRINTF("AHCI port %d: POD\r\n", i);
mmio_or32(AHCI_PxCMD(base, i), AHCI_PORT_CMD_POD);
}
/* Detect pre-spinning */
if (cap_sud != 0) {
AHCI_DEBUG_PRINTF("AHCI port %d: Spinning\r\n", i);
mmio_or32(AHCI_PxCMD(base, i), AHCI_PORT_CMD_SUD);
}
/* Disable aggressive powersaving */
mmio_or32(AHCI_PxSCTL(base, i), (0x03 << 8));
/* Disable interrupt reporting to SW */
//mmio_write32(AHCI_PxIE(base, i), 0);
count = 0;
while (1) {
ssts = mmio_read32(AHCI_PxSSTS(base, i));
ipm = (ssts >> 8) & 0x0F;
ssts &= AHCI_SSTS_DET_MASK;
if (ssts == AHCI_PORT_SSTS_DET_PCE)
break;
if (count++ > 5) {
AHCI_DEBUG_PRINTF("AHCI port %d: Timeout occurred.\r\n", i);
break;
}
delay(1000);
};
if (ssts == 0) {
wolfBoot_printf("AHCI port %d: No disk detected\r\n", i);
} else {
wolfBoot_printf("AHCI port %d: Disk detected (det: %02x ipm: %02x)\r\n",
i, det, ipm);
/* Clear port SERR */
reg = mmio_read32(AHCI_PxSERR(base, i));
mmio_write32(AHCI_PxSERR(base,i), reg);
AHCI_DEBUG_PRINTF("AHCI port: Cleared SERR\r\n");
/* Clear port IS */
reg = mmio_read32(AHCI_PxIS(base, i));
mmio_write32(AHCI_PxIS(base,i), reg);
AHCI_DEBUG_PRINTF("AHCI port: Cleared IS\r\n");
/* Send STOP command */
reg = mmio_read32(AHCI_PxCMD(base, i));
if ((reg & (AHCI_PORT_CMD_START | AHCI_PORT_CMD_CR)) != 0) {
if (reg & AHCI_PORT_CMD_START)
mmio_write32(AHCI_PxCMD(base, i),
(reg & (~AHCI_PORT_CMD_START)));
}
AHCI_DEBUG_PRINTF("AHCI port: Sending STOP ...\r\n");
/* Wait for CR to be cleared */
count = 0;
do {
reg = mmio_read32(AHCI_PxCMD(base, i));
if (count++ > 5) {
AHCI_DEBUG_PRINTF("AHCI Error: Port did not clear CR!\r\n");
break;
}
delay(1000);
} while ((reg & AHCI_PORT_CMD_CR) != 0);
AHCI_DEBUG_PRINTF("AHCI port: Sent STOP.\r\n");
AHCI_DEBUG_PRINTF("AHCI port: Disabling FIS ...\r\n");
/* Disable FIS RX */
reg = mmio_read32(AHCI_PxCMD(base, i));
if (reg & (AHCI_PORT_CMD_CR | AHCI_PORT_CMD_START)) {
wolfBoot_printf("AHCI Error: Could not disable FIS while DMA is running\r");
} else if ((reg & AHCI_PORT_CMD_FR) != 0) {
mmio_write32(AHCI_PxCMD(base, i),
reg & (~AHCI_PORT_CMD_FRE));
}
/* Wait for FR to be cleared */
count = 0;
do {
reg = mmio_read32(AHCI_PxCMD(base, i));
if (count++ > 5) {
wolfBoot_printf("AHCI Error: Port did not clear FR!\r\n");
break;
}
delay(1000);
} while ((reg & AHCI_PORT_CMD_FR) != 0);
AHCI_DEBUG_PRINTF("AHCI port: FIS disabled.\r\n");
/* Initialize FIS and CLB address */
mmio_write32(AHCI_PxCLB(base, i),
HBA_CLB_BASE + i * HBA_CLB_SIZE);
mmio_write32(AHCI_PxCLBH(base, i), 0);
mmio_write32(AHCI_PxFB(base, i),
HBA_FIS_BASE + i * HBA_FIS_SIZE);
mmio_write32(AHCI_PxFBH(base, i), 0);
memset((void*)(uintptr_t)(HBA_CLB_BASE + i * HBA_CLB_SIZE),
0, HBA_CLB_SIZE);
memset((void*)(uintptr_t)(HBA_FIS_BASE + i * HBA_FIS_SIZE),
0, HBA_FIS_SIZE);
/* Wait until CR is cleared */
do {
reg = mmio_read32(AHCI_PxCMD(base, i));
} while(reg & AHCI_PORT_CMD_CR);
reg |= AHCI_PORT_CMD_FRE | AHCI_PORT_CMD_START;
mmio_write32(AHCI_PxCMD(base, i), reg);
AHCI_DEBUG_PRINTF("AHCI port %d command engine started\r\n", i);
/* Put port into active state */
reg = mmio_read32(AHCI_PxCMD(base, i));
mmio_write32(AHCI_PxCMD(base, i), reg | AHCI_PORT_CMD_ICC_ACTIVE);
/* Check device type by signature */
reg = mmio_read32(AHCI_PxSIG(base, i));
AHCI_DEBUG_PRINTF("SATA disk drive detected on AHCI. Sign: %x\r\n",
reg);
if (reg == AHCI_PORT_SIG_SATA) {
int drv;
wolfBoot_printf("SATA disk drive detected on AHCI port %d\r\n",
i);
drv = ata_drive_new(base, i, HBA_CLB_BASE + i * HBA_CLB_SIZE,
HBA_TBL_BASE + i * HBA_TBL_SIZE, HBA_FIS_BASE + i * HBA_FIS_SIZE);
if (drv < 0) {
wolfBoot_printf("Failed to associate ATA drive to disk\r\n");
} else {
char buf[512] ="";
int r;
AHCI_DEBUG_PRINTF("ATA%d associated to AHCI port %d\r\n",
drv, i);
r = ata_identify_device(drv);
AHCI_DEBUG_PRINTF("ATA identify: returned %d\r\n", r);
}
} else {
AHCI_DEBUG_PRINTF("AHCI port %d: device with signature %08x is not supported\r\n",
i, reg);
}
}
}
}
}
/**
* @brief Disables SATA ports in the AHCI controller.
*
* This function disables SATA ports in the AHCI controller and stops any DMA operation.
* It clears status registers and puts the AHCI ports into an inactive state.
*
* @param base The AHCI Base Address Register (ABAR) for accessing AHCI registers.
*/
void sata_disable(uint32_t base)
{
uint32_t i, reg;
volatile uint32_t count;
for (i = 0; i < AHCI_MAX_PORTS; i++) {
/* Clear port SERR */
reg = mmio_read32(AHCI_PxSERR(base, i));
mmio_write32(AHCI_PxSERR(base,i), reg);
/* Clear port IS */
reg = mmio_read32(AHCI_PxIS(base, i));
mmio_write32(AHCI_PxIS(base,i), reg);
/* Send STOP command */
reg = mmio_read32(AHCI_PxCMD(base, i));
if ((reg & (AHCI_PORT_CMD_START | AHCI_PORT_CMD_CR)) != 0) {
if (reg & AHCI_PORT_CMD_START)
mmio_write32(AHCI_PxCMD(base, i),
(reg & (~AHCI_PORT_CMD_START)));
}
/* Wait for CR to be cleared */
count = 0;
do {
reg = mmio_read32(AHCI_PxCMD(base, i));
if (count++ > 5) {
break;
}
delay(1000);
} while ((reg & AHCI_PORT_CMD_CR) != 0);
/* Disable FIS RX */
reg = mmio_read32(AHCI_PxCMD(base, i));
if (reg & (AHCI_PORT_CMD_CR | AHCI_PORT_CMD_START)) {
wolfBoot_printf("AHCI Error: Could not disable FIS while DMA is running\r\n");
} else if ((reg & AHCI_PORT_CMD_FR) != 0) {
mmio_write32(AHCI_PxCMD(base, i),
reg & (~AHCI_PORT_CMD_FRE));
}
/* Wait for FR to be cleared */
count = 0;
do {
reg = mmio_read32(AHCI_PxCMD(base, i));
if (count++ > 5) {
wolfBoot_printf("AHCI Error: Port did not clear FR!\r\n");
break;
}
delay(1000);
} while ((reg & AHCI_PORT_CMD_FR) != 0);
reg = mmio_read32(AHCI_PxCMD(base, i));
mmio_write32(AHCI_PxCMD(base, i),
reg & (~AHCI_PORT_CMD_ICC_ACTIVE));
}
/* reg = mmio_read32(AHCI_HBA_GHC(base)); */
/* mmio_write32(AHCI_HBA_GHC(base), reg & (~HBA_GHC_AE)); */
/* mmio_or32(AHCI_HBA_GHC(base), HBA_GHC_HR | HBA_GHC_IE); */
/* memset((void *)SATA_BASE, 0, 0x1000000); */
}
#endif /* AHCI_H_ */