* 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 <new>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "mmc_disk.h"
#include "mmc_icon.h"
#include "mmc.h"
#include <drivers/device_manager.h>
#include <drivers/KernelExport.h>
#include <drivers/Drivers.h>
#include <kernel/OS.h>
#include <util/fs_trim_support.h>
#include <AutoDeleter.h>
#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;
static device_manager_info* sDeviceManager;
struct mmc_disk_csd {
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;
return 8;
}
};
static float
mmc_disk_supports_device(device_node* parent)
{
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();
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;
geometry->read_only = false;
geometry->write_once = false;
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);
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);
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;
}
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;
}
static status_t
mmc_block_init_device(void* _info, void** _cookie)
{
CALLED();
mmc_disk_driver_info* info = (mmc_disk_driver_info*)_info;
*_cookie = info;
return B_OK;
}
static void
mmc_block_uninit_device(void* _cookie)
{
CALLED();
}
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;
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)
{
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,
kEraseModeDiscard = 1,
kEraseModeFullErase = 2,
};
TRACE("trim_device()\n");
trimData->trimmed_size = 0;
const off_t deviceSize = info->DeviceSize();
if (deviceSize < 0)
return B_BAD_VALUE;
STATIC_ASSERT(sizeof(deviceSize) <= sizeof(uint64));
ASSERT(deviceSize >= 0);
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;
offset = ROUNDUP(offset, kBlockSize);
length -= offset - trimData->ranges[i].offset;
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;
}
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:
{
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:
{
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:
{
return mmc_block_trim(info, (fs_trim_data*)buffer);
}
return synchronize_cache(info);*/
}
return B_DEV_INVALID_IOCTL;
}
module_dependency module_dependencies[] = {
{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
{}
};
struct device_module_info sMMCBlockDevice = {
{
MMC_DISK_DEVICE_MODULE_NAME,
0,
NULL
},
mmc_block_init_device,
mmc_block_uninit_device,
NULL,
mmc_block_open,
mmc_block_close,
mmc_block_free,
mmc_block_read,
mmc_block_write,
mmc_block_io,
mmc_block_ioctl,
NULL,
NULL,
};
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,
NULL,
};
module_info* modules[] = {
(module_info*)&sMMCDiskDriver,
(module_info*)&sMMCBlockDevice,
NULL
};