Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion docs/linux_kernel_development/1.empty.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Empty Module

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

The first exercise is to write an empty module. The module will print a message when it loads and
a message when it is unloaded.

Expand Down Expand Up @@ -241,6 +244,10 @@ Every time we change the module, we have to perform the following steps:

We can use a `run.sh` script like the following placed in the module's folder to automate this:

<Tabs>

<TabItem value="x86" label="x86_64">

```bash
#!/bin/sh

Expand Down Expand Up @@ -278,13 +285,76 @@ echo "Compressing initramfs"
(cd "$INIT_RAM_FS" && find . -print0 | cpio --null -ov --format=newc | gzip -9 > "$BUILD_DIR/initramfs.cpio.gz")

echo "Running QEMU"
qemu-system-x86_64 -kernel "$KDIR/arch/x86_64/boot/bzImage" --initrd build/initramfs.cpio.gz -nographic -append "console=ttyS0" -s
qemu-system-x86_64 -nographic \
-kernel "$KDIR/arch/x86_64/boot/bzImage" \
-initrd build/initramfs.cpio.gz \
-append "console=ttyS0" \
-s
```

</TabItem>

<TabItem value="arm64" label="ARM64">

```bash
#!/bin/sh

MODULE=empty.ko
BUILD_DIR="$(pwd)/build"

set -e

if [ -z $KDIR ]; then
echo "Kernel folder not set, use export KDIR=..."
exit 1
fi

if [ -z $INIT_RAM_FS ]; then
echo "initramfs folder not set, use export INIT_RAM_FS=..."
ecit 1
fi

echo "Building module"
make

echo "Kernel folder $KDIR"
echo "initramfs folder $INIT_RAM_FS"

KVERSION=$(cd "$KDIR" && make kernelversion)

echo "Kernel version $KVERSION"

echo "Copying driver"
MODULES_DIR="$INIT_RAM_FS/lib/modules/$KVERSION"
mkdir -p "$MODULES_DIR"
cp build/empty.ko "$MODULES_DIR"

echo "Compressing initramfs"
(cd "$INIT_RAM_FS" && find . -print0 | cpio --null -ov --format=newc | gzip -9 > "$BUILD_DIR/initramfs.cpio.gz")

echo "Running QEMU"
qemu-system-aarch64 -machine virt \
-cpu cortex-a53 \
-smp 1 \
-m 512M \
-nographic \
-kernel "$KDIR/arch/arm64/boot/Image" \
-initrd build/initramfs.cpio.gz \
-append "earlyprintk=serial,ttyAMA0 console=ttyAMA0 debug" \
-s
```

</TabItem>

</Tabs>

:::note
Make sure to export both `$KDIR` and `$INIT_RAM_FS` variables before running the script.
:::

The script will place the module in `/lib/modules/<kernel_version>/` folder so that we
can use `modprobe module_name` to load the module.

## Exercises

1. Modify the `Module::init` function to return an `Error`. Try loading the module with different errors and see what the kernel prints.
Expand Down
53 changes: 44 additions & 9 deletions docs/linux_kernel_development/2.debug.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,50 @@ We have to run `make menuconfig` and select the following components:

```text
- Kernel hacking
- Kernel Debugging
- Debug information -> Rely on the toolchain's implicit default DWARF version
- Compile-time checks and compiler options
- Provide GDB scripts for kernel debugging
- Compile the kernel with frame pointers
- Reduice debugging infromation (DISABLE)
- Kernel Debuggng
- Reduce debugging information (DISABLE)
```

## Run QEMU with debug server

We use the `-s` parameter to enable QEMU's `gdb` server. This allows `gdb` to connect to it and debug the running kernel.

<Tabs>

<TabItem value="x86" label="x86_64">

```bash
$ qemu-system-x86_64 -nographic \
-kernel "$KDIR/arch/x86_64/boot/bzImage" \
-initrd build/initramfs.cpio.gz \
-append "earlyprintk=serial,ttyS0 console=ttyS0" \
-s
```

