diff --git a/src/host.c b/src/host.c index 8a5ceeb..31e1677 100644 --- a/src/host.c +++ b/src/host.c @@ -11,6 +11,7 @@ #include "connection.h" #include "compiler.h" #include "host.h" +#include "lock.h" #include "log.h" #include "ccan/autodata/autodata.h" @@ -21,6 +22,7 @@ struct bridge { struct list_node entry; const struct bridge_driver *driver; struct ahb *ahb; + int lock; }; void print_bridge_drivers(void) @@ -89,6 +91,7 @@ static inline int host_probe_bridge(struct host *ctx, { struct ahb *ahb; struct bridge *bridge; + int lock; if (driver->disabled) { logd("Skipping bridge driver %s\n", driver->name); @@ -97,16 +100,32 @@ static inline int host_probe_bridge(struct host *ctx, logd("Trying bridge driver %s\n", driver->name); + /* + * Hold the bridge's lock across the probe attempt as well as its + * subsequent use, not just the successful case: probing can itself + * drive the bridge hardware (e.g. to fingerprint what's present), + * which is exactly the kind of access that must not race against + * another culvert instance. + */ + lock = lock_acquire(driver->name); + if (lock < 0) + return lock; + ahb = driver->probe(connection); - if (!ahb) + if (!ahb) { + lock_release(lock); return 0; + } bridge = malloc(sizeof(*bridge)); - if (!bridge) + if (!bridge) { + lock_release(lock); return -ENOMEM; + } bridge->driver = driver; bridge->ahb = ahb; + bridge->lock = lock; list_add(&ctx->bridges, &bridge->entry); return 0; @@ -152,6 +171,7 @@ void host_destroy(struct host *ctx) list_for_each_safe(&ctx->bridges, bridge, next, entry) { bridge->driver->destroy(bridge->ahb); + lock_release(bridge->lock); list_del(&bridge->entry); free(bridge); } diff --git a/src/lock.c b/src/lock.c new file mode 100644 index 0000000..8be906f --- /dev/null +++ b/src/lock.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Jackson Bopp + +#include "lock.h" +#include "log.h" + +#include +#include +#include +#include +#include + +#define LOCK_DIR "/run/lock" + +int lock_acquire(const char *name) +{ + char path[64]; + int fd; + int rc; + + rc = snprintf(path, sizeof(path), "%s/culvert.%s", LOCK_DIR, name); + if (rc < 0 || (size_t)rc >= sizeof(path)) { + loge("Bridge name '%s' is too long to form a lock path\n", + name); + return -ENAMETOOLONG; + } + + fd = open(path, O_CREAT | O_RDWR | O_CLOEXEC, 0644); + if (fd < 0) { + rc = -errno; + loge("Failed to open lock file '%s': %d\n", path, rc); + return rc; + } + + if (flock(fd, LOCK_EX | LOCK_NB) < 0) { + rc = -errno; + if (rc == -EWOULDBLOCK) { + loge("Bridge '%s' is already in use by another culvert instance\n", + name); + } else { + loge("Failed to lock '%s': %d\n", path, rc); + } + close(fd); + return rc; + } + + return fd; +} + +void lock_release(int fd) +{ + if (fd < 0) + return; + + flock(fd, LOCK_UN); + close(fd); +} diff --git a/src/lock.h b/src/lock.h new file mode 100644 index 0000000..97dd5e2 --- /dev/null +++ b/src/lock.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2026 Jackson Bopp */ + +#ifndef _LOCK_H +#define _LOCK_H + +/* + * Acquire an exclusive, non-blocking advisory lock scoped to `name`, + * preventing another culvert instance from concurrently using the same + * named resource (e.g. a bridge driver). Returns an open file descriptor + * on success, or a negative errno if the lock is already held elsewhere + * or could not be acquired. + */ +int lock_acquire(const char *name); + +/* Release a lock previously acquired with lock_acquire(). No-op if fd < 0. */ +void lock_release(int fd); + +#endif diff --git a/src/meson.build b/src/meson.build index f552b47..ca37217 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,6 +12,7 @@ src = files( 'culvert.c', 'flash.c', 'host.c', + 'lock.c', 'log.c', 'mmio.c', 'pci.c',