1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! AHCI driver module
//!
//! This driver discovers HBAs on the PCI, initialize them,
//! and exposes IPC endpoints to read and write sectors from/to a disk.
//!
//! # Features
//!
//! Here's a list of wonderful features this driver does **not** provide:
//!
//! - ATAPI support
//! - NCQ support
//! - hotplug/remove of a device
//! - hotplug/remove of a controller
//! - interrupts notifying command has been completed (WIP)
//! - real error management
//! - Port Multipliers
//! - PCI-to-PCI bridges
//!
//! # Interface
//!
//! This driver exposes two IPC interfaces: [AhciInterface], registered as `"ahci:\0"`,
//! and some [IDisk]s.
//!
//! Basically at initialization the driver will assign an id to every discovered disk.
//! You can then ask the [AhciInterface] to give you a session to any [IDisk] from its id,
//! and finally read/write some sectors.
//!
//! We read and write disk sectors only by DMA, letting the device do all the copying.
//! Our client will provide us a handle to a shared memory, and we will make the device
//! DMA read/write to it.
//!
//! # Parallelism
//!
//! For now this driver is "highly single-threaded" and blocking, this means that only one
//! request can be outstanding at any moment, and the driver will wait for it to be completed
//! before accepting other requests.
//!
//! This is highly unsatisfying, since AHCI supports up to 32 commands being issued
//! simultaneously. Unfortunately we can't take advantage of that until we manage to
//! make command-completion interrupts work.

#![feature(box_syntax, untagged_unions)]
#![no_std]

// rustc warnings
#![warn(unused)]
#![warn(missing_debug_implementations)]
#![allow(unused_unsafe)]
#![allow(unreachable_code)]
#![allow(dead_code)]
#![cfg_attr(test, allow(unused_imports))]

// rustdoc warnings
#![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)]
#![deny(intra_doc_link_resolution_failure)]

#[macro_use]
extern crate alloc;
#[macro_use]
extern crate sunrise_libuser;
#[macro_use]
extern crate log;
#[macro_use]
extern crate bitfield;

mod pci;
mod hba;
mod fis;
mod disk;

use crate::hba::HbaMemoryRegisters;
use crate::disk::{Disk, IDisk};
use alloc::boxed::Box;
use alloc::vec::Vec;
use alloc::sync::Arc;
use sunrise_libuser::error::{Error, AhciError};
use sunrise_libuser::futures::{WaitableManager, WorkQueue};
use sunrise_libuser::ipc::server::{port_handler, new_session_wrapper};
use spin::Mutex;
use sunrise_libuser::syscalls;
use sunrise_libuser::ahci::{AhciInterface as IAhciInterface, IDiskProxy, IDisk as _};
use sunrise_libuser::futures_rs::future::FutureObj;

/// Array of discovered disk.
///
/// At startup, the driver will initialize each disk it discovers,
/// and populate this vec.
///
/// As hotplug/remove of a disk is not supported, this array remains
/// unchanged for the rest of the driver's execution.
///
/// A disk id is just the index of a disk in this array.
static DISKS: Mutex<Vec<Arc<Mutex<Disk>>>> = Mutex::new(Vec::new());

/// Ahci driver initialisation.
///
/// 1. Discover HBAs on the PCI.
/// 2. For every found HBA:
///     - Initialize each implemented port if we detect it is connected to a device.
///     - Push the created [Disk]s in [DISKS].
/// 3. Start the event loop.
fn main() {
    debug!("AHCI driver starting up");
    let ahci_controllers = pci::get_ahci_controllers();
    debug!("AHCI controllers : {:#x?}", ahci_controllers);
    for (bar5, _) in ahci_controllers {
        DISKS.lock().extend(
            HbaMemoryRegisters::init(bar5 as _)
                .drain(..).map(|disk| Arc::new(Mutex::new(disk)))
        );
    }
    debug!("AHCI initialised disks : {:#x?}", DISKS);

    // event loop
    let mut man = WaitableManager::new();
    let handler = port_handler(man.work_queue(), "ahci:\0", AhciInterface::dispatch).unwrap();
    man.work_queue().spawn(FutureObj::new(Box::new(handler)));
    man.run();
}

