Skip to content

Commit 8f668bd

Browse files
committed
laurent: driver for KernelChip Laurent family of relays
Add support for the KernelChip Laurent-2 / Laurent-5 / Laurent-112 / Laurent-128 network-controlled relay arrays. Signed-off-by: Dmitry Baryshkov <[email protected]>
1 parent 798a76a commit 8f668bd

File tree

6 files changed

+234
-0
lines changed

6 files changed

+234
-0
lines changed

config-samples/sample12.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
devices:
3+
- board: myboard
4+
name: "My Board"
5+
description: |
6+
My Awesome board
7+
console: /dev/ttyABC0
8+
fastboot: cacafada
9+
fastboot_set_active: true
10+
fastboot_key_timeout: 2
11+
laurent:
12+
server: laurent.lan
13+
relay: 5

device.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ extern const struct control_ops ftdi_gpio_ops;
105105
extern const struct control_ops local_gpio_ops;
106106
extern const struct control_ops external_ops;
107107
extern const struct control_ops qcomlt_dbg_ops;
108+
extern const struct control_ops laurent_ops;
108109

109110
extern const struct console_ops conmux_console_ops;
110111
extern const struct console_ops console_ops;

device_parser.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ static void parse_board(struct device_parser *dp)
115115
if (dev->control_options)
116116
set_control_ops(dev, &ftdi_gpio_ops);
117117
continue;
118+
} else if (!strcmp(key, "laurent")) {
119+
dev->control_options = laurent_ops.parse_options(dp);
120+
if (dev->control_options)
121+
set_control_ops(dev, &laurent_ops);
122+
continue;
118123
}
119124

120125
device_parser_expect(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH);

drivers/laurent.c

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright (c) 2024, Linaro Ltd.
3+
* All rights reserved.
4+
*
5+
* SPDX-License-Identifier: BSD-3-Clause
6+
*
7+
* Driver for KernelChip Laurent family of Ethernet-controlled relay arrays.
8+
*/
9+
#define _GNU_SOURCE
10+
#include <sys/types.h>
11+
#include <sys/socket.h>
12+
#include <err.h>
13+
#include <netdb.h>
14+
#include <stdio.h>
15+
#include <unistd.h>
16+
#include <yaml.h>
17+
18+
#include "cdba-server.h"
19+
#include "device.h"
20+
#include "device_parser.h"
21+
22+
struct laurent_options {
23+
const char *server;
24+
const char *password;
25+
unsigned int relay;
26+
};
27+
28+
struct laurent {
29+
struct laurent_options *options;
30+
31+
struct addrinfo addr;
32+
};
33+
34+
#define DEFAULT_PASSWORD "Laurent"
35+
#define TOKEN_LENGTH 128
36+
37+
void *laurent_parse_options(struct device_parser *dp)
38+
{
39+
struct laurent_options *options;
40+
char value[TOKEN_LENGTH];
41+
char key[TOKEN_LENGTH];
42+
43+
options = calloc(1, sizeof(*options));
44+
options->password = DEFAULT_PASSWORD;
45+
46+
device_parser_accept(dp, YAML_MAPPING_START_EVENT, NULL, 0);
47+
while (device_parser_accept(dp, YAML_SCALAR_EVENT, key, TOKEN_LENGTH)) {
48+
if (!device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH))
49+
errx(1, "%s: expected value for \"%s\"", __func__, key);
50+
51+
if (!strcmp(key, "server"))
52+
options->server = strdup(value);
53+
else if (!strcmp(key, "password"))
54+
options->password = strdup(value);
55+
else if (!strcmp(key, "relay"))
56+
options->relay = strtoul(value, NULL, 0);
57+
else
58+
errx(1, "%s: unknown option \"%s\"", __func__, key);
59+
}
60+
61+
device_parser_expect(dp, YAML_MAPPING_END_EVENT, NULL, 0);
62+
63+
if (!options->server)
64+
errx(1, "%s: server hostname not specified", __func__);
65+
66+
return options;
67+
}
68+
69+
static void laurent_resolve(struct laurent *laurent)
70+
{
71+
struct addrinfo hints = {};
72+
struct addrinfo *result, *rp;
73+
74+
int ret;
75+
76+
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
77+
hints.ai_socktype = SOCK_STREAM;
78+
hints.ai_flags = AI_PASSIVE;
79+
hints.ai_protocol = 0;
80+
hints.ai_canonname = NULL;
81+
hints.ai_addr = NULL;
82+
hints.ai_next = NULL;
83+
84+
ret = getaddrinfo(laurent->options->server, "80", &hints, &result);
85+
if (ret != 0) {
86+
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
87+
exit(EXIT_FAILURE);
88+
}
89+
90+
for (rp = result; rp != NULL; rp = rp->ai_next) {
91+
int fd = socket(rp->ai_family, rp->ai_socktype,
92+
rp->ai_protocol);
93+
if (fd == -1)
94+
continue;
95+
96+
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
97+
close(fd);
98+
break;
99+
}
100+
101+
close(fd);
102+
}
103+
104+
if (rp == NULL)
105+
errx(1, "Could not resolve / connect to the controller\n");
106+
107+
laurent->addr = *rp;
108+
laurent->addr.ai_addr = malloc(rp->ai_addrlen);
109+
memcpy(laurent->addr.ai_addr, rp->ai_addr,rp->ai_addrlen);
110+
111+
freeaddrinfo(result); /* No longer needed */
112+
}
113+
114+
static void *laurent_open(struct device *dev)
115+
{
116+
struct laurent *laurent;
117+
118+
laurent = calloc(1, sizeof(*laurent));
119+
120+
laurent->options = dev->control_options;
121+
122+
laurent_resolve(laurent);
123+
124+
return laurent;
125+
}
126+
127+
static int laurent_power(struct device *dev, bool on)
128+
{
129+
struct laurent *laurent = dev->cdb;
130+
char buf[BUFSIZ];
131+
int fd, ret, len, off;
132+
133+
fd = socket(laurent->addr.ai_family, laurent->addr.ai_socktype,
134+
laurent->addr.ai_protocol);
135+
if (fd == -1) {
136+
warn("failed to open socket\n");
137+
return -1;
138+
}
139+
140+
ret = connect(fd, laurent->addr.ai_addr, laurent->addr.ai_addrlen);
141+
if (ret == -1) {
142+
warn("failed to connect\n");
143+
goto err;
144+
}
145+
146+
len = snprintf(buf, sizeof(buf), "GET /cmd.cgi?psw=%s&cmd=REL,%u,%d HTTP/1.0\r\n\r\n",
147+
laurent->options->password,
148+
laurent->options->relay,
149+
on);
150+
if (len < 0) {
151+
warn("asprintf failed\n");
152+
goto err;
153+
}
154+
155+
for (off = 0; off != len; ) {
156+
ret = send(fd, buf + off, len - off, 0);
157+
if (ret == -1) {
158+
warn("failed to send\n");
159+
goto err;
160+
}
161+
162+
off += ret;
163+
}
164+
165+
/* Dump controller response to stderr */
166+
while (true) {
167+
ret = recv(fd, buf, sizeof(buf), 0);
168+
if (ret == -1) {
169+
warn("failed to recv\n");
170+
goto err;
171+
}
172+
173+
if (!ret)
174+
break;
175+
176+
write(STDERR_FILENO, buf, ret);
177+
}
178+
179+
write(STDERR_FILENO, "\n", 1);
180+
181+
shutdown(fd, SHUT_RDWR);
182+
close(fd);
183+
184+
return 0;
185+
186+
err:
187+
shutdown(fd, SHUT_RDWR);
188+
close(fd);
189+
190+
return -1;
191+
}
192+
193+
const struct control_ops laurent_ops = {
194+
.parse_options = laurent_parse_options,
195+
.open = laurent_open,
196+
.power = laurent_power,
197+
};

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ drivers_srcs = ['drivers/alpaca.c',
7070
'drivers/conmux.c',
7171
'drivers/external.c',
7272
'drivers/ftdi-gpio.c',
73+
'drivers/laurent.c',
7374
'drivers/local-gpio.c',
7475
'drivers/qcomlt_dbg.c',
7576
]

schema.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,23 @@ properties:
142142
patternProperties:
143143
"^power|fastboot_key|power_key|usb_disconnect$":
144144
$ref: "#/$defs/local_gpio"
145+
146+
laurent:
147+
description: KernelChip Laurent relays
148+
type: object
149+
unevaluatedItems: false
150+
properties:
151+
server:
152+
type: string
153+
relay:
154+
type: integer
155+
password:
156+
description: password to access the relays, defaults to 'Laurent'
157+
type: string
158+
required:
159+
- server
160+
- relay
161+
145162
required:
146163
- board
147164
- name

0 commit comments

Comments
 (0)