This is not the current version of the class.

Lecture 17: Device interaction and prefetching

Chickadee devices

Device interaction

Port-mapped I/O

Memory-mapped I/O

    console = (uint16_t*) 0xB8000; // fixed physical address
    console[0] = 'H' | 0xC000;
    console[1] = 'i' | 0xC000;
    console[2] = '!' | 0xC000;
void console_show_cursor(int cpos) { 
    outb(0x3D4, 14);
    outb(0x3D5, cpos / 256);
    outb(0x3D4, 15);
    outb(0x3D5, cpos % 256);
}

Discoverable I/O

    int addr = pci.find([&] (int a) {
            uint32_t vd = pci.readl(a + pci.config_vendor);
            return vd == 0x71138086U /* PIIX4 Power Management Controller */
                || vd == 0x29188086U /* ICH9 LPC Interface Controller */;
        });
    assert(addr >= 0);
    // Read I/O base register from controller's PCI configuration space.
    int pm_io_base = pci.readl(addr + 0x40) & 0xFFC0;
    // Write `suspend enable` to the power management control register.
    outw(pm_io_base + 4, 0x2000);

Interrupts

Direct memory access

AHCI overall layout

AHCI Host Bus Adapter Configuration

AHCI initialization

ahcistate* ahcistate::find(int addr, int port) {
    auto& pci = pcistate::get();
    for (; addr >= 0; addr = pci.next(addr), port = 0) {
        if (pci.readw(addr + pci.config_subclass) != 0x0106) {
            continue;
        }
        uint32_t pa = pci.readl(addr + pci.config_bar5);
        if (pa == 0) {
            continue;
        }
        auto dr = pa2ka<volatile regs*>(pa); // memory-mapped I/O
        dr->ghc = ghc_ahci_enable; // indicate host is AHCI aware
        // assume port 0 is available
        assert((dr->port_mask & 1U) && dr->p[0].sstatus);
        return knew<ahcistate>(addr, 0, dr);
    }
}

struct ahcistate { 
    // DMA and memory-mapped I/O state
    dmastate dma_;
    int pci_addr_;
    int sata_port_;
    volatile regs* dr_;
    volatile portregs* pr_;
    
};

ahcistate::ahcistate(...) { 
    // place port in idle state
    pr_->command &= ~uint32_t(pcmd_rfis_enable | pcmd_start);
    while (pr_->command & (pcmd_command_running | pcmd_rfis_running)) {
        pause();
    }

    // set up DMA area
    memset(&dma_, 0, sizeof(dma_));
    for (int i = 0; i != 32; ++i) {
        dma_.ch[i].cmdtable_pa = ka2pa(&dma_.ct[i]);
    }
    pr_->cmdlist_pa = ka2pa(&dma_.ch[0]);
    pr_->rfis_pa = ka2pa(&dma_.rfis);
    
}

AHCI port structure (one per drive)

AHCI port structures

Why 32 commands in parallel?

Queue depth 1

Queue depth 32

AHCI command list structure

AHCI command list structure

AHCI command table structure

AHCI command table structure

Reading or writing a block, abstractly

Reading or writing a block, concretely

Reading or writing a block, in code

    clear(0);
    push_buffer(0, buf, sz);
    issue_ncq(0, command, off / sectorsize);
    int slot = 0;
    assert(/* `slot` is not currently an active command */);

    // clear
    dma_.ch[slot].nbuf = 0;
    dma_.ch[slot].buf_byte_pos = 0;

    // push_buffer
    dma_.ct[slot].buf[0].pa = ka2pa(buf);
    dma_.ct[slot].buf[0].maxbyte = sz - 1;
    dma_.ch[slot].nbuf = 1;
    dma_.ch[slot].buf_byte_pos = sz;

    // issue_ncq
    size_t first_sector = off / sectorsize;
    size_t nsectors = sz / sectorsize;

    dma_.ct[slot].cfis[0] = cfis_command
        | (unsigned(cmd_read_fpdma_queued) << 16)
        | ((nsectors & 0xFF) << 24);
    dma_.ct[slot].cfis[1] = (first_sector & 0xFFFFFF)
        | (uint32_t(fua) << 31) | 0x40000000U;
    dma_.ct[slot].cfis[2] = (first_sector >> 24)
        | ((nsectors & 0xFF00) << 16);
    dma_.ct[slot].cfis[3] = (slot << 3);

    dma_.ch[slot].flags = 4 /* # words in `cfis` */
        | ch_clear_flag; // would also contain `ch_write_flag` for writes
    dma_.ch[slot].buf_byte_pos = 0;

    // ensure all previous writes have made it out to memory
    std::atomic_thread_fence(std::memory_order_release);

    // tell interface NCQ slot used
    pr_->ncq_active_mask = 1U << slot; // NB!
    // tell interface command available
    pr_->command_mask = 1U << slot;
    // The write to `command_mask` wakes up the device.

I/O parallelism and synchronization

Interrupt processing

    // obtain lock, read data
    auto irqs = lock_.lock();

    // check interrupt reason, clear interrupt
    assert(/* not error */);
    pr_->interrupt_status = ~0U;
    dr_->interrupt_status = ~0U;

    // acknowledge completed commands
    uint32_t still_active = pr_->ncq_active_mask;
    uint32_t acks = slots_outstanding_mask_ & ~still_active;
    for (int slot = 0; acks != 0; ++slot, acks >>= 1) {
        if (acks & 1) {
            // mark `slot` as successfully completed and available for reuse
            assert(slots_outstanding_mask_ & (1U << slot));
            slots_outstanding_mask_ &= ~(1U << slot);
            ++nslots_available_;
            inform_thread_waiting_for_slot();
        }
    }

    // acknowledge errored commands
    if (is_error) {
        handle_error_interrupt();
    }

    lock_.unlock(irqs); 

Informing the kernel thread: handout code

What does prefetching mean?

Separating concerns in prefetching

Prefetching thread

Multiple prefetching threads

Nonblocking prefetching

Example: Extend bcentry

struct bcentry {
    std::atomic<int> state_ = state_empty;
    spinlock lock_;
    blocknum_t bn_;
    std::atomic<unsigned> ref_ = 0;

    volatile int ready_;  // new

Handout code: Waiting for a bcentry to read

    // load block, or wait for concurrent reader to load it
    while (true) {
        assert(state_ != state_empty);
        if (state_ == state_allocated) { 
            state_ = state_loading;
            lock_.unlock(irqs);

            sata_disk->read(buf_, chkfs::blocksize,
                            bn_ * chkfs::blocksize);
            // which means:
            sata_disk->read_or_write
                (ahcistate::cmd_read_fpdma_queued,
                 buf_, chkfs::blocksize, bn_ * chkfs::blocksize);

            irqs = lock_.lock();
            
        } else if (state_ == state_loading) {
            waiter(current()).block_until(bc.read_wq_, [&] () {
                    return state_ != state_loading;
                }, lock_, irqs);
        } else 
    }

Nonblocking bcentry::load?

    while (true) {
        if (state_ == state_allocated) { 
            state_ = state_loading;
            ready_ = E_AGAIN;

            sata_disk->read_or_write_nonblocking
                (ahcistate::cmd_read_fpdma_queued,
                 buf_, chkfs::blocksize, bn_ * chkfs::blocksize,
                 &ready_);

            // ????????