| // 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/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/binding.h> |
| #include <ddk/protocol/block.h> |
| |
| #include <zircon/types.h> |
| #include <pretty/hexdump.h> |
| #include <sync/completion.h> |
| #include <sys/param.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "sata.h" |
| |
| #define TRACE 1 |
| |
| #if TRACE |
| #define xprintf(fmt...) printf(fmt) |
| #else |
| #define xprintf(fmt...) \ |
| do { \ |
| } while (0) |
| #endif |
| |
| #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* mxdev; |
| zx_device_t* parent; |
| |
| block_callbacks_t* callbacks; |
| |
| int port; |
| int flags; |
| int max_cmd; // inclusive |
| |
| size_t sector_sz; |
| zx_off_t capacity; // bytes |
| } sata_device_t; |
| |
| static void sata_device_identify_complete(iotxn_t* txn, void* cookie) { |
| completion_signal((completion_t*)cookie); |
| } |
| |
| static zx_status_t sata_device_identify(sata_device_t* dev, zx_device_t* controller, const char* name) { |
| // send IDENTIFY DEVICE |
| iotxn_t* txn; |
| zx_status_t status = iotxn_alloc(&txn, IOTXN_ALLOC_CONTIGUOUS, 512); |
| if (status != ZX_OK) { |
| xprintf("%s: error %d allocating iotxn\n", name, status); |
| return status; |
| } |
| |
| completion_t completion = COMPLETION_INIT; |
| |
| sata_pdata_t* pdata = sata_iotxn_pdata(txn); |
| pdata->cmd = SATA_CMD_IDENTIFY_DEVICE; |
| pdata->device = 0; |
| pdata->max_cmd = dev->max_cmd; |
| pdata->port = dev->port; |
| txn->complete_cb = sata_device_identify_complete; |
| txn->cookie = &completion; |
| txn->length = 512; |
| |
| iotxn_queue(controller, txn); |
| completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| if (txn->status != ZX_OK) { |
| xprintf("%s: error %d in device identify\n", name, txn->status); |
| return txn->status; |
| } |
| assert(txn->actual == 512); |
| |
| // parse results |
| int flags = 0; |
| uint16_t devinfo[512 / sizeof(uint16_t)]; |
| iotxn_copyfrom(txn, devinfo, 512, 0); |
| iotxn_release(txn); |
| |
| char str[41]; // model id is 40 chars |
| xprintf("%s: dev info\n", name); |
| snprintf(str, SATA_DEVINFO_SERIAL_LEN + 1, "%s", (char*)(devinfo + SATA_DEVINFO_SERIAL)); |
| xprintf(" serial=%s\n", str); |
| snprintf(str, SATA_DEVINFO_FW_REV_LEN + 1, "%s", (char*)(devinfo + SATA_DEVINFO_FW_REV)); |
| xprintf(" firmware rev=%s\n", str); |
| snprintf(str, SATA_DEVINFO_MODEL_ID_LEN + 1, "%s", (char*)(devinfo + SATA_DEVINFO_MODEL_ID)); |
| xprintf(" model id=%s\n", str); |
| |
| uint16_t major = *(devinfo + SATA_DEVINFO_MAJOR_VERS); |
| xprintf(" major=0x%x ", major); |
| switch (32 - __builtin_clz(major) - 1) { |
| case 10: |
| xprintf("ACS3"); |
| break; |
| case 9: |
| xprintf("ACS2"); |
| break; |
| case 8: |
| xprintf("ATA8-ACS"); |
| break; |
| case 7: |
| case 6: |
| case 5: |
| xprintf("ATA/ATAPI"); |
| break; |
| default: |
| xprintf("Obsolete"); |
| break; |
| } |
| |
| uint16_t cap = *(devinfo + SATA_DEVINFO_CAP); |
| if (cap & (1 << 8)) { |
| xprintf(" DMA"); |
| flags |= SATA_FLAG_DMA; |
| } else { |
| xprintf(" PIO"); |
| } |
| dev->max_cmd = *(devinfo + SATA_DEVINFO_QUEUE_DEPTH); |
| xprintf(" %d commands\n", dev->max_cmd + 1); |
| if (cap & (1 << 9)) { |
| dev->sector_sz = 512; // default |
| if ((*(devinfo + SATA_DEVINFO_SECTOR_SIZE) & 0xd000) == 0x5000) { |
| dev->sector_sz = 2 * sata_devinfo_u32(devinfo, SATA_DEVINFO_LOGICAL_SECTOR_SIZE); |
| } |
| if (*(devinfo + SATA_DEVINFO_CMD_SET_2) & (1 << 10)) { |
| flags |= SATA_FLAG_LBA48; |
| dev->capacity = sata_devinfo_u64(devinfo, SATA_DEVINFO_LBA_CAPACITY_2) * dev->sector_sz; |
| xprintf(" LBA48"); |
| } else { |
| dev->capacity = sata_devinfo_u32(devinfo, SATA_DEVINFO_LBA_CAPACITY) * dev->sector_sz; |
| xprintf(" LBA"); |
| } |
| xprintf(" %" PRIu64 " sectors, size=%zu\n", dev->capacity, dev->sector_sz); |
| } else { |
| xprintf(" CHS unsupported!\n"); |
| } |
| dev->flags = flags; |
| |
| return ZX_OK; |
| } |
| |
| // implement device protocol: |
| |
| static zx_protocol_device_t sata_device_proto; |
| |
| static void sata_iotxn_queue(void* ctx, iotxn_t* txn) { |
| sata_device_t* device = ctx; |
| |
| // offset must be aligned to block size |
| if (txn->offset % device->sector_sz) { |
| iotxn_complete(txn, ZX_ERR_INVALID_ARGS, 0); |
| return; |
| } |
| |
| // constrain to device capacity and round down to block aligned |
| txn->length = MIN(ROUNDDOWN(txn->length, device->sector_sz), device->capacity - txn->offset); |
| |
| sata_pdata_t* pdata = sata_iotxn_pdata(txn); |
| pdata->cmd = txn->opcode == IOTXN_OP_READ ? SATA_CMD_READ_DMA_EXT : SATA_CMD_WRITE_DMA_EXT; |
| pdata->device = 0x40; |
| pdata->lba = txn->offset / device->sector_sz; |
| pdata->count = txn->length / device->sector_sz; |
| pdata->max_cmd = device->max_cmd; |
| pdata->port = device->port; |
| |
| iotxn_queue(device->parent, txn); |
| } |
| |
| static void sata_sync_complete(iotxn_t* txn, void* cookie) { |
| completion_signal((completion_t*)cookie); |
| } |
| |
| static void sata_get_info(sata_device_t* dev, block_info_t* info) { |
| memset(info, 0, sizeof(*info)); |
| info->block_size = dev->sector_sz; |
| info->block_count = dev->capacity / dev->sector_sz; |
| info->max_transfer_size = AHCI_MAX_PRDS * PAGE_SIZE; // fully discontiguous |
| } |
| |
| 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; |
| // TODO implement other block ioctls |
| switch (op) { |
| case IOCTL_BLOCK_GET_INFO: { |
| block_info_t* info = reply; |
| if (max < sizeof(*info)) |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| sata_get_info(device, info); |
| *out_actual = sizeof(*info); |
| return ZX_OK; |
| } |
| case IOCTL_BLOCK_RR_PART: { |
| // rebind to reread the partition table |
| return device_rebind(device->mxdev); |
| } |
| case IOCTL_DEVICE_SYNC: { |
| iotxn_t* txn; |
| zx_status_t status = iotxn_alloc(&txn, IOTXN_ALLOC_CONTIGUOUS, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| completion_t completion = COMPLETION_INIT; |
| txn->opcode = IOTXN_OP_READ; |
| txn->flags = IOTXN_SYNC_BEFORE; |
| txn->offset = 0; |
| txn->length = 0; |
| txn->complete_cb = sata_sync_complete; |
| txn->cookie = &completion; |
| iotxn_queue(device->mxdev, txn); |
| completion_wait(&completion, ZX_TIME_INFINITE); |
| status = txn->status; |
| iotxn_release(txn); |
| return status; |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| static zx_off_t sata_getsize(void* ctx) { |
| sata_device_t* device = ctx; |
| return device->capacity; |
| } |
| |
| 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, |
| .iotxn_queue = sata_iotxn_queue, |
| .get_size = sata_getsize, |
| .release = sata_release, |
| }; |
| |
| static void sata_block_set_callbacks(void* ctx, block_callbacks_t* cb) { |
| sata_device_t* device = ctx; |
| device->callbacks = cb; |
| } |
| |
| static void sata_block_get_info(void* ctx, block_info_t* info) { |
| sata_device_t* device = ctx; |
| sata_get_info(device, info); |
| } |
| |
| static void sata_block_complete(iotxn_t* txn, void* cookie) { |
| sata_device_t* dev; |
| memcpy(&dev, txn->extra, sizeof(sata_device_t*)); |
| //xprintf("sata: fifo_complete dev %p cookie %p callbacks %p status %d actual 0x%" PRIx64 "\n", dev, cookie, dev->callbacks, txn->status, txn->actual); |
| dev->callbacks->complete(cookie, txn->status); |
| iotxn_release(txn); |
| } |
| |
| static void sata_block_txn(sata_device_t* dev, uint32_t opcode, zx_handle_t vmo, |
| uint64_t length, uint64_t vmo_offset, uint64_t dev_offset, |
| void* cookie) { |
| if ((dev_offset % dev->sector_sz) || (length % dev->sector_sz)) { |
| dev->callbacks->complete(cookie, ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| if ((dev_offset >= dev->capacity) || (length >= (dev->capacity - dev_offset))) { |
| dev->callbacks->complete(cookie, ZX_ERR_OUT_OF_RANGE); |
| return; |
| } |
| |
| zx_status_t status; |
| iotxn_t* txn; |
| if ((status = iotxn_alloc_vmo(&txn, IOTXN_ALLOC_POOL, vmo, vmo_offset, length)) != ZX_OK) { |
| dev->callbacks->complete(cookie, status); |
| return; |
| } |
| txn->opcode = opcode; |
| txn->offset = dev_offset; |
| txn->complete_cb = sata_block_complete; |
| txn->cookie = cookie; |
| memcpy(txn->extra, &dev, sizeof(sata_device_t*)); |
| |
| iotxn_queue(dev->mxdev, txn); |
| } |
| |
| static void sata_block_read(void* ctx, zx_handle_t vmo, uint64_t length, |
| uint64_t vmo_offset, uint64_t dev_offset, void* cookie) { |
| sata_block_txn(ctx, IOTXN_OP_READ, vmo, length, vmo_offset, dev_offset, cookie); |
| } |
| |
| static void sata_block_write(void* ctx, zx_handle_t vmo, uint64_t length, |
| uint64_t vmo_offset, uint64_t dev_offset, void* cookie) { |
| sata_block_txn(ctx, IOTXN_OP_WRITE, vmo, length, vmo_offset, dev_offset, cookie); |
| } |
| |
| static block_protocol_ops_t sata_block_ops = { |
| .set_callbacks = sata_block_set_callbacks, |
| .get_info = sata_block_get_info, |
| .read = sata_block_read, |
| .write = sata_block_write, |
| }; |
| |
| zx_status_t sata_bind(zx_device_t* dev, int port) { |
| // initialize the device |
| sata_device_t* device = calloc(1, sizeof(sata_device_t)); |
| if (!device) { |
| xprintf("sata: out of memory\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| device->parent = dev; |
| |
| device->port = port; |
| |
| char name[8]; |
| snprintf(name, sizeof(name), "sata%d", port); |
| |
| // send device identify |
| zx_status_t status = sata_device_identify(device, dev, 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_CORE, |
| .proto_ops = &sata_block_ops, |
| }; |
| |
| status = device_add(dev, &args, &device->mxdev); |
| if (status < 0) { |
| free(device); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |