From patchwork Wed Feb 24 16:50:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Hrubis X-Patchwork-Id: 1443963 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=2001:1418:10:5::2; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Received: from picard.linux.it (picard.linux.it [IPv6:2001:1418:10:5::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Dm22X4B9Xz9sS8 for ; Thu, 25 Feb 2021 03:49:37 +1100 (AEDT) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id 6ABE43C4E27 for ; Wed, 24 Feb 2021 17:49:24 +0100 (CET) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-7.smtp.seeweb.it (in-7.smtp.seeweb.it [217.194.8.7]) by picard.linux.it (Postfix) with ESMTP id E0E5F3C4E27 for ; Wed, 24 Feb 2021 17:49:22 +0100 (CET) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by in-7.smtp.seeweb.it (Postfix) with ESMTPS id 47578200DF5 for ; Wed, 24 Feb 2021 17:49:22 +0100 (CET) Received: from relay2.suse.de (unknown [195.135.221.27]) by mx2.suse.de (Postfix) with ESMTP id AB1C5AF2C; Wed, 24 Feb 2021 16:49:21 +0000 (UTC) From: Cyril Hrubis To: ltp@lists.linux.it Date: Wed, 24 Feb 2021 17:50:41 +0100 Message-Id: <20210224165045.17738-2-chrubis@suse.cz> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210224165045.17738-1-chrubis@suse.cz> References: <20210224165045.17738-1-chrubis@suse.cz> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-7.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.2 required=7.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-7.smtp.seeweb.it Subject: [LTP] [PATCH v2 1/5] lib: tst_cmd: Make tst_cmd() usable for global paths X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Michal Simek , Carlos Hernandez , automated-testing@yoctoproject.org, Orson Zhai Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" We need to skip the check if the path to the binary starts with '/'. Signed-off-by: Cyril Hrubis Reviewed-by: Petr Vorel --- lib/tst_cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tst_cmd.c b/lib/tst_cmd.c index 7446249f9..b73978e20 100644 --- a/lib/tst_cmd.c +++ b/lib/tst_cmd.c @@ -58,7 +58,7 @@ int tst_cmd_fds_(void (cleanup_fn)(void), char path[PATH_MAX]; - if (tst_get_path(argv[0], path, sizeof(path))) { + if (argv[0][0] != '/' && tst_get_path(argv[0], path, sizeof(path))) { if (flags & TST_CMD_TCONF_ON_MISSING) tst_brkm(TCONF, cleanup_fn, "Couldn't find '%s' in $PATH at %s:%d", argv[0], __FILE__, __LINE__); From patchwork Wed Feb 24 16:50:42 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Hrubis X-Patchwork-Id: 1443964 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=2001:1418:10:5::2; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Received: from picard.linux.it (picard.linux.it [IPv6:2001:1418:10:5::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Dm22X6Nwtz9sVS for ; Thu, 25 Feb 2021 03:49:37 +1100 (AEDT) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id ABC7E3C5A68 for ; Wed, 24 Feb 2021 17:49:33 +0100 (CET) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-3.smtp.seeweb.it (in-3.smtp.seeweb.it [IPv6:2001:4b78:1:20::3]) by picard.linux.it (Postfix) with ESMTP id A11A33C4E27 for ; Wed, 24 Feb 2021 17:49:23 +0100 (CET) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by in-3.smtp.seeweb.it (Postfix) with ESMTPS id 9D9D51A0117F for ; Wed, 24 Feb 2021 17:49:22 +0100 (CET) Received: from relay2.suse.de (unknown [195.135.221.27]) by mx2.suse.de (Postfix) with ESMTP id 387ADAF33; Wed, 24 Feb 2021 16:49:22 +0000 (UTC) From: Cyril Hrubis To: ltp@lists.linux.it Date: Wed, 24 Feb 2021 17:50:42 +0100 Message-Id: <20210224165045.17738-3-chrubis@suse.cz> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210224165045.17738-1-chrubis@suse.cz> References: <20210224165045.17738-1-chrubis@suse.cz> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-3.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.2 required=7.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-3.smtp.seeweb.it Subject: [LTP] [PATCH v2 2/5] lib: Add minimalistic json parser X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Michal Simek , Carlos Hernandez , automated-testing@yoctoproject.org, Orson Zhai Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" Signed-off-by: Cyril Hrubis Reviewed-by: Petr Vorel Reviewed-by: Richard Palethorpe --- include/tst_json.h | 177 ++++++++++++ lib/tst_json.c | 679 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 856 insertions(+) create mode 100644 include/tst_json.h create mode 100644 lib/tst_json.c diff --git a/include/tst_json.h b/include/tst_json.h new file mode 100644 index 000000000..4b3669824 --- /dev/null +++ b/include/tst_json.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2021 Cyril Hrubis + */ + +#ifndef TST_JSON_H +#define TST_JSON_H + +#include + +#define TST_JSON_ERR_MAX 128 +#define TST_JSON_ID_MAX 64 + +enum tst_json_type { + TST_JSON_VOID = 0, + TST_JSON_INT, + TST_JSON_STR, + TST_JSON_OBJ, + TST_JSON_ARR, +}; + +struct tst_json_buf { + /** Pointer to a null terminated JSON string */ + const char *json; + /** A length of the JSON string */ + size_t len; + /** A current offset into the JSON string */ + size_t off; + /** An offset to the start of the last array or object */ + size_t sub_off; + + char err[TST_JSON_ERR_MAX]; + char buf[]; +}; + +struct tst_json_val { + enum tst_json_type type; + + /** An user supplied buffer and size to store a string values to. */ + char *buf; + size_t buf_size; + + /** An union to store the parsed value into. */ + union { + long val_int; + const char *val_str; + }; + + /** An ID for object values */ + char id[TST_JSON_ID_MAX]; +}; + +/* + * @brief Resets the parser. + * + * Resets the parse offset and clears errors. + * + * @buf An tst_json buffer + */ +static inline void tst_json_reset(struct tst_json_buf *buf) +{ + buf->off = 0; + buf->err[0] = 0; +} + +/* + * @brief Fills the buffer error. + * + * Once buffer error is set all parsing functions return immediatelly with type + * set to TST_JSON_VOID. + * + * @buf An tst_json buffer + * @fmt A printf like format string + * @... A printf like parameters + */ +void tst_json_err(struct tst_json_buf *buf, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); + +/* + * @brief Prints error into a file. + * + * The error takes into consideration the current offset in the buffer and + * prints a few preceding lines along with the exact position of the error. + * + * @f A file to print the error to. + * @buf An tst_json buffer. + */ +void tst_json_err_print(FILE *f, struct tst_json_buf *buf); + +/* + * @brief Returns true if error was encountered. + * + * @bfu An tst_json buffer. + * @return True if error was encountered false otherwise. + */ +static inline int tst_json_is_err(struct tst_json_buf *buf) +{ + return !!buf->err[0]; +} + +/* + * @brief Checks is result has valid type. + * + * @res An tst_json value. + * @return Zero if result is not valid, non-zero otherwise. + */ +static inline int tst_json_valid(struct tst_json_val *res) +{ + return !!res->type; +} + +/* + * @brief Returns the type of next element in buffer. + * + * @buf An tst_json buffer. + * @return A type of next element in the buffer. + */ +enum tst_json_type tst_json_next_type(struct tst_json_buf *buf); + +/* + * @brief Returns if first element in JSON is object or array. + * + * @buf An tst_json buffer. + * @return On success returns TST_JSON_OBJ or TST_JSON_ARR. On failure TST_JSON_VOID. + */ +enum tst_json_type tst_json_start(struct tst_json_buf *buf); + +/* + * @brief Starts parsing of an JSON object. + * + * @buf An tst_json buffer. + * @res An tst_json result. + */ +int tst_json_obj_first(struct tst_json_buf *buf, struct tst_json_val *res); +int tst_json_obj_next(struct tst_json_buf *buf, struct tst_json_val *res); + +#define TST_JSON_OBJ_FOREACH(buf, res) \ + for (tst_json_obj_first(buf, res); tst_json_valid(res); tst_json_obj_next(buf, res)) + +/* + * @brief Skips parsing of an JSON object. + * + * @buf An tst_json buffer. + * @return Zero on success, non-zero otherwise. + */ +int tst_json_obj_skip(struct tst_json_buf *buf); + +int tst_json_arr_first(struct tst_json_buf *buf, struct tst_json_val *res); +int tst_json_arr_next(struct tst_json_buf *buf, struct tst_json_val *res); + +#define TST_JSON_ARR_FOREACH(buf, res) \ + for (tst_json_arr_first(buf, res); tst_json_valid(res); tst_json_arr_next(buf, res)) + +/* + * @brief Skips parsing of an JSON array. + * + * @buf An tst_json buffer. + * @return Zero on success, non-zero otherwise. + */ +int tst_json_arr_skip(struct tst_json_buf *buf); + +/* + * @brief Loads a file into an tst_json buffer. + * + * @path A path to a file. + * @return An tst_json buffer or NULL in a case of a failure. + */ +struct tst_json_buf *tst_json_load(const char *path); + +/* + * @brief Frees an tst_json buffer. + * + * @buf An tst_json buffer allcated by tst_json_load() function. + */ +void tst_json_free(struct tst_json_buf *buf); + +#endif /* TST_JSON_H */ diff --git a/lib/tst_json.c b/lib/tst_json.c new file mode 100644 index 000000000..3a4cb9d0b --- /dev/null +++ b/lib/tst_json.c @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2021 Cyril Hrubis + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "tst_json.h" + +static inline int buf_empty(struct tst_json_buf *buf) +{ + return buf->off >= buf->len; +} + +static int eatws(struct tst_json_buf *buf) +{ + while (!buf_empty(buf)) { + switch (buf->json[buf->off]) { + case ' ': + case '\t': + case '\n': + case '\f': + break; + default: + goto ret; + } + + buf->off += 1; + } +ret: + return buf_empty(buf); +} + +static char getb(struct tst_json_buf *buf) +{ + if (buf_empty(buf)) + return 0; + + return buf->json[buf->off++]; +} + +static char peekb(struct tst_json_buf *buf) +{ + if (buf_empty(buf)) + return 0; + + return buf->json[buf->off]; +} + +static int eatb(struct tst_json_buf *buf, char ch) +{ + if (peekb(buf) != ch) + return 0; + + getb(buf); + return 1; +} + +static int hex2val(unsigned char b) +{ + switch (b) { + case '0' ... '9': + return b - '0'; + case 'a' ... 'f': + return b - 'a' + 10; + case 'A' ... 'F': + return b - 'A' + 10; + default: + return -1; + } +} + +static int32_t parse_ucode_cp(struct tst_json_buf *buf) +{ + int ret = 0, v, i; + + for (i = 0; i < 4; i++) { + if ((v = hex2val(getb(buf))) < 0) + goto err; + ret *= 16; + ret += v; + } + + return ret; +err: + tst_json_err(buf, "Expected four hexadecimal digits"); + return -1; +} + +static unsigned int utf8_bytes(uint32_t ucode_cp) +{ + if (ucode_cp < 0x0080) + return 1; + + if (ucode_cp < 0x0800) + return 2; + + if (ucode_cp < 0x10000) + return 3; + + return 4; +} + +static int to_utf8(uint32_t ucode_cp, char *buf) +{ + if (ucode_cp < 0x0080) { + buf[0] = ucode_cp & 0x00f7; + return 1; + } + + if (ucode_cp < 0x0800) { + buf[0] = 0xc0 | (0x1f & (ucode_cp>>6)); + buf[1] = 0x80 | (0x3f & ucode_cp); + return 2; + } + + if (ucode_cp < 0x10000) { + buf[0] = 0xe0 | (0x0f & (ucode_cp>>12)); + buf[1] = 0x80 | (0x3f & (ucode_cp>>6)); + buf[2] = 0x80 | (0x3f & ucode_cp); + return 3; + } + + buf[0] = 0xf0 | (0x07 & (ucode_cp>>18)); + buf[1] = 0x80 | (0x3f & (ucode_cp>>12)); + buf[2] = 0x80 | (0x3f & (ucode_cp>>6)); + buf[3] = 0x80 | (0x3f & ucode_cp); + return 4; +} + +static unsigned int parse_ucode_esc(struct tst_json_buf *buf, char *str, + size_t off, size_t len) +{ + int32_t ucode = parse_ucode_cp(buf); + + if (ucode < 0) + return 0; + + if (!str) + return ucode; + + if (utf8_bytes(ucode) + 1 >= len - off) { + tst_json_err(buf, "String buffer too short!"); + return 0; + } + + return to_utf8(ucode, str+off); +} + +static int copy_str(struct tst_json_buf *buf, char *str, size_t len) +{ + size_t pos = 0; + int esc = 0; + unsigned int l; + + eatb(buf, '"'); + + for (;;) { + if (buf_empty(buf)) { + tst_json_err(buf, "Unterminated string"); + return 1; + } + + if (!esc && eatb(buf, '"')) { + if (str) + str[pos] = 0; + return 0; + } + + char b = getb(buf); + + if (!esc && b == '\\') { + esc = 1; + continue; + } + + if (esc) { + switch (b) { + case '"': + case '\\': + case '/': + break; + case 'b': + b = '\b'; + break; + case 'f': + b = '\f'; + break; + case 'n': + b = '\n'; + break; + case 'r': + b = '\r'; + break; + case 't': + b = '\t'; + break; + case 'u': + if (!(l = parse_ucode_esc(buf, str, pos, len))) + return 1; + pos += l; + b = 0; + break; + default: + tst_json_err(buf, "Invalid escape \\%c", b); + return 1; + } + esc = 0; + } + + if (str && b) { + if (pos + 1 >= len) { + tst_json_err(buf, "String buffer too short!"); + return 1; + } + + str[pos++] = b; + } + } + + return 1; +} + +static int copy_id_str(struct tst_json_buf *buf, char *str, size_t len) +{ + size_t pos = 0; + + if (eatws(buf)) + goto err0; + + if (!eatb(buf, '"')) + goto err0; + + for (;;) { + if (buf_empty(buf)) { + tst_json_err(buf, "Unterminated ID string"); + return 1; + } + + if (eatb(buf, '"')) { + str[pos] = 0; + break; + } + + if (pos >= len-1) { + tst_json_err(buf, "ID string too long"); + return 1; + } + + str[pos++] = getb(buf); + } + + if (eatws(buf)) + goto err1; + + if (!eatb(buf, ':')) + goto err1; + + return 0; +err0: + tst_json_err(buf, "Expected ID string"); + return 1; +err1: + tst_json_err(buf, "Expected ':' after ID string"); + return 1; +} + +static int is_digit(char b) +{ + switch (b) { + case '0' ... '9': + return 1; + default: + return 0; + } +} + +static int get_number(struct tst_json_buf *buf, struct tst_json_val *res) +{ + long val = 0; + int neg = 0; + + if (peekb(buf) == '-') { + neg = 1; + getb(buf); + if (!is_digit(peekb(buf))) { + tst_json_err(buf, "Expected digits after '-'"); + return 1; + } + } + + while (is_digit(peekb(buf))) { + val *= 10; + val += getb(buf) - '0'; + //TODO: overflow? + } + + if (neg) + val = -val; + + res->val_int = val; + + return 0; +} + +int tst_json_obj_skip(struct tst_json_buf *buf) +{ + struct tst_json_val res = {}; + + TST_JSON_OBJ_FOREACH(buf, &res) { + switch (res.type) { + case TST_JSON_OBJ: + if (tst_json_obj_skip(buf)) + return 1; + break; + case TST_JSON_ARR: + if (tst_json_arr_skip(buf)) + return 1; + break; + default: + break; + } + } + + return 0; +} + +int tst_json_arr_skip(struct tst_json_buf *buf) +{ + struct tst_json_val res = {}; + + TST_JSON_ARR_FOREACH(buf, &res) { + switch (res.type) { + case TST_JSON_OBJ: + if (tst_json_obj_skip(buf)) + return 1; + break; + case TST_JSON_ARR: + if (tst_json_arr_skip(buf)) + return 1; + break; + default: + break; + } + } + + return 0; +} + +enum tst_json_type tst_json_next_type(struct tst_json_buf *buf) +{ + if (eatws(buf)) { + tst_json_err(buf, "Unexpected end"); + return TST_JSON_VOID; + } + + char b = peekb(buf); + + switch (b) { + case '{': + return TST_JSON_OBJ; + case '[': + return TST_JSON_ARR; + case '"': + return TST_JSON_STR; + case '-': + case '0' ... '9': + return TST_JSON_INT; + default: + tst_json_err(buf, "Expected object, array, number or string"); + return TST_JSON_VOID; + } +} + +enum tst_json_type tst_json_start(struct tst_json_buf *buf) +{ + enum tst_json_type type = tst_json_next_type(buf); + + switch (type) { + case TST_JSON_ARR: + case TST_JSON_OBJ: + case TST_JSON_VOID: + break; + case TST_JSON_INT: + case TST_JSON_STR: + tst_json_err(buf, "JSON can start only with array or object"); + type = TST_JSON_VOID; + break; + } + + return type; +} + +static int get_value(struct tst_json_buf *buf, struct tst_json_val *res) +{ + res->type = tst_json_next_type(buf); + + switch (res->type) { + case TST_JSON_STR: + if (copy_str(buf, res->buf, res->buf_size)) { + res->type = TST_JSON_VOID; + return 0; + } + res->val_str = res->buf; + return 1; + case TST_JSON_INT: + return !get_number(buf, res); + case TST_JSON_VOID: + //tst_json_err(buf, "Unexpected character"); + return 0; + case TST_JSON_ARR: + case TST_JSON_OBJ: + buf->sub_off = buf->off; + return 1; + } + + return 1; +} + +static int pre_next(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (!eatb(buf, ',')) { + tst_json_err(buf, "Expected ','"); + res->type = TST_JSON_VOID; + return 1; + } + + if (eatws(buf)) { + tst_json_err(buf, "Unexpected end"); + res->type = TST_JSON_VOID; + return 1; + } + + return 0; +} + +static int check_end(struct tst_json_buf *buf, struct tst_json_val *res, char b) +{ + if (eatws(buf)) { + tst_json_err(buf, "Unexpected end"); + return 1; + } + + if (eatb(buf, b)) { + res->type = TST_JSON_VOID; + return 1; + } + + return 0; +} + +static int obj_next(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (copy_id_str(buf, res->id, sizeof(res->id))) + return 0; + + return get_value(buf, res); +} + +static int check_err(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (tst_json_is_err(buf)) { + res->type = TST_JSON_VOID; + return 1; + } + + return 0; +} + +int tst_json_obj_next(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (check_err(buf, res)) + return 0; + + if (check_end(buf, res, '}')) + return 0; + + if (pre_next(buf, res)) + return 0; + + return obj_next(buf, res); +} + +static int any_first(struct tst_json_buf *buf, char b) +{ + if (eatws(buf)) { + tst_json_err(buf, "Unexpected end"); + return 1; + } + + if (!eatb(buf, b)) { + tst_json_err(buf, "Expected '%c'", b); + return 1; + } + + return 0; +} + +int tst_json_obj_first(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (check_err(buf, res)) + return 0; + + if (any_first(buf, '{')) + return 1; + + if (check_end(buf, res, '}')) + return 0; + + return obj_next(buf, res); +} + +static int arr_next(struct tst_json_buf *buf, struct tst_json_val *res) +{ + return get_value(buf, res); +} + +int tst_json_arr_first(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (check_err(buf, res)) + return 0; + + if (any_first(buf, '[')) + return 1; + + if (check_end(buf, res, ']')) + return 0; + + return arr_next(buf, res); +} + +int tst_json_arr_next(struct tst_json_buf *buf, struct tst_json_val *res) +{ + if (check_err(buf, res)) + return 0; + + if (check_end(buf, res, ']')) + return 0; + + if (pre_next(buf, res)) + return 0; + + return arr_next(buf, res); +} + +void tst_json_err(struct tst_json_buf *buf, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vsnprintf(buf->err, TST_JSON_ERR_MAX, fmt, va); + va_end(va); +} + +static void print_line(FILE *f, const char *line) +{ + while (*line && *line != '\n') + fputc(*(line++), f); +} + +static void print_spaces(FILE *f, size_t count) +{ + while (count--) + fputc(' ', f); +} + +static void print_spaceline(FILE *f, const char *line, size_t count) +{ + size_t i; + + for (i = 0; i < count; i++) + fputc(line[i] == '\t' ? '\t' : ' ', f); +} + +#define ERR_LINES 10 + +#define MIN(A, B) ((A < B) ? (A) : (B)) + +void tst_json_err_print(FILE *f, struct tst_json_buf *buf) +{ + ssize_t i; + const char *lines[ERR_LINES] = {}; + size_t cur_line = 0; + size_t cur_off = 0; + size_t last_off = buf->off; + + for (;;) { + lines[(cur_line++) % ERR_LINES] = buf->json + cur_off; + + while (cur_off < buf->len && buf->json[cur_off] != '\n') + cur_off++; + + if (cur_off >= buf->off) + break; + + cur_off++; + last_off = buf->off - cur_off; + } + + fprintf(f, "Parse error at line %zu\n\n", cur_line); + + size_t idx = 0; + + for (i = MIN(ERR_LINES, cur_line); i > 0; i--) { + idx = (cur_line - i) % ERR_LINES; + fprintf(f, "%03zu: ", cur_line - i + 1); + print_line(f, lines[idx]); + fputc('\n', f); + } + + print_spaces(f, 5); + print_spaceline(f, lines[idx], last_off); + fprintf(f, "^\n"); + fprintf(f, "%s\n", buf->err); +} + +struct tst_json_buf *tst_json_load(const char *path) +{ + int fd = open(path, O_RDONLY); + struct tst_json_buf *ret; + ssize_t res; + off_t len, off = 0; + + if (fd < 0) + return NULL; + + len = lseek(fd, 0, SEEK_END); + if (len == (off_t)-1) { + fprintf(stderr, "lseek() failed\n"); + goto err0; + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { + fprintf(stderr, "lseek() failed\n"); + goto err0; + } + + ret = malloc(sizeof(struct tst_json_buf) + len + 1); + if (!ret) { + fprintf(stderr, "malloc() failed\n"); + goto err0; + } + + memset(ret, 0, sizeof(*ret)); + + ret->buf[len] = 0; + ret->len = len; + ret->json = ret->buf; + + while (off < len) { + res = read(fd, ret->buf + off, len - off); + if (res < 0) { + fprintf(stderr, "read() failed\n"); + goto err1; + } + + off += res; + } + + close(fd); + + return ret; +err1: + free(ret); +err0: + close(fd); + return NULL; +} + +void tst_json_free(struct tst_json_buf *buf) +{ + free(buf); +} From patchwork Wed Feb 24 16:50:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Hrubis X-Patchwork-Id: 1443966 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=213.254.12.146; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Received: from picard.linux.it (picard.linux.it [213.254.12.146]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Dm22p2433z9sS8 for ; Thu, 25 Feb 2021 03:49:54 +1100 (AEDT) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id 134B63C5A85 for ; Wed, 24 Feb 2021 17:49:51 +0100 (CET) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-2.smtp.seeweb.it (in-2.smtp.seeweb.it [IPv6:2001:4b78:1:20::2]) by picard.linux.it (Postfix) with ESMTP id 0538B3C5A66 for ; Wed, 24 Feb 2021 17:49:24 +0100 (CET) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by in-2.smtp.seeweb.it (Postfix) with ESMTPS id 7C3E2601A75 for ; Wed, 24 Feb 2021 17:49:23 +0100 (CET) Received: from relay2.suse.de (unknown [195.135.221.27]) by mx2.suse.de (Postfix) with ESMTP id CDC51AFAF; Wed, 24 Feb 2021 16:49:22 +0000 (UTC) From: Cyril Hrubis To: ltp@lists.linux.it Date: Wed, 24 Feb 2021 17:50:43 +0100 Message-Id: <20210224165045.17738-4-chrubis@suse.cz> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210224165045.17738-1-chrubis@suse.cz> References: <20210224165045.17738-1-chrubis@suse.cz> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-2.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.2 required=7.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-2.smtp.seeweb.it Subject: [LTP] [PATCH v2 3/5] lib: Add hardware discovery code X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Michal Simek , Carlos Hernandez , automated-testing@yoctoproject.org, Orson Zhai Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" See lib/tst_hardware.h for details. Signed-off-by: Cyril Hrubis --- include/tst_hwconf.h | 13 +++ include/tst_test.h | 3 + lib/tst_hardware.c | 218 +++++++++++++++++++++++++++++++++++++++++++ lib/tst_hardware.h | 83 ++++++++++++++++ lib/tst_test.c | 30 ++++++ 5 files changed, 347 insertions(+) create mode 100644 include/tst_hwconf.h create mode 100644 lib/tst_hardware.c create mode 100644 lib/tst_hardware.h diff --git a/include/tst_hwconf.h b/include/tst_hwconf.h new file mode 100644 index 000000000..5b0b95441 --- /dev/null +++ b/include/tst_hwconf.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2021 Cyril Hrubis + */ + +#ifndef TST_HWCONF_H__ +#define TST_HWCONF_H__ + +#include "tst_json.h" + +struct tst_json_buf *tst_hwconf_get(void); + +#endif /* TST_HWCONF_H__ */ diff --git a/include/tst_test.h b/include/tst_test.h index 1fbebe752..bd6de601e 100644 --- a/include/tst_test.h +++ b/include/tst_test.h @@ -230,6 +230,9 @@ struct tst_test { /* NULL terminated array of needed kernel drivers */ const char * const *needs_drivers; + /* Unique hardware identifier */ + const char *needs_hardware; + /* * NULL terminated array of (/proc, /sys) files to save * before setup and restore after cleanup diff --git a/lib/tst_hardware.c b/lib/tst_hardware.c new file mode 100644 index 000000000..c67c9b5c6 --- /dev/null +++ b/lib/tst_hardware.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2021 Cyril Hrubis + */ + +#define _GNU_SOURCE +#include +#include + +#include "tst_hardware.h" +#include "tst_hwconf.h" + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +static struct tst_hwconf *cur_conf; +static char *reconfigure_path; +static struct tst_hwconf *conf_root; +static unsigned int conf_cnt; + +static int new_hwconf(char *buf, size_t start_off, size_t end_off) +{ + struct tst_hwconf *conf = malloc(sizeof(struct tst_hwconf)); + + if (!conf) { + tst_res(TWARN, "malloc() failed!"); + return 0; + } + + memset(conf, 0, sizeof(*conf)); + + conf->json.json = buf + start_off; + conf->json.len = end_off - start_off; + + struct tst_json_val val = { + .buf = conf->uid, + .buf_size = sizeof(conf->uid) + }; + + TST_JSON_OBJ_FOREACH(&conf->json, &val) { + switch (val.type) { + case TST_JSON_STR: + if (!strcmp(val.id, "uid")) + goto done; + break; + case TST_JSON_ARR: + tst_json_arr_skip(&conf->json); + break; + case TST_JSON_OBJ: + tst_json_obj_skip(&conf->json); + break; + default: + break; + } + } + + free(conf); + + return 1; +done: + tst_json_reset(&conf->json); + + if (!cur_conf) + conf_root = conf; + else + cur_conf->next = conf; + + cur_conf = conf; + conf_cnt++; + + tst_res(TINFO, "Added hardware configuration UID='%s'", conf->uid); + + return 0; +} + +static void parse_devices(struct tst_json_buf *json) +{ + struct tst_json_val val = {}; + size_t start_off; + + TST_JSON_ARR_FOREACH(json, &val) { + switch (val.type) { + case TST_JSON_OBJ: + start_off = json->sub_off; + tst_json_obj_skip(json); + if (new_hwconf(json->buf, start_off, json->off)) + tst_json_err(json, "Missing 'uid' in hwconf entry!"); + break; + case TST_JSON_ARR: + case TST_JSON_INT: + case TST_JSON_STR: + tst_json_err(json, "Invalid record in hwconf list!"); + break; + case TST_JSON_VOID: + break; + } + } +} + +static void save_reconfigure(const char *reconfigure, const char *ltproot) +{ + if (reconfigure[0] == '/') { + reconfigure_path = strdup(reconfigure); + if (!reconfigure_path) + tst_brk(TBROK, "strdup() failed"); + } else { + if (asprintf(&reconfigure_path, "%s/%s", ltproot, reconfigure) < 0) + tst_brk(TBROK, "asprintf() failed"); + } +} + +unsigned int tst_hwlist_cnt(void) +{ + return conf_cnt; +} + +static void reconfigure(void) +{ + if (!reconfigure_path) + return; + + if (!cur_conf) + return; + + const char *const argv[] = {reconfigure_path, cur_conf->uid, NULL}; + + tst_res(TINFO, "Running reconfigure '%s %s'", reconfigure_path, cur_conf->uid); + + tst_cmd(argv, NULL, NULL, TST_CMD_TCONF_ON_MISSING); +} + +void tst_hwlist_reset(void) +{ + cur_conf = conf_root; + + reconfigure(); +} + +void tst_hwlist_next(void) +{ + cur_conf = cur_conf->next; + + reconfigure(); +} + +struct tst_json_buf *tst_hwconf_get(void) +{ + if (!cur_conf) + return NULL; + + return &cur_conf->json; +} + +unsigned int tst_hwlist_discover(const char *hardware_uid) +{ + const char *ltproot = getenv("LTPROOT"); + const char *hardware_discovery = getenv("HARDWARE_DISCOVERY"); + char buf[2048]; + + if (!hardware_discovery) { + if (!ltproot) { + tst_res(TCONF, "No LTPROOT nor HARDWARE_DISCOVERY set!"); + return 0; + } + + snprintf(buf, sizeof(buf), "%s/hardware-discovery.sh", ltproot); + + hardware_discovery = buf; + } + + const char *const argv[] = {hardware_discovery, hardware_uid, NULL}; + + tst_res(TINFO, "Executing '%s %s'", hardware_discovery, hardware_uid); + + //TODO: read the data from a pipe instead + unlink("/tmp/hwlist.json"); + tst_cmd(argv, "/tmp/hwlist.json", NULL, TST_CMD_TCONF_ON_MISSING); + + struct tst_json_buf *json = tst_json_load("/tmp/hwlist.json"); + + if (!json) + tst_brk(TBROK, "Failed to load JSON hardware description"); + + if (tst_json_start(json) != TST_JSON_OBJ) + tst_brk(TBROK, "JSON hardware description must start with object!"); + + struct tst_json_val val = {.buf = buf, .buf_size = sizeof(buf)}; + + TST_JSON_OBJ_FOREACH(json, &val) { + switch (val.type) { + case TST_JSON_STR: + if (!strcmp(val.id, "reconfigure")) + save_reconfigure(val.val_str, ltproot); + else + tst_json_err(json, "Invalid object attr id='%s'", val.id); + break; + case TST_JSON_INT: + case TST_JSON_OBJ: + tst_json_err(json, "Invalid object attr id='%s'", val.id); + break; + case TST_JSON_ARR: + if (!strcmp(val.id, "hwconfs")) + parse_devices(json); + else + tst_json_err(json, "Invalid array attr id='%s'", val.id); + break; + case TST_JSON_VOID: + break; + } + } + + if (tst_json_is_err(json)) { + tst_json_err_print(stderr, json); + tst_brk(TBROK, "Failed to parse JSON hardware description"); + } + + return conf_cnt; +} diff --git a/lib/tst_hardware.h b/lib/tst_hardware.h new file mode 100644 index 000000000..9abf5d648 --- /dev/null +++ b/lib/tst_hardware.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2021 Cyril Hrubis + */ + +/* + * Hardware discovery code. + * + * Each hardware type is uniquely identified by an hardware unique id. Test + * that needs a particular hardware sets the .needs_hardware field in the + * tst_test structure which is then passed to the discovery code. + * + * The discovery launches a script/binary that returns a JSON in a format: + * + * { + * "reconfigure": "/path/to/reconfigure/script", + * "hwconfs": [ + * { + * "uid": "device_unique_id01", + * ... + * }, + * { + * "uid": "device_unique_id02", + * ... + * } + * ] + * } + * + * The optional reconfigure script could be used to set up the hardware, e.g. + * connect different serial ports together with relays, the only parameter + * it takes is the device uid. E.g. to set up the testing environment for the + * first device in the list above LTP will execute command: + * '/path/to/reconfigure/script "device_unique_id01"' etc. + * + * The second parameter in the JSON is "hwconfs" array that describes all + * available hardware configurations. These objects, apart from "uid" are not + * interpreted by the test libary but rather passed down to the test one object + * per test iteration. + */ + +#ifndef TST_HARDWARE_H__ +#define TST_HARDWARE_H__ + +#include "tst_json.h" + +struct tst_hwconf { + struct tst_hwconf *next; + /* unique id for the hardware configuration */ + char uid[128]; + /* buffer with the JSON description */ + struct tst_json_buf json; +}; + +/* + * Discovers a hardware based on hardware_uid. + * + * Exits the test with TBROK on error and with TCONF if no hardware was discovered. + */ +unsigned int tst_hwlist_discover(const char *hardware_uid); + +/* + * Returns number of parsed entries. + */ +unsigned int tst_hwlist_cnt(void); + +/* + * Resets current tst_hwconf to point to the first hardware entry. + */ +void tst_hwlist_reset(void); + +/* + * Allows to loop over all entries in the hareware list. + * + * If needed calls the reconfigure script. + */ +void tst_hwlist_next(void); + +/* + * Free the hwlist. + */ +void tst_hwlist_free(void); + +#endif /* TST_HARDWARE_H__ */ diff --git a/lib/tst_test.c b/lib/tst_test.c index 6bbee030b..3883ce227 100644 --- a/lib/tst_test.c +++ b/lib/tst_test.c @@ -27,6 +27,7 @@ #include "tst_wallclock.h" #include "tst_sys_conf.h" #include "tst_kconfig.h" +#include "tst_hardware.h" #include "old_resource.h" #include "old_device.h" @@ -499,6 +500,14 @@ static void print_test_tags(void) printf("\n"); } +static void print_test_hardware(void) +{ + if (!tst_test->needs_hardware) + return; + + printf("\nNeeded hardware\n--------------\n%s\n\n", tst_test->needs_hardware); +} + static void check_option_collision(void) { unsigned int i, j; @@ -578,6 +587,7 @@ static void parse_opts(int argc, char *argv[]) case 'h': print_help(); print_test_tags(); + print_test_hardware(); exit(0); case 'i': iterations = atoi(optarg); @@ -942,6 +952,18 @@ static void do_setup(int argc, char *argv[]) tst_brk(TCONF, "%s driver not available", name); } + if (tst_test->needs_hardware) { + unsigned int cnt = tst_hwlist_discover(tst_test->needs_hardware); + + if (!cnt) { + tst_brk(TCONF, "No hardware '%s' discovered", + tst_test->needs_hardware); + } + + tst_res(TINFO, "Found %u hardware configurations for '%s'", + cnt, tst_test->needs_hardware); + } + if (tst_test->format_device) tst_test->needs_device = 1; @@ -1395,6 +1417,11 @@ void tst_run_tcases(int argc, char *argv[], struct tst_test *self) if (tst_test->test_variants) test_variants = tst_test->test_variants; + if (tst_test->needs_hardware) { + test_variants = tst_hwlist_cnt(); + tst_hwlist_reset(); + } + for (tst_variant = 0; tst_variant < test_variants; tst_variant++) { if (tst_test->all_filesystems) ret |= run_tcases_per_fs(); @@ -1403,6 +1430,9 @@ void tst_run_tcases(int argc, char *argv[], struct tst_test *self) if (ret & ~(TCONF)) goto exit; + + if (tst_test->needs_hardware) + tst_hwlist_next(); } exit: From patchwork Wed Feb 24 16:50:44 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Hrubis X-Patchwork-Id: 1443971 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=213.254.12.146; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Received: from picard.linux.it (picard.linux.it [213.254.12.146]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Dm2330nMTz9sTD for ; Thu, 25 Feb 2021 03:50:06 +1100 (AEDT) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id 712243C5A9F for ; Wed, 24 Feb 2021 17:50:04 +0100 (CET) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-2.smtp.seeweb.it (in-2.smtp.seeweb.it [IPv6:2001:4b78:1:20::2]) by picard.linux.it (Postfix) with ESMTP id B95A13C5A6A for ; Wed, 24 Feb 2021 17:49:25 +0100 (CET) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by in-2.smtp.seeweb.it (Postfix) with ESMTPS id 78880601A75 for ; Wed, 24 Feb 2021 17:49:25 +0100 (CET) Received: from relay2.suse.de (unknown [195.135.221.27]) by mx2.suse.de (Postfix) with ESMTP id 5FD9AAF9C; Wed, 24 Feb 2021 16:49:24 +0000 (UTC) From: Cyril Hrubis To: ltp@lists.linux.it Date: Wed, 24 Feb 2021 17:50:44 +0100 Message-Id: <20210224165045.17738-5-chrubis@suse.cz> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210224165045.17738-1-chrubis@suse.cz> References: <20210224165045.17738-1-chrubis@suse.cz> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-2.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.2 required=7.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-2.smtp.seeweb.it Subject: [LTP] [PATCH v2 4/5] Sample hardware discovery and reconfigure scripts X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Michal Simek , Carlos Hernandez , automated-testing@yoctoproject.org, Orson Zhai Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" This is not going to be part of the final patchset. Signed-off-by: Cyril Hrubis --- hardware-discovery.sh | 36 ++++++++++++++++++++++++++++++++++++ hardware-reconfigure.sh | 3 +++ 2 files changed, 39 insertions(+) create mode 100755 hardware-discovery.sh create mode 100755 hardware-reconfigure.sh diff --git a/hardware-discovery.sh b/hardware-discovery.sh new file mode 100755 index 000000000..2d1eeefaa --- /dev/null +++ b/hardware-discovery.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +if [ "$1" = "UART-loopback" ]; then + echo '{' + echo ' "reconfigure": "hardware-reconfigure.sh",' + echo ' "hwconfs": [' + echo ' {' + echo ' "uid": "ttyUSB0-ttyUSB0-01",' + echo ' "tx": "/dev/ttyUSB0",' + echo ' "rx": "/dev/ttyUSB0",' + echo ' "hwflow": 0,' + echo ' "baud_rates": [' + echo ' 4800,' + echo ' 9600,' + echo ' 19200' + echo ' ]' + echo ' },' + echo ' {' + echo ' "uid": "ttyUSB0-ttyUSB0-02",' + echo ' "tx": "/dev/ttyUSB0",' + echo ' "rx": "/dev/ttyUSB0",' + echo ' "hwflow": 1,' + echo ' "baud_rates": [' + echo ' 4800,' + echo ' 9600,' + echo ' 19200' + echo ' ]' + echo ' }' + echo ' ]' + echo '}' + + exit 0 +fi + +echo '{}' +exit 0 diff --git a/hardware-reconfigure.sh b/hardware-reconfigure.sh new file mode 100755 index 000000000..079f6588a --- /dev/null +++ b/hardware-reconfigure.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Reconfigure '$@'" From patchwork Wed Feb 24 16:50:45 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Hrubis X-Patchwork-Id: 1443972 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=213.254.12.146; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Received: from picard.linux.it (picard.linux.it [213.254.12.146]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Dm23C5rM1z9sS8 for ; Thu, 25 Feb 2021 03:50:15 +1100 (AEDT) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id 52A353C5A65 for ; Wed, 24 Feb 2021 17:50:13 +0100 (CET) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-7.smtp.seeweb.it (in-7.smtp.seeweb.it [217.194.8.7]) by picard.linux.it (Postfix) with ESMTP id 55C233C5A87 for ; Wed, 24 Feb 2021 17:49:26 +0100 (CET) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by in-7.smtp.seeweb.it (Postfix) with ESMTPS id 78EF4200DFA for ; Wed, 24 Feb 2021 17:49:25 +0100 (CET) Received: from relay2.suse.de (unknown [195.135.221.27]) by mx2.suse.de (Postfix) with ESMTP id 72AD9AFAC; Wed, 24 Feb 2021 16:49:24 +0000 (UTC) From: Cyril Hrubis To: ltp@lists.linux.it Date: Wed, 24 Feb 2021 17:50:45 +0100 Message-Id: <20210224165045.17738-6-chrubis@suse.cz> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210224165045.17738-1-chrubis@suse.cz> References: <20210224165045.17738-1-chrubis@suse.cz> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-7.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.2 required=7.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-7.smtp.seeweb.it Subject: [LTP] [PATCH v2 5/5] testcases: uart01: Add. X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Michal Simek , Carlos Hernandez , automated-testing@yoctoproject.org, Orson Zhai Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" Execute from the git checkout as: LTPROOT=~/foo/bar/ltp/ HARDWARE_DISCOVERY=${LTPROOT}hardware-discovery.sh ./uart01 -s 10240 Signed-off-by: Cyril Hrubis --- runtest/device_drivers | 2 + testcases/kernel/device-drivers/Makefile | 1 + .../kernel/device-drivers/uart/.gitignore | 1 + testcases/kernel/device-drivers/uart/Makefile | 3 + testcases/kernel/device-drivers/uart/uart01.c | 620 ++++++++++++++++++ 5 files changed, 627 insertions(+) create mode 100644 runtest/device_drivers create mode 100644 testcases/kernel/device-drivers/uart/.gitignore create mode 100644 testcases/kernel/device-drivers/uart/Makefile create mode 100644 testcases/kernel/device-drivers/uart/uart01.c diff --git a/runtest/device_drivers b/runtest/device_drivers new file mode 100644 index 000000000..9295c7a90 --- /dev/null +++ b/runtest/device_drivers @@ -0,0 +1,2 @@ +uart01 +uart01 -s 10240 diff --git a/testcases/kernel/device-drivers/Makefile b/testcases/kernel/device-drivers/Makefile index 55e0d25a0..a214f211b 100644 --- a/testcases/kernel/device-drivers/Makefile +++ b/testcases/kernel/device-drivers/Makefile @@ -27,6 +27,7 @@ SUBDIRS := acpi \ rtc \ tbio \ uaccess \ + uart \ zram include $(top_srcdir)/include/mk/generic_trunk_target.mk diff --git a/testcases/kernel/device-drivers/uart/.gitignore b/testcases/kernel/device-drivers/uart/.gitignore new file mode 100644 index 000000000..9333e8db9 --- /dev/null +++ b/testcases/kernel/device-drivers/uart/.gitignore @@ -0,0 +1 @@ +uart01 diff --git a/testcases/kernel/device-drivers/uart/Makefile b/testcases/kernel/device-drivers/uart/Makefile new file mode 100644 index 000000000..939e433d0 --- /dev/null +++ b/testcases/kernel/device-drivers/uart/Makefile @@ -0,0 +1,3 @@ +top_srcdir ?= ../../../.. +include $(top_srcdir)/include/mk/testcases.mk +include $(top_srcdir)/include/mk/generic_leaf_target.mk diff --git a/testcases/kernel/device-drivers/uart/uart01.c b/testcases/kernel/device-drivers/uart/uart01.c new file mode 100644 index 000000000..3f244fe8f --- /dev/null +++ b/testcases/kernel/device-drivers/uart/uart01.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + Copyright (c) 2014 Sebastian Andrzej Siewior + Copyright (c) 2020-2021 Cyril Hrubis + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tst_test.h" +#include "tst_hwconf.h" + +static const char hex_asc[] = "0123456789abcdef"; +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +static struct uart_params { + int hwflow; + char rx[128]; + char tx[128]; + unsigned int baud_rates[20]; +} uart_params = { + .baud_rates = {9600}, +}; + +struct g_opt { + char *uart_dev; + char *file_trans; + int baud_rate; + unsigned int loops; + unsigned char hwflow; + unsigned char do_termios; +#define MODE_TX_ONLY (1 << 0) +#define MODE_RX_ONLY (1 << 1) +#define MODE_DUPLEX (MODE_TX_ONLY | MODE_RX_ONLY) + unsigned int mode; + unsigned char *cmp_buff; +}; + +static int vscnprintf(char *buf, size_t size, const char *fmt, va_list args) +{ + int i; + + i = vsnprintf(buf, size, fmt, args); + + if (i < (int)size) + return i; + if (size != 0) + return size - 1; + return 0; +} + +static int scnprintf(char *buf, size_t size, const char *fmt, ...) +{ + va_list args; + int i; + + va_start(args, fmt); + i = vscnprintf(buf, size, fmt, args); + va_end(args); + + return i; +} + + +static void hex_dump_to_buffer(const void *buf, int len, int rowsize, + int groupsize, char *linebuf, int linebuflen, int ascii) +{ + const uint8_t *ptr = buf; + uint8_t ch; + int j, lx = 0; + int ascii_column; + + if (rowsize != 16 && rowsize != 32) + rowsize = 16; + + if (!len) + goto nil; + if (len > rowsize) /* limit to one line at a time */ + len = rowsize; + if ((len % groupsize) != 0) /* no mixed size output */ + groupsize = 1; + + switch (groupsize) { + case 8: { + const uint64_t *ptr8 = buf; + int ngroups = len / groupsize; + + for (j = 0; j < ngroups; j++) + lx += scnprintf(linebuf + lx, linebuflen - lx, + "%s%16.16llx", j ? " " : "", + (unsigned long long)*(ptr8 + j)); + ascii_column = 17 * ngroups + 2; + break; + } + + case 4: { + const uint32_t *ptr4 = buf; + int ngroups = len / groupsize; + + for (j = 0; j < ngroups; j++) + lx += scnprintf(linebuf + lx, linebuflen - lx, + "%s%8.8x", j ? " " : "", *(ptr4 + j)); + ascii_column = 9 * ngroups + 2; + break; + } + + case 2: { + const uint16_t *ptr2 = buf; + int ngroups = len / groupsize; + + for (j = 0; j < ngroups; j++) + lx += scnprintf(linebuf + lx, linebuflen - lx, + "%s%4.4x", j ? " " : "", *(ptr2 + j)); + ascii_column = 5 * ngroups + 2; + break; + } + + default: + for (j = 0; (j < len) && (lx + 3) <= linebuflen; j++) { + ch = ptr[j]; + linebuf[lx++] = hex_asc_hi(ch); + linebuf[lx++] = hex_asc_lo(ch); + linebuf[lx++] = ' '; + if (j == 7) + linebuf[lx++] = ' '; + } + if (j) + lx--; + + ascii_column = 3 * rowsize + 2 + 2; + break; + } + if (!ascii) + goto nil; + + while (lx < (linebuflen - 1) && lx < (ascii_column - 1)) + linebuf[lx++] = ' '; + for (j = 0; (j < len) && (lx + 2) < linebuflen; j++) { + ch = ptr[j]; + linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.'; + } +nil: + linebuf[lx++] = '\0'; +} + +static void print_hex_dump(const void *buf, int len, int offset) +{ + const uint8_t *ptr = buf; + int i, linelen, remaining = len; + char linebuf[32 * 3 + 2 + 32 + 2 + 1]; + int rowsize = 16; + int groupsize = 1; + + if (rowsize != 16 && rowsize != 32) + rowsize = 16; + + for (i = 0; i < len; i += rowsize) { + linelen = MIN(remaining, rowsize); + remaining -= rowsize; + + hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, + linebuf, sizeof(linebuf), 1); + + printf("%.8x: %s\n", i + offset, linebuf); + } +} + +static int stress_test_uart_once(struct g_opt *opts, int fd, unsigned char *data, + off_t data_len) +{ + unsigned char *cmp_data = opts->cmp_buff; + ssize_t size; + int wait_rx; + int wait_tx; + ssize_t progress_rx = 0; + ssize_t progress_tx = 0; + unsigned int reads = 0; + unsigned int writes = 0; + + do { + struct pollfd pfd = { + .fd = fd, + }; + int ret; + + if (opts->mode & MODE_RX_ONLY && progress_rx < data_len) { + pfd.events |= POLLIN; + wait_rx = 1; + } else { + wait_rx = 0; + } + + if (opts->mode & MODE_TX_ONLY && progress_tx < data_len) { + pfd.events |= POLLOUT; + wait_tx = 1; + } else { + wait_tx = 0; + } + + ret = poll(&pfd, 1, 10 * 1000); + if (ret == 0) { + tst_res(TFAIL, "timeout, RX/TX: %zd/%zd\n", progress_rx, progress_tx); + break; + } + if (ret < 0) { + tst_res(TFAIL | TERRNO, "poll() failed"); + return 1; + } + + if (pfd.revents & POLLIN) { + size = read(fd, cmp_data + progress_rx, data_len - progress_rx); + if (size < 0) { + tst_res(TFAIL | TERRNO, "read() failed"); + return 1; + } + reads++; + progress_rx += size; + if (progress_rx >= data_len) + wait_rx = 0; + } + + if (pfd.revents & POLLOUT) { + + size = write(fd, data + progress_tx, data_len - progress_tx); + if (size < 0) { + tst_res(TFAIL | TERRNO, "write() failed"); + return 1; + } + writes++; + progress_tx += size; + if (progress_tx >= data_len) + wait_tx = 0; + } + } while (wait_rx || wait_tx); + + tst_res(TINFO, "Needed %u reads %u writes ", reads, writes); + + if (opts->mode & MODE_RX_ONLY) { + unsigned int i; + int found = 0; + unsigned int min_pos; + unsigned int max_pos; + + if (!memcmp(data, cmp_data, data_len)) { + tst_res(TPASS, "RX passed"); + return 0; + } + + for (i = 0; i < data_len && !found; i++) { + if (data[i] != cmp_data[i]) { + found = 1; + break; + } + } + + if (!found) { + tst_res(TFAIL, "memcmp() didn't match but manual cmp did"); + return 1; + } + + max_pos = (i & ~0xfULL) + 16 * 3; + if (max_pos > data_len) + max_pos = data_len; + + min_pos = i & ~0xfULL; + if (min_pos > 16 * 3) + min_pos -= 16 * 3; + else + min_pos = 0; + + tst_res(TFAIL, "Oh oh, inconsistency at pos %d (0x%x)", i, i); + + printf("\nOriginal sample:\n"); + print_hex_dump(data + min_pos, max_pos - min_pos, min_pos); + + printf("\nReceived sample:\n"); + print_hex_dump(cmp_data + min_pos, max_pos - min_pos, min_pos); + return 1; + } + + if (opts->mode & MODE_TX_ONLY) + tst_res(TPASS, "TX passed"); + + return 0; +} + +static int stress_test_uart(struct g_opt *opts, int fd, unsigned char *data, off_t data_len) +{ + unsigned int loops = 0; + int status; + + opts->cmp_buff = SAFE_MALLOC(data_len); + memset(opts->cmp_buff, 0, data_len); + + do { + status = stress_test_uart_once(opts, fd, data, data_len); + memset(opts->cmp_buff, 0, data_len); + } while (++loops < opts->loops && !status); + + free(opts->cmp_buff); + + return status; +} + +static int setup_uart(struct g_opt *opts, int open_mode, struct termios *old_term) +{ + struct termios new_term; + int fd; + int ret; + + tst_res(TINFO, "Setting up %s speed %u hwflow=%u", + opts->uart_dev, opts->baud_rate, opts->hwflow); + + fd = SAFE_OPEN(opts->uart_dev, open_mode | O_NONBLOCK); + + ret = tcgetattr(fd, old_term); + if (ret < 0) + tst_brk(TBROK, "tcgetattr() failed: %m\n"); + + new_term = *old_term; + + /* or c_cflag |= BOTHER and c_ospeed for any speed */ + ret = cfsetspeed(&new_term, opts->baud_rate); + if (ret < 0) + tst_brk(TBROK, "cfsetspeed(, %u) failed %m\n", opts->baud_rate); + cfmakeraw(&new_term); + new_term.c_cflag |= CREAD; + if (opts->hwflow) + new_term.c_cflag |= CRTSCTS; + else + new_term.c_cflag &= ~CRTSCTS; + new_term.c_cc[VMIN] = 64; + new_term.c_cc[VTIME] = 8; + + ret = tcsetattr(fd, TCSANOW, &new_term); + if (ret < 0) + tst_brk(TBROK, "tcsetattr failed: %m\n"); + + if (opts->do_termios) { + ret = tcflush(fd, TCIFLUSH); + if (ret < 0) + tst_brk(TBROK, "tcflush failed: %m\n"); + } + + ret = fcntl(fd, F_SETFL, 0); + if (ret) + printf("Failed to remove nonblock mode\n"); + + return fd; +} + +static void restore_uart(int fd, struct termios *old_term) +{ + int ret = tcsetattr(fd, TCSAFLUSH, old_term); + if (ret) + printf("tcsetattr() of old ones failed: %m\n"); +} + +static void print_counters(const char *prefix, + struct serial_icounter_struct *old, + struct serial_icounter_struct *new) +{ +#define CNT(x) (new->x - old->x) + printf("%scts: %d dsr: %d rng: %d dcd: %d rx: %d tx: %d " + "frame %d ovr %d par: %d brk: %d buf_ovrr: %d\n", prefix, + CNT(cts), CNT(dsr), CNT(rng), CNT(dcd), CNT(rx), + CNT(tx), CNT(frame), CNT(overrun), CNT(parity), + CNT(brk), CNT(buf_overrun)); +#undef CNT +} + +unsigned char *data; +static long data_len; + +static void one_run(unsigned int baud_rate) +{ + struct serial_icounter_struct old_counters; + struct serial_icounter_struct new_counters; + int ret, fd_rx, fd_tx; + struct termios old_term_rx, old_term_tx; + + struct g_opt opts_in = { + .loops = 1, + .do_termios = 1, + .uart_dev = uart_params.rx, + .baud_rate = baud_rate, + .hwflow = uart_params.hwflow, + .mode = MODE_RX_ONLY, + }; + + struct g_opt opts_out = { + .loops = 1, + .do_termios = 1, + .uart_dev = uart_params.tx, + .baud_rate = baud_rate, + .hwflow = uart_params.hwflow, + .mode = MODE_TX_ONLY, + }; + + fd_rx = setup_uart(&opts_in, O_RDONLY, &old_term_rx); + + if (!strcmp(uart_params.rx, uart_params.tx)) + fd_tx = SAFE_OPEN(uart_params.tx, O_WRONLY); + else + fd_tx = setup_uart(&opts_out, O_WRONLY, &old_term_tx); + + if (!SAFE_FORK()) { + ioctl(fd_rx, TIOCGICOUNT, &old_counters); + stress_test_uart(&opts_in, fd_rx, data, data_len); + ret = ioctl(fd_rx, TIOCGICOUNT, &new_counters); + if (ret > 0) + print_counters("RX:", &old_counters, &new_counters); + exit(0); + } + + if (!SAFE_FORK()) { + ioctl(fd_tx, TIOCGICOUNT, &old_counters); + stress_test_uart(&opts_out, fd_tx, data, data_len); + ret = ioctl(fd_tx, TIOCGICOUNT, &new_counters); + if (ret > 0) + print_counters("TX:", &old_counters, &new_counters); + exit(0); + } + + SAFE_WAIT(NULL); + SAFE_WAIT(NULL); + + restore_uart(fd_rx, &old_term_rx); + + if (strcmp(uart_params.rx, uart_params.tx)) + restore_uart(fd_tx, &old_term_tx); + + close(fd_rx); + close(fd_tx); +} + +void run(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(uart_params.baud_rates); i++) { + if (!uart_params.baud_rates[i]) + return; + one_run(uart_params.baud_rates[i]); + } +} + +static void map_file(const char *fname) +{ + struct stat st; + int fd; + + fd = SAFE_OPEN(fname, O_RDONLY); + + SAFE_FSTAT(fd, &st); + + data_len = st.st_size; + + data = SAFE_MMAP(NULL, data_len, PROT_READ, + MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, 0); + + tst_res(TINFO, "Mapped file '%s' size %li bytes", fname, data_len); + + SAFE_CLOSE(fd); +} + +static void map_buffer(long buf_size) +{ + size_t i; + + data_len = buf_size; + + data = SAFE_MMAP(NULL, data_len, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED | MAP_LOCKED, -1, 0); + + long *p = (void*)data; + + srandom(time(NULL)); + + for (i = 0; i < data_len / sizeof(long); i++) + p[i] = random(); + + tst_res(TINFO, "Mapped anynymous memory size %li bytes", data_len); +} + +static char *fname; +static char *buf_size; + +static int invalid_baudrate(long baudrate) +{ + if (baudrate < 0) + return 1; + + if (baudrate > 230400) + return 1; + //TODO: do we have to for standard speeds? + + return 0; +} + +static void parse_baudrate(struct tst_json_buf *buf, struct tst_json_val *val) +{ + unsigned int pos = 0; + + memset(uart_params.baud_rates, 0, sizeof(uart_params.baud_rates)); + + if (val->type != TST_JSON_ARR) { + tst_json_err(buf, "baudrate must be array of integers"); + return; + } + + TST_JSON_ARR_FOREACH(buf, val) { + if (val->type != TST_JSON_INT) { + tst_json_err(buf, "expected integer"); + return; + } + + if (invalid_baudrate(val->val_int)) { + tst_json_err(buf, "invalid baudrate"); + return; + } + + if (pos >= ARRAY_SIZE(uart_params.baud_rates)) { + tst_json_err(buf, "baudrates array too long"); + return; + } + + uart_params.baud_rates[pos++] = val->val_int; + } + + if (pos <= 0) { + tst_json_err(buf, "at least one baudrate is required"); + return; + } +} + +static void setup(void) +{ + long size = 1024; + + if (fname && buf_size) + tst_brk(TBROK, "Only one of -f and -s could be set!"); + + if (buf_size) + tst_parse_long(buf_size, &size, 0, LONG_MAX); + + struct tst_json_buf *json = tst_hwconf_get(); + char buf[128]; + struct tst_json_val val = {.buf = buf, .buf_size = sizeof(buf)}; + + TST_JSON_OBJ_FOREACH(json, &val) { + if (!strcmp(val.id, "uid")) { + continue; + } else if (!strcmp(val.id, "hwflow")) { + if (val.type != TST_JSON_INT) + tst_json_err(json, "hwflow must be int"); + else + uart_params.hwflow = !!val.val_int; + } else if (!strcmp(val.id, "tx")) { + if (val.type != TST_JSON_STR) + tst_json_err(json, "tx must be string"); + else + strcpy(uart_params.tx, val.val_str); + } else if (!strcmp(val.id, "rx")) { + if (val.type != TST_JSON_STR) + tst_json_err(json, "rx must be string"); + else + strcpy(uart_params.rx, val.val_str); + } else if (!strcmp(val.id, "tx")) { + if (val.type != TST_JSON_STR) + tst_json_err(json, "tx must be string"); + else + strcpy(uart_params.tx, val.val_str); + } else if (!strcmp(val.id, "baud_rates")) { + parse_baudrate(json, &val); + } else { + tst_json_err(json, "Invalid attribute '%s'", val.id); + } + } + + if (tst_json_is_err(json)) { + tst_json_err_print(stderr, json); + tst_brk(TBROK, "Invalid parameters!"); + } + + if (!uart_params.rx[0] || !uart_params.tx[0]) + tst_brk(TBROK, "Missing UART RX or TX"); + + if (fname) + map_file(fname); + else + map_buffer(size); +} + +static struct tst_test test = { + .setup = setup, + .test_all = run, + .options = (struct tst_option[]) { + {"f:", &fname, "-f Binary file for transfers"}, + {"s:", &buf_size, "-s Binary buffer size"}, + {} + }, + .needs_hardware = "UART-loopback", + .forks_child = 1, +};