From 1cdb3beb26e8dadfab910e3afb8eb0e12d4144ab Mon Sep 17 00:00:00 2001 From: SED4906 Date: Tue, 27 Feb 2024 20:20:43 -0800 Subject: [PATCH] sdhci: add ACPI-enumerated device support Change-Id: I5c42344b2499bd22581d6b564192decbce937645 Reviewed-on: https://review.haiku-os.org/c/haiku/+/7456 Reviewed-by: Adrien Destugues Tested-by: Commit checker robot --- build/jam/packages/Haiku | 4 +- .../kernel/bus_managers/mmc/mmc_bus.cpp | 4 +- src/add-ons/kernel/busses/mmc/Jamfile | 6 +- src/add-ons/kernel/busses/mmc/sdhci.cpp | 833 ++++++++++++++++++ .../busses/mmc/{sdhci_pci.h => sdhci.h} | 85 +- src/add-ons/kernel/busses/mmc/sdhci_acpi.cpp | 245 ++++++ src/add-ons/kernel/busses/mmc/sdhci_pci.cpp | 817 +---------------- .../kernel/device_manager/device_manager.cpp | 1 + 8 files changed, 1205 insertions(+), 790 deletions(-) create mode 100644 src/add-ons/kernel/busses/mmc/sdhci.cpp rename src/add-ons/kernel/busses/mmc/{sdhci_pci.h => sdhci.h} (80%) create mode 100644 src/add-ons/kernel/busses/mmc/sdhci_acpi.cpp diff --git a/build/jam/packages/Haiku b/build/jam/packages/Haiku index 11ac6d61b6..61485784e7 100644 --- a/build/jam/packages/Haiku +++ b/build/jam/packages/Haiku @@ -29,7 +29,7 @@ AddFilesToPackage add-ons kernel busses ata : generic_ide_pci it8211 legacy_sata silicon_image_3112 highpoint_ide_pci ide_isa@x86 ; AddFilesToPackage add-ons kernel busses i2c : pch_i2c@x86,x86_64 ; -AddFilesToPackage add-ons kernel busses mmc : sdhci_pci ; +AddFilesToPackage add-ons kernel busses mmc : sdhci ; AddFilesToPackage add-ons kernel busses pci : designware@riscv64 ecam@riscv64,arm,arm64 x86@x86,x86_64 ; AddFilesToPackage add-ons kernel busses random : ccp_rng@x86,x86_64 virtio_rng ; @@ -202,7 +202,7 @@ AddBootModuleSymlinksToPackage designware@riscv64 ecam@riscv64,arm,arm64 x86@x86,x86_64 fdt@riscv64,arm,arm64 scsi scsi_cd scsi_disk scsi_periph silicon_image_3112 highpoint_ide_pci - sdhci_pci + sdhci usb usb_disk ehci ohci uhci xhci virtio virtio_block virtio_mmio@riscv64,arm,arm64 virtio_pci virtio_scsi ; diff --git a/src/add-ons/kernel/bus_managers/mmc/mmc_bus.cpp b/src/add-ons/kernel/bus_managers/mmc/mmc_bus.cpp index 560dab9fc5..43f8070b1f 100644 --- a/src/add-ons/kernel/bus_managers/mmc/mmc_bus.cpp +++ b/src/add-ons/kernel/bus_managers/mmc/mmc_bus.cpp @@ -261,7 +261,9 @@ MMCBus::_WorkerThread(void* cookie) // (and a matching published device on our side). uint32_t cid[4]; - while (bus->ExecuteCommand(0, SD_ALL_SEND_CID, 0, cid) == B_OK) { + // This being an if statement as opposed to a while statement restricts + // it to one device per bus. + if (bus->ExecuteCommand(0, SD_ALL_SEND_CID, 0, cid) == B_OK) { bus->ExecuteCommand(0, SD_SEND_RELATIVE_ADDR, 0, &response); TRACE("RCA: %x Status: %x\n", response >> 16, response & 0xFFFF); diff --git a/src/add-ons/kernel/busses/mmc/Jamfile b/src/add-ons/kernel/busses/mmc/Jamfile index 9b8fc1c754..6190c2fa0d 100644 --- a/src/add-ons/kernel/busses/mmc/Jamfile +++ b/src/add-ons/kernel/busses/mmc/Jamfile @@ -5,7 +5,11 @@ SubDirC++Flags -fno-rtti ; UsePrivateKernelHeaders ; UsePrivateHeaders drivers ; SubDirHdrs $(HAIKU_TOP) src system kernel device_manager ; +SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include ; +SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include platform ; -KernelAddon sdhci_pci : +KernelAddon sdhci : + sdhci.cpp + sdhci_acpi.cpp sdhci_pci.cpp ; diff --git a/src/add-ons/kernel/busses/mmc/sdhci.cpp b/src/add-ons/kernel/busses/mmc/sdhci.cpp new file mode 100644 index 0000000000..b9d446cc86 --- /dev/null +++ b/src/add-ons/kernel/busses/mmc/sdhci.cpp @@ -0,0 +1,833 @@ +/* + * Copyright 2018-2024 Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * B Krishnan Iyer, krishnaniyer97@gmail.com + * Adrien Destugues, pulkomandy@pulkomandy.tk + * Ron Ben Aroya, sed4906birdie@gmail.com + */ +#include +#include +#include +#include + +#include +#include +#include "acpi.h" + +#include + +#include "IOSchedulerSimple.h" +#include "mmc.h" +#include "sdhci.h" + + +#define TRACE_SDHCI +#ifdef TRACE_SDHCI +# define TRACE(x...) dprintf("\33[33msdhci:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci:\33[0m " x) +#define ERROR(x...) dprintf("\33[33msdhci:\33[0m " x) +#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__) + + +#define SDHCI_DEVICE_MODULE_NAME "busses/mmc/sdhci/driver_v1" +#define SDHCI_ACPI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/acpi/device/v1" +#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/pci/device/v1" + +#define SLOT_NUMBER "device/slot" +#define BAR_INDEX "device/bar" + +device_manager_info* gDeviceManager; +device_module_info* gMMCBusController; + +static int32 +sdhci_generic_interrupt(void* data) +{ + SdhciBus* bus = (SdhciBus*)data; + return bus->HandleInterrupt(); +} + + +SdhciBus::SdhciBus(struct registers* registers, uint8_t irq, bool poll) + : + fRegisters(registers), + fIrq(irq), + fSemaphore(0) +{ + if (irq == 0 || irq == 0xff) { + ERROR("IRQ not assigned\n"); + fStatus = B_BAD_DATA; + return; + } + + fSemaphore = create_sem(0, "SDHCI interrupts"); + + DisableInterrupts(); + + fStatus = install_io_interrupt_handler(fIrq, + sdhci_generic_interrupt, this, 0); + + if (fStatus != B_OK) { + ERROR("can't install interrupt handler\n"); + return; + } + + // First of all, we have to make sure we are in a sane state. The easiest + // way is to reset everything. + Reset(); + + // Turn on the power supply to the card, if there is a card inserted + if (PowerOn()) { + // Then we configure the clock to the frequency needed for + // initialization + SetClock(400); + } + + // Finally, configure some useful interrupts + EnableInterrupts(SDHCI_INT_CMD_CMP | SDHCI_INT_CARD_REM + | SDHCI_INT_TRANS_CMP); + + // We want to see the error bits in the status register, but not have an + // interrupt trigger on them (we get a "command complete" interrupt on + // errors already) + fRegisters->interrupt_status_enable |= SDHCI_INT_ERROR + | SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX + | SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT; + + if (poll) { + // Spawn a polling thread, as the interrupts won't currently work on ACPI. + fWorkerThread = spawn_kernel_thread(_WorkerThread, "SD bus poller", + B_NORMAL_PRIORITY, this); + resume_thread(fWorkerThread); + } +} + + +SdhciBus::~SdhciBus() +{ + DisableInterrupts(); + + if (fSemaphore != 0) + delete_sem(fSemaphore); + + if (fIrq != 0) + remove_io_interrupt_handler(fIrq, sdhci_generic_interrupt, this); + + area_id regs_area = area_for(fRegisters); + delete_area(regs_area); + + fStatus = B_SHUTTING_DOWN; + + status_t result; + if (fWorkerThread != 0) + wait_for_thread(fWorkerThread, &result); +} + + +void +SdhciBus::EnableInterrupts(uint32_t mask) +{ + fRegisters->interrupt_status_enable |= mask; + fRegisters->interrupt_signal_enable |= mask; +} + + +void +SdhciBus::DisableInterrupts() +{ + fRegisters->interrupt_status_enable = 0; + fRegisters->interrupt_signal_enable = 0; +} + + +// #pragma mark - +/* +PartA2, SD Host Controller Simplified Specification, Version 4.20 +§3.7.1.1 The sequence to issue an SD Command +*/ +status_t +SdhciBus::ExecuteCommand(uint8_t command, uint32_t argument, uint32_t* response) +{ + TRACE("ExecuteCommand(%d, %x)\n", command, argument); + + // First of all clear the result + fCommandResult = 0; + + // Check if it's possible to send a command right now. + // It is not possible to send a command as long as the command line is busy. + // The spec says we should wait, but we can't do that on kernel side, since + // it leaves no chance for the upper layers to handle the problem. So we + // just say we're busy and the caller can retry later. + // Note that this should normally never happen: the command line is busy + // only during command execution, and we don't leave this function with ac + // command running. + if (fRegisters->present_state.CommandInhibit()) { + panic("Command execution impossible, command inhibit\n"); + return B_BUSY; + } + if (fRegisters->present_state.DataInhibit()) { + panic("Command execution unwise, data inhibit\n"); + return B_BUSY; + } + + uint32_t replyType; + + switch (command) { + case SD_GO_IDLE_STATE: + replyType = Command::kNoReplyType; + break; + case SD_ALL_SEND_CID: + case SD_SEND_CSD: + replyType = Command::kR2Type; + break; + case SD_SEND_RELATIVE_ADDR: + replyType = Command::kR6Type; + break; + case SD_SELECT_DESELECT_CARD: + case SD_ERASE: + replyType = Command::kR1bType; + break; + case SD_SEND_IF_COND: + replyType = Command::kR7Type; + break; + case SD_READ_SINGLE_BLOCK: + case SD_READ_MULTIPLE_BLOCKS: + case SD_WRITE_SINGLE_BLOCK: + case SD_WRITE_MULTIPLE_BLOCKS: + replyType = Command::kR1Type | Command::kDataPresent; + break; + case SD_APP_CMD: + case SD_ERASE_WR_BLK_START: + case SD_ERASE_WR_BLK_END: + case SD_SET_BUS_WIDTH: // SD Application command + replyType = Command::kR1Type; + break; + case SD_SEND_OP_COND: // SD Application command + replyType = Command::kR3Type; + break; + default: + ERROR("Unknown command %x\n", command); + return B_BAD_DATA; + } + + // Check if DATA line is available (if needed) + if ((replyType & Command::k32BitResponseCheckBusy) != 0 + && command != SD_STOP_TRANSMISSION && command != SD_IO_ABORT) { + if (fRegisters->present_state.DataInhibit()) { + ERROR("Execution aborted, data inhibit\n"); + return B_BUSY; + } + } + + if (fRegisters->present_state.CommandInhibit()) + panic("Command line busy at start of execute command\n"); + + if (replyType == Command::kR1bType) + fRegisters->transfer_mode = 0; + + fRegisters->argument = argument; + fRegisters->command.SendCommand(command, replyType); + + // Wait for command response to be available ("command complete" interrupt) + TRACE("Wait for command complete..."); + do { + //fCommandResult = fRegisters->interrupt_status; + acquire_sem(fSemaphore); + TRACE("command complete sem acquired, status: %x\n", fCommandResult); + TRACE("real status = %x command line busy: %d\n", + fRegisters->interrupt_status, + fRegisters->present_state.CommandInhibit()); + } while (fCommandResult == 0); + + TRACE("Command response available\n"); + + if (fCommandResult & SDHCI_INT_ERROR) { + fRegisters->interrupt_status |= fCommandResult; + if (fCommandResult & SDHCI_INT_TIMEOUT) { + ERROR("Command execution timed out\n"); + if (fRegisters->present_state.CommandInhibit()) { + TRACE("Command line is still busy, clearing it\n"); + // Clear the stall + fRegisters->software_reset.ResetCommandLine(); + } + return B_TIMED_OUT; + } + if (fCommandResult & SDHCI_INT_CRC) { + ERROR("CRC error\n"); + return B_BAD_VALUE; + } + ERROR("Command execution failed %x\n", fCommandResult); + // TODO look at errors in interrupt_status register for more details + // and return a more appropriate error code + return B_ERROR; + } + + if (fRegisters->present_state.CommandInhibit()) { + TRACE("Command execution failed, card stalled\n"); + // Clear the stall + fRegisters->software_reset.ResetCommandLine(); + return B_ERROR; + } + + switch (replyType & Command::kReplySizeMask) { + case Command::k32BitResponse: + *response = fRegisters->response[0]; + break; + case Command::k128BitResponse: + response[0] = fRegisters->response[0]; + response[1] = fRegisters->response[1]; + response[2] = fRegisters->response[2]; + response[3] = fRegisters->response[3]; + break; + + default: + // No response + break; + } + + if (replyType == Command::kR1bType + && (fCommandResult & SDHCI_INT_TRANS_CMP) == 0) { + // R1b commands may use the data line so we must wait for the + // "transfer complete" interrupt here. + TRACE("Waiting for data line...\n"); + do { + acquire_sem(fSemaphore); + } while (fRegisters->present_state.DataInhibit()); + TRACE("Dataline is released.\n"); + } + + ERROR("Command execution %d complete\n", command); + return B_OK; +} + + +status_t +SdhciBus::InitCheck() +{ + return fStatus; +} + + +void +SdhciBus::Reset() +{ + if (!fRegisters->software_reset.ResetAll()) + ERROR("SdhciBus::Reset: SoftwareReset timeout\n"); +} + + +void +SdhciBus::SetClock(int kilohertz) +{ + int base_clock = fRegisters->capabilities.BaseClockFrequency(); + // Try to get as close to 400kHz as possible, but not faster + int divider = base_clock * 1000 / kilohertz; + + if (fRegisters->host_controller_version.specVersion <= 1) { + // Old controller only support power of two dividers up to 256, + // round to next power of two up to 256 + if (divider > 256) + divider = 256; + + divider--; + divider |= divider >> 1; + divider |= divider >> 2; + divider |= divider >> 4; + divider++; + } + + divider = fRegisters->clock_control.SetDivider(divider); + + // Log the value after possible rounding by SetDivider (only even values + // are allowed). + TRACE("SDCLK frequency: %dMHz / %d = %dkHz\n", base_clock, divider, + base_clock * 1000 / divider); + + // We have set the divider, now we can enable the internal clock. + fRegisters->clock_control.EnableInternal(); + + // wait until internal clock is stabilized + while (!(fRegisters->clock_control.InternalStable())); + + fRegisters->clock_control.EnablePLL(); + while (!(fRegisters->clock_control.InternalStable())); + + // Finally, route the clock to the SD card + fRegisters->clock_control.EnableSD(); +} + + +status_t +SdhciBus::DoIO(uint8_t command, IOOperation* operation, bool offsetAsSectors) +{ + bool isWrite = operation->IsWrite(); + + static const uint32 kBlockSize = 512; + off_t offset = operation->Offset(); + generic_size_t length = operation->Length(); + + TRACE("%s %" B_PRIu64 " bytes at %" B_PRIdOFF "\n", + isWrite ? "Write" : "Read", length, offset); + + // Check that the IO scheduler did its job in following our DMA restrictions + // We can start a read only at a sector boundary + ASSERT(offset % kBlockSize == 0); + // We can only read complete sectors + ASSERT(length % kBlockSize == 0); + + const generic_io_vec* vecs = operation->Vecs(); + generic_size_t vecOffset = 0; + + // FIXME can this be moved to the init function instead? + // + // For simplicity we use a transfer size equal to the sector size. We could + // go up to 2K here if the length to read in each individual vec is a + // multiple of 2K, but we have no easy way to know this (we would need to + // iterate through the IOOperation vecs and check the size of each of them). + // We could also do smaller transfers, but it is not possible to start a + // transfer anywhere else than the start of a sector, so it's a lot simpler + // to always work in complete sectors. We set the B_DMA_ALIGNMENT device + // node property accordingly, making sure that we don't get asked to do + // transfers that are not aligned with sectors. + // + // Additionnally, set SDMA buffer boundary aligment to 512K. This is the + // largest possible size. We also set the B_DMA_BOUNDARY property on the + // published device node, so that the DMA resource manager knows that it + // must respect this boundary. As a result, we will never be asked to + // do a transfer that crosses this boundary, and we don't need to handle + // the DMA boundary interrupt (the transfer will be split in two at an + // upper layer). + fRegisters->block_size.ConfigureTransfer(kBlockSize, + BlockSize::kDmaBoundary512K); + status_t result = B_OK; + + while (length > 0) { + size_t toCopy = std::min((generic_size_t)length, + vecs->length - vecOffset); + + // If the current vec is empty, we can move to the next + if (toCopy == 0) { + vecs++; + vecOffset = 0; + continue; + } + + // With SDMA we can only transfer multiples of 1 sector + ASSERT(toCopy % kBlockSize == 0); + + fRegisters->system_address = vecs->base + vecOffset; + // fRegisters->adma_system_address = fDmaMemory; + + fRegisters->block_count = toCopy / kBlockSize; + + uint16 direction; + if (isWrite) + direction = TransferMode::kWrite; + else + direction = TransferMode::kRead; + fRegisters->transfer_mode = TransferMode::kMulti | direction + | TransferMode::kAutoCmd12Enable + | TransferMode::kBlockCountEnable | TransferMode::kDmaEnable; + + uint32_t response; + result = ExecuteCommand(command, + offset / (offsetAsSectors ? kBlockSize : 1), &response); + if (result != B_OK) + break; + + // Wait for DMA transfer to complete + // In theory we could go on and send other commands as long as they + // don't need the DAT lines, but it's overcomplicating things. + TRACE("Wait for transfer complete..."); + //while ((fRegisters->interrupt_status & SDHCI_INT_TRANS_CMP) == 0); + acquire_sem(fSemaphore); + TRACE("transfer complete OK.\n"); + + length -= toCopy; + vecOffset += toCopy; + offset += toCopy; + } + + return result; +} + + +void +SdhciBus::SetScanSemaphore(sem_id sem) +{ + fScanSemaphore = sem; + + // If there is already a card in, start a scan immediately + if (fRegisters->present_state.IsCardInserted()) + release_sem(fScanSemaphore); + + // We can now enable the card insertion interrupt for next time a card + // is inserted + EnableInterrupts(SDHCI_INT_CARD_INS); +} + + +void +SdhciBus::SetBusWidth(int width) +{ + uint8_t widthBits; + switch(width) { + case 1: + widthBits = HostControl::kDataTransfer1Bit; + break; + case 4: + widthBits = HostControl::kDataTransfer4Bit; + break; + case 8: + widthBits = HostControl::kDataTransfer8Bit; + break; + default: + panic("Incorrect bitwidth value"); + return; + } + fRegisters->host_control.SetDataTransferWidth(widthBits); +} + + +bool +SdhciBus::PowerOn() +{ + if (!fRegisters->present_state.IsCardInserted()) { + TRACE("Card not inserted, not powering on for now\n"); + return false; + } + + uint8_t supportedVoltages = fRegisters->capabilities.SupportedVoltages(); + if ((supportedVoltages & Capabilities::k3v3) != 0) + fRegisters->power_control.SetVoltage(PowerControl::k3v3); + else if ((supportedVoltages & Capabilities::k3v0) != 0) + fRegisters->power_control.SetVoltage(PowerControl::k3v0); + else if ((supportedVoltages & Capabilities::k1v8) != 0) + fRegisters->power_control.SetVoltage(PowerControl::k1v8); + else { + fRegisters->power_control.PowerOff(); + ERROR("No voltage is supported\n"); + return false; + } + + return true; +} + + +void +SdhciBus::RecoverError() +{ + fRegisters->interrupt_signal_enable &= ~(SDHCI_INT_CMD_CMP + | SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM); + + if (fRegisters->interrupt_status & 7) + fRegisters->software_reset.ResetCommandLine(); + + int16_t error_status = fRegisters->interrupt_status; + fRegisters->interrupt_status &= ~(error_status); +} + + +int32 +SdhciBus::HandleInterrupt() +{ +#if 0 + // We could use the slot register to quickly see for which slot the + // interrupt is. But since we have an interrupt handler call for each slot + // anyway, it's just as simple to let each of them scan its own interrupt + // status register. + if ( !(fRegisters->slot_interrupt_status & (1 << fSlot)) ) { + TRACE("interrupt not for me.\n"); + return B_UNHANDLED_INTERRUPT; + } +#endif + + uint32_t intmask = fRegisters->interrupt_status; + + // Shortcut: exit early if there is no interrupt or if the register is + // clearly invalid. + if ((intmask == 0) || (intmask == 0xffffffff)) { + return B_UNHANDLED_INTERRUPT; + } + + TRACE("interrupt function called %x\n", intmask); + + // handling card presence interrupts + if ((intmask & SDHCI_INT_CARD_REM) != 0) { + // We can get spurious interrupts as the card is inserted or removed, + // so check the actual state before acting + if (!fRegisters->present_state.IsCardInserted()) + fRegisters->power_control.PowerOff(); + else + TRACE("Card removed interrupt, but card is inserted\n"); + + fRegisters->interrupt_status |= SDHCI_INT_CARD_REM; + TRACE("Card removal interrupt handled\n"); + } + + if ((intmask & SDHCI_INT_CARD_INS) != 0) { + // We can get spurious interrupts as the card is inserted or removed, + // so check the actual state before acting + if (fRegisters->present_state.IsCardInserted()) { + if (PowerOn()) + SetClock(400); + release_sem_etc(fScanSemaphore, 1, B_DO_NOT_RESCHEDULE); + } else + TRACE("Card insertion interrupt, but card is removed\n"); + + fRegisters->interrupt_status |= SDHCI_INT_CARD_INS; + TRACE("Card presence interrupt handled\n"); + } + + // handling command interrupt + if (intmask & SDHCI_INT_CMD_MASK) { + fCommandResult = intmask; + // Save the status before clearing so the thread can handle it + fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK); + + // Notify the thread + release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE); + TRACE("Command complete interrupt handled\n"); + } + + if (intmask & SDHCI_INT_TRANS_CMP) { + fCommandResult = intmask; + fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP; + release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE); + TRACE("Transfer complete interrupt handled\n"); + } + + // handling bus power interrupt + if (intmask & SDHCI_INT_BUS_POWER) { + fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER; + TRACE("card is consuming too much power\n"); + } + + // Check that all interrupts have been cleared (we check all the ones we + // enabled, so that should always be the case) + intmask = fRegisters->interrupt_status; + if (intmask != 0) { + ERROR("Remaining interrupts at end of handler: %x\n", intmask); + } + + return B_HANDLED_INTERRUPT; +} + +status_t +SdhciBus::_WorkerThread(void* cookie) { + SdhciBus* bus = (SdhciBus*)cookie; + while (bus->fStatus != B_SHUTTING_DOWN) { + uint32_t intmask = bus->fRegisters->interrupt_status; + if (intmask & SDHCI_INT_CMD_CMP) { + bus->fCommandResult = intmask; + bus->fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK); + release_sem(bus->fSemaphore); + } + if (intmask & SDHCI_INT_TRANS_CMP) { + bus->fCommandResult = intmask; + bus->fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP; + release_sem(bus->fSemaphore); + } + snooze(100); + } + TRACE("poller thread terminating"); + return B_OK; +} + +// #pragma mark - + +void +uninit_bus(void* bus_cookie) +{ + SdhciBus* bus = (SdhciBus*)bus_cookie; + delete bus; + + // FIXME do we need to put() the PCI module here? +} + + +void +bus_removed(void* bus_cookie) +{ + return; +} + +static status_t +register_child_devices(void* cookie) +{ + CALLED(); + SdhciDevice* context = (SdhciDevice*)cookie; + status_t status = B_OK; + const char* bus; + device_node* parent = gDeviceManager->get_parent_node(context->fNode); + status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false); + if (status != B_OK) { + TRACE("Could not find required attribute device/bus\n"); + return status; + } + + if (strcmp(bus, "pci") == 0) + status = register_child_devices_pci(cookie); + else if (strcmp(bus, "acpi") == 0) + status = register_child_devices_acpi(cookie); + else + status = B_BAD_VALUE; + + return status; +} + +static status_t +init_device(device_node* node, void** device_cookie) +{ + CALLED(); + + SdhciDevice* context = new(std::nothrow)SdhciDevice; + if (context == NULL) + return B_NO_MEMORY; + context->fNode = node; + *device_cookie = context; + + status_t status = B_OK; + const char* bus; + device_node* parent = gDeviceManager->get_parent_node(node); + status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false); + if (status != B_OK) { + TRACE("Could not find required attribute device/bus\n"); + return status; + } + + if (strcmp(bus, "pci") == 0) + return init_device_pci(node, context); + + return B_OK; +} + +static void +uninit_device(void* device_cookie) +{ + SdhciDevice* context = (SdhciDevice*)device_cookie; + device_node* parent = gDeviceManager->get_parent_node(context->fNode); + + const char* bus; + if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK) { + TRACE("Could not find required attribute device/bus\n"); + } + + if (strcmp(bus, "pci") == 0) + uninit_device_pci(context, parent); + + gDeviceManager->put_node(parent); + + delete context; +} + + +static status_t +register_device(device_node* parent) +{ + device_attr attrs[] = { + {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "SD Host Controller"}}, + {} + }; + + return gDeviceManager->register_node(parent, SDHCI_DEVICE_MODULE_NAME, + attrs, NULL, NULL); +} + +static float +supports_device(device_node* parent) +{ + const char* bus; + + // make sure parent is either an ACPI or PCI SDHCI device node + if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) + != B_OK) { + TRACE("Could not find required attribute device/bus\n"); + return -1; + } + + if (strcmp(bus, "pci") == 0) + return supports_device_pci(parent); + else if (strcmp(bus, "acpi") == 0) + return supports_device_acpi(parent); + + return 0.0f; +} + + +module_dependency module_dependencies[] = { + { MMC_BUS_MODULE_NAME, (module_info**)&gMMCBusController}, + { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager }, + {} +}; + +status_t +set_clock(void* controller, uint32_t kilohertz) +{ + SdhciBus* bus = (SdhciBus*)controller; + bus->SetClock(kilohertz); + return B_OK; +} + + +status_t +execute_command(void* controller, uint8_t command, uint32_t argument, + uint32_t* response) +{ + SdhciBus* bus = (SdhciBus*)controller; + return bus->ExecuteCommand(command, argument, response); +} + + +status_t +do_io(void* controller, uint8_t command, IOOperation* operation, + bool offsetAsSectors) +{ + SdhciBus* bus = (SdhciBus*)controller; + return bus->DoIO(command, operation, offsetAsSectors); +} + + +void +set_scan_semaphore(void* controller, sem_id sem) +{ + SdhciBus* bus = (SdhciBus*)controller; + return bus->SetScanSemaphore(sem); +} + + +void +set_bus_width(void* controller, int width) +{ + SdhciBus* bus = (SdhciBus*)controller; + return bus->SetBusWidth(width); +} + +// Root device that binds to the ACPI or PCI bus. It will register an mmc_bus_interface +// node for each SD slot in the device. +static driver_module_info sSDHCIDevice = { + { + SDHCI_DEVICE_MODULE_NAME, + 0, + NULL + }, + supports_device, + register_device, + init_device, + uninit_device, + register_child_devices, + NULL, // rescan + NULL, // device removed +}; + + +module_info* modules[] = { + (module_info* )&sSDHCIDevice, + (module_info* )&gSDHCIPCIDeviceModule, + (module_info* )&gSDHCIACPIDeviceModule, + NULL +}; diff --git a/src/add-ons/kernel/busses/mmc/sdhci_pci.h b/src/add-ons/kernel/busses/mmc/sdhci.h similarity index 80% rename from src/add-ons/kernel/busses/mmc/sdhci_pci.h rename to src/add-ons/kernel/busses/mmc/sdhci.h index 3a04de6df4..aa6b13dce0 100644 --- a/src/add-ons/kernel/busses/mmc/sdhci_pci.h +++ b/src/add-ons/kernel/busses/mmc/sdhci.h @@ -1,12 +1,13 @@ /* - * Copyright 2018-2020 Haiku, Inc. All rights reserved. + * Copyright 2018-2024 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * B Krishnan Iyer, krishnaniyer97@gmail.com + * Ron Ben Aroya, sed4906birdie@gmail.com */ -#ifndef _SDHCI_PCI_H -#define _SDHCI_PCI_H +#ifndef _SDHCI_H +#define _SDHCI_H #include @@ -27,6 +28,46 @@ #define SDHCI_BUS_TYPE_NAME "bus/sdhci/v1" +class SdhciBus { + public: + SdhciBus(struct registers* registers, uint8_t irq, bool poll); + ~SdhciBus(); + + void EnableInterrupts(uint32_t mask); + void DisableInterrupts(); + status_t ExecuteCommand(uint8_t command, uint32_t argument, + uint32_t* response); + int32 HandleInterrupt(); + status_t InitCheck(); + void Reset(); + void SetClock(int kilohertz); + status_t DoIO(uint8_t command, IOOperation* operation, + bool offsetAsSectors); + void SetScanSemaphore(sem_id sem); + void SetBusWidth(int width); + + private: + bool PowerOn(); + void RecoverError(); + static status_t _WorkerThread(void*); + + private: + struct registers* fRegisters; + uint32_t fCommandResult; + uint8_t fIrq; + sem_id fSemaphore; + sem_id fScanSemaphore; + status_t fStatus; + thread_id fWorkerThread; +}; + + +class SdhciDevice { + public: + device_node* fNode; + uint8_t fRicohOriginalMode; +}; + class TransferMode { public: uint16_t Bits() { return fBits; } @@ -368,6 +409,42 @@ struct registers { typedef void* sdhci_mmc_bus; +struct sdhci_crs { + uint8 irq; +// uint8 irq_triggering; +// uint8 irq_polarity; +// uint8 irq_shareable; + uint32 addr_bas; + uint32 addr_len; +}; -#endif /*_SDHCI_PCI_H*/ +extern float supports_device_acpi(device_node* parent); +extern float supports_device_pci(device_node* parent); + +extern status_t register_child_devices_acpi(void* cookie); +extern status_t register_child_devices_pci(void* cookie); + +extern status_t init_device_pci(device_node* node, SdhciDevice* context); +extern void uninit_device_pci(SdhciDevice* context, device_node* pciParent); + +extern status_t init_bus_acpi(device_node* node, void** bus_cookie); +extern status_t init_bus_pci(device_node* node, void** bus_cookie); + +extern void uninit_bus(void* bus_cookie); +extern void bus_removed(void* bus_cookie); + +status_t set_clock(void* controller, uint32_t kilohertz); +status_t execute_command(void* controller, uint8_t command, + uint32_t argument, uint32_t* response); +status_t do_io(void* controller, uint8_t command, + IOOperation* operation, bool offsetAsSectors); +void set_scan_semaphore(void* controller, sem_id sem); +void set_bus_width(void* controller, int width); + +extern mmc_bus_interface gSDHCIACPIDeviceModule; +extern mmc_bus_interface gSDHCIPCIDeviceModule; + +extern device_manager_info* gDeviceManager; + +#endif /*_SDHCI_H*/ diff --git a/src/add-ons/kernel/busses/mmc/sdhci_acpi.cpp b/src/add-ons/kernel/busses/mmc/sdhci_acpi.cpp new file mode 100644 index 0000000000..79f58da503 --- /dev/null +++ b/src/add-ons/kernel/busses/mmc/sdhci_acpi.cpp @@ -0,0 +1,245 @@ +/* + * Copyright 2018-2024 Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * B Krishnan Iyer, krishnaniyer97@gmail.com + * Adrien Destugues, pulkomandy@pulkomandy.tk + * Ron Ben Aroya, sed4906birdie@gmail.com + */ +#include +#include +#include +#include + +#include +#include +#include "acpi.h" + +#include + +#include "IOSchedulerSimple.h" +#include "mmc.h" +#include "sdhci.h" + + +#define TRACE_SDHCI +#ifdef TRACE_SDHCI +# define TRACE(x...) dprintf("\33[33msdhci:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci:\33[0m " x) +#define ERROR(x...) dprintf("\33[33msdhci:\33[0m " x) +#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__) + + +#define SDHCI_DEVICE_MODULE_NAME "busses/mmc/sdhci/driver_v1" +#define SDHCI_ACPI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/acpi/device/v1" + +static acpi_status +sdhci_acpi_scan_parse_callback(ACPI_RESOURCE *res, void *context) +{ + struct sdhci_crs* crs = (struct sdhci_crs*)context; + + if (res->Type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) { + crs->addr_bas = res->Data.FixedMemory32.Address; + crs->addr_len = res->Data.FixedMemory32.AddressLength; + } else if (res->Type == ACPI_RESOURCE_TYPE_IRQ) { + crs->irq = res->Data.Irq.Interrupt; + //crs->irq_triggering = res->Data.Irq.Triggering; + //crs->irq_polarity = res->Data.Irq.Polarity; + //crs->irq_shareable = res->Data.Irq.Shareable; + } else if (res->Type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { + crs->irq = res->Data.ExtendedIrq.Interrupt; + //crs->irq_triggering = res->Data.ExtendedIrq.Triggering; + //crs->irq_polarity = res->Data.ExtendedIrq.Polarity; + //crs->irq_shareable = res->Data.ExtendedIrq.Shareable; + } + + return B_OK; +} + +status_t +init_bus_acpi(device_node* node, void** bus_cookie) +{ + CALLED(); + + // Get the ACPI driver and device + acpi_device_module_info* acpi; + acpi_device device; + + device_node* parent = gDeviceManager->get_parent_node(node); + device_node* acpiParent = gDeviceManager->get_parent_node(parent); + gDeviceManager->get_driver(acpiParent, (driver_module_info**)&acpi, + (void**)&device); + gDeviceManager->put_node(acpiParent); + gDeviceManager->put_node(parent); + + // Ignore invalid bars + TRACE("Register SD bus\n"); + + struct sdhci_crs crs; + if(acpi->walk_resources(device, (ACPI_STRING)"_CRS", + sdhci_acpi_scan_parse_callback, &crs) != B_OK) { + ERROR("Couldn't scan ACPI register set\n"); + return B_IO_ERROR; + } + + TRACE("addr: %" B_PRIx32 " len: %" B_PRIx32 "\n", crs.addr_bas, crs.addr_len); + + if (crs.addr_bas == 0 || crs.addr_len == 0) { + ERROR("No registers to map\n"); + return B_IO_ERROR; + } + + // map the slot registers + area_id regs_area; + struct registers* _regs; + regs_area = map_physical_memory("sdhc_regs_map", + crs.addr_bas, crs.addr_len, B_ANY_KERNEL_BLOCK_ADDRESS, + B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&_regs); + + if (regs_area < B_OK) { + ERROR("Could not map registers\n"); + return B_BAD_VALUE; + } + + // the interrupt is shared between all busses in an SDHC controller, but + // they each register an handler. Not a problem, we will just test the + // interrupt registers for all busses one after the other and find no + // interrupts on the idle busses. + uint8_t irq = crs.irq; + TRACE("irq interrupt line: %d\n", irq); + + SdhciBus* bus = new(std::nothrow) SdhciBus(_regs, irq, true); + + status_t status = B_NO_MEMORY; + if (bus != NULL) + status = bus->InitCheck(); + + if (status != B_OK) { + if (bus != NULL) + delete bus; + else + delete_area(regs_area); + return status; + } + + // Store the created object as a cookie, allowing users of the bus to + // locate it. + *bus_cookie = bus; + + return status; +} + +status_t +register_child_devices_acpi(void* cookie) +{ + CALLED(); + SdhciDevice* context = (SdhciDevice*)cookie; + device_node* parent = gDeviceManager->get_parent_node(context->fNode); + acpi_device_module_info* acpi; + acpi_device* device; + + gDeviceManager->get_driver(parent, (driver_module_info**)&acpi, + (void**)&device); + + TRACE("register_child_devices\n"); + + char prettyName[25]; + + sprintf(prettyName, "SDHC bus"); + device_attr attrs[] = { + // properties of this controller for mmc bus manager + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = prettyName } }, + { B_DEVICE_FIXED_CHILD, B_STRING_TYPE, + {.string = MMC_BUS_MODULE_NAME} }, + { B_DEVICE_BUS, B_STRING_TYPE, {.string = "mmc"} }, + + // DMA properties + // The high alignment is to force access only to complete sectors + // These constraints could be removed by using ADMA which allows + // use of the full 64bit address space and can do scatter-gather. + { B_DMA_ALIGNMENT, B_UINT32_TYPE, { .ui32 = 511 }}, + { B_DMA_HIGH_ADDRESS, B_UINT64_TYPE, { .ui64 = 0x100000000LL }}, + { B_DMA_BOUNDARY, B_UINT32_TYPE, { .ui32 = (1 << 19) - 1 }}, + { B_DMA_MAX_SEGMENT_COUNT, B_UINT32_TYPE, { .ui32 = 1 }}, + { B_DMA_MAX_SEGMENT_BLOCKS, B_UINT32_TYPE, { .ui32 = (1 << 10) - 1 }}, + + // private data to identify device + { NULL } + }; + device_node* node; + if (gDeviceManager->register_node(context->fNode, + SDHCI_ACPI_MMC_BUS_MODULE_NAME, attrs, NULL, + &node) != B_OK) + return B_BAD_VALUE; + return B_OK; +} + +float +supports_device_acpi(device_node* parent) +{ + const char* hid; + const char* uid; + uint32 type; + + if (gDeviceManager->get_attr_uint32(parent, ACPI_DEVICE_TYPE_ITEM, &type, false) + || type != ACPI_TYPE_DEVICE) { + return 0.0f; + } + + if (gDeviceManager->get_attr_string(parent, ACPI_DEVICE_HID_ITEM, &hid, false)) { + TRACE("No hid attribute\n"); + return 0.0f; + } + + if (gDeviceManager->get_attr_string(parent, ACPI_DEVICE_UID_ITEM, &uid, false)) { + TRACE("No uid attribute\n"); + return 0.0f; + } + + TRACE("supports_device(hid:%s uid:%s)\n", hid, uid); + + if (!(strcmp(hid, "80860F14") == 0 + || strcmp(hid, "80860F16") == 0 + || strcmp(hid, "80865ACA") == 0 + || strcmp(hid, "80865AD0") == 0 + || strcmp(hid, "INT33C6") == 0 + || strcmp(hid, "INT3436") == 0 + || strcmp(hid, "INT344D") == 0 + || strcmp(hid, "INT33BB") == 0 + || strcmp(hid, "NXP0003") == 0 + || strcmp(hid, "RKCP0D40") == 0 + || strcmp(hid, "PNP0D40") == 0)) + return 0.0f; + + acpi_device_module_info* acpi; + acpi_device* device; + gDeviceManager->get_driver(parent, (driver_module_info**)&acpi, + (void**)&device); + TRACE("SDHCI Device found! hid: %s, uid: %s\n", hid, uid); + + return 0.8f; +} + +// Device node registered for each SD slot. It implements the MMC operations so +// the bus manager can use it to communicate with SD cards. +mmc_bus_interface gSDHCIACPIDeviceModule = { + .info = { + .info = { + .name = SDHCI_ACPI_MMC_BUS_MODULE_NAME, + }, + + .init_driver = init_bus_acpi, + .uninit_driver = uninit_bus, + .device_removed = bus_removed, + }, + + .set_clock = set_clock, + .execute_command = execute_command, + .do_io = do_io, + .set_scan_semaphore = set_scan_semaphore, + .set_bus_width = set_bus_width, +}; \ No newline at end of file diff --git a/src/add-ons/kernel/busses/mmc/sdhci_pci.cpp b/src/add-ons/kernel/busses/mmc/sdhci_pci.cpp index 29eaadc4c8..9805633fc5 100644 --- a/src/add-ons/kernel/busses/mmc/sdhci_pci.cpp +++ b/src/add-ons/kernel/busses/mmc/sdhci_pci.cpp @@ -1,10 +1,11 @@ /* - * Copyright 2018-2020 Haiku, Inc. All rights reserved. + * Copyright 2018-2024 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * B Krishnan Iyer, krishnaniyer97@gmail.com * Adrien Destugues, pulkomandy@pulkomandy.tk + * Ron Ben Aroya, sed4906birdie@gmail.com */ #include #include @@ -12,637 +13,35 @@ #include #include +#include +#include "acpi.h" #include #include "IOSchedulerSimple.h" #include "mmc.h" -#include "sdhci_pci.h" +#include "sdhci.h" #define TRACE_SDHCI #ifdef TRACE_SDHCI -# define TRACE(x...) dprintf("\33[33msdhci_pci:\33[0m " x) +# define TRACE(x...) dprintf("\33[33msdhci:\33[0m " x) #else # define TRACE(x...) ; #endif -#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci_pci:\33[0m " x) -#define ERROR(x...) dprintf("\33[33msdhci_pci:\33[0m " x) +#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci:\33[0m " x) +#define ERROR(x...) dprintf("\33[33msdhci:\33[0m " x) #define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__) -#define SDHCI_PCI_DEVICE_MODULE_NAME "busses/mmc/sdhci_pci/driver_v1" -#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci_pci/device/v1" +#define SDHCI_DEVICE_MODULE_NAME "busses/mmc/sdhci/driver_v1" +#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/pci/device/v1" #define SLOT_NUMBER "device/slot" #define BAR_INDEX "device/bar" - -class SdhciBus { - public: - SdhciBus(struct registers* registers, uint8_t irq); - ~SdhciBus(); - - void EnableInterrupts(uint32_t mask); - void DisableInterrupts(); - status_t ExecuteCommand(uint8_t command, uint32_t argument, - uint32_t* response); - int32 HandleInterrupt(); - status_t InitCheck(); - void Reset(); - void SetClock(int kilohertz); - status_t DoIO(uint8_t command, IOOperation* operation, - bool offsetAsSectors); - void SetScanSemaphore(sem_id sem); - void SetBusWidth(int width); - - private: - bool PowerOn(); - void RecoverError(); - - private: - struct registers* fRegisters; - uint32_t fCommandResult; - uint8_t fIrq; - sem_id fSemaphore; - sem_id fScanSemaphore; - status_t fStatus; -}; - - -class SdhciDevice { - public: - device_node* fNode; - uint8_t fRicohOriginalMode; -}; - - -device_manager_info* gDeviceManager; -device_module_info* gMMCBusController; - - -static int32 -sdhci_generic_interrupt(void* data) -{ - SdhciBus* bus = (SdhciBus*)data; - return bus->HandleInterrupt(); -} - - -SdhciBus::SdhciBus(struct registers* registers, uint8_t irq) - : - fRegisters(registers), - fIrq(irq), - fSemaphore(0) -{ - if (irq == 0 || irq == 0xff) { - ERROR("PCI IRQ not assigned\n"); - fStatus = B_BAD_DATA; - return; - } - - fSemaphore = create_sem(0, "SDHCI interrupts"); - - DisableInterrupts(); - - fStatus = install_io_interrupt_handler(fIrq, - sdhci_generic_interrupt, this, 0); - - if (fStatus != B_OK) { - ERROR("can't install interrupt handler\n"); - return; - } - - // First of all, we have to make sure we are in a sane state. The easiest - // way is to reset everything. - Reset(); - - // Turn on the power supply to the card, if there is a card inserted - if (PowerOn()) { - // Then we configure the clock to the frequency needed for - // initialization - SetClock(400); - } - - // Finally, configure some useful interrupts - EnableInterrupts(SDHCI_INT_CMD_CMP | SDHCI_INT_CARD_REM - | SDHCI_INT_TRANS_CMP); - - // We want to see the error bits in the status register, but not have an - // interrupt trigger on them (we get a "command complete" interrupt on - // errors already) - fRegisters->interrupt_status_enable |= SDHCI_INT_ERROR - | SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX - | SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT; -} - - -SdhciBus::~SdhciBus() -{ - DisableInterrupts(); - - if (fSemaphore != 0) - delete_sem(fSemaphore); - - if (fIrq != 0) - remove_io_interrupt_handler(fIrq, sdhci_generic_interrupt, this); - - area_id regs_area = area_for(fRegisters); - delete_area(regs_area); -} - - -void -SdhciBus::EnableInterrupts(uint32_t mask) -{ - fRegisters->interrupt_status_enable |= mask; - fRegisters->interrupt_signal_enable |= mask; -} - - -void -SdhciBus::DisableInterrupts() -{ - fRegisters->interrupt_status_enable = 0; - fRegisters->interrupt_signal_enable = 0; -} - - -// #pragma mark - -/* -PartA2, SD Host Controller Simplified Specification, Version 4.20 -§3.7.1.1 The sequence to issue an SD Command -*/ status_t -SdhciBus::ExecuteCommand(uint8_t command, uint32_t argument, uint32_t* response) -{ - TRACE("ExecuteCommand(%d, %x)\n", command, argument); - - // First of all clear the result - fCommandResult = 0; - - // Check if it's possible to send a command right now. - // It is not possible to send a command as long as the command line is busy. - // The spec says we should wait, but we can't do that on kernel side, since - // it leaves no chance for the upper layers to handle the problem. So we - // just say we're busy and the caller can retry later. - // Note that this should normally never happen: the command line is busy - // only during command execution, and we don't leave this function with ac - // command running. - if (fRegisters->present_state.CommandInhibit()) { - panic("Command execution impossible, command inhibit\n"); - return B_BUSY; - } - if (fRegisters->present_state.DataInhibit()) { - panic("Command execution unwise, data inhibit\n"); - return B_BUSY; - } - - uint32_t replyType; - - switch (command) { - case SD_GO_IDLE_STATE: - replyType = Command::kNoReplyType; - break; - case SD_ALL_SEND_CID: - case SD_SEND_CSD: - replyType = Command::kR2Type; - break; - case SD_SEND_RELATIVE_ADDR: - replyType = Command::kR6Type; - break; - case SD_SELECT_DESELECT_CARD: - case SD_ERASE: - replyType = Command::kR1bType; - break; - case SD_SEND_IF_COND: - replyType = Command::kR7Type; - break; - case SD_READ_SINGLE_BLOCK: - case SD_READ_MULTIPLE_BLOCKS: - case SD_WRITE_SINGLE_BLOCK: - case SD_WRITE_MULTIPLE_BLOCKS: - replyType = Command::kR1Type | Command::kDataPresent; - break; - case SD_APP_CMD: - case SD_ERASE_WR_BLK_START: - case SD_ERASE_WR_BLK_END: - case SD_SET_BUS_WIDTH: // SD Application command - replyType = Command::kR1Type; - break; - case SD_SEND_OP_COND: // SD Application command - replyType = Command::kR3Type; - break; - default: - ERROR("Unknown command %x\n", command); - return B_BAD_DATA; - } - - // Check if DATA line is available (if needed) - if ((replyType & Command::k32BitResponseCheckBusy) != 0 - && command != SD_STOP_TRANSMISSION && command != SD_IO_ABORT) { - if (fRegisters->present_state.DataInhibit()) { - ERROR("Execution aborted, data inhibit\n"); - return B_BUSY; - } - } - - if (fRegisters->present_state.CommandInhibit()) - panic("Command line busy at start of execute command\n"); - - if (replyType == Command::kR1bType) - fRegisters->transfer_mode = 0; - - fRegisters->argument = argument; - fRegisters->command.SendCommand(command, replyType); - - // Wait for command response to be available ("command complete" interrupt) - TRACE("Wait for command complete..."); - do { - acquire_sem(fSemaphore); - TRACE("command complete sem acquired, status: %x\n", fCommandResult); - TRACE("real status = %x command line busy: %d\n", - fRegisters->interrupt_status, - fRegisters->present_state.CommandInhibit()); - } while (fCommandResult == 0); - - TRACE("Command response available\n"); - - if (fCommandResult & SDHCI_INT_ERROR) { - fRegisters->interrupt_status |= fCommandResult; - if (fCommandResult & SDHCI_INT_TIMEOUT) { - ERROR("Command execution timed out\n"); - if (fRegisters->present_state.CommandInhibit()) { - TRACE("Command line is still busy, clearing it\n"); - // Clear the stall - fRegisters->software_reset.ResetCommandLine(); - } - return B_TIMED_OUT; - } - if (fCommandResult & SDHCI_INT_CRC) { - ERROR("CRC error\n"); - return B_BAD_VALUE; - } - ERROR("Command execution failed %x\n", fCommandResult); - // TODO look at errors in interrupt_status register for more details - // and return a more appropriate error code - return B_ERROR; - } - - if (fRegisters->present_state.CommandInhibit()) { - TRACE("Command execution failed, card stalled\n"); - // Clear the stall - fRegisters->software_reset.ResetCommandLine(); - return B_ERROR; - } - - switch (replyType & Command::kReplySizeMask) { - case Command::k32BitResponse: - *response = fRegisters->response[0]; - break; - case Command::k128BitResponse: - response[0] = fRegisters->response[0]; - response[1] = fRegisters->response[1]; - response[2] = fRegisters->response[2]; - response[3] = fRegisters->response[3]; - break; - - default: - // No response - break; - } - - if (replyType == Command::kR1bType - && (fCommandResult & SDHCI_INT_TRANS_CMP) == 0) { - // R1b commands may use the data line so we must wait for the - // "transfer complete" interrupt here. - TRACE("Waiting for data line...\n"); - do { - acquire_sem(fSemaphore); - } while (fRegisters->present_state.DataInhibit()); - TRACE("Dataline is released.\n"); - } - - ERROR("Command execution %d complete\n", command); - return B_OK; -} - - -status_t -SdhciBus::InitCheck() -{ - return fStatus; -} - - -void -SdhciBus::Reset() -{ - if (!fRegisters->software_reset.ResetAll()) - ERROR("SdhciBus::Reset: SoftwareReset timeout\n"); -} - - -void -SdhciBus::SetClock(int kilohertz) -{ - int base_clock = fRegisters->capabilities.BaseClockFrequency(); - // Try to get as close to 400kHz as possible, but not faster - int divider = base_clock * 1000 / kilohertz; - - if (fRegisters->host_controller_version.specVersion <= 1) { - // Old controller only support power of two dividers up to 256, - // round to next power of two up to 256 - if (divider > 256) - divider = 256; - - divider--; - divider |= divider >> 1; - divider |= divider >> 2; - divider |= divider >> 4; - divider++; - } - - divider = fRegisters->clock_control.SetDivider(divider); - - // Log the value after possible rounding by SetDivider (only even values - // are allowed). - TRACE("SDCLK frequency: %dMHz / %d = %dkHz\n", base_clock, divider, - base_clock * 1000 / divider); - - // We have set the divider, now we can enable the internal clock. - fRegisters->clock_control.EnableInternal(); - - // wait until internal clock is stabilized - while (!(fRegisters->clock_control.InternalStable())); - - fRegisters->clock_control.EnablePLL(); - while (!(fRegisters->clock_control.InternalStable())); - - // Finally, route the clock to the SD card - fRegisters->clock_control.EnableSD(); -} - - -status_t -SdhciBus::DoIO(uint8_t command, IOOperation* operation, bool offsetAsSectors) -{ - bool isWrite = operation->IsWrite(); - - static const uint32 kBlockSize = 512; - off_t offset = operation->Offset(); - generic_size_t length = operation->Length(); - - TRACE("%s %" B_PRIu64 " bytes at %" B_PRIdOFF "\n", - isWrite ? "Write" : "Read", length, offset); - - // Check that the IO scheduler did its job in following our DMA restrictions - // We can start a read only at a sector boundary - ASSERT(offset % kBlockSize == 0); - // We can only read complete sectors - ASSERT(length % kBlockSize == 0); - - const generic_io_vec* vecs = operation->Vecs(); - generic_size_t vecOffset = 0; - - // FIXME can this be moved to the init function instead? - // - // For simplicity we use a transfer size equal to the sector size. We could - // go up to 2K here if the length to read in each individual vec is a - // multiple of 2K, but we have no easy way to know this (we would need to - // iterate through the IOOperation vecs and check the size of each of them). - // We could also do smaller transfers, but it is not possible to start a - // transfer anywhere else than the start of a sector, so it's a lot simpler - // to always work in complete sectors. We set the B_DMA_ALIGNMENT device - // node property accordingly, making sure that we don't get asked to do - // transfers that are not aligned with sectors. - // - // Additionnally, set SDMA buffer boundary aligment to 512K. This is the - // largest possible size. We also set the B_DMA_BOUNDARY property on the - // published device node, so that the DMA resource manager knows that it - // must respect this boundary. As a result, we will never be asked to - // do a transfer that crosses this boundary, and we don't need to handle - // the DMA boundary interrupt (the transfer will be split in two at an - // upper layer). - fRegisters->block_size.ConfigureTransfer(kBlockSize, - BlockSize::kDmaBoundary512K); - status_t result = B_OK; - - while (length > 0) { - size_t toCopy = std::min((generic_size_t)length, - vecs->length - vecOffset); - - // If the current vec is empty, we can move to the next - if (toCopy == 0) { - vecs++; - vecOffset = 0; - continue; - } - - // With SDMA we can only transfer multiples of 1 sector - ASSERT(toCopy % kBlockSize == 0); - - fRegisters->system_address = vecs->base + vecOffset; - // fRegisters->adma_system_address = fDmaMemory; - - fRegisters->block_count = toCopy / kBlockSize; - - uint16 direction; - if (isWrite) - direction = TransferMode::kWrite; - else - direction = TransferMode::kRead; - fRegisters->transfer_mode = TransferMode::kMulti | direction - | TransferMode::kAutoCmd12Enable - | TransferMode::kBlockCountEnable | TransferMode::kDmaEnable; - - uint32_t response; - result = ExecuteCommand(command, - offset / (offsetAsSectors ? kBlockSize : 1), &response); - if (result != B_OK) - break; - - // Wait for DMA transfer to complete - // In theory we could go on and send other commands as long as they - // don't need the DAT lines, but it's overcomplicating things. - TRACE("Wait for transfer complete..."); - acquire_sem(fSemaphore); - TRACE("transfer complete OK.\n"); - - length -= toCopy; - vecOffset += toCopy; - offset += toCopy; - } - - return result; -} - - -void -SdhciBus::SetScanSemaphore(sem_id sem) -{ - fScanSemaphore = sem; - - // If there is already a card in, start a scan immediately - if (fRegisters->present_state.IsCardInserted()) - release_sem(fScanSemaphore); - - // We can now enable the card insertion interrupt for next time a card - // is inserted - EnableInterrupts(SDHCI_INT_CARD_INS); -} - - -void -SdhciBus::SetBusWidth(int width) -{ - uint8_t widthBits; - switch(width) { - case 1: - widthBits = HostControl::kDataTransfer1Bit; - break; - case 4: - widthBits = HostControl::kDataTransfer4Bit; - break; - case 8: - widthBits = HostControl::kDataTransfer8Bit; - break; - default: - panic("Incorrect bitwidth value"); - return; - } - fRegisters->host_control.SetDataTransferWidth(widthBits); -} - - -bool -SdhciBus::PowerOn() -{ - if (!fRegisters->present_state.IsCardInserted()) { - TRACE("Card not inserted, not powering on for now\n"); - return false; - } - - uint8_t supportedVoltages = fRegisters->capabilities.SupportedVoltages(); - if ((supportedVoltages & Capabilities::k3v3) != 0) - fRegisters->power_control.SetVoltage(PowerControl::k3v3); - else if ((supportedVoltages & Capabilities::k3v0) != 0) - fRegisters->power_control.SetVoltage(PowerControl::k3v0); - else if ((supportedVoltages & Capabilities::k1v8) != 0) - fRegisters->power_control.SetVoltage(PowerControl::k1v8); - else { - fRegisters->power_control.PowerOff(); - ERROR("No voltage is supported\n"); - return false; - } - - return true; -} - - -void -SdhciBus::RecoverError() -{ - fRegisters->interrupt_signal_enable &= ~(SDHCI_INT_CMD_CMP - | SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM); - - if (fRegisters->interrupt_status & 7) - fRegisters->software_reset.ResetCommandLine(); - - int16_t error_status = fRegisters->interrupt_status; - fRegisters->interrupt_status &= ~(error_status); -} - - -int32 -SdhciBus::HandleInterrupt() -{ -#if 0 - // We could use the slot register to quickly see for which slot the - // interrupt is. But since we have an interrupt handler call for each slot - // anyway, it's just as simple to let each of them scan its own interrupt - // status register. - if ( !(fRegisters->slot_interrupt_status & (1 << fSlot)) ) { - TRACE("interrupt not for me.\n"); - return B_UNHANDLED_INTERRUPT; - } -#endif - - uint32_t intmask = fRegisters->interrupt_status; - - // Shortcut: exit early if there is no interrupt or if the register is - // clearly invalid. - if ((intmask == 0) || (intmask == 0xffffffff)) { - return B_UNHANDLED_INTERRUPT; - } - - TRACE("interrupt function called %x\n", intmask); - - // handling card presence interrupts - if ((intmask & SDHCI_INT_CARD_REM) != 0) { - // We can get spurious interrupts as the card is inserted or removed, - // so check the actual state before acting - if (!fRegisters->present_state.IsCardInserted()) - fRegisters->power_control.PowerOff(); - else - TRACE("Card removed interrupt, but card is inserted\n"); - - fRegisters->interrupt_status |= SDHCI_INT_CARD_REM; - TRACE("Card removal interrupt handled\n"); - } - - if ((intmask & SDHCI_INT_CARD_INS) != 0) { - // We can get spurious interrupts as the card is inserted or removed, - // so check the actual state before acting - if (fRegisters->present_state.IsCardInserted()) { - if (PowerOn()) - SetClock(400); - release_sem_etc(fScanSemaphore, 1, B_DO_NOT_RESCHEDULE); - } else - TRACE("Card insertion interrupt, but card is removed\n"); - - fRegisters->interrupt_status |= SDHCI_INT_CARD_INS; - TRACE("Card presence interrupt handled\n"); - } - - // handling command interrupt - if (intmask & SDHCI_INT_CMD_MASK) { - fCommandResult = intmask; - // Save the status before clearing so the thread can handle it - fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK); - - // Notify the thread - release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE); - TRACE("Command complete interrupt handled\n"); - } - - if (intmask & SDHCI_INT_TRANS_CMP) { - fCommandResult = intmask; - fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP; - release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE); - TRACE("Transfer complete interrupt handled\n"); - } - - // handling bus power interrupt - if (intmask & SDHCI_INT_BUS_POWER) { - fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER; - TRACE("card is consuming too much power\n"); - } - - // Check that all interrupts have been cleared (we check all the ones we - // enabled, so that should always be the case) - intmask = fRegisters->interrupt_status; - if (intmask != 0) { - ERROR("Remaining interrupts at end of handler: %x\n", intmask); - } - - return B_HANDLED_INTERRUPT; -} -// #pragma mark - - - -static status_t -init_bus(device_node* node, void** bus_cookie) +init_bus_pci(device_node* node, void** bus_cookie) { CALLED(); @@ -713,7 +112,7 @@ init_bus(device_node* node, void** bus_cookie) uint8_t irq = pciInfo.u.h0.interrupt_line; TRACE("irq interrupt line: %d\n", irq); - SdhciBus* bus = new(std::nothrow) SdhciBus(_regs, irq); + SdhciBus* bus = new(std::nothrow) SdhciBus(_regs, irq, false); status_t status = B_NO_MEMORY; if (bus != NULL) @@ -734,26 +133,8 @@ init_bus(device_node* node, void** bus_cookie) return status; } - -static void -uninit_bus(void* bus_cookie) -{ - SdhciBus* bus = (SdhciBus*)bus_cookie; - delete bus; - - // FIXME do we need to put() the PCI module here? -} - - -static void -bus_removed(void* bus_cookie) -{ - return; -} - - -static status_t -register_child_devices(void* cookie) +status_t +register_child_devices_pci(void* cookie) { CALLED(); SdhciDevice* context = (SdhciDevice*)cookie; @@ -809,28 +190,19 @@ register_child_devices(void* cookie) return B_OK; } - -static status_t -init_device(device_node* node, void** device_cookie) +status_t +init_device_pci(device_node* node, SdhciDevice* context) { - CALLED(); - // Get the PCI driver and device pci_device_module_info* pci; pci_device* device; uint16 vendorId, deviceId; - device_node* pciParent = gDeviceManager->get_parent_node(node); + device_node* pciParent = gDeviceManager->get_parent_node(context->fNode); gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci, (void**)&device); gDeviceManager->put_node(pciParent); - SdhciDevice* context = new(std::nothrow)SdhciDevice; - if (context == NULL) - return B_NO_MEMORY; - context->fNode = node; - *device_cookie = context; - if (gDeviceManager->get_attr_uint16(node, B_DEVICE_VENDOR_ID, &vendorId, true) != B_OK || gDeviceManager->get_attr_uint16(node, B_DEVICE_ID, &deviceId, @@ -857,17 +229,14 @@ init_device(device_node* node, void** device_cookie) return B_OK; } - -static void -uninit_device(void* device_cookie) +void +uninit_device_pci(SdhciDevice* context, device_node* pciParent) { // Get the PCI driver and device pci_device_module_info* pci; pci_device* device; uint16 vendorId, deviceId; - SdhciDevice* context = (SdhciDevice*)device_cookie; - device_node* pciParent = gDeviceManager->get_parent_node(context->fNode); gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci, (void**)&device); @@ -882,43 +251,14 @@ uninit_device(void* device_cookie) context->fRicohOriginalMode); pci->write_pci_config(device, SDHCI_PCI_RICOH_MODE_KEY, 1, 0); } - - gDeviceManager->put_node(pciParent); - - delete context; } - -static status_t -register_device(device_node* parent) +float +supports_device_pci(device_node* parent) { - device_attr attrs[] = { - {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "SD Host Controller"}}, - {} - }; - - return gDeviceManager->register_node(parent, SDHCI_PCI_DEVICE_MODULE_NAME, - attrs, NULL, NULL); -} - - -static float -supports_device(device_node* parent) -{ - const char* bus; uint16 type, subType; uint16 vendorId, deviceId; - // make sure parent is a PCI SDHCI device node - if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) - != B_OK) { - TRACE("Could not find required attribute device/bus\n"); - return -1; - } - - if (strcmp(bus, "pci") != 0) - return 0.0f; - if (gDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID, &vendorId, false) != B_OK || gDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceId, @@ -961,109 +301,22 @@ supports_device(device_node* parent) return 0.0f; } - -static status_t -set_clock(void* controller, uint32_t kilohertz) -{ - SdhciBus* bus = (SdhciBus*)controller; - bus->SetClock(kilohertz); - return B_OK; -} - - -static status_t -execute_command(void* controller, uint8_t command, uint32_t argument, - uint32_t* response) -{ - SdhciBus* bus = (SdhciBus*)controller; - return bus->ExecuteCommand(command, argument, response); -} - - -static status_t -do_io(void* controller, uint8_t command, IOOperation* operation, - bool offsetAsSectors) -{ - CALLED(); - - SdhciBus* bus = (SdhciBus*)controller; - return bus->DoIO(command, operation, offsetAsSectors); -} - - -static void -set_scan_semaphore(void* controller, sem_id sem) -{ - CALLED(); - - SdhciBus* bus = (SdhciBus*)controller; - return bus->SetScanSemaphore(sem); -} - - -static void -set_bus_width(void* controller, int width) -{ - CALLED(); - - SdhciBus* bus = (SdhciBus*)controller; - return bus->SetBusWidth(width); -} - - -module_dependency module_dependencies[] = { - { MMC_BUS_MODULE_NAME, (module_info**)&gMMCBusController}, - { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager }, - {} -}; - - // Device node registered for each SD slot. It implements the MMC operations so // the bus manager can use it to communicate with SD cards. -static mmc_bus_interface gSDHCIPCIDeviceModule = { - { - { - SDHCI_PCI_MMC_BUS_MODULE_NAME, - 0, - NULL +mmc_bus_interface gSDHCIPCIDeviceModule = { + .info = { + .info = { + .name = SDHCI_PCI_MMC_BUS_MODULE_NAME, }, - NULL, // supports device - NULL, // register device - init_bus, - uninit_bus, - NULL, // register child devices - NULL, // rescan - bus_removed, + + .init_driver = init_bus_pci, + .uninit_driver = uninit_bus, + .device_removed = bus_removed, }, - set_clock, - execute_command, - do_io, - set_scan_semaphore, - set_bus_width -}; - - -// Root device that binds to the PCI bus. It will register an mmc_bus_interface -// node for each SD slot in the device. -static driver_module_info sSDHCIDevice = { - { - SDHCI_PCI_DEVICE_MODULE_NAME, - 0, - NULL - }, - supports_device, - register_device, - init_device, - uninit_device, - register_child_devices, - NULL, // rescan - NULL, // device removed -}; - - -module_info* modules[] = { - (module_info* )&sSDHCIDevice, - (module_info* )&gSDHCIPCIDeviceModule, - NULL -}; + .set_clock = set_clock, + .execute_command = execute_command, + .do_io = do_io, + .set_scan_semaphore = set_scan_semaphore, + .set_bus_width = set_bus_width, +}; \ No newline at end of file diff --git a/src/system/kernel/device_manager/device_manager.cpp b/src/system/kernel/device_manager/device_manager.cpp index d8e046dc4f..3f872dadba 100644 --- a/src/system/kernel/device_manager/device_manager.cpp +++ b/src/system/kernel/device_manager/device_manager.cpp @@ -1770,6 +1770,7 @@ device_node::_GetNextDriverPath(void*& cookie, KPath& _path) _AddPath(*stack, "busses/virtio"); _AddPath(*stack, "bus_managers/pci"); _AddPath(*stack, "busses/pci"); + _AddPath(*stack, "busses/mmc"); } break; }