diff --git a/config-samples/sample12.yaml b/config-samples/sample12.yaml new file mode 100644 index 0000000..4d3a824 --- /dev/null +++ b/config-samples/sample12.yaml @@ -0,0 +1,13 @@ +--- +devices: + - board: myboard + name: "My Board" + description: | + My Awesome board + console: /dev/ttyABC0 + fastboot: cacafada + fastboot_set_active: true + fastboot_key_timeout: 2 + laurent: + server: laurent.lan + relay: 5 diff --git a/device.h b/device.h index 0edb048..75a780a 100644 --- a/device.h +++ b/device.h @@ -105,6 +105,7 @@ extern const struct control_ops ftdi_gpio_ops; extern const struct control_ops local_gpio_ops; extern const struct control_ops external_ops; extern const struct control_ops qcomlt_dbg_ops; +extern const struct control_ops laurent_ops; extern const struct console_ops conmux_console_ops; extern const struct console_ops console_ops; diff --git a/device_parser.c b/device_parser.c index 8b55741..f5c2aa7 100644 --- a/device_parser.c +++ b/device_parser.c @@ -115,6 +115,11 @@ static void parse_board(struct device_parser *dp) if (dev->control_options) set_control_ops(dev, &ftdi_gpio_ops); continue; + } else if (!strcmp(key, "laurent")) { + dev->control_options = laurent_ops.parse_options(dp); + if (dev->control_options) + set_control_ops(dev, &laurent_ops); + continue; } device_parser_expect(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH); diff --git a/drivers/laurent.c b/drivers/laurent.c new file mode 100644 index 0000000..d096a3a --- /dev/null +++ b/drivers/laurent.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2024, Linaro Ltd. + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Driver for KernelChip Laurent family of Ethernet-controlled relay arrays. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "cdba-server.h" +#include "device.h" +#include "device_parser.h" + +struct laurent_options { + const char *server; + const char *password; + unsigned int relay; +}; + +struct laurent { + struct laurent_options *options; + + struct addrinfo addr; +}; + +#define DEFAULT_PASSWORD "Laurent" +#define TOKEN_LENGTH 128 + +void *laurent_parse_options(struct device_parser *dp) +{ + struct laurent_options *options; + char value[TOKEN_LENGTH]; + char key[TOKEN_LENGTH]; + + options = calloc(1, sizeof(*options)); + options->password = DEFAULT_PASSWORD; + + device_parser_accept(dp, YAML_MAPPING_START_EVENT, NULL, 0); + while (device_parser_accept(dp, YAML_SCALAR_EVENT, key, TOKEN_LENGTH)) { + if (!device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH)) + errx(1, "%s: expected value for \"%s\"", __func__, key); + + if (!strcmp(key, "server")) + options->server = strdup(value); + else if (!strcmp(key, "password")) + options->password = strdup(value); + else if (!strcmp(key, "relay")) + options->relay = strtoul(value, NULL, 0); + else + errx(1, "%s: unknown option \"%s\"", __func__, key); + } + + device_parser_expect(dp, YAML_MAPPING_END_EVENT, NULL, 0); + + if (!options->server) + errx(1, "%s: server hostname not specified", __func__); + + return options; +} + +static void laurent_resolve(struct laurent *laurent) +{ + struct addrinfo hints = {}; + struct addrinfo *result, *rp; + + int ret; + + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + ret = getaddrinfo(laurent->options->server, "80", &hints, &result); + if (ret != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + exit(EXIT_FAILURE); + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + int fd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (fd == -1) + continue; + + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) { + close(fd); + break; + } + + close(fd); + } + + if (rp == NULL) + errx(1, "Could not resolve / connect to the controller\n"); + + laurent->addr = *rp; + laurent->addr.ai_addr = malloc(rp->ai_addrlen); + memcpy(laurent->addr.ai_addr, rp->ai_addr,rp->ai_addrlen); + + freeaddrinfo(result); /* No longer needed */ +} + +static void *laurent_open(struct device *dev) +{ + struct laurent *laurent; + + laurent = calloc(1, sizeof(*laurent)); + + laurent->options = dev->control_options; + + laurent_resolve(laurent); + + return laurent; +} + +static int laurent_power(struct device *dev, bool on) +{ + struct laurent *laurent = dev->cdb; + char buf[BUFSIZ]; + int fd, ret, len, off; + + fd = socket(laurent->addr.ai_family, laurent->addr.ai_socktype, + laurent->addr.ai_protocol); + if (fd == -1) { + warn("failed to open socket\n"); + return -1; + } + + ret = connect(fd, laurent->addr.ai_addr, laurent->addr.ai_addrlen); + if (ret == -1) { + warn("failed to connect\n"); + goto err; + } + + len = snprintf(buf, sizeof(buf), "GET /cmd.cgi?psw=%s&cmd=REL,%u,%d HTTP/1.0\r\n\r\n", + laurent->options->password, + laurent->options->relay, + on); + if (len < 0) { + warn("asprintf failed\n"); + goto err; + } + + for (off = 0; off != len; ) { + ret = send(fd, buf + off, len - off, 0); + if (ret == -1) { + warn("failed to send\n"); + goto err; + } + + off += ret; + } + + /* Dump controller response to stderr */ + while (true) { + ret = recv(fd, buf, sizeof(buf), 0); + if (ret == -1) { + warn("failed to recv\n"); + goto err; + } + + if (!ret) + break; + + write(STDERR_FILENO, buf, ret); + } + + write(STDERR_FILENO, "\n", 1); + + shutdown(fd, SHUT_RDWR); + close(fd); + + return 0; + +err: + shutdown(fd, SHUT_RDWR); + close(fd); + + return -1; +} + +const struct control_ops laurent_ops = { + .parse_options = laurent_parse_options, + .open = laurent_open, + .power = laurent_power, +}; diff --git a/meson.build b/meson.build index fe03e81..c9e22ee 100644 --- a/meson.build +++ b/meson.build @@ -70,6 +70,7 @@ drivers_srcs = ['drivers/alpaca.c', 'drivers/conmux.c', 'drivers/external.c', 'drivers/ftdi-gpio.c', + 'drivers/laurent.c', 'drivers/local-gpio.c', 'drivers/qcomlt_dbg.c', ] diff --git a/schema.yaml b/schema.yaml index d4c0338..6ff209a 100644 --- a/schema.yaml +++ b/schema.yaml @@ -142,6 +142,23 @@ properties: patternProperties: "^power|fastboot_key|power_key|usb_disconnect$": $ref: "#/$defs/local_gpio" + + laurent: + description: KernelChip Laurent relays + type: object + unevaluatedItems: false + properties: + server: + type: string + relay: + type: integer + password: + description: password to access the relays, defaults to 'Laurent' + type: string + required: + - server + - relay + required: - board - name