From 954b46ccc2455a7581450bb1bd76b2c6a29c6988 Mon Sep 17 00:00:00 2001 From: dmxruser Date: Sun, 24 Aug 2025 14:04:00 +1200 Subject: [PATCH] cpupower: Add Intel boost state and frequency monitor This patch adds a new monitor to the cpupower tool to read boost state and frequency for Intel CPUs. It outputs it by reading the APERF and MPERF Model Specific Registers (MSRs), which are not fully utilized by existing cpupower methods. This is a better approach to reading the boost frequencies, as other methods often fail at reflecting the dynamic frequencies during turbo boost, making it difficult to debug and monitor preformance issues. new file: tools/leds/iio/.gitignore new file: tools/leds/iio/Build new file: tools/leds/iio/Makefile new file: tools/leds/iio/iio_event_monitor.c new file: tools/leds/iio/iio_generic_buffer.c new file: tools/leds/iio/iio_utils.c new file: tools/leds/iio/iio_utils.h new file: tools/leds/iio/lsiio.c modified: tools/power/cpupower/Makefile modified: tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c modified: tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h modified: tools/power/cpupower/utils/idle_monitor/idle_monitors.def new file: tools/power/cpupower/utils/idle_monitor/intelmonitor_idle.c modified: tools/power/cpupower/utils/idle_monitor/mperf_monitor.c Signed-off-by: Daniel Maior --- tools/leds/iio/.gitignore | 5 + tools/leds/iio/Build | 4 + tools/leds/iio/Makefile | 71 ++ tools/leds/iio/iio_event_monitor.c | 461 ++++++++ tools/leds/iio/iio_generic_buffer.c | 782 ++++++++++++++ tools/leds/iio/iio_utils.c | 988 ++++++++++++++++++ tools/leds/iio/iio_utils.h | 79 ++ tools/leds/iio/lsiio.c | 188 ++++ tools/power/cpupower/Makefile | 101 +- .../utils/idle_monitor/cpuidle_sysfs.c | 9 +- .../utils/idle_monitor/cpupower-monitor.h | 15 +- .../utils/idle_monitor/idle_monitors.def | 1 + .../utils/idle_monitor/intelmonitor_idle.c | 152 +++ .../utils/idle_monitor/mperf_monitor.c | 31 +- 14 files changed, 2811 insertions(+), 76 deletions(-) create mode 100644 tools/leds/iio/.gitignore create mode 100644 tools/leds/iio/Build create mode 100644 tools/leds/iio/Makefile create mode 100644 tools/leds/iio/iio_event_monitor.c create mode 100644 tools/leds/iio/iio_generic_buffer.c create mode 100644 tools/leds/iio/iio_utils.c create mode 100644 tools/leds/iio/iio_utils.h create mode 100644 tools/leds/iio/lsiio.c create mode 100644 tools/power/cpupower/utils/idle_monitor/intelmonitor_idle.c diff --git a/tools/leds/iio/.gitignore b/tools/leds/iio/.gitignore new file mode 100644 index 000000000000..5bd6f4df98b7 --- /dev/null +++ b/tools/leds/iio/.gitignore @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +iio_event_monitor +iio_generic_buffer +lsiio +include/ diff --git a/tools/leds/iio/Build b/tools/leds/iio/Build new file mode 100644 index 000000000000..8d0f3af3723f --- /dev/null +++ b/tools/leds/iio/Build @@ -0,0 +1,4 @@ +iio_utils-y += iio_utils.o +lsiio-y += lsiio.o iio_utils.o +iio_event_monitor-y += iio_event_monitor.o iio_utils.o +iio_generic_buffer-y += iio_generic_buffer.o iio_utils.o diff --git a/tools/leds/iio/Makefile b/tools/leds/iio/Makefile new file mode 100644 index 000000000000..3bcce0b7d10f --- /dev/null +++ b/tools/leds/iio/Makefile @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../scripts/Makefile.include + +bindir ?= /usr/bin + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +# (this improves performance and avoids hard-to-debug behaviour); +MAKEFLAGS += -r + +override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include + +ALL_TARGETS := iio_event_monitor lsiio iio_generic_buffer +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +# +# We need the following to be outside of kernel tree +# +$(OUTPUT)include/linux/iio: ../../include/uapi/linux/iio + mkdir -p $(OUTPUT)include/linux/iio 2>&1 || true + ln -sf $(CURDIR)/../../include/uapi/linux/iio/buffer.h $@ + ln -sf $(CURDIR)/../../include/uapi/linux/iio/events.h $@ + ln -sf $(CURDIR)/../../include/uapi/linux/iio/types.h $@ + +prepare: $(OUTPUT)include/linux/iio + +IIO_UTILS_IN := $(OUTPUT)iio_utils-in.o +$(IIO_UTILS_IN): prepare FORCE + $(Q)$(MAKE) $(build)=iio_utils + +LSIIO_IN := $(OUTPUT)lsiio-in.o +$(LSIIO_IN): prepare FORCE $(OUTPUT)iio_utils-in.o + $(Q)$(MAKE) $(build)=lsiio +$(OUTPUT)lsiio: $(LSIIO_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +IIO_EVENT_MONITOR_IN := $(OUTPUT)iio_event_monitor-in.o +$(IIO_EVENT_MONITOR_IN): prepare FORCE $(OUTPUT)iio_utils-in.o + $(Q)$(MAKE) $(build)=iio_event_monitor +$(OUTPUT)iio_event_monitor: $(IIO_EVENT_MONITOR_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +IIO_GENERIC_BUFFER_IN := $(OUTPUT)iio_generic_buffer-in.o +$(IIO_GENERIC_BUFFER_IN): prepare FORCE $(OUTPUT)iio_utils-in.o + $(Q)$(MAKE) $(build)=iio_generic_buffer +$(OUTPUT)iio_generic_buffer: $(IIO_GENERIC_BUFFER_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +clean: + rm -f $(ALL_PROGRAMS) + rm -rf $(OUTPUT)include/linux/iio + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -name '\.*.cmd' -delete + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(bindir); \ + for program in $(ALL_PROGRAMS); do \ + install $$program $(DESTDIR)$(bindir); \ + done + +FORCE: + +.PHONY: all install clean FORCE prepare diff --git a/tools/leds/iio/iio_event_monitor.c b/tools/leds/iio/iio_event_monitor.c new file mode 100644 index 000000000000..eab7b082f19d --- /dev/null +++ b/tools/leds/iio/iio_event_monitor.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Industrialio event test code. + * + * Copyright (c) 2011-2012 Lars-Peter Clausen + * + * This program is primarily intended as an example application. + * Reads the current buffer setup from sysfs and starts a short capture + * from the specified device, pretty printing the result after appropriate + * conversion. + * + * Usage: + * iio_event_monitor + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio_utils.h" +#include +#include + +static const char * const iio_chan_type_name_spec[] = { + [IIO_VOLTAGE] = "voltage", + [IIO_CURRENT] = "current", + [IIO_POWER] = "power", + [IIO_ACCEL] = "accel", + [IIO_ANGL_VEL] = "anglvel", + [IIO_MAGN] = "magn", + [IIO_LIGHT] = "illuminance", + [IIO_INTENSITY] = "intensity", + [IIO_PROXIMITY] = "proximity", + [IIO_TEMP] = "temp", + [IIO_INCLI] = "incli", + [IIO_ROT] = "rot", + [IIO_ANGL] = "angl", + [IIO_TIMESTAMP] = "timestamp", + [IIO_CAPACITANCE] = "capacitance", + [IIO_ALTVOLTAGE] = "altvoltage", + [IIO_CCT] = "cct", + [IIO_PRESSURE] = "pressure", + [IIO_HUMIDITYRELATIVE] = "humidityrelative", + [IIO_ACTIVITY] = "activity", + [IIO_STEPS] = "steps", + [IIO_ENERGY] = "energy", + [IIO_DISTANCE] = "distance", + [IIO_VELOCITY] = "velocity", + [IIO_CONCENTRATION] = "concentration", + [IIO_RESISTANCE] = "resistance", + [IIO_PH] = "ph", + [IIO_UVINDEX] = "uvindex", + [IIO_GRAVITY] = "gravity", + [IIO_POSITIONRELATIVE] = "positionrelative", + [IIO_PHASE] = "phase", + [IIO_MASSCONCENTRATION] = "massconcentration", + [IIO_DELTA_ANGL] = "deltaangl", + [IIO_DELTA_VELOCITY] = "deltavelocity", + [IIO_COLORTEMP] = "colortemp", + [IIO_CHROMATICITY] = "chromaticity", + [IIO_ATTENTION] = "attention", +}; + +static const char * const iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc", + [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", + [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", + [IIO_EV_TYPE_CHANGE] = "change", + [IIO_EV_TYPE_MAG_REFERENCED] = "mag_referenced", + [IIO_EV_TYPE_GESTURE] = "gesture", + [IIO_EV_TYPE_FAULT] = "fault", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling", + [IIO_EV_DIR_SINGLETAP] = "singletap", + [IIO_EV_DIR_DOUBLETAP] = "doubletap", + [IIO_EV_DIR_FAULT_OPENWIRE] = "openwire", +}; + +static const char * const iio_modifier_names[] = { + [IIO_MOD_X] = "x", + [IIO_MOD_Y] = "y", + [IIO_MOD_Z] = "z", + [IIO_MOD_X_AND_Y] = "x&y", + [IIO_MOD_X_AND_Z] = "x&z", + [IIO_MOD_Y_AND_Z] = "y&z", + [IIO_MOD_X_AND_Y_AND_Z] = "x&y&z", + [IIO_MOD_X_OR_Y] = "x|y", + [IIO_MOD_X_OR_Z] = "x|z", + [IIO_MOD_Y_OR_Z] = "y|z", + [IIO_MOD_X_OR_Y_OR_Z] = "x|y|z", + [IIO_MOD_LIGHT_BOTH] = "both", + [IIO_MOD_LIGHT_IR] = "ir", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y] = "sqrt(x^2+y^2)", + [IIO_MOD_SUM_SQUARED_X_Y_Z] = "x^2+y^2+z^2", + [IIO_MOD_LIGHT_CLEAR] = "clear", + [IIO_MOD_LIGHT_RED] = "red", + [IIO_MOD_LIGHT_GREEN] = "green", + [IIO_MOD_LIGHT_BLUE] = "blue", + [IIO_MOD_LIGHT_UV] = "uv", + [IIO_MOD_LIGHT_UVA] = "uva", + [IIO_MOD_LIGHT_UVB] = "uvb", + [IIO_MOD_LIGHT_DUV] = "duv", + [IIO_MOD_QUATERNION] = "quaternion", + [IIO_MOD_TEMP_AMBIENT] = "ambient", + [IIO_MOD_TEMP_OBJECT] = "object", + [IIO_MOD_NORTH_MAGN] = "from_north_magnetic", + [IIO_MOD_NORTH_TRUE] = "from_north_true", + [IIO_MOD_NORTH_MAGN_TILT_COMP] = "from_north_magnetic_tilt_comp", + [IIO_MOD_NORTH_TRUE_TILT_COMP] = "from_north_true_tilt_comp", + [IIO_MOD_RUNNING] = "running", + [IIO_MOD_JOGGING] = "jogging", + [IIO_MOD_WALKING] = "walking", + [IIO_MOD_STILL] = "still", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z] = "sqrt(x^2+y^2+z^2)", + [IIO_MOD_I] = "i", + [IIO_MOD_Q] = "q", + [IIO_MOD_CO2] = "co2", + [IIO_MOD_ETHANOL] = "ethanol", + [IIO_MOD_H2] = "h2", + [IIO_MOD_VOC] = "voc", + [IIO_MOD_PM1] = "pm1", + [IIO_MOD_PM2P5] = "pm2p5", + [IIO_MOD_PM4] = "pm4", + [IIO_MOD_PM10] = "pm10", + [IIO_MOD_O2] = "o2", + [IIO_MOD_LINEAR_X] = "linear_x", + [IIO_MOD_LINEAR_Y] = "linear_y", + [IIO_MOD_LINEAR_Z] = "linear_z", + [IIO_MOD_PITCH] = "pitch", + [IIO_MOD_YAW] = "yaw", + [IIO_MOD_ROLL] = "roll", +}; + +static bool event_is_known(struct iio_event_data *event) +{ + enum iio_chan_type type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event->id); + enum iio_modifier mod = IIO_EVENT_CODE_EXTRACT_MODIFIER(event->id); + enum iio_event_type ev_type = IIO_EVENT_CODE_EXTRACT_TYPE(event->id); + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(event->id); + + switch (type) { + case IIO_VOLTAGE: + case IIO_CURRENT: + case IIO_POWER: + case IIO_ACCEL: + case IIO_ANGL_VEL: + case IIO_MAGN: + case IIO_LIGHT: + case IIO_INTENSITY: + case IIO_PROXIMITY: + case IIO_TEMP: + case IIO_INCLI: + case IIO_ROT: + case IIO_ANGL: + case IIO_TIMESTAMP: + case IIO_CAPACITANCE: + case IIO_ALTVOLTAGE: + case IIO_CCT: + case IIO_PRESSURE: + case IIO_HUMIDITYRELATIVE: + case IIO_ACTIVITY: + case IIO_STEPS: + case IIO_ENERGY: + case IIO_DISTANCE: + case IIO_VELOCITY: + case IIO_CONCENTRATION: + case IIO_RESISTANCE: + case IIO_PH: + case IIO_UVINDEX: + case IIO_GRAVITY: + case IIO_POSITIONRELATIVE: + case IIO_PHASE: + case IIO_MASSCONCENTRATION: + case IIO_DELTA_ANGL: + case IIO_DELTA_VELOCITY: + case IIO_COLORTEMP: + case IIO_CHROMATICITY: + case IIO_ATTENTION: + break; + default: + return false; + } + + switch (mod) { + case IIO_NO_MOD: + case IIO_MOD_X: + case IIO_MOD_Y: + case IIO_MOD_Z: + case IIO_MOD_X_AND_Y: + case IIO_MOD_X_AND_Z: + case IIO_MOD_Y_AND_Z: + case IIO_MOD_X_AND_Y_AND_Z: + case IIO_MOD_X_OR_Y: + case IIO_MOD_X_OR_Z: + case IIO_MOD_Y_OR_Z: + case IIO_MOD_X_OR_Y_OR_Z: + case IIO_MOD_LIGHT_BOTH: + case IIO_MOD_LIGHT_IR: + case IIO_MOD_ROOT_SUM_SQUARED_X_Y: + case IIO_MOD_SUM_SQUARED_X_Y_Z: + case IIO_MOD_LIGHT_CLEAR: + case IIO_MOD_LIGHT_RED: + case IIO_MOD_LIGHT_GREEN: + case IIO_MOD_LIGHT_BLUE: + case IIO_MOD_LIGHT_UV: + case IIO_MOD_LIGHT_DUV: + case IIO_MOD_QUATERNION: + case IIO_MOD_TEMP_AMBIENT: + case IIO_MOD_TEMP_OBJECT: + case IIO_MOD_NORTH_MAGN: + case IIO_MOD_NORTH_TRUE: + case IIO_MOD_NORTH_MAGN_TILT_COMP: + case IIO_MOD_NORTH_TRUE_TILT_COMP: + case IIO_MOD_RUNNING: + case IIO_MOD_JOGGING: + case IIO_MOD_WALKING: + case IIO_MOD_STILL: + case IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z: + case IIO_MOD_I: + case IIO_MOD_Q: + case IIO_MOD_CO2: + case IIO_MOD_ETHANOL: + case IIO_MOD_H2: + case IIO_MOD_VOC: + case IIO_MOD_PM1: + case IIO_MOD_PM2P5: + case IIO_MOD_PM4: + case IIO_MOD_PM10: + case IIO_MOD_O2: + break; + default: + return false; + } + + switch (ev_type) { + case IIO_EV_TYPE_THRESH: + case IIO_EV_TYPE_MAG: + case IIO_EV_TYPE_ROC: + case IIO_EV_TYPE_THRESH_ADAPTIVE: + case IIO_EV_TYPE_MAG_ADAPTIVE: + case IIO_EV_TYPE_CHANGE: + case IIO_EV_TYPE_GESTURE: + case IIO_EV_TYPE_FAULT: + break; + default: + return false; + } + + switch (dir) { + case IIO_EV_DIR_EITHER: + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + case IIO_EV_DIR_SINGLETAP: + case IIO_EV_DIR_DOUBLETAP: + case IIO_EV_DIR_FAULT_OPENWIRE: + case IIO_EV_DIR_NONE: + break; + default: + return false; + } + + return true; +} + +static void print_event(struct iio_event_data *event) +{ + enum iio_chan_type type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event->id); + enum iio_modifier mod = IIO_EVENT_CODE_EXTRACT_MODIFIER(event->id); + enum iio_event_type ev_type = IIO_EVENT_CODE_EXTRACT_TYPE(event->id); + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(event->id); + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(event->id); + int chan2 = IIO_EVENT_CODE_EXTRACT_CHAN2(event->id); + bool diff = IIO_EVENT_CODE_EXTRACT_DIFF(event->id); + + if (!event_is_known(event)) { + fprintf(stderr, "Unknown event: time: %lld, id: %llx\n", + event->timestamp, event->id); + + return; + } + + printf("Event: time: %lld, type: %s", event->timestamp, + iio_chan_type_name_spec[type]); + + if (mod != IIO_NO_MOD) + printf("(%s)", iio_modifier_names[mod]); + + if (chan >= 0) { + printf(", channel: %d", chan); + if (diff && chan2 >= 0) + printf("-%d", chan2); + } + + printf(", evtype: %s", iio_ev_type_text[ev_type]); + + if (dir != IIO_EV_DIR_NONE) + printf(", direction: %s", iio_ev_dir_text[dir]); + + printf("\n"); + fflush(stdout); +} + +/* Enable or disable events in sysfs if the knob is available */ +static void enable_events(char *dev_dir, int enable) +{ + const struct dirent *ent; + char evdir[256]; + int ret; + DIR *dp; + + snprintf(evdir, sizeof(evdir), FORMAT_EVENTS_DIR, dev_dir); + evdir[sizeof(evdir)-1] = '\0'; + + dp = opendir(evdir); + if (!dp) { + fprintf(stderr, "Enabling/disabling events: can't open %s\n", + evdir); + return; + } + + while (ent = readdir(dp), ent) { + if (iioutils_check_suffix(ent->d_name, "_en")) { + printf("%sabling: %s\n", + enable ? "En" : "Dis", + ent->d_name); + ret = write_sysfs_int(ent->d_name, evdir, + enable); + if (ret < 0) + fprintf(stderr, "Failed to enable/disable %s\n", + ent->d_name); + } + } + + if (closedir(dp) == -1) { + perror("Enabling/disabling channels: " + "Failed to close directory"); + return; + } +} + +int main(int argc, char **argv) +{ + struct iio_event_data event; + const char *device_name; + char *dev_dir_name = NULL; + char *chrdev_name; + int ret; + int dev_num; + int fd, event_fd; + bool all_events = false; + + if (argc == 2) { + device_name = argv[1]; + } else if (argc == 3) { + device_name = argv[2]; + if (!strcmp(argv[1], "-a")) + all_events = true; + } else { + fprintf(stderr, + "Usage: iio_event_monitor [options] \n" + "Listen and display events from IIO devices\n" + " -a Auto-activate all available events\n"); + return -1; + } + + dev_num = find_type_by_name(device_name, "iio:device"); + if (dev_num >= 0) { + printf("Found IIO device with name %s with device number %d\n", + device_name, dev_num); + ret = asprintf(&chrdev_name, "/dev/iio:device%d", dev_num); + if (ret < 0) + return -ENOMEM; + /* Look up sysfs dir as well if we can */ + ret = asprintf(&dev_dir_name, "%siio:device%d", iio_dir, dev_num); + if (ret < 0) + return -ENOMEM; + } else { + /* + * If we can't find an IIO device by name assume device_name is + * an IIO chrdev + */ + chrdev_name = strdup(device_name); + if (!chrdev_name) + return -ENOMEM; + } + + if (all_events && dev_dir_name) + enable_events(dev_dir_name, 1); + + fd = open(chrdev_name, 0); + if (fd == -1) { + ret = -errno; + fprintf(stderr, "Failed to open %s\n", chrdev_name); + goto error_free_chrdev_name; + } + + ret = ioctl(fd, IIO_GET_EVENT_FD_IOCTL, &event_fd); + if (ret == -1 || event_fd == -1) { + ret = -errno; + if (ret == -ENODEV) + fprintf(stderr, + "This device does not support events\n"); + else + fprintf(stderr, "Failed to retrieve event fd\n"); + if (close(fd) == -1) + perror("Failed to close character device file"); + + goto error_free_chrdev_name; + } + + if (close(fd) == -1) { + ret = -errno; + goto error_free_chrdev_name; + } + + while (true) { + ret = read(event_fd, &event, sizeof(event)); + if (ret == -1) { + if (errno == EAGAIN) { + fprintf(stderr, "nothing available\n"); + continue; + } else { + ret = -errno; + perror("Failed to read event from device"); + break; + } + } + + if (ret != sizeof(event)) { + fprintf(stderr, "Reading event failed!\n"); + ret = -EIO; + break; + } + + print_event(&event); + } + + if (close(event_fd) == -1) + perror("Failed to close event file"); + +error_free_chrdev_name: + /* Disable events after use */ + if (all_events && dev_dir_name) + enable_events(dev_dir_name, 0); + + free(chrdev_name); + free(dev_dir_name); + + return ret; +} diff --git a/tools/leds/iio/iio_generic_buffer.c b/tools/leds/iio/iio_generic_buffer.c new file mode 100644 index 000000000000..bc82bb6a7a2a --- /dev/null +++ b/tools/leds/iio/iio_generic_buffer.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Industrialio buffer test code. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is primarily intended as an example application. + * Reads the current buffer setup from sysfs and starts a short capture + * from the specified device, pretty printing the result after appropriate + * conversion. + * + * Command line parameters + * generic_buffer -n -t + * If trigger name is not specified the program assumes you want a dataready + * trigger associated with the device and goes looking for it. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio_utils.h" + +/** + * enum autochan - state for the automatic channel enabling mechanism + */ +enum autochan { + AUTOCHANNELS_DISABLED, + AUTOCHANNELS_ENABLED, + AUTOCHANNELS_ACTIVE, +}; + +/** + * size_from_channelarray() - calculate the storage size of a scan + * @channels: the channel info array + * @num_channels: number of channels + * + * Has the side effect of filling the channels[i].location values used + * in processing the buffer output. + **/ +static unsigned int size_from_channelarray(struct iio_channel_info *channels, int num_channels) +{ + unsigned int bytes = 0; + int i = 0, max = 0; + unsigned int misalignment; + + while (i < num_channels) { + if (channels[i].bytes > max) + max = channels[i].bytes; + if (bytes % channels[i].bytes == 0) + channels[i].location = bytes; + else + channels[i].location = bytes - bytes % channels[i].bytes + + channels[i].bytes; + + bytes = channels[i].location + channels[i].bytes; + i++; + } + /* + * We want the data in next sample to also be properly aligned so + * we'll add padding at the end if needed. Adding padding only + * works for channel data which size is 2^n bytes. + */ + misalignment = bytes % max; + if (misalignment) + bytes += max - misalignment; + + return bytes; +} + +static void print1byte(uint8_t input, struct iio_channel_info *info) +{ + /* + * Shift before conversion to avoid sign extension + * of left aligned data + */ + input >>= info->shift; + input &= info->mask; + if (info->is_signed) { + int8_t val = (int8_t)(input << (8 - info->bits_used)) >> + (8 - info->bits_used); + printf("%05f ", ((float)val + info->offset) * info->scale); + } else { + printf("%05f ", ((float)input + info->offset) * info->scale); + } +} + +static void print2byte(uint16_t input, struct iio_channel_info *info) +{ + /* First swap if incorrect endian */ + if (info->be) + input = be16toh(input); + else + input = le16toh(input); + + /* + * Shift before conversion to avoid sign extension + * of left aligned data + */ + input >>= info->shift; + input &= info->mask; + if (info->is_signed) { + int16_t val = (int16_t)(input << (16 - info->bits_used)) >> + (16 - info->bits_used); + printf("%05f ", ((float)val + info->offset) * info->scale); + } else { + printf("%05f ", ((float)input + info->offset) * info->scale); + } +} + +static void print4byte(uint32_t input, struct iio_channel_info *info) +{ + /* First swap if incorrect endian */ + if (info->be) + input = be32toh(input); + else + input = le32toh(input); + + /* + * Shift before conversion to avoid sign extension + * of left aligned data + */ + input >>= info->shift; + input &= info->mask; + if (info->is_signed) { + int32_t val = (int32_t)(input << (32 - info->bits_used)) >> + (32 - info->bits_used); + printf("%05f ", ((float)val + info->offset) * info->scale); + } else { + printf("%05f ", ((float)input + info->offset) * info->scale); + } +} + +static void print8byte(uint64_t input, struct iio_channel_info *info) +{ + /* First swap if incorrect endian */ + if (info->be) + input = be64toh(input); + else + input = le64toh(input); + + /* + * Shift before conversion to avoid sign extension + * of left aligned data + */ + input >>= info->shift; + input &= info->mask; + if (info->is_signed) { + int64_t val = (int64_t)(input << (64 - info->bits_used)) >> + (64 - info->bits_used); + /* special case for timestamp */ + if (info->scale == 1.0f && info->offset == 0.0f) + printf("%" PRId64 " ", val); + else + printf("%05f ", + ((float)val + info->offset) * info->scale); + } else { + printf("%05f ", ((float)input + info->offset) * info->scale); + } +} + +/** + * process_scan() - print out the values in SI units + * @data: pointer to the start of the scan + * @channels: information about the channels. + * Note: size_from_channelarray must have been called first + * to fill the location offsets. + * @num_channels: number of channels + **/ +static void process_scan(char *data, struct iio_channel_info *channels, + int num_channels) +{ + int k; + + for (k = 0; k < num_channels; k++) + switch (channels[k].bytes) { + /* only a few cases implemented so far */ + case 1: + print1byte(*(uint8_t *)(data + channels[k].location), + &channels[k]); + break; + case 2: + print2byte(*(uint16_t *)(data + channels[k].location), + &channels[k]); + break; + case 4: + print4byte(*(uint32_t *)(data + channels[k].location), + &channels[k]); + break; + case 8: + print8byte(*(uint64_t *)(data + channels[k].location), + &channels[k]); + break; + default: + break; + } + printf("\n"); +} + +static int enable_disable_all_channels(char *dev_dir_name, int buffer_idx, int enable) +{ + const struct dirent *ent; + char scanelemdir[256]; + DIR *dp; + int ret; + + snprintf(scanelemdir, sizeof(scanelemdir), + FORMAT_SCAN_ELEMENTS_DIR, dev_dir_name, buffer_idx); + scanelemdir[sizeof(scanelemdir)-1] = '\0'; + + dp = opendir(scanelemdir); + if (!dp) { + fprintf(stderr, "Enabling/disabling channels: can't open %s\n", + scanelemdir); + return -EIO; + } + + ret = -ENOENT; + while (ent = readdir(dp), ent) { + if (iioutils_check_suffix(ent->d_name, "_en")) { + printf("%sabling: %s\n", + enable ? "En" : "Dis", + ent->d_name); + ret = write_sysfs_int(ent->d_name, scanelemdir, + enable); + if (ret < 0) + fprintf(stderr, "Failed to enable/disable %s\n", + ent->d_name); + } + } + + if (closedir(dp) == -1) { + perror("Enabling/disabling channels: " + "Failed to close directory"); + return -errno; + } + return 0; +} + +static void print_usage(void) +{ + fprintf(stderr, "Usage: generic_buffer [options]...\n" + "Capture, convert and output data from IIO device buffer\n" + " -a Auto-activate all available channels\n" + " -A Force-activate ALL channels\n" + " -b The buffer which to open (by index), default 0\n" + " -c Do n conversions, or loop forever if n < 0\n" + " -e Disable wait for event (new data)\n" + " -g Use trigger-less mode\n" + " -l Set buffer length to n samples\n" + " --device-name -n \n" + " --device-num -N \n" + " Set device by name or number (mandatory)\n" + " --trigger-name -t \n" + " --trigger-num -T \n" + " Set trigger by name or number\n" + " -w Set delay between reads in us (event-less mode)\n"); +} + +static enum autochan autochannels = AUTOCHANNELS_DISABLED; +static char *dev_dir_name = NULL; +static char *buf_dir_name = NULL; +static int buffer_idx = 0; +static bool current_trigger_set = false; + +static void cleanup(void) +{ + int ret; + + /* Disable trigger */ + if (dev_dir_name && current_trigger_set) { + /* Disconnect the trigger - just write a dummy name. */ + ret = write_sysfs_string("trigger/current_trigger", + dev_dir_name, "NULL"); + if (ret < 0) + fprintf(stderr, "Failed to disable trigger: %s\n", + strerror(-ret)); + current_trigger_set = false; + } + + /* Disable buffer */ + if (buf_dir_name) { + ret = write_sysfs_int("enable", buf_dir_name, 0); + if (ret < 0) + fprintf(stderr, "Failed to disable buffer: %s\n", + strerror(-ret)); + } + + /* Disable channels if auto-enabled */ + if (dev_dir_name && autochannels == AUTOCHANNELS_ACTIVE) { + ret = enable_disable_all_channels(dev_dir_name, buffer_idx, 0); + if (ret) + fprintf(stderr, "Failed to disable all channels\n"); + autochannels = AUTOCHANNELS_DISABLED; + } +} + +static void sig_handler(int signum) +{ + fprintf(stderr, "Caught signal %d\n", signum); + cleanup(); + exit(-signum); +} + +static void register_cleanup(void) +{ + struct sigaction sa = { .sa_handler = sig_handler }; + const int signums[] = { SIGINT, SIGTERM, SIGABRT }; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(signums); ++i) { + ret = sigaction(signums[i], &sa, NULL); + if (ret) { + perror("Failed to register signal handler"); + exit(-1); + } + } +} + +static const struct option longopts[] = { + { "device-name", 1, 0, 'n' }, + { "device-num", 1, 0, 'N' }, + { "trigger-name", 1, 0, 't' }, + { "trigger-num", 1, 0, 'T' }, + { } +}; + +int main(int argc, char **argv) +{ + long long num_loops = 2; + unsigned long timedelay = 1000000; + unsigned long buf_len = 128; + + ssize_t i; + unsigned long long j; + unsigned long toread; + int ret, c; + struct stat st; + int fd = -1; + int buf_fd = -1; + + int num_channels = 0; + char *trigger_name = NULL, *device_name = NULL; + + char *data = NULL; + ssize_t read_size; + int dev_num = -1, trig_num = -1; + char *buffer_access = NULL; + unsigned int scan_size; + int noevents = 0; + int notrigger = 0; + char *dummy; + bool force_autochannels = false; + + struct iio_channel_info *channels = NULL; + + register_cleanup(); + + while ((c = getopt_long(argc, argv, "aAb:c:egl:n:N:t:T:w:?", longopts, + NULL)) != -1) { + switch (c) { + case 'a': + autochannels = AUTOCHANNELS_ENABLED; + break; + case 'A': + autochannels = AUTOCHANNELS_ENABLED; + force_autochannels = true; + break; + case 'b': + errno = 0; + buffer_idx = strtoll(optarg, &dummy, 10); + if (errno) { + ret = -errno; + goto error; + } + if (buffer_idx < 0) { + ret = -ERANGE; + goto error; + } + + break; + case 'c': + errno = 0; + num_loops = strtoll(optarg, &dummy, 10); + if (errno) { + ret = -errno; + goto error; + } + + break; + case 'e': + noevents = 1; + break; + case 'g': + notrigger = 1; + break; + case 'l': + errno = 0; + buf_len = strtoul(optarg, &dummy, 10); + if (errno) { + ret = -errno; + goto error; + } + + break; + case 'n': + device_name = strdup(optarg); + break; + case 'N': + errno = 0; + dev_num = strtoul(optarg, &dummy, 10); + if (errno) { + ret = -errno; + goto error; + } + break; + case 't': + trigger_name = strdup(optarg); + break; + case 'T': + errno = 0; + trig_num = strtoul(optarg, &dummy, 10); + if (errno) + return -errno; + break; + case 'w': + errno = 0; + timedelay = strtoul(optarg, &dummy, 10); + if (errno) { + ret = -errno; + goto error; + } + break; + case '?': + print_usage(); + ret = -1; + goto error; + } + } + + /* Find the device requested */ + if (dev_num < 0 && !device_name) { + fprintf(stderr, "Device not set\n"); + print_usage(); + ret = -1; + goto error; + } else if (dev_num >= 0 && device_name) { + fprintf(stderr, "Only one of --device-num or --device-name needs to be set\n"); + print_usage(); + ret = -1; + goto error; + } else if (dev_num < 0) { + dev_num = find_type_by_name(device_name, "iio:device"); + if (dev_num < 0) { + fprintf(stderr, "Failed to find the %s\n", device_name); + ret = dev_num; + goto error; + } + } + printf("iio device number being used is %d\n", dev_num); + + ret = asprintf(&dev_dir_name, "%siio:device%d", iio_dir, dev_num); + if (ret < 0) + return -ENOMEM; + /* Fetch device_name if specified by number */ + if (!device_name) { + device_name = malloc(IIO_MAX_NAME_LENGTH); + if (!device_name) { + ret = -ENOMEM; + goto error; + } + ret = read_sysfs_string("name", dev_dir_name, device_name); + if (ret < 0) { + fprintf(stderr, "Failed to read name of device %d\n", dev_num); + goto error; + } + } + + if (notrigger) { + printf("trigger-less mode selected\n"); + } else if (trig_num >= 0) { + char *trig_dev_name; + ret = asprintf(&trig_dev_name, "%strigger%d", iio_dir, trig_num); + if (ret < 0) { + return -ENOMEM; + } + trigger_name = malloc(IIO_MAX_NAME_LENGTH); + if (!trigger_name) { + ret = -ENOMEM; + goto error; + } + ret = read_sysfs_string("name", trig_dev_name, trigger_name); + free(trig_dev_name); + if (ret < 0) { + fprintf(stderr, "Failed to read trigger%d name from\n", trig_num); + return ret; + } + printf("iio trigger number being used is %d\n", trig_num); + } else { + if (!trigger_name) { + /* + * Build the trigger name. If it is device associated + * its name is _dev[n] where n matches + * the device number found above. + */ + ret = asprintf(&trigger_name, + "%s-dev%d", device_name, dev_num); + if (ret < 0) { + ret = -ENOMEM; + goto error; + } + } + + /* Look for this "-devN" trigger */ + trig_num = find_type_by_name(trigger_name, "trigger"); + if (trig_num < 0) { + /* OK try the simpler "-trigger" suffix instead */ + free(trigger_name); + ret = asprintf(&trigger_name, + "%s-trigger", device_name); + if (ret < 0) { + ret = -ENOMEM; + goto error; + } + } + + trig_num = find_type_by_name(trigger_name, "trigger"); + if (trig_num < 0) { + fprintf(stderr, "Failed to find the trigger %s\n", + trigger_name); + ret = trig_num; + goto error; + } + + printf("iio trigger number being used is %d\n", trig_num); + } + + /* + * Parse the files in scan_elements to identify what channels are + * present + */ + ret = build_channel_array(dev_dir_name, buffer_idx, &channels, &num_channels); + if (ret) { + fprintf(stderr, "Problem reading scan element information\n" + "diag %s\n", dev_dir_name); + goto error; + } + if (num_channels && autochannels == AUTOCHANNELS_ENABLED && + !force_autochannels) { + fprintf(stderr, "Auto-channels selected but some channels " + "are already activated in sysfs\n"); + fprintf(stderr, "Proceeding without activating any channels\n"); + } + + if ((!num_channels && autochannels == AUTOCHANNELS_ENABLED) || + (autochannels == AUTOCHANNELS_ENABLED && force_autochannels)) { + fprintf(stderr, "Enabling all channels\n"); + + ret = enable_disable_all_channels(dev_dir_name, buffer_idx, 1); + if (ret) { + fprintf(stderr, "Failed to enable all channels\n"); + goto error; + } + + /* This flags that we need to disable the channels again */ + autochannels = AUTOCHANNELS_ACTIVE; + + ret = build_channel_array(dev_dir_name, buffer_idx, &channels, + &num_channels); + if (ret) { + fprintf(stderr, "Problem reading scan element " + "information\n" + "diag %s\n", dev_dir_name); + goto error; + } + if (!num_channels) { + fprintf(stderr, "Still no channels after " + "auto-enabling, giving up\n"); + goto error; + } + } + + if (!num_channels && autochannels == AUTOCHANNELS_DISABLED) { + fprintf(stderr, + "No channels are enabled, we have nothing to scan.\n"); + fprintf(stderr, "Enable channels manually in " + FORMAT_SCAN_ELEMENTS_DIR + "/*_en or pass -a to autoenable channels and " + "try again.\n", dev_dir_name, buffer_idx); + ret = -ENOENT; + goto error; + } + + /* + * Construct the directory name for the associated buffer. + * As we know that the lis3l02dq has only one buffer this may + * be built rather than found. + */ + ret = asprintf(&buf_dir_name, + "%siio:device%d/buffer%d", iio_dir, dev_num, buffer_idx); + if (ret < 0) { + ret = -ENOMEM; + goto error; + } + + if (stat(buf_dir_name, &st)) { + fprintf(stderr, "Could not stat() '%s', got error %d: %s\n", + buf_dir_name, errno, strerror(errno)); + ret = -errno; + goto error; + } + + if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "File '%s' is not a directory\n", buf_dir_name); + ret = -EFAULT; + goto error; + } + + if (!notrigger) { + printf("%s %s\n", dev_dir_name, trigger_name); + /* + * Set the device trigger to be the data ready trigger found + * above + */ + ret = write_sysfs_string_and_verify("trigger/current_trigger", + dev_dir_name, + trigger_name); + if (ret < 0) { + fprintf(stderr, + "Failed to write current_trigger file\n"); + goto error; + } + } + + ret = asprintf(&buffer_access, "/dev/iio:device%d", dev_num); + if (ret < 0) { + ret = -ENOMEM; + goto error; + } + + /* Attempt to open non blocking the access dev */ + fd = open(buffer_access, O_RDONLY | O_NONBLOCK); + if (fd == -1) { /* TODO: If it isn't there make the node */ + ret = -errno; + fprintf(stderr, "Failed to open %s\n", buffer_access); + goto error; + } + + /* specify for which buffer index we want an FD */ + buf_fd = buffer_idx; + + ret = ioctl(fd, IIO_BUFFER_GET_FD_IOCTL, &buf_fd); + if (ret == -1 || buf_fd == -1) { + ret = -errno; + if (ret == -ENODEV || ret == -EINVAL) + fprintf(stderr, + "Device does not have this many buffers\n"); + else + fprintf(stderr, "Failed to retrieve buffer fd\n"); + + goto error; + } + + /* Setup ring buffer parameters */ + ret = write_sysfs_int("length", buf_dir_name, buf_len); + if (ret < 0) + goto error; + + /* Enable the buffer */ + ret = write_sysfs_int("enable", buf_dir_name, 1); + if (ret < 0) { + fprintf(stderr, + "Failed to enable buffer '%s': %s\n", + buf_dir_name, strerror(-ret)); + goto error; + } + + scan_size = size_from_channelarray(channels, num_channels); + + size_t total_buf_len = scan_size * buf_len; + + if (scan_size > 0 && total_buf_len / scan_size != buf_len) { + ret = -EFAULT; + perror("Integer overflow happened when calculate scan_size * buf_len"); + goto error; + } + + data = malloc(total_buf_len); + if (!data) { + ret = -ENOMEM; + goto error; + } + + /** + * This check is being done here for sanity reasons, however it + * should be omitted under normal operation. + * If this is buffer0, we check that we get EBUSY after this point. + */ + if (buffer_idx == 0) { + errno = 0; + read_size = read(fd, data, 1); + if (read_size > -1 || errno != EBUSY) { + ret = -EFAULT; + perror("Reading from '%s' should not be possible after ioctl()"); + goto error; + } + } + + /* close now the main chardev FD and let the buffer FD work */ + if (close(fd) == -1) + perror("Failed to close character device file"); + fd = -1; + + for (j = 0; j < num_loops || num_loops < 0; j++) { + if (!noevents) { + struct pollfd pfd = { + .fd = buf_fd, + .events = POLLIN, + }; + + ret = poll(&pfd, 1, -1); + if (ret < 0) { + ret = -errno; + goto error; + } else if (ret == 0) { + continue; + } + + } else { + usleep(timedelay); + } + + toread = buf_len; + + read_size = read(buf_fd, data, toread * scan_size); + if (read_size < 0) { + if (errno == EAGAIN) { + fprintf(stderr, "nothing available\n"); + continue; + } else { + break; + } + } + for (i = 0; i < read_size / scan_size; i++) + process_scan(data + scan_size * i, channels, + num_channels); + } + +error: + cleanup(); + + if (fd >= 0 && close(fd) == -1) + perror("Failed to close character device"); + if (buf_fd >= 0 && close(buf_fd) == -1) + perror("Failed to close buffer"); + free(buffer_access); + free(data); + free(buf_dir_name); + for (i = num_channels - 1; i >= 0; i--) { + free(channels[i].name); + free(channels[i].generic_name); + } + free(channels); + free(trigger_name); + free(device_name); + free(dev_dir_name); + + return ret; +} diff --git a/tools/leds/iio/iio_utils.c b/tools/leds/iio/iio_utils.c new file mode 100644 index 000000000000..c5c5082cb24e --- /dev/null +++ b/tools/leds/iio/iio_utils.c @@ -0,0 +1,988 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* IIO - useful set of util functionality + * + * Copyright (c) 2008 Jonathan Cameron + */ +#include +#include +#include +#include +#include +#include +#include +#include "iio_utils.h" + +const char *iio_dir = "/sys/bus/iio/devices/"; + +static char * const iio_direction[] = { + "in", + "out", +}; + +/** + * iioutils_break_up_name() - extract generic name from full channel name + * @full_name: the full channel name + * @generic_name: the output generic channel name + * + * Returns 0 on success, or a negative error code if string extraction failed. + **/ +int iioutils_break_up_name(const char *full_name, char **generic_name) +{ + char *current; + char *w, *r; + char *working, *prefix = ""; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(iio_direction); i++) + if (!strncmp(full_name, iio_direction[i], + strlen(iio_direction[i]))) { + prefix = iio_direction[i]; + break; + } + + current = strdup(full_name + strlen(prefix) + 1); + if (!current) + return -ENOMEM; + + working = strtok(current, "_\0"); + if (!working) { + free(current); + return -EINVAL; + } + + w = working; + r = working; + + while (*r != '\0') { + if (!isdigit(*r)) { + *w = *r; + w++; + } + + r++; + } + *w = '\0'; + ret = asprintf(generic_name, "%s_%s", prefix, working); + free(current); + + return (ret == -1) ? -ENOMEM : 0; +} + +/** + * iioutils_get_type() - find and process _type attribute data + * @is_signed: output whether channel is signed + * @bytes: output how many bytes the channel storage occupies + * @bits_used: output number of valid bits of data + * @shift: output amount of bits to shift right data before applying bit mask + * @mask: output a bit mask for the raw data + * @be: output if data in big endian + * @device_dir: the IIO device directory + * @buffer_idx: the IIO buffer index + * @name: the channel name + * @generic_name: the channel type name + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +static int iioutils_get_type(unsigned int *is_signed, unsigned int *bytes, + unsigned int *bits_used, unsigned int *shift, + uint64_t *mask, unsigned int *be, + const char *device_dir, int buffer_idx, + const char *name, const char *generic_name) +{ + FILE *sysfsfp; + int ret; + DIR *dp; + char *scan_el_dir, *builtname, *builtname_generic, *filename = 0; + char signchar, endianchar; + unsigned padint; + const struct dirent *ent; + + ret = asprintf(&scan_el_dir, FORMAT_SCAN_ELEMENTS_DIR, device_dir, buffer_idx); + if (ret < 0) + return -ENOMEM; + + ret = asprintf(&builtname, FORMAT_TYPE_FILE, name); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_scan_el_dir; + } + ret = asprintf(&builtname_generic, FORMAT_TYPE_FILE, generic_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_builtname; + } + + dp = opendir(scan_el_dir); + if (!dp) { + ret = -errno; + goto error_free_builtname_generic; + } + + ret = -ENOENT; + while (ent = readdir(dp), ent) + if ((strcmp(builtname, ent->d_name) == 0) || + (strcmp(builtname_generic, ent->d_name) == 0)) { + ret = asprintf(&filename, + "%s/%s", scan_el_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_closedir; + } + + sysfsfp = fopen(filename, "r"); + if (!sysfsfp) { + ret = -errno; + fprintf(stderr, "failed to open %s\n", + filename); + goto error_free_filename; + } + + ret = fscanf(sysfsfp, + "%ce:%c%u/%u>>%u", + &endianchar, + &signchar, + bits_used, + &padint, shift); + if (ret < 0) { + ret = -errno; + fprintf(stderr, + "failed to pass scan type description\n"); + goto error_close_sysfsfp; + } else if (ret != 5) { + ret = -EIO; + fprintf(stderr, + "scan type description didn't match\n"); + goto error_close_sysfsfp; + } + + *be = (endianchar == 'b'); + *bytes = padint / 8; + if (*bits_used == 64) + *mask = ~(0ULL); + else + *mask = (1ULL << *bits_used) - 1ULL; + + *is_signed = (signchar == 's'); + if (fclose(sysfsfp)) { + ret = -errno; + fprintf(stderr, "Failed to close %s\n", + filename); + goto error_free_filename; + } + + sysfsfp = 0; + free(filename); + filename = 0; + + /* + * Avoid having a more generic entry overwriting + * the settings. + */ + if (strcmp(builtname, ent->d_name) == 0) + break; + } + +error_close_sysfsfp: + if (sysfsfp) + if (fclose(sysfsfp)) + perror("iioutils_get_type(): Failed to close file"); + +error_free_filename: + if (filename) + free(filename); + +error_closedir: + if (closedir(dp) == -1) + perror("iioutils_get_type(): Failed to close directory"); + +error_free_builtname_generic: + free(builtname_generic); +error_free_builtname: + free(builtname); +error_free_scan_el_dir: + free(scan_el_dir); + + return ret; +} + +/** + * iioutils_get_param_float() - read a float value from a channel parameter + * @output: output the float value + * @param_name: the parameter name to read + * @device_dir: the IIO device directory in sysfs + * @name: the channel name + * @generic_name: the channel type name + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int iioutils_get_param_float(float *output, const char *param_name, + const char *device_dir, const char *name, + const char *generic_name) +{ + FILE *sysfsfp; + int ret; + DIR *dp; + char *builtname, *builtname_generic; + char *filename = NULL; + const struct dirent *ent; + + ret = asprintf(&builtname, "%s_%s", name, param_name); + if (ret < 0) + return -ENOMEM; + + ret = asprintf(&builtname_generic, + "%s_%s", generic_name, param_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_builtname; + } + + dp = opendir(device_dir); + if (!dp) { + ret = -errno; + goto error_free_builtname_generic; + } + + ret = -ENOENT; + while (ent = readdir(dp), ent) + if ((strcmp(builtname, ent->d_name) == 0) || + (strcmp(builtname_generic, ent->d_name) == 0)) { + ret = asprintf(&filename, + "%s/%s", device_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_closedir; + } + + sysfsfp = fopen(filename, "r"); + if (!sysfsfp) { + ret = -errno; + goto error_free_filename; + } + + errno = 0; + if (fscanf(sysfsfp, "%f", output) != 1) + ret = errno ? -errno : -ENODATA; + + fclose(sysfsfp); + break; + } +error_free_filename: + if (filename) + free(filename); + +error_closedir: + if (closedir(dp) == -1) + perror("iioutils_get_param_float(): Failed to close directory"); + +error_free_builtname_generic: + free(builtname_generic); +error_free_builtname: + free(builtname); + + return ret; +} + +/** + * bsort_channel_array_by_index() - sort the array in index order + * @ci_array: the iio_channel_info array to be sorted + * @cnt: the amount of array elements + **/ + +void bsort_channel_array_by_index(struct iio_channel_info *ci_array, int cnt) +{ + struct iio_channel_info temp; + int x, y; + + for (x = 0; x < cnt; x++) + for (y = 0; y < (cnt - 1); y++) + if (ci_array[y].index > ci_array[y + 1].index) { + temp = ci_array[y + 1]; + ci_array[y + 1] = ci_array[y]; + ci_array[y] = temp; + } +} + +/** + * build_channel_array() - function to figure out what channels are present + * @device_dir: the IIO device directory in sysfs + * @buffer_idx: the IIO buffer for this channel array + * @ci_array: output the resulting array of iio_channel_info + * @counter: output the amount of array elements + * + * Returns 0 on success, otherwise a negative error code. + **/ +int build_channel_array(const char *device_dir, int buffer_idx, + struct iio_channel_info **ci_array, int *counter) +{ + DIR *dp; + FILE *sysfsfp; + int count = 0, i; + struct iio_channel_info *current; + int ret; + const struct dirent *ent; + char *scan_el_dir; + char *filename; + + *counter = 0; + ret = asprintf(&scan_el_dir, FORMAT_SCAN_ELEMENTS_DIR, device_dir, buffer_idx); + if (ret < 0) + return -ENOMEM; + + dp = opendir(scan_el_dir); + if (!dp) { + ret = -errno; + goto error_free_name; + } + + while (ent = readdir(dp), ent) + if (strcmp(ent->d_name + strlen(ent->d_name) - strlen("_en"), + "_en") == 0) { + ret = asprintf(&filename, + "%s/%s", scan_el_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_close_dir; + } + + sysfsfp = fopen(filename, "r"); + free(filename); + if (!sysfsfp) { + ret = -errno; + goto error_close_dir; + } + + errno = 0; + if (fscanf(sysfsfp, "%i", &ret) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("build_channel_array(): Failed to close file"); + + goto error_close_dir; + } + if (ret == 1) + (*counter)++; + + if (fclose(sysfsfp)) { + ret = -errno; + goto error_close_dir; + } + + } + + *ci_array = malloc(sizeof(**ci_array) * (*counter)); + if (!*ci_array) { + ret = -ENOMEM; + goto error_close_dir; + } + + rewinddir(dp); + while (ent = readdir(dp), ent) { + if (strcmp(ent->d_name + strlen(ent->d_name) - strlen("_en"), + "_en") == 0) { + int current_enabled = 0; + + current = &(*ci_array)[count++]; + ret = asprintf(&filename, + "%s/%s", scan_el_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + /* decrement count to avoid freeing name */ + count--; + goto error_cleanup_array; + } + + sysfsfp = fopen(filename, "r"); + free(filename); + if (!sysfsfp) { + ret = -errno; + count--; + goto error_cleanup_array; + } + + errno = 0; + if (fscanf(sysfsfp, "%i", ¤t_enabled) != 1) { + ret = errno ? -errno : -ENODATA; + count--; + goto error_cleanup_array; + } + + if (fclose(sysfsfp)) { + ret = -errno; + count--; + goto error_cleanup_array; + } + + if (!current_enabled) { + count--; + continue; + } + + current->scale = 1.0; + current->offset = 0; + current->name = strndup(ent->d_name, + strlen(ent->d_name) - + strlen("_en")); + if (!current->name) { + ret = -ENOMEM; + count--; + goto error_cleanup_array; + } + + /* Get the generic and specific name elements */ + ret = iioutils_break_up_name(current->name, + ¤t->generic_name); + if (ret) { + free(current->name); + count--; + goto error_cleanup_array; + } + + ret = asprintf(&filename, + "%s/%s_index", + scan_el_dir, + current->name); + if (ret < 0) { + ret = -ENOMEM; + goto error_cleanup_array; + } + + sysfsfp = fopen(filename, "r"); + free(filename); + if (!sysfsfp) { + ret = -errno; + fprintf(stderr, "failed to open %s/%s_index\n", + scan_el_dir, current->name); + goto error_cleanup_array; + } + + errno = 0; + if (fscanf(sysfsfp, "%u", ¤t->index) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("build_channel_array(): Failed to close file"); + + goto error_cleanup_array; + } + + if (fclose(sysfsfp)) { + ret = -errno; + goto error_cleanup_array; + } + + /* Find the scale */ + ret = iioutils_get_param_float(¤t->scale, + "scale", + device_dir, + current->name, + current->generic_name); + if ((ret < 0) && (ret != -ENOENT)) + goto error_cleanup_array; + + ret = iioutils_get_param_float(¤t->offset, + "offset", + device_dir, + current->name, + current->generic_name); + if ((ret < 0) && (ret != -ENOENT)) + goto error_cleanup_array; + + ret = iioutils_get_type(¤t->is_signed, + ¤t->bytes, + ¤t->bits_used, + ¤t->shift, + ¤t->mask, + ¤t->be, + device_dir, + buffer_idx, + current->name, + current->generic_name); + if (ret < 0) + goto error_cleanup_array; + } + } + + if (closedir(dp) == -1) { + ret = -errno; + goto error_cleanup_array; + } + + free(scan_el_dir); + /* reorder so that the array is in index order */ + bsort_channel_array_by_index(*ci_array, *counter); + + return 0; + +error_cleanup_array: + for (i = count - 1; i >= 0; i--) { + free((*ci_array)[i].name); + free((*ci_array)[i].generic_name); + } + free(*ci_array); + *ci_array = NULL; + *counter = 0; +error_close_dir: + if (dp) + if (closedir(dp) == -1) + perror("build_channel_array(): Failed to close dir"); + +error_free_name: + free(scan_el_dir); + + return ret; +} + +static int calc_digits(int num) +{ + int count = 0; + + /* It takes a digit to represent zero */ + if (!num) + return 1; + + while (num != 0) { + num /= 10; + count++; + } + + return count; +} + +/** + * find_type_by_name() - function to match top level types by name + * @name: top level type instance name + * @type: the type of top level instance being searched + * + * Returns the device number of a matched IIO device on success, otherwise a + * negative error code. + * Typical types this is used for are device and trigger. + **/ +int find_type_by_name(const char *name, const char *type) +{ + const struct dirent *ent; + int number, numstrlen, ret; + + FILE *namefp; + DIR *dp; + char thisname[IIO_MAX_NAME_LENGTH]; + char *filename; + + dp = opendir(iio_dir); + if (!dp) { + fprintf(stderr, "No industrialio devices available\n"); + return -ENODEV; + } + + while (ent = readdir(dp), ent) { + if (strcmp(ent->d_name, ".") != 0 && + strcmp(ent->d_name, "..") != 0 && + strlen(ent->d_name) > strlen(type) && + strncmp(ent->d_name, type, strlen(type)) == 0) { + errno = 0; + ret = sscanf(ent->d_name + strlen(type), "%d", &number); + if (ret < 0) { + ret = -errno; + fprintf(stderr, + "failed to read element number\n"); + goto error_close_dir; + } else if (ret != 1) { + ret = -EIO; + fprintf(stderr, + "failed to match element number\n"); + goto error_close_dir; + } + + numstrlen = calc_digits(number); + /* verify the next character is not a colon */ + if (strncmp(ent->d_name + strlen(type) + numstrlen, + ":", 1) != 0) { + filename = malloc(strlen(iio_dir) + strlen(type) + + numstrlen + 6); + if (!filename) { + ret = -ENOMEM; + goto error_close_dir; + } + + ret = sprintf(filename, "%s%s%d/name", iio_dir, + type, number); + if (ret < 0) { + free(filename); + goto error_close_dir; + } + + namefp = fopen(filename, "r"); + if (!namefp) { + free(filename); + continue; + } + + free(filename); + errno = 0; + if (fscanf(namefp, "%s", thisname) != 1) { + ret = errno ? -errno : -ENODATA; + goto error_close_dir; + } + + if (fclose(namefp)) { + ret = -errno; + goto error_close_dir; + } + + if (strcmp(name, thisname) == 0) { + if (closedir(dp) == -1) + return -errno; + + return number; + } + } + } + } + if (closedir(dp) == -1) + return -errno; + + return -ENODEV; + +error_close_dir: + if (closedir(dp) == -1) + perror("find_type_by_name(): Failed to close directory"); + + return ret; +} + +static int _write_sysfs_int(const char *filename, const char *basedir, int val, + int verify) +{ + int ret = 0; + FILE *sysfsfp; + int test; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + + if (!temp) + return -ENOMEM; + + ret = sprintf(temp, "%s/%s", basedir, filename); + if (ret < 0) + goto error_free; + + sysfsfp = fopen(temp, "w"); + if (!sysfsfp) { + ret = -errno; + fprintf(stderr, "failed to open %s\n", temp); + goto error_free; + } + + ret = fprintf(sysfsfp, "%d", val); + if (ret < 0) { + if (fclose(sysfsfp)) + perror("_write_sysfs_int(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) { + ret = -errno; + goto error_free; + } + + if (verify) { + sysfsfp = fopen(temp, "r"); + if (!sysfsfp) { + ret = -errno; + fprintf(stderr, "failed to open %s\n", temp); + goto error_free; + } + + if (fscanf(sysfsfp, "%d", &test) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("_write_sysfs_int(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) { + ret = -errno; + goto error_free; + } + + if (test != val) { + fprintf(stderr, + "Possible failure in int write %d to %s/%s\n", + val, basedir, filename); + ret = -1; + } + } + +error_free: + free(temp); + return ret; +} + +/** + * write_sysfs_int() - write an integer value to a sysfs file + * @filename: name of the file to write to + * @basedir: the sysfs directory in which the file is to be found + * @val: integer value to write to file + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int write_sysfs_int(const char *filename, const char *basedir, int val) +{ + return _write_sysfs_int(filename, basedir, val, 0); +} + +/** + * write_sysfs_int_and_verify() - write an integer value to a sysfs file + * and verify + * @filename: name of the file to write to + * @basedir: the sysfs directory in which the file is to be found + * @val: integer value to write to file + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int write_sysfs_int_and_verify(const char *filename, const char *basedir, + int val) +{ + return _write_sysfs_int(filename, basedir, val, 1); +} + +static int _write_sysfs_string(const char *filename, const char *basedir, + const char *val, int verify) +{ + int ret = 0; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + + if (!temp) { + fprintf(stderr, "Memory allocation failed\n"); + return -ENOMEM; + } + + ret = sprintf(temp, "%s/%s", basedir, filename); + if (ret < 0) + goto error_free; + + sysfsfp = fopen(temp, "w"); + if (!sysfsfp) { + ret = -errno; + fprintf(stderr, "Could not open %s\n", temp); + goto error_free; + } + + ret = fprintf(sysfsfp, "%s", val); + if (ret < 0) { + if (fclose(sysfsfp)) + perror("_write_sysfs_string(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) { + ret = -errno; + goto error_free; + } + + if (verify) { + sysfsfp = fopen(temp, "r"); + if (!sysfsfp) { + ret = -errno; + fprintf(stderr, "Could not open file to verify\n"); + goto error_free; + } + + if (fscanf(sysfsfp, "%s", temp) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("_write_sysfs_string(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) { + ret = -errno; + goto error_free; + } + + if (strcmp(temp, val) != 0) { + fprintf(stderr, + "Possible failure in string write of %s " + "Should be %s written to %s/%s\n", temp, val, + basedir, filename); + ret = -1; + } + } + +error_free: + free(temp); + + return ret; +} + +/** + * write_sysfs_string_and_verify() - string write, readback and verify + * @filename: name of file to write to + * @basedir: the sysfs directory in which the file is to be found + * @val: the string to write + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int write_sysfs_string_and_verify(const char *filename, const char *basedir, + const char *val) +{ + return _write_sysfs_string(filename, basedir, val, 1); +} + +/** + * write_sysfs_string() - write string to a sysfs file + * @filename: name of file to write to + * @basedir: the sysfs directory in which the file is to be found + * @val: the string to write + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int write_sysfs_string(const char *filename, const char *basedir, + const char *val) +{ + return _write_sysfs_string(filename, basedir, val, 0); +} + +/** + * read_sysfs_posint() - read an integer value from file + * @filename: name of file to read from + * @basedir: the sysfs directory in which the file is to be found + * + * Returns the read integer value >= 0 on success, otherwise a negative error + * code. + **/ +int read_sysfs_posint(const char *filename, const char *basedir) +{ + int ret; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + + if (!temp) { + fprintf(stderr, "Memory allocation failed"); + return -ENOMEM; + } + + ret = sprintf(temp, "%s/%s", basedir, filename); + if (ret < 0) + goto error_free; + + sysfsfp = fopen(temp, "r"); + if (!sysfsfp) { + ret = -errno; + goto error_free; + } + + errno = 0; + if (fscanf(sysfsfp, "%d\n", &ret) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("read_sysfs_posint(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) + ret = -errno; + +error_free: + free(temp); + + return ret; +} + +/** + * read_sysfs_float() - read a float value from file + * @filename: name of file to read from + * @basedir: the sysfs directory in which the file is to be found + * @val: output the read float value + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int read_sysfs_float(const char *filename, const char *basedir, float *val) +{ + int ret = 0; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + + if (!temp) { + fprintf(stderr, "Memory allocation failed"); + return -ENOMEM; + } + + ret = sprintf(temp, "%s/%s", basedir, filename); + if (ret < 0) + goto error_free; + + sysfsfp = fopen(temp, "r"); + if (!sysfsfp) { + ret = -errno; + goto error_free; + } + + errno = 0; + if (fscanf(sysfsfp, "%f\n", val) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("read_sysfs_float(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) + ret = -errno; + +error_free: + free(temp); + + return ret; +} + +/** + * read_sysfs_string() - read a string from file + * @filename: name of file to read from + * @basedir: the sysfs directory in which the file is to be found + * @str: output the read string + * + * Returns a value >= 0 on success, otherwise a negative error code. + **/ +int read_sysfs_string(const char *filename, const char *basedir, char *str) +{ + int ret = 0; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + + if (!temp) { + fprintf(stderr, "Memory allocation failed"); + return -ENOMEM; + } + + ret = sprintf(temp, "%s/%s", basedir, filename); + if (ret < 0) + goto error_free; + + sysfsfp = fopen(temp, "r"); + if (!sysfsfp) { + ret = -errno; + goto error_free; + } + + errno = 0; + if (fscanf(sysfsfp, "%s\n", str) != 1) { + ret = errno ? -errno : -ENODATA; + if (fclose(sysfsfp)) + perror("read_sysfs_string(): Failed to close dir"); + + goto error_free; + } + + if (fclose(sysfsfp)) + ret = -errno; + +error_free: + free(temp); + + return ret; +} diff --git a/tools/leds/iio/iio_utils.h b/tools/leds/iio/iio_utils.h new file mode 100644 index 000000000000..663c94a6c705 --- /dev/null +++ b/tools/leds/iio/iio_utils.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _IIO_UTILS_H_ +#define _IIO_UTILS_H_ + +/* IIO - useful set of util functionality + * + * Copyright (c) 2008 Jonathan Cameron + */ + +#include + +/* Made up value to limit allocation sizes */ +#define IIO_MAX_NAME_LENGTH 64 + +#define FORMAT_SCAN_ELEMENTS_DIR "%s/buffer%d" +#define FORMAT_EVENTS_DIR "%s/events" +#define FORMAT_TYPE_FILE "%s_type" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +extern const char *iio_dir; + +/** + * struct iio_channel_info - information about a given channel + * @name: channel name + * @generic_name: general name for channel type + * @scale: scale factor to be applied for conversion to si units + * @offset: offset to be applied for conversion to si units + * @index: the channel index in the buffer output + * @bytes: number of bytes occupied in buffer output + * @bits_used: number of valid bits of data + * @shift: amount of bits to shift right data before applying bit mask + * @mask: a bit mask for the raw output + * @be: flag if data is big endian + * @is_signed: is the raw value stored signed + * @location: data offset for this channel inside the buffer (in bytes) + **/ +struct iio_channel_info { + char *name; + char *generic_name; + float scale; + float offset; + unsigned index; + unsigned bytes; + unsigned bits_used; + unsigned shift; + uint64_t mask; + unsigned be; + unsigned is_signed; + unsigned location; +}; + +static inline int iioutils_check_suffix(const char *str, const char *suffix) +{ + return strlen(str) >= strlen(suffix) && + strncmp(str+strlen(str)-strlen(suffix), + suffix, strlen(suffix)) == 0; +} + +int iioutils_break_up_name(const char *full_name, char **generic_name); +int iioutils_get_param_float(float *output, const char *param_name, + const char *device_dir, const char *name, + const char *generic_name); +void bsort_channel_array_by_index(struct iio_channel_info *ci_array, int cnt); +int build_channel_array(const char *device_dir, int buffer_idx, + struct iio_channel_info **ci_array, int *counter); +int find_type_by_name(const char *name, const char *type); +int write_sysfs_int(const char *filename, const char *basedir, int val); +int write_sysfs_int_and_verify(const char *filename, const char *basedir, + int val); +int write_sysfs_string_and_verify(const char *filename, const char *basedir, + const char *val); +int write_sysfs_string(const char *filename, const char *basedir, + const char *val); +int read_sysfs_posint(const char *filename, const char *basedir); +int read_sysfs_float(const char *filename, const char *basedir, float *val); +int read_sysfs_string(const char *filename, const char *basedir, char *str); + +#endif /* _IIO_UTILS_H_ */ diff --git a/tools/leds/iio/lsiio.c b/tools/leds/iio/lsiio.c new file mode 100644 index 000000000000..2cf56fb2449b --- /dev/null +++ b/tools/leds/iio/lsiio.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Industrial I/O utilities - lsiio.c + * + * Copyright (c) 2010 Manuel Stahl + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio_utils.h" + +static enum verbosity { + VERBLEVEL_DEFAULT, /* 0 gives lspci behaviour */ + VERBLEVEL_SENSORS, /* 1 lists sensors */ +} verblevel = VERBLEVEL_DEFAULT; + +const char *type_device = "iio:device"; +const char *type_trigger = "trigger"; + +static inline int check_prefix(const char *str, const char *prefix) +{ + return strlen(str) > strlen(prefix) && + strncmp(str, prefix, strlen(prefix)) == 0; +} + +static inline int check_postfix(const char *str, const char *postfix) +{ + return strlen(str) > strlen(postfix) && + strcmp(str + strlen(str) - strlen(postfix), postfix) == 0; +} + +static int dump_channels(const char *dev_dir_name) +{ + DIR *dp; + const struct dirent *ent; + + dp = opendir(dev_dir_name); + if (!dp) + return -errno; + + while (ent = readdir(dp), ent) + if (check_prefix(ent->d_name, "in_") && + (check_postfix(ent->d_name, "_raw") || + check_postfix(ent->d_name, "_input"))) + printf(" %-10s\n", ent->d_name); + + return (closedir(dp) == -1) ? -errno : 0; +} + +static int dump_one_device(const char *dev_dir_name) +{ + char name[IIO_MAX_NAME_LENGTH]; + int dev_idx; + int ret; + + ret = sscanf(dev_dir_name + strlen(iio_dir) + strlen(type_device), "%i", + &dev_idx); + if (ret != 1) + return -EINVAL; + + ret = read_sysfs_string("name", dev_dir_name, name); + if (ret < 0) + return ret; + + printf("Device %03d: %s\n", dev_idx, name); + + if (verblevel >= VERBLEVEL_SENSORS) + return dump_channels(dev_dir_name); + + return 0; +} + +static int dump_one_trigger(const char *dev_dir_name) +{ + char name[IIO_MAX_NAME_LENGTH]; + int dev_idx; + int ret; + + ret = sscanf(dev_dir_name + strlen(iio_dir) + strlen(type_trigger), + "%i", &dev_idx); + if (ret != 1) + return -EINVAL; + + ret = read_sysfs_string("name", dev_dir_name, name); + if (ret < 0) + return ret; + + printf("Trigger %03d: %s\n", dev_idx, name); + + return 0; +} + +static int dump_devices(void) +{ + const struct dirent *ent; + int ret; + DIR *dp; + + dp = opendir(iio_dir); + if (!dp) { + fprintf(stderr, "No industrial I/O devices available\n"); + return -ENODEV; + } + + while (ent = readdir(dp), ent) { + if (check_prefix(ent->d_name, type_device)) { + char *dev_dir_name; + + if (asprintf(&dev_dir_name, "%s%s", iio_dir, + ent->d_name) < 0) { + ret = -ENOMEM; + goto error_close_dir; + } + + ret = dump_one_device(dev_dir_name); + if (ret) { + free(dev_dir_name); + goto error_close_dir; + } + + free(dev_dir_name); + if (verblevel >= VERBLEVEL_SENSORS) + printf("\n"); + } + } + rewinddir(dp); + while (ent = readdir(dp), ent) { + if (check_prefix(ent->d_name, type_trigger)) { + char *dev_dir_name; + + if (asprintf(&dev_dir_name, "%s%s", iio_dir, + ent->d_name) < 0) { + ret = -ENOMEM; + goto error_close_dir; + } + + ret = dump_one_trigger(dev_dir_name); + if (ret) { + free(dev_dir_name); + goto error_close_dir; + } + + free(dev_dir_name); + } + } + + return (closedir(dp) == -1) ? -errno : 0; + +error_close_dir: + if (closedir(dp) == -1) + perror("dump_devices(): Failed to close directory"); + + return ret; +} + +int main(int argc, char **argv) +{ + int c, err = 0; + + while ((c = getopt(argc, argv, "v")) != EOF) { + switch (c) { + case 'v': + verblevel++; + break; + + case '?': + default: + err++; + break; + } + } + if (err || argc > optind) { + fprintf(stderr, "Usage: lsiio [options]...\n" + "List industrial I/O devices\n" + " -v Increase verbosity (may be given multiple times)\n"); + exit(1); + } + + return dump_devices(); +} diff --git a/tools/power/cpupower/Makefile b/tools/power/cpupower/Makefile index c43db1c41205..2b082ae99dd9 100644 --- a/tools/power/cpupower/Makefile +++ b/tools/power/cpupower/Makefile @@ -10,10 +10,10 @@ # OUTPUT=./ ifeq ("$(origin O)", "command line") - OUTPUT := $(O)/ + OUTPUT := $(O)/ endif -ifneq ($(OUTPUT),) +ifneq ($(OUTPUT),) # check that the output directory actually exists OUTDIR := $(shell cd $(OUTPUT) && pwd) $(if $(OUTDIR),, $(error output directory "$(OUTPUT)" does not exist)) @@ -24,14 +24,14 @@ endif # Set the following to `true' to make a unstripped, unoptimized # binary. Leave this set to `false' for production use. -DEBUG ?= true +DEBUG ?= true # make the build silent. Set this to something else to make it noisy again. -V ?= false +V ?= false # Internationalization support (output in different languages). # Requires gettext. -NLS ?= true +NLS ?= true # Set the following to 'true' to build/install the # cpufreq-bench benchmarking tool @@ -43,7 +43,7 @@ CPUFREQ_BENCH ?= true export STATIC ?= false # Prefix to the directories we're installing to -DESTDIR ?= +DESTDIR ?= # --- CONFIGURATION END --- @@ -52,32 +52,32 @@ DESTDIR ?= # Package-related definitions. Distributions can modify the version # and _should_ modify the PACKAGE_BUGREPORT definition -VERSION:= $(shell ./utils/version-gen.sh) -LIB_FIX= 1 -LIB_MIN= 0 -LIB_MAJ= 1 -LIB_VER= $(LIB_MAJ).$(LIB_MIN).$(LIB_FIX) +VERSION:= $(shell ./utils/version-gen.sh) +LIB_FIX= 1 +LIB_MIN= 0 +LIB_MAJ= 1 +LIB_VER= $(LIB_MAJ).$(LIB_MIN).$(LIB_FIX) -PACKAGE = cpupower -PACKAGE_BUGREPORT = linux-pm@vger.kernel.org -LANGUAGES = de fr it cs pt ka zh_CN +PACKAGE = cpupower +PACKAGE_BUGREPORT = linux-pm@vger.kernel.org +LANGUAGES = de fr it cs pt ka zh_CN # Directory definitions. These are default and most probably # do not need to be changed. Please note that DESTDIR is # added in front of any of them -bindir ?= /usr/bin -sbindir ?= /usr/sbin -mandir ?= /usr/man -libdir ?= /usr/lib -libexecdir ?= /usr/libexec -unitdir ?= /usr/lib/systemd/system -includedir ?= /usr/include -localedir ?= /usr/share/locale -docdir ?= /usr/share/doc/packages/cpupower -confdir ?= /etc/ +bindir ?= /usr/bin +sbindir ?= /usr/sbin +mandir ?= /usr/man +libdir ?= /usr/lib +libexecdir ?= /usr/libexec +unitdir ?= /usr/lib/systemd/system +includedir ?= /usr/include +localedir ?= /usr/share/locale +docdir ?= /usr/share/doc/packages/cpupower +confdir ?= /etc/ bash_completion_dir ?= /usr/share/bash-completion/completions # Toolchain: what tools do we use, and what options do they need: @@ -113,7 +113,7 @@ MKDIR = mkdir # Now we set up the build system # -GMO_FILES = ${shell for HLANG in ${LANGUAGES}; do echo $(OUTPUT)po/$$HLANG.gmo; done;} +GMO_FILES = ${shell for HLANG in ${LANGUAGES}; do echo $(OUTPUT)po/$HLANG.gmo; done;} export CROSS CC AR STRIP RANLIB CFLAGS LDFLAGS LIB_OBJS @@ -136,6 +136,7 @@ UTIL_OBJS = utils/helpers/amd.o utils/helpers/msr.o \ utils/helpers/pci.o utils/helpers/bitmask.o \ utils/idle_monitor/nhm_idle.o utils/idle_monitor/snb_idle.o \ utils/idle_monitor/hsw_ext_idle.o \ + utils/idle_monitor/intelmonitor_idle.o \ utils/idle_monitor/amd_fam14h_idle.o utils/idle_monitor/cpuidle_sysfs.o \ utils/idle_monitor/mperf_monitor.o utils/idle_monitor/cpupower-monitor.o \ utils/idle_monitor/rapl_monitor.o \ @@ -157,7 +158,7 @@ LIB_SRC = lib/cpufreq.c lib/cpupower.c lib/cpuidle.c lib/acpi_cppc.c \ lib/powercap.c LIB_OBJS = lib/cpufreq.o lib/cpupower.o lib/cpuidle.o lib/acpi_cppc.o \ lib/powercap.o -LIB_OBJS := $(addprefix $(OUTPUT),$(LIB_OBJS)) +LIB_OBJS := $(addprefix $(OUTPUT),$(LIB_OBJS)) override CFLAGS += -pipe @@ -185,7 +186,7 @@ ifeq ($(strip $(V)),false) ECHO=@echo else QUIET= - ECHO=@\# + ECHO=@# endif export QUIET ECHO @@ -258,15 +259,15 @@ else update-po: $(OUTPUT)po/$(PACKAGE).pot $(ECHO) " MSGMRG " $@ $(QUIET) @for HLANG in $(LANGUAGES); do \ - echo -n "Updating $$HLANG "; \ - if msgmerge po/$$HLANG.po $< -o \ - $(OUTPUT)po/$$HLANG.new.po; then \ - mv -f $(OUTPUT)po/$$HLANG.new.po $(OUTPUT)po/$$HLANG.po; \ - else \ - echo "msgmerge for $$HLANG failed!"; \ - rm -f $(OUTPUT)po/$$HLANG.new.po; \ - fi; \ - done; + echo -n "Updating $HLANG "; \ + if msgmerge po/$HLANG.po $< -o \ + $(OUTPUT)po/$HLANG.new.po; then \ + mv -f $(OUTPUT)po/$HLANG.new.po $(OUTPUT)po/$HLANG.po; \ + else \ + echo "msgmerge for $HLANG failed!"; \ + rm -f $(OUTPUT)po/$HLANG.new.po; \ + fi; \ + done; endif compile-bench: $(OUTPUT)libcpupower.so.$(LIB_VER) @@ -328,9 +329,9 @@ install-man: install-gmo: create-gmo $(INSTALL) -d $(DESTDIR)${localedir} for HLANG in $(LANGUAGES); do \ - echo '$(INSTALL_DATA) -D $(OUTPUT)po/$$HLANG.gmo $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo'; \ - $(INSTALL_DATA) -D $(OUTPUT)po/$$HLANG.gmo $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo; \ - done; + echo '$(INSTALL_DATA) -D $(OUTPUT)po/$HLANG.gmo $(DESTDIR)${localedir}/$HLANG/LC_MESSAGES/cpupower.mo'; \ + $(INSTALL_DATA) -D $(OUTPUT)po/$HLANG.gmo $(DESTDIR)${localedir}/$HLANG/LC_MESSAGES/cpupower.mo; \ + done; install-bench: compile-bench @#DESTDIR must be set from outside to survive @@ -358,42 +359,42 @@ uninstall: - rm -f $(DESTDIR)${mandir}/man1/cpupower-monitor.1 - rm -f $(DESTDIR)${mandir}/man1/cpupower-powercap-info.1 - for HLANG in $(LANGUAGES); do \ - rm -f $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo; \ - done; + rm -f $(DESTDIR)${localedir}/$HLANG/LC_MESSAGES/cpupower.mo; \ + done; help: @echo 'Building targets:' - @echo ' all - Default target. Could be omitted. Put build artifacts' + @echo ' all - Default target. Could be omitted. Put build artifacts' @echo ' to "O" cmdline option dir (default: current dir)' - @echo ' install - Install previously built project files from the output' + @echo ' install - Install previously built project files from the output' @echo ' dir defined by "O" cmdline option (default: current dir)' @echo ' to the install dir defined by "DESTDIR" cmdline or' @echo ' Makefile config block option (default: "")' - @echo ' install-lib - Install previously built library binary from the output' + @echo ' install-lib - Install previously built library binary from the output' @echo ' dir defined by "O" cmdline option (default: current dir)' @echo ' and library headers from "lib/" for userspace to the install' @echo ' dir defined by "DESTDIR" cmdline (default: "")' - @echo ' install-tools - Install previously built "cpupower" util from the output' + @echo ' install-tools - Install previously built "cpupower" util from the output' @echo ' dir defined by "O" cmdline option (default: current dir) and' @echo ' "cpupower-completion.sh" script from the src dir to the' @echo ' install dir defined by "DESTDIR" cmdline or Makefile' @echo ' config block option (default: "")' - @echo ' install-man - Install man pages from the "man" src subdir to the' + @echo ' install-man - Install man pages from the "man" src subdir to the' @echo ' install dir defined by "DESTDIR" cmdline or Makefile' @echo ' config block option (default: "")' - @echo ' install-gmo - Install previously built language files from the output' + @echo ' install-gmo - Install previously built language files from the output' @echo ' dir defined by "O" cmdline option (default: current dir)' @echo ' to the install dir defined by "DESTDIR" cmdline or Makefile' @echo ' config block option (default: "")' - @echo ' install-bench - Install previously built "cpufreq-bench" util files from the' + @echo ' install-bench - Install previously built "cpufreq-bench" util files from the' @echo ' output dir defined by "O" cmdline option (default: current dir)' @echo ' to the install dir defined by "DESTDIR" cmdline or Makefile' @echo ' config block option (default: "")' @echo '' @echo 'Cleaning targets:' - @echo ' clean - Clean build artifacts from the dir defined by "O" cmdline' + @echo ' clean - Clean build artifacts from the dir defined by "O" cmdline' @echo ' option (default: current dir)' - @echo ' uninstall - Remove previously installed files from the dir defined by "DESTDIR"' + @echo ' uninstall - Remove previously installed files from the dir defined by "DESTDIR"' @echo ' cmdline or Makefile config block option (default: "")' .PHONY: all utils libcpupower update-po create-gmo install-lib install-tools install-man install-gmo install uninstall clean help diff --git a/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c b/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c index 8b42c2f0a5b0..21756c2b1f90 100644 --- a/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c +++ b/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "helpers/helpers.h" #include "idle_monitor/cpupower-monitor.h" @@ -135,7 +136,7 @@ void map_power_idle_state_name(char *tmp) strcpy(tmp, "stop2L"); } #else -void map_power_idle_state_name(char *tmp) { } +void map_power_idle_state_name(char *tmp) { } #endif static struct cpuidle_monitor *cpuidle_register(void) @@ -179,9 +180,9 @@ static struct cpuidle_monitor *cpuidle_register(void) current_count = malloc(sizeof(long long *) * cpu_count); for (num = 0; num < cpu_count; num++) { previous_count[num] = malloc(sizeof(long long) * - cpuidle_sysfs_monitor.hw_states_num); + cpuidle_sysfs_monitor.hw_states_num); current_count[num] = malloc(sizeof(long long) * - cpuidle_sysfs_monitor.hw_states_num); + cpuidle_sysfs_monitor.hw_states_num); } cpuidle_sysfs_monitor.name_len = strlen(cpuidle_sysfs_monitor.name); @@ -209,4 +210,4 @@ struct cpuidle_monitor cpuidle_sysfs_monitor = { .unregister = cpuidle_unregister, .flags.needs_root = 0, .overflow_s = UINT_MAX, -}; +}; \ No newline at end of file diff --git a/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h b/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h index c559d3115330..17f651de1553 100644 --- a/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h +++ b/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h @@ -1,13 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * (C) 2010,2011 Thomas Renninger , Novell Inc. + * (C) 2010,2011 Thomas Renninger , Novell Inc. */ #ifndef __CPUIDLE_INFO_HW__ #define __CPUIDLE_INFO_HW__ #include -#include +#include +#include #include "idle_monitor/idle_monitors.h" @@ -68,11 +69,11 @@ struct cpuidle_monitor { extern long long timespec_diff_us(struct timespec start, struct timespec end); -#define print_overflow_err(mes, ov) \ -{ \ - fprintf(stderr, gettext("Measure took %u seconds, but registers could " \ - "overflow at %u seconds, results " \ - "could be inaccurate\n"), mes, ov); \ +#define print_overflow_err(mes, ov) \ +{\ + fprintf(stderr, gettext("Measure took %u seconds, but registers could "\ + "overflow at %u seconds, results "\ + "could be inaccurate\n"), mes, ov);\ } diff --git a/tools/power/cpupower/utils/idle_monitor/idle_monitors.def b/tools/power/cpupower/utils/idle_monitor/idle_monitors.def index 7c926e90c87e..0085087aa3f9 100644 --- a/tools/power/cpupower/utils/idle_monitor/idle_monitors.def +++ b/tools/power/cpupower/utils/idle_monitor/idle_monitors.def @@ -5,5 +5,6 @@ DEF(intel_snb) DEF(intel_hsw_ext) DEF(mperf) DEF(rapl) +DEF(intel_freq) #endif DEF(cpuidle_sysfs) diff --git a/tools/power/cpupower/utils/idle_monitor/intelmonitor_idle.c b/tools/power/cpupower/utils/idle_monitor/intelmonitor_idle.c new file mode 100644 index 000000000000..8a16200c3d65 --- /dev/null +++ b/tools/power/cpupower/utils/idle_monitor/intelmonitor_idle.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only +#if defined(__i386__) || defined(__x86_64__) + +#include +#include +#include +#include +#include +#include +#include "idle_monitor/cpupower-monitor.h" +#include "helpers/helpers.h" +#include "helpers/helpers.h" +#include "cpufreq.h" +#include + +#define MSR_APERF 0xE8 +#define MSR_MPERF 0xE7 + +static unsigned long long *mperf_previous_count; +static unsigned long long *aperf_previous_count; +static unsigned long long *mperf_current_count; +static unsigned long long *aperf_current_count; +static int *is_valid; +static unsigned long max_frequency; + +static int intel_freq_get_count(unsigned int id, unsigned long long *count, + unsigned int cpu) +{ + unsigned long long aperf_diff, mperf_diff; + + if (!is_valid[cpu]) + return -1; + + mperf_diff = mperf_current_count[cpu] - mperf_previous_count[cpu]; + aperf_diff = aperf_current_count[cpu] - aperf_previous_count[cpu]; + + if (mperf_diff == 0) { + *count = 0; + return 0; + } + + *count = max_frequency * ((double)aperf_diff / mperf_diff); + return 0; +} + +cstate_t intel_freq_cstates[] = { + { + .name = "Freq", + .desc = "Average Frequency (including boost) in MHz", + .id = 0, + .range = RANGE_THREAD, + .get_count = intel_freq_get_count, + }, +}; + +static int get_aperf_mperf(int cpu, unsigned long long *aval, + unsigned long long *mval) +{ + int ret; + + ret = read_msr(cpu, MSR_APERF, aval); + ret |= read_msr(cpu, MSR_MPERF, mval); + + return ret; +} + +static int intel_freq_start(void) +{ + int cpu; + unsigned long long aperf, mperf; + + for (cpu = 0; cpu < cpu_count; cpu++) { + if (get_aperf_mperf(cpu, &aperf, &mperf)) { + is_valid[cpu] = 0; + continue; + } + is_valid[cpu] = 1; + aperf_previous_count[cpu] = aperf; + mperf_previous_count[cpu] = mperf; + } + return 0; +} + +static int intel_freq_stop(void) +{ + int cpu; + unsigned long long aperf, mperf; + + for (cpu = 0; cpu < cpu_count; cpu++) { + if (!is_valid[cpu]) + continue; + if (get_aperf_mperf(cpu, &aperf, &mperf)) { + is_valid[cpu] = 0; + continue; + } + aperf_current_count[cpu] = aperf; + mperf_current_count[cpu] = mperf; + } + return 0; +} + +static void intel_freq_unregister(void); +static struct cpuidle_monitor *intel_freq_register(void); + + +struct cpuidle_monitor intel_freq_monitor = { + .name = "Intel_Freq", + .hw_states_num = 1, + .hw_states = intel_freq_cstates, + .start = intel_freq_start, + .stop = intel_freq_stop, + .do_register = intel_freq_register, + .unregister = intel_freq_unregister, + .flags.needs_root = 1, + .overflow_s = 922000000 +}; + +struct cpuidle_monitor *intel_freq_register(void) +{ + unsigned long min; + + if (cpupower_cpu_info.vendor != X86_VENDOR_INTEL) + return NULL; + + if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF)) + return NULL; + + if (cpufreq_get_hardware_limits(0, &min, &max_frequency)) + return NULL; + max_frequency /= 1000; + + is_valid = calloc(cpu_count, sizeof(int)); + mperf_previous_count = calloc(cpu_count, sizeof(unsigned long long)); + aperf_previous_count = calloc(cpu_count, sizeof(unsigned long long)); + mperf_current_count = calloc(cpu_count, sizeof(unsigned long long)); + aperf_current_count = calloc(cpu_count, sizeof(unsigned long long)); + + intel_freq_monitor.name_len = strlen(intel_freq_monitor.name); + return &intel_freq_monitor; +} + +void intel_freq_unregister(void) +{ + free(mperf_previous_count); + free(aperf_previous_count); + free(mperf_current_count); + free(aperf_current_count); + free(is_valid); +} + + +#endif diff --git a/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c b/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c index 5ae02c3d5b64..d9bef4646361 100644 --- a/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c +++ b/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -32,7 +33,7 @@ enum mperf_id { C0 = 0, Cx, AVG_FREQ, MPERF_CSTATE_COUNT }; static int mperf_get_count_percent(unsigned int self_id, double *percent, unsigned int cpu); static int mperf_get_count_freq(unsigned int id, unsigned long long *count, - unsigned int cpu); + unsigned int cpu); static struct timespec *time_start, *time_end; static cstate_t mperf_cstates[MPERF_CSTATE_COUNT] = { @@ -86,7 +87,7 @@ static int mperf_get_tsc(unsigned long long *tsc) ret = read_msr(base_cpu, MSR_TSC, tsc); if (ret) - dprint("Reading TSC MSR failed, returning %llu\n", *tsc); + dprint("Reading TSC MSR failed, returning %%llu\n", *tsc); return ret; } @@ -171,12 +172,12 @@ static int mperf_get_count_percent(unsigned int id, double *percent, if (max_freq_mode == MAX_FREQ_TSC_REF) { tsc_diff = tsc_at_measure_end[cpu] - tsc_at_measure_start[cpu]; *percent = 100.0 * mperf_diff / tsc_diff; - dprint("%s: TSC Ref - mperf_diff: %llu, tsc_diff: %llu\n", + dprint("%s: TSC Ref - mperf_diff: %%llu, tsc_diff: %%llu\n", mperf_cstates[id].name, mperf_diff, tsc_diff); } else if (max_freq_mode == MAX_FREQ_SYSFS) { timediff = max_frequency * timespec_diff_us(time_start[cpu], time_end[cpu]); *percent = 100.0 * mperf_diff / timediff; - dprint("%s: MAXFREQ - mperf_diff: %llu, time_diff: %llu\n", + dprint("%s: MAXFREQ - mperf_diff: %%llu, time_diff: %%llu\n", mperf_cstates[id].name, mperf_diff, timediff); } else return -1; @@ -184,9 +185,9 @@ static int mperf_get_count_percent(unsigned int id, double *percent, if (id == Cx) *percent = 100.0 - *percent; - dprint("%s: previous: %llu - current: %llu - (%u)\n", - mperf_cstates[id].name, mperf_diff, aperf_diff, cpu); - dprint("%s: %f\n", mperf_cstates[id].name, *percent); + dprint("%s: previous: %%llu - current: %%llu - (%%u)\n", + mperf_cstates[id].name, mperf_diff, aperf_diff, cpu); + dprint("%s: %%f\n", mperf_cstates[id].name, *percent); return 0; } @@ -212,13 +213,13 @@ static int mperf_get_count_freq(unsigned int id, unsigned long long *count, } *count = max_frequency * ((double)aperf_diff / mperf_diff); - dprint("%s: Average freq based on %s maximum frequency:\n", + dprint("%s: Average freq based on %%%s maximum frequency:\n", mperf_cstates[id].name, (max_freq_mode == MAX_FREQ_TSC_REF) ? "TSC calculated" : "sysfs read"); - dprint("max_frequency: %lu\n", max_frequency); - dprint("aperf_diff: %llu\n", aperf_diff); - dprint("mperf_diff: %llu\n", mperf_diff); - dprint("avg freq: %llu\n", *count); + dprint("max_frequency: %%lu\n", max_frequency); + dprint("aperf_diff: %%llu\n", aperf_diff); + dprint("mperf_diff: %%llu\n", mperf_diff); + dprint("avg freq: %%llu\n", *count); return 0; } @@ -290,7 +291,7 @@ static int init_maxfreq_mode(void) * not explicitly provide access to it and assume TSC works */ if (ret != 0) { - dprint("TSC read 0x%x failed - assume TSC working\n", + dprint("TSC read 0x%%x failed - assume TSC working\n", MSR_AMD_HWCR); return 0; } else if (1 & (hwcr >> 24)) { @@ -377,6 +378,6 @@ struct cpuidle_monitor mperf_monitor = { .unregister = mperf_unregister, .flags.needs_root = 1, .overflow_s = 922000000 /* 922337203 seconds TSC overflow - at 20GHz */ + at 20GHz */ }; -#endif /* #if defined(__i386__) || defined(__x86_64__) */ +#endif /* #if defined(__i386__) || defined(__x86_64__) */ \ No newline at end of file -- 2.50.1