</TabItem>

<TabItem value="arm64" label="ARM64">

```bash
$ qemu-system-x86_64 -kernel "$KDIR/arch/x86_64/boot/bzImage" \
--initrd build/initramfs.cpio.gz \
-nographic \
-append "earlyprintk=serial,ttyS0 console=ttyS0" \
-s
$ qemu-system-aarch64 -machine virt \
-cpu cortex-a53 \
-smp 1 \
-m 512M \
-nographic \
-kernel arch/arm64/boot/Image \
-initrd build/initramfs.cpio.gz \
-append "earlyprintk=serial,ttyAMA0 console=ttyAMA0 debug"
-s
```

</TabItem>

</Tabs>

QEMU will start the `gdb` server on `127.0.0.1:1234`.


Expand Down Expand Up @@ -99,6 +124,11 @@ scripts to `~/.config/gdb/gdbinit`.

Edit the `.vscode/launch.json` file. The `program` field points to the `vmlinux` file in the kernel folder.

:::danger
Please make sure to replace `KDIR` with the actual path of the kernel. Visual Studio Code's `launch.json`
does not recognize the `$KDIR` environment variable.
:::

```json
{
"version": "0.2.0",
Expand All @@ -107,7 +137,7 @@ Edit the `.vscode/launch.json` file. The `program` field points to the `vmlinux`
"name": "Debug Linux Kernel with GDB",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/../linux-6.18-rc4/vmlinux",
"program": "$KDIR/vmlinux",
"cwd": "${workspaceFolder}",
"miDebuggerPath": "/usr/bin/gdb",
"MIMode": "gdb",
Expand All @@ -126,13 +156,18 @@ After the debugger starts, run `-exec lx-symbols` in the console to ask `gdb` to

Edit the `.zed/debug.json` file. The `program` filed points to the `vmlinux` file in the kernel folder.

:::danger
Please make sure to replace `KDIR` with the actual path of the kernel. Zed's `debug.json`
does not recognize the `$KDIR` environment variable.
:::

```json
[
{
"label": "Attach GDB",
"adapter": "GDB",
"request": "attach",
"program": "$ZED_WORKTREE_ROOT/../linux-6.18-rc4/vmlinux",
"program": "$KDIR/linux-6.18-rc4/vmlinux",
"cwd": "$ZED_WORKTREE_ROOT",
"target": "127.0.0.1:1234"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Power Off Driver
# x86_64

We will try to build a driver that powers off QEMU.
:::info
This driver works only on x86_64.
:::

We will build a driver that powers off QEMU.

## ISA Debug Exit
## Power Off

QEMU provides a virual peripheral called ISA Debug Exit that maps an IO port. Writing a numeric value to this port
QEMU provides a virtual peripheral called ISA Debug Exit that maps an IO port. Writing a numeric value to this port
will power off QEMU and return the value as an error code.

Attaching this device to QEMU is done by adding `-device isa-debug-exit,iobase=0x10f4,iosize=0x04` to the command line.
Expand Down Expand Up @@ -49,12 +53,13 @@ unsafe {
outb %al, %dx
";
in("dx") port as u16,
in("al") value as u8
in("al") value as u8,
options(noreturn)
);
}
```

#### Intel Sytnax
#### Intel Syntax

```rust
// core API
Expand All @@ -64,7 +69,8 @@ unsafe {
out dx, al
",
in("dx") port as u16,
in("al") value as u8
in("al") value as u8,
options(intel_syntax, noreturn)
);
}
```
Expand All @@ -75,7 +81,7 @@ as we might override another driver's ports. if we cannot reserve a port, we jus
and fail to load the driver returning a `EBUSY` error.
:::

### Realeasing the port reagion
### Releasing the port region

If we have reserved a port region, we must make sure to release it when we do not need it anymore
or when we unload the driver.
Expand All @@ -86,7 +92,7 @@ unsafe { bindings::release_region(start, len); }

## Exercises

1. Write an empty module called `Reset`
1. Write an empty module called `PowerOff`
2. Reserve the `0x10f4` port with 4 bytes long in the `Module::init` function.
- Print the contents of `/proc/ioports` to see if your port has been reserved
- Try reserving a port that already exists and fail to initialize the module
Expand Down
56 changes: 56 additions & 0 deletions docs/linux_kernel_development/3.poweroff/2.poweroff_arm64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# ARM64

:::info
This driver works only on ARM64.
:::

We will build a driver that powers off QEMU.

## Power Off

QEMU provides the `semihosting` interface and the [Power State Coordination Interface](https://developer.arm.com/documentation/den0022/latest/) (PSCI) interface. Both can be used to power off QEMU, the first one is returning a value to QEMU.

A simple driver that we can write is one that powers off QEMU when it is loaded.

### Using `semihosting`

This is used for debugging ARM devices, it basically traps the `hlt` instruction with the `0xf000` argument
and uses them similar to hypervisor calls. It executes actions based on the values found in the registers.
- register `x0` stores the command `SYS_*`
- register `x1` stores a reference (pointer) to the exit reason

We have to enable `semihosting` in QEMU by appending `-semihosting` to the command line.

```rust
unsafe {
asm!("
hlt #0xF000
";
in("x0") 0x18_u64, // SYS_EXIT
in("x1") &0x20026u64, // Reason ApplicationExit
options(noreturn)
);
};
```

### Using PCSI

We need to use the `PSCI_SYSTEM_OFF` hypervisor call.

We use the `hvc` instruction in inline assembly.

```rust
// kernel API (use kernel::asm)
unsafe {
asm!(
"hvc #0";
in("x0") 0x8400_0008_u64, // PSCI SYSTEM_OFF (SMC32 ID)
options(noreturn)
);
};
```

## Exercises

1. Write an empty module called `PowerOff`
2. Use the `hvc` instruction with `x0` set to `0x8400_0008`.
53 changes: 53 additions & 0 deletions docs/linux_kernel_development/3.poweroff/3.bonus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Bonus

Use a `Task` to schedule a power off after a certain amount of time defined in a module parameter. Use
a [workqueue](https://rust.docs.kernel.org/next/kernel/workqueue/index.html).

```rust
/// Stores all the possible tasks (Work)
#[pin_data]
struct DeferredWork {
module: &'static ThisModule,
#[pin]
work: Work<DeferredWork>,
}

