storage — Storage Device Models

IDE Disk Controller and Disk Image Subsystem

The storage sub-module provides block storage device models for gem5 full-system simulation: an IDE (ATA) disk controller, IDE disk device with full state machine, and flexible disk image backends supporting raw and copy-on-write formats.

IDE Controller IDE Disk Disk Images PIO + DMA

📚 What is this?

The storage module simulates IDE (Integrated Drive Electronics) hard disks so a guest OS can boot from and read/write to virtual disk images. It models the full ATA command protocol, PIO and DMA transfer modes, and the controller-disk interface — allowing standard IDE drivers in Linux or other operating systems to work without modification inside gem5.

💡 Analogy: A Filing Cabinet

Think of the storage sub-module as a filing cabinet system. The IdeDisk is an individual filing cabinet drawer — it holds data organized in numbered sectors (like numbered folders). The IdeController is the desk clerk who accepts requests from the CPU ("Please get me folder #1234") and manages the physical retrieval. The clerk can work in two modes: PIO (manual mode — the CPU carries each folder personally) or DMA (courier mode — the data is delivered directly to your desk without the CPU waiting). The DiskImage is the actual paper stored in the filing cabinet — it can be a real image file or a "copy-on-write" overlay that records changes without modifying the original.

📜 Table of Contents

Storage Architecture

The storage subsystem connects the PCI bus (via the IDE controller) to disk images through a layered architecture:

flowchart TD
  subgraph CPU_Side["CPU / Memory Bus"]
    CPU["CPU Core"]
    BUS["Memory Bus"]
  end

  subgraph Controller["IDE Controller (PCI Device)"]
    CTRL["IdeController\n(BasicPioDevice)"]
    CH0["Channel 0\n(Primary)"]
    CH1["Channel 1\n(Secondary)"]
  end

  subgraph Disks["IDE Disks"]
    DISK0["IdeDisk 0\n(Master)"]
    DISK1["IdeDisk 1\n(Slave)"]
    DISK2["IdeDisk 2\n(Master)"]
    DISK3["IdeDisk 3\n(Slave)"]
  end

  subgraph Images["Disk Image Backends"]
    RAW["RawDiskImage\n(.img file)"]
    COW["CowDiskImage\n(copy-on-write overlay)"]
    PARENT["Parent Image\n(read-only base)"]
  end

  CPU -->|"PIO/DMA"| BUS
  BUS -->|"BAR0/BAR1 regs"| CTRL
  CTRL --> CH0
  CTRL --> CH1
  CH0 --> DISK0
  CH0 --> DISK1
  CH1 --> DISK2
  CH1 --> DISK3
  DISK0 --> RAW
  DISK1 --> COW
  COW -->|"reads fall through"| PARENT

IDE Controller

IdeController is a PCI device (inheriting from BasicPioDevice) that implements the standard IDE/ATA controller interface. It manages two channels (primary and secondary), each supporting up to two drives (master and slave).

Controller Registers

The controller exposes its registers through PCI BARs:

BARSpaceContents
BAR0Primary Command BlockData, Error/Features, Sector Count, LBA registers, Status/Command
BAR1Primary Control BlockAlternate Status, Device Control
BAR2Secondary Command BlockSame as BAR0 for secondary channel
BAR3Secondary Control BlockSame as BAR1 for secondary channel
BAR4Bus Master IDEDMA command, status, and PRD table address registers

Controller Operation

When the guest OS writes to a command register, the controller dispatches the operation to the selected disk on the appropriate channel. For DMA operations, it manages the Physical Region Descriptor (PRD) table that maps scatter-gather DMA transfers.

// IdeController channel structure
class IdeController::Channel {
    IdeController *controller();
    IdeDisk *device(int index);  // 0=master, 1=slave

    // Register access from PCI BAR
    void accessCommand(Addr offset, int size,
                       uint8_t *data, bool read);
    void accessControl(Addr offset, int size,
                       uint8_t *data, bool read);
    void accessBMI(Addr offset, int size,
                   uint8_t *data, bool read);
};

IDE Disk

IdeDisk models a single IDE hard drive with a complete ATA state machine. It handles ATA commands (read, write, identify), manages PIO and DMA transfers, and interfaces with a DiskImage backend for persistent storage.

Disk State Machine

The IDE disk operates through a detailed state machine handling the ATA protocol:

