Skip to content
Closed
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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
13 changes: 13 additions & 0 deletions src/driver/Kbuild
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions src/driver/Makefile
Original file line number Diff line number Diff line change
@@ -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)"
69 changes: 69 additions & 0 deletions src/driver/include/uapi/spanker_ioctl.h
Original file line number Diff line number Diff line change
@@ -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 <linux/types.h>
#include <linux/ioctl.h>

/*
* 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 */
59 changes: 59 additions & 0 deletions src/driver/spanker_ioctl.c
Original file line number Diff line number Diff line change
@@ -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
* <uapi/linux/spanker_ioctl.h>. 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 <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/compat.h>

#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,
};
164 changes: 164 additions & 0 deletions src/driver/spanker_main.c
Original file line number Diff line number Diff line change
@@ -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 <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/pci.h>

#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");
Loading