HelenOS sources
This source file includes following definitions.
- fun_sata_dev
- get_sata_device_name
- get_num_blocks
- get_block_size
- read_blocks
- write_blocks
- ahci_wait_event
- ahci_identify_device_cmd
- ahci_identify_packet_device_cmd
- ahci_identify_device
- ahci_set_mode_cmd
- ahci_set_highest_ultra_dma_mode
- ahci_rb_fpdma_cmd
- ahci_rb_fpdma
- ahci_wb_fpdma_cmd
- ahci_wb_fpdma
- ahci_interrupt
- ahci_sata_allocate
- ahci_sata_hw_start
- ahci_sata_create
- ahci_sata_devices_create
- ahci_ahci_create
- ahci_ahci_hw_start
- ahci_dev_add
- ahci_get_model_name
- main
#include <as.h>
#include <errno.h>
#include <stdio.h>
#include <ddf/interrupt.h>
#include <ddf/log.h>
#include <device/hw_res.h>
#include <device/hw_res_parsed.h>
#include <pci_dev_iface.h>
#include <ahci_iface.h>
#include "ahci.h"
#include "ahci_hw.h"
#include "ahci_sata.h"
#define NAME "ahci"
#define LO(ptr) \
((uint32_t) (((uint64_t) ((uintptr_t) (ptr))) & 0xffffffff))
#define HI(ptr) \
((uint32_t) (((uint64_t) ((uintptr_t) (ptr))) >> 32))
#define AHCI_PORT_CMDS(port) \
{ \
\
.cmd = CMD_PIO_READ_32, \
.addr = NULL, \
.dstarg = 2 \
}, \
{ \
\
.cmd = CMD_PREDICATE, \
.value = 5, \
.srcarg = 2, \
}, \
{ \
\
.cmd = CMD_PIO_WRITE_A_32, \
.addr = NULL, \
.srcarg = 2 \
}, \
{ \
\
.cmd = CMD_PIO_READ_32, \
.addr = NULL, \
.dstarg = 0 \
}, \
{ \
\
.cmd = CMD_PIO_WRITE_A_32, \
.addr = NULL, \
.srcarg = 0 \
}, \
{ \
\
.cmd = CMD_LOAD, \
.value = (port), \
.dstarg = 1 \
}, \
{ \
\
.cmd = CMD_ACCEPT \
}
static errno_t get_sata_device_name(ddf_fun_t *, size_t, char *);
static errno_t get_num_blocks(ddf_fun_t *, uint64_t *);
static errno_t get_block_size(ddf_fun_t *, size_t *);
static errno_t read_blocks(ddf_fun_t *, uint64_t, size_t, void *);
static errno_t write_blocks(ddf_fun_t *, uint64_t, size_t, void *);
static errno_t ahci_identify_device(sata_dev_t *);
static errno_t ahci_set_highest_ultra_dma_mode(sata_dev_t *);
static errno_t ahci_rb_fpdma(sata_dev_t *, uintptr_t, uint64_t);
static errno_t ahci_wb_fpdma(sata_dev_t *, uintptr_t, uint64_t);
static void ahci_sata_devices_create(ahci_dev_t *, ddf_dev_t *);
static ahci_dev_t *ahci_ahci_create(ddf_dev_t *);
static void ahci_ahci_hw_start(ahci_dev_t *);
static errno_t ahci_dev_add(ddf_dev_t *);
static void ahci_get_model_name(uint16_t *, char *);
static fibril_mutex_t sata_devices_count_lock;
static int sata_devices_count = 0;
static ahci_iface_t ahci_interface = {
.get_sata_device_name = &get_sata_device_name,
.get_num_blocks = &get_num_blocks,
.get_block_size = &get_block_size,
.read_blocks = &read_blocks,
.write_blocks = &write_blocks
};
static ddf_dev_ops_t ahci_ops = {
.interfaces[AHCI_DEV_IFACE] = &ahci_interface
};
static driver_ops_t driver_ops = {
.dev_add = &ahci_dev_add
};
static driver_t ahci_driver = {
.name = NAME,
.driver_ops = &driver_ops
};
static sata_dev_t *fun_sata_dev(ddf_fun_t *fun)
{
return ddf_fun_data_get(fun);
}
static errno_t get_sata_device_name(ddf_fun_t *fun,
size_t sata_dev_name_length, char *sata_dev_name)
{
sata_dev_t *sata = fun_sata_dev(fun);
str_cpy(sata_dev_name, sata_dev_name_length, sata->model);
return EOK;
}
static errno_t get_num_blocks(ddf_fun_t *fun, uint64_t *num_blocks)
{
sata_dev_t *sata = fun_sata_dev(fun);
*num_blocks = sata->blocks;
return EOK;
}
static errno_t get_block_size(ddf_fun_t *fun, size_t *block_size)
{
sata_dev_t *sata = fun_sata_dev(fun);
*block_size = sata->block_size;
return EOK;
}
static errno_t read_blocks(ddf_fun_t *fun, uint64_t blocknum,
size_t count, void *buf)
{
sata_dev_t *sata = fun_sata_dev(fun);
uintptr_t phys;
void *ibuf = AS_AREA_ANY;
errno_t rc = dmamem_map_anonymous(sata->block_size, DMAMEM_4GiB,
AS_AREA_READ | AS_AREA_WRITE, 0, &phys, &ibuf);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Cannot allocate read buffer.");
return rc;
}
memset(buf, 0, sata->block_size);
fibril_mutex_lock(&sata->lock);
for (size_t cur = 0; cur < count; cur++) {
rc = ahci_rb_fpdma(sata, phys, blocknum + cur);
if (rc != EOK)
break;
memcpy((void *) (((uint8_t *) buf) + (sata->block_size * cur)),
ibuf, sata->block_size);
}
fibril_mutex_unlock(&sata->lock);
dmamem_unmap_anonymous(ibuf);
return rc;
}
static errno_t write_blocks(ddf_fun_t *fun, uint64_t blocknum,
size_t count, void *buf)
{
sata_dev_t *sata = fun_sata_dev(fun);
uintptr_t phys;
void *ibuf = AS_AREA_ANY;
errno_t rc = dmamem_map_anonymous(sata->block_size, DMAMEM_4GiB,
AS_AREA_READ | AS_AREA_WRITE, 0, &phys, &ibuf);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Cannot allocate write buffer.");
return rc;
}
fibril_mutex_lock(&sata->lock);
for (size_t cur = 0; cur < count; cur++) {
memcpy(ibuf, (void *) (((uint8_t *) buf) + (sata->block_size * cur)),
sata->block_size);
rc = ahci_wb_fpdma(sata, phys, blocknum + cur);
if (rc != EOK)
break;
}
fibril_mutex_unlock(&sata->lock);
dmamem_unmap_anonymous(ibuf);
return rc;
}
static ahci_port_is_t ahci_wait_event(sata_dev_t *sata)
{
fibril_mutex_lock(&sata->event_lock);
sata->event_pxis = 0;
while (sata->event_pxis == 0)
fibril_condvar_wait(&sata->event_condvar, &sata->event_lock);
ahci_port_is_t pxis = sata->event_pxis;
if (ahci_port_is_permanent_error(pxis))
sata->is_invalid_device = true;
fibril_mutex_unlock(&sata->event_lock);
return pxis;
}
static void ahci_identify_device_cmd(sata_dev_t *sata, uintptr_t phys)
{
volatile sata_std_command_frame_t *cmd =
(sata_std_command_frame_t *) sata->cmd_table;
cmd->fis_type = SATA_CMD_FIS_TYPE;
cmd->c = SATA_CMD_FIS_COMMAND_INDICATOR;
cmd->command = 0xec;
cmd->features = 0;
cmd->lba_lower = 0;
cmd->device = 0;
cmd->lba_upper = 0;
cmd->features_upper = 0;
cmd->count = 0;
cmd->reserved1 = 0;
cmd->control = 0;
cmd->reserved2 = 0;
volatile ahci_cmd_prdt_t *prdt =
(ahci_cmd_prdt_t *) (&sata->cmd_table[0x20]);
prdt->data_address_low = LO(phys);
prdt->data_address_upper = HI(phys);
prdt->reserved1 = 0;
prdt->dbc = SATA_IDENTIFY_DEVICE_BUFFER_LENGTH - 1;
prdt->reserved2 = 0;
prdt->ioc = 0;
sata->cmd_header->prdtl = 1;
sata->cmd_header->flags =
AHCI_CMDHDR_FLAGS_CLEAR_BUSY_UPON_OK |
AHCI_CMDHDR_FLAGS_2DWCMD;
sata->cmd_header->bytesprocessed = 0;
sata->port->pxsact |= 1;
sata->port->pxci |= 1;
}
static void ahci_identify_packet_device_cmd(sata_dev_t *sata, uintptr_t phys)
{
volatile sata_std_command_frame_t *cmd =
(sata_std_command_frame_t *) sata->cmd_table;
cmd->fis_type = SATA_CMD_FIS_TYPE;
cmd->c = SATA_CMD_FIS_COMMAND_INDICATOR;
cmd->command = 0xa1;
cmd->features = 0;
cmd->lba_lower = 0;
cmd->device = 0;
cmd->lba_upper = 0;
cmd->features_upper = 0;
cmd->count = 0;
cmd->reserved1 = 0;
cmd->control = 0;
cmd->reserved2 = 0;
volatile ahci_cmd_prdt_t *prdt =
(ahci_cmd_prdt_t *) (&sata->cmd_table[0x20]);
prdt->data_address_low = LO(phys);
prdt->data_address_upper = HI(phys);
prdt->reserved1 = 0;
prdt->dbc = SATA_IDENTIFY_DEVICE_BUFFER_LENGTH - 1;
prdt->reserved2 = 0;
prdt->ioc = 0;
sata->cmd_header->prdtl = 1;
sata->cmd_header->flags =
AHCI_CMDHDR_FLAGS_CLEAR_BUSY_UPON_OK |
AHCI_CMDHDR_FLAGS_2DWCMD;
sata->cmd_header->bytesprocessed = 0;
sata->port->pxsact |= 1;
sata->port->pxci |= 1;
}
static errno_t ahci_identify_device(sata_dev_t *sata)
{
if (sata->is_invalid_device) {
ddf_msg(LVL_ERROR,
"Identify command device on invalid device");
return EINTR;
}
uintptr_t phys;
sata_identify_data_t *idata = AS_AREA_ANY;
errno_t rc = dmamem_map_anonymous(SATA_IDENTIFY_DEVICE_BUFFER_LENGTH,
DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0, &phys,
(void *) &idata);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Cannot allocate buffer to identify device.");
return rc;
}
memset(idata, 0, SATA_IDENTIFY_DEVICE_BUFFER_LENGTH);
fibril_mutex_lock(&sata->lock);
ahci_identify_device_cmd(sata, phys);
ahci_port_is_t pxis = ahci_wait_event(sata);
if (sata->is_invalid_device) {
ddf_msg(LVL_ERROR,
"Unrecoverable error during ata identify device");
goto error;
}
if (ahci_port_is_tfes(pxis)) {
ahci_identify_packet_device_cmd(sata, phys);
pxis = ahci_wait_event(sata);
if ((sata->is_invalid_device) || (ahci_port_is_error(pxis))) {
ddf_msg(LVL_ERROR,
"Unrecoverable error during ata identify packet device");
goto error;
}
sata->is_packet_device = true;
}
ahci_get_model_name(idata->model_name, sata->model);
if ((idata->sata_cap & sata_np_cap_ncq) == 0) {
ddf_msg(LVL_ERROR, "%s: NCQ must be supported", sata->model);
goto error;
}
uint16_t logsec = idata->physical_logic_sector_size;
if ((logsec & 0xc000) == 0x4000) {
if (logsec & 0x0100) {
ddf_msg(LVL_ERROR,
"%s: Sector length other than 512 B not supported",
sata->model);
goto error;
}
if ((logsec & 0x0200) && ((logsec & 0x000f) != 0)) {
ddf_msg(LVL_ERROR,
"%s: Sector length other than 512 B not supported",
sata->model);
goto error;
}
}
if (sata->is_packet_device) {
sata->block_size = SATA_DEFAULT_SECTOR_SIZE;
sata->blocks = 0;
} else {
sata->block_size = SATA_DEFAULT_SECTOR_SIZE;
if ((idata->caps & sata_rd_cap_lba) == 0) {
ddf_msg(LVL_ERROR, "%s: LBA for NCQ must be supported",
sata->model);
goto error;
} else if ((idata->cmd_set1 & sata_cs1_addr48) == 0) {
sata->blocks = (uint32_t) idata->total_lba28_0 |
((uint32_t) idata->total_lba28_1 << 16);
} else {
sata->blocks = (uint64_t) idata->total_lba48_0 |
((uint64_t) idata->total_lba48_1 << 16) |
((uint64_t) idata->total_lba48_2 << 32) |
((uint64_t) idata->total_lba48_3 << 48);
}
}
uint8_t udma_mask = idata->udma & 0x007f;
sata->highest_udma_mode = (uint8_t) -1;
if (udma_mask == 0) {
ddf_msg(LVL_ERROR,
"%s: UDMA mode for NCQ FPDMA mode must be supported",
sata->model);
goto error;
} else {
for (uint8_t i = 0; i < 7; i++) {
if (udma_mask & (1 << i))
sata->highest_udma_mode = i;
}
}
fibril_mutex_unlock(&sata->lock);
dmamem_unmap_anonymous(idata);
return EOK;
error:
fibril_mutex_unlock(&sata->lock);
dmamem_unmap_anonymous(idata);
return EINTR;
}
static void ahci_set_mode_cmd(sata_dev_t *sata, uintptr_t phys, uint8_t mode)
{
volatile sata_std_command_frame_t *cmd =
(sata_std_command_frame_t *) sata->cmd_table;
cmd->fis_type = SATA_CMD_FIS_TYPE;
cmd->c = SATA_CMD_FIS_COMMAND_INDICATOR;
cmd->command = 0xef;
cmd->features = 0x03;
cmd->lba_lower = 0;
cmd->device = 0;
cmd->lba_upper = 0;
cmd->features_upper = 0;
cmd->count = mode;
cmd->reserved1 = 0;
cmd->control = 0;
cmd->reserved2 = 0;
volatile ahci_cmd_prdt_t *prdt =
(ahci_cmd_prdt_t *) (&sata->cmd_table[0x20]);
prdt->data_address_low = LO(phys);
prdt->data_address_upper = HI(phys);
prdt->reserved1 = 0;
prdt->dbc = SATA_SET_FEATURE_BUFFER_LENGTH - 1;
prdt->reserved2 = 0;
prdt->ioc = 0;
sata->cmd_header->prdtl = 1;
sata->cmd_header->flags =
AHCI_CMDHDR_FLAGS_CLEAR_BUSY_UPON_OK |
AHCI_CMDHDR_FLAGS_2DWCMD;
sata->cmd_header->bytesprocessed = 0;
sata->port->pxsact |= 1;
sata->port->pxci |= 1;
}
static errno_t ahci_set_highest_ultra_dma_mode(sata_dev_t *sata)
{
if (sata->is_invalid_device) {
ddf_msg(LVL_ERROR,
"%s: Setting highest UDMA mode on invalid device",
sata->model);
return EINTR;
}
if (sata->highest_udma_mode == (uint8_t) -1) {
ddf_msg(LVL_ERROR,
"%s: No AHCI UDMA support.", sata->model);
return EINTR;
}
if (sata->highest_udma_mode > 6) {
ddf_msg(LVL_ERROR,
"%s: Unknown AHCI UDMA mode.", sata->model);
return EINTR;
}
uintptr_t phys;
sata_identify_data_t *idata = AS_AREA_ANY;
errno_t rc = dmamem_map_anonymous(SATA_SET_FEATURE_BUFFER_LENGTH,
DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0, &phys,
(void *) &idata);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Cannot allocate buffer for device set mode.");
return rc;
}
memset(idata, 0, SATA_SET_FEATURE_BUFFER_LENGTH);
fibril_mutex_lock(&sata->lock);
uint8_t mode = 0x40 | (sata->highest_udma_mode & 0x07);
ahci_set_mode_cmd(sata, phys, mode);
ahci_port_is_t pxis = ahci_wait_event(sata);
if (sata->is_invalid_device) {
ddf_msg(LVL_ERROR,
"%s: Unrecoverable error during set highest UDMA mode",
sata->model);
goto error;
}
if (ahci_port_is_error(pxis)) {
ddf_msg(LVL_ERROR,
"%s: Error during set highest UDMA mode", sata->model);
goto error;
}
fibril_mutex_unlock(&sata->lock);
dmamem_unmap_anonymous(idata);
return EOK;
error:
fibril_mutex_unlock(&sata->lock);
dmamem_unmap_anonymous(idata);
return EINTR;
}
static void ahci_rb_fpdma_cmd(sata_dev_t *sata, uintptr_t phys,
uint64_t blocknum)
{
volatile sata_ncq_command_frame_t *cmd =
(sata_ncq_command_frame_t *) sata->cmd_table;
cmd->fis_type = SATA_CMD_FIS_TYPE;
cmd->c = SATA_CMD_FIS_COMMAND_INDICATOR;
cmd->command = 0x60;
cmd->tag = 0;
cmd->control = 0;
cmd->reserved1 = 0;
cmd->reserved2 = 0;
cmd->reserved3 = 0;
cmd->reserved4 = 0;
cmd->reserved5 = 0;
cmd->reserved6 = 0;
cmd->sector_count_low = 1;
cmd->sector_count_high = 0;
cmd->lba0 = blocknum & 0xff;
cmd->lba1 = (blocknum >> 8) & 0xff;
cmd->lba2 = (blocknum >> 16) & 0xff;
cmd->lba3 = (blocknum >> 24) & 0xff;
cmd->lba4 = (blocknum >> 32) & 0xff;
cmd->lba5 = (blocknum >> 40) & 0xff;
volatile ahci_cmd_prdt_t *prdt =
(ahci_cmd_prdt_t *) (&sata->cmd_table[0x20]);
prdt->data_address_low = LO(phys);
prdt->data_address_upper = HI(phys);
prdt->reserved1 = 0;
prdt->dbc = sata->block_size - 1;
prdt->reserved2 = 0;
prdt->ioc = 0;
sata->cmd_header->prdtl = 1;
sata->cmd_header->flags =
AHCI_CMDHDR_FLAGS_CLEAR_BUSY_UPON_OK |
AHCI_CMDHDR_FLAGS_5DWCMD;
sata->cmd_header->bytesprocessed = 0;
sata->port->pxsact |= 1;
sata->port->pxci |= 1;
}
static errno_t ahci_rb_fpdma(sata_dev_t *sata, uintptr_t phys, uint64_t blocknum)
{
if (sata->is_invalid_device) {
ddf_msg(LVL_ERROR,
"%s: FPDMA read from invalid device", sata->model);
return EINTR;
}
ahci_rb_fpdma_cmd(sata, phys, blocknum);
ahci_port_is_t pxis = ahci_wait_event(sata);
if ((sata->is_invalid_device) || (ahci_port_is_error(pxis))) {
ddf_msg(LVL_ERROR,
"%s: Unrecoverable error during FPDMA read", sata->model);
return EINTR;
}
return EOK;
}
static void ahci_wb_fpdma_cmd(sata_dev_t *sata, uintptr_t phys,
uint64_t blocknum)
{
volatile sata_ncq_command_frame_t *cmd =
(sata_ncq_command_frame_t *) sata->cmd_table;
cmd->fis_type = SATA_CMD_FIS_TYPE;
cmd->c = SATA_CMD_FIS_COMMAND_INDICATOR;
cmd->command = 0x61;
cmd->tag = 0;
cmd->control = 0;
cmd->reserved1 = 0;
cmd->reserved2 = 0;
cmd->reserved3 = 0;
cmd->reserved4 = 0;
cmd->reserved5 = 0;
cmd->reserved6 = 0;
cmd->sector_count_low = 1;
cmd->sector_count_high = 0;
cmd->lba0 = blocknum & 0xff;
cmd->lba1 = (blocknum >> 8) & 0xff;
cmd->lba2 = (blocknum >> 16) & 0xff;
cmd->lba3 = (blocknum >> 24) & 0xff;
cmd->lba4 = (blocknum >> 32) & 0xff;
cmd->lba5 = (blocknum >> 40) & 0xff;
volatile ahci_cmd_prdt_t *prdt =
(ahci_cmd_prdt_t *) (&sata->cmd_table[0x20]);
prdt->data_address_low = LO(phys);
prdt->data_address_upper = HI(phys);
prdt->reserved1 = 0;
prdt->dbc = sata->block_size - 1;
prdt->reserved2 = 0;
prdt->ioc = 0;
sata->cmd_header->prdtl = 1;
sata->cmd_header->flags =
AHCI_CMDHDR_FLAGS_CLEAR_BUSY_UPON_OK |
AHCI_CMDHDR_FLAGS_WRITE |
AHCI_CMDHDR_FLAGS_5DWCMD;
sata->cmd_header->bytesprocessed = 0;
sata->port->pxsact |= 1;
sata->port->pxci |= 1;
}
static errno_t ahci_wb_fpdma(sata_dev_t *sata, uintptr_t phys, uint64_t blocknum)
{
if (sata->is_invalid_device) {
ddf_msg(LVL_ERROR,
"%s: FPDMA write to invalid device", sata->model);
return EINTR;
}
ahci_wb_fpdma_cmd(sata, phys, blocknum);
ahci_port_is_t pxis = ahci_wait_event(sata);
if ((sata->is_invalid_device) || (ahci_port_is_error(pxis))) {
ddf_msg(LVL_ERROR,
"%s: Unrecoverable error during FPDMA write", sata->model);
return EINTR;
}
return EOK;
}
static irq_pio_range_t ahci_ranges[] = {
{
.base = 0,
.size = 0,
}
};
static irq_cmd_t ahci_cmds[] = {
AHCI_PORT_CMDS(0),
AHCI_PORT_CMDS(1),
AHCI_PORT_CMDS(2),
AHCI_PORT_CMDS(3),
AHCI_PORT_CMDS(4),
AHCI_PORT_CMDS(5),
AHCI_PORT_CMDS(6),
AHCI_PORT_CMDS(7),
AHCI_PORT_CMDS(8),
AHCI_PORT_CMDS(9),
AHCI_PORT_CMDS(10),
AHCI_PORT_CMDS(11),
AHCI_PORT_CMDS(12),
AHCI_PORT_CMDS(13),
AHCI_PORT_CMDS(14),
AHCI_PORT_CMDS(15),
AHCI_PORT_CMDS(16),
AHCI_PORT_CMDS(17),
AHCI_PORT_CMDS(18),
AHCI_PORT_CMDS(19),
AHCI_PORT_CMDS(20),
AHCI_PORT_CMDS(21),
AHCI_PORT_CMDS(22),
AHCI_PORT_CMDS(23),
AHCI_PORT_CMDS(24),
AHCI_PORT_CMDS(25),
AHCI_PORT_CMDS(26),
AHCI_PORT_CMDS(27),
AHCI_PORT_CMDS(28),
AHCI_PORT_CMDS(29),
AHCI_PORT_CMDS(30),
AHCI_PORT_CMDS(31)
};
static void ahci_interrupt(ipc_call_t *icall, void *arg)
{
ahci_dev_t *ahci = (ahci_dev_t *)arg;
unsigned int port = ipc_get_arg1(icall);
ahci_port_is_t pxis = ipc_get_arg2(icall);
if (port >= AHCI_MAX_PORTS)
return;
sata_dev_t *sata = (sata_dev_t *) ahci->sata_devs[port];
if (sata == NULL)
return;
if ((ahci_port_is_end_of_operation(pxis)) ||
(ahci_port_is_error(pxis))) {
fibril_mutex_lock(&sata->event_lock);
sata->event_pxis = pxis;
fibril_condvar_signal(&sata->event_condvar);
fibril_mutex_unlock(&sata->event_lock);
}
}
static sata_dev_t *ahci_sata_allocate(ahci_dev_t *ahci, volatile ahci_port_t *port)
{
size_t size = 4096;
uintptr_t phys = 0;
void *virt_fb = AS_AREA_ANY;
void *virt_cmd = AS_AREA_ANY;
void *virt_table = AS_AREA_ANY;
ddf_fun_t *fun;
fun = ddf_fun_create(ahci->dev, fun_exposed, NULL);
sata_dev_t *sata = ddf_fun_data_alloc(fun, sizeof(sata_dev_t));
if (sata == NULL)
return NULL;
sata->fun = fun;
sata->port = port;
errno_t rc = dmamem_map_anonymous(size, DMAMEM_4GiB,
AS_AREA_READ | AS_AREA_WRITE, 0, &phys, &virt_fb);
if (rc != EOK)
goto error_retfis;
memset(virt_fb, 0, size);
sata->port->pxfbu = HI(phys);
sata->port->pxfb = LO(phys);
rc = dmamem_map_anonymous(size, DMAMEM_4GiB,
AS_AREA_READ | AS_AREA_WRITE, 0, &phys, &virt_cmd);
if (rc != EOK)
goto error_cmd;
memset(virt_cmd, 0, size);
sata->port->pxclbu = HI(phys);
sata->port->pxclb = LO(phys);
sata->cmd_header = (ahci_cmdhdr_t *) virt_cmd;
rc = dmamem_map_anonymous(size, DMAMEM_4GiB,
AS_AREA_READ | AS_AREA_WRITE, 0, &phys, &virt_table);
if (rc != EOK)
goto error_table;
memset(virt_table, 0, size);
sata->cmd_header->cmdtableu = HI(phys);
sata->cmd_header->cmdtable = LO(phys);
sata->cmd_table = (uint32_t *) virt_table;
return sata;
error_table:
dmamem_unmap(virt_cmd, size);
error_cmd:
dmamem_unmap(virt_fb, size);
error_retfis:
free(sata);
return NULL;
}
static void ahci_sata_hw_start(sata_dev_t *sata)
{
ahci_port_cmd_t pxcmd;
pxcmd.u32 = sata->port->pxcmd;
pxcmd.fre = 0;
pxcmd.st = 0;
sata->port->pxcmd = pxcmd.u32;
sata->port->pxis = 0xffffffff;
sata->port->pxserr = 0xffffffff;
sata->port->pxie = 0xffffffff;
pxcmd.fre = 1;
pxcmd.st = 1;
sata->port->pxcmd = pxcmd.u32;
}
static errno_t ahci_sata_create(ahci_dev_t *ahci, ddf_dev_t *dev,
volatile ahci_port_t *port, unsigned int port_num)
{
ddf_fun_t *fun = NULL;
errno_t rc;
sata_dev_t *sata = ahci_sata_allocate(ahci, port);
if (sata == NULL)
return EINTR;
sata->ahci = ahci;
sata->port_num = port_num;
ahci->sata_devs[port_num] = sata;
fibril_mutex_initialize(&sata->lock);
fibril_mutex_initialize(&sata->event_lock);
fibril_condvar_initialize(&sata->event_condvar);
ahci_sata_hw_start(sata);
if (ahci_identify_device(sata) != EOK)
goto error;
if (ahci_set_highest_ultra_dma_mode(sata) != EOK)
goto error;
char sata_dev_name[16];
snprintf(sata_dev_name, 16, "ahci_%u", sata_devices_count);
fibril_mutex_lock(&sata_devices_count_lock);
sata_devices_count++;
fibril_mutex_unlock(&sata_devices_count_lock);
rc = ddf_fun_set_name(sata->fun, sata_dev_name);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed setting function name.");
goto error;
}
ddf_fun_set_ops(fun, &ahci_ops);
rc = ddf_fun_bind(fun);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed binding function.");
goto error;
}
return EOK;
error:
sata->is_invalid_device = true;
if (fun != NULL)
ddf_fun_destroy(fun);
return EINTR;
}
static void ahci_sata_devices_create(ahci_dev_t *ahci, ddf_dev_t *dev)
{
for (unsigned int port_num = 0; port_num < AHCI_MAX_PORTS; port_num++) {
if (!(ahci->memregs->ghc.pi & (1 << port_num)))
continue;
volatile ahci_port_t *port = ahci->memregs->ports + port_num;
ahci_port_ssts_t pxssts;
pxssts.u32 = port->pxssts;
if (pxssts.det != AHCI_PORT_SSTS_DET_ACTIVE)
continue;
ahci_sata_create(ahci, dev, port, port_num);
}
}
static ahci_dev_t *ahci_ahci_create(ddf_dev_t *dev)
{
ahci_dev_t *ahci = ddf_dev_data_alloc(dev, sizeof(ahci_dev_t));
if (!ahci)
return NULL;
ahci->parent_sess = ddf_dev_parent_sess_get(dev);
if (ahci->parent_sess == NULL)
return NULL;
ahci->dev = dev;
hw_res_list_parsed_t hw_res_parsed;
hw_res_list_parsed_init(&hw_res_parsed);
if (hw_res_get_list_parsed(ahci->parent_sess, &hw_res_parsed, 0) != EOK)
goto error_get_res_parsed;
ahci->memregs = AS_AREA_ANY;
physmem_map(RNGABS(hw_res_parsed.mem_ranges.ranges[0]),
AHCI_MEMREGS_PAGES_COUNT, AS_AREA_READ | AS_AREA_WRITE,
(void *) &ahci->memregs);
if (ahci->memregs == NULL)
goto error_map_registers;
ahci_ranges[0].base = RNGABS(hw_res_parsed.mem_ranges.ranges[0]);
ahci_ranges[0].size = sizeof(ahci_memregs_t);
for (unsigned int port = 0; port < AHCI_MAX_PORTS; port++) {
size_t base = port * 7;
ahci_cmds[base].addr =
((uint32_t *) RNGABSPTR(hw_res_parsed.mem_ranges.ranges[0])) +
AHCI_PORTS_REGISTERS_OFFSET + port * AHCI_PORT_REGISTERS_SIZE +
AHCI_PORT_IS_REGISTER_OFFSET;
ahci_cmds[base + 2].addr = ahci_cmds[base].addr;
ahci_cmds[base + 3].addr =
((uint32_t *) RNGABSPTR(hw_res_parsed.mem_ranges.ranges[0])) +
AHCI_GHC_IS_REGISTER_OFFSET;
ahci_cmds[base + 4].addr = ahci_cmds[base + 3].addr;
}
irq_code_t ct;
ct.cmdcount = sizeof(ahci_cmds) / sizeof(irq_cmd_t);
ct.cmds = ahci_cmds;
ct.rangecount = sizeof(ahci_ranges) / sizeof(irq_pio_range_t);
ct.ranges = ahci_ranges;
cap_irq_handle_t irq_cap;
errno_t rc = register_interrupt_handler(dev,
hw_res_parsed.irqs.irqs[0], ahci_interrupt, (void *)ahci, &ct,
&irq_cap);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed registering interrupt handler.");
goto error_register_interrupt_handler;
}
rc = hw_res_enable_interrupt(ahci->parent_sess,
hw_res_parsed.irqs.irqs[0]);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed enable interupt.");
goto error_enable_interrupt;
}
hw_res_list_parsed_clean(&hw_res_parsed);
return ahci;
error_enable_interrupt:
unregister_interrupt_handler(dev, irq_cap);
error_register_interrupt_handler:
error_map_registers:
hw_res_list_parsed_clean(&hw_res_parsed);
error_get_res_parsed:
free(ahci);
return NULL;
}
static void ahci_ahci_hw_start(ahci_dev_t *ahci)
{
ahci_ghc_ccc_ctl_t ccc;
ccc.u32 = ahci->memregs->ghc.ccc_ctl;
ccc.en = 0;
ahci->memregs->ghc.ccc_ctl = ccc.u32;
pci_config_space_write_8(ahci->parent_sess, AHCI_PCI_MLT, 32);
ahci_pcireg_cmd_t cmd;
pci_config_space_read_16(ahci->parent_sess, AHCI_PCI_CMD, &cmd.u16);
cmd.id = 0;
cmd.bme = 1;
pci_config_space_write_16(ahci->parent_sess, AHCI_PCI_CMD, cmd.u16);
ahci->memregs->ghc.ghc = AHCI_GHC_GHC_AE | AHCI_GHC_GHC_IE;
}
static errno_t ahci_dev_add(ddf_dev_t *dev)
{
ahci_dev_t *ahci = ahci_ahci_create(dev);
if (ahci == NULL)
goto error;
ahci_ahci_hw_start(ahci);
ahci_sata_devices_create(ahci, dev);
return EOK;
error:
return EINTR;
}
static void ahci_get_model_name(uint16_t *src, char *dst)
{
uint8_t model[40];
memset(model, 0, 40);
for (unsigned int i = 0; i < 20; i++) {
uint16_t w = src[i];
model[2 * i] = w >> 8;
model[2 * i + 1] = w & 0x00ff;
}
uint32_t len = 40;
while ((len > 0) && (model[len - 1] == 0x20))
len--;
size_t pos = 0;
for (unsigned int i = 0; i < len; i++) {
uint8_t c = model[i];
if (c >= 0x80)
c = '?';
chr_encode(c, dst, &pos, 40);
}
dst[pos] = '\0';
}
int main(int argc, char *argv[])
{
printf("%s: HelenOS AHCI device driver\n", NAME);
ddf_log_init(NAME);
fibril_mutex_initialize(&sata_devices_count_lock);
return ddf_driver_main(&ahci_driver);
}
HelenOS homepage, sources at GitHub