/
githubmirror
/
nvme-cli
Обзор
Документация
Войти
/
githubmirror
/
nvme-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
discoverd/src/events.c
390 строк
9 KB
Martin Belanger
discoverd: add the nvme-discoverd daemon
28 июл 2026, 17:26
28 июл 2026, 17:26
b3e5bc1
Код
Авторство
О чём код?
// SPDX-License-Identifier: GPL-2.0-or-later /* * This file is part of nvme-cli. * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries. * * Authors: Martin Belanger <martin.belanger@dell.com> */ #include <errno.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <systemd/sd-device.h> #include <systemd/sd-event.h> #include "events.h" #include "log.h" #include "tid.h" /* Sysfs soak delay: kernel sysfs attributes settle ~1 s after the add uevent. */ #define SOAK_DELAY_USEC (UINT64_C(1000000)) /* NVMe AEN for "Discovery Log Page Changed" (NVMe Base Spec §5.2.4.2). */ #define NVME_AEN_DLP_CHANGED "0x70f002" struct soak_entry { struct soak_entry *next; char *syspath; char *devname; sd_event_source *timer; struct events_ctx *ctx; }; struct events_ctx { sd_event *event; struct events_callbacks callbacks; void *user_data; sd_device_monitor *nvme_monitor; sd_device_monitor *fc_monitor; struct soak_entry *soaking; }; /* * Extract a named field from a comma-separated "key=value,..." string such * as the address sysattr: "traddr=192.0.2.1,trsvcid=4420,src_addr=10.0.0.1". * Returns an allocated string on success, NULL if not found. */ static char *extract_addr_field(const char *address, const char *key) { char search[64]; const char *p, *end; size_t len; if (!address || !key) return NULL; snprintf(search, sizeof(search), "%s=", key); p = strstr(address, search); if (!p) return NULL; p += strlen(search); end = strchr(p, ','); len = end ? (size_t)(end - p) : strlen(p); if (!len) return NULL; return strndup(p, len); } /* * Build a TID from sysfs attributes of an nvme device. * Returns NULL on error (e.g. required attributes missing). */ struct libnvmf_tid *tid_from_sysfs(sd_device *dev, bool *is_dc) { const char *transport = NULL, *address = NULL; const char *subsysnqn = NULL, *hostnqn = NULL; const char *host_iface = NULL, *cntrltype = NULL; char *traddr = NULL, *trsvcid = NULL, *host_traddr = NULL; struct libnvmf_tid *t = NULL; if (is_dc) *is_dc = false; sd_device_get_sysattr_value(dev, "transport", &transport); sd_device_get_sysattr_value(dev, "address", &address); sd_device_get_sysattr_value(dev, "subsysnqn", &subsysnqn); sd_device_get_sysattr_value(dev, "hostnqn", &hostnqn); sd_device_get_sysattr_value(dev, "host_iface", &host_iface); sd_device_get_sysattr_value(dev, "cntrltype", &cntrltype); if (!transport || !address || !subsysnqn) return NULL; traddr = extract_addr_field(address, "traddr"); trsvcid = extract_addr_field(address, "trsvcid"); // host_traddr may appear as "src_addr" in some kernel versions; try both host_traddr = extract_addr_field(address, "host_traddr"); if (!host_traddr) host_traddr = extract_addr_field(address, "src_addr"); if (traddr) { bool dc = shr_streq0(cntrltype, "discovery"); t = tid_new(transport, traddr, trsvcid, subsysnqn, host_traddr, host_iface, hostnqn, dc); if (is_dc) *is_dc = dc; } free(traddr); free(trsvcid); free(host_traddr); return t; } static void soak_entry_free(struct soak_entry *e) { if (!e) return; sd_event_source_unref(e->timer); free(e->syspath); free(e->devname); free(e); } static void soak_unlink(struct events_ctx *ctx, struct soak_entry *entry) { struct soak_entry **ep; for (ep = &ctx->soaking; *ep; ep = &(*ep)->next) { if (*ep == entry) { *ep = entry->next; return; } } } static int soak_timeout(sd_event_source *src __attribute__((unused)), uint64_t usec __attribute__((unused)), void *user_data) { struct soak_entry *e = user_data; struct events_ctx *ctx = e->ctx; sd_device *dev = NULL; const char *cntrltype = NULL; struct libnvmf_tid *t; if (sd_device_new_from_syspath(&dev, e->syspath) < 0) { // Device disappeared during soak — treat as removal. if (ctx->callbacks.nvme_remove) ctx->callbacks.nvme_remove(e->devname, ctx->user_data); goto out; } sd_device_get_sysattr_value(dev, "cntrltype", &cntrltype); if (shr_streq0(cntrltype, "discovery")) { t = tid_from_sysfs(dev, NULL); if (t) { if (ctx->callbacks.dc_add) ctx->callbacks.dc_add(e->devname, t, ctx->user_data); tid_free(t); } } else if (shr_streq0(cntrltype, "io")) { if (ctx->callbacks.ioc_add) ctx->callbacks.ioc_add(e->devname, ctx->user_data); } sd_device_unref(dev); out: soak_unlink(ctx, e); soak_entry_free(e); return 0; } static int schedule_soak(struct events_ctx *ctx, sd_device *dev, const char *devname) { struct soak_entry *e; const char *syspath = NULL; uint64_t now; int r; sd_device_get_syspath(dev, &syspath); if (!syspath) return -EINVAL; e = calloc(1, sizeof(*e)); if (!e) return -ENOMEM; e->syspath = strdup(syspath); e->devname = strdup(devname); e->ctx = ctx; if (!e->syspath || !e->devname) { soak_entry_free(e); return -ENOMEM; } r = sd_event_now(ctx->event, CLOCK_BOOTTIME, &now); if (r < 0) { soak_entry_free(e); return r; } r = sd_event_add_time(ctx->event, &e->timer, CLOCK_BOOTTIME, now + SOAK_DELAY_USEC, 0, soak_timeout, e); if (r < 0) { soak_entry_free(e); return r; } e->next = ctx->soaking; ctx->soaking = e; return 0; } static int nvme_monitor_handler(sd_device_monitor *monitor __attribute__((unused)), sd_device *dev, void *user_data) { struct events_ctx *ctx = user_data; sd_device_action_t action; const char *devname = NULL, *cntrltype = NULL; const char *nvme_event = NULL, *nvme_aen = NULL; if (sd_device_get_action(dev, &action) < 0) return 0; if (sd_device_get_sysname(dev, &devname) < 0) return 0; switch (action) { case SD_DEVICE_ADD: // Schedule a soak timer; the soak callback reads cntrltype // and calls the appropriate add handler. schedule_soak(ctx, dev, devname); break; case SD_DEVICE_REMOVE: if (ctx->callbacks.nvme_remove) ctx->callbacks.nvme_remove(devname, ctx->user_data); break; case SD_DEVICE_CHANGE: /* * Two relevant CHANGE events: * NVME_AEN=="0x70f002" — DLP changed AEN * NVME_EVENT=="rediscover" — ctrl reconnected after loss * Both are handled the same way: re-fetch the DLP. */ sd_device_get_property_value(dev, "NVME_AEN", &nvme_aen); sd_device_get_property_value(dev, "NVME_EVENT", &nvme_event); sd_device_get_sysattr_value(dev, "cntrltype", &cntrltype); if (shr_streq0(nvme_aen, NVME_AEN_DLP_CHANGED) || (shr_streq0(nvme_event, "rediscover") && shr_streq0(cntrltype, "discovery"))) { if (ctx->callbacks.dc_changed) ctx->callbacks.dc_changed(devname, ctx->user_data); } break; default: break; } return 0; } static int fc_monitor_handler(sd_device_monitor *monitor __attribute__((unused)), sd_device *dev, void *user_data) { struct events_ctx *ctx = user_data; const char *fc_event = NULL; const char *traddr = NULL, *host_traddr = NULL; struct libnvmf_tid *t; sd_device_get_property_value(dev, "FC_EVENT", &fc_event); if (!shr_streq0(fc_event, "nvmediscovery")) return 0; /* * Properties set by the kernel FC nvme-discovery uevent * (nvme_fc_signal_discovery_scan(), drivers/nvme/host/fc.c). * Note the "NVMEFC_" prefix, not "NVME_" - and the kernel never * sends TRSVCID or NQN for this event: FC has no port concept, * and the target is identified by address only, not by NQN. */ sd_device_get_property_value(dev, "NVMEFC_TRADDR", &traddr); sd_device_get_property_value(dev, "NVMEFC_HOST_TRADDR", &host_traddr); if (!traddr) return 0; /* * The kernel only fires this uevent for a remote port that * advertised FC_PORT_ROLE_NVME_DISCOVERY, so every instance of * this event is, by the kernel's own gating, a DC - there is no * NQN to check, and no well-known port to default either way. */ t = tid_new("fc", traddr, NULL, "nqn.2014-08.org.nvmexpress.discovery", host_traddr, NULL, NULL, true); if (!t) return 0; if (ctx->callbacks.fc_discovery) ctx->callbacks.fc_discovery(t, ctx->user_data); tid_free(t); return 0; } struct events_ctx *events_start(sd_event *event, const struct events_callbacks *callbacks, void *user_data) { struct events_ctx *ctx; int r; ctx = calloc(1, sizeof(*ctx)); if (!ctx) return NULL; ctx->event = event; ctx->callbacks = *callbacks; ctx->user_data = user_data; // nvme subsystem monitor r = sd_device_monitor_new(&ctx->nvme_monitor); if (r < 0) goto err; r = sd_device_monitor_filter_add_match_subsystem_devtype( ctx->nvme_monitor, "nvme", NULL); if (r < 0) goto err; r = sd_device_monitor_attach_event(ctx->nvme_monitor, event); if (r < 0) goto err; r = sd_device_monitor_start(ctx->nvme_monitor, nvme_monitor_handler, ctx); if (r < 0) goto err; // FC subsystem monitor r = sd_device_monitor_new(&ctx->fc_monitor); if (r < 0) goto err; r = sd_device_monitor_filter_add_match_subsystem_devtype( ctx->fc_monitor, "fc", NULL); if (r < 0) goto err; r = sd_device_monitor_attach_event(ctx->fc_monitor, event); if (r < 0) goto err; r = sd_device_monitor_start(ctx->fc_monitor, fc_monitor_handler, ctx); if (r < 0) goto err; return ctx; err: disc_err("%s: %s", __func__, strerror(-r)); events_stop(ctx); return NULL; } void events_stop(struct events_ctx *ctx) { struct soak_entry *e, *next; if (!ctx) return; for (e = ctx->soaking; e; e = next) { next = e->next; soak_entry_free(e); } sd_device_monitor_unref(ctx->nvme_monitor); sd_device_monitor_unref(ctx->fc_monitor); free(ctx); }