Skip to content
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

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config-samples/sample12.yaml
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
1 change: 1 addition & 0 deletions device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions device_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
197 changes: 197 additions & 0 deletions drivers/laurent.c
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];
Copy link
Member

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

Copy link
Collaborator Author

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!

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);
Dismissed Show dismissed Hide dismissed
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);
Dismissed Show dismissed Hide dismissed
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,
};
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
Expand Down
17 changes: 17 additions & 0 deletions schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the relay port number ? Perhaps use something like "port" instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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
Expand Down
Loading