Skip to content
Open
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
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,16 @@ jobs:
IFACE=$(ls /sys/bus/pci/devices/${DOMAIN}:${BUS}:02.0/net/ 2>/dev/null | head -1)
[ -n "$IFACE" ] && ethtool -T "$IFACE" || echo "WARN: No interface found"

- name: DPLL unit tests
if: steps.load.outputs.load_ok == 'true'
run: sudo ./scripts/test-dpll.sh --no-load

- name: Cleanup
if: always()
run: |
echo "1" | sudo tee /sys/bus/netdevsim/del_device 2>/dev/null || true
for id in $(ls /sys/bus/netdevsim/devices/ 2>/dev/null | sed 's/netdevsim//'); do
echo "$id" | sudo tee /sys/bus/netdevsim/del_device 2>/dev/null || true
done
sudo rmmod netdevsim nsim_dpll nsim_ptp_mock nsim_ptp 2>/dev/null || true
sudo dkms remove ${DKMS_PKG}/${DKMS_VER} --all 2>/dev/null || true

Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@
*.o
*.ko
*.ko.cmd
*.ko.zst
*.mod
*.mod.c
*.mod.cmd
*.order
*.symvers
*.unsigned
*.o.d
*.o.cmd
.tmp_versions/
.cache.mk
modules.order
Module.symvers

# kbuild per-object command files
.*.cmd
.module-common.o

# RPM build
rpmbuild/
*.tar.gz
Expand Down
10 changes: 9 additions & 1 deletion 99-nsim-ptp.rules
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@
# is not in the default cgroup device allowlist; containers and
# systemd-sandboxed services (DevicePolicy=closed) would get
# "Operation not permitted" without world-readable permissions.
#
# The bind-mount of /sys/class/nsim_ptp over /sys/class/ptp makes
# PTP sysfs entries (pins, clock_name, etc.) visible at the standard
# path expected by monitoring tools and the PTP dashboard.
SUBSYSTEM=="nsim_ptp", KERNEL=="nsim_ptp[0-9]*", MODE="0666", \
RUN+="/bin/sh -c 'rm -f /dev/ptp%n; mknod /dev/ptp%n c %M %m; chmod 666 /dev/ptp%n'"
RUN+="/bin/sh -c 'rm -f /dev/ptp%n; mknod /dev/ptp%n c %M %m; chmod 666 /dev/ptp%n; if [ -d /sys/class/ptp ] && ! mountpoint -q /sys/class/ptp; then mount --bind /sys/class/nsim_ptp /sys/class/ptp 2>/dev/null || true; fi'"
SUBSYSTEM=="nsim_ptp", KERNEL=="nsim_ptp[0-9]*", ACTION=="remove", \
RUN+="/bin/rm -f /dev/ptp%n"

# gpsd drops privileges to nobody; without world access it fails with
# EACCES, frees the device, and ts2phc starves for NMEA.
SUBSYSTEM=="gnss", KERNEL=="gnss[0-9]*", MODE="0666"
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,40 @@ ssh my-dev-vm
If an SSH config entry for the VM name already exists, the script prompts
you to remove it or pick a different `--vm-name`.

### Testing
### DPLL Unit Tests

`scripts/test-dpll.sh` exercises the DPLL emulation in `netdevsim/dpll.c`.
It requires root privileges and loaded modules:

```bash
# Run after DKMS install (modules will be loaded automatically)
sudo ./scripts/test-dpll.sh

# If modules are already loaded, skip load/unload
sudo ./scripts/test-dpll.sh --no-load

# Verbose mode (set -x)
sudo ./scripts/test-dpll.sh --verbose
```

Or via `make`:

```bash
make test-dpll
```

The test suite covers:
- Module loading (`nsim_dpll`, `netdevsim`)
- Device creation with `wpc=1` (DPLL activation) and `wpc=0` (no DPLL)
- Sysfs `lock_status` read/write/transitions/invalid input/rapid cycling
- Generic netlink DPLL device and pin dump (PPS+EEC devices, GNSS/EXT/SyncE pins)
- Sysfs-to-netlink lock status consistency
- GNSS device presence and NMEA echo
- PTP clock and network interface verification
- Device teardown and re-creation
- dmesg sanity (no kernel warnings/errors)

### Testing with UTM

`scripts/test-utm-ubuntu.sh` does the same VM setup plus smoke tests and
(optionally) the full ptp-operator test suite.
Expand Down
371 changes: 371 additions & 0 deletions docs/netdevsim-ptp-operator-ci.drawio

Large diffs are not rendered by default.

69 changes: 68 additions & 1 deletion dpll/dpll_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -853,14 +853,67 @@ const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref)
return reg->ops;
}

/*
* On kernels with CONFIG_DPLL=y the built-in DPLL subsystem already
* registers a genl family named "dpll". We need to replace it with
* our own so that netdevsim DPLL devices are served by the DKMS code
* (which matches the netdevsim driver API exactly).
*
* Strategy:
* 1. Look up the kernel's dpll_nl_family via kprobes
* 2. genl_unregister_family() it
* 3. genl_register_family() our own (also named "dpll")
* 4. On exit, reverse the process
*/

