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

posix: implement fgetc() #68839

Closed
6 changes: 3 additions & 3 deletions doc/services/portability/posix/option_groups/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ POSIX_DEVICE_IO
feof(),
ferror(),
fflush(),
fgetc(),
fgetc(),yes (will fail with ``ENOSYS``:ref:`†<posix_undefined_behaviour>`)
Copy link
Member

Choose a reason for hiding this comment

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

Technically, this shouldn't fail, as long as there is an open FILE*. But what happens when we do an fopen()? What path would we even pass to fopen() to get a FILE*? Is stdin already available? It should probably be (stdin, stdout, and stderr should be opened before main() in ISO C, but that is part of the c runtime).

That's just ISO C.

I think we are putting the cart before the horse so to speak.

The issue is that I don't believe we support file based streams (yet) in the libc / os level. We only support reading / writing to standard targets by integer file descriptor.

We might need to modify fdtable.c to allow us to use FILE based streams as well.

This seems to be a requirement for POSIX_DEVICE_IO in any case.

fgets(),
fileno(),
fopen(),
Expand All @@ -233,8 +233,8 @@ POSIX_DEVICE_IO
freopen(),
fscanf(),
fwrite(),yes
getc(),
getchar(),
getc(),yes (will fail with ``ENOSYS``:ref:`†<posix_undefined_behaviour>`)
getchar(),yes (will fail with ``ENOSYS``:ref:`†<posix_undefined_behaviour>`)
gets(),
open(),yes
perror(),yes
Expand Down
19 changes: 19 additions & 0 deletions tests/posix/headers/src/stdio_h.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2024 Dawid Osuchowski
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "_common.h"
#include <stdio.h>

/**
* @brief Test existence and basic functionality of stdio.h
*
* @see stdio.h
*/
ZTEST(posix_headers, test_stdio_h)
{
zassert_not_null((void *)getchar, "getchar is null");
zassert_not_null((void *)getc, "getc is null");
zassert_not_null((void *)fgetc, "fgetc is null");
}
10 changes: 10 additions & 0 deletions tests/posix/stdio/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(posix_stdio)

FILE(GLOB app_sources src/*.c)
zephyr_include_directories(${ZEPHYR_BASE}/lib/posix)

target_sources(app PRIVATE ${app_sources})
25 changes: 25 additions & 0 deletions tests/posix/stdio/boards/qemu_riscv64.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2024 Dawid Osuchowski
*
* SPDX-License-Identifier: Apache-2.0
*/

/ {
chosen {
zephyr,console = &devmux0;
zephyr,shell_uart = &devmux0;
};

euart0: uart_emul0 {
compatible = "zephyr,uart-emul";
current-speed = <0>;
status = "okay";
};

devmux0: dev_mux_0 {
compatible = "zephyr,devmux";
devices = <&uart0 &euart0 &euart1>;
zephyr,mutable;
status = "okay";
};
};
10 changes: 10 additions & 0 deletions tests/posix/stdio/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CONFIG_ZTEST=y
CONFIG_POSIX_API=y
CONFIG_POSIX_FS=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_CONSOLE_SUBSYS=y
CONFIG_DEVICE_MUTABLE=y
CONFIG_FILE_SYSTEM=y
CONFIG_DEVMUX=y
CONFIG_UART_EMUL=y
97 changes: 97 additions & 0 deletions tests/posix/stdio/src/stdio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2024 Dawid Osuchowski
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/console/console.h>
#include <zephyr/drivers/misc/devmux/devmux.h>
#include <zephyr/drivers/serial/uart_emul.h>
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#include <stdio.h>
#include <errno.h>

#define BUF_SIZE 32

/* array of const struct device* */
#define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx))
static const struct device *devs[] = {
DT_FOREACH_PROP_ELEM_SEP(DT_NODELABEL(devmux0), devices, PHANDLE_TO_DEVICE, (,))};

/* array of names, e.g. "euart0" */
#define PHANDLE_TO_NAME(node_id, prop, idx) DT_NODE_FULL_NAME(DT_PHANDLE_BY_IDX(node_id, prop, idx))
static const char *const name[] = {
DT_FOREACH_PROP_ELEM_SEP(DT_NODELABEL(devmux0), devices, PHANDLE_TO_NAME, (,))};

/* array of greetings, e.g. "Hello, euart0!" */
#define PHANDLE_TO_TEXT(node_id, prop, idx) \
"Hello, " DT_NODE_FULL_NAME(DT_PHANDLE_BY_IDX(node_id, prop, idx)) "!"
static const char *const text[] = {
DT_FOREACH_PROP_ELEM_SEP(DT_NODELABEL(devmux0), devices, PHANDLE_TO_TEXT, (,))};

ZTEST(stdio, test_fgetc_one_char)
{
size_t normal_uart = DT_PROP(DT_NODELABEL(devmux0), selected);
struct device *const devmux_dev = DEVICE_DT_GET(DT_NODELABEL(devmux0));

/* for each uart_emul device */
for (size_t i = 0, j = 0, N = ARRAY_SIZE(devs); i < 2 * N; i++, j++, j %= N) {
if (j == normal_uart) {
/* skip testing non-emul uart */
continue;
}

int ret[4];
char buf[BUF_SIZE] = {0};

/* read text[j] from dev[j] */
ret[0] = devmux_select_set(devmux_dev, j);
// console_getline_init();
ret[1] = uart_emul_put_rx_data(devs[j], (uint8_t *)"H", sizeof(uint8_t));
// ret[3] = uart_emul_put_rx_data(devs[j], "\n", 1);
ret[3] = uart_emul_flush_rx_data(devs[j]);
int rett = fgetc(stdin);
// snprintf(buf, BUF_SIZE, "%s", console_getline());
ret[2] = devmux_select_set(devmux_dev, normal_uart);

zassert_ok(ret[0], "Failed to select devmux %zu", j);
zassert_ok(ret[2], "Switching back to selection %zu failed", normal_uart);

/* verify that text[j] was written to dev[j] */
TC_PRINT("read '%s' from %s\n", buf, name[j]);
TC_PRINT("flushed %d bytes\n", ret[3]);
zassert_equal(rett, 'H', "Expected return value %d, got %d", 'H', rett);


zassert_equal(ret[1], strlen(text[j]), "Only put %zu/%zu bytes of '%s'",
ret[1], strlen(text[j]), text[j]);
zassert_equal(0, strcmp(text[j], buf), "Strings '%s' and '%s' do not match",
text[j], buf);
}
}

static void *setup(void)
{
size_t selected = DT_PROP(DT_NODELABEL(devmux1), selected);
struct device *const devmux_dev = DEVICE_DT_GET(DT_NODELABEL(devmux1));

/* ensure that non-default initial selection via DT works */
zassert_equal(devmux_select_get(devmux_dev), selected);

return NULL;
}

static void before(void *arg)
{
struct device *const devmux_dev = DEVICE_DT_GET(DT_NODELABEL(devmux0));

zassert_ok(devmux_select_set(devmux_dev, 0));
zassert_ok(devmux_select_get(devmux_dev));

for (size_t i = 1; i < ARRAY_SIZE(devs); ++i) {
uart_emul_flush_tx_data(devs[i]);
}
}

ZTEST_SUITE(stdio, NULL, setup, before, NULL, NULL);
10 changes: 10 additions & 0 deletions tests/posix/stdio/testcase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
common:
tags: posix
platform_allow:
- qemu_riscv64
integration_platforms:
- qemu_riscv64
tests:
portability.posix.stdio:
extra_configs:
- CONFIG_USERSPACE=y
Loading