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
6 changes: 6 additions & 0 deletions doc/ledmon.conf.pod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ is set to false - only the drive that the RAID is rebuilding to will be marked
with appropriate LED pattern. If value is set to true all drives from RAID
that is during rebuild will blink during this operation.

B<USERSPACE_NPEM> - Use the userspace NPEM controller instead of the controller
that talks to the kernel NPEM driver. This is useful for kernels that don't
include the NPEM driver (CONFIG_PCI_NPEM), but it does not work with LED
control _DSM, and does not work if userspace can't access PCI config space.
The default value is false.

B<ALLOWLIST> - Ledmon will limit changing LED state to controllers listed on
allowlist. If any allowlist is set, only devices from the list will be scanned by
ledmon. The controllers should be separated by a comma (B<,>) character. Only
Expand Down
7 changes: 7 additions & 0 deletions src/common/config_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ static int parse_next(FILE *fd, struct ledmon_conf *conf)
conf->raid_members_only = parse_bool(s);
if (conf->raid_members_only < 0)
return -1;
} else if (!strncmp(s, "USERSPACE_NPEM=", 15)) {
s += 15;
conf->userspace_npem = parse_bool(s);
if (conf->userspace_npem < 0)
return -1;
} else if (_parse_and_add_to_list(s, WHITELIST, WHITELIST_LEN, &conf->cntrls_allowlist)) {
/* Deprecated, provided for backwards compatibility */
return 0;
Expand Down Expand Up @@ -329,6 +334,8 @@ int ledmon_write_shared_conf(struct ledmon_conf *conf)
"REBUILD_BLINK_ON_ALL=%d\n", conf->rebuild_blink_on_all);
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
"INTERVAL=%d\n", conf->scan_interval);
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
"USERSPACE_NPEM=%d\n", conf->userspace_npem);
allowlist = conf_list_to_str(&conf->cntrls_allowlist);
if (allowlist) {
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
Expand Down
1 change: 1 addition & 0 deletions src/common/config_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct ledmon_conf {
int blink_on_init;
int rebuild_blink_on_all;
int raid_members_only;
int userspace_npem;

/* allowlist and excludelist of controllers for blinking */
struct list cntrls_allowlist;
Expand Down
6 changes: 4 additions & 2 deletions src/ledctl/ledctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -985,8 +985,8 @@ static led_status_t _read_shared_conf(void)
/**
* @brief Unset unsupported config parameters.
*
* For ledctl only LOG_LEVEL and LOG_PATH are supported and desired.
* Unset other options.
* For ledctl only LOG_LEVEL, LOG_PATH, and USERSPACE_NPEM are supported and
* desired. Unset other options.
*/
static void _unset_unused_options(void)
{
Expand Down Expand Up @@ -1045,6 +1045,8 @@ static led_status_t load_library_prefs(void)

led_log_fd_set(ctx, get_log_fd(&conf));
led_log_level_set(ctx, conf.log_level);
if (conf.userspace_npem)
use_userspace_npem_controller(ctx);
return LED_STATUS_SUCCESS;
}

Expand Down
2 changes: 2 additions & 0 deletions src/ledmon/ledmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,8 @@ static int load_library_prefs(void)

led_log_fd_set(ctx, get_log_fd(&conf));
led_log_level_set(ctx, conf.log_level);
if (conf.userspace_npem)
use_userspace_npem_controller(ctx);
device_blink_behavior_set(ctx, conf.blink_on_migration, conf.blink_on_init,
conf.rebuild_blink_on_all, conf.raid_members_only);

Expand Down
4 changes: 2 additions & 2 deletions src/lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ SUBDIRS = include
LIB_SRCS = ahci.c block.c cntrl.c enclosure.c utils.c list.c \
raid.c scsi.c tail.c sysfs.c smp.c dellssd.c \
pci_slot.c vmdssd.c amd.c amd_sgpio.c amd_ipmi.c\
ipmi.c npem.c ses.c slot.c \
ipmi.c npem.c ses.c slot.c kernel_npem.c\
ahci.h amd_sgpio.h block.h cntrl.h dellssd.h utils.h \
enclosure.h list.h pci_slot.h raid.h scsi.h \
ses.h tail.h smp.h status.h sysfs.h \
vmdssd.h ipmi.h amd.h amd_ipmi.h npem.h libled_internal.c \
slot.h libled_private.h libled_internal.h
kernel_npem.h slot.h libled_private.h libled_internal.h

# Make a convenience library, to be used for led tools and the shared library
noinst_LTLIBRARIES = libledinternal.la
Expand Down
15 changes: 11 additions & 4 deletions src/lib/block.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "block.h"
#include "config.h"
#include "dellssd.h"
#include "kernel_npem.h"
#include "libled_private.h"
#include "npem.h"
#include "pci_slot.h"
Expand Down Expand Up @@ -83,7 +84,10 @@ static void _set_send_message_fn(struct block_device *device)
device->send_message_fn = vmdssd_write;
break;
case LED_CNTRL_TYPE_NPEM:
device->send_message_fn = npem_write;
if (device->cntrl->ctx->config.userspace_npem)
device->send_message_fn = npem_write;
else
device->send_message_fn = kernel_npem_write;
break;
case LED_CNTRL_TYPE_AMD:
device->send_message_fn = amd_write;
Expand Down Expand Up @@ -137,12 +141,15 @@ static char *_get_host(char *path, struct cntrl_device *cntrl)
result = scsi_get_host_path(path, cntrl->sysfs_path);
else if (cntrl->cntrl_type == LED_CNTRL_TYPE_AHCI)
result = ahci_get_port_path(path);
else if (cntrl->cntrl_type == LED_CNTRL_TYPE_DELLSSD)
else if (cntrl->cntrl_type == LED_CNTRL_TYPE_NPEM) {
if (cntrl->ctx->config.userspace_npem)
result = npem_get_path(cntrl->sysfs_path);
else
result = kernel_npem_get_path(cntrl->sysfs_path);
} else if (cntrl->cntrl_type == LED_CNTRL_TYPE_DELLSSD)
result = dellssd_get_path(cntrl->sysfs_path);
else if (cntrl->cntrl_type == LED_CNTRL_TYPE_VMD)
Comment thread
stuarthayes marked this conversation as resolved.
result = vmdssd_get_path(cntrl->sysfs_path);
else if (cntrl->cntrl_type == LED_CNTRL_TYPE_NPEM)
result = npem_get_path(cntrl->sysfs_path);
else if (cntrl->cntrl_type == LED_CNTRL_TYPE_AMD)
result = amd_get_path(path, cntrl->sysfs_path, cntrl->ctx);

Expand Down
6 changes: 5 additions & 1 deletion src/lib/cntrl.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "amd.h"
#include "cntrl.h"
#include "config.h"
#include "kernel_npem.h"
#include "list.h"
#include "libled_private.h"
#include "npem.h"
Expand Down Expand Up @@ -176,7 +177,10 @@ static int _is_vmd_cntrl(const char *path)

static int _is_npem_cntrl(const char *path, struct led_ctx *ctx)
{
return is_npem_capable(path, ctx);
if (ctx->config.userspace_npem)
return is_npem_capable(path, ctx);
else
return is_kernel_npem_present(path);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/lib/include/led/libled.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,16 @@ void LED_SYM_PUBLIC led_log_fd_set(struct led_ctx *ctx, int log_fd);
*/
void LED_SYM_PUBLIC led_log_level_set(struct led_ctx *ctx, enum led_log_level_enum level);

/**
* @brief Set use of userspace NPEM controller (instead of kernel NPEM controller).
*
* @param[in] ctx Library context
*
* Notes:
* - The kernel NPEM controller will be used unless this is called.
*/
void LED_SYM_PUBLIC use_userspace_npem_controller(struct led_ctx *ctx);

/**
* @brief Instructs the library to scan system hardware for block devices with LED support.
* This needs to be called before any other library functions can be utilized. Can be called to
Expand Down
212 changes: 212 additions & 0 deletions src/lib/kernel_npem.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2022 Intel Corporation.
// Copyright (C) 2025 Dell Inc.

/* System headers */
#include <stdio.h>
#include <string.h>
#include <pci/pci.h>
#include <time.h>
#include <stdbool.h>
#include <sys/stat.h>

/* Local headers */
#include "libled_private.h"
#include "cntrl.h"
#include "npem.h"
#include "utils.h"

/* NPEM OK Capable/Control */
#define PCI_NPEM_OK_CAP 0x004
/* NPEM Locate Capable/Control */
#define PCI_NPEM_LOCATE_CAP 0x008
/* NPEM Fail Capable/Control */
#define PCI_NPEM_FAIL_CAP 0x010
/* NPEM Rebuild Capable/Control */
#define PCI_NPEM_REBUILD_CAP 0x020
/* NPEM Predicted Failure Analysis Capable/Control */
#define PCI_NPEM_PFA_CAP 0x040
/* NPEM Hot Spare Capable/Control */
#define PCI_NPEM_HOT_SPARE_CAP 0x080
/* NPEM in a Critical Array Capable/Control */
#define PCI_NPEM_CRA_CAP 0x100
/* NPEM in a Failed Array Capable/Control */
#define PCI_NPEM_FA_CAP 0x200

static const struct ibpi2value ibpi_to_npem_capability[] = {
{LED_IBPI_PATTERN_NORMAL, PCI_NPEM_OK_CAP},
{LED_IBPI_PATTERN_ONESHOT_NORMAL, PCI_NPEM_OK_CAP},
{LED_IBPI_PATTERN_DEGRADED, PCI_NPEM_CRA_CAP},
{LED_IBPI_PATTERN_HOTSPARE, PCI_NPEM_HOT_SPARE_CAP},
{LED_IBPI_PATTERN_REBUILD, PCI_NPEM_REBUILD_CAP},
{LED_IBPI_PATTERN_FAILED_ARRAY, PCI_NPEM_FA_CAP},
{LED_IBPI_PATTERN_PFA, PCI_NPEM_PFA_CAP},
{LED_IBPI_PATTERN_FAILED_DRIVE, PCI_NPEM_FAIL_CAP},
{LED_IBPI_PATTERN_LOCATE, PCI_NPEM_LOCATE_CAP},
{LED_IBPI_PATTERN_LOCATE_OFF, PCI_NPEM_OK_CAP},
{LED_IBPI_PATTERN_UNKNOWN, 0}
};

struct kernel_npem_led {
int bitmask;
char *sysfs_led_name;
};

const struct kernel_npem_led kernel_npem_leds[] = {
{PCI_NPEM_OK_CAP, "enclosure:ok"},
{PCI_NPEM_LOCATE_CAP, "enclosure:locate"},
{PCI_NPEM_FAIL_CAP, "enclosure:fail"},
{PCI_NPEM_REBUILD_CAP, "enclosure:rebuild"},
{PCI_NPEM_PFA_CAP, "enclosure:pfa"},
{PCI_NPEM_HOT_SPARE_CAP, "enclosure:hotspare"},
{PCI_NPEM_CRA_CAP, "enclosure:ica"},
{PCI_NPEM_FA_CAP, "enclosure:ifa"},
};

char *kernel_npem_get_path(const char *cntrl_path)
{
return strdup(cntrl_path);
}

#define make_led_path(sysfs_led_path, sysfs_path, sysfs_led_name) \
snprintf(sysfs_led_path, sizeof(sysfs_led_path), "%s/leds/%s:%s/brightness", \
sysfs_path, basename(sysfs_path), sysfs_led_name)

static u32 read_kernel_npem_register(const char *sysfs_path)
{
char led_path[PATH_MAX];
int i;
u32 reg = 0;

for (i = 0; i < ARRAY_SIZE(kernel_npem_leds); i++) {
make_led_path(led_path, sysfs_path, kernel_npem_leds[i].sysfs_led_name);
reg |= get_int("/", 0, led_path) ? kernel_npem_leds[i].bitmask : 0;
}
return reg;
}

static int write_kernel_npem_register(const char *sysfs_path, u32 val)
{
char led_path[PATH_MAX], val_text[4];
int i;
struct stat sb;

for (i = 0; i < ARRAY_SIZE(kernel_npem_leds); i++) {
make_led_path(led_path, sysfs_path, kernel_npem_leds[i].sysfs_led_name);
snprintf(val_text, sizeof(val_text), val & kernel_npem_leds[i].bitmask ? "1" : "0");
if (!stat(led_path, &sb))
buf_write(led_path, val_text);
}
return 0;
}

static u32 kernel_npem_supported_mask(const char *sysfs_path)
{
char led_path[PATH_MAX];
int i;
struct stat sb;
u32 supported = 0;

for (i = 0; i < ARRAY_SIZE(kernel_npem_leds); i++) {
make_led_path(led_path, sysfs_path, kernel_npem_leds[i].sysfs_led_name);
if (!stat(led_path, &sb))
supported |= kernel_npem_leds[i].bitmask;
}
return supported;
}

int is_kernel_npem_present(const char *path)
{
return kernel_npem_supported_mask(path) ? 1 : 0;
}

enum led_ibpi_pattern kernel_npem_get_state(struct slot_property *slot)
{
const char *path = slot->slot_spec.cntrl->sysfs_path;
const struct ibpi2value *ibpi2val;
u32 reg;

reg = read_kernel_npem_register(path);
ibpi2val = get_by_bits(reg, ibpi_to_npem_capability,
ARRAY_SIZE(ibpi_to_npem_capability));

/*
* If LOCATE is the only pattern supported, report LOCATE_OFF instead
* of UNKNOWN.
*/
if ((ibpi2val->ibpi == LED_IBPI_PATTERN_UNKNOWN) &&
(kernel_npem_supported_mask(path) == PCI_NPEM_LOCATE_CAP))
return LED_IBPI_PATTERN_LOCATE_OFF;

return ibpi2val->ibpi;
}

status_t kernel_npem_set_slot(struct led_ctx *ctx, const char *sysfs_path,
enum led_ibpi_pattern state)
{
const struct ibpi2value *ibpi2val;
u32 requested, supported;

ibpi2val = get_by_ibpi(state, ibpi_to_npem_capability,
ARRAY_SIZE(ibpi_to_npem_capability));

if (ibpi2val->ibpi == LED_IBPI_PATTERN_UNKNOWN) {
lib_log(ctx, LED_LOG_LEVEL_INFO,
"NPEM: Controller doesn't support %s pattern\n", ibpi2str(state));
return STATUS_INVALID_STATE;
}

requested = (u32)ibpi2val->value;
supported = kernel_npem_supported_mask(sysfs_path);

if (!(requested & supported))
/*
* Allow OK (normal and locate_off states) to turn off other
* states even if OK state isn't actually supported.
*/
if (requested != PCI_NPEM_OK_CAP) {
lib_log(ctx, LED_LOG_LEVEL_INFO,
"NPEM: Controller %s doesn't support %s pattern\n",
sysfs_path, ibpi2str(state));
return STATUS_INVALID_STATE;
}

write_kernel_npem_register(sysfs_path, requested);

return STATUS_SUCCESS;
}

status_t kernel_npem_set_state(struct slot_property *slot, enum led_ibpi_pattern state)
{
return kernel_npem_set_slot(slot->slot_spec.cntrl->ctx,
slot->slot_spec.cntrl->sysfs_path, state);
}

const struct slot_property_common kernel_npem_slot_common = {
.cntrl_type = LED_CNTRL_TYPE_NPEM,
.get_state_fn = kernel_npem_get_state,
.set_slot_fn = kernel_npem_set_state,
};

struct slot_property *kernel_npem_slot_property_init(struct cntrl_device *kernel_npem_cntrl)
{
struct slot_property *result = calloc(1, sizeof(struct slot_property));

if (result == NULL)
return NULL;

result->bl_device = get_block_device_from_sysfs_path(kernel_npem_cntrl->ctx,
kernel_npem_cntrl->sysfs_path, true);
result->slot_spec.cntrl = kernel_npem_cntrl;
snprintf(result->slot_id, PATH_MAX, "%s", kernel_npem_cntrl->sysfs_path);
result->c = &kernel_npem_slot_common;
return result;
}

status_t kernel_npem_write(struct block_device *device, enum led_ibpi_pattern ibpi)
{
if (ibpi < LED_IBPI_PATTERN_NORMAL || ibpi > LED_IBPI_PATTERN_LOCATE_OFF)
return STATUS_INVALID_STATE;

return kernel_npem_set_slot(device->cntrl->ctx, device->cntrl->sysfs_path, ibpi);
}
Loading