stateDiagram-v2
    [*] --> Device_Idle_S : Power on / Reset

    Device_Idle_S --> Command_Execution : Non-data command
    Device_Idle_S --> Prepare_Data_In : PIO read command
    Device_Idle_S --> Prepare_Data_Out : PIO write command
    Device_Idle_S --> Prepare_Data_Dma : DMA command
    Device_Idle_S --> Device_Srst : Software reset

    Command_Execution --> Device_Idle_S : Command complete

    Prepare_Data_In --> Data_Ready_INTRQ_In : Data loaded
    Data_Ready_INTRQ_In --> Transfer_Data_In : Host reads data
    Transfer_Data_In --> Data_Ready_INTRQ_In : More sectors
    Transfer_Data_In --> Device_Idle_S : All sectors done

    Prepare_Data_Out --> Data_Ready_INTRQ_Out : Ready for data
    Data_Ready_INTRQ_Out --> Transfer_Data_Out : Host writes data
    Transfer_Data_Out --> Data_Ready_INTRQ_Out : More sectors
    Transfer_Data_Out --> Device_Idle_S : All sectors done

    Prepare_Data_Dma --> Transfer_Data_Dma : DMA engine started
    Transfer_Data_Dma --> Device_Idle_S : DMA complete
    Transfer_Data_Dma --> Device_Dma_Abort : DMA error

    Device_Srst --> Device_Idle_S : Reset complete
    Device_Dma_Abort --> Device_Idle_S : Abort handled

Supported ATA Commands

CommandTransfer ModeDescription
WIN_READPIORead sectors using PIO transfer
WIN_WRITEPIOWrite sectors using PIO transfer
WIN_READDMADMARead sectors using DMA transfer
WIN_WRITEDMADMAWrite sectors using DMA transfer
WIN_IDENTIFYPIOReturn drive identification data (model, capacity, features)
WIN_SETMULTNon-dataSet multiple sector count for PIO multi-sector transfers
WIN_SRSTNon-dataSoftware reset of the drive
WIN_SEEKNon-dataSeek to a specific LBA sector

DMA Transfer Flow

DMA transfers use a Physical Region Descriptor (PRD) table in host memory. The controller reads PRD entries to build a scatter-gather list for the DMA engine:

sequenceDiagram
    participant OS as Guest OS Driver
    participant CTRL as IdeController
    participant DISK as IdeDisk
    participant DMA as DMA Engine
    participant MEM as System Memory

    OS->>CTRL: Write LBA, sector count to command regs
    OS->>CTRL: Write PRD table base to BMI BAR
    OS->>CTRL: Write DMA Read command to BMI
    CTRL->>DISK: startDma(prdTableBase)
    DISK->>DMA: Read PRD entry from memory
    DMA->>MEM: dmaRead(prdAddr)
    MEM-->>DMA: PRD entry (baseAddr, byteCount, EOT)
    DISK->>DISK: readDisk(sector, dataBuffer)
    DISK->>DMA: dmaWrite(prdBaseAddr, data)
    DMA->>MEM: Write sector data to host buffer
    MEM-->>DMA: Write complete
    Note over DISK: Check EOT flag in PRD entry
    DISK->>DISK: If more PRDs, repeat
    DISK->>CTRL: Transfer complete
    CTRL->>OS: Post interrupt (DMA done)

PRD Table Structure

// Physical Region Descriptor entry
struct PrdEntry_t {
    uint32_t baseAddr;   // Physical address of buffer (bit 0 must be 0)
    uint16_t byteCount;  // Byte count (0 = 64KB)
    uint16_t endOfTable; // Bit 15 = End of Table flag
};

// Constants
#define PRD_BASE_MASK   0xfffffffe  // Mask for base address
#define PRD_COUNT_MASK  0xfffe      // Mask for byte count
#define PRD_EOT_MASK    0x8000      // End-of-table bit
#define MAX_DMA_SIZE    0x20000     // 128KB max DMA per transfer
#define MAX_SINGLE_DMA_SIZE 0x10000 // 64KB max per PRD entry

Disk Image Backends

The DiskImage hierarchy provides the actual storage backing for IDE disks:

RawDiskImage

A Atomic and Timing Simple CPUs — gem5 Wiki" href="../../cpu_doc/simple/index.html">simple image backed by a raw binary file. Every sector maps directly to a file offset. Supports both read-only and read-write modes.

// Python configuration
raw_image = RawDiskImage(
    image_file='disk.img',
    read_only=False
)

CowDiskImage (Copy-on-Write)

CowDiskImage layers a write overlay on top of a parent image. Reads fall through to the parent; writes are captured in the overlay. This enables multiple simulations to share one base image without modification:

flowchart TD
  READ["Read sector N"] --> CHECK{Sector N in COW table?}
  CHECK -->|"Yes"| COW_DATA["Return data from COW overlay"]
  CHECK -->|"No"| PARENT["Read from parent image"]
  PARENT --> RETURN["Return data"]
  COW_DATA --> RETURN

  WRITE["Write sector N"] --> COW_WRITE["Store in COW overlay table"]
  COW_WRITE --> DONE["Write complete\n(parent unchanged)"]
// Python configuration for copy-on-write
base_image = RawDiskImage(image_file='base.img', read_only=True)
cow_image = CowDiskImage(
    child=base_image,
    table_size='16MiB',   // Hash table size for COW sector tracking
    image_file=''         // Empty = no persistence; or path to save overlay
)

SimpleDisk

SimpleDisk is a lightweight disk interface that bypasses the full IDE controller. It provides direct disk access for simple full-system configurations where full ATA emulation is not needed.

Key Classes Reference