/// Main interface to the AHCI driver.
///
/// Registered under the name `"ahci:\0"` to the Service Manager, after the discovery stage.
///
/// Provides an endpoint to get the number of discovered disks,
/// and another one to get a session to a given disk.
///
/// As hotplug/remove of a disk is not supported, a disk id remains valid for the whole
/// lifetime of the ahci driver.
#[derive(Default, Debug, Clone)]
struct AhciInterface;

impl IAhciInterface for AhciInterface {
    /// Returns the number of discovered disks.
    ///
    /// Any number in the range `0..disk_count()` is considered a valid disk id.
    fn discovered_disks_count(&mut self, _manager: WorkQueue<'static>) -> Result<u32, Error> {
        Ok(DISKS.lock().len() as u32)
    }

    /// Gets the interface to a disk.
    ///
    /// This creates a session to an [IDisk].
    ///
    /// # Error
    ///
    /// - InvalidArg: `disk_id` is not a valid disk id.
    fn get_disk(&mut self, work_queue: WorkQueue<'static>, disk_id: u32,) -> Result<IDiskProxy, Error> {
        let idisk = IDisk::new(Arc::clone(
            DISKS.lock().get(disk_id as usize)
            .ok_or(AhciError::InvalidArg)?
        ));
        let (server, client) = syscalls::create_session(false, 0)?;
        let wrapper = new_session_wrapper(work_queue.clone(), server, idisk, IDisk::dispatch);
        work_queue.spawn(FutureObj::new(Box::new(wrapper)));
        Ok(IDiskProxy::from(client))
    }
}

kip_header!(HEADER = sunrise_libuser::caps::KipHeader {
    magic: *b"KIP1",
    name: *b"ahci\0\0\0\0\0\0\0\0",
    title_id: 0x0200000000000100,
    process_category: sunrise_libuser::caps::ProcessCategory::KernelBuiltin,
    main_thread_priority: 0,
    default_cpu_core: 0,
    flags: 0,
    reserved: 0,
    stack_page_count: 16,
});

capabilities!(CAPABILITIES = Capabilities {
    svcs: [
        sunrise_libuser::syscalls::nr::SleepThread,
        sunrise_libuser::syscalls::nr::ExitProcess,
        sunrise_libuser::syscalls::nr::CloseHandle,
        sunrise_libuser::syscalls::nr::WaitSynchronization,
        sunrise_libuser::syscalls::nr::OutputDebugString,
        sunrise_libuser::syscalls::nr::SetThreadArea,

        sunrise_libuser::syscalls::nr::SetHeapSize,
        sunrise_libuser::syscalls::nr::QueryMemory,
        sunrise_libuser::syscalls::nr::ConnectToNamedPort,
        sunrise_libuser::syscalls::nr::CreateInterruptEvent,
        sunrise_libuser::syscalls::nr::QueryPhysicalAddress,
        sunrise_libuser::syscalls::nr::MapMmioRegion,
        sunrise_libuser::syscalls::nr::SendSyncRequestWithUserBuffer,
        sunrise_libuser::syscalls::nr::ReplyAndReceiveWithUserBuffer,
        sunrise_libuser::syscalls::nr::AcceptSession,
        sunrise_libuser::syscalls::nr::CreateSession,
    ],
    raw_caps: [
        // todo: IRQ capabilities at runtime
        // body: Currently IRQ capabilities are declared at compile-time.
        // body:
        // body: However, for PCI, the IRQ line we want to subscribe to
        // body: can only be determined at runtime by reading the `Interrupt Line` register
        // body: that has been set-up during POST.
        // body:
        // body: What would be the proper way to handle such a case ?
        // body:
        // body: - Declaring every IRQ line in our capabilities, but only effectively using one ?
        // body: - Deporting the PIC management to a userspace module, and allow it to accept
        // body:   dynamic irq capabilities in yet undefined way.
        sunrise_libuser::caps::ioport(pci::CONFIG_ADDRESS + 0), sunrise_libuser::caps::ioport(pci::CONFIG_ADDRESS + 1), sunrise_libuser::caps::ioport(pci::CONFIG_ADDRESS + 2), sunrise_libuser::caps::ioport(pci::CONFIG_ADDRESS + 3),
        sunrise_libuser::caps::ioport(pci::CONFIG_DATA    + 0), sunrise_libuser::caps::ioport(pci::CONFIG_DATA    + 1), sunrise_libuser::caps::ioport(pci::CONFIG_DATA    + 2), sunrise_libuser::caps::ioport(pci::CONFIG_DATA    + 3),
    ]
});