Skip to content

Commit 32d6193

Browse files
author
Jakub Klimczak
committed
lib: Implement POSIX read(), write(), poll() etc. on stdio
Give the user the ability to interact with stdio using POSIX read() and write() using the tty.h API. This avoids depending on standard C functions and allows poll(), select() et al. to work for stdio thanks to the internal use of semaphores. Alternatively, should there be no UART console device, read() and write() will use stdio hooks (where available) and poll() will simply return POLLNVAL for fd's 0, 1 and 2. Tested using the posix/device_io test suite. Signed-off-by: Jakub Klimczak <[email protected]>
1 parent 5863171 commit 32d6193

File tree

5 files changed

+227
-21
lines changed

5 files changed

+227
-21
lines changed

lib/libc/minimal/source/stdout/stdout_console.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,28 @@ static inline size_t z_vrfy_zephyr_fwrite(const void *ZRESTRICT ptr,
107107
#include <zephyr/syscalls/zephyr_fwrite_mrsh.c>
108108
#endif
109109

110+
int z_impl_zephyr_write_stdout(const void *buffer, int nbytes)
111+
{
112+
const char *buf = buffer;
113+
114+
for (int i = 0; i < nbytes; i++) {
115+
if (*(buf + i) == '\n') {
116+
_stdout_hook('\r');
117+
}
118+
_stdout_hook(*(buf + i));
119+
}
120+
return nbytes;
121+
}
122+
123+
#ifdef CONFIG_USERSPACE
124+
static inline int z_vrfy_zephyr_write_stdout(const void *buf, int nbytes)
125+
{
126+
K_OOPS(K_SYSCALL_MEMORY_READ(buf, nbytes));
127+
return z_impl_zephyr_write_stdout((const void *)buf, nbytes);
128+
}
129+
#include <zephyr/syscalls/zephyr_write_stdout_mrsh.c>
130+
#endif
131+
110132
size_t fwrite(const void *ZRESTRICT ptr, size_t size, size_t nitems,
111133
FILE *ZRESTRICT stream)
112134
{

lib/libc/picolibc/stdio.c

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@
77
#include "picolibc-hooks.h"
88
#include "stdio-bufio.h"
99

10-
static LIBC_DATA int (*_stdout_hook)(int);
10+
static int _stdout_hook_default(int c)
11+
{
12+
(void)(c); /* Prevent warning about unused argument */
13+
14+
return EOF;
15+
}
16+
17+
static LIBC_DATA int (*_stdout_hook)(int) = _stdout_hook_default;
1118

1219
int z_impl_zephyr_fputc(int a, FILE *out)
1320
{
14-
(*_stdout_hook)(a);
15-
return 0;
21+
return ((out == stdout) || (out == stderr)) ? _stdout_hook(a) : EOF;
1622
}
1723

1824
#ifdef CONFIG_USERSPACE
@@ -23,15 +29,14 @@ static inline int z_vrfy_zephyr_fputc(int c, FILE *stream)
2329
#include <zephyr/syscalls/zephyr_fputc_mrsh.c>
2430
#endif
2531

26-
#ifndef CONFIG_ZVFS
2732
static int picolibc_put(char a, FILE *f)
2833
{
29-
zephyr_fputc(a, f);
30-
return 0;
34+
return zephyr_fputc(a, f);
3135
}
3236

33-
static LIBC_DATA FILE __stdout = FDEV_SETUP_STREAM(picolibc_put, NULL, NULL, 0);
34-
static LIBC_DATA FILE __stdin = FDEV_SETUP_STREAM(NULL, NULL, NULL, 0);
37+
#ifndef CONFIG_ZVFS
38+
static LIBC_DATA FILE __stdout = FDEV_SETUP_STREAM(picolibc_put, NULL, NULL, _FDEV_SETUP_WRITE);
39+
static LIBC_DATA FILE __stdin = FDEV_SETUP_STREAM(NULL, NULL, NULL, _FDEV_SETUP_READ);
3540
#endif
3641

3742
#ifdef __strong_reference
@@ -48,27 +53,48 @@ STDIO_ALIAS(stderr);
4853

4954
void __stdout_hook_install(int (*hook)(int))
5055
{
56+
_stdout_hook = hook;
5157
#ifdef CONFIG_ZVFS
58+
stdout->put = picolibc_put;
59+
stdout->flags |= _FDEV_SETUP_WRITE;
60+
5261
struct __file_bufio *bp = (struct __file_bufio *)stdout;
5362

5463
bp->ptr = INT_TO_POINTER(1 /* STDOUT_FILENO */);
5564
bp->bflags |= _FDEV_SETUP_WRITE;
56-
#else
57-
__stdout.flags |= _FDEV_SETUP_WRITE;
5865
#endif
59-
60-
_stdout_hook = hook;
6166
}
6267

6368
void __stdin_hook_install(unsigned char (*hook)(void))
6469
{
70+
stdin->get = (int (*)(FILE *))hook;
6571
#ifdef CONFIG_ZVFS
72+
stdin->flags |= _FDEV_SETUP_READ;
6673
struct __file_bufio *bp = (struct __file_bufio *)stdin;
6774

6875
bp->bflags |= _FDEV_SETUP_READ;
6976
bp->ptr = INT_TO_POINTER(0 /* STDIN_FILENO */);
70-
#else
71-
__stdin.get = (int (*)(FILE *)) hook;
72-
__stdin.flags |= _FDEV_SETUP_READ;
7377
#endif
7478
}
79+
80+
int z_impl_zephyr_write_stdout(const void *buffer, int nbytes)
81+
{
82+
const char *buf = buffer;
83+
84+
for (int i = 0; i < nbytes; i++) {
85+
if (*(buf + i) == '\n') {
86+
_stdout_hook('\r');
87+
}
88+
_stdout_hook(*(buf + i));
89+
}
90+
return nbytes;
91+
}
92+
93+
#ifdef CONFIG_USERSPACE
94+
static inline int z_vrfy_zephyr_write_stdout(const void *buf, int nbytes)
95+
{
96+
K_OOPS(K_SYSCALL_MEMORY_READ(buf, nbytes));
97+
return z_impl_zephyr_write_stdout((const void *)buf, nbytes);
98+
}
99+
#include <zephyr/syscalls/zephyr_write_stdout_mrsh.c>
100+
#endif

lib/os/fdtable.c

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
#include <zephyr/sys/speculation.h>
2424
#include <zephyr/internal/syscall_handler.h>
2525
#include <zephyr/sys/atomic.h>
26+
#include <zephyr/device.h>
27+
#include <zephyr/console/console.h>
28+
#include <zephyr/console/tty.h>
29+
#include <zephyr/drivers/uart.h>
2630

2731
struct stat;
2832

@@ -42,27 +46,42 @@ static const struct fd_op_vtable stdinout_fd_op_vtable;
4246
BUILD_ASSERT(CONFIG_ZVFS_OPEN_MAX >= 3, "CONFIG_ZVFS_OPEN_MAX >= 3 for CONFIG_POSIX_DEVICE_IO");
4347
#endif /* defined(CONFIG_POSIX_DEVICE_IO) */
4448

49+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
50+
static struct tty_serial tty;
51+
static uint8_t tty_rxbuf[CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE];
52+
static uint8_t tty_txbuf[CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE];
53+
#endif
54+
4555
static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = {
4656
#if defined(CONFIG_POSIX_DEVICE_IO)
4757
/*
4858
* Predefine entries for stdin/stdout/stderr.
4959
*/
5060
{
5161
/* STDIN */
62+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
63+
.obj = &tty,
64+
#endif
5265
.vtable = &stdinout_fd_op_vtable,
5366
.refcount = ATOMIC_INIT(1),
5467
.lock = Z_MUTEX_INITIALIZER(fdtable[0].lock),
5568
.cond = Z_CONDVAR_INITIALIZER(fdtable[0].cond),
5669
},
5770
{
5871
/* STDOUT */
72+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
73+
.obj = &tty,
74+
#endif
5975
.vtable = &stdinout_fd_op_vtable,
6076
.refcount = ATOMIC_INIT(1),
6177
.lock = Z_MUTEX_INITIALIZER(fdtable[1].lock),
6278
.cond = Z_CONDVAR_INITIALIZER(fdtable[1].cond),
6379
},
6480
{
6581
/* STDERR */
82+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
83+
.obj = &tty,
84+
#endif
6685
.vtable = &stdinout_fd_op_vtable,
6786
.refcount = ATOMIC_INIT(1),
6887
.lock = Z_MUTEX_INITIALIZER(fdtable[2].lock),
@@ -378,7 +397,7 @@ static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t
378397
* Seekable file types should support pread() / pwrite() and per-fd offset passing.
379398
* Otherwise, it's a bug.
380399
*/
381-
errno = ENOTSUP;
400+
errno = ESPIPE;
382401
res = -1;
383402
goto unlock;
384403
}
@@ -593,16 +612,66 @@ int zvfs_ioctl(int fd, unsigned long request, va_list args)
593612
* fd operations for stdio/stdout/stderr
594613
*/
595614

615+
int z_impl_zephyr_read_stdin(char *buf, int nbytes);
596616
int z_impl_zephyr_write_stdout(const char *buf, int nbytes);
597617

618+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
619+
static int initialize_tty(struct tty_serial *obj)
620+
{
621+
static bool initialized;
622+
static int ret;
623+
624+
if (initialized) {
625+
return ret;
626+
}
627+
ret = tty_init(obj, DEVICE_DT_GET(DT_CHOSEN(zephyr_console)));
628+
initialized = true;
629+
if (ret) {
630+
errno = -ret;
631+
ret = -1;
632+
return ret;
633+
}
634+
tty_set_tx_buf(obj, tty_txbuf, sizeof(tty_txbuf));
635+
tty_set_rx_buf(obj, tty_rxbuf, sizeof(tty_rxbuf));
636+
return ret;
637+
}
638+
#endif
639+
598640
static ssize_t stdinout_read_vmeth(void *obj, void *buffer, size_t count)
599641
{
642+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
643+
int ret = initialize_tty(obj);
644+
645+
if (ret) {
646+
return ret;
647+
}
648+
ret = tty_read(obj, buffer, count);
649+
if (ret < -1) { /* return no less than -1 as per POSIX; errno is already set */
650+
return -1;
651+
}
652+
return ret;
653+
#elif defined(CONFIG_NEWLIB_LIBC)
654+
return z_impl_zephyr_read_stdin(buffer, count);
655+
#else
600656
return 0;
657+
#endif
601658
}
602659

603660
static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count)
604661
{
605-
#if defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_ARCMWDT_LIBC)
662+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
663+
int ret = initialize_tty(obj);
664+
665+
if (ret) {
666+
return ret;
667+
}
668+
ret = tty_write(obj, buffer, count);
669+
if (ret < -1) { /* return no less than -1 as per POSIX; errno is already set */
670+
return -1;
671+
}
672+
return ret;
673+
#elif defined(CONFIG_ARCMWDT_LIBC) || defined(CONFIG_MINIMAL_LIBC) || \
674+
defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_PICOLIBC)
606675
return z_impl_zephyr_write_stdout(buffer, count);
607676
#else
608677
return 0;
@@ -611,10 +680,69 @@ static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count)
611680

612681
static int stdinout_ioctl_vmeth(void *obj, unsigned int request, va_list args)
613682
{
614-
errno = EINVAL;
615-
return -1;
616-
}
683+
#if defined(CONFIG_POSIX_DEVICE_IO_STDIO_CONSOLE)
684+
struct tty_serial *cons = obj;
617685

686+
if (initialize_tty(cons)) {
687+
return -1;
688+
}
689+
#else
690+
request = 0xffff;
691+
#endif
692+
switch (request) {
693+
case ZFD_IOCTL_POLL_PREPARE: {
694+
__maybe_unused struct zvfs_pollfd *pfd = va_arg(args, struct zvfs_pollfd *);
695+
struct k_poll_event **pev = va_arg(args, struct k_poll_event **);
696+
struct k_poll_event *pev_end = va_arg(args, struct k_poll_event *);
697+
698+
if (*pev == pev_end) {
699+
return -ENOMEM;
700+
}
701+
#if CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE + CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE > 0
702+
if ((pfd->fd == 0) && (pfd->events & ZVFS_POLLIN)) {
703+
(*pev)->type = K_POLL_TYPE_SEM_AVAILABLE;
704+
(*pev)->state = K_POLL_STATE_NOT_READY;
705+
(*pev)->sem = &(cons->rx_sem);
706+
}
707+
if (((pfd->fd == 1) || (pfd->fd == 2)) && (pfd->events & ZVFS_POLLOUT)) {
708+
(*pev)->type = K_POLL_TYPE_SEM_AVAILABLE;
709+
(*pev)->state = K_POLL_STATE_NOT_READY;
710+
(*pev)->sem = &(cons->tx_sem);
711+
}
712+
#else
713+
(*pev)->type = K_POLL_TYPE_IGNORE;
714+
(*pev)->state = K_POLL_STATE_NOT_READY;
715+
(*pev)->obj = NULL;
716+
#endif
717+
(*pev)++;
718+
return 0;
719+
}
720+
case ZFD_IOCTL_POLL_UPDATE: {
721+
struct zvfs_pollfd *pfd = va_arg(args, struct zvfs_pollfd *);
722+
struct k_poll_event **pev = va_arg(args, struct k_poll_event **);
723+
724+
#if CONFIG_POSIX_DEVICE_IO_STDIN_BUFSIZE + CONFIG_POSIX_DEVICE_IO_STDOUT_BUFSIZE > 0
725+
if ((*pev)->state & K_POLL_STATE_SEM_AVAILABLE) {
726+
if ((pfd->fd == 0) && (pfd->events & ZVFS_POLLIN)) {
727+
pfd->revents |= ZVFS_POLLIN;
728+
}
729+
if (((pfd->fd == 1) || (pfd->fd == 2)) && (pfd->events & ZVFS_POLLOUT)) {
730+
pfd->revents |= ZVFS_POLLOUT;
731+
}
732+
}
733+
#else
734+
if (pfd->events & (ZVFS_POLLIN | ZVFS_POLLPRI | ZVFS_POLLOUT)) {
735+
pfd->revents |= ZVFS_POLLNVAL;
736+
}
737+
#endif
738+
(*pev)++;
739+
return 0;
740+
}
741+
default:
742+
errno = EINVAL;
743+
return -1;
744+
}
745+
}
618746

