// Copyright 2016 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sata.h" #define sata_devinfo_u32(base, offs) (((uint32_t)(base)[(offs) + 1] << 16) | ((uint32_t)(base)[(offs)])) #define sata_devinfo_u64(base, offs) (((uint64_t)(base)[(offs) + 3] << 48) | ((uint64_t)(base)[(offs) + 2] << 32) | ((uint64_t)(base)[(offs) + 1] << 16) | ((uint32_t)(base)[(offs)])) #define SATA_FLAG_DMA (1 << 0) #define SATA_FLAG_LBA48 (1 << 1) typedef struct sata_device { zx_device_t* zxdev; ahci_device_t* controller; block_info_t info; int port; int flags; int max_cmd; // inclusive } sata_device_t; static void sata_device_identify_complete(block_op_t* op, zx_status_t status) { sata_txn_t* txn = containerof(op, sata_txn_t, bop); txn->status = status; sync_completion_signal((sync_completion_t*)op->cookie); } #define QEMU_MODEL_ID "EQUMH RADDSI K" // "QEMU HARDDISK" #define QEMU_SG_MAX 1024 // Linux kernel limit static bool model_id_is_qemu(char* model_id) { return !memcmp(model_id, QEMU_MODEL_ID, sizeof(QEMU_MODEL_ID)-1); } static zx_status_t sata_device_identify(sata_device_t* dev, ahci_device_t* controller, const char* name) { // Set default devinfo sata_devinfo_t di = { .block_size = 512, .max_cmd = 1, }; ahci_set_devinfo(controller, dev->port, &di); // send IDENTIFY DEVICE zx_handle_t vmo; zx_status_t status = zx_vmo_create(512, 0, &vmo); if (status != ZX_OK) { zxlogf(TRACE, "sata: error %d allocating vmo\n", status); return status; } sync_completion_t completion = SYNC_COMPLETION_INIT; sata_txn_t txn = { .bop = { .rw.vmo = vmo, .rw.length = 1, .rw.offset_dev = 0, .rw.offset_vmo = 0, .rw.pages = NULL, .completion_cb = sata_device_identify_complete, .cookie = &completion, }, .cmd = SATA_CMD_IDENTIFY_DEVICE, .device = 0, }; ahci_queue(controller, dev->port, &txn); sync_completion_wait(&completion, ZX_TIME_INFINITE); if (txn.status != ZX_OK) { zxlogf(ERROR, "%s: error %d in device identify\n", name, txn.status); return txn.status; } // parse results int flags = 0; uint16_t devinfo[512 / sizeof(uint16_t)]; status = zx_vmo_read(vmo, devinfo, 0, sizeof(devinfo)); if (status != ZX_OK) { zxlogf(ERROR, "sata: error %d in vmo_read\n", status); return ZX_ERR_INTERNAL; } zx_handle_close(vmo); char str[41]; // model id is 40 chars zxlogf(INFO, "%s: dev info\n", name); snprintf(str, SATA_DEVINFO_SERIAL_LEN + 1, "%s", (char*)(devinfo + SATA_DEVINFO_SERIAL)); zxlogf(INFO, " serial=%s\n", str); snprintf(str, SATA_DEVINFO_FW_REV_LEN + 1, "%s", (char*)(devinfo + SATA_DEVINFO_FW_REV)); zxlogf(INFO, " firmware rev=%s\n", str); snprintf(str, SATA_DEVINFO_MODEL_ID_LEN + 1, "%s", (char*)(devinfo + SATA_DEVINFO_MODEL_ID)); zxlogf(INFO, " model id=%s\n", str); bool is_qemu = model_id_is_qemu((char*)(devinfo + SATA_DEVINFO_MODEL_ID)); uint16_t major = *(devinfo + SATA_DEVINFO_MAJOR_VERS); zxlogf(INFO, " major=0x%x ", major); switch (32 - __builtin_clz(major) - 1) { case 10: zxlogf(INFO, "ACS3"); break; case 9: zxlogf(INFO, "ACS2"); break; case 8: zxlogf(INFO, "ATA8-ACS"); break; case 7: case 6: case 5: zxlogf(INFO, "ATA/ATAPI"); break; default: zxlogf(INFO, "Obsolete"); break; } uint16_t cap = *(devinfo + SATA_DEVINFO_CAP); if (cap & (1 << 8)) { zxlogf(INFO, " DMA"); flags |= SATA_FLAG_DMA; } else { zxlogf(INFO, " PIO"); } dev->max_cmd = *(devinfo + SATA_DEVINFO_QUEUE_DEPTH); zxlogf(INFO, " %d commands\n", dev->max_cmd + 1); uint32_t block_size = 512; // default uint64_t block_count = 0; if (cap & (1 << 9)) { if ((*(devinfo + SATA_DEVINFO_SECTOR_SIZE) & 0xd000) == 0x5000) { block_size = 2 * sata_devinfo_u32(devinfo, SATA_DEVINFO_LOGICAL_SECTOR_SIZE); } if (*(devinfo + SATA_DEVINFO_CMD_SET_2) & (1 << 10)) { flags |= SATA_FLAG_LBA48; block_count = sata_devinfo_u64(devinfo, SATA_DEVINFO_LBA_CAPACITY_2); zxlogf(INFO, " LBA48"); } else { block_count = sata_devinfo_u32(devinfo, SATA_DEVINFO_LBA_CAPACITY); zxlogf(INFO, " LBA"); } zxlogf(INFO, " %" PRIu64 " sectors, sector size=%u\n", block_count, block_size); } else { zxlogf(INFO, " CHS unsupported!\n"); } dev->flags = flags; memset(&dev->info, 0, sizeof(dev->info)); dev->info.block_size = block_size; dev->info.block_count = block_count; uint32_t max_sg_size = SATA_MAX_BLOCK_COUNT * block_size; // SATA cmd limit if (is_qemu) { max_sg_size = MIN(max_sg_size, QEMU_SG_MAX * block_size); } dev->info.max_transfer_size = MIN(AHCI_MAX_BYTES, max_sg_size); // set devinfo on controller di.block_size = block_size, di.max_cmd = dev->max_cmd, ahci_set_devinfo(controller, dev->port, &di); return ZX_OK; } // implement device protocol: static zx_protocol_device_t sata_device_proto; static zx_status_t sata_ioctl(void* ctx, uint32_t op, const void* cmd, size_t cmdlen, void* reply, size_t max, size_t* out_actual) { sata_device_t* device = ctx; switch (op) { case IOCTL_BLOCK_GET_INFO: { block_info_t* info = reply; if (max < sizeof(*info)) return ZX_ERR_BUFFER_TOO_SMALL; memcpy(info, &device->info, sizeof(*info)); *out_actual = sizeof(*info); return ZX_OK; } case IOCTL_DEVICE_SYNC: { zxlogf(TRACE, "sata: IOCTL_DEVICE_SYNC\n"); return ZX_OK; } default: return ZX_ERR_NOT_SUPPORTED; } } static zx_off_t sata_getsize(void* ctx) { sata_device_t* device = ctx; return device->info.block_count * device->info.block_size; } static void sata_release(void* ctx) { sata_device_t* device = ctx; free(device); } static zx_protocol_device_t sata_device_proto = { .version = DEVICE_OPS_VERSION, .ioctl = sata_ioctl, .get_size = sata_getsize, .release = sata_release, }; static void sata_query(void* ctx, block_info_t* info_out, size_t* block_op_size_out) { sata_device_t* dev = ctx; memcpy(info_out, &dev->info, sizeof(*info_out)); *block_op_size_out = sizeof(sata_txn_t); } static void sata_queue(void* ctx, block_op_t* bop) { sata_device_t* dev = ctx; sata_txn_t* txn = containerof(bop, sata_txn_t, bop); switch (BLOCK_OP(bop->command)) { case BLOCK_OP_READ: case BLOCK_OP_WRITE: // complete empty transactions immediately if (bop->rw.length == 0) { block_complete(bop, ZX_ERR_INVALID_ARGS); return; } // transaction must fit within device if ((bop->rw.offset_dev >= dev->info.block_count) || ((dev->info.block_count - bop->rw.offset_dev) < bop->rw.length)) { block_complete(bop, ZX_ERR_OUT_OF_RANGE); return; } txn->cmd = (BLOCK_OP(bop->command) == BLOCK_OP_READ) ? SATA_CMD_READ_DMA_EXT : SATA_CMD_WRITE_DMA_EXT; txn->device = 0x40; zxlogf(TRACE, "sata: queue op 0x%x txn %p\n", bop->command, txn); break; case BLOCK_OP_FLUSH: zxlogf(TRACE, "sata: queue FLUSH txn %p\n", txn); break; default: block_complete(bop, ZX_ERR_NOT_SUPPORTED); return; } ahci_queue(dev->controller, dev->port, txn); } static block_protocol_ops_t sata_block_proto = { .query = sata_query, .queue = sata_queue, }; zx_status_t sata_bind(ahci_device_t* controller, zx_device_t* parent, int port) { // initialize the device sata_device_t* device = calloc(1, sizeof(sata_device_t)); if (!device) { zxlogf(ERROR, "sata: out of memory\n"); return ZX_ERR_NO_MEMORY; } device->controller = controller; device->port = port; char name[8]; snprintf(name, sizeof(name), "sata%d", port); // send device identify zx_status_t status = sata_device_identify(device, controller, name); if (status < 0) { free(device); return status; } // add the device device_add_args_t args = { .version = DEVICE_ADD_ARGS_VERSION, .name = name, .ctx = device, .ops = &sata_device_proto, .proto_id = ZX_PROTOCOL_BLOCK_IMPL, .proto_ops = &sata_block_proto, }; status = device_add(parent, &args, &device->zxdev); if (status < 0) { free(device); return status; } return ZX_OK; }