/* * Copyright (c) 2011-2016, 2022 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * disk/ata/hba.cpp * Driver for ATA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../node.h" #include "hba.h" #include "port.h" #include "registers.h" namespace Sortix { namespace ATA { static unsigned long AllocateDiskNumber() { static unsigned long next_disk_number = 0; return InterlockedIncrement(&next_disk_number).o; } static void sleep_400_nanoseconds(uint16_t port_base) { // TODO: The clock granularity of 10 ms slows down the early boot. #if 0 struct timespec delay = timespec_make(0, 400); Clock* clock = Time::GetClock(CLOCK_BOOTTIME); clock->SleepDelay(delay); #else for ( int i = 0; i < 14; i++ ) inport8(port_base + REG_STATUS); #endif } Channel::Channel() { hw_lock = KTHREAD_MUTEX_INITIALIZER; interrupt_registered = false; drives[0] = NULL; drives[1] = NULL; } Channel::~Channel() { if ( interrupt_registered ) Interrupt::UnregisterHandler(interrupt_index, &interrupt_registration); // TODO: Destroy all the ports? } void Channel::LogF(const char* format, ...) { // TODO: Print this line in an atomic manner. const char* cdesc = channel_index == 0 ? "primary" : "secondary"; Log::PrintF("ata: pci 0x%X: %s channel: ", devaddr, cdesc); va_list ap; va_start(ap, format); Log::PrintFV(format, ap); va_end(ap); Log::PrintF("\n"); } void Channel::SelectDrive(unsigned int drive_index) // hw_lock locked { if ( current_drive == drive_index ) return; #if 0 // TODO: Perhaps not do this here. This appears to time out on boot on real // hardware where there is no drive there. if ( !wait_inport8_clear(port_base + REG_STATUS, STATUS_BUSY | STATUS_DATAREADY, false, 10000 /*ms*/) ) { LogF("error: timed out waiting for idle"); // TODO: This can fail! //return errno = EIO, false; return; } #endif uint8_t value = 0xA0 | (drive_index << 4); outport8(port_base + REG_DRIVE_SELECT, value); //outport8(port_control, value); // TODO: Or is it port_control we use? sleep_400_nanoseconds(port_base); // TODO: Do we need to wait for non-busy now? Can this operation fail? current_drive = drive_index; } void Channel::OnInterrupt() { // Check whether the interrupt came from this channel. uint8_t status = inport8(busmaster_base + BUSMASTER_REG_STATUS); if ( !(status & BUSMASTER_STATUS_INTERRUPT_PENDING) ) return; if ( status & BUSMASTER_STATUS_DMA_FAILURE ) { // TODO: What do we do here? } // Clear interrupt flag. // TODO: This filters away BUSMASTER_STATUS_DMA and // BUSMASTER_STATUS_DMA_FAILURE and might be a bug? //status &= 0xF8 | BUSMASTER_STATUS_DMA; status |= BUSMASTER_STATUS_INTERRUPT_PENDING; outport8(busmaster_base + BUSMASTER_REG_STATUS, status); if ( current_drive < 2 && drives[current_drive] ) drives[current_drive]->OnInterrupt(); } void Channel__OnInterrupt(struct interrupt_context*, void* context) { ((Channel*) context)->OnInterrupt(); } static void FixDefaultDeviceBars(pcibar_t* basebar, pcibar_t* ctrlbar, uint8_t* irq, unsigned int channel_index, uint8_t interface) { bool compatibility = interface == 0x00 || interface == 0x02; if ( compatibility ) *irq = channel_index == 0 ? Interrupt::IRQ14 : Interrupt::IRQ15; if ( compatibility || basebar->addr_raw == 0 || (basebar->is_iospace() && !ctrlbar->ioaddr()) ) { uint16_t ioport = channel_index == 0 ? 0x1F0 : 0x170; basebar->addr_raw = ioport | PCIBAR_TYPE_IOSPACE; basebar->size_raw = 8; } if ( compatibility || ctrlbar->addr_raw == 0 || (ctrlbar->is_iospace() && !ctrlbar->ioaddr()) ) { uint16_t ioport = channel_index == 0 ? 0x3F4 : 0x374; ctrlbar->addr_raw = ioport | PCIBAR_TYPE_IOSPACE; ctrlbar->size_raw = 4; // TODO: This is just a guess. } } bool Channel::Initialize(Ref dev, const char* devpath) { uint8_t prog_if = PCI::Read8(devaddr, PCIFIELD_PROG_IF); uint8_t interface = (prog_if >> (channel_index * 2)) & 0x3; pcibar_t basebar = PCI::GetBAR(devaddr, 2 * channel_index + 0); pcibar_t ctrlbar = PCI::GetBAR(devaddr, 2 * channel_index + 1); pcibar_t busmasterbar = PCI::GetBAR(devaddr, 4); interrupt_index = PCI::SetupInterruptLine(devaddr); FixDefaultDeviceBars(&basebar, &ctrlbar, &interrupt_index, channel_index, interface); if ( !interrupt_index ) { LogF("error: cannot determine interrupt line"); return errno = EINVAL, false; } if ( !basebar.is_iospace() ) { LogF("ignoring: non-iospace base BAR"); return errno = EINVAL, false; } if ( basebar.size() < 8 ) { LogF("ignoring: too small base BAR"); return errno = EINVAL, false; } if ( !ctrlbar.is_iospace() ) { LogF("ignoring: non-iospace control BAR"); return errno = EINVAL, false; } if ( ctrlbar.size() < 4 ) { LogF("ignoring: too small control BAR"); return errno = EINVAL, false; } if ( !busmasterbar.is_iospace() ) { LogF("ignoring: non-iospace bus master BAR"); return errno = EINVAL, false; } if ( busmasterbar.size() < 16 ) { LogF("ignoring: too small bus master BAR"); return errno = EINVAL, false; } port_base = basebar.addr(); if ( inport8(port_base + REG_STATUS) == 0xFF ) return errno = ENODEV, false; // Non-existent. // TODO: Ensure this is the correct logic. port_control = ctrlbar.addr() + 2; busmaster_base = busmasterbar.addr() + 8 * channel_index; current_drive = (inport8(port_base + REG_DRIVE_SELECT) >> 4) & 1; for ( unsigned int i = 0; i < 2; i++ ) { drives[i] = NULL; ScopedLock lock(&hw_lock); SelectDrive(i); // TODO: May we do this before sending an IDENTITY command? uint8_t status = inport8(port_base + REG_STATUS); if ( status == 0 ) continue; // Non-existent. const char* name = i == 0 ? "master" : "slave"; drives[i] = new Port(this, i); if ( !drives[i] ) { LogF("error: failed to allocate %s drive", name); continue; } if ( !drives[i]->Initialize() ) { delete drives[i]; drives[i] = NULL; continue; } } interrupt_registration.handler = Channel__OnInterrupt; interrupt_registration.context = this; Interrupt::RegisterHandler(interrupt_index, &interrupt_registration); interrupt_registered = true; for ( unsigned int i = 0; i < 2; i++ ) { if ( !drives[i] ) continue; if ( !drives[i]->FinishInitialize() ) { // TODO: Gracefully destroy the drive here? // TODO: Unsafe with respect to interrupt handler. delete drives[i]; drives[i] = NULL; continue; } } for ( unsigned int i = 0; i < 2; i++ ) { if ( !drives[i] ) continue; unsigned long number = AllocateDiskNumber(); char name[3 + sizeof(unsigned long) * 3]; snprintf(name, sizeof(name), "ata%lu", number); Ref node(new PortNode(drives[i], 0, 0, 0660, dev->dev, 0)); if ( !node ) PanicF("Unable to allocate memory for %s/%s inode", devpath, name); ioctx_t ctx; SetupKernelIOCtx(&ctx); if ( LinkInodeInDir(&ctx, dev, name, node) != 0 ) PanicF("Unable to link %s/%s to ATA driver inode", devpath, name); } return true; } HBA::HBA(uint32_t devaddr) { this->devaddr = devaddr; } HBA::~HBA() { } bool HBA::InitializeChannel(Ref dev, const char* devpath, unsigned int channel_index) { channels[channel_index].devaddr = devaddr; channels[channel_index].hba = this; channels[channel_index].channel_index = channel_index; return channels[channel_index].Initialize(dev, devpath); } bool HBA::Initialize(Ref dev, const char* devpath) { uint16_t dev_pci_command_cur = PCI::Read16(devaddr, PCIFIELD_COMMAND); uint16_t dev_pci_command_new = dev_pci_command_cur; dev_pci_command_new &= ~PCIFIELD_COMMAND_INTERRUPT_DISABLE; dev_pci_command_new |= PCIFIELD_COMMAND_IO_SPACE; dev_pci_command_new |= PCIFIELD_COMMAND_BUS_MASTER; if ( dev_pci_command_cur != dev_pci_command_new ) PCI::Write16(devaddr, PCIFIELD_COMMAND, dev_pci_command_new); InitializeChannel(dev, devpath, 0); InitializeChannel(dev, devpath, 1); return true; } } // namespace ATA } // namespace Sortix