diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8818818..349c776 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,3 +21,24 @@ jobs: [ -f "$f" ] || continue grep -q "SPDX-License-Identifier" "$f" || { echo "$f missing SPDX header" ; exit 1; } done + + driver-build: + name: Driver / kbuild + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Install kernel headers and build tools + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends \ + linux-headers-$(uname -r) build-essential + - name: Build spanker.ko (out-of-tree) + run: make -C src/driver + - name: Verify module metadata + run: | + modinfo src/driver/spanker.ko \ + | grep -E '^(license|description|version|alias):' + # Sanity: license must be GPL per ADR-002. + modinfo src/driver/spanker.ko | grep -q '^license:.*GPL' + - name: Clean + run: make -C src/driver clean diff --git a/.gitignore b/.gitignore index cb9ce47..eedf627 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,13 @@ __pycache__/ *.swp .vscode/ .idea/ + +# kbuild out-of-tree module artefacts (per ADR-002) +*.mod +*.mod.c +*.cmd +.*.cmd +Module.symvers +modules.order +*.symvers +.tmp_versions/ diff --git a/src/driver/Kbuild b/src/driver/Kbuild new file mode 100644 index 0000000..893e7e5 --- /dev/null +++ b/src/driver/Kbuild @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2026 PopSolutions Cooperative +# +# Kbuild for the Spanker accelerator driver (out-of-tree module). +# Per ADR-002 — Linux driver model. + +obj-m += spanker.o + +spanker-y := \ + spanker_main.o \ + spanker_ioctl.o + +ccflags-y += -I$(src)/include diff --git a/src/driver/Makefile b/src/driver/Makefile new file mode 100644 index 0000000..a552d9f --- /dev/null +++ b/src/driver/Makefile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2026 PopSolutions Cooperative +# +# Top-level Makefile for the Spanker accelerator driver +# (out-of-tree kernel module). Per ADR-002 — Linux driver model. +# +# Usage: +# make # build spanker.ko against the running kernel +# make KDIR=... # cross-build against a specific kernel tree +# make clean # remove build artefacts +# +# Kbuild does the actual work; this Makefile only wraps the +# canonical "make -C $(KDIR) M=$(PWD)" recipe. + +KDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +.PHONY: default modules clean help + +default: modules + +modules: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + +help: + @echo "Spanker driver — out-of-tree kbuild module" + @echo "" + @echo "Targets:" + @echo " modules build spanker.ko (default)" + @echo " clean remove build artefacts" + @echo "" + @echo "Variables:" + @echo " KDIR kernel build tree (default: /lib/modules/\$$(uname -r)/build)" diff --git a/src/driver/include/uapi/spanker_ioctl.h b/src/driver/include/uapi/spanker_ioctl.h new file mode 100644 index 0000000..94a006f --- /dev/null +++ b/src/driver/include/uapi/spanker_ioctl.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2026 PopSolutions Cooperative + * + * Public ioctl ABI for the Spanker accelerator driver. + * + * This header is shared between the in-kernel driver under + * src/driver/ and the Rust userspace runtime (per ADR-001). + * + * Stability: v0 is unstable. The ABI may break between any two + * commits while the major version is 0. The first stable major + * version (v1) lands when ADR-003 (Public interface contracts) is + * accepted and PR #2 ships the runtime that depends on it. + * + * Discoverability: SPANKER_IOC_GET_VERSION lets userspace fail + * cleanly when it sees a kernel driver with an incompatible major. + */ + +#ifndef _UAPI_LINUX_SPANKER_IOCTL_H +#define _UAPI_LINUX_SPANKER_IOCTL_H + +#include +#include + +/* + * ioctl magic byte. + * + * 0xE3 is used here as a placeholder; before any in-tree submission + * (Generation B per ADR-002), this must be reconciled with + * Documentation/userspace-api/ioctl/ioctl-number.rst in mainline. + */ +#define SPANKER_IOC_MAGIC 0xE3 + +/* ABI version reported by SPANKER_IOC_GET_VERSION. */ +#define SPANKER_ABI_VERSION_MAJOR 0 +#define SPANKER_ABI_VERSION_MINOR 1 +#define SPANKER_ABI_VERSION_PATCH 0 + +/* + * struct spanker_version — wire format of SPANKER_IOC_GET_VERSION. + * + * Fields are __u16 to keep the struct exactly 8 bytes regardless + * of architecture or compiler padding choices. Reserved must be 0. + */ +struct spanker_version { + __u16 major; + __u16 minor; + __u16 patch; + __u16 reserved; +}; + +/* + * Smoke-test the ioctl dispatcher. + * + * No payload, no return data; success indicates the driver is + * loaded and the dispatcher reaches this opcode. + */ +#define SPANKER_IOC_PING _IO(SPANKER_IOC_MAGIC, 0x01) + +/* + * Read driver/ABI version. + * + * Userspace passes a writable struct spanker_version; the kernel + * fills it with SPANKER_ABI_VERSION_{MAJOR,MINOR,PATCH} and a + * zeroed reserved field. + */ +#define SPANKER_IOC_GET_VERSION _IOR(SPANKER_IOC_MAGIC, 0x02, struct spanker_version) + +#endif /* _UAPI_LINUX_SPANKER_IOCTL_H */ diff --git a/src/driver/spanker_ioctl.c b/src/driver/spanker_ioctl.c new file mode 100644 index 0000000..c8e1b02 --- /dev/null +++ b/src/driver/spanker_ioctl.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 PopSolutions Cooperative + * + * Spanker — ioctl dispatcher for /dev/spankerctl. + * + * Implements the v0 ABI declared in + * . The skeleton handles two opcodes: + * + * SPANKER_IOC_PING — no-op; returns 0 on success. + * SPANKER_IOC_GET_VERSION — copies struct spanker_version + * {0,1,0,0} to userspace. + * + * Future PRs add per-device opcodes (buffer alloc/free, work + * submit, completion poll) keyed off /dev/spanker0..N-1, governed + * by ADR-003 (Public interface contracts). + */ + +#include +#include +#include +#include +#include + +#include "include/uapi/spanker_ioctl.h" + +static long spanker_ctl_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + if (_IOC_TYPE(cmd) != SPANKER_IOC_MAGIC) + return -ENOTTY; + + switch (cmd) { + case SPANKER_IOC_PING: + return 0; + + case SPANKER_IOC_GET_VERSION: { + struct spanker_version v = { + .major = SPANKER_ABI_VERSION_MAJOR, + .minor = SPANKER_ABI_VERSION_MINOR, + .patch = SPANKER_ABI_VERSION_PATCH, + .reserved = 0, + }; + if (copy_to_user((void __user *)arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + + default: + return -ENOTTY; + } +} + +const struct file_operations spanker_ctl_fops = { + .owner = THIS_MODULE, + .open = stream_open, + .unlocked_ioctl = spanker_ctl_ioctl, + .compat_ioctl = compat_ptr_ioctl, +}; diff --git a/src/driver/spanker_main.c b/src/driver/spanker_main.c new file mode 100644 index 0000000..37978ea --- /dev/null +++ b/src/driver/spanker_main.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 PopSolutions Cooperative + * + * Spanker — main module file. + * + * Per ADR-002 (out-of-tree kbuild kernel module), this file owns + * module init/exit, the singleton control character device + * /dev/spankerctl, and PCI driver registration. + * + * The skeleton creates /dev/spankerctl unconditionally so userspace + * can verify the driver is loaded and read the v0 ABI even when no + * Sails are physically attached. Per-Sail device nodes + * /dev/spanker0..N-1 are added by a follow-up PR once the PCI probe + * path is wired up against real silicon. + * + * The PCI driver registration uses placeholder vendor/device IDs + * (0xDEAD/0xBEEF). PopSolutions has not yet been assigned a PCI + * vendor ID; the follow-up issue cited in PR #1's description tracks + * reconciliation before silicon ships. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/uapi/spanker_ioctl.h" + +#define SPANKER_DRIVER_NAME "spanker" +#define SPANKER_CTL_DEV_NAME "spankerctl" + +/* + * Placeholder PCI IDs. Must be replaced before silicon; tracked by + * the follow-up issue cited in the PR #1 description. + */ +#define SPANKER_PCI_VENDOR_PLACEHOLDER 0xDEAD +#define SPANKER_PCI_DEVICE_PLACEHOLDER 0xBEEF + +/* Defined in spanker_ioctl.c. */ +extern const struct file_operations spanker_ctl_fops; + +static const struct pci_device_id spanker_pci_ids[] = { + { PCI_DEVICE(SPANKER_PCI_VENDOR_PLACEHOLDER, + SPANKER_PCI_DEVICE_PLACEHOLDER) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, spanker_pci_ids); + +static int spanker_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + /* + * Skeleton: log probe and accept. BAR mapping, IRQ allocation, + * and DMA-coherent ring setup land in follow-up PRs. + */ + pci_info(pdev, + "spanker: probed placeholder device (vendor=0x%04x device=0x%04x)\n", + id->vendor, id->device); + return 0; +} + +static void spanker_pci_remove(struct pci_dev *pdev) +{ + pci_info(pdev, "spanker: remove placeholder device\n"); +} + +static struct pci_driver spanker_pci_driver = { + .name = SPANKER_DRIVER_NAME, + .id_table = spanker_pci_ids, + .probe = spanker_pci_probe, + .remove = spanker_pci_remove, +}; + +/* Singleton control device. */ +static dev_t spanker_ctl_dev_num; +static struct cdev spanker_ctl_cdev; +static struct class *spanker_ctl_class; +static struct device *spanker_ctl_device; + +static int __init spanker_init(void) +{ + int err; + + err = alloc_chrdev_region(&spanker_ctl_dev_num, 0, 1, + SPANKER_CTL_DEV_NAME); + if (err < 0) { + pr_err("spanker: alloc_chrdev_region failed: %d\n", err); + return err; + } + + cdev_init(&spanker_ctl_cdev, &spanker_ctl_fops); + spanker_ctl_cdev.owner = THIS_MODULE; + err = cdev_add(&spanker_ctl_cdev, spanker_ctl_dev_num, 1); + if (err < 0) { + pr_err("spanker: cdev_add failed: %d\n", err); + goto unregister_region; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + spanker_ctl_class = class_create(THIS_MODULE, SPANKER_CTL_DEV_NAME); +#else + spanker_ctl_class = class_create(SPANKER_CTL_DEV_NAME); +#endif + if (IS_ERR(spanker_ctl_class)) { + err = PTR_ERR(spanker_ctl_class); + pr_err("spanker: class_create failed: %d\n", err); + goto del_cdev; + } + + spanker_ctl_device = device_create(spanker_ctl_class, NULL, + spanker_ctl_dev_num, NULL, + SPANKER_CTL_DEV_NAME); + if (IS_ERR(spanker_ctl_device)) { + err = PTR_ERR(spanker_ctl_device); + pr_err("spanker: device_create failed: %d\n", err); + goto destroy_class; + } + + err = pci_register_driver(&spanker_pci_driver); + if (err) { + pr_err("spanker: pci_register_driver failed: %d\n", err); + goto destroy_device; + } + + pr_info("spanker: loaded (ABI v%u.%u.%u, /dev/%s ready)\n", + SPANKER_ABI_VERSION_MAJOR, + SPANKER_ABI_VERSION_MINOR, + SPANKER_ABI_VERSION_PATCH, + SPANKER_CTL_DEV_NAME); + return 0; + +destroy_device: + device_destroy(spanker_ctl_class, spanker_ctl_dev_num); +destroy_class: + class_destroy(spanker_ctl_class); +del_cdev: + cdev_del(&spanker_ctl_cdev); +unregister_region: + unregister_chrdev_region(spanker_ctl_dev_num, 1); + return err; +} + +static void __exit spanker_exit(void) +{ + pci_unregister_driver(&spanker_pci_driver); + device_destroy(spanker_ctl_class, spanker_ctl_dev_num); + class_destroy(spanker_ctl_class); + cdev_del(&spanker_ctl_cdev); + unregister_chrdev_region(spanker_ctl_dev_num, 1); + pr_info("spanker: unloaded\n"); +} + +module_init(spanker_init); +module_exit(spanker_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("PopSolutions Cooperative"); +MODULE_DESCRIPTION("Spanker accelerator driver (skeleton; PR #1)"); +MODULE_VERSION("0.1.0");