-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
laurent: driver for KernelChip Laurent family of relays #72
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <sys/types.h> | ||
#include <sys/socket.h> | ||
#include <err.h> | ||
#include <netdb.h> | ||
#include <stdio.h> | ||
#include <unistd.h> | ||
#include <yaml.h> | ||
|
||
#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, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the relay port number ? Perhaps use something like "port" instead There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want for it to be confused with the TCP port on which to talk to the server. |
||
type: integer | ||
password: | ||
description: password to access the relays, defaults to 'Laurent' | ||
type: string | ||
required: | ||
- server | ||
- relay | ||
|
||
required: | ||
- board | ||
- name | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it's safe to assume nobody has a 470+ bytes long password
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't give me wrong ideas!