#include <linux/kprobes.h>

static bool kernel_dpll_hijacked;

typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);

static unsigned long nsim_lookup_name(const char *name)
{
static kallsyms_lookup_name_t fn;

if (!fn) {
struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" };
int ret = register_kprobe(&kp);

if (ret < 0)
return 0;
fn = (kallsyms_lookup_name_t)kp.addr;
unregister_kprobe(&kp);
}
return fn(name);
}

static int __init dpll_init(void)
{
unsigned long addr;
int ret;

addr = nsim_lookup_name("dpll_nl_family");
if (!addr) {
pr_warn("nsim_dpll: cannot find kernel dpll_nl_family\n");
} else {
const struct genl_family *kfam =
(const struct genl_family *)addr;

ret = genl_unregister_family(kfam);
if (ret) {
pr_warn("nsim_dpll: failed to unregister kernel dpll family: %d\n", ret);
} else {
kernel_dpll_hijacked = true;
pr_info("nsim_dpll: unregistered kernel built-in dpll genl family\n");
}
}

ret = genl_register_family(&dpll_nl_family);
if (ret)
goto error;

pr_info("nsim_dpll: registered dpll genl family (replacing kernel built-in)\n");
return 0;

error:
Expand All @@ -871,10 +924,24 @@ static int __init dpll_init(void)
static void __exit dpll_exit(void)
{
genl_unregister_family(&dpll_nl_family);

/*
* We intentionally do NOT re-register the kernel's built-in
* dpll genl family here. genl_register_family() writes to the
* struct (setting family->id etc.) and the kernel's
* dpll_nl_family is in __ro_after_init memory — writing to it
* causes a fatal page fault on aarch64.
*
* This means the dpll genl family is absent after module
* unload, which is acceptable for CI/test environments.
*/
if (kernel_dpll_hijacked)
pr_info("nsim_dpll: kernel dpll family was hijacked; not restoring (ro_after_init)\n");

mutex_destroy(&dpll_lock);
}

subsys_initcall(dpll_init);
module_init(dpll_init);
module_exit(dpll_exit);

MODULE_LICENSE("GPL");
Expand Down
2 changes: 1 addition & 1 deletion dpll/dpll_nl.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ static const struct genl_multicast_group dpll_nl_mcgrps[] = {
};

struct genl_family dpll_nl_family __ro_after_init = {
.name = "nsim_dpll",
.name = "dpll",
.version = DPLL_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
Expand Down
17 changes: 11 additions & 6 deletions include/linux/ptp_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,33 @@ struct device;
struct mock_phc {
struct ptp_clock_info info;
struct ptp_clock *clock;
struct timecounter tc;
struct cyclecounter cc;
int logical_clk_id;
struct kref ref;
spinlock_t lock;
/* MONOTONIC-based timekeeping (immune to phc2sys stepping REALTIME/TAI) */
s64 offset_ns; /* PHC = CLOCK_MONOTONIC + offset_ns */
s64 freq_ppb; /* rate correction from adjfine */
u64 last_mono_ns; /* CLOCK_MONOTONIC snapshot at last update */
/*
* Two pins: index 0 unused, index 1 "GNSS1PPS" — matches common NIC
* layouts and ts2phc defaults (e.g. ts2phc.pin_index 1 on ens1f0).
* E810-style pin layout: GNSS-1PPS input + 4 external connectors.
* Pin index 0 = GNSS-1PPS (ts2phc default), followed by SMA1, SMA2,
* U.FL1, U.FL2 so the dashboard SMA probe discovers them.
*/
struct ptp_pin_desc pins[2];
struct ptp_pin_desc pins[5];
/* EXTTS (1PPS) simulation */
struct hrtimer extts_timer;
bool extts_enabled;
int extts_channel;
u64 last_extts_sec; /* dedup: last CLOCK_TAI second we reported */
};

#if IS_ENABLED(CONFIG_PTP_1588_CLOCK_MOCK)

struct mock_phc *mock_phc_create(struct device *dev, int logical_clk_id);
int mock_phc_index(struct mock_phc *phc);
int mock_phc_logical_clk_id(struct mock_phc *phc);
int mock_phc_logical_clk_id(struct mock_phc *phc);
void mock_phc_release(struct mock_phc *phc);
struct kobject *mock_phc_dev_kobj(struct mock_phc *phc);

struct ptp_clock_info *mock_phc_get_ptp_info(struct mock_phc *phc);
#else
Expand Down
1 change: 1 addition & 0 deletions include/nsim_rename.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#define mock_phc_index nsim_mock_phc_index
#define mock_phc_logical_clk_id nsim_mock_phc_logical_clk_id
#define mock_phc_get_ptp_info nsim_mock_phc_get_ptp_info
#define mock_phc_dev_kobj nsim_mock_phc_dev_kobj

/* ---- DPLL core (dpll_core.c) ------------------------------------------- */
#define dpll_device_get nsim_dpll_device_get
Expand Down
Loading
Loading