kexts: ahci: implement identification of ATAPI devices
This commit is contained in:
@@ -26,9 +26,15 @@
|
||||
#define HBA_PxCMD_CR 0x8000
|
||||
|
||||
#define HBA_PxIS_TFES 0x40000000
|
||||
#define HBA_PxIS_DPS 0x20u
|
||||
|
||||
#define ATA_CMD_READ_DMA_EX 0x25u
|
||||
#define ATA_CMD_IDENTIFY_DEVICE 0xECu
|
||||
#define ATA_CMD_PACKET 0xA0u
|
||||
#define ATA_CMD_IDENTIFY_PACKET_DEVICE 0xA1u
|
||||
|
||||
#define SCSI_CMD_READ 0xA8u
|
||||
#define SCSI_CMD_READ_CAPACITY 0x25u
|
||||
|
||||
enum fis_type {
|
||||
FIS_TYPE_REG_H2D = 0x27u,
|
||||
@@ -193,10 +199,37 @@ struct hba_prdt_entry {
|
||||
uint32_t i : 1;
|
||||
} __packed;
|
||||
|
||||
struct scsi_command {
|
||||
uint8_t cmd;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t rsvd[15];
|
||||
} read_capacity;
|
||||
|
||||
struct {
|
||||
uint8_t rdprotect : 3;
|
||||
uint8_t dpo : 1;
|
||||
uint8_t fua : 1;
|
||||
uint8_t rarc : 1;
|
||||
uint8_t obsolete : 2;
|
||||
uint32_t lba;
|
||||
uint32_t count;
|
||||
} read;
|
||||
} __packed;
|
||||
} __packed;
|
||||
|
||||
/* struct scsi_command /must/ be exactly 16 bytes long, so that it fits properly
|
||||
within struct hba_cmd_table */
|
||||
_Static_assert(sizeof(struct scsi_command) == 16, "ahci: scsi_command must be 16 bytes");
|
||||
_Static_assert(sizeof(struct fis_dma_setup) == 28, "ahci: fis_dma_setup must be 28 bytes");
|
||||
_Static_assert(sizeof(struct fis_pio_setup) == 20, "ahci: fis_pio_set must be 20 bytes");
|
||||
_Static_assert(sizeof(struct fis_reg_d2h) == 20, "ahci: fis_reg_d2h must be 20 bytes");
|
||||
|
||||
struct hba_cmd_table {
|
||||
uint8_t cfis[64];
|
||||
|
||||
uint8_t acmd[16];
|
||||
struct scsi_command acmd;
|
||||
|
||||
uint8_t rsv[48];
|
||||
|
||||
@@ -245,28 +278,40 @@ struct hba_config {
|
||||
struct hba_port ports[32];
|
||||
} __packed;
|
||||
|
||||
struct ata_identify_device_data {
|
||||
uint16_t g_config;
|
||||
uint8_t rsv0[2];
|
||||
uint16_t s_config;
|
||||
uint8_t rsv1[14];
|
||||
uint8_t serial_number[20];
|
||||
uint8_t rsv2[6];
|
||||
uint32_t firmware;
|
||||
char model[40];
|
||||
} __packed;
|
||||
|
||||
struct identify_device_data {
|
||||
struct {
|
||||
uint16_t reserved_1 : 1;
|
||||
uint16_t retired_3 : 1;
|
||||
uint16_t response_incomplete : 1;
|
||||
uint16_t retired_2 : 3;
|
||||
uint16_t fixed_device : 1;
|
||||
uint16_t removable_media : 1;
|
||||
uint16_t retired_1 : 7;
|
||||
uint16_t device_type : 1;
|
||||
} general_config;
|
||||
union {
|
||||
struct {
|
||||
uint16_t reserved_1 : 1;
|
||||
uint16_t retired_3 : 1;
|
||||
uint16_t response_incomplete : 1;
|
||||
uint16_t retired_2 : 3;
|
||||
uint16_t fixed_device : 1;
|
||||
uint16_t removable_media : 1;
|
||||
uint16_t retired_1 : 7;
|
||||
uint16_t device_type : 1;
|
||||
} general_config;
|
||||
|
||||
struct {
|
||||
/*
|
||||
uint16_t is_atapi : 2;
|
||||
uint16_t reserved_1: 1;
|
||||
uint16_t command_set : 5;
|
||||
uint16_t obsolete_1: 1;
|
||||
uint16_t packet_drq_time : 2;
|
||||
uint16_t reserved_2 : 2;
|
||||
uint16_t identify_incomplete : 1;
|
||||
uint16_t packet_size : 2;
|
||||
*/
|
||||
uint16_t packet_size : 2;
|
||||
uint16_t identify_incomplete : 1;
|
||||
uint16_t reserved_2 : 2;
|
||||
uint16_t packet_drq_time : 2;
|
||||
uint16_t obsolete_1: 1;
|
||||
uint16_t command_set : 5;
|
||||
uint16_t reserved_1: 1;
|
||||
uint16_t is_atapi : 2;
|
||||
} atapi_general_config;
|
||||
};
|
||||
uint16_t num_cylinders;
|
||||
uint16_t specific_configuration;
|
||||
uint16_t num_heads;
|
||||
@@ -628,4 +673,9 @@ struct identify_device_data {
|
||||
uint16_t check_sum : 8;
|
||||
} __packed;
|
||||
|
||||
struct scsi_read_capacity_data {
|
||||
uint32_t last_sector;
|
||||
uint32_t block_size;
|
||||
} __packed;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,21 +3,37 @@
|
||||
#include <socks/device.h>
|
||||
#include <socks/kext.h>
|
||||
#include <socks/pci.h>
|
||||
#include <socks/util.h>
|
||||
#include <socks/machine/init.h>
|
||||
#include <socks/libc/stdio.h>
|
||||
#include <socks/libc/ctype.h>
|
||||
#include <arch/irq.h>
|
||||
#include "ahci.h"
|
||||
|
||||
struct rfis_data {
|
||||
struct fis_dma_setup dma_setup;
|
||||
char pad0[4];
|
||||
struct fis_pio_setup pio_setup;
|
||||
char pad1[12];
|
||||
struct fis_reg_d2h reg_d2h;
|
||||
} __packed;
|
||||
|
||||
struct ahci_device {
|
||||
int port_no;
|
||||
int type;
|
||||
volatile struct hba_port *port;
|
||||
|
||||
struct hba_cmd_header cmd_header[32];
|
||||
char fis_data[256];
|
||||
union {
|
||||
char rfis_data[256];
|
||||
struct rfis_data rfis;
|
||||
};
|
||||
};
|
||||
|
||||
_Static_assert(offsetof(struct rfis_data, dma_setup) == 0, "offsetof dma_setup in rfis_data must be 0x00");
|
||||
_Static_assert(offsetof(struct rfis_data, pio_setup) == 0x20, "offsetof dma_setup in rfis_data must be 0x20");
|
||||
_Static_assert(offsetof(struct rfis_data, reg_d2h) == 0x40, "offsetof dma_setup in rfis_data must be 0x40");
|
||||
|
||||
/************************** STOLEN **********************/
|
||||
|
||||
#define ATA_DEV_BUSY 0x80
|
||||
@@ -76,6 +92,7 @@ static kern_status_t send_command(struct ahci_device *dev, unsigned int cmd, str
|
||||
struct hba_cmd_header *cmdheader = &dev->cmd_header[slot];
|
||||
cmdheader->cfl = sizeof(struct fis_reg_h2d) / sizeof(uint32_t);
|
||||
cmdheader->w = 0;
|
||||
cmdheader->a = 0;
|
||||
cmdheader->prdtl = nvec;
|
||||
|
||||
struct hba_cmd_table *cmdtbl = create_cmd_table(vec, nvec);
|
||||
@@ -114,6 +131,82 @@ static kern_status_t send_command(struct ahci_device *dev, unsigned int cmd, str
|
||||
return KERN_IO_ERROR;
|
||||
}
|
||||
}
|
||||
free_cmd_table(cmdtbl);
|
||||
|
||||
if (port->is & HBA_PxIS_TFES) {
|
||||
printk("Read disk error 2");
|
||||
return KERN_IO_ERROR;
|
||||
}
|
||||
|
||||
return KERN_OK;
|
||||
}
|
||||
|
||||
static kern_status_t send_atapi_command(struct ahci_device *dev, struct scsi_command *cmd, struct iovec *vec, size_t nvec)
|
||||
{
|
||||
if (nvec == 0) {
|
||||
return KERN_OK;
|
||||
}
|
||||
|
||||
volatile struct hba_port *port = dev->port;
|
||||
port->is = (uint32_t) -1;
|
||||
int spin = 0;
|
||||
int slot = find_cmdslot(port);
|
||||
if (slot == -1) {
|
||||
return KERN_BUSY;
|
||||
}
|
||||
|
||||
struct hba_cmd_header *cmdheader = &dev->cmd_header[slot];
|
||||
memset(cmdheader, 0x0, sizeof *cmdheader);
|
||||
cmdheader->cfl = sizeof(struct fis_reg_h2d) / sizeof(uint32_t);
|
||||
cmdheader->w = 0;
|
||||
cmdheader->a = 1;
|
||||
cmdheader->prdtl = nvec;
|
||||
|
||||
struct hba_cmd_table *cmdtbl = create_cmd_table(vec, nvec);
|
||||
phys_addr_t cmdtbl_phys = vm_virt_to_phys(cmdtbl);
|
||||
cmdheader->ctba = cmdtbl_phys & 0xFFFFFFFF;
|
||||
cmdheader->ctbau = (cmdtbl_phys >> 32) & 0xFFFFFFFF;
|
||||
memcpy(&cmdtbl->acmd, cmd, sizeof *cmd);
|
||||
|
||||
struct fis_reg_h2d *cmdfis = (struct fis_reg_h2d *)(&cmdtbl->cfis);
|
||||
memset(cmdfis, 0x0, sizeof *cmdfis);
|
||||
|
||||
cmdfis->fis_type = FIS_TYPE_REG_H2D;
|
||||
cmdfis->c = 1;
|
||||
cmdfis->rsv0 = 0;
|
||||
cmdfis->feature_l = 1;
|
||||
cmdfis->command = ATA_CMD_PACKET;
|
||||
|
||||
while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) {
|
||||
spin++;
|
||||
}
|
||||
|
||||
if (spin == 1000000) {
|
||||
printk("Port is hung");
|
||||
free_cmd_table(cmdtbl);
|
||||
return KERN_DEVICE_STUCK;
|
||||
}
|
||||
|
||||
port->ci = 1 << slot;
|
||||
|
||||
// Wait for completion
|
||||
while (1)
|
||||
{
|
||||
if ((port->ci & (1 << slot)) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (port->is & HBA_PxIS_TFES) {
|
||||
printk("Read disk error 1");
|
||||
free_cmd_table(cmdtbl);
|
||||
return KERN_IO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
volatile int _i = 0;
|
||||
while ((port->is & 0x1) != 0x1) {
|
||||
_i++;
|
||||
}
|
||||
|
||||
free_cmd_table(cmdtbl);
|
||||
|
||||
@@ -125,74 +218,36 @@ static kern_status_t send_command(struct ahci_device *dev, unsigned int cmd, str
|
||||
return KERN_OK;
|
||||
}
|
||||
|
||||
static kern_status_t identify_device(struct ahci_device *dev, struct identify_device_data *out)
|
||||
static kern_status_t identify_ata_device(struct ahci_device *dev, struct identify_device_data *out)
|
||||
{
|
||||
/*
|
||||
port->is = (uint32_t) -1; // Clear pending interrupt bits
|
||||
int spin = 0; // Spin lock timeout counter
|
||||
int slot = find_cmdslot(port);
|
||||
if (slot == -1)
|
||||
return false;
|
||||
|
||||
struct hba_cmd_header *cmdheader = vm_phys_to_virt(port->clb);
|
||||
cmdheader += slot;
|
||||
cmdheader->cfl = sizeof(struct fis_reg_h2d)/sizeof(uint32_t); // Command FIS size
|
||||
cmdheader->w = 0; // Read from device
|
||||
cmdheader->prdtl = 1;
|
||||
|
||||
struct hba_cmd_table *cmdtbl = vm_phys_to_virt(cmdheader->ctba);
|
||||
memset(cmdtbl, 0, sizeof(struct hba_cmd_table) +
|
||||
(cmdheader->prdtl-1)*sizeof(struct hba_prdt_entry));
|
||||
|
||||
cmdtbl->prdt_entry[0].dba = (uint32_t)vm_virt_to_phys(out);
|
||||
cmdtbl->prdt_entry[0].dbc = sizeof *out - 1;
|
||||
cmdtbl->prdt_entry[0].i = 1;
|
||||
|
||||
// Setup command
|
||||
struct fis_reg_h2d *cmdfis = (struct fis_reg_h2d*)(&cmdtbl->cfis);
|
||||
|
||||
cmdfis->fis_type = FIS_TYPE_REG_H2D;
|
||||
cmdfis->c = 1; // Command
|
||||
cmdfis->command = ATA_CMD_IDENTIFY_DEVICE;
|
||||
|
||||
// The below loop waits until the port is no longer busy before issuing a new command
|
||||
while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000)
|
||||
{
|
||||
spin++;
|
||||
}
|
||||
if (spin == 1000000)
|
||||
{
|
||||
printk("Port is hung");
|
||||
return -1;
|
||||
struct iovec vec = { .io_buf = out, .io_len = sizeof *out };
|
||||
kern_status_t status = send_command(dev, ATA_CMD_IDENTIFY_DEVICE, &vec, 1);
|
||||
if (status != KERN_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
port->ci = 1<<slot; // Issue command
|
||||
out->model_number[39] = 0;
|
||||
for (int i = 0; i < 40; i += 2) {
|
||||
unsigned char c = out->model_number[i];
|
||||
out->model_number[i] = out->model_number[i + 1];
|
||||
out->model_number[i + 1] = c;
|
||||
}
|
||||
|
||||
// Wait for completion
|
||||
while (1)
|
||||
{
|
||||
// In some longer duration reads, it may be helpful to spin on the DPS bit
|
||||
// in the PxIS port field as well (1 << 5)
|
||||
if ((port->ci & (1<<slot)) == 0)
|
||||
for (int i = sizeof out->model_number - 1; i >= 0; i--) {
|
||||
if (isspace(out->model_number[i]) || out->model_number[i] == 0) {
|
||||
out->model_number[i] = 0;
|
||||
} else {
|
||||
break;
|
||||
if (port->is & HBA_PxIS_TFES) // Task file error
|
||||
{
|
||||
printk("Read disk error 1");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check again
|
||||
if (port->is & HBA_PxIS_TFES)
|
||||
{
|
||||
printk("Read disk error 2");
|
||||
return -1;
|
||||
}
|
||||
return KERN_OK;
|
||||
}
|
||||
|
||||
return 0;
|
||||
*/
|
||||
static kern_status_t identify_atapi_device(struct ahci_device *dev, struct identify_device_data *out)
|
||||
{
|
||||
struct iovec vec = { .io_buf = out, .io_len = sizeof *out };
|
||||
kern_status_t status = send_command(dev, ATA_CMD_IDENTIFY_DEVICE, &vec, 1);
|
||||
kern_status_t status = send_command(dev, ATA_CMD_IDENTIFY_PACKET_DEVICE, &vec, 1);
|
||||
if (status != KERN_OK) {
|
||||
return status;
|
||||
}
|
||||
@@ -252,43 +307,21 @@ void port_rebase(struct ahci_device *dev)
|
||||
{
|
||||
volatile struct hba_port *port = dev->port;
|
||||
|
||||
stop_cmd(port); // Stop command engine
|
||||
stop_cmd(port);
|
||||
|
||||
struct hba_cmd_header *cmdheader = dev->cmd_header;
|
||||
|
||||
// Command list offset: 1K*portno
|
||||
// Command list entry size = 32
|
||||
// Command list entry maxim count = 32
|
||||
// Command list maxim size = 32*32 = 1K per port
|
||||
phys_addr_t cmdheader_phys = vm_virt_to_phys(cmdheader);
|
||||
port->clb = cmdheader_phys & 0xFFFFFFFF;
|
||||
port->clbu = (cmdheader_phys >> 32) & 0xFFFFFFFF;
|
||||
memset(cmdheader, 0, sizeof *cmdheader);
|
||||
|
||||
// FIS offset: 32K+256*portno
|
||||
// FIS entry size = 256 bytes per port
|
||||
phys_addr_t fis_phys = vm_virt_to_phys(dev->fis_data);
|
||||
phys_addr_t fis_phys = vm_virt_to_phys(dev->rfis_data);
|
||||
port->fb = fis_phys & 0xFFFFFFFF;
|
||||
port->fbu = (fis_phys >> 32) & 0xFFFFFFFF;
|
||||
memset(dev->fis_data, 0, 256);
|
||||
memset(dev->rfis_data, 0, 256);
|
||||
|
||||
/*
|
||||
// Command table offset: 40K + 8K*portno
|
||||
// Command table size = 256*32 = 8K per port
|
||||
for (int i=0; i<32; i++)
|
||||
{
|
||||
cmdheader[i].prdtl = 8; // 8 prdt entries per command table
|
||||
// 256 bytes per command table, 64+16+48+16*8
|
||||
// Command table offset: 40K + 8K*portno + cmdheader_index*256
|
||||
cmdheader[i].ctba = phys + offset;
|
||||
cmdheader[i].ctbau = 0;
|
||||
memset(virt + offset, 0, 256);
|
||||
|
||||
offset += 256;
|
||||
}
|
||||
*/
|
||||
|
||||
start_cmd(port); // Start command engine
|
||||
start_cmd(port);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -452,7 +485,7 @@ void probe_ports(struct driver *driver, struct device *controller, volatile stru
|
||||
|
||||
if (dt == AHCI_DEV_SATA) {
|
||||
struct identify_device_data identity_data = {0};
|
||||
kern_status_t status = identify_device(ahci_dev, &identity_data);
|
||||
kern_status_t status = identify_ata_device(ahci_dev, &identity_data);
|
||||
if (status != KERN_OK) {
|
||||
kfree(ahci_dev);
|
||||
device_deref(bdev_base);
|
||||
@@ -471,6 +504,31 @@ void probe_ports(struct driver *driver, struct device *controller, volatile stru
|
||||
bdev->sector_size = 512;
|
||||
bdev->capacity = identity_data.user_addressable_sectors;
|
||||
|
||||
snprintf(bdev_base->dev_model_name, sizeof bdev_base->dev_model_name, "%s", identity_data.model_number);
|
||||
} else if (dt == AHCI_DEV_SATAPI) {
|
||||
struct identify_device_data identity_data = {0};
|
||||
kern_status_t status = identify_atapi_device(ahci_dev, &identity_data);
|
||||
if (status != KERN_OK) {
|
||||
kfree(ahci_dev);
|
||||
device_deref(bdev_base);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct scsi_command cmd = {0};
|
||||
cmd.cmd = SCSI_CMD_READ_CAPACITY;
|
||||
|
||||
struct scsi_read_capacity_data cmd_result = {0};
|
||||
struct iovec vec = { .io_buf = &cmd_result, .io_len = sizeof cmd_result };
|
||||
|
||||
status = send_atapi_command(ahci_dev, &cmd, &vec, 1);
|
||||
if (status != KERN_OK) {
|
||||
printk("ahci: failed to contact ATAPI device: %s", kern_status_string(status));
|
||||
continue;
|
||||
}
|
||||
|
||||
bdev->sector_size = big_to_host_u32(cmd_result.block_size);
|
||||
bdev->capacity = big_to_host_u32(cmd_result.last_sector) + 1;
|
||||
|
||||
snprintf(bdev_base->dev_model_name, sizeof bdev_base->dev_model_name, "%s", identity_data.model_number);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user