/// Initializes the possible tasks
impl DeferredWork {
fn new(module: &'static ThisModule) -> Result<Arc<DeferredWork>> {
Arc::pin_init(
pin_init!(DeferredWork {
module,
work <- new_work!("DeferredWork::work"),
}),
GFP_KERNEL,
)
}
}

/// Must be implemented
impl_has_work! {
impl HasWork<Self> for DeferredWork { self.work }
}

/// Implement the task
impl WorkItem for DeferredWork {
type Pointer = Arc<DeferredWork>;

fn run(_this: Self::Pointer) {
// ...
}
}

/// Setup the work
fn setup_work() {
match DeferredWork::new(module) {
Ok(work) => {
let _ = workqueue::system().enqueue(work);
}
Err(err) => {
pr_warn!("Failed to setup work: {:?}", err);
}
}
}
```
3 changes: 3 additions & 0 deletions docs/linux_kernel_development/3.poweroff/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Power Off Module

Powering off QEMU is done differently for [x86_64](poweroff_x86) and for [ARM64](poweroff_arm64). Please select one of the platform.
4 changes: 4 additions & 0 deletions docs/linux_kernel_development/4.misc.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Misc Driver

:::info
This is still work in progress and will be updated.
:::

We want to control the power off driver from user space.

## Exercises
Expand Down
Loading