File pisound-micro.c#
File List > libpisoundmicro > pisound-micro.c
Go to the documentation of this file
// SPDX-License-Identifier: LGPL-2.1-only
//
// libpisoundmicro - a utility library for Pisound Micro I/O expander capabilities.
// Copyright (c) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
//
// This file is part of libpisoundmicro.
//
// libpisoundmicro is free software: you can redistribute it and/or modify it under the terms of the
// GNU Lesser General Public License as published by the Free Software Foundation, version 2.1 of the License.
//
// libpisoundmicro is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with libpisoundmicro. If not, see <https://www.gnu.org/licenses/>.
#include "include/pisound-micro.h"
#include "include/pisound-micro/api_internal.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/input.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <threads.h>
#include <stdatomic.h>
#include "utils.h"
static const uint32_t UPISND_TIMEOUT_MS = 2000ul;
static const char *const UPISND_TEXT_SEPARATORS = " \n\t";
static const char const UPISND_PIN_NAMES[UPISND_PIN_COUNT][4] =
{
"A27", "A28", "A29", "A30", "A31", "A32", "B03", "B04",
"B05", "B06", "B07", "B08", "B09", "B10", "B11", "B12",
"B13", "B14", "B15", "B16", "B17", "B18", "B23", "B24",
"B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32",
"B33", "B34", "B37", "B38", "B39"
};
enum
{
UPISND_MAX_BASE_PATH_LENGTH = 64,
UPISND_ELEMENT_MAX_PATH_LENGTH = UPISND_MAX_ELEMENT_NAME_LENGTH + UPISND_MAX_BASE_PATH_LENGTH,
UPISND_MAX_REQUEST_LENGTH = UPISND_MAX_ELEMENT_NAME_LENGTH + 64,
};
static const char UPISND_SYSFS_DEFAULT_BASE_PATH[] = "/sys/pisound-micro";
static const char UPISND_PATH_SETUP_FMT[] = "%s/setup";
static const char UPISND_PATH_UNSETUP_FMT[] = "%s/unsetup";
#ifdef UPISND_INTERNAL
static const char UPISND_PATH_ADC_OFFSET_FMT[] = "%s/adc_offset";
static const char UPISND_PATH_ADC_GAIN_FMT[] = "%s/adc_gain";
#endif
static const char UPISND_ELEMENT_PATH_ATTR_FMT[] = "%s/elements/%s/%s";
struct upisnd_ctx_t
{
volatile int refcount;
mtx_t mutex;
const char *sysfs_base;
upisnd_xoshiro128_star_star_seed_t seed;
struct upisnd_elements_list_node_t *elements;
struct upisnd_ctx_t *next;
};
static struct upisnd_ctx_t *upisnd_active_ctx;
static struct upisnd_ctx_t *upisnd_ctx_list;
static struct upisnd_ctx_t *upisnd_ctx_alloc(const char *sysfs_base)
{
upisnd_xoshiro128_star_star_seed_t seed;
int rand_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY, 0);
if (rand_fd < 0)
return NULL;
int err = read(rand_fd, seed, sizeof(seed));
if (err != sizeof(seed))
err = -errno;
close(rand_fd);
if (err < 0)
{
errno = -err;
return NULL;
}
struct upisnd_ctx_t *ctx = malloc(sizeof(struct upisnd_ctx_t));
if (!ctx)
{
errno = ENOMEM;
return NULL;
}
ctx->refcount = 1;
mtx_init(&ctx->mutex, mtx_plain);
ctx->sysfs_base = sysfs_base ? sysfs_base : UPISND_SYSFS_DEFAULT_BASE_PATH;
memcpy(ctx->seed, seed, sizeof(seed));
ctx->elements = NULL;
ctx->next = NULL;
return ctx;
}
static void upisnd_ctx_free(struct upisnd_ctx_t *ctx);
static void upisnd_contexts_prepend(struct upisnd_ctx_t *ctx)
{
ctx->next = upisnd_ctx_list;
upisnd_ctx_list = ctx;
}
static int upisnd_contexts_remove(struct upisnd_ctx_t *ctx)
{
int err = ENOENT;
struct upisnd_ctx_t **n = &upisnd_ctx_list;
while (*n)
{
if (*n == ctx)
{
err = 0;
*n = ctx->next;
break;
}
n = &(*n)->next;
}
if (err)
{
errno = err;
return -err;
}
upisnd_ctx_free(ctx);
errno = 0;
return 0;
}
struct upisnd_elements_list_node_t
{
volatile int refcount;
const char *name;
struct upisnd_ctx_t *ctx;
struct upisnd_elements_list_node_t *next;
};
static struct upisnd_elements_list_node_t *upisnd_element_alloc(const char *name, struct upisnd_ctx_t *ctx)
{
struct upisnd_elements_list_node_t *node = malloc(sizeof(struct upisnd_elements_list_node_t));
if (!node)
return NULL;
node->name = strdup(name);
if (node->name)
{
node->refcount = 1;
node->ctx = ctx;
node->next = NULL;
return node;
}
free(node);
return NULL;
}
static void upisnd_elements_free(upisnd_element_ref_t el)
{
free((void*)el->name);
free(el);
}
static void upisnd_elements_prepend(struct upisnd_elements_list_node_t *node)
{
node->next = node->ctx->elements;
node->ctx->elements = node;
}
static int upisnd_elements_remove(upisnd_element_ref_t el)
{
int err = ENOENT;
struct upisnd_elements_list_node_t **n = &el->ctx->elements;
while (*n)
{
if (*n == el)
{
err = 0;
*n = el->next;
break;
}
n = &(*n)->next;
}
if (err)
{
errno = err;
return -err;
}
upisnd_elements_free(el);
errno = 0;
return 0;
}
static uint32_t upisnd_get_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000ul + ts.tv_nsec / 1000000ull;
}
#ifndef UPISND_INTERNAL
static
#endif
struct upisnd_ctx_t *upisnd_set_active_ctx(struct upisnd_ctx_t *ctx)
{
struct upisnd_ctx_t *old = upisnd_active_ctx;
upisnd_active_ctx = ctx;
return old;
}
#ifndef UPISND_INTERNAL
static
#endif
struct upisnd_ctx_t *upisnd_init_internal(const char *sysfs_base)
{
if (!sysfs_base)
{
sysfs_base = UPISND_SYSFS_DEFAULT_BASE_PATH;
}
else
{
if (strlen(sysfs_base) > UPISND_MAX_BASE_PATH_LENGTH)
{
errno = ENAMETOOLONG;
return NULL;
}
if (*sysfs_base != '/')
{
errno = EINVAL;
return NULL;
}
}
struct upisnd_ctx_t *ctx = upisnd_active_ctx;
while (ctx)
{
if (strcmp(ctx->sysfs_base, sysfs_base) == 0)
{
__atomic_add_fetch(&ctx->refcount, 1, memory_order_seq_cst);
upisnd_set_active_ctx(ctx);
return ctx;
}
ctx = ctx->next;
}
ctx = upisnd_ctx_alloc(sysfs_base);
if (ctx)
{
upisnd_contexts_prepend(ctx);
upisnd_set_active_ctx(ctx);
}
return ctx;
}
typedef enum
{
UPISND_PATH_SETUP,
UPISND_PATH_UNSETUP,
#ifdef UPISND_INTERNAL
UPISND_PATH_ADC_OFFSET,
UPISND_PATH_ADC_GAIN,
#endif
} upisnd_path_type_t;
static int upisnd_path(char *path, upisnd_path_type_t type, const char *sysfs_base)
{
int n;
switch (type)
{
case UPISND_PATH_SETUP:
n = snprintf(path, UPISND_MAX_BASE_PATH_LENGTH, UPISND_PATH_SETUP_FMT, sysfs_base);
break;
case UPISND_PATH_UNSETUP:
n = snprintf(path, UPISND_MAX_BASE_PATH_LENGTH, UPISND_PATH_UNSETUP_FMT, sysfs_base);
break;
#ifdef UPISND_INTERNAL
case UPISND_PATH_ADC_OFFSET:
n = snprintf(path, UPISND_MAX_BASE_PATH_LENGTH, UPISND_PATH_ADC_OFFSET_FMT, sysfs_base);
break;
case UPISND_PATH_ADC_GAIN:
n = snprintf(path, UPISND_MAX_BASE_PATH_LENGTH, UPISND_PATH_ADC_GAIN_FMT, sysfs_base);
break;
#endif
default:
return -EINVAL;
}
return n > 0 && n < UPISND_MAX_BASE_PATH_LENGTH ? n : -ENAMETOOLONG;
}
static int upisnd_open_fd(upisnd_path_type_t type, int flags, const char *sysfs_base)
{
char path[UPISND_MAX_BASE_PATH_LENGTH];
int err = upisnd_path(path, type, sysfs_base);
if (err < 0)
return err;
errno = 0;
int fd = open(path, flags, 0);
if (fd < 0)
return -errno;
return fd;
}
typedef enum
{
UPISND_ELEMENT_ATTR_ROOT,
UPISND_ELEMENT_ATTR_TYPE,
UPISND_ELEMENT_ATTR_DIRECTION,
UPISND_ELEMENT_ATTR_PIN,
UPISND_ELEMENT_ATTR_PIN_NAME,
UPISND_ELEMENT_ATTR_PIN_PULL,
UPISND_ELEMENT_ATTR_PIN_B,
UPISND_ELEMENT_ATTR_PIN_B_NAME,
UPISND_ELEMENT_ATTR_PIN_B_PULL,
UPISND_ELEMENT_ATTR_GPIO_EXPORT,
UPISND_ELEMENT_ATTR_GPIO_UNEXPORT,
UPISND_ELEMENT_ATTR_INPUT_MIN,
UPISND_ELEMENT_ATTR_INPUT_MAX,
UPISND_ELEMENT_ATTR_VALUE_LOW,
UPISND_ELEMENT_ATTR_VALUE_HIGH,
UPISND_ELEMENT_ATTR_VALUE_MODE,
UPISND_ELEMENT_ATTR_VALUE,
UPISND_ELEMENT_ATTR_ACTIVITY_TYPE,
} upisnd_element_attr_e;
static int upisnd_element_path(char *path, const char *sysfs_base, const char *name, upisnd_element_attr_e attr)
{
const char *a;
switch (attr)
{
case UPISND_ELEMENT_ATTR_ROOT: a = ""; break;
case UPISND_ELEMENT_ATTR_TYPE: a = "type"; break;
case UPISND_ELEMENT_ATTR_DIRECTION: a = "direction"; break;
case UPISND_ELEMENT_ATTR_PIN: a = "pin"; break;
case UPISND_ELEMENT_ATTR_PIN_NAME: a = "pin_name"; break;
case UPISND_ELEMENT_ATTR_PIN_PULL: a = "pin_pull"; break;
case UPISND_ELEMENT_ATTR_PIN_B: a = "pin_b"; break;
case UPISND_ELEMENT_ATTR_PIN_B_NAME: a = "pin_b_name"; break;
case UPISND_ELEMENT_ATTR_PIN_B_PULL: a = "pin_b_pull"; break;
case UPISND_ELEMENT_ATTR_GPIO_EXPORT: a = "gpio_export"; break;
case UPISND_ELEMENT_ATTR_GPIO_UNEXPORT: a = "gpio_unexport"; break;
case UPISND_ELEMENT_ATTR_INPUT_MIN: a = "input_min"; break;
case UPISND_ELEMENT_ATTR_INPUT_MAX: a = "input_max"; break;
case UPISND_ELEMENT_ATTR_VALUE_LOW: a = "value_low"; break;
case UPISND_ELEMENT_ATTR_VALUE_HIGH: a = "value_high"; break;
case UPISND_ELEMENT_ATTR_VALUE_MODE: a = "value_mode"; break;
case UPISND_ELEMENT_ATTR_VALUE: a = "value"; break;
case UPISND_ELEMENT_ATTR_ACTIVITY_TYPE: a = "activity_type"; break;
default: return -EINVAL;
}
int n = snprintf(path, UPISND_ELEMENT_MAX_PATH_LENGTH, UPISND_ELEMENT_PATH_ATTR_FMT, sysfs_base, name, a);
if (n > 0 && n < UPISND_ELEMENT_MAX_PATH_LENGTH)
{
if (attr == UPISND_ELEMENT_ATTR_ROOT)
path[n--] = '\0'; // Strip trailing slash.
return n;
}
return n > 0 && n < UPISND_ELEMENT_MAX_PATH_LENGTH ? n : -ENAMETOOLONG;
}
int upisnd_validate_element_name(const char *name)
{
int n;
if (!name || !*name || (n = strlen(name)) >= UPISND_MAX_ELEMENT_NAME_LENGTH)
return -1;
if (strchr(name, '/') != NULL)
return -1;
return n;
}
static int upisnd_element_attr_open(upisnd_element_attr_e attr, int flags, upisnd_element_ref_t el)
{
if (!el)
{
errno = EINVAL;
return -1;
}
char path[UPISND_ELEMENT_MAX_PATH_LENGTH];
int err=upisnd_element_path(path, el->ctx->sysfs_base, el->name, attr);
if (err < 0)
{
errno = -err;
return -1;
}
int fd;
uint32_t started_at = upisnd_get_ms();
do
{
if ((fd = open(path, flags, 0)) >= 0)
{
errno = 0;
return fd;
}
else
{
switch (errno)
{
case ENOENT:
case EACCES:
usleep(1000);
continue;
default:
return -1;
}
}
}
while (upisnd_get_ms() - started_at < UPISND_TIMEOUT_MS); // Wait for up to UPISND_TIMEOUT_MS for udev permission rule to kick in.
return -1;
}
static int upisnd_element_attr_read_int(upisnd_element_attr_e attr, upisnd_element_ref_t el)
{
int value, err, fd = upisnd_element_attr_open(attr, O_CLOEXEC | O_RDONLY, el);
if (fd < 0)
return -1;
value = upisnd_value_read(fd);
err = errno;
close(fd);
errno = err;
return value;
}
static int upisnd_element_attr_write_int(upisnd_element_attr_e attr, upisnd_element_ref_t el, int i)
{
int err, fd = upisnd_element_attr_open(attr, O_CLOEXEC | O_WRONLY, el);
if (fd < 0)
return -1;
err = upisnd_value_write(fd, i) < 0 ? errno : 0;
close(fd);
errno = err;
return err == 0;
}
static int upisnd_element_attr_read_str(char *dst, size_t n, upisnd_element_attr_e attr, upisnd_element_ref_t el)
{
if (!dst || n < 2)
{
errno = EINVAL;
return -1;
}
int fd = upisnd_element_attr_open(attr, O_CLOEXEC | O_RDONLY, el);
if (fd < 0)
return -1;
int r = read(fd, dst, n-1);
int err;
if (r >= 0)
{
err = 0;
dst[r] = '\0';
char *t = strpbrk(dst, UPISND_TEXT_SEPARATORS);
if (t) *t = '\0';
}
else err = errno;
close(fd);
errno = err;
return r;
}
static int upisnd_element_attr_write_str(upisnd_element_attr_e attr, upisnd_element_ref_t el, const char *str)
{
int err, fd = upisnd_element_attr_open(attr, O_CLOEXEC | O_WRONLY, el);
if (fd < 0)
return -1;
char s[UPISND_MAX_REQUEST_LENGTH+1];
strncpy(s, str, UPISND_MAX_REQUEST_LENGTH);
s[UPISND_MAX_REQUEST_LENGTH] = '\0';
size_t n = strlen(s);
errno = err = 0;
if (lseek(fd, 0, SEEK_SET) < 0 || write(fd, s, n) < 0 || fdatasync(fd) < 0)
err = errno;
close(fd);
if (err != 0)
{
errno = err;
return -1;
}
return n;
}
int upisnd_generate_random_element_name(char *dst, size_t n, const char *prefix)
{
struct upisnd_ctx_t *ctx = upisnd_active_ctx;
if (!ctx)
{
errno = EINVAL;
return -1;
}
uint32_t uuid[4];
mtx_lock(&ctx->mutex);
uuid[0] = upisnd_xoshiro128_star_star_next(ctx->seed);
uuid[1] = upisnd_xoshiro128_star_star_next(ctx->seed);
uuid[2] = upisnd_xoshiro128_star_star_next(ctx->seed);
uuid[3] = upisnd_xoshiro128_star_star_next(ctx->seed);
mtx_unlock(&ctx->mutex);
char name[23];
upisnd_base64_encode(name, sizeof(name), uuid, sizeof(uuid), false);
if (prefix && *prefix)
{
return snprintf(dst, n, "%s-%s", prefix, name);
}
else
{
return snprintf(dst, n, "%s", name);
}
}
static int upisnd_unsetup_do(const char *sysfs_base, const char *name)
{
int fd = upisnd_open_fd(UPISND_PATH_UNSETUP, O_CLOEXEC | O_WRONLY, sysfs_base);
if (fd < 0)
return fd;
int err = 0;
if (write(fd, name, strlen(name)) < 0 || fdatasync(fd) < 0)
err = errno;
if (close(fd) < 0)
err = errno;
errno = err;
return -err;
}
int upisnd_unsetup(const char *name)
{
if (!upisnd_active_ctx)
{
errno = EINVAL;
return -1;
}
return upisnd_unsetup_do(upisnd_active_ctx->sysfs_base, name);
}
upisnd_element_ref_t upisnd_element_get(const char *name)
{
struct upisnd_ctx_t *ctx = upisnd_active_ctx;
if (!ctx || upisnd_validate_element_name(name) < 0)
{
errno = EINVAL;
return NULL;
}
mtx_lock(&ctx->mutex);
struct upisnd_elements_list_node_t *el = ctx->elements;
while (el)
{
if (strcmp(el->name, name) == 0)
{
upisnd_element_add_ref(el);
errno = 0;
break;
}
el = el->next;
}
mtx_unlock(&ctx->mutex);
return el;
}
upisnd_element_ref_t upisnd_element_add_ref(upisnd_element_ref_t ref)
{
if (ref)
{
__atomic_add_fetch(&ref->refcount, 1, memory_order_seq_cst);
return ref;
}
return NULL;
}
void upisnd_element_unref(upisnd_element_ref_t *ref)
{
if (!ref || !*ref)
return;
if (__atomic_sub_fetch(&(*ref)->refcount, 1, memory_order_seq_cst) == 0)
{
mtx_t *mtx = &(*ref)->ctx->mutex;
mtx_lock(mtx);
upisnd_unsetup_do((*ref)->ctx->sysfs_base, (*ref)->name);
upisnd_elements_remove(*ref);
mtx_unlock(mtx);
}
*ref = NULL;
}
int upisnd_element_open_value_fd(upisnd_element_ref_t el, int flags)
{
return upisnd_element_attr_open(UPISND_ELEMENT_ATTR_VALUE, flags, el);
}
int upisnd_value_read(int fd)
{
char value[16];
errno = 0;
int n;
if (lseek(fd, 0, SEEK_SET) < 0 || (n = read(fd, value, 15)) < 0)
return -1;
value[n] = '\0';
return strtol(value, NULL, 10);
}
int upisnd_value_write(int fd, int value)
{
char s[16];
int n = sprintf(s, "%d", value);
errno = 0;
if (lseek(fd, 0, SEEK_SET) < 0 || write(fd, s, n) < 0 || fdatasync(fd) < 0)
return -1;
return n;
}
bool upisnd_is_pin_valid(upisnd_pin_t pin)
{
return pin < UPISND_PIN_COUNT;
}
const char *upisnd_pin_to_str(upisnd_pin_t pin)
{
return upisnd_is_pin_valid(pin) ? UPISND_PIN_NAMES[pin] : "";
}
upisnd_pin_t upisnd_str_to_pin(const char *str)
{
if (!str || strlen(str) != 3 || !isdigit(str[1]) || !isdigit(str[2]))
return UPISND_PIN_INVALID;
char sanitized[4];
switch (*str)
{
case 'a': case 'A':
sanitized[0] = 'A';
break;
case 'b': case 'B':
sanitized[0] = 'B';
break;
default:
return UPISND_PIN_INVALID;
}
memcpy(&sanitized[1], &str[1], 2);
sanitized[3] = '\0';
for (int i=0; i<UPISND_PIN_COUNT; ++i)
{
if (strcmp(sanitized, UPISND_PIN_NAMES[i]) == 0)
return i;
}
return UPISND_PIN_INVALID;
}
#define UPISND_DEFINE_STR_TO_ENUM(middle_part, count) \
upisnd_ ## middle_part ## _e upisnd_str_to_ ## middle_part(const char *str) \
{ \
for (int i=0; i<count; ++i) \
{ \
if (strcmp(str, upisnd_ ## middle_part ## _to_str((upisnd_ ## middle_part ## _e)i)) == 0) \
return (upisnd_ ## middle_part ## _e)i; \
} \
return (upisnd_ ## middle_part ## _e)-1; \
}
const char *upisnd_pin_pull_to_str(upisnd_pin_pull_e pull)
{
switch (pull)
{
case UPISND_PIN_PULL_NONE: return "pull_none";
case UPISND_PIN_PULL_UP: return "pull_up";
case UPISND_PIN_PULL_DOWN: return "pull_down";
default: return "";
}
}
UPISND_DEFINE_STR_TO_ENUM(pin_pull, UPISND_PIN_PULL_COUNT);
const char *upisnd_activity_to_str(upisnd_activity_e activity)
{
switch (activity)
{
case UPISND_ACTIVITY_MIDI_INPUT: return "midi_in";
case UPISND_ACTIVITY_MIDI_OUTPUT: return "midi_out";
default: return "";
}
}
UPISND_DEFINE_STR_TO_ENUM(activity, UPISND_ACTIVITY_COUNT);
const char *upisnd_element_type_to_str(upisnd_element_type_e type)
{
switch (type)
{
case UPISND_ELEMENT_TYPE_NONE: return "none";
case UPISND_ELEMENT_TYPE_ENCODER: return "encoder";
case UPISND_ELEMENT_TYPE_ANALOG_INPUT: return "analog_in";
case UPISND_ELEMENT_TYPE_GPIO: return "gpio";
case UPISND_ELEMENT_TYPE_ACTIVITY: return "activity";
default: return "";
}
}
UPISND_DEFINE_STR_TO_ENUM(element_type, UPISND_ELEMENT_TYPE_COUNT);
const char *upisnd_pin_direction_to_str(upisnd_pin_direction_e dir)
{
switch (dir)
{
case UPISND_PIN_DIR_INPUT: return "in";
case UPISND_PIN_DIR_OUTPUT: return "out";
default: return "";
}
}
UPISND_DEFINE_STR_TO_ENUM(pin_direction, UPISND_PIN_DIR_COUNT);
const char *upisnd_value_mode_to_str(upisnd_value_mode_e mode)
{
switch (mode)
{
case UPISND_VALUE_MODE_CLAMP: return "clamp";
case UPISND_VALUE_MODE_WRAP: return "wrap";
default: return "";
}
}
UPISND_DEFINE_STR_TO_ENUM(value_mode, UPISND_VALUE_MODE_COUNT);
#undef UPISND_DEFINE_STR_TO_ENUM
#define UPISND_DEFINE_INTERNAL_SETUP_FIELD(shift, bits, type, name) \
static inline type upisnd_internal_setup_get_ ## name(upisnd_setup_t setup) { \
return (type)((setup & (((1 << bits)-1) << shift)) >> shift); \
} \
static inline void upisnd_internal_setup_set_ ## name(upisnd_setup_t *setup, type value) { \
*setup = ((*setup)&~(((1 << bits)-1) << shift)) | ((value & ((1 << bits)-1)) << shift); \
}
UPISND_DEFINE_INTERNAL_SETUP_FIELD( 0, 3, upisnd_element_type_e, element_type);
UPISND_DEFINE_INTERNAL_SETUP_FIELD( 3, 8, upisnd_pin_t, pin_id);
UPISND_DEFINE_INTERNAL_SETUP_FIELD(11, 2, upisnd_pin_pull_e, gpio_pull);
UPISND_DEFINE_INTERNAL_SETUP_FIELD(13, 1, upisnd_pin_direction_e, gpio_dir);
UPISND_DEFINE_INTERNAL_SETUP_FIELD(12, 1, bool, gpio_output);
UPISND_DEFINE_INTERNAL_SETUP_FIELD(13, 8, upisnd_pin_t, encoder_pin_b_id);
UPISND_DEFINE_INTERNAL_SETUP_FIELD(21, 2, upisnd_pin_pull_e, encoder_pin_b_pull);
UPISND_DEFINE_INTERNAL_SETUP_FIELD(11, 2, upisnd_activity_e, activity_type);
#undef UPISND_DEFINE_INTERNAL_SETUP_FIELD
upisnd_element_type_e upisnd_setup_get_element_type(upisnd_setup_t setup)
{
return upisnd_internal_setup_get_element_type(setup);
}
upisnd_pin_t upisnd_setup_get_pin_id(upisnd_setup_t setup)
{
switch (upisnd_internal_setup_get_element_type(setup))
{
case UPISND_ELEMENT_TYPE_ENCODER:
case UPISND_ELEMENT_TYPE_ANALOG_INPUT:
case UPISND_ELEMENT_TYPE_GPIO:
case UPISND_ELEMENT_TYPE_ACTIVITY:
return upisnd_internal_setup_get_pin_id(setup);
default:
return UPISND_PIN_INVALID;
}
}
upisnd_pin_pull_e upisnd_setup_get_gpio_pull(upisnd_setup_t setup)
{
switch (upisnd_internal_setup_get_element_type(setup))
{
case UPISND_ELEMENT_TYPE_ENCODER:
break;
case UPISND_ELEMENT_TYPE_GPIO:
if (upisnd_internal_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT)
break;
// fallthrough intentional.
default:
return UPISND_PIN_PULL_INVALID;
}
return upisnd_internal_setup_get_gpio_pull(setup);
}
upisnd_pin_direction_e upisnd_setup_get_gpio_dir(upisnd_setup_t setup)
{
return (upisnd_internal_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) ? upisnd_internal_setup_get_gpio_dir(setup) : UPISND_PIN_DIR_INVALID;
}
int upisnd_setup_get_gpio_output(upisnd_setup_t setup)
{
return (upisnd_internal_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO &&
upisnd_internal_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) ?
(upisnd_internal_setup_get_gpio_output(setup) ? 1 : 0) : -EINVAL;
}
upisnd_pin_t upisnd_setup_get_encoder_pin_b_id(upisnd_setup_t setup)
{
return (upisnd_internal_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_ENCODER) ? upisnd_internal_setup_get_encoder_pin_b_id(setup) : UPISND_PIN_INVALID;
}
upisnd_pin_pull_e upisnd_setup_get_encoder_pin_b_pull(upisnd_setup_t setup)
{
return (upisnd_internal_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_ENCODER) ? upisnd_internal_setup_get_encoder_pin_b_pull(setup) : UPISND_PIN_PULL_INVALID;
}
upisnd_activity_e upisnd_setup_get_activity_type(upisnd_setup_t setup)
{
return (upisnd_internal_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_ACTIVITY) ? upisnd_internal_setup_get_activity_type(setup) : UPISND_ACTIVITY_INVALID;
}
int upisnd_setup_set_element_type(upisnd_setup_t *setup, upisnd_element_type_e value)
{
if (value < 0 || value >= UPISND_ELEMENT_TYPE_COUNT)
return -EINVAL;
memset(setup, 0, sizeof(*setup));
upisnd_internal_setup_set_element_type(setup, value);
return 0;
}
int upisnd_setup_set_pin_id(upisnd_setup_t *setup, upisnd_pin_t value)
{
switch (upisnd_internal_setup_get_element_type(*setup))
{
case UPISND_ELEMENT_TYPE_ENCODER:
case UPISND_ELEMENT_TYPE_ANALOG_INPUT:
case UPISND_ELEMENT_TYPE_GPIO:
case UPISND_ELEMENT_TYPE_ACTIVITY:
upisnd_internal_setup_set_pin_id(setup, value);
return 0;
default:
return -EINVAL;
}
}
int upisnd_setup_set_gpio_dir(upisnd_setup_t *setup, upisnd_pin_direction_e value)
{
if (upisnd_internal_setup_get_element_type(*setup) != UPISND_ELEMENT_TYPE_GPIO)
return -EINVAL;
upisnd_internal_setup_set_gpio_dir(setup, value);
return 0;
}
int upisnd_setup_set_gpio_pull(upisnd_setup_t *setup, upisnd_pin_pull_e value)
{
switch (upisnd_internal_setup_get_element_type(*setup))
{
case UPISND_ELEMENT_TYPE_ENCODER:
break;
case UPISND_ELEMENT_TYPE_GPIO:
if (upisnd_internal_setup_get_gpio_dir(*setup) == UPISND_PIN_DIR_INPUT)
break;
// fallthrough intentional.
default:
return -EINVAL;
}
upisnd_internal_setup_set_gpio_pull(setup, value);
return 0;
}
int upisnd_setup_set_gpio_output(upisnd_setup_t *setup, bool value)
{
if (upisnd_internal_setup_get_element_type(*setup) != UPISND_ELEMENT_TYPE_GPIO || upisnd_internal_setup_get_gpio_dir(*setup) != UPISND_PIN_DIR_OUTPUT)
return -EINVAL;
upisnd_internal_setup_set_gpio_output(setup, value);
return 0;
}
int upisnd_setup_set_encoder_pin_b_id(upisnd_setup_t *setup, upisnd_pin_t value)
{
if (upisnd_internal_setup_get_element_type(*setup) != UPISND_ELEMENT_TYPE_ENCODER)
return -EINVAL;
upisnd_internal_setup_set_encoder_pin_b_id(setup, value);
return 0;
}
int upisnd_setup_set_encoder_pin_b_pull(upisnd_setup_t *setup, upisnd_pin_pull_e value)
{
if (upisnd_internal_setup_get_element_type(*setup) != UPISND_ELEMENT_TYPE_ENCODER)
return -EINVAL;
upisnd_internal_setup_set_encoder_pin_b_pull(setup, value);
return 0;
}
int upisnd_setup_set_activity_type(upisnd_setup_t *setup, upisnd_activity_e value)
{
if (upisnd_internal_setup_get_element_type(*setup) != UPISND_ELEMENT_TYPE_ACTIVITY)
return -EINVAL;
upisnd_internal_setup_set_activity_type(setup, value);
return 0;
}
static bool upisnd_element_exists_in_sysfs(const char *sysfs_base, const char *name)
{
char path[UPISND_ELEMENT_MAX_PATH_LENGTH];
int err = upisnd_element_path(path, sysfs_base, name, UPISND_ELEMENT_ATTR_ROOT);
if (err < 0)
return false;
struct stat st;
if (stat(path, &st) == 0)
{
if (st.st_mode & S_IFDIR)
return true;
}
return false;
}
// Request_fmt without %s for name.
static upisnd_element_ref_t upisnd_setup_do(struct upisnd_ctx_t *ctx, const char *name, const char *request_fmt, ...)
{
int name_len;
if (!ctx || (name_len = upisnd_validate_element_name(name)) < 0)
{
errno = EINVAL;
return NULL;
}
char path[UPISND_MAX_BASE_PATH_LENGTH];
int err = upisnd_path(path, UPISND_PATH_SETUP, ctx->sysfs_base);
if (err < 0)
{
errno = -err;
return NULL;
}
char request[UPISND_MAX_REQUEST_LENGTH];
strncpy(request, name, UPISND_MAX_ELEMENT_NAME_LENGTH);
request[name_len++] = ' ';
va_list ap;
va_start(ap, request_fmt);
int n = vsnprintf(request+name_len, sizeof(request)-name_len, request_fmt, ap);
va_end(ap);
if (n < 0 || n+name_len >= UPISND_MAX_REQUEST_LENGTH)
{
errno = EINVAL;
return NULL;
}
struct upisnd_elements_list_node_t *el = upisnd_element_get(name);
bool existing, existed_in_sysfs = false;
if (el == NULL)
{
existing = false;
el = upisnd_element_alloc(name, ctx);
if (!el)
{
errno = ENOMEM;
return NULL;
}
existed_in_sysfs = upisnd_element_exists_in_sysfs(ctx->sysfs_base, name);
}
else existing = true;
int fd;
mtx_lock(&ctx->mutex);
fd = open(path, O_CLOEXEC | O_WRONLY, 0);
if (fd >= 0)
{
err = 0;
if (write(fd, request, n+name_len) < 0 || fdatasync(fd) < 0)
err = errno;
if (close(fd) < 0)
err = errno;
}
else err = errno;
if (err == 0 && !existing)
{
upisnd_elements_prepend(el);
if (existed_in_sysfs)
existing = true;
}
mtx_unlock(&ctx->mutex);
if (err != 0)
{
if (existing && !existed_in_sysfs) upisnd_element_unref(&el);
else upisnd_elements_free(el);
errno = err;
return NULL;
}
errno = !existing ? 0 : EEXIST;
return el;
}
upisnd_element_ref_t upisnd_setup(const char *name, upisnd_setup_t setup)
{
switch (upisnd_setup_get_element_type(setup))
{
case UPISND_ELEMENT_TYPE_ENCODER:
return upisnd_setup_encoder(
name,
upisnd_internal_setup_get_pin_id(setup),
upisnd_internal_setup_get_gpio_pull(setup),
upisnd_internal_setup_get_encoder_pin_b_id(setup),
upisnd_internal_setup_get_encoder_pin_b_pull(setup)
);
case UPISND_ELEMENT_TYPE_ANALOG_INPUT:
return upisnd_setup_analog_input(name, upisnd_internal_setup_get_pin_id(setup));
case UPISND_ELEMENT_TYPE_GPIO:
switch (upisnd_internal_setup_get_gpio_dir(setup))
{
case UPISND_PIN_DIR_INPUT:
return upisnd_setup_gpio_input(name, upisnd_internal_setup_get_pin_id(setup), upisnd_internal_setup_get_gpio_pull(setup));
case UPISND_PIN_DIR_OUTPUT:
return upisnd_setup_gpio_output(name, upisnd_internal_setup_get_pin_id(setup), upisnd_internal_setup_get_gpio_output(setup));
default:
break;
}
break;
case UPISND_ELEMENT_TYPE_ACTIVITY:
return upisnd_setup_activity(name, upisnd_internal_setup_get_pin_id(setup), upisnd_internal_setup_get_activity_type(setup));
default:
break;
}
errno = EINVAL;
return NULL;
}
upisnd_element_ref_t upisnd_setup_encoder(const char *name, upisnd_pin_t pin_a, upisnd_pin_pull_e pull_a, upisnd_pin_t pin_b, upisnd_pin_pull_e pull_b)
{
return upisnd_setup_do(upisnd_active_ctx, name, "encoder %s %s %s %s", upisnd_pin_to_str(pin_a), upisnd_pin_pull_to_str(pull_a), upisnd_pin_to_str(pin_b), upisnd_pin_pull_to_str(pull_b));
}
upisnd_element_ref_t upisnd_setup_analog_input(const char *name, upisnd_pin_t pin)
{
return upisnd_setup_do(upisnd_active_ctx, name, "analog_in %s ", upisnd_pin_to_str(pin));
}
upisnd_element_ref_t upisnd_setup_gpio_input(const char *name, upisnd_pin_t pin, upisnd_pin_pull_e pull)
{
return upisnd_setup_do(upisnd_active_ctx, name, "gpio %s input %s", upisnd_pin_to_str(pin), upisnd_pin_pull_to_str(pull));
}
upisnd_element_ref_t upisnd_setup_gpio_output(const char *name, upisnd_pin_t pin, bool high)
{
return upisnd_setup_do(upisnd_active_ctx, name, "gpio %s output %c", upisnd_pin_to_str(pin), high ? '1' : '0');
}
upisnd_element_ref_t upisnd_setup_activity(const char *name, upisnd_pin_t pin, upisnd_activity_e activity)
{
return upisnd_setup_do(upisnd_active_ctx, name, "activity_%s %s", upisnd_activity_to_str(activity), upisnd_pin_to_str(pin));
}
upisnd_pin_direction_e upisnd_element_gpio_get_direction(upisnd_element_ref_t el)
{
if (!el)
{
errno = EINVAL;
return UPISND_PIN_DIR_INVALID;
}
char dir[64];
int err = upisnd_element_attr_read_str(dir, sizeof(dir), UPISND_ELEMENT_ATTR_DIRECTION, el);
if (err < 0)
return UPISND_PIN_DIR_INVALID;
upisnd_pin_direction_e d = upisnd_str_to_pin_direction(dir);
if (d == UPISND_PIN_DIR_INVALID)
{
errno = EINVAL;
return UPISND_PIN_DIR_INVALID;
}
return d;
}
static upisnd_pin_pull_e upisnd_element_get_pull(upisnd_element_ref_t el, upisnd_element_attr_e attr)
{
if (!el)
{
errno = EINVAL;
return UPISND_PIN_PULL_INVALID;
}
char pull[64];
int err = upisnd_element_attr_read_str(pull, sizeof(pull), attr, el);
if (err < 0)
return UPISND_PIN_PULL_INVALID;
upisnd_pin_pull_e p = upisnd_str_to_pin_pull(pull);
if (p == UPISND_PIN_PULL_INVALID)
{
errno = EINVAL;
return UPISND_PIN_PULL_INVALID;
}
return p;
}
upisnd_pin_pull_e upisnd_element_gpio_get_pull(upisnd_element_ref_t el)
{
return upisnd_element_get_pull(el, UPISND_ELEMENT_ATTR_PIN_PULL);
}
upisnd_activity_e upisnd_element_activity_get_type(upisnd_element_ref_t el)
{
if (!el)
{
errno = EINVAL;
return UPISND_ACTIVITY_INVALID;
}
char activity[64];
int err = upisnd_element_attr_read_str(activity, sizeof(activity), UPISND_ELEMENT_ATTR_ACTIVITY_TYPE, el);
if (err < 0)
return UPISND_ACTIVITY_INVALID;
upisnd_activity_e a = upisnd_str_to_activity(activity);
if (a == UPISND_ACTIVITY_INVALID)
{
errno = EINVAL;
return UPISND_ACTIVITY_INVALID;
}
return a;
}
void upisnd_element_encoder_init_default_opts(upisnd_encoder_opts_t *opts)
{
opts->input_range.low = opts->value_range.low = 0;
opts->input_range.high = opts->value_range.high = 23;
opts->value_mode = UPISND_VALUE_MODE_CLAMP;
}
int upisnd_element_encoder_get_opts(upisnd_element_ref_t el, upisnd_encoder_opts_t *opts)
{
struct
{
upisnd_element_attr_e attr;
int *dst;
} t[4] =
{
{ UPISND_ELEMENT_ATTR_INPUT_MIN, &opts->input_range.low },
{ UPISND_ELEMENT_ATTR_INPUT_MAX, &opts->input_range.high },
{ UPISND_ELEMENT_ATTR_VALUE_LOW, &opts->value_range.low },
{ UPISND_ELEMENT_ATTR_VALUE_HIGH, &opts->value_range.high },
};
for (int i=0; i<sizeof(t)/sizeof(t[0]); ++i)
{
*t[i].dst = upisnd_element_attr_read_int(t[i].attr, el);
if (errno != 0)
return -1;
}
char mode[64];
int err = upisnd_element_attr_read_str(mode, sizeof(mode), UPISND_ELEMENT_ATTR_VALUE_MODE, el);
if (err < 0)
return -1;
opts->value_mode = upisnd_str_to_value_mode(mode);
if (opts->value_mode == UPISND_VALUE_MODE_INVALID)
{
errno = EINVAL;
return -1;
}
return 0;
}
int upisnd_element_encoder_set_opts(upisnd_element_ref_t el, const upisnd_encoder_opts_t *opts)
{
int err;
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_INPUT_MIN, el, opts->input_range.low)) >= 0 &&
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_INPUT_MAX, el, opts->input_range.high)) >= 0 &&
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_VALUE_LOW, el, opts->value_range.low)) >= 0 &&
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_VALUE_HIGH, el, opts->value_range.high)) >= 0 &&
(err = upisnd_element_attr_write_str(UPISND_ELEMENT_ATTR_VALUE_MODE, el, upisnd_value_mode_to_str(opts->value_mode))) >= 0;
return err >= 0 ? 0 : err;
}
upisnd_pin_t upisnd_element_encoder_get_pin_b(upisnd_element_ref_t el)
{
if (!el)
{
errno = EINVAL;
return UPISND_PIN_INVALID;
}
int i = upisnd_element_attr_read_int(UPISND_ELEMENT_ATTR_PIN_B, el);
return upisnd_is_pin_valid(i) ? (upisnd_pin_t)i : UPISND_PIN_INVALID;
}
upisnd_pin_pull_e upisnd_element_encoder_get_pin_b_pull(upisnd_element_ref_t el)
{
return upisnd_element_get_pull(el, UPISND_ELEMENT_ATTR_PIN_B_PULL);
}
void upisnd_element_analog_input_init_default_opts(upisnd_analog_input_opts_t *opts)
{
opts->input_range.low = opts->value_range.low = 0;
opts->input_range.high = opts->value_range.high = 1023;
}
int upisnd_element_analog_input_get_opts(upisnd_element_ref_t el, upisnd_analog_input_opts_t *opts)
{
struct
{
upisnd_element_attr_e attr;
int *dst;
} t[4] =
{
{ UPISND_ELEMENT_ATTR_INPUT_MIN, &opts->input_range.low },
{ UPISND_ELEMENT_ATTR_INPUT_MAX, &opts->input_range.high },
{ UPISND_ELEMENT_ATTR_VALUE_LOW, &opts->value_range.low },
{ UPISND_ELEMENT_ATTR_VALUE_HIGH, &opts->value_range.high },
};
for (int i=0; i<sizeof(t)/sizeof(t[0]); ++i)
{
*t[i].dst = upisnd_element_attr_read_int(t[i].attr, el);
if (errno != 0)
return -1;
}
return 0;
}
int upisnd_element_analog_input_set_opts(upisnd_element_ref_t el, const upisnd_analog_input_opts_t *opts)
{
int err;
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_INPUT_MIN, el, opts->input_range.low)) >= 0 &&
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_INPUT_MAX, el, opts->input_range.high)) >= 0 &&
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_VALUE_LOW, el, opts->value_range.low)) >= 0 &&
(err = upisnd_element_attr_write_int(UPISND_ELEMENT_ATTR_VALUE_HIGH, el, opts->value_range.high)) >= 0;
return err >= 0 ? 0 : err;
}
const char *upisnd_element_get_name(upisnd_element_ref_t el)
{
return el ? el->name : NULL;
}
upisnd_element_type_e upisnd_element_get_type(upisnd_element_ref_t el)
{
if (!el)
return UPISND_ELEMENT_TYPE_INVALID;
char type[64];
int n = upisnd_element_attr_read_str(type, sizeof(type), UPISND_ELEMENT_ATTR_TYPE, el);
if (n < 0)
return UPISND_ELEMENT_TYPE_INVALID;
return upisnd_str_to_element_type(type);
}
upisnd_pin_t upisnd_element_get_pin(upisnd_element_ref_t el)
{
if (!el)
{
errno = EINVAL;
return UPISND_PIN_INVALID;
}
int i = upisnd_element_attr_read_int(UPISND_ELEMENT_ATTR_PIN, el);
return upisnd_is_pin_valid(i) ? (upisnd_pin_t)i : UPISND_PIN_INVALID;
}
int upisnd_init(void)
{
return upisnd_init_internal(NULL) != NULL ? 0 : -1;
}
static int upisnd_unsetup_all_elements(struct upisnd_ctx_t *ctx)
{
int fd;
mtx_lock(&ctx->mutex);
if (!ctx->elements)
{
mtx_unlock(&ctx->mutex);
return 0;
}
fd = upisnd_open_fd(UPISND_PATH_UNSETUP, O_CLOEXEC | O_WRONLY, ctx->sysfs_base);
if (fd < 0)
{
mtx_unlock(&ctx->mutex);
return fd;
}
struct upisnd_elements_list_node_t *n = ctx->elements;
while (n)
{
struct upisnd_elements_list_node_t *next = n->next;
write(fd, n->name, strlen(n->name));
fdatasync(fd);
lseek(fd, 0, SEEK_SET);
upisnd_elements_free(n);
n = next;
}
ctx->elements = NULL;
close(fd);
mtx_unlock(&ctx->mutex);
return 0;
}
void upisnd_uninit(void)
{
if (upisnd_active_ctx && __atomic_sub_fetch(&upisnd_active_ctx->refcount, 1, memory_order_seq_cst) == 0)
{
upisnd_contexts_remove(upisnd_active_ctx);
upisnd_active_ctx = upisnd_ctx_list;
}
}
static void upisnd_ctx_free(struct upisnd_ctx_t *ctx)
{
upisnd_unsetup_all_elements(ctx);
mtx_destroy(&ctx->mutex);
free(ctx);
}
#ifdef UPISND_INTERNAL
int upisnd_set_adc_offset(int16_t offset)
{
if (!upisnd_active_ctx)
{
errno = ENODEV;
return -ENODEV;
}
int fd = upisnd_open_fd(UPISND_PATH_ADC_OFFSET, O_CLOEXEC | O_WRONLY, upisnd_active_ctx->sysfs_base);
if (fd < 0)
return fd;
int err = 0;
char buf[8];
int n = snprintf(buf, sizeof(buf), "%d", offset);
if (write(fd, buf, n) < 0 || fdatasync(fd) < 0)
err = errno;
if (close(fd) < 0)
err = errno;
errno = err;
return -err;
}
UPISND_API int16_t upisnd_get_adc_offset(void)
{
if (!upisnd_active_ctx)
{
errno = ENODEV;
return -ENODEV;
}
int fd = upisnd_open_fd(UPISND_PATH_ADC_OFFSET, O_CLOEXEC | O_RDONLY, upisnd_active_ctx->sysfs_base);
if (fd < 0)
return fd;
int err = 0;
char buf[8];
int n = read(fd, buf, sizeof(buf)-1);
if (n < 0)
err = errno;
else
{
buf[n] = '\0';
errno = 0;
n = strtol(buf, NULL, 10);
if (errno)
err = errno;
}
if (close(fd) < 0)
err = errno;
errno = err;
return err ? -err : n;
}
int upisnd_set_adc_gain(uint16_t gain)
{
if (!upisnd_active_ctx)
{
errno = ENODEV;
return -ENODEV;
}
int fd = upisnd_open_fd(UPISND_PATH_ADC_GAIN, O_CLOEXEC | O_WRONLY, upisnd_active_ctx->sysfs_base);
if (fd < 0)
return fd;
int err = 0;
char buf[8];
int n = snprintf(buf, sizeof(buf), "%u", gain);
if (write(fd, buf, n) < 0 || fdatasync(fd) < 0)
err = errno;
if (close(fd) < 0)
err = errno;
errno = err;
return -err;
}
UPISND_API uint16_t upisnd_get_adc_gain(void)
{
if (!upisnd_active_ctx)
{
errno = ENODEV;
return -ENODEV;
}
int fd = upisnd_open_fd(UPISND_PATH_ADC_GAIN, O_CLOEXEC | O_RDONLY, upisnd_active_ctx->sysfs_base);
if (fd < 0)
return fd;
int err = 0;
char buf[8];
int n = read(fd, buf, sizeof(buf)-1);
if (n < 0)
err = errno;
else
{
buf[n] = '\0';
errno = 0;
n = strtoul(buf, NULL, 10);
if (errno)
err = errno;
}
if (close(fd) < 0)
err = errno;
errno = err;
return err ? -err : n;
}
#endif