/* * Copyright 2018-2021 Haiku, Inc. All rights reserved. * Copyright 2020, Viveris Technologies. * Distributed under the terms of the MIT License. * * Authors: * B Krishnan Iyer, krishnaniyer97@gmail.com * Adrien Destugues, pulkomandy@pulkomandy.tk */ #include #include #include #include #include #include "mmc_disk.h" #include "mmc_icon.h" #include "mmc.h" #include #include #include #include #include #include #define TRACE_MMC_DISK #ifdef TRACE_MMC_DISK # define TRACE(x...) dprintf("\33[33mmmc_disk:\33[0m " x) #else # define TRACE(x...) ; #endif #define ERROR(x...) dprintf("\33[33mmmc_disk:\33[0m " x) #define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__) #define MMC_DISK_DRIVER_MODULE_NAME "drivers/disk/mmc/mmc_disk/driver_v1" #define MMC_DISK_DEVICE_MODULE_NAME "drivers/disk/mmc/mmc_disk/device_v1" #define MMC_DEVICE_ID_GENERATOR "mmc/device_id" static const uint32 kBlockSize = 512; // FIXME get it from the CSD static device_manager_info* sDeviceManager; struct mmc_disk_csd { // The content of this register is described in Physical Layer Simplified // Specification Version 8.00, section 5.3 uint64 bits[2]; uint8 structure_version() { return bits[1] >> 54; } uint8 read_bl_len() { return (bits[1] >> 8) & 0xF; } uint32 c_size() { if (structure_version() == 0) return ((bits[0] >> 54) & 0x3FF) | ((bits[1] & 0x3) << 10); if (structure_version() == 1) return (bits[0] >> 40) & 0x3FFFFF; return ((bits[0] >> 40) & 0xFFFFFF) | ((bits[1] & 0xF) << 24); } uint8 c_size_mult() { if (structure_version() == 0) return (bits[0] >> 39) & 0x7; // In later versions this field is not present in the structure and a // fixed value is used. return 8; } }; static float mmc_disk_supports_device(device_node* parent) { // Filter all devices that are not on an MMC bus const char* bus; if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, true) != B_OK) return -1; if (strcmp(bus, "mmc") != 0) return 0.0; CALLED(); // Filter all devices that are not of the known types uint8_t deviceType; if (sDeviceManager->get_attr_uint8(parent, kMmcTypeAttribute, &deviceType, true) != B_OK) { ERROR("Could not get device type\n"); return -1; } if (deviceType == CARD_TYPE_SD) TRACE("SD card found, parent: %p\n", parent); else if (deviceType == CARD_TYPE_SDHC) TRACE("SDHC card found, parent: %p\n", parent); else return 0.0; return 0.8; } static status_t mmc_disk_register_device(device_node* node) { CALLED(); device_attr attrs[] = { { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "SD Card" }}, { NULL } }; return sDeviceManager->register_node(node, MMC_DISK_DRIVER_MODULE_NAME, attrs, NULL, NULL); } static status_t mmc_disk_execute_iorequest(void* data, IOOperation* operation) { mmc_disk_driver_info* info = (mmc_disk_driver_info*)data; status_t error; uint8_t command; if (operation->IsWrite()) command = SD_WRITE_MULTIPLE_BLOCKS; else command = SD_READ_MULTIPLE_BLOCKS; error = info->mmc->do_io(info->parent, info->parentCookie, info->rca, command, operation, (info->flags & kIoCommandOffsetAsSectors) != 0); if (error != B_OK) { info->scheduler->OperationCompleted(operation, error, 0); return error; } info->scheduler->OperationCompleted(operation, B_OK, operation->Length()); return B_OK; } static status_t mmc_block_get_geometry(mmc_disk_driver_info* info, device_geometry* geometry) { struct mmc_disk_csd csd; TRACE("Get geometry\n"); status_t error = info->mmc->execute_command(info->parent, info->parentCookie, 0, SD_SEND_CSD, info->rca << 16, (uint32_t*)&csd); if (error != B_OK) { TRACE("Could not get CSD! %s\n", strerror(error)); return error; } TRACE("CSD: %" PRIx64 " %" PRIx64 "\n", csd.bits[0], csd.bits[1]); if (csd.structure_version() >= 3) { TRACE("unknown CSD version %d\n", csd.structure_version()); return B_NOT_SUPPORTED; } geometry->bytes_per_sector = 1 << csd.read_bl_len(); geometry->sectors_per_track = csd.c_size() + 1; geometry->cylinder_count = 1 << (csd.c_size_mult() + 2); geometry->head_count = 1; geometry->device_type = B_DISK; geometry->removable = true; // TODO detect eMMC which isn't geometry->read_only = false; // TODO check write protect switch? geometry->write_once = false; // This function will be called before all data transfers, so we use this // opportunity to switch the card to 4-bit data transfers (instead of the // default 1 bit mode) uint32_t cardStatus; const uint32 k4BitMode = 2; info->mmc->execute_command(info->parent, info->parentCookie, info->rca, SD_APP_CMD, info->rca << 16, &cardStatus); info->mmc->execute_command(info->parent, info->parentCookie, info->rca, SD_SET_BUS_WIDTH, k4BitMode, &cardStatus); // From now on we use 4 bit mode info->mmc->set_bus_width(info->parent, info->parentCookie, 4); return B_OK; } static status_t mmc_disk_init_driver(device_node* node, void** cookie) { CALLED(); mmc_disk_driver_info* info = (mmc_disk_driver_info*)malloc( sizeof(mmc_disk_driver_info)); if (info == NULL) return B_NO_MEMORY; memset(info, 0, sizeof(*info)); void* unused2; info->node = node; info->parent = sDeviceManager->get_parent_node(info->node); sDeviceManager->get_driver(info->parent, (driver_module_info **)&info->mmc, &unused2); // We need to grab the bus cookie as well // FIXME it would be easier if that was available from the get_driver call // above directly, but currently it isn't. device_node* busNode = sDeviceManager->get_parent_node(info->parent); driver_module_info* unused; sDeviceManager->get_driver(busNode, &unused, &info->parentCookie); sDeviceManager->put_node(busNode); TRACE("MMC bus handle: %p %s\n", info->mmc, info->mmc->info.info.name); if (sDeviceManager->get_attr_uint16(node, kMmcRcaAttribute, &info->rca, true) != B_OK) { TRACE("MMC card node has no RCA attribute\n"); free(info); return B_BAD_DATA; } uint8_t deviceType; if (sDeviceManager->get_attr_uint8(info->parent, kMmcTypeAttribute, &deviceType, true) != B_OK) { ERROR("Could not get device type\n"); free(info); return B_BAD_DATA; } // SD and MMC cards use byte offsets for IO commands, later ones (SDHC, // SDXC, ...) use sectors. if (deviceType == CARD_TYPE_SD || deviceType == CARD_TYPE_MMC) info->flags = 0; else info->flags = kIoCommandOffsetAsSectors; status_t error; static const uint32 kDMAResourceBufferCount = 16; static const uint32 kDMAResourceBounceBufferCount = 16; info->dmaResource = new(std::nothrow) DMAResource; if (info->dmaResource == NULL) { TRACE("Failed to allocate DMA resource"); free(info); return B_NO_MEMORY; } error = info->dmaResource->Init(info->node, kBlockSize, kDMAResourceBufferCount, kDMAResourceBounceBufferCount); if (error != B_OK) { TRACE("Failed to init DMA resource"); delete info->dmaResource; free(info); return error; } info->scheduler = new(std::nothrow) IOSchedulerSimple(info->dmaResource); if (info->scheduler == NULL) { TRACE("Failed to allocate scheduler"); delete info->dmaResource; free(info); return B_NO_MEMORY; } error = info->scheduler->Init("mmc storage"); if (error != B_OK) { TRACE("Failed to init scheduler"); delete info->scheduler; delete info->dmaResource; free(info); return error; } info->scheduler->SetCallback(&mmc_disk_execute_iorequest, info); memset(&info->geometry, 0, sizeof(info->geometry)); TRACE("MMC card device initialized for RCA %x\n", info->rca); *cookie = info; return B_OK; } static void mmc_disk_uninit_driver(void* _cookie) { CALLED(); mmc_disk_driver_info* info = (mmc_disk_driver_info*)_cookie; delete info->scheduler; delete info->dmaResource; sDeviceManager->put_node(info->parent); free(info); } static status_t mmc_disk_register_child_devices(void* _cookie) { CALLED(); mmc_disk_driver_info* info = (mmc_disk_driver_info*)_cookie; status_t status; int32 id = sDeviceManager->create_id(MMC_DEVICE_ID_GENERATOR); if (id < 0) return id; char name[64]; snprintf(name, sizeof(name), "disk/mmc/%" B_PRId32 "/raw", id); status = sDeviceManager->publish_device(info->node, name, MMC_DISK_DEVICE_MODULE_NAME); return status; } // #pragma mark - device module API static status_t mmc_block_init_device(void* _info, void** _cookie) { CALLED(); // No additional context, so just reuse the same data as the disk device mmc_disk_driver_info* info = (mmc_disk_driver_info*)_info; *_cookie = info; // Note: it is not possible to execute commands here, because this is called // with the mmc_bus locked for enumeration (and still using slow clock). return B_OK; } static void mmc_block_uninit_device(void* _cookie) { CALLED(); //mmc_disk_driver_info* info = (mmc_disk_driver_info*)_cookie; // TODO cleanup whatever is relevant } static status_t mmc_block_open(void* _info, const char* path, int openMode, void** _cookie) { CALLED(); mmc_disk_driver_info* info = (mmc_disk_driver_info*)_info; // allocate cookie mmc_disk_handle* handle = new(std::nothrow) mmc_disk_handle; *_cookie = handle; if (handle == NULL) return B_NO_MEMORY; handle->info = info; if (handle->info->geometry.bytes_per_sector == 0) { status_t error = mmc_block_get_geometry(handle->info, &handle->info->geometry); if (error != B_OK) { TRACE("Failed to get disk capacity"); delete handle; *_cookie = NULL; return error; } } return B_OK; } static status_t mmc_block_close(void* cookie) { //mmc_disk_handle* handle = (mmc_disk_handle*)cookie; CALLED(); return B_OK; } static status_t mmc_block_free(void* cookie) { CALLED(); mmc_disk_handle* handle = (mmc_disk_handle*)cookie; delete handle; return B_OK; } static status_t mmc_block_read(void* cookie, off_t position, void* buffer, size_t* _length) { CALLED(); mmc_disk_handle* handle = (mmc_disk_handle*)cookie; size_t length = *_length; if (position >= handle->info->DeviceSize()) return ERANGE; if ((position + (off_t)length) > handle->info->DeviceSize()) length = (handle->info->DeviceSize() - position); IORequest request; status_t status = request.Init(position, (addr_t)buffer, length, false, 0); if (status != B_OK) return status; status = handle->info->scheduler->ScheduleRequest(&request); if (status != B_OK) return status; status = request.Wait(0, 0); *_length = request.TransferredBytes(); return status; } static status_t mmc_block_write(void* cookie, off_t position, const void* buffer, size_t* _length) { CALLED(); mmc_disk_handle* handle = (mmc_disk_handle*)cookie; size_t length = *_length; if (position >= handle->info->DeviceSize()) return ERANGE; if ((position + (off_t)length) > handle->info->DeviceSize()) length = (handle->info->DeviceSize() - position); IORequest request; status_t status = request.Init(position, (addr_t)buffer, length, true, 0); if (status != B_OK) return status; status = handle->info->scheduler->ScheduleRequest(&request); if (status != B_OK) return status; status = request.Wait(0, 0); *_length = request.TransferredBytes(); return status; } static status_t mmc_block_io(void* cookie, io_request* request) { CALLED(); mmc_disk_handle* handle = (mmc_disk_handle*)cookie; if ((request->Offset() + (off_t)request->Length()) > handle->info->DeviceSize()) return ERANGE; return handle->info->scheduler->ScheduleRequest(request); } static status_t mmc_block_trim(mmc_disk_driver_info* info, fs_trim_data* trimData) { enum { kEraseModeErase = 0, // force to actually erase the data kEraseModeDiscard = 1, // just mark the data as unused for internal wear leveling // algorithms kEraseModeFullErase = 2, // erase the whole card }; TRACE("trim_device()\n"); trimData->trimmed_size = 0; const off_t deviceSize = info->DeviceSize(); // in bytes if (deviceSize < 0) return B_BAD_VALUE; STATIC_ASSERT(sizeof(deviceSize) <= sizeof(uint64)); ASSERT(deviceSize >= 0); // Do not trim past device end for (uint32 i = 0; i < trimData->range_count; i++) { uint64 offset = trimData->ranges[i].offset; uint64& size = trimData->ranges[i].size; if (offset >= (uint64)deviceSize) return B_BAD_VALUE; size = min_c(size, (uint64)deviceSize - offset); } uint64 trimmedSize = 0; status_t result = B_OK; for (uint32 i = 0; i < trimData->range_count; i++) { uint64 offset = trimData->ranges[i].offset; uint64 length = trimData->ranges[i].size; // Round up offset and length to multiple of the sector size // The offset is rounded up, so some space may be left // (not trimmed) at the start of the range. offset = ROUNDUP(offset, kBlockSize); // Adjust the length for the possibly skipped range length -= offset - trimData->ranges[i].offset; // The length is rounded down, so some space at the end may also // be left (not trimmed). length &= ~(kBlockSize - 1); if (length == 0) continue; TRACE("trim %" B_PRIu64 " bytes from %" B_PRIu64 "\n", length, offset); ASSERT(offset % kBlockSize == 0); ASSERT(length % kBlockSize == 0); if ((info->flags & kIoCommandOffsetAsSectors) != 0) { offset /= kBlockSize; length /= kBlockSize; } // Parameter of execute_command is uint32_t if (offset > UINT32_MAX || length > UINT32_MAX - offset) { result = B_BAD_VALUE; break; } uint32_t response; result = info->mmc->execute_command(info->parent, info->parentCookie, info->rca, SD_ERASE_WR_BLK_START, offset, &response); if (result != B_OK) break; result = info->mmc->execute_command(info->parent, info->parentCookie, info->rca, SD_ERASE_WR_BLK_END, offset + length, &response); if (result != B_OK) break; result = info->mmc->execute_command(info->parent, info->parentCookie, info->rca, SD_ERASE, kEraseModeDiscard, &response); if (result != B_OK) break; trimmedSize += (info->flags & kIoCommandOffsetAsSectors) != 0 ? length * kBlockSize : length; } trimData->trimmed_size = trimmedSize; return result; } static status_t mmc_block_ioctl(void* cookie, uint32 op, void* buffer, size_t length) { mmc_disk_handle* handle = (mmc_disk_handle*)cookie; mmc_disk_driver_info* info = handle->info; switch (op) { case B_GET_MEDIA_STATUS: { if (buffer == NULL || length < sizeof(status_t)) return B_BAD_VALUE; status_t status = B_OK; return user_memcpy(buffer, &status, sizeof(status_t)); } case B_GET_DEVICE_SIZE: { // Legacy ioctl, use B_GET_GEOMETRY uint64_t size = info->DeviceSize(); if (size > SIZE_MAX) return B_NOT_SUPPORTED; size_t size32 = size; return user_memcpy(buffer, &size32, sizeof(size_t)); } case B_GET_GEOMETRY: { if (buffer == NULL || length > sizeof(device_geometry)) return B_BAD_VALUE; return user_memcpy(buffer, &info->geometry, length); } case B_GET_ICON_NAME: return user_strlcpy((char*)buffer, "devices/drive-harddisk", B_FILE_NAME_LENGTH); case B_GET_VECTOR_ICON: { // TODO: take device type into account! device_icon iconData; if (length != sizeof(device_icon)) return B_BAD_VALUE; if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK) return B_BAD_ADDRESS; if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) { if (user_memcpy(iconData.icon_data, kDriveIcon, sizeof(kDriveIcon)) != B_OK) return B_BAD_ADDRESS; } iconData.icon_size = sizeof(kDriveIcon); return user_memcpy(buffer, &iconData, sizeof(device_icon)); } case B_TRIM_DEVICE: { // We know the buffer is kernel-side because it has been // preprocessed in devfs return mmc_block_trim(info, (fs_trim_data*)buffer); } /*case B_FLUSH_DRIVE_CACHE: return synchronize_cache(info);*/ } return B_DEV_INVALID_IOCTL; } module_dependency module_dependencies[] = { {B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager}, {} }; // The "block device" associated with the device file. It can be open() // multiple times, eash allocating an mmc_disk_handle. It does not interact // with the hardware directly, instead it forwards all IO requests to the // disk driver through the IO scheduler. struct device_module_info sMMCBlockDevice = { { MMC_DISK_DEVICE_MODULE_NAME, 0, NULL }, mmc_block_init_device, mmc_block_uninit_device, NULL, // remove, mmc_block_open, mmc_block_close, mmc_block_free, mmc_block_read, mmc_block_write, mmc_block_io, mmc_block_ioctl, NULL, // select NULL, // deselect }; // Driver for the disk devices itself. This is paired with an // mmc_disk_driver_info instanciated once per device. Handles the actual disk // I/O operations struct driver_module_info sMMCDiskDriver = { { MMC_DISK_DRIVER_MODULE_NAME, 0, NULL }, mmc_disk_supports_device, mmc_disk_register_device, mmc_disk_init_driver, mmc_disk_uninit_driver, mmc_disk_register_child_devices, NULL, // mmc_disk_rescan_child_devices, NULL, }; module_info* modules[] = { (module_info*)&sMMCDiskDriver, (module_info*)&sMMCBlockDevice, NULL };