ClassFileInheritsRole
IdeControlleride_ctrl.hhBasicPioDevicePCI IDE controller with two channels, DMA bus master
IdeController::Channelide_ctrl.hh—Single IDE channel managing master/slave disks
IdeDiskide_disk.hhSimObjectIDE disk device with ATA state machine, PIO/DMA transfers
DiskImagedisk_image.hhSimObjectAbstract base class for disk image backends
RawDiskImagedisk_image.hhDiskImageRaw binary file-backed disk image
CowDiskImagedisk_image.hhDiskImageCopy-on-write overlay disk image
SimpleDisksimple_disk.hhSimObjectSimplified direct-access disk (no controller emulation)
PrdTableEntryide_disk.hh—Physical Region Descriptor for DMA scatter-gather

Disk Statistics

IdeDisk tracks detailed I/O statistics for performance analysis:

StatisticDescription
dmaReadFullPagesNumber of full-page DMA reads
dmaReadBytesTotal bytes read via DMA
dmaReadTxsNumber of DMA read transactions
dmaWriteFullPagesNumber of full-page DMA writes
dmaWriteBytesTotal bytes written via DMA
dmaWriteTxsNumber of DMA write transactions

Configuration Reference

ParameterPython ClassDefaultDescription
imageIdeDisk—DiskImage object providing the storage backend
driveIDIdeDisk'device0'Drive identifier: device0 (master) or device1 (slave)
delayIdeDisk'1us'Disk access latency (simulated seek/rotation time)
image_fileRawDiskImage—Path to the raw disk image file
read_onlyRawDiskImageFalseWhether the image is read-only
childCowDiskImage—Parent DiskImage for read fall-through
table_sizeCowDiskImage65536Number of entries in the COW hash table
image_fileCowDiskImage''Optional file path to persist the COW overlay
pci_busIdeController0PCI bus number
pci_devIdeController0PCI device number
disksIdeController[]List of IdeDisk objects (up to 4: 2 per channel)

Example Configuration

A typical IDE disk setup for full-system simulation:

// Python config — IDE disk with copy-on-write image
base = RawDiskImage(image_file='ubuntu-22.04.img', read_only=True)
disk_image = CowDiskImage(child=base)

disk0 = IdeDisk(driveID='device0', image=disk_image, delay='1us')
disk1 = IdeDisk(driveID='device1',
                image=RawDiskImage(image_file='data.img'))

ide = IdeController(
    disks=[disk0, disk1],  // Channel 0: master + slave
    pci_bus=0, pci_dev=1, pci_func=0,
    InterruptLine=14
)
system.pci_host.attach(ide)

Walkthrough: Reading a Disk Sector

Guest OS driver writes LBA address. The driver writes the target sector number to the LBA Low/Mid/High registers in the IDE controller's command block (BAR0 offsets 3–5).
Driver issues READ DMA command. Writing 0xC8 (WIN_READDMA) to the Command register triggers the IdeDisk state machine to transition from Device_Idle_S to Prepare_Data_Dma.
Driver sets up PRD table and starts DMA. The driver writes the PRD table base address to the Bus Master IDE register (BAR4) and sets the Start bit.
IdeDisk reads PRD entries. The disk reads the first PRD entry via DMA to get the target buffer address in host memory.
IdeDisk reads from disk image. readDisk(sector, dataBuffer) calls DiskImage::read() — either hitting the raw file or the COW overlay.
DMA write to host memory. The disk writes sector data to the address specified by the PRD entry, splitting into Cache Hierarchy | gem5 Wiki" href="../../mem_doc/cache/index.html">cache-line-sized chunks.
Interrupt posted. On completion, the IdeDisk transitions to Device_Idle_S and posts an interrupt to the CPU through the IDE controller.

File Map

storage/ directory structure
src/dev/storage/
├── ide_ctrl.hh / ide_ctrl.cc     ← IdeController (PCI IDE controller with channels)
├── ide_disk.hh / ide_disk.cc     ← IdeDisk (ATA disk state machine + DMA)
├── ide_atareg.h                  ← ATA register definitions and ataparams struct
├── ide_wdcreg.h                  ← WDC (Western Digital) register constants
├── disk_image.hh / disk_image.cc ← DiskImage, RawDiskImage, CowDiskImage
├── simple_disk.hh / simple_disk.cc ← SimpleDisk (lightweight direct access)
├── Ide.py                        ← Python config for IdeController, IdeDisk
├── DiskImage.py                  ← Python config for RawDiskImage, CowDiskImage
├── SimpleDisk.py                 ← Python config for SimpleDisk
└── SConscript                    ← Build definitions

Glossary

TermDefinition
ATAAT Attachment — standard interface for connecting storage devices to PCs
IDEIntegrated Drive Electronics — the disk has its own controller on-board
PIOProgrammed I/O — CPU directly reads/writes data through I/O registers
DMADirect Memory Access — disk transfers data to/from memory without CPU
PRDPhysical Region Descriptor — scatter-gather entry for DMA transfers
LBALogical Block Addressing — sector addressing scheme (vs. CHS)
COWCopy-on-Write — overlay layer capturing writes, reads fall through to base
BMIBus Master IDE — DMA controller registers in the IDE controller