619747
static const struct fd_op_vtable stdinout_fd_op_vtable = {
620748
.read = stdinout_read_vmeth,

lib/posix/options/Kconfig.device_io

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,35 @@ config POSIX_DEVICE_IO
2020

2121
if POSIX_DEVICE_IO
2222

23+
config POSIX_DEVICE_IO_STDIO_CONSOLE
24+
bool "read() and write() to/from stdio use console subsystem"
25+
default y
26+
depends on CONSOLE_SUBSYS
27+
depends on !(CONSOLE_GETCHAR || CONSOLE_GETLINE)
28+
help
29+
Enable to use UART console whenever read() or write() is called
30+
on stdin, stdout or stderr.
31+
32+
if POSIX_DEVICE_IO_STDIO_CONSOLE
33+
34+
config POSIX_DEVICE_IO_STDIN_BUFSIZE
35+
int "read() buffer size for stdin"
36+
default 16
37+
help
38+
Size of internal buffer for read()-ing from stdin. Setting both this and
39+
POSIX_DEVICE_IO_STDOUT_BUFSIZE to 0 will replace interrupt-driven operation
40+
with busy-polling and stop poll(), select() and others from working with stdio.
41+
42+
config POSIX_DEVICE_IO_STDOUT_BUFSIZE
43+
int "write() buffer size for stdout"
44+
default 16
45+
help
46+
Size of internal buffer for write()-ing to stdout. Setting both this and
47+
POSIX_DEVICE_IO_STDIN_BUFSIZE to 0 will replace interrupt-driven operation
48+
with busy-polling and stop poll(), select() and others from working with stdio.
49+
50+
endif # POSIX_DEVICE_IO_STDIO_CONSOLE
51+
2352
# These options are intended to be used for compatibility with external POSIX
2453
# implementations such as those in Newlib or Picolibc.
2554

subsys/console/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# SPDX-License-Identifier: Apache-2.0
22

3-
zephyr_sources_ifdef(CONFIG_CONSOLE_GETCHAR tty.c getchar.c)
3+
zephyr_sources_ifdef(CONFIG_CONSOLE_SUBSYS tty.c)
4+
zephyr_sources_ifdef(CONFIG_CONSOLE_GETCHAR getchar.c)
45
zephyr_sources_ifdef(CONFIG_CONSOLE_GETLINE getline.c)

0 commit comments

Comments
 (0)