blob: d5c58c50b7cebb80bb6853166d3f1cca06563a94 [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 <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <pretty/hexdump.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <lib/sync/completion.h>
#include <sys/param.h>
#include <zircon/device/block.h>
#include <zircon/types.h>
#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(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((sync_completion_t*)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,
},
.cmd = SATA_CMD_IDENTIFY_DEVICE,
.device = 0,
.completion_cb = sata_device_identify_complete,
.cookie = &completion,
};
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_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,
.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, block_impl_queue_callback completion_cb,
void* cookie) {
sata_device_t* dev = 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(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(txn, ZX_ERR_NOT_SUPPORTED);
return;
}
ahci_queue(dev->controller, dev->port, txn);
}
static block_impl_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;
}