blob: 72834663721c1e05360ff12c5f73f3834f46744e [file] [log] [blame]
// 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 "sata.h"
#include <byteswap.h>
#include <fcntl.h>
#include <inttypes.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/sync/completion.h>
#include <lib/zx/vmo.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <zircon/types.h>
#include <fbl/alloc_checker.h>
#include "controller.h"
#define SATA_FLAG_DMA (1 << 0)
#define SATA_FLAG_LBA48 (1 << 1)
namespace ahci {
struct sata_device_t {
zx_device_t* zxdev = nullptr;
Controller* controller = nullptr;
block_info_t info{};
uint32_t port = 0;
uint32_t flags = 0;
uint32_t max_cmd = 0; // inclusive
};
// Strings are byte-flipped in pairs.
void string_fix(uint16_t* buf, size_t size) {
for (size_t i = 0; i < (size / 2); i++) {
buf[i] = bswap_16(buf[i]);
}
}
static void sata_device_identify_complete(void* cookie, zx_status_t status, block_op_t* op) {
sata_txn_t* txn = containerof(op, sata_txn_t, bop);
txn->status = status;
sync_completion_signal(static_cast<sync_completion_t*>(cookie));
}
#define QEMU_MODEL_ID "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, Controller* controller,
const char* name) {
// Set default devinfo
sata_devinfo_t di;
di.block_size = 512;
di.max_cmd = 1;
controller->SetDevInfo(dev->port, &di);
// send IDENTIFY DEVICE
zx::vmo vmo;
zx_status_t status = zx::vmo::create(512, 0, &vmo);
if (status != ZX_OK) {
zxlogf(DEBUG, "sata: error %d allocating vmo", status);
return status;
}
sync_completion_t completion;
sata_txn_t txn = {};
txn.bop.rw.vmo = vmo.get();
txn.bop.rw.length = 1;
txn.bop.rw.offset_dev = 0;
txn.bop.rw.offset_vmo = 0;
txn.cmd = SATA_CMD_IDENTIFY_DEVICE;
txn.device = 0;
txn.completion_cb = sata_device_identify_complete;
txn.cookie = &completion;
controller->Queue(dev->port, &txn);
sync_completion_wait(&completion, ZX_TIME_INFINITE);
if (txn.status != ZX_OK) {
zxlogf(ERROR, "%s: error %d in device identify", name, txn.status);
return txn.status;
}
// parse results
int flags = 0;
sata_devinfo_response_t devinfo;
status = vmo.read(&devinfo, 0, sizeof(devinfo));
if (status != ZX_OK) {
zxlogf(ERROR, "sata: error %d in vmo_read", status);
return ZX_ERR_INTERNAL;
}
vmo.reset();
// Strings are 16-bit byte-flipped. Fix in place.
// Strings are NOT null-terminated.
string_fix(devinfo.serial.word, sizeof(devinfo.serial.word));
string_fix(devinfo.firmware_rev.word, sizeof(devinfo.firmware_rev.word));
string_fix(devinfo.model_id.word, sizeof(devinfo.model_id.word));
zxlogf(INFO, "%s: dev info", name);
zxlogf(INFO, " serial=%.*s", SATA_DEVINFO_SERIAL_LEN, devinfo.serial.string);
zxlogf(INFO, " firmware rev=%.*s", SATA_DEVINFO_FW_REV_LEN, devinfo.firmware_rev.string);
zxlogf(INFO, " model id=%.*s", SATA_DEVINFO_MODEL_ID_LEN, devinfo.model_id.string);
bool is_qemu = model_id_is_qemu(devinfo.model_id.string);
uint16_t major = devinfo.major_version;
zxlogf(INFO, " major=0x%x ", major);
switch (32 - __builtin_clz(major) - 1) {
case 11:
zxlogf(INFO, "ACS4");
break;
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.capabilities_1;
if (cap & (1 << 8)) {
zxlogf(INFO, " DMA");
flags |= SATA_FLAG_DMA;
} else {
zxlogf(INFO, " PIO");
}
dev->max_cmd = devinfo.queue_depth;
zxlogf(INFO, " %u commands", dev->max_cmd + 1);
uint32_t block_size = 512; // default
uint64_t block_count = 0;
if (cap & (1 << 9)) {
if ((devinfo.sector_size & 0xd000) == 0x5000) {
block_size = 2 * devinfo.logical_sector_size;
}
if (devinfo.command_set1_1 & (1 << 10)) {
flags |= SATA_FLAG_LBA48;
block_count = devinfo.lba_capacity2;
zxlogf(INFO, " LBA48");
} else {
block_count = devinfo.lba_capacity;
zxlogf(INFO, " LBA");
}
zxlogf(INFO, " %" PRIu64 " sectors, sector size=%u", block_count, block_size);
} else {
zxlogf(INFO, " CHS unsupported!");
}
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,
controller->SetDevInfo(dev->port, &di);
return ZX_OK;
}
// implement device protocol:
static zx_off_t sata_getsize(void* ctx) {
sata_device_t* device = static_cast<sata_device_t*>(ctx);
return device->info.block_count * device->info.block_size;
}
static void sata_release(void* ctx) {
sata_device_t* device = static_cast<sata_device_t*>(ctx);
delete device;
}
static zx_protocol_device_t sata_device_proto = []() {
zx_protocol_device_t device = {};
device.version = DEVICE_OPS_VERSION;
device.get_size = sata_getsize;
device.release = sata_release;
return device;
}();
static void sata_query(void* ctx, block_info_t* info_out, size_t* block_op_size_out) {
sata_device_t* dev = static_cast<sata_device_t*>(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, block_impl_queue_callback completion_cb,
void* cookie) {
sata_device_t* dev = static_cast<sata_device_t*>(ctx);
sata_txn_t* txn = containerof(bop, sata_txn_t, bop);
txn->completion_cb = completion_cb;
txn->cookie = cookie;
switch (BLOCK_OP(bop->command)) {
case BLOCK_OP_READ:
case BLOCK_OP_WRITE:
// complete empty transactions immediately
if (bop->rw.length == 0) {
block_complete(txn, 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(txn, 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(DEBUG, "sata: queue op 0x%x txn %p", bop->command, txn);
break;
case BLOCK_OP_FLUSH:
zxlogf(DEBUG, "sata: queue FLUSH txn %p", txn);
break;
default:
block_complete(txn, ZX_ERR_NOT_SUPPORTED);
return;
}
dev->controller->Queue(dev->port, txn);
}
static block_impl_protocol_ops_t sata_block_proto = {
.query = sata_query,
.queue = sata_queue,
};
zx_status_t sata_bind(Controller* controller, zx_device_t* parent, uint32_t port) {
// initialize the device
fbl::AllocChecker ac;
std::unique_ptr<sata_device_t> device(new (&ac) sata_device_t);
if (!ac.check()) {
zxlogf(ERROR, "sata: out of memory");
return ZX_ERR_NO_MEMORY;
}
device->controller = controller;
device->port = port;
char name[8];
snprintf(name, sizeof(name), "sata%u", port);
// send device identify
zx_status_t status = sata_device_identify(device.get(), controller, name);
if (status < 0) {
return status;
}
// add the device
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = name;
args.ctx = device.get();
args.ops = &sata_device_proto;
args.proto_id = ZX_PROTOCOL_BLOCK_IMPL;
args.proto_ops = &sata_block_proto;
status = device_add(parent, &args, &device->zxdev);
if (status < 0) {
return status;
}
device.release(); // Device has been retained by device_add().
return ZX_OK;
}
} // namespace ahci