diff options
Diffstat (limited to 'tools')
430 files changed, 34276 insertions, 4359 deletions
diff --git a/tools/arch/arm64/include/uapi/asm/bitsperlong.h b/tools/arch/arm64/include/uapi/asm/bitsperlong.h deleted file mode 100644 index 485d60bee26c..000000000000 --- a/tools/arch/arm64/include/uapi/asm/bitsperlong.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Copyright (C) 2012 ARM Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -#ifndef __ASM_BITSPERLONG_H -#define __ASM_BITSPERLONG_H - -#define __BITS_PER_LONG 64 - -#include <asm-generic/bitsperlong.h> - -#endif /* __ASM_BITSPERLONG_H */ diff --git a/tools/arch/hexagon/include/uapi/asm/bitsperlong.h b/tools/arch/hexagon/include/uapi/asm/bitsperlong.h deleted file mode 100644 index 5adca0d26913..000000000000 --- a/tools/arch/hexagon/include/uapi/asm/bitsperlong.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#ifndef __ASM_HEXAGON_BITSPERLONG_H -#define __ASM_HEXAGON_BITSPERLONG_H - -#define __BITS_PER_LONG 32 - -#include <asm-generic/bitsperlong.h> - -#endif diff --git a/tools/arch/loongarch/include/uapi/asm/bitsperlong.h b/tools/arch/loongarch/include/uapi/asm/bitsperlong.h deleted file mode 100644 index 00b4ba1e5cdf..000000000000 --- a/tools/arch/loongarch/include/uapi/asm/bitsperlong.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#ifndef __ASM_LOONGARCH_BITSPERLONG_H -#define __ASM_LOONGARCH_BITSPERLONG_H - -#define __BITS_PER_LONG (__SIZEOF_LONG__ * 8) - -#include <asm-generic/bitsperlong.h> - -#endif /* __ASM_LOONGARCH_BITSPERLONG_H */ diff --git a/tools/arch/microblaze/include/uapi/asm/bitsperlong.h b/tools/arch/microblaze/include/uapi/asm/bitsperlong.h deleted file mode 100644 index 76da34b10f59..000000000000 --- a/tools/arch/microblaze/include/uapi/asm/bitsperlong.h +++ /dev/null @@ -1,2 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#include <asm-generic/bitsperlong.h> diff --git a/tools/arch/riscv/include/uapi/asm/bitsperlong.h b/tools/arch/riscv/include/uapi/asm/bitsperlong.h deleted file mode 100644 index 0b9b58b57ff6..000000000000 --- a/tools/arch/riscv/include/uapi/asm/bitsperlong.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2012 ARM Ltd. - * Copyright (C) 2015 Regents of the University of California - */ - -#ifndef _UAPI_ASM_RISCV_BITSPERLONG_H -#define _UAPI_ASM_RISCV_BITSPERLONG_H - -#define __BITS_PER_LONG (__SIZEOF_POINTER__ * 8) - -#include <asm-generic/bitsperlong.h> - -#endif /* _UAPI_ASM_RISCV_BITSPERLONG_H */ diff --git a/tools/arch/x86/include/asm/nops.h b/tools/arch/x86/include/asm/nops.h index c5573eaa5bb9..1c1b7550fa55 100644 --- a/tools/arch/x86/include/asm/nops.h +++ b/tools/arch/x86/include/asm/nops.h @@ -34,6 +34,8 @@ #define BYTES_NOP7 0x8d,0xb4,0x26,0x00,0x00,0x00,0x00 #define BYTES_NOP8 0x3e,BYTES_NOP7 +#define ASM_NOP_MAX 8 + #else /* @@ -47,6 +49,9 @@ * 6: osp nopl 0x00(%eax,%eax,1) * 7: nopl 0x00000000(%eax) * 8: nopl 0x00000000(%eax,%eax,1) + * 9: cs nopl 0x00000000(%eax,%eax,1) + * 10: osp cs nopl 0x00000000(%eax,%eax,1) + * 11: osp osp cs nopl 0x00000000(%eax,%eax,1) */ #define BYTES_NOP1 0x90 #define BYTES_NOP2 0x66,BYTES_NOP1 @@ -56,6 +61,15 @@ #define BYTES_NOP6 0x66,BYTES_NOP5 #define BYTES_NOP7 0x0f,0x1f,0x80,0x00,0x00,0x00,0x00 #define BYTES_NOP8 0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00 +#define BYTES_NOP9 0x2e,BYTES_NOP8 +#define BYTES_NOP10 0x66,BYTES_NOP9 +#define BYTES_NOP11 0x66,BYTES_NOP10 + +#define ASM_NOP9 _ASM_BYTES(BYTES_NOP9) +#define ASM_NOP10 _ASM_BYTES(BYTES_NOP10) +#define ASM_NOP11 _ASM_BYTES(BYTES_NOP11) + +#define ASM_NOP_MAX 11 #endif /* CONFIG_64BIT */ @@ -68,8 +82,6 @@ #define ASM_NOP7 _ASM_BYTES(BYTES_NOP7) #define ASM_NOP8 _ASM_BYTES(BYTES_NOP8) -#define ASM_NOP_MAX 8 - #ifndef __ASSEMBLY__ extern const unsigned char * const x86_nops[]; #endif diff --git a/tools/arch/x86/kcpuid/.gitignore b/tools/arch/x86/kcpuid/.gitignore new file mode 100644 index 000000000000..1b8541bc8dd0 --- /dev/null +++ b/tools/arch/x86/kcpuid/.gitignore @@ -0,0 +1 @@ +kcpuid diff --git a/tools/arch/x86/kcpuid/kcpuid.c b/tools/arch/x86/kcpuid/kcpuid.c index 416f5b35dd8f..24b7d017ec2c 100644 --- a/tools/arch/x86/kcpuid/kcpuid.c +++ b/tools/arch/x86/kcpuid/kcpuid.c @@ -517,15 +517,16 @@ static void show_range(struct cpuid_range *range) static inline struct cpuid_func *index_to_func(u32 index) { struct cpuid_range *range; + u32 func_idx; range = (index & 0x80000000) ? leafs_ext : leafs_basic; - index &= 0x7FFFFFFF; + func_idx = index & 0xffff; - if (((index & 0xFFFF) + 1) > (u32)range->nr) { + if ((func_idx + 1) > (u32)range->nr) { printf("ERR: invalid input index (0x%x)\n", index); return NULL; } - return &range->funcs[index]; + return &range->funcs[func_idx]; } static void show_info(void) diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst index 11250c4734fe..3b7ba037af95 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-map.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst @@ -28,7 +28,7 @@ MAP COMMANDS | **bpftool** **map** { **show** | **list** } [*MAP*] | **bpftool** **map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* \ | **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**inner_map** *MAP*] \ -| [**dev** *NAME*] +| [**offload_dev** *NAME*] | **bpftool** **map dump** *MAP* | **bpftool** **map update** *MAP* [**key** *DATA*] [**value** *VALUE*] [*UPDATE_FLAGS*] | **bpftool** **map lookup** *MAP* [**key** *DATA*] @@ -73,7 +73,7 @@ DESCRIPTION maps. On such kernels bpftool will automatically emit this information as well. - **bpftool map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**inner_map** *MAP*] [**dev** *NAME*] + **bpftool map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**inner_map** *MAP*] [**offload_dev** *NAME*] Create a new map with given parameters and pin it to *bpffs* as *FILE*. @@ -86,8 +86,8 @@ DESCRIPTION kernel needs it to collect metadata related to the inner maps that the new map will work with. - Keyword **dev** expects a network interface name, and is used - to request hardware offload for the map. + Keyword **offload_dev** expects a network interface name, + and is used to request hardware offload for the map. **bpftool map dump** *MAP* Dump all entries in a given *MAP*. In case of **name**, diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst index 9443c524bb76..dcae81bd27ed 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst @@ -31,7 +31,7 @@ PROG COMMANDS | **bpftool** **prog dump xlated** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] [**visual**] }] | **bpftool** **prog dump jited** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] }] | **bpftool** **prog pin** *PROG* *FILE* -| **bpftool** **prog** { **load** | **loadall** } *OBJ* *PATH* [**type** *TYPE*] [**map** {**idx** *IDX* | **name** *NAME*} *MAP*] [**dev** *NAME*] [**pinmaps** *MAP_DIR*] [**autoattach**] +| **bpftool** **prog** { **load** | **loadall** } *OBJ* *PATH* [**type** *TYPE*] [**map** { **idx** *IDX* | **name** *NAME* } *MAP*] [{ **offload_dev** | **xdpmeta_dev** } *NAME*] [**pinmaps** *MAP_DIR*] [**autoattach**] | **bpftool** **prog attach** *PROG* *ATTACH_TYPE* [*MAP*] | **bpftool** **prog detach** *PROG* *ATTACH_TYPE* [*MAP*] | **bpftool** **prog tracelog** @@ -129,7 +129,7 @@ DESCRIPTION contain a dot character ('.'), which is reserved for future extensions of *bpffs*. - **bpftool prog { load | loadall }** *OBJ* *PATH* [**type** *TYPE*] [**map** {**idx** *IDX* | **name** *NAME*} *MAP*] [**dev** *NAME*] [**pinmaps** *MAP_DIR*] [**autoattach**] + **bpftool prog { load | loadall }** *OBJ* *PATH* [**type** *TYPE*] [**map** { **idx** *IDX* | **name** *NAME* } *MAP*] [{ **offload_dev** | **xdpmeta_dev** } *NAME*] [**pinmaps** *MAP_DIR*] [**autoattach**] Load bpf program(s) from binary *OBJ* and pin as *PATH*. **bpftool prog load** pins only the first program from the *OBJ* as *PATH*. **bpftool prog loadall** pins all programs @@ -143,8 +143,11 @@ DESCRIPTION to be replaced in the ELF file counting from 0, while *NAME* allows to replace a map by name. *MAP* specifies the map to use, referring to it by **id** or through a **pinned** file. - If **dev** *NAME* is specified program will be loaded onto - given networking device (offload). + If **offload_dev** *NAME* is specified program will be loaded + onto given networking device (offload). + If **xdpmeta_dev** *NAME* is specified program will become + device-bound without offloading, this facilitates access + to XDP metadata. Optional **pinmaps** argument can be provided to pin all maps under *MAP_DIR* directory. diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool index e7234d1a5306..085bf18f3659 100644 --- a/tools/bpf/bpftool/bash-completion/bpftool +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -278,7 +278,7 @@ _bpftool() _bpftool_get_prog_tags return 0 ;; - dev) + dev|offload_dev|xdpmeta_dev) _sysfs_get_netdevs return 0 ;; @@ -508,7 +508,8 @@ _bpftool() ;; *) COMPREPLY=( $( compgen -W "map" -- "$cur" ) ) - _bpftool_once_attr 'type dev pinmaps autoattach' + _bpftool_once_attr 'type pinmaps autoattach' + _bpftool_one_of_list 'offload_dev xdpmeta_dev' return 0 ;; esac @@ -733,7 +734,7 @@ _bpftool() esac ;; *) - _bpftool_once_attr 'type key value entries name flags dev' + _bpftool_once_attr 'type key value entries name flags offload_dev' if _bpftool_search_list 'array_of_maps' 'hash_of_maps'; then _bpftool_once_attr 'inner_map' fi diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c index 1360c82ae732..cc6e6aae2447 100644 --- a/tools/bpf/bpftool/common.c +++ b/tools/bpf/bpftool/common.c @@ -68,7 +68,7 @@ void p_info(const char *fmt, ...) va_end(ap); } -static bool is_bpffs(char *path) +static bool is_bpffs(const char *path) { struct statfs st_fs; @@ -244,13 +244,16 @@ int open_obj_pinned_any(const char *path, enum bpf_obj_type exp_type) return fd; } -int mount_bpffs_for_pin(const char *name) +int mount_bpffs_for_pin(const char *name, bool is_dir) { char err_str[ERR_MAX_LEN]; char *file; char *dir; int err = 0; + if (is_dir && is_bpffs(name)) + return err; + file = malloc(strlen(name) + 1); if (!file) { p_err("mem alloc failed"); @@ -286,7 +289,7 @@ int do_pin_fd(int fd, const char *name) { int err; - err = mount_bpffs_for_pin(name); + err = mount_bpffs_for_pin(name, false); if (err) return err; diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c index da16e6a27ccc..0675d6a46413 100644 --- a/tools/bpf/bpftool/feature.c +++ b/tools/bpf/bpftool/feature.c @@ -167,12 +167,12 @@ static int get_vendor_id(int ifindex) return strtol(buf, NULL, 0); } -static int read_procfs(const char *path) +static long read_procfs(const char *path) { char *endptr, *line = NULL; size_t len = 0; FILE *fd; - int res; + long res; fd = fopen(path, "r"); if (!fd) @@ -194,7 +194,7 @@ static int read_procfs(const char *path) static void probe_unprivileged_disabled(void) { - int res; + long res; /* No support for C-style ouptut */ @@ -216,14 +216,14 @@ static void probe_unprivileged_disabled(void) printf("Unable to retrieve required privileges for bpf() syscall\n"); break; default: - printf("bpf() syscall restriction has unknown value %d\n", res); + printf("bpf() syscall restriction has unknown value %ld\n", res); } } } static void probe_jit_enable(void) { - int res; + long res; /* No support for C-style ouptut */ @@ -245,7 +245,7 @@ static void probe_jit_enable(void) printf("Unable to retrieve JIT-compiler status\n"); break; default: - printf("JIT-compiler status has unknown value %d\n", + printf("JIT-compiler status has unknown value %ld\n", res); } } @@ -253,7 +253,7 @@ static void probe_jit_enable(void) static void probe_jit_harden(void) { - int res; + long res; /* No support for C-style ouptut */ @@ -275,7 +275,7 @@ static void probe_jit_harden(void) printf("Unable to retrieve JIT hardening status\n"); break; default: - printf("JIT hardening status has unknown value %d\n", + printf("JIT hardening status has unknown value %ld\n", res); } } @@ -283,7 +283,7 @@ static void probe_jit_harden(void) static void probe_jit_kallsyms(void) { - int res; + long res; /* No support for C-style ouptut */ @@ -302,14 +302,14 @@ static void probe_jit_kallsyms(void) printf("Unable to retrieve JIT kallsyms export status\n"); break; default: - printf("JIT kallsyms exports status has unknown value %d\n", res); + printf("JIT kallsyms exports status has unknown value %ld\n", res); } } } static void probe_jit_limit(void) { - int res; + long res; /* No support for C-style ouptut */ @@ -322,7 +322,7 @@ static void probe_jit_limit(void) printf("Unable to retrieve global memory limit for JIT compiler for unprivileged users\n"); break; default: - printf("Global memory limit for JIT compiler for unprivileged users is %d bytes\n", res); + printf("Global memory limit for JIT compiler for unprivileged users is %ld bytes\n", res); } } } diff --git a/tools/bpf/bpftool/iter.c b/tools/bpf/bpftool/iter.c index 9a1d2365a297..6b0e5202ca7a 100644 --- a/tools/bpf/bpftool/iter.c +++ b/tools/bpf/bpftool/iter.c @@ -76,7 +76,7 @@ static int do_pin(int argc, char **argv) goto close_obj; } - err = mount_bpffs_for_pin(path); + err = mount_bpffs_for_pin(path, false); if (err) goto close_link; diff --git a/tools/bpf/bpftool/link.c b/tools/bpf/bpftool/link.c index d98dbc50cf4c..2d786072ed0d 100644 --- a/tools/bpf/bpftool/link.c +++ b/tools/bpf/bpftool/link.c @@ -195,6 +195,8 @@ static int show_link_close_json(int fd, struct bpf_link_info *info) show_link_attach_type_json(info->tracing.attach_type, json_wtr); + jsonw_uint_field(json_wtr, "target_obj_id", info->tracing.target_obj_id); + jsonw_uint_field(json_wtr, "target_btf_id", info->tracing.target_btf_id); break; case BPF_LINK_TYPE_CGROUP: jsonw_lluint_field(json_wtr, "cgroup_id", @@ -212,7 +214,10 @@ static int show_link_close_json(int fd, struct bpf_link_info *info) case BPF_LINK_TYPE_NETFILTER: netfilter_dump_json(info, json_wtr); break; - + case BPF_LINK_TYPE_STRUCT_OPS: + jsonw_uint_field(json_wtr, "map_id", + info->struct_ops.map_id); + break; default: break; } @@ -245,7 +250,10 @@ static void show_link_header_plain(struct bpf_link_info *info) else printf("type %u ", info->type); - printf("prog %u ", info->prog_id); + if (info->type == BPF_LINK_TYPE_STRUCT_OPS) + printf("map %u ", info->struct_ops.map_id); + else + printf("prog %u ", info->prog_id); } static void show_link_attach_type_plain(__u32 attach_type) @@ -369,6 +377,10 @@ static int show_link_close_plain(int fd, struct bpf_link_info *info) printf("\n\tprog_type %u ", prog_info.type); show_link_attach_type_plain(info->tracing.attach_type); + if (info->tracing.target_obj_id || info->tracing.target_btf_id) + printf("\n\ttarget_obj_id %u target_btf_id %u ", + info->tracing.target_obj_id, + info->tracing.target_btf_id); break; case BPF_LINK_TYPE_CGROUP: printf("\n\tcgroup_id %zu ", (size_t)info->cgroup.cgroup_id); diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h index a49534d7eafa..b8bb08d10dec 100644 --- a/tools/bpf/bpftool/main.h +++ b/tools/bpf/bpftool/main.h @@ -142,7 +142,7 @@ const char *get_fd_type_name(enum bpf_obj_type type); char *get_fdinfo(int fd, const char *key); int open_obj_pinned(const char *path, bool quiet); int open_obj_pinned_any(const char *path, enum bpf_obj_type exp_type); -int mount_bpffs_for_pin(const char *name); +int mount_bpffs_for_pin(const char *name, bool is_dir); int do_pin_any(int argc, char **argv, int (*get_fd_by_id)(int *, char ***)); int do_pin_fd(int fd, const char *name); diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c index aaeb8939e137..f98f7bbea2b1 100644 --- a/tools/bpf/bpftool/map.c +++ b/tools/bpf/bpftool/map.c @@ -139,6 +139,9 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key, print_hex_data_json(key, info->key_size); jsonw_name(json_wtr, "value"); print_hex_data_json(value, info->value_size); + if (map_is_map_of_maps(info->type)) + jsonw_uint_field(json_wtr, "inner_map_id", + *(unsigned int *)value); if (btf) { struct btf_dumper d = { .btf = btf, @@ -259,8 +262,13 @@ static void print_entry_plain(struct bpf_map_info *info, unsigned char *key, } if (info->value_size) { - printf("value:%c", break_names ? '\n' : ' '); - fprint_hex(stdout, value, info->value_size, " "); + if (map_is_map_of_maps(info->type)) { + printf("inner_map_id:%c", break_names ? '\n' : ' '); + printf("%u ", *(unsigned int *)value); + } else { + printf("value:%c", break_names ? '\n' : ' '); + fprint_hex(stdout, value, info->value_size, " "); + } } printf("\n"); @@ -1279,6 +1287,11 @@ static int do_create(int argc, char **argv) "flags")) goto exit; } else if (is_prefix(*argv, "dev")) { + p_info("Warning: 'bpftool map create [...] dev <ifname>' syntax is deprecated.\n" + "Going further, please use 'offload_dev <ifname>' to request hardware offload for the map."); + goto offload_dev; + } else if (is_prefix(*argv, "offload_dev")) { +offload_dev: NEXT_ARG(); if (attr.map_ifindex) { @@ -1423,7 +1436,7 @@ static int do_help(int argc, char **argv) "Usage: %1$s %2$s { show | list } [MAP]\n" " %1$s %2$s create FILE type TYPE key KEY_SIZE value VALUE_SIZE \\\n" " entries MAX_ENTRIES name NAME [flags FLAGS] \\\n" - " [inner_map MAP] [dev NAME]\n" + " [inner_map MAP] [offload_dev NAME]\n" " %1$s %2$s dump MAP\n" " %1$s %2$s update MAP [key DATA] [value VALUE] [UPDATE_FLAGS]\n" " %1$s %2$s lookup MAP [key DATA]\n" diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c index 91b6075b2db3..8443a149dd17 100644 --- a/tools/bpf/bpftool/prog.c +++ b/tools/bpf/bpftool/prog.c @@ -1517,12 +1517,13 @@ static int load_with_options(int argc, char **argv, bool first_prog_only) struct bpf_program *prog = NULL, *pos; unsigned int old_map_fds = 0; const char *pinmaps = NULL; + __u32 xdpmeta_ifindex = 0; + __u32 offload_ifindex = 0; bool auto_attach = false; struct bpf_object *obj; struct bpf_map *map; const char *pinfile; unsigned int i, j; - __u32 ifindex = 0; const char *file; int idx, err; @@ -1614,17 +1615,46 @@ static int load_with_options(int argc, char **argv, bool first_prog_only) map_replace[old_map_fds].fd = fd; old_map_fds++; } else if (is_prefix(*argv, "dev")) { + p_info("Warning: 'bpftool prog load [...] dev <ifname>' syntax is deprecated.\n" + "Going further, please use 'offload_dev <ifname>' to offload program to device.\n" + "For applications using XDP hints only, use 'xdpmeta_dev <ifname>'."); + goto offload_dev; + } else if (is_prefix(*argv, "offload_dev")) { +offload_dev: NEXT_ARG(); - if (ifindex) { - p_err("offload device already specified"); + if (offload_ifindex) { + p_err("offload_dev already specified"); + goto err_free_reuse_maps; + } else if (xdpmeta_ifindex) { + p_err("xdpmeta_dev and offload_dev are mutually exclusive"); + goto err_free_reuse_maps; + } + if (!REQ_ARGS(1)) + goto err_free_reuse_maps; + + offload_ifindex = if_nametoindex(*argv); + if (!offload_ifindex) { + p_err("unrecognized netdevice '%s': %s", + *argv, strerror(errno)); + goto err_free_reuse_maps; + } + NEXT_ARG(); + } else if (is_prefix(*argv, "xdpmeta_dev")) { + NEXT_ARG(); + + if (xdpmeta_ifindex) { + p_err("xdpmeta_dev already specified"); + goto err_free_reuse_maps; + } else if (offload_ifindex) { + p_err("xdpmeta_dev and offload_dev are mutually exclusive"); goto err_free_reuse_maps; } if (!REQ_ARGS(1)) goto err_free_reuse_maps; - ifindex = if_nametoindex(*argv); - if (!ifindex) { + xdpmeta_ifindex = if_nametoindex(*argv); + if (!xdpmeta_ifindex) { p_err("unrecognized netdevice '%s': %s", *argv, strerror(errno)); goto err_free_reuse_maps; @@ -1671,7 +1701,12 @@ static int load_with_options(int argc, char **argv, bool first_prog_only) goto err_close_obj; } - bpf_program__set_ifindex(pos, ifindex); + if (prog_type == BPF_PROG_TYPE_XDP && xdpmeta_ifindex) { + bpf_program__set_flags(pos, BPF_F_XDP_DEV_BOUND_ONLY); + bpf_program__set_ifindex(pos, xdpmeta_ifindex); + } else { + bpf_program__set_ifindex(pos, offload_ifindex); + } if (bpf_program__type(pos) != prog_type) bpf_program__set_type(pos, prog_type); bpf_program__set_expected_attach_type(pos, expected_attach_type); @@ -1709,7 +1744,7 @@ static int load_with_options(int argc, char **argv, bool first_prog_only) idx = 0; bpf_object__for_each_map(map, obj) { if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY) - bpf_map__set_ifindex(map, ifindex); + bpf_map__set_ifindex(map, offload_ifindex); if (j < old_map_fds && idx == map_replace[j].idx) { err = bpf_map__reuse_fd(map, map_replace[j++].fd); @@ -1739,7 +1774,7 @@ static int load_with_options(int argc, char **argv, bool first_prog_only) goto err_close_obj; } - err = mount_bpffs_for_pin(pinfile); + err = mount_bpffs_for_pin(pinfile, !first_prog_only); if (err) goto err_close_obj; @@ -2416,7 +2451,7 @@ static int do_help(int argc, char **argv) " %1$s %2$s dump jited PROG [{ file FILE | [opcodes] [linum] }]\n" " %1$s %2$s pin PROG FILE\n" " %1$s %2$s { load | loadall } OBJ PATH \\\n" - " [type TYPE] [dev NAME] \\\n" + " [type TYPE] [{ offload_dev | xdpmeta_dev } NAME] \\\n" " [map { idx IDX | name NAME } MAP]\\\n" " [pinmaps MAP_DIR]\n" " [autoattach]\n" diff --git a/tools/bpf/bpftool/struct_ops.c b/tools/bpf/bpftool/struct_ops.c index 57c3da70aa31..3ebc9fe91e0e 100644 --- a/tools/bpf/bpftool/struct_ops.c +++ b/tools/bpf/bpftool/struct_ops.c @@ -509,7 +509,7 @@ static int do_register(int argc, char **argv) if (argc == 1) linkdir = GET_ARG(); - if (linkdir && mount_bpffs_for_pin(linkdir)) { + if (linkdir && mount_bpffs_for_pin(linkdir, true)) { p_err("can't mount bpffs for pinning"); return -1; } diff --git a/tools/bpf/resolve_btfids/Makefile b/tools/bpf/resolve_btfids/Makefile index ac548a7baa73..4b8079f294f6 100644 --- a/tools/bpf/resolve_btfids/Makefile +++ b/tools/bpf/resolve_btfids/Makefile @@ -67,7 +67,7 @@ $(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OU LIBELF_FLAGS := $(shell $(HOSTPKG_CONFIG) libelf --cflags 2>/dev/null) LIBELF_LIBS := $(shell $(HOSTPKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) -HOSTCFLAGS += -g \ +HOSTCFLAGS_resolve_btfids += -g \ -I$(srctree)/tools/include \ -I$(srctree)/tools/include/uapi \ -I$(LIBBPF_INCLUDE) \ @@ -76,7 +76,7 @@ HOSTCFLAGS += -g \ LIBS = $(LIBELF_LIBS) -lz -export srctree OUTPUT HOSTCFLAGS Q HOSTCC HOSTLD HOSTAR +export srctree OUTPUT HOSTCFLAGS_resolve_btfids Q HOSTCC HOSTLD HOSTAR include $(srctree)/tools/build/Makefile.include $(BINARY_IN): fixdep FORCE prepare | $(OUTPUT) diff --git a/tools/counter/.gitignore b/tools/counter/.gitignore new file mode 100644 index 000000000000..9fd290d4bf43 --- /dev/null +++ b/tools/counter/.gitignore @@ -0,0 +1,2 @@ +/counter_example +/include/linux/counter.h diff --git a/tools/counter/Makefile b/tools/counter/Makefile index 8843f0fa6119..a0f4cab71fe5 100644 --- a/tools/counter/Makefile +++ b/tools/counter/Makefile @@ -40,6 +40,7 @@ $(OUTPUT)counter_example: $(COUNTER_EXAMPLE) clean: rm -f $(ALL_PROGRAMS) rm -rf $(OUTPUT)include/linux/counter.h + rmdir -p $(OUTPUT)include/linux find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete install: $(ALL_PROGRAMS) diff --git a/tools/gpio/lsgpio.c b/tools/gpio/lsgpio.c index c61d061247e1..52a0be45410c 100644 --- a/tools/gpio/lsgpio.c +++ b/tools/gpio/lsgpio.c @@ -94,7 +94,7 @@ static void print_attributes(struct gpio_v2_line_info *info) for (i = 0; i < info->num_attrs; i++) { if (info->attrs[i].id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) fprintf(stdout, ", debounce_period=%dusec", - info->attrs[0].debounce_period_us); + info->attrs[i].debounce_period_us); } } diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 9839feafd38a..64d67b080744 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -25,8 +25,23 @@ endif nolibc_arch := $(patsubst arm64,aarch64,$(ARCH)) arch_file := arch-$(nolibc_arch).h -all_files := ctype.h errno.h nolibc.h signal.h stackprotector.h std.h stdint.h \ - stdio.h stdlib.h string.h sys.h time.h types.h unistd.h +all_files := \ + compiler.h \ + ctype.h \ + errno.h \ + nolibc.h \ + signal.h \ + stackprotector.h \ + std.h \ + stdint.h \ + stdlib.h \ + string.h \ + sys.h \ + time.h \ + types.h \ + unistd.h \ + stdio.h \ + # install all headers needed to support a bare-metal compiler all: headers diff --git a/tools/include/nolibc/arch-aarch64.h b/tools/include/nolibc/arch-aarch64.h index 383baddef701..11f294a406b7 100644 --- a/tools/include/nolibc/arch-aarch64.h +++ b/tools/include/nolibc/arch-aarch64.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_AARCH64_H #define _NOLIBC_ARCH_AARCH64_H +#include "compiler.h" + /* The struct returned by the newfstatat() syscall. Differs slightly from the * x86_64's stat one by field ordering, so be careful. */ @@ -173,27 +175,30 @@ char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); /* startup code */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( - "ldr x0, [sp]\n" // argc (x0) was in the stack - "add x1, sp, 8\n" // argv (x1) = sp - "lsl x2, x0, 3\n" // envp (x2) = 8*argc ... - "add x2, x2, 8\n" // + 8 (skip null) - "add x2, x2, x1\n" // + argv - "adrp x3, environ\n" // x3 = &environ (high bits) - "str x2, [x3, #:lo12:environ]\n" // store envp into environ - "mov x4, x2\n" // search for auxv (follows NULL after last env) +#ifdef _NOLIBC_STACKPROTECTOR + "bl __stack_chk_init\n" /* initialize stack protector */ +#endif + "ldr x0, [sp]\n" /* argc (x0) was in the stack */ + "add x1, sp, 8\n" /* argv (x1) = sp */ + "lsl x2, x0, 3\n" /* envp (x2) = 8*argc ... */ + "add x2, x2, 8\n" /* + 8 (skip null) */ + "add x2, x2, x1\n" /* + argv */ + "adrp x3, environ\n" /* x3 = &environ (high bits) */ + "str x2, [x3, #:lo12:environ]\n" /* store envp into environ */ + "mov x4, x2\n" /* search for auxv (follows NULL after last env) */ "0:\n" - "ldr x5, [x4], 8\n" // x5 = *x4; x4 += 8 - "cbnz x5, 0b\n" // and stop at NULL after last env - "adrp x3, _auxv\n" // x3 = &_auxv (high bits) - "str x4, [x3, #:lo12:_auxv]\n" // store x4 into _auxv - "and sp, x1, -16\n" // sp must be 16-byte aligned in the callee - "bl main\n" // main() returns the status code, we'll exit with it. - "mov x8, 93\n" // NR_exit == 93 + "ldr x5, [x4], 8\n" /* x5 = *x4; x4 += 8 */ + "cbnz x5, 0b\n" /* and stop at NULL after last env */ + "adrp x3, _auxv\n" /* x3 = &_auxv (high bits) */ + "str x4, [x3, #:lo12:_auxv]\n" /* store x4 into _auxv */ + "and sp, x1, -16\n" /* sp must be 16-byte aligned in the callee */ + "bl main\n" /* main() returns the status code, we'll exit with it. */ + "mov x8, 93\n" /* NR_exit == 93 */ "svc #0\n" ); __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_AARCH64_H +#endif /* _NOLIBC_ARCH_AARCH64_H */ diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index 42499f23e73c..ca4c66987497 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_ARM_H #define _NOLIBC_ARCH_ARM_H +#include "compiler.h" + /* The struct returned by the stat() syscall, 32-bit only, the syscall returns * exactly 56 bytes (stops before the unused array). In big endian, the format * differs as devices are returned as short only. @@ -196,41 +198,67 @@ struct sys_stat_struct { _arg1; \ }) +#define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + register long _arg5 __asm__ ("r4") = (long)(arg5); \ + register long _arg6 __asm__ ("r5") = (long)(arg6); \ + \ + __asm__ volatile ( \ + _NOLIBC_THUMB_SET_R7 \ + "svc #0\n" \ + _NOLIBC_THUMB_RESTORE_R7 \ + : "=r"(_arg1), "=r" (_num) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ + "r"(_arg6), "r"(_num) \ + : "memory", "cc", "lr" \ + ); \ + _arg1; \ +}) + + char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); /* startup code */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( - "pop {%r0}\n" // argc was in the stack - "mov %r1, %sp\n" // argv = sp +#ifdef _NOLIBC_STACKPROTECTOR + "bl __stack_chk_init\n" /* initialize stack protector */ +#endif + "pop {%r0}\n" /* argc was in the stack */ + "mov %r1, %sp\n" /* argv = sp */ - "add %r2, %r0, $1\n" // envp = (argc + 1) ... - "lsl %r2, %r2, $2\n" // * 4 ... - "add %r2, %r2, %r1\n" // + argv - "ldr %r3, 1f\n" // r3 = &environ (see below) - "str %r2, [r3]\n" // store envp into environ + "add %r2, %r0, $1\n" /* envp = (argc + 1) ... */ + "lsl %r2, %r2, $2\n" /* * 4 ... */ + "add %r2, %r2, %r1\n" /* + argv */ + "ldr %r3, 1f\n" /* r3 = &environ (see below) */ + "str %r2, [r3]\n" /* store envp into environ */ - "mov r4, r2\n" // search for auxv (follows NULL after last env) + "mov r4, r2\n" /* search for auxv (follows NULL after last env) */ "0:\n" - "mov r5, r4\n" // r5 = r4 - "add r4, r4, #4\n" // r4 += 4 - "ldr r5,[r5]\n" // r5 = *r5 = *(r4-4) - "cmp r5, #0\n" // and stop at NULL after last env + "mov r5, r4\n" /* r5 = r4 */ + "add r4, r4, #4\n" /* r4 += 4 */ + "ldr r5,[r5]\n" /* r5 = *r5 = *(r4-4) */ + "cmp r5, #0\n" /* and stop at NULL after last env */ "bne 0b\n" - "ldr %r3, 2f\n" // r3 = &_auxv (low bits) - "str r4, [r3]\n" // store r4 into _auxv + "ldr %r3, 2f\n" /* r3 = &_auxv (low bits) */ + "str r4, [r3]\n" /* store r4 into _auxv */ - "mov %r3, $8\n" // AAPCS : sp must be 8-byte aligned in the - "neg %r3, %r3\n" // callee, and bl doesn't push (lr=pc) - "and %r3, %r3, %r1\n" // so we do sp = r1(=sp) & r3(=-8); - "mov %sp, %r3\n" // + "mov %r3, $8\n" /* AAPCS : sp must be 8-byte aligned in the */ + "neg %r3, %r3\n" /* callee, and bl doesn't push (lr=pc) */ + "and %r3, %r3, %r1\n" /* so we do sp = r1(=sp) & r3(=-8); */ + "mov %sp, %r3\n" - "bl main\n" // main() returns the status code, we'll exit with it. - "movs r7, $1\n" // NR_exit == 1 + "bl main\n" /* main() returns the status code, we'll exit with it. */ + "movs r7, $1\n" /* NR_exit == 1 */ "svc $0x00\n" - ".align 2\n" // below are the pointers to a few variables + ".align 2\n" /* below are the pointers to a few variables */ "1:\n" ".word environ\n" "2:\n" @@ -239,4 +267,4 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_ARM_H +#endif /* _NOLIBC_ARCH_ARM_H */ diff --git a/tools/include/nolibc/arch-i386.h b/tools/include/nolibc/arch-i386.h index 2d98d78fd3f3..3d672d925e9e 100644 --- a/tools/include/nolibc/arch-i386.h +++ b/tools/include/nolibc/arch-i386.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_I386_H #define _NOLIBC_ARCH_I386_H +#include "compiler.h" + /* The struct returned by the stat() syscall, 32-bit only, the syscall returns * exactly 56 bytes (stops before the unused array). */ @@ -181,8 +183,6 @@ struct sys_stat_struct { char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); -#define __ARCH_SUPPORTS_STACK_PROTECTOR - /* startup code */ /* * i386 System V ABI mandates: @@ -190,35 +190,35 @@ const unsigned long *_auxv __attribute__((weak)); * 2) The deepest stack frame should be set to zero * */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"),no_stack_protector)) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( -#ifdef NOLIBC_STACKPROTECTOR - "call __stack_chk_init\n" // initialize stack protector +#ifdef _NOLIBC_STACKPROTECTOR + "call __stack_chk_init\n" /* initialize stack protector */ #endif - "pop %eax\n" // argc (first arg, %eax) - "mov %esp, %ebx\n" // argv[] (second arg, %ebx) - "lea 4(%ebx,%eax,4),%ecx\n" // then a NULL then envp (third arg, %ecx) - "mov %ecx, environ\n" // save environ - "xor %ebp, %ebp\n" // zero the stack frame - "mov %ecx, %edx\n" // search for auxv (follows NULL after last env) + "pop %eax\n" /* argc (first arg, %eax) */ + "mov %esp, %ebx\n" /* argv[] (second arg, %ebx) */ + "lea 4(%ebx,%eax,4),%ecx\n" /* then a NULL then envp (third arg, %ecx) */ + "mov %ecx, environ\n" /* save environ */ + "xor %ebp, %ebp\n" /* zero the stack frame */ + "mov %ecx, %edx\n" /* search for auxv (follows NULL after last env) */ "0:\n" - "add $4, %edx\n" // search for auxv using edx, it follows the - "cmp -4(%edx), %ebp\n" // ... NULL after last env (ebp is zero here) + "add $4, %edx\n" /* search for auxv using edx, it follows the */ + "cmp -4(%edx), %ebp\n" /* ... NULL after last env (ebp is zero here) */ "jnz 0b\n" - "mov %edx, _auxv\n" // save it into _auxv - "and $-16, %esp\n" // x86 ABI : esp must be 16-byte aligned before - "sub $4, %esp\n" // the call instruction (args are aligned) - "push %ecx\n" // push all registers on the stack so that we - "push %ebx\n" // support both regparm and plain stack modes + "mov %edx, _auxv\n" /* save it into _auxv */ + "and $-16, %esp\n" /* x86 ABI : esp must be 16-byte aligned before */ + "sub $4, %esp\n" /* the call instruction (args are aligned) */ + "push %ecx\n" /* push all registers on the stack so that we */ + "push %ebx\n" /* support both regparm and plain stack modes */ "push %eax\n" - "call main\n" // main() returns the status code in %eax - "mov %eax, %ebx\n" // retrieve exit code (32-bit int) - "movl $1, %eax\n" // NR_exit == 1 - "int $0x80\n" // exit now - "hlt\n" // ensure it does not + "call main\n" /* main() returns the status code in %eax */ + "mov %eax, %ebx\n" /* retrieve exit code (32-bit int) */ + "movl $1, %eax\n" /* NR_exit == 1 */ + "int $0x80\n" /* exit now */ + "hlt\n" /* ensure it does not */ ); __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_I386_H +#endif /* _NOLIBC_ARCH_I386_H */ diff --git a/tools/include/nolibc/arch-loongarch.h b/tools/include/nolibc/arch-loongarch.h index 029ee3cd6baf..ad3f266e7093 100644 --- a/tools/include/nolibc/arch-loongarch.h +++ b/tools/include/nolibc/arch-loongarch.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_LOONGARCH_H #define _NOLIBC_ARCH_LOONGARCH_H +#include "compiler.h" + /* Syscalls for LoongArch : * - stack is 16-byte aligned * - syscall number is passed in a7 @@ -158,7 +160,7 @@ const unsigned long *_auxv __attribute__((weak)); #define LONG_ADDI "addi.w" #define LONG_SLL "slli.w" #define LONG_BSTRINS "bstrins.w" -#else // __loongarch_grlen == 64 +#else /* __loongarch_grlen == 64 */ #define LONGLOG "3" #define SZREG "8" #define REG_L "ld.d" @@ -170,31 +172,34 @@ const unsigned long *_auxv __attribute__((weak)); #endif /* startup code */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( - REG_L " $a0, $sp, 0\n" // argc (a0) was in the stack - LONG_ADDI " $a1, $sp, "SZREG"\n" // argv (a1) = sp + SZREG - LONG_SLL " $a2, $a0, "LONGLOG"\n" // envp (a2) = SZREG*argc ... - LONG_ADDI " $a2, $a2, "SZREG"\n" // + SZREG (skip null) - LONG_ADD " $a2, $a2, $a1\n" // + argv - - "move $a3, $a2\n" // iterate a3 over envp to find auxv (after NULL) - "0:\n" // do { - REG_L " $a4, $a3, 0\n" // a4 = *a3; - LONG_ADDI " $a3, $a3, "SZREG"\n" // a3 += sizeof(void*); - "bne $a4, $zero, 0b\n" // } while (a4); - "la.pcrel $a4, _auxv\n" // a4 = &_auxv - LONG_S " $a3, $a4, 0\n" // store a3 into _auxv - - "la.pcrel $a3, environ\n" // a3 = &environ - LONG_S " $a2, $a3, 0\n" // store envp(a2) into environ - LONG_BSTRINS " $sp, $zero, 3, 0\n" // sp must be 16-byte aligned - "bl main\n" // main() returns the status code, we'll exit with it. - "li.w $a7, 93\n" // NR_exit == 93 +#ifdef _NOLIBC_STACKPROTECTOR + "bl __stack_chk_init\n" /* initialize stack protector */ +#endif + REG_L " $a0, $sp, 0\n" /* argc (a0) was in the stack */ + LONG_ADDI " $a1, $sp, "SZREG"\n" /* argv (a1) = sp + SZREG */ + LONG_SLL " $a2, $a0, "LONGLOG"\n" /* envp (a2) = SZREG*argc ... */ + LONG_ADDI " $a2, $a2, "SZREG"\n" /* + SZREG (skip null) */ + LONG_ADD " $a2, $a2, $a1\n" /* + argv */ + + "move $a3, $a2\n" /* iterate a3 over envp to find auxv (after NULL) */ + "0:\n" /* do { */ + REG_L " $a4, $a3, 0\n" /* a4 = *a3; */ + LONG_ADDI " $a3, $a3, "SZREG"\n" /* a3 += sizeof(void*); */ + "bne $a4, $zero, 0b\n" /* } while (a4); */ + "la.pcrel $a4, _auxv\n" /* a4 = &_auxv */ + LONG_S " $a3, $a4, 0\n" /* store a3 into _auxv */ + + "la.pcrel $a3, environ\n" /* a3 = &environ */ + LONG_S " $a2, $a3, 0\n" /* store envp(a2) into environ */ + LONG_BSTRINS " $sp, $zero, 3, 0\n" /* sp must be 16-byte aligned */ + "bl main\n" /* main() returns the status code, we'll exit with it. */ + "li.w $a7, 93\n" /* NR_exit == 93 */ "syscall 0\n" ); __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_LOONGARCH_H +#endif /* _NOLIBC_ARCH_LOONGARCH_H */ diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index bf83432d23ed..db24e0837a39 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_MIPS_H #define _NOLIBC_ARCH_MIPS_H +#include "compiler.h" + /* The struct returned by the stat() syscall. 88 bytes are returned by the * syscall. */ @@ -180,45 +182,49 @@ char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); /* startup code, note that it's called __start on MIPS */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector __start(void) { __asm__ volatile ( - //".set nomips16\n" + /*".set nomips16\n"*/ ".set push\n" ".set noreorder\n" ".option pic0\n" - //".ent __start\n" - //"__start:\n" - "lw $a0,($sp)\n" // argc was in the stack - "addiu $a1, $sp, 4\n" // argv = sp + 4 - "sll $a2, $a0, 2\n" // a2 = argc * 4 - "add $a2, $a2, $a1\n" // envp = argv + 4*argc ... - "addiu $a2, $a2, 4\n" // ... + 4 - "lui $a3, %hi(environ)\n" // load environ into a3 (hi) - "addiu $a3, %lo(environ)\n" // load environ into a3 (lo) - "sw $a2,($a3)\n" // store envp(a2) into environ - - "move $t0, $a2\n" // iterate t0 over envp, look for NULL - "0:" // do { - "lw $a3, ($t0)\n" // a3=*(t0); - "bne $a3, $0, 0b\n" // } while (a3); - "addiu $t0, $t0, 4\n" // delayed slot: t0+=4; - "lui $a3, %hi(_auxv)\n" // load _auxv into a3 (hi) - "addiu $a3, %lo(_auxv)\n" // load _auxv into a3 (lo) - "sw $t0, ($a3)\n" // store t0 into _auxv +#ifdef _NOLIBC_STACKPROTECTOR + "jal __stack_chk_init\n" /* initialize stack protector */ + "nop\n" /* delayed slot */ +#endif + /*".ent __start\n"*/ + /*"__start:\n"*/ + "lw $a0,($sp)\n" /* argc was in the stack */ + "addiu $a1, $sp, 4\n" /* argv = sp + 4 */ + "sll $a2, $a0, 2\n" /* a2 = argc * 4 */ + "add $a2, $a2, $a1\n" /* envp = argv + 4*argc ... */ + "addiu $a2, $a2, 4\n" /* ... + 4 */ + "lui $a3, %hi(environ)\n" /* load environ into a3 (hi) */ + "addiu $a3, %lo(environ)\n" /* load environ into a3 (lo) */ + "sw $a2,($a3)\n" /* store envp(a2) into environ */ + + "move $t0, $a2\n" /* iterate t0 over envp, look for NULL */ + "0:" /* do { */ + "lw $a3, ($t0)\n" /* a3=*(t0); */ + "bne $a3, $0, 0b\n" /* } while (a3); */ + "addiu $t0, $t0, 4\n" /* delayed slot: t0+=4; */ + "lui $a3, %hi(_auxv)\n" /* load _auxv into a3 (hi) */ + "addiu $a3, %lo(_auxv)\n" /* load _auxv into a3 (lo) */ + "sw $t0, ($a3)\n" /* store t0 into _auxv */ "li $t0, -8\n" - "and $sp, $sp, $t0\n" // sp must be 8-byte aligned - "addiu $sp,$sp,-16\n" // the callee expects to save a0..a3 there! - "jal main\n" // main() returns the status code, we'll exit with it. - "nop\n" // delayed slot - "move $a0, $v0\n" // retrieve 32-bit exit code from v0 - "li $v0, 4001\n" // NR_exit == 4001 + "and $sp, $sp, $t0\n" /* sp must be 8-byte aligned */ + "addiu $sp,$sp,-16\n" /* the callee expects to save a0..a3 there! */ + "jal main\n" /* main() returns the status code, we'll exit with it. */ + "nop\n" /* delayed slot */ + "move $a0, $v0\n" /* retrieve 32-bit exit code from v0 */ + "li $v0, 4001\n" /* NR_exit == 4001 */ "syscall\n" - //".end __start\n" + /*".end __start\n"*/ ".set pop\n" ); __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_MIPS_H +#endif /* _NOLIBC_ARCH_MIPS_H */ diff --git a/tools/include/nolibc/arch-riscv.h b/tools/include/nolibc/arch-riscv.h index e197fcb10ac0..a2e8564e66d6 100644 --- a/tools/include/nolibc/arch-riscv.h +++ b/tools/include/nolibc/arch-riscv.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_RISCV_H #define _NOLIBC_ARCH_RISCV_H +#include "compiler.h" + struct sys_stat_struct { unsigned long st_dev; /* Device. */ unsigned long st_ino; /* File serial number. */ @@ -33,9 +35,13 @@ struct sys_stat_struct { #if __riscv_xlen == 64 #define PTRLOG "3" #define SZREG "8" +#define REG_L "ld" +#define REG_S "sd" #elif __riscv_xlen == 32 #define PTRLOG "2" #define SZREG "4" +#define REG_L "lw" +#define REG_S "sw" #endif /* Syscalls for RISCV : @@ -174,35 +180,38 @@ char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); /* startup code */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( ".option push\n" ".option norelax\n" "lla gp, __global_pointer$\n" ".option pop\n" - "lw a0, 0(sp)\n" // argc (a0) was in the stack - "add a1, sp, "SZREG"\n" // argv (a1) = sp - "slli a2, a0, "PTRLOG"\n" // envp (a2) = SZREG*argc ... - "add a2, a2, "SZREG"\n" // + SZREG (skip null) - "add a2,a2,a1\n" // + argv - - "add a3, a2, zero\n" // iterate a3 over envp to find auxv (after NULL) - "0:\n" // do { - "ld a4, 0(a3)\n" // a4 = *a3; - "add a3, a3, "SZREG"\n" // a3 += sizeof(void*); - "bne a4, zero, 0b\n" // } while (a4); - "lui a4, %hi(_auxv)\n" // a4 = &_auxv (high bits) - "sd a3, %lo(_auxv)(a4)\n" // store a3 into _auxv - - "lui a3, %hi(environ)\n" // a3 = &environ (high bits) - "sd a2,%lo(environ)(a3)\n" // store envp(a2) into environ - "andi sp,a1,-16\n" // sp must be 16-byte aligned - "call main\n" // main() returns the status code, we'll exit with it. - "li a7, 93\n" // NR_exit == 93 +#ifdef _NOLIBC_STACKPROTECTOR + "call __stack_chk_init\n" /* initialize stack protector */ +#endif + REG_L" a0, 0(sp)\n" /* argc (a0) was in the stack */ + "add a1, sp, "SZREG"\n" /* argv (a1) = sp */ + "slli a2, a0, "PTRLOG"\n" /* envp (a2) = SZREG*argc ... */ + "add a2, a2, "SZREG"\n" /* + SZREG (skip null) */ + "add a2,a2,a1\n" /* + argv */ + + "add a3, a2, zero\n" /* iterate a3 over envp to find auxv (after NULL) */ + "0:\n" /* do { */ + REG_L" a4, 0(a3)\n" /* a4 = *a3; */ + "add a3, a3, "SZREG"\n" /* a3 += sizeof(void*); */ + "bne a4, zero, 0b\n" /* } while (a4); */ + "lui a4, %hi(_auxv)\n" /* a4 = &_auxv (high bits) */ + REG_S" a3, %lo(_auxv)(a4)\n" /* store a3 into _auxv */ + + "lui a3, %hi(environ)\n" /* a3 = &environ (high bits) */ + REG_S" a2,%lo(environ)(a3)\n"/* store envp(a2) into environ */ + "andi sp,a1,-16\n" /* sp must be 16-byte aligned */ + "call main\n" /* main() returns the status code, we'll exit with it. */ + "li a7, 93\n" /* NR_exit == 93 */ "ecall\n" ); __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_RISCV_H +#endif /* _NOLIBC_ARCH_RISCV_H */ diff --git a/tools/include/nolibc/arch-s390.h b/tools/include/nolibc/arch-s390.h index 6b0e54ed543d..516dff5bff8b 100644 --- a/tools/include/nolibc/arch-s390.h +++ b/tools/include/nolibc/arch-s390.h @@ -5,8 +5,11 @@ #ifndef _NOLIBC_ARCH_S390_H #define _NOLIBC_ARCH_S390_H +#include <asm/signal.h> #include <asm/unistd.h> +#include "compiler.h" + /* The struct returned by the stat() syscall, equivalent to stat64(). The * syscall returns 116 bytes and stops in the middle of __unused. */ @@ -163,7 +166,7 @@ char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); /* startup code */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( "lg %r2,0(%r15)\n" /* argument count */ @@ -223,4 +226,12 @@ void *sys_mmap(void *addr, size_t length, int prot, int flags, int fd, return (void *)my_syscall1(__NR_mmap, &args); } #define sys_mmap sys_mmap -#endif // _NOLIBC_ARCH_S390_H + +static __attribute__((unused)) +pid_t sys_fork(void) +{ + return my_syscall5(__NR_clone, 0, SIGCHLD, 0, 0, 0); +} +#define sys_fork sys_fork + +#endif /* _NOLIBC_ARCH_S390_H */ diff --git a/tools/include/nolibc/arch-x86_64.h b/tools/include/nolibc/arch-x86_64.h index f7f2a11d4c3b..6fc4d8392742 100644 --- a/tools/include/nolibc/arch-x86_64.h +++ b/tools/include/nolibc/arch-x86_64.h @@ -7,6 +7,8 @@ #ifndef _NOLIBC_ARCH_X86_64_H #define _NOLIBC_ARCH_X86_64_H +#include "compiler.h" + /* The struct returned by the stat() syscall, equivalent to stat64(). The * syscall returns 116 bytes and stops in the middle of __unused. */ @@ -181,8 +183,6 @@ struct sys_stat_struct { char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); -#define __ARCH_SUPPORTS_STACK_PROTECTOR - /* startup code */ /* * x86-64 System V ABI mandates: @@ -190,31 +190,31 @@ const unsigned long *_auxv __attribute__((weak)); * 2) The deepest stack frame should be zero (the %rbp). * */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void) { __asm__ volatile ( -#ifdef NOLIBC_STACKPROTECTOR - "call __stack_chk_init\n" // initialize stack protector +#ifdef _NOLIBC_STACKPROTECTOR + "call __stack_chk_init\n" /* initialize stack protector */ #endif - "pop %rdi\n" // argc (first arg, %rdi) - "mov %rsp, %rsi\n" // argv[] (second arg, %rsi) - "lea 8(%rsi,%rdi,8),%rdx\n" // then a NULL then envp (third arg, %rdx) - "mov %rdx, environ\n" // save environ - "xor %ebp, %ebp\n" // zero the stack frame - "mov %rdx, %rax\n" // search for auxv (follows NULL after last env) + "pop %rdi\n" /* argc (first arg, %rdi) */ + "mov %rsp, %rsi\n" /* argv[] (second arg, %rsi) */ + "lea 8(%rsi,%rdi,8),%rdx\n" /* then a NULL then envp (third arg, %rdx) */ + "mov %rdx, environ\n" /* save environ */ + "xor %ebp, %ebp\n" /* zero the stack frame */ + "mov %rdx, %rax\n" /* search for auxv (follows NULL after last env) */ "0:\n" - "add $8, %rax\n" // search for auxv using rax, it follows the - "cmp -8(%rax), %rbp\n" // ... NULL after last env (rbp is zero here) + "add $8, %rax\n" /* search for auxv using rax, it follows the */ + "cmp -8(%rax), %rbp\n" /* ... NULL after last env (rbp is zero here) */ "jnz 0b\n" - "mov %rax, _auxv\n" // save it into _auxv - "and $-16, %rsp\n" // x86 ABI : esp must be 16-byte aligned before call - "call main\n" // main() returns the status code, we'll exit with it. - "mov %eax, %edi\n" // retrieve exit code (32 bit) - "mov $60, %eax\n" // NR_exit == 60 - "syscall\n" // really exit - "hlt\n" // ensure it does not return + "mov %rax, _auxv\n" /* save it into _auxv */ + "and $-16, %rsp\n" /* x86 ABI : esp must be 16-byte aligned before call */ + "call main\n" /* main() returns the status code, we'll exit with it. */ + "mov %eax, %edi\n" /* retrieve exit code (32 bit) */ + "mov $60, %eax\n" /* NR_exit == 60 */ + "syscall\n" /* really exit */ + "hlt\n" /* ensure it does not return */ ); __builtin_unreachable(); } -#endif // _NOLIBC_ARCH_X86_64_H +#endif /* _NOLIBC_ARCH_X86_64_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 2d5386a8d6aa..82b43935650f 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -7,7 +7,7 @@ * the syscall declarations and the _start code definition. This is the only * global part. On all architectures the kernel puts everything in the stack * before jumping to _start just above us, without any return address (_start - * is not a function but an entry pint). So at the stack pointer we find argc. + * is not a function but an entry point). So at the stack pointer we find argc. * Then argv[] begins, and ends at the first NULL. Then we have envp which * starts and ends with a NULL as well. So envp=argv+argc+1. */ diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h new file mode 100644 index 000000000000..beddc3665d69 --- /dev/null +++ b/tools/include/nolibc/compiler.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * NOLIBC compiler support header + * Copyright (C) 2023 Thomas Weißschuh <linux@weissschuh.net> + */ +#ifndef _NOLIBC_COMPILER_H +#define _NOLIBC_COMPILER_H + +#if defined(__SSP__) || defined(__SSP_STRONG__) || defined(__SSP_ALL__) || defined(__SSP_EXPLICIT__) + +#define _NOLIBC_STACKPROTECTOR + +#endif /* defined(__SSP__) ... */ + +#if defined(__has_attribute) +# if __has_attribute(no_stack_protector) +# define __no_stack_protector __attribute__((no_stack_protector)) +# else +# define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector"))) +# endif +#else +# define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector"))) +#endif /* defined(__has_attribute) */ + +#endif /* _NOLIBC_COMPILER_H */ diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h index 04739a6293c4..05a228a6ee78 100644 --- a/tools/include/nolibc/nolibc.h +++ b/tools/include/nolibc/nolibc.h @@ -99,11 +99,11 @@ #include "sys.h" #include "ctype.h" #include "signal.h" +#include "unistd.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #include "time.h" -#include "unistd.h" #include "stackprotector.h" /* Used by programs to avoid std includes */ diff --git a/tools/include/nolibc/stackprotector.h b/tools/include/nolibc/stackprotector.h index d119cbbbc256..88f7b2d098ff 100644 --- a/tools/include/nolibc/stackprotector.h +++ b/tools/include/nolibc/stackprotector.h @@ -7,13 +7,9 @@ #ifndef _NOLIBC_STACKPROTECTOR_H #define _NOLIBC_STACKPROTECTOR_H -#include "arch.h" +#include "compiler.h" -#if defined(NOLIBC_STACKPROTECTOR) - -#if !defined(__ARCH_SUPPORTS_STACK_PROTECTOR) -#error "nolibc does not support stack protectors on this arch" -#endif +#if defined(_NOLIBC_STACKPROTECTOR) #include "sys.h" #include "stdlib.h" @@ -41,13 +37,14 @@ void __stack_chk_fail_local(void) __attribute__((weak,section(".data.nolibc_stack_chk"))) uintptr_t __stack_chk_guard; -__attribute__((weak,no_stack_protector,section(".text.nolibc_stack_chk"))) +__attribute__((weak,section(".text.nolibc_stack_chk"))) __no_stack_protector void __stack_chk_init(void) { my_syscall3(__NR_getrandom, &__stack_chk_guard, sizeof(__stack_chk_guard), 0); - /* a bit more randomness in case getrandom() fails */ - __stack_chk_guard ^= (uintptr_t) &__stack_chk_guard; + /* a bit more randomness in case getrandom() fails, ensure the guard is never 0 */ + if (__stack_chk_guard != (uintptr_t) &__stack_chk_guard) + __stack_chk_guard ^= (uintptr_t) &__stack_chk_guard; } -#endif // defined(NOLIBC_STACKPROTECTOR) +#endif /* defined(_NOLIBC_STACKPROTECTOR) */ -#endif // _NOLIBC_STACKPROTECTOR_H +#endif /* _NOLIBC_STACKPROTECTOR_H */ diff --git a/tools/include/nolibc/stdint.h b/tools/include/nolibc/stdint.h index c1ce4f5e0603..4b282435a59a 100644 --- a/tools/include/nolibc/stdint.h +++ b/tools/include/nolibc/stdint.h @@ -36,8 +36,8 @@ typedef ssize_t int_fast16_t; typedef size_t uint_fast16_t; typedef ssize_t int_fast32_t; typedef size_t uint_fast32_t; -typedef ssize_t int_fast64_t; -typedef size_t uint_fast64_t; +typedef int64_t int_fast64_t; +typedef uint64_t uint_fast64_t; typedef int64_t intmax_t; typedef uint64_t uintmax_t; @@ -84,16 +84,30 @@ typedef uint64_t uintmax_t; #define INT_FAST8_MIN INT8_MIN #define INT_FAST16_MIN INTPTR_MIN #define INT_FAST32_MIN INTPTR_MIN -#define INT_FAST64_MIN INTPTR_MIN +#define INT_FAST64_MIN INT64_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MAX INTPTR_MAX #define INT_FAST32_MAX INTPTR_MAX -#define INT_FAST64_MAX INTPTR_MAX +#define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX SIZE_MAX #define UINT_FAST32_MAX SIZE_MAX -#define UINT_FAST64_MAX SIZE_MAX +#define UINT_FAST64_MAX UINT64_MAX + +#ifndef INT_MIN +#define INT_MIN (-__INT_MAX__ - 1) +#endif +#ifndef INT_MAX +#define INT_MAX __INT_MAX__ +#endif + +#ifndef LONG_MIN +#define LONG_MIN (-__LONG_MAX__ - 1) +#endif +#ifndef LONG_MAX +#define LONG_MAX __LONG_MAX__ +#endif #endif /* _NOLIBC_STDINT_H */ diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index 6cbbb52836a0..0eef91daf289 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -21,17 +21,75 @@ #define EOF (-1) #endif -/* just define FILE as a non-empty type */ +/* just define FILE as a non-empty type. The value of the pointer gives + * the FD: FILE=~fd for fd>=0 or NULL for fd<0. This way positive FILE + * are immediately identified as abnormal entries (i.e. possible copies + * of valid pointers to something else). + */ typedef struct FILE { char dummy[1]; } FILE; -/* We define the 3 common stdio files as constant invalid pointers that - * are easily recognized. - */ -static __attribute__((unused)) FILE* const stdin = (FILE*)-3; -static __attribute__((unused)) FILE* const stdout = (FILE*)-2; -static __attribute__((unused)) FILE* const stderr = (FILE*)-1; +static __attribute__((unused)) FILE* const stdin = (FILE*)(intptr_t)~STDIN_FILENO; +static __attribute__((unused)) FILE* const stdout = (FILE*)(intptr_t)~STDOUT_FILENO; +static __attribute__((unused)) FILE* const stderr = (FILE*)(intptr_t)~STDERR_FILENO; + +/* provides a FILE* equivalent of fd. The mode is ignored. */ +static __attribute__((unused)) +FILE *fdopen(int fd, const char *mode __attribute__((unused))) +{ + if (fd < 0) { + SET_ERRNO(EBADF); + return NULL; + } + return (FILE*)(intptr_t)~fd; +} + +/* provides the fd of stream. */ +static __attribute__((unused)) +int fileno(FILE *stream) +{ + intptr_t i = (intptr_t)stream; + + if (i >= 0) { + SET_ERRNO(EBADF); + return -1; + } + return ~i; +} + +/* flush a stream. */ +static __attribute__((unused)) +int fflush(FILE *stream) +{ + intptr_t i = (intptr_t)stream; + + /* NULL is valid here. */ + if (i > 0) { + SET_ERRNO(EBADF); + return -1; + } + + /* Don't do anything, nolibc does not support buffering. */ + return 0; +} + +/* flush a stream. */ +static __attribute__((unused)) +int fclose(FILE *stream) +{ + intptr_t i = (intptr_t)stream; + + if (i >= 0) { + SET_ERRNO(EBADF); + return -1; + } + + if (close(~i)) + return EOF; + + return 0; +} /* getc(), fgetc(), getchar() */ @@ -41,14 +99,8 @@ static __attribute__((unused)) int fgetc(FILE* stream) { unsigned char ch; - int fd; - if (stream < stdin || stream > stderr) - return EOF; - - fd = 3 + (long)stream; - - if (read(fd, &ch, 1) <= 0) + if (read(fileno(stream), &ch, 1) <= 0) return EOF; return ch; } @@ -68,14 +120,8 @@ static __attribute__((unused)) int fputc(int c, FILE* stream) { unsigned char ch = c; - int fd; - - if (stream < stdin || stream > stderr) - return EOF; - - fd = 3 + (long)stream; - if (write(fd, &ch, 1) <= 0) + if (write(fileno(stream), &ch, 1) <= 0) return EOF; return ch; } @@ -96,12 +142,7 @@ static __attribute__((unused)) int _fwrite(const void *buf, size_t size, FILE *stream) { ssize_t ret; - int fd; - - if (stream < stdin || stream > stderr) - return EOF; - - fd = 3 + (long)stream; + int fd = fileno(stream); while (size) { ret = write(fd, buf, size); diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index 894c955d027e..902162f80337 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -102,7 +102,7 @@ char *_getenv(const char *name, char **environ) return NULL; } -static inline __attribute__((unused,always_inline)) +static __inline__ __attribute__((unused,always_inline)) char *getenv(const char *name) { extern char **environ; @@ -231,7 +231,7 @@ int utoh_r(unsigned long in, char *buffer) /* converts unsigned long <in> to an hex string using the static itoa_buffer * and returns the pointer to that string. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *utoh(unsigned long in) { utoh_r(in, itoa_buffer); @@ -293,7 +293,7 @@ int itoa_r(long in, char *buffer) /* for historical compatibility, same as above but returns the pointer to the * buffer. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *ltoa_r(long in, char *buffer) { itoa_r(in, buffer); @@ -303,7 +303,7 @@ char *ltoa_r(long in, char *buffer) /* converts long integer <in> to a string using the static itoa_buffer and * returns the pointer to that string. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *itoa(long in) { itoa_r(in, itoa_buffer); @@ -313,7 +313,7 @@ char *itoa(long in) /* converts long integer <in> to a string using the static itoa_buffer and * returns the pointer to that string. Same as above, for compatibility. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *ltoa(long in) { itoa_r(in, itoa_buffer); @@ -323,7 +323,7 @@ char *ltoa(long in) /* converts unsigned long integer <in> to a string using the static itoa_buffer * and returns the pointer to that string. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *utoa(unsigned long in) { utoa_r(in, itoa_buffer); @@ -367,7 +367,7 @@ int u64toh_r(uint64_t in, char *buffer) /* converts uint64_t <in> to an hex string using the static itoa_buffer and * returns the pointer to that string. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *u64toh(uint64_t in) { u64toh_r(in, itoa_buffer); @@ -429,7 +429,7 @@ int i64toa_r(int64_t in, char *buffer) /* converts int64_t <in> to a string using the static itoa_buffer and returns * the pointer to that string. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *i64toa(int64_t in) { i64toa_r(in, itoa_buffer); @@ -439,7 +439,7 @@ char *i64toa(int64_t in) /* converts uint64_t <in> to a string using the static itoa_buffer and returns * the pointer to that string. */ -static inline __attribute__((unused)) +static __inline__ __attribute__((unused)) char *u64toa(uint64_t in) { u64toa_r(in, itoa_buffer); diff --git a/tools/include/nolibc/string.h b/tools/include/nolibc/string.h index fffdaf6ff467..0c2e06c7c477 100644 --- a/tools/include/nolibc/string.h +++ b/tools/include/nolibc/string.h @@ -90,7 +90,7 @@ void *memset(void *dst, int b, size_t len) while (len--) { /* prevent gcc from recognizing memset() here */ - asm volatile(""); + __asm__ volatile(""); *(p++) = b; } return dst; @@ -139,7 +139,7 @@ size_t strlen(const char *str) size_t len; for (len = 0; str[len]; len++) - asm(""); + __asm__(""); return len; } diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 5d624dc63a42..856249a11890 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -12,15 +12,17 @@ /* system includes */ #include <asm/unistd.h> -#include <asm/signal.h> // for SIGCHLD +#include <asm/signal.h> /* for SIGCHLD */ #include <asm/ioctls.h> #include <asm/mman.h> #include <linux/fs.h> #include <linux/loop.h> #include <linux/time.h> #include <linux/auxvec.h> -#include <linux/fcntl.h> // for O_* and AT_* -#include <linux/stat.h> // for statx() +#include <linux/fcntl.h> /* for O_* and AT_* */ +#include <linux/stat.h> /* for statx() */ +#include <linux/reboot.h> /* for LINUX_REBOOT_* */ +#include <linux/prctl.h> #include "arch.h" #include "errno.h" @@ -322,7 +324,7 @@ static __attribute__((noreturn,unused)) void sys_exit(int status) { my_syscall1(__NR_exit, status & 255); - while(1); // shut the "noreturn" warnings. + while(1); /* shut the "noreturn" warnings. */ } static __attribute__((noreturn,unused)) @@ -336,6 +338,7 @@ void exit(int status) * pid_t fork(void); */ +#ifndef sys_fork static __attribute__((unused)) pid_t sys_fork(void) { @@ -351,6 +354,7 @@ pid_t sys_fork(void) #error Neither __NR_clone nor __NR_fork defined, cannot implement sys_fork() #endif } +#endif static __attribute__((unused)) pid_t fork(void) @@ -858,7 +862,7 @@ int open(const char *path, int flags, ...) va_list args; va_start(args, flags); - mode = va_arg(args, mode_t); + mode = va_arg(args, int); va_end(args); } @@ -873,6 +877,32 @@ int open(const char *path, int flags, ...) /* + * int prctl(int option, unsigned long arg2, unsigned long arg3, + * unsigned long arg4, unsigned long arg5); + */ + +static __attribute__((unused)) +int sys_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + return my_syscall5(__NR_prctl, option, arg2, arg3, arg4, arg5); +} + +static __attribute__((unused)) +int prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + int ret = sys_prctl(option, arg2, arg3, arg4, arg5); + + if (ret < 0) { + SET_ERRNO(-ret); + ret = -1; + } + return ret; +} + + +/* * int pivot_root(const char *new, const char *old); */ @@ -909,7 +939,7 @@ int sys_poll(struct pollfd *fds, int nfds, int timeout) t.tv_sec = timeout / 1000; t.tv_nsec = (timeout % 1000) * 1000000; } - return my_syscall4(__NR_ppoll, fds, nfds, (timeout >= 0) ? &t : NULL, NULL); + return my_syscall5(__NR_ppoll, fds, nfds, (timeout >= 0) ? &t : NULL, NULL, 0); #elif defined(__NR_poll) return my_syscall3(__NR_poll, fds, nfds, timeout); #else @@ -1131,23 +1161,26 @@ int sys_stat(const char *path, struct stat *buf) long ret; ret = sys_statx(AT_FDCWD, path, AT_NO_AUTOMOUNT, STATX_BASIC_STATS, &statx); - buf->st_dev = ((statx.stx_dev_minor & 0xff) - | (statx.stx_dev_major << 8) - | ((statx.stx_dev_minor & ~0xff) << 12)); - buf->st_ino = statx.stx_ino; - buf->st_mode = statx.stx_mode; - buf->st_nlink = statx.stx_nlink; - buf->st_uid = statx.stx_uid; - buf->st_gid = statx.stx_gid; - buf->st_rdev = ((statx.stx_rdev_minor & 0xff) - | (statx.stx_rdev_major << 8) - | ((statx.stx_rdev_minor & ~0xff) << 12)); - buf->st_size = statx.stx_size; - buf->st_blksize = statx.stx_blksize; - buf->st_blocks = statx.stx_blocks; - buf->st_atime = statx.stx_atime.tv_sec; - buf->st_mtime = statx.stx_mtime.tv_sec; - buf->st_ctime = statx.stx_ctime.tv_sec; + buf->st_dev = ((statx.stx_dev_minor & 0xff) + | (statx.stx_dev_major << 8) + | ((statx.stx_dev_minor & ~0xff) << 12)); + buf->st_ino = statx.stx_ino; + buf->st_mode = statx.stx_mode; + buf->st_nlink = statx.stx_nlink; + buf->st_uid = statx.stx_uid; + buf->st_gid = statx.stx_gid; + buf->st_rdev = ((statx.stx_rdev_minor & 0xff) + | (statx.stx_rdev_major << 8) + | ((statx.stx_rdev_minor & ~0xff) << 12)); + buf->st_size = statx.stx_size; + buf->st_blksize = statx.stx_blksize; + buf->st_blocks = statx.stx_blocks; + buf->st_atim.tv_sec = statx.stx_atime.tv_sec; + buf->st_atim.tv_nsec = statx.stx_atime.tv_nsec; + buf->st_mtim.tv_sec = statx.stx_mtime.tv_sec; + buf->st_mtim.tv_nsec = statx.stx_mtime.tv_nsec; + buf->st_ctim.tv_sec = statx.stx_ctime.tv_sec; + buf->st_ctim.tv_nsec = statx.stx_ctime.tv_nsec; return ret; } #else @@ -1165,19 +1198,22 @@ int sys_stat(const char *path, struct stat *buf) #else #error Neither __NR_newfstatat nor __NR_stat defined, cannot implement sys_stat() #endif - buf->st_dev = stat.st_dev; - buf->st_ino = stat.st_ino; - buf->st_mode = stat.st_mode; - buf->st_nlink = stat.st_nlink; - buf->st_uid = stat.st_uid; - buf->st_gid = stat.st_gid; - buf->st_rdev = stat.st_rdev; - buf->st_size = stat.st_size; - buf->st_blksize = stat.st_blksize; - buf->st_blocks = stat.st_blocks; - buf->st_atime = stat.st_atime; - buf->st_mtime = stat.st_mtime; - buf->st_ctime = stat.st_ctime; + buf->st_dev = stat.st_dev; + buf->st_ino = stat.st_ino; + buf->st_mode = stat.st_mode; + buf->st_nlink = stat.st_nlink; + buf->st_uid = stat.st_uid; + buf->st_gid = stat.st_gid; + buf->st_rdev = stat.st_rdev; + buf->st_size = stat.st_size; + buf->st_blksize = stat.st_blksize; + buf->st_blocks = stat.st_blocks; + buf->st_atim.tv_sec = stat.st_atime; + buf->st_atim.tv_nsec = stat.st_atime_nsec; + buf->st_mtim.tv_sec = stat.st_mtime; + buf->st_mtim.tv_nsec = stat.st_mtime_nsec; + buf->st_ctim.tv_sec = stat.st_ctime; + buf->st_ctim.tv_nsec = stat.st_ctime_nsec; return ret; } #endif @@ -1365,6 +1401,29 @@ ssize_t write(int fd, const void *buf, size_t count) return ret; } + +/* + * int memfd_create(const char *name, unsigned int flags); + */ + +static __attribute__((unused)) +int sys_memfd_create(const char *name, unsigned int flags) +{ + return my_syscall2(__NR_memfd_create, name, flags); +} + +static __attribute__((unused)) +int memfd_create(const char *name, unsigned int flags) +{ + ssize_t ret = sys_memfd_create(name, flags); + + if (ret < 0) { + SET_ERRNO(-ret); + ret = -1; + } + return ret; +} + /* make sure to include all global symbols */ #include "nolibc.h" diff --git a/tools/include/nolibc/types.h b/tools/include/nolibc/types.h index aedd7d9e3f64..f96e28bff4ba 100644 --- a/tools/include/nolibc/types.h +++ b/tools/include/nolibc/types.h @@ -86,14 +86,6 @@ #define SEEK_CUR 1 #define SEEK_END 2 -/* cmd for reboot() */ -#define LINUX_REBOOT_MAGIC1 0xfee1dead -#define LINUX_REBOOT_MAGIC2 0x28121969 -#define LINUX_REBOOT_CMD_HALT 0xcdef0123 -#define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc -#define LINUX_REBOOT_CMD_RESTART 0x01234567 -#define LINUX_REBOOT_CMD_SW_SUSPEND 0xd000fce2 - /* Macros used on waitpid()'s return status */ #define WEXITSTATUS(status) (((status) & 0xff00) >> 8) #define WIFEXITED(status) (((status) & 0x7f) == 0) @@ -206,9 +198,9 @@ struct stat { off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ - time_t st_atime; /* time of last access */ - time_t st_mtime; /* time of last modification */ - time_t st_ctime; /* time of last status change */ + union { time_t st_atime; struct timespec st_atim; }; /* time of last access */ + union { time_t st_mtime; struct timespec st_mtim; }; /* time of last modification */ + union { time_t st_ctime; struct timespec st_ctim; }; /* time of last status change */ }; /* WARNING, it only deals with the 4096 first majors and 256 first minors */ diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h index ac7d53d986cd..0e832e10a0b2 100644 --- a/tools/include/nolibc/unistd.h +++ b/tools/include/nolibc/unistd.h @@ -56,6 +56,21 @@ int tcsetpgrp(int fd, pid_t pid) return ioctl(fd, TIOCSPGRP, &pid); } +#define _syscall(N, ...) \ +({ \ + long _ret = my_syscall##N(__VA_ARGS__); \ + if (_ret < 0) { \ + SET_ERRNO(-_ret); \ + _ret = -1; \ + } \ + _ret; \ +}) + +#define _syscall_narg(...) __syscall_narg(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) +#define __syscall_narg(_0, _1, _2, _3, _4, _5, _6, N, ...) N +#define _syscall_n(N, ...) _syscall(N, __VA_ARGS__) +#define syscall(...) _syscall_n(_syscall_narg(__VA_ARGS__), ##__VA_ARGS__) + /* make sure to include all global symbols */ #include "nolibc.h" diff --git a/tools/include/uapi/asm-generic/bitsperlong.h b/tools/include/uapi/asm-generic/bitsperlong.h index 23e6c416b85f..352cb81947b8 100644 --- a/tools/include/uapi/asm-generic/bitsperlong.h +++ b/tools/include/uapi/asm-generic/bitsperlong.h @@ -1,6 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ #ifndef _UAPI__ASM_GENERIC_BITS_PER_LONG #define _UAPI__ASM_GENERIC_BITS_PER_LONG +#ifndef __BITS_PER_LONG +/* + * In order to keep safe and avoid regression, only unify uapi + * bitsperlong.h for some archs which are using newer toolchains + * that have the definitions of __CHAR_BIT__ and __SIZEOF_LONG__. + * See the following link for more info: + * https://lore.kernel.org/linux-arch/b9624545-2c80-49a1-ac3c-39264a591f7b@app.fastmail.com/ + */ +#if defined(__CHAR_BIT__) && defined(__SIZEOF_LONG__) +#define __BITS_PER_LONG (__CHAR_BIT__ * __SIZEOF_LONG__) +#else /* * There seems to be no way of detecting this automatically from user * space, so 64 bit architectures should override this in their @@ -8,8 +20,8 @@ * both 32 and 64 bit user space must not rely on CONFIG_64BIT * to decide it, but rather check a compiler provided macro. */ -#ifndef __BITS_PER_LONG #define __BITS_PER_LONG 32 #endif +#endif #endif /* _UAPI__ASM_GENERIC_BITS_PER_LONG */ diff --git a/tools/include/uapi/asm-generic/socket.h b/tools/include/uapi/asm-generic/socket.h index 8756df13be50..54d9c8bf7c55 100644 --- a/tools/include/uapi/asm-generic/socket.h +++ b/tools/include/uapi/asm-generic/socket.h @@ -121,6 +121,9 @@ #define SO_RCVMARK 75 +#define SO_PASSPIDFD 76 +#define SO_PEERPIDFD 77 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__)) diff --git a/tools/include/uapi/asm-generic/unistd.h b/tools/include/uapi/asm-generic/unistd.h index 45fa180cc56a..dd7d8e10f16d 100644 --- a/tools/include/uapi/asm-generic/unistd.h +++ b/tools/include/uapi/asm-generic/unistd.h @@ -38,12 +38,12 @@ __SYSCALL(__NR_io_destroy, sys_io_destroy) __SC_COMP(__NR_io_submit, sys_io_submit, compat_sys_io_submit) #define __NR_io_cancel 3 __SYSCALL(__NR_io_cancel, sys_io_cancel) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_io_getevents 4 __SC_3264(__NR_io_getevents, sys_io_getevents_time32, sys_io_getevents) #endif -/* fs/xattr.c */ #define __NR_setxattr 5 __SYSCALL(__NR_setxattr, sys_setxattr) #define __NR_lsetxattr 6 @@ -68,58 +68,38 @@ __SYSCALL(__NR_removexattr, sys_removexattr) __SYSCALL(__NR_lremovexattr, sys_lremovexattr) #define __NR_fremovexattr 16 __SYSCALL(__NR_fremovexattr, sys_fremovexattr) - -/* fs/dcache.c */ #define __NR_getcwd 17 __SYSCALL(__NR_getcwd, sys_getcwd) - -/* fs/cookies.c */ #define __NR_lookup_dcookie 18 __SC_COMP(__NR_lookup_dcookie, sys_lookup_dcookie, compat_sys_lookup_dcookie) - -/* fs/eventfd.c */ #define __NR_eventfd2 19 __SYSCALL(__NR_eventfd2, sys_eventfd2) - -/* fs/eventpoll.c */ #define __NR_epoll_create1 20 __SYSCALL(__NR_epoll_create1, sys_epoll_create1) #define __NR_epoll_ctl 21 __SYSCALL(__NR_epoll_ctl, sys_epoll_ctl) #define __NR_epoll_pwait 22 __SC_COMP(__NR_epoll_pwait, sys_epoll_pwait, compat_sys_epoll_pwait) - -/* fs/fcntl.c */ #define __NR_dup 23 __SYSCALL(__NR_dup, sys_dup) #define __NR_dup3 24 __SYSCALL(__NR_dup3, sys_dup3) #define __NR3264_fcntl 25 __SC_COMP_3264(__NR3264_fcntl, sys_fcntl64, sys_fcntl, compat_sys_fcntl64) - -/* fs/inotify_user.c */ #define __NR_inotify_init1 26 __SYSCALL(__NR_inotify_init1, sys_inotify_init1) #define __NR_inotify_add_watch 27 __SYSCALL(__NR_inotify_add_watch, sys_inotify_add_watch) #define __NR_inotify_rm_watch 28 __SYSCALL(__NR_inotify_rm_watch, sys_inotify_rm_watch) - -/* fs/ioctl.c */ #define __NR_ioctl 29 __SC_COMP(__NR_ioctl, sys_ioctl, compat_sys_ioctl) - -/* fs/ioprio.c */ #define __NR_ioprio_set 30 __SYSCALL(__NR_ioprio_set, sys_ioprio_set) #define __NR_ioprio_get 31 __SYSCALL(__NR_ioprio_get, sys_ioprio_get) - -/* fs/locks.c */ #define __NR_flock 32 __SYSCALL(__NR_flock, sys_flock) - -/* fs/namei.c */ #define __NR_mknodat 33 __SYSCALL(__NR_mknodat, sys_mknodat) #define __NR_mkdirat 34 @@ -130,25 +110,21 @@ __SYSCALL(__NR_unlinkat, sys_unlinkat) __SYSCALL(__NR_symlinkat, sys_symlinkat) #define __NR_linkat 37 __SYSCALL(__NR_linkat, sys_linkat) + #ifdef __ARCH_WANT_RENAMEAT /* renameat is superseded with flags by renameat2 */ #define __NR_renameat 38 __SYSCALL(__NR_renameat, sys_renameat) #endif /* __ARCH_WANT_RENAMEAT */ -/* fs/namespace.c */ #define __NR_umount2 39 __SYSCALL(__NR_umount2, sys_umount) #define __NR_mount 40 __SYSCALL(__NR_mount, sys_mount) #define __NR_pivot_root 41 __SYSCALL(__NR_pivot_root, sys_pivot_root) - -/* fs/nfsctl.c */ #define __NR_nfsservctl 42 __SYSCALL(__NR_nfsservctl, sys_ni_syscall) - -/* fs/open.c */ #define __NR3264_statfs 43 __SC_COMP_3264(__NR3264_statfs, sys_statfs64, sys_statfs, \ compat_sys_statfs64) @@ -161,7 +137,6 @@ __SC_COMP_3264(__NR3264_truncate, sys_truncate64, sys_truncate, \ #define __NR3264_ftruncate 46 __SC_COMP_3264(__NR3264_ftruncate, sys_ftruncate64, sys_ftruncate, \ compat_sys_ftruncate64) - #define __NR_fallocate 47 __SC_COMP(__NR_fallocate, sys_fallocate, compat_sys_fallocate) #define __NR_faccessat 48 @@ -186,20 +161,12 @@ __SYSCALL(__NR_openat, sys_openat) __SYSCALL(__NR_close, sys_close) #define __NR_vhangup 58 __SYSCALL(__NR_vhangup, sys_vhangup) - -/* fs/pipe.c */ #define __NR_pipe2 59 __SYSCALL(__NR_pipe2, sys_pipe2) - -/* fs/quota.c */ #define __NR_quotactl 60 __SYSCALL(__NR_quotactl, sys_quotactl) - -/* fs/readdir.c */ #define __NR_getdents64 61 __SYSCALL(__NR_getdents64, sys_getdents64) - -/* fs/read_write.c */ #define __NR3264_lseek 62 __SC_3264(__NR3264_lseek, sys_llseek, sys_lseek) #define __NR_read 63 @@ -218,12 +185,9 @@ __SC_COMP(__NR_pwrite64, sys_pwrite64, compat_sys_pwrite64) __SC_COMP(__NR_preadv, sys_preadv, compat_sys_preadv) #define __NR_pwritev 70 __SC_COMP(__NR_pwritev, sys_pwritev, compat_sys_pwritev) - -/* fs/sendfile.c */ #define __NR3264_sendfile 71 __SYSCALL(__NR3264_sendfile, sys_sendfile64) -/* fs/select.c */ #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_pselect6 72 __SC_COMP_3264(__NR_pselect6, sys_pselect6_time32, sys_pselect6, compat_sys_pselect6_time32) @@ -231,21 +195,17 @@ __SC_COMP_3264(__NR_pselect6, sys_pselect6_time32, sys_pselect6, compat_sys_psel __SC_COMP_3264(__NR_ppoll, sys_ppoll_time32, sys_ppoll, compat_sys_ppoll_time32) #endif -/* fs/signalfd.c */ #define __NR_signalfd4 74 __SC_COMP(__NR_signalfd4, sys_signalfd4, compat_sys_signalfd4) - -/* fs/splice.c */ #define __NR_vmsplice 75 __SYSCALL(__NR_vmsplice, sys_vmsplice) #define __NR_splice 76 __SYSCALL(__NR_splice, sys_splice) #define __NR_tee 77 __SYSCALL(__NR_tee, sys_tee) - -/* fs/stat.c */ #define __NR_readlinkat 78 __SYSCALL(__NR_readlinkat, sys_readlinkat) + #if defined(__ARCH_WANT_NEW_STAT) || defined(__ARCH_WANT_STAT64) #define __NR3264_fstatat 79 __SC_3264(__NR3264_fstatat, sys_fstatat64, sys_newfstatat) @@ -253,13 +213,13 @@ __SC_3264(__NR3264_fstatat, sys_fstatat64, sys_newfstatat) __SC_3264(__NR3264_fstat, sys_fstat64, sys_newfstat) #endif -/* fs/sync.c */ #define __NR_sync 81 __SYSCALL(__NR_sync, sys_sync) #define __NR_fsync 82 __SYSCALL(__NR_fsync, sys_fsync) #define __NR_fdatasync 83 __SYSCALL(__NR_fdatasync, sys_fdatasync) + #ifdef __ARCH_WANT_SYNC_FILE_RANGE2 #define __NR_sync_file_range2 84 __SC_COMP(__NR_sync_file_range2, sys_sync_file_range2, \ @@ -270,9 +230,9 @@ __SC_COMP(__NR_sync_file_range, sys_sync_file_range, \ compat_sys_sync_file_range) #endif -/* fs/timerfd.c */ #define __NR_timerfd_create 85 __SYSCALL(__NR_timerfd_create, sys_timerfd_create) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_timerfd_settime 86 __SC_3264(__NR_timerfd_settime, sys_timerfd_settime32, \ @@ -282,45 +242,35 @@ __SC_3264(__NR_timerfd_gettime, sys_timerfd_gettime32, \ sys_timerfd_gettime) #endif -/* fs/utimes.c */ #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_utimensat 88 __SC_3264(__NR_utimensat, sys_utimensat_time32, sys_utimensat) #endif -/* kernel/acct.c */ #define __NR_acct 89 __SYSCALL(__NR_acct, sys_acct) - -/* kernel/capability.c */ #define __NR_capget 90 __SYSCALL(__NR_capget, sys_capget) #define __NR_capset 91 __SYSCALL(__NR_capset, sys_capset) - -/* kernel/exec_domain.c */ #define __NR_personality 92 __SYSCALL(__NR_personality, sys_personality) - -/* kernel/exit.c */ #define __NR_exit 93 __SYSCALL(__NR_exit, sys_exit) #define __NR_exit_group 94 __SYSCALL(__NR_exit_group, sys_exit_group) #define __NR_waitid 95 __SC_COMP(__NR_waitid, sys_waitid, compat_sys_waitid) - -/* kernel/fork.c */ #define __NR_set_tid_address 96 __SYSCALL(__NR_set_tid_address, sys_set_tid_address) #define __NR_unshare 97 __SYSCALL(__NR_unshare, sys_unshare) -/* kernel/futex.c */ #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_futex 98 __SC_3264(__NR_futex, sys_futex_time32, sys_futex) #endif + #define __NR_set_robust_list 99 __SC_COMP(__NR_set_robust_list, sys_set_robust_list, \ compat_sys_set_robust_list) @@ -328,43 +278,40 @@ __SC_COMP(__NR_set_robust_list, sys_set_robust_list, \ __SC_COMP(__NR_get_robust_list, sys_get_robust_list, \ compat_sys_get_robust_list) -/* kernel/hrtimer.c */ #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_nanosleep 101 __SC_3264(__NR_nanosleep, sys_nanosleep_time32, sys_nanosleep) #endif -/* kernel/itimer.c */ #define __NR_getitimer 102 __SC_COMP(__NR_getitimer, sys_getitimer, compat_sys_getitimer) #define __NR_setitimer 103 __SC_COMP(__NR_setitimer, sys_setitimer, compat_sys_setitimer) - -/* kernel/kexec.c */ #define __NR_kexec_load 104 __SC_COMP(__NR_kexec_load, sys_kexec_load, compat_sys_kexec_load) - -/* kernel/module.c */ #define __NR_init_module 105 __SYSCALL(__NR_init_module, sys_init_module) #define __NR_delete_module 106 __SYSCALL(__NR_delete_module, sys_delete_module) - -/* kernel/posix-timers.c */ #define __NR_timer_create 107 __SC_COMP(__NR_timer_create, sys_timer_create, compat_sys_timer_create) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_timer_gettime 108 __SC_3264(__NR_timer_gettime, sys_timer_gettime32, sys_timer_gettime) #endif + #define __NR_timer_getoverrun 109 __SYSCALL(__NR_timer_getoverrun, sys_timer_getoverrun) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_timer_settime 110 __SC_3264(__NR_timer_settime, sys_timer_settime32, sys_timer_settime) #endif + #define __NR_timer_delete 111 __SYSCALL(__NR_timer_delete, sys_timer_delete) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_clock_settime 112 __SC_3264(__NR_clock_settime, sys_clock_settime32, sys_clock_settime) @@ -377,15 +324,10 @@ __SC_3264(__NR_clock_nanosleep, sys_clock_nanosleep_time32, \ sys_clock_nanosleep) #endif -/* kernel/printk.c */ #define __NR_syslog 116 __SYSCALL(__NR_syslog, sys_syslog) - -/* kernel/ptrace.c */ #define __NR_ptrace 117 __SC_COMP(__NR_ptrace, sys_ptrace, compat_sys_ptrace) - -/* kernel/sched/core.c */ #define __NR_sched_setparam 118 __SYSCALL(__NR_sched_setparam, sys_sched_setparam) #define __NR_sched_setscheduler 119 @@ -406,13 +348,13 @@ __SYSCALL(__NR_sched_yield, sys_sched_yield) __SYSCALL(__NR_sched_get_priority_max, sys_sched_get_priority_max) #define __NR_sched_get_priority_min 126 __SYSCALL(__NR_sched_get_priority_min, sys_sched_get_priority_min) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_sched_rr_get_interval 127 __SC_3264(__NR_sched_rr_get_interval, sys_sched_rr_get_interval_time32, \ sys_sched_rr_get_interval) #endif -/* kernel/signal.c */ #define __NR_restart_syscall 128 __SYSCALL(__NR_restart_syscall, sys_restart_syscall) #define __NR_kill 129 @@ -431,18 +373,18 @@ __SC_COMP(__NR_rt_sigaction, sys_rt_sigaction, compat_sys_rt_sigaction) __SC_COMP(__NR_rt_sigprocmask, sys_rt_sigprocmask, compat_sys_rt_sigprocmask) #define __NR_rt_sigpending 136 __SC_COMP(__NR_rt_sigpending, sys_rt_sigpending, compat_sys_rt_sigpending) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_rt_sigtimedwait 137 __SC_COMP_3264(__NR_rt_sigtimedwait, sys_rt_sigtimedwait_time32, \ sys_rt_sigtimedwait, compat_sys_rt_sigtimedwait_time32) #endif + #define __NR_rt_sigqueueinfo 138 __SC_COMP(__NR_rt_sigqueueinfo, sys_rt_sigqueueinfo, \ compat_sys_rt_sigqueueinfo) #define __NR_rt_sigreturn 139 __SC_COMP(__NR_rt_sigreturn, sys_rt_sigreturn, compat_sys_rt_sigreturn) - -/* kernel/sys.c */ #define __NR_setpriority 140 __SYSCALL(__NR_setpriority, sys_setpriority) #define __NR_getpriority 141 @@ -507,7 +449,6 @@ __SYSCALL(__NR_prctl, sys_prctl) #define __NR_getcpu 168 __SYSCALL(__NR_getcpu, sys_getcpu) -/* kernel/time.c */ #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_gettimeofday 169 __SC_COMP(__NR_gettimeofday, sys_gettimeofday, compat_sys_gettimeofday) @@ -517,7 +458,6 @@ __SC_COMP(__NR_settimeofday, sys_settimeofday, compat_sys_settimeofday) __SC_3264(__NR_adjtimex, sys_adjtimex_time32, sys_adjtimex) #endif -/* kernel/sys.c */ #define __NR_getpid 172 __SYSCALL(__NR_getpid, sys_getpid) #define __NR_getppid 173 @@ -534,12 +474,11 @@ __SYSCALL(__NR_getegid, sys_getegid) __SYSCALL(__NR_gettid, sys_gettid) #define __NR_sysinfo 179 __SC_COMP(__NR_sysinfo, sys_sysinfo, compat_sys_sysinfo) - -/* ipc/mqueue.c */ #define __NR_mq_open 180 __SC_COMP(__NR_mq_open, sys_mq_open, compat_sys_mq_open) #define __NR_mq_unlink 181 __SYSCALL(__NR_mq_unlink, sys_mq_unlink) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_mq_timedsend 182 __SC_3264(__NR_mq_timedsend, sys_mq_timedsend_time32, sys_mq_timedsend) @@ -547,12 +486,11 @@ __SC_3264(__NR_mq_timedsend, sys_mq_timedsend_time32, sys_mq_timedsend) __SC_3264(__NR_mq_timedreceive, sys_mq_timedreceive_time32, \ sys_mq_timedreceive) #endif + #define __NR_mq_notify 184 __SC_COMP(__NR_mq_notify, sys_mq_notify, compat_sys_mq_notify) #define __NR_mq_getsetattr 185 __SC_COMP(__NR_mq_getsetattr, sys_mq_getsetattr, compat_sys_mq_getsetattr) - -/* ipc/msg.c */ #define __NR_msgget 186 __SYSCALL(__NR_msgget, sys_msgget) #define __NR_msgctl 187 @@ -561,20 +499,18 @@ __SC_COMP(__NR_msgctl, sys_msgctl, compat_sys_msgctl) __SC_COMP(__NR_msgrcv, sys_msgrcv, compat_sys_msgrcv) #define __NR_msgsnd 189 __SC_COMP(__NR_msgsnd, sys_msgsnd, compat_sys_msgsnd) - -/* ipc/sem.c */ #define __NR_semget 190 __SYSCALL(__NR_semget, sys_semget) #define __NR_semctl 191 __SC_COMP(__NR_semctl, sys_semctl, compat_sys_semctl) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_semtimedop 192 __SC_3264(__NR_semtimedop, sys_semtimedop_time32, sys_semtimedop) #endif + #define __NR_semop 193 __SYSCALL(__NR_semop, sys_semop) - -/* ipc/shm.c */ #define __NR_shmget 194 __SYSCALL(__NR_shmget, sys_shmget) #define __NR_shmctl 195 @@ -583,8 +519,6 @@ __SC_COMP(__NR_shmctl, sys_shmctl, compat_sys_shmctl) __SC_COMP(__NR_shmat, sys_shmat, compat_sys_shmat) #define __NR_shmdt 197 __SYSCALL(__NR_shmdt, sys_shmdt) - -/* net/socket.c */ #define __NR_socket 198 __SYSCALL(__NR_socket, sys_socket) #define __NR_socketpair 199 @@ -615,40 +549,30 @@ __SYSCALL(__NR_shutdown, sys_shutdown) __SC_COMP(__NR_sendmsg, sys_sendmsg, compat_sys_sendmsg) #define __NR_recvmsg 212 __SC_COMP(__NR_recvmsg, sys_recvmsg, compat_sys_recvmsg) - -/* mm/filemap.c */ #define __NR_readahead 213 __SC_COMP(__NR_readahead, sys_readahead, compat_sys_readahead) - -/* mm/nommu.c, also with MMU */ #define __NR_brk 214 __SYSCALL(__NR_brk, sys_brk) #define __NR_munmap 215 __SYSCALL(__NR_munmap, sys_munmap) #define __NR_mremap 216 __SYSCALL(__NR_mremap, sys_mremap) - -/* security/keys/keyctl.c */ #define __NR_add_key 217 __SYSCALL(__NR_add_key, sys_add_key) #define __NR_request_key 218 __SYSCALL(__NR_request_key, sys_request_key) #define __NR_keyctl 219 __SC_COMP(__NR_keyctl, sys_keyctl, compat_sys_keyctl) - -/* arch/example/kernel/sys_example.c */ #define __NR_clone 220 __SYSCALL(__NR_clone, sys_clone) #define __NR_execve 221 __SC_COMP(__NR_execve, sys_execve, compat_sys_execve) - #define __NR3264_mmap 222 __SC_3264(__NR3264_mmap, sys_mmap2, sys_mmap) -/* mm/fadvise.c */ #define __NR3264_fadvise64 223 __SC_COMP(__NR3264_fadvise64, sys_fadvise64_64, compat_sys_fadvise64_64) -/* mm/, CONFIG_MMU only */ +/* CONFIG_MMU only */ #ifndef __ARCH_NOMMU #define __NR_swapon 224 __SYSCALL(__NR_swapon, sys_swapon) @@ -691,6 +615,7 @@ __SC_COMP(__NR_rt_tgsigqueueinfo, sys_rt_tgsigqueueinfo, \ __SYSCALL(__NR_perf_event_open, sys_perf_event_open) #define __NR_accept4 242 __SYSCALL(__NR_accept4, sys_accept4) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_recvmmsg 243 __SC_COMP_3264(__NR_recvmmsg, sys_recvmmsg_time32, sys_recvmmsg, compat_sys_recvmmsg_time32) @@ -706,6 +631,7 @@ __SC_COMP_3264(__NR_recvmmsg, sys_recvmmsg_time32, sys_recvmmsg, compat_sys_recv #define __NR_wait4 260 __SC_COMP(__NR_wait4, sys_wait4, compat_sys_wait4) #endif + #define __NR_prlimit64 261 __SYSCALL(__NR_prlimit64, sys_prlimit64) #define __NR_fanotify_init 262 @@ -716,10 +642,12 @@ __SYSCALL(__NR_fanotify_mark, sys_fanotify_mark) __SYSCALL(__NR_name_to_handle_at, sys_name_to_handle_at) #define __NR_open_by_handle_at 265 __SYSCALL(__NR_open_by_handle_at, sys_open_by_handle_at) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_clock_adjtime 266 __SC_3264(__NR_clock_adjtime, sys_clock_adjtime32, sys_clock_adjtime) #endif + #define __NR_syncfs 267 __SYSCALL(__NR_syncfs, sys_syncfs) #define __NR_setns 268 @@ -770,15 +698,19 @@ __SYSCALL(__NR_pkey_alloc, sys_pkey_alloc) __SYSCALL(__NR_pkey_free, sys_pkey_free) #define __NR_statx 291 __SYSCALL(__NR_statx, sys_statx) + #if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32 #define __NR_io_pgetevents 292 __SC_COMP_3264(__NR_io_pgetevents, sys_io_pgetevents_time32, sys_io_pgetevents, compat_sys_io_pgetevents) #endif + #define __NR_rseq 293 __SYSCALL(__NR_rseq, sys_rseq) #define __NR_kexec_file_load 294 __SYSCALL(__NR_kexec_file_load, sys_kexec_file_load) + /* 295 through 402 are unassigned to sync up with generic numbers, don't use */ + #if defined(__SYSCALL_COMPAT) || __BITS_PER_LONG == 32 #define __NR_clock_gettime64 403 __SYSCALL(__NR_clock_gettime64, sys_clock_gettime) @@ -844,13 +776,14 @@ __SYSCALL(__NR_fsmount, sys_fsmount) __SYSCALL(__NR_fspick, sys_fspick) #define __NR_pidfd_open 434 __SYSCALL(__NR_pidfd_open, sys_pidfd_open) + #ifdef __ARCH_WANT_SYS_CLONE3 #define __NR_clone3 435 __SYSCALL(__NR_clone3, sys_clone3) #endif + #define __NR_close_range 436 __SYSCALL(__NR_close_range, sys_close_range) - #define __NR_openat2 437 __SYSCALL(__NR_openat2, sys_openat2) #define __NR_pidfd_getfd 438 @@ -865,7 +798,6 @@ __SC_COMP(__NR_epoll_pwait2, sys_epoll_pwait2, compat_sys_epoll_pwait2) __SYSCALL(__NR_mount_setattr, sys_mount_setattr) #define __NR_quotactl_fd 443 __SYSCALL(__NR_quotactl_fd, sys_quotactl_fd) - #define __NR_landlock_create_ruleset 444 __SYSCALL(__NR_landlock_create_ruleset, sys_landlock_create_ruleset) #define __NR_landlock_add_rule 445 @@ -877,12 +809,11 @@ __SYSCALL(__NR_landlock_restrict_self, sys_landlock_restrict_self) #define __NR_memfd_secret 447 __SYSCALL(__NR_memfd_secret, sys_memfd_secret) #endif + #define __NR_process_mrelease 448 __SYSCALL(__NR_process_mrelease, sys_process_mrelease) - #define __NR_futex_waitv 449 __SYSCALL(__NR_futex_waitv, sys_futex_waitv) - #define __NR_set_mempolicy_home_node 450 __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node) diff --git a/tools/include/uapi/asm/bitsperlong.h b/tools/include/uapi/asm/bitsperlong.h index da5206517158..c65267afc341 100644 --- a/tools/include/uapi/asm/bitsperlong.h +++ b/tools/include/uapi/asm/bitsperlong.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ #if defined(__i386__) || defined(__x86_64__) #include "../../../arch/x86/include/uapi/asm/bitsperlong.h" -#elif defined(__aarch64__) -#include "../../../arch/arm64/include/uapi/asm/bitsperlong.h" #elif defined(__powerpc__) #include "../../../arch/powerpc/include/uapi/asm/bitsperlong.h" #elif defined(__s390__) @@ -13,12 +11,8 @@ #include "../../../arch/mips/include/uapi/asm/bitsperlong.h" #elif defined(__ia64__) #include "../../../arch/ia64/include/uapi/asm/bitsperlong.h" -#elif defined(__riscv) -#include "../../../arch/riscv/include/uapi/asm/bitsperlong.h" #elif defined(__alpha__) #include "../../../arch/alpha/include/uapi/asm/bitsperlong.h" -#elif defined(__loongarch__) -#include "../../../arch/loongarch/include/uapi/asm/bitsperlong.h" #else #include <asm-generic/bitsperlong.h> #endif diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 1bb11a6ee667..60a9d59beeab 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1035,6 +1035,7 @@ enum bpf_attach_type { BPF_TRACE_KPROBE_MULTI, BPF_LSM_CGROUP, BPF_STRUCT_OPS, + BPF_NETFILTER, __MAX_BPF_ATTACH_TYPE }; @@ -1272,6 +1273,9 @@ enum { /* Create a map that will be registered/unregesitered by the backed bpf_link */ BPF_F_LINK = (1U << 13), + +/* Get path from provided FD in BPF_OBJ_PIN/BPF_OBJ_GET commands */ + BPF_F_PATH_FD = (1U << 14), }; /* Flags for BPF_PROG_QUERY. */ @@ -1420,6 +1424,13 @@ union bpf_attr { __aligned_u64 pathname; __u32 bpf_fd; __u32 file_flags; + /* Same as dirfd in openat() syscall; see openat(2) + * manpage for details of path FD and pathname semantics; + * path_fd should accompanied by BPF_F_PATH_FD flag set in + * file_flags field, otherwise it should be set to zero; + * if BPF_F_PATH_FD flag is not set, AT_FDCWD is assumed. + */ + __s32 path_fd; }; struct { /* anonymous struct used by BPF_PROG_ATTACH/DETACH commands */ @@ -3167,6 +3178,10 @@ union bpf_attr { * **BPF_FIB_LOOKUP_DIRECT** * Do a direct table lookup vs full lookup using FIB * rules. + * **BPF_FIB_LOOKUP_TBID** + * Used with BPF_FIB_LOOKUP_DIRECT. + * Use the routing table ID present in *params*->tbid + * for the fib lookup. * **BPF_FIB_LOOKUP_OUTPUT** * Perform lookup from an egress perspective (default is * ingress). @@ -6821,6 +6836,7 @@ enum { BPF_FIB_LOOKUP_DIRECT = (1U << 0), BPF_FIB_LOOKUP_OUTPUT = (1U << 1), BPF_FIB_LOOKUP_SKIP_NEIGH = (1U << 2), + BPF_FIB_LOOKUP_TBID = (1U << 3), }; enum { @@ -6881,9 +6897,19 @@ struct bpf_fib_lookup { __u32 ipv6_dst[4]; /* in6_addr; network order */ }; - /* output */ - __be16 h_vlan_proto; - __be16 h_vlan_TCI; + union { + struct { + /* output */ + __be16 h_vlan_proto; + __be16 h_vlan_TCI; + }; + /* input: when accompanied with the + * 'BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_TBID` flags, a + * specific routing table to use for the fib lookup. + */ + __u32 tbid; + }; + __u8 smac[6]; /* ETH_ALEN */ __u8 dmac[6]; /* ETH_ALEN */ }; diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c index 128ac723c4ea..ed86b37d8024 100644 --- a/tools/lib/bpf/bpf.c +++ b/tools/lib/bpf/bpf.c @@ -572,20 +572,30 @@ int bpf_map_update_batch(int fd, const void *keys, const void *values, __u32 *co (void *)keys, (void *)values, count, opts); } -int bpf_obj_pin(int fd, const char *pathname) +int bpf_obj_pin_opts(int fd, const char *pathname, const struct bpf_obj_pin_opts *opts) { - const size_t attr_sz = offsetofend(union bpf_attr, file_flags); + const size_t attr_sz = offsetofend(union bpf_attr, path_fd); union bpf_attr attr; int ret; + if (!OPTS_VALID(opts, bpf_obj_pin_opts)) + return libbpf_err(-EINVAL); + memset(&attr, 0, attr_sz); + attr.path_fd = OPTS_GET(opts, path_fd, 0); attr.pathname = ptr_to_u64((void *)pathname); + attr.file_flags = OPTS_GET(opts, file_flags, 0); attr.bpf_fd = fd; ret = sys_bpf(BPF_OBJ_PIN, &attr, attr_sz); return libbpf_err_errno(ret); } +int bpf_obj_pin(int fd, const char *pathname) +{ + return bpf_obj_pin_opts(fd, pathname, NULL); +} + int bpf_obj_get(const char *pathname) { return bpf_obj_get_opts(pathname, NULL); @@ -593,7 +603,7 @@ int bpf_obj_get(const char *pathname) int bpf_obj_get_opts(const char *pathname, const struct bpf_obj_get_opts *opts) { - const size_t attr_sz = offsetofend(union bpf_attr, file_flags); + const size_t attr_sz = offsetofend(union bpf_attr, path_fd); union bpf_attr attr; int fd; @@ -601,6 +611,7 @@ int bpf_obj_get_opts(const char *pathname, const struct bpf_obj_get_opts *opts) return libbpf_err(-EINVAL); memset(&attr, 0, attr_sz); + attr.path_fd = OPTS_GET(opts, path_fd, 0); attr.pathname = ptr_to_u64((void *)pathname); attr.file_flags = OPTS_GET(opts, file_flags, 0); diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h index a2c091389b18..9aa0ee473754 100644 --- a/tools/lib/bpf/bpf.h +++ b/tools/lib/bpf/bpf.h @@ -284,16 +284,30 @@ LIBBPF_API int bpf_map_update_batch(int fd, const void *keys, const void *values __u32 *count, const struct bpf_map_batch_opts *opts); -struct bpf_obj_get_opts { +struct bpf_obj_pin_opts { size_t sz; /* size of this struct for forward/backward compatibility */ __u32 file_flags; + int path_fd; size_t :0; }; -#define bpf_obj_get_opts__last_field file_flags +#define bpf_obj_pin_opts__last_field path_fd LIBBPF_API int bpf_obj_pin(int fd, const char *pathname); +LIBBPF_API int bpf_obj_pin_opts(int fd, const char *pathname, + const struct bpf_obj_pin_opts *opts); + +struct bpf_obj_get_opts { + size_t sz; /* size of this struct for forward/backward compatibility */ + + __u32 file_flags; + int path_fd; + + size_t :0; +}; +#define bpf_obj_get_opts__last_field path_fd + LIBBPF_API int bpf_obj_get(const char *pathname); LIBBPF_API int bpf_obj_get_opts(const char *pathname, const struct bpf_obj_get_opts *opts); diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h index 929a3baca8ef..bbab9ad9dc5a 100644 --- a/tools/lib/bpf/bpf_helpers.h +++ b/tools/lib/bpf/bpf_helpers.h @@ -77,16 +77,21 @@ /* * Helper macros to manipulate data structures */ -#ifndef offsetof -#define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER) -#endif -#ifndef container_of + +/* offsetof() definition that uses __builtin_offset() might not preserve field + * offset CO-RE relocation properly, so force-redefine offsetof() using + * old-school approach which works with CO-RE correctly + */ +#undef offsetof +#define offsetof(type, member) ((unsigned long)&((type *)0)->member) + +/* redefined container_of() to ensure we use the above offsetof() macro */ +#undef container_of #define container_of(ptr, type, member) \ ({ \ void *__mptr = (void *)(ptr); \ ((type *)(__mptr - offsetof(type, member))); \ }) -#endif /* * Compiler (optimization) barrier. diff --git a/tools/lib/bpf/bpf_tracing.h b/tools/lib/bpf/bpf_tracing.h index 6fb3d0f9af17..be076a4041ab 100644 --- a/tools/lib/bpf/bpf_tracing.h +++ b/tools/lib/bpf/bpf_tracing.h @@ -351,6 +351,7 @@ struct pt_regs___arm64 { * https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc#risc-v-calling-conventions */ +/* riscv provides struct user_regs_struct instead of struct pt_regs to userspace */ #define __PT_REGS_CAST(x) ((const struct user_regs_struct *)(x)) #define __PT_PARM1_REG a0 #define __PT_PARM2_REG a1 @@ -383,7 +384,7 @@ struct pt_regs___arm64 { * https://raw.githubusercontent.com/wiki/foss-for-synopsys-dwc-arc-processors/toolchain/files/ARCv2_ABI.pdf */ -/* arc provides struct user_pt_regs instead of struct pt_regs to userspace */ +/* arc provides struct user_regs_struct instead of struct pt_regs to userspace */ #define __PT_REGS_CAST(x) ((const struct user_regs_struct *)(x)) #define __PT_PARM1_REG scratch.r0 #define __PT_PARM2_REG scratch.r1 diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c index 0a2c079244b6..8484b563b53d 100644 --- a/tools/lib/bpf/btf.c +++ b/tools/lib/bpf/btf.c @@ -1064,7 +1064,7 @@ static struct btf *btf_parse_raw(const char *path, struct btf *base_btf) int err = 0; long sz; - f = fopen(path, "rb"); + f = fopen(path, "rbe"); if (!f) { err = -errno; goto err_out; diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c index 580985ee5545..4d9f30bf7f01 100644 --- a/tools/lib/bpf/btf_dump.c +++ b/tools/lib/bpf/btf_dump.c @@ -2250,9 +2250,25 @@ static int btf_dump_type_data_check_overflow(struct btf_dump *d, const struct btf_type *t, __u32 id, const void *data, - __u8 bits_offset) + __u8 bits_offset, + __u8 bit_sz) { - __s64 size = btf__resolve_size(d->btf, id); + __s64 size; + + if (bit_sz) { + /* bits_offset is at most 7. bit_sz is at most 128. */ + __u8 nr_bytes = (bits_offset + bit_sz + 7) / 8; + + /* When bit_sz is non zero, it is called from + * btf_dump_struct_data() where it only cares about + * negative error value. + * Return nr_bytes in success case to make it + * consistent as the regular integer case below. + */ + return data + nr_bytes > d->typed_dump->data_end ? -E2BIG : nr_bytes; + } + + size = btf__resolve_size(d->btf, id); if (size < 0 || size >= INT_MAX) { pr_warn("unexpected size [%zu] for id [%u]\n", @@ -2407,7 +2423,7 @@ static int btf_dump_dump_type_data(struct btf_dump *d, { int size, err = 0; - size = btf_dump_type_data_check_overflow(d, t, id, data, bits_offset); + size = btf_dump_type_data_check_overflow(d, t, id, data, bits_offset, bit_sz); if (size < 0) return size; err = btf_dump_type_data_check_zero(d, t, id, data, bits_offset, bit_sz); diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c index 83e8e3bfd8ff..cf3323fd47b8 100644 --- a/tools/lib/bpf/gen_loader.c +++ b/tools/lib/bpf/gen_loader.c @@ -703,17 +703,17 @@ static void emit_relo_kfunc_btf(struct bpf_gen *gen, struct ksym_relo_desc *relo /* obtain fd in BPF_REG_9 */ emit(gen, BPF_MOV64_REG(BPF_REG_9, BPF_REG_7)); emit(gen, BPF_ALU64_IMM(BPF_RSH, BPF_REG_9, 32)); - /* jump to fd_array store if fd denotes module BTF */ - emit(gen, BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0, 2)); - /* set the default value for off */ - emit(gen, BPF_ST_MEM(BPF_H, BPF_REG_8, offsetof(struct bpf_insn, off), 0)); - /* skip BTF fd store for vmlinux BTF */ - emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, 4)); /* load fd_array slot pointer */ emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_0, BPF_PSEUDO_MAP_IDX_VALUE, 0, 0, 0, blob_fd_array_off(gen, btf_fd_idx))); - /* store BTF fd in slot */ + /* store BTF fd in slot, 0 for vmlinux */ emit(gen, BPF_STX_MEM(BPF_W, BPF_REG_0, BPF_REG_9, 0)); + /* jump to insn[insn_idx].off store if fd denotes module BTF */ + emit(gen, BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0, 2)); + /* set the default value for off */ + emit(gen, BPF_ST_MEM(BPF_H, BPF_REG_8, offsetof(struct bpf_insn, off), 0)); + /* skip BTF fd store for vmlinux BTF */ + emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, 1)); /* store index into insn[insn_idx].off */ emit(gen, BPF_ST_MEM(BPF_H, BPF_REG_8, offsetof(struct bpf_insn, off), btf_fd_idx)); log: diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index ad1ec893b41b..214f828ece6b 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -117,6 +117,7 @@ static const char * const attach_type_name[] = { [BPF_PERF_EVENT] = "perf_event", [BPF_TRACE_KPROBE_MULTI] = "trace_kprobe_multi", [BPF_STRUCT_OPS] = "struct_ops", + [BPF_NETFILTER] = "netfilter", }; static const char * const link_type_name[] = { @@ -1500,16 +1501,36 @@ static struct bpf_map *bpf_object__add_map(struct bpf_object *obj) return map; } -static size_t bpf_map_mmap_sz(const struct bpf_map *map) +static size_t bpf_map_mmap_sz(unsigned int value_sz, unsigned int max_entries) { - long page_sz = sysconf(_SC_PAGE_SIZE); + const long page_sz = sysconf(_SC_PAGE_SIZE); size_t map_sz; - map_sz = (size_t)roundup(map->def.value_size, 8) * map->def.max_entries; + map_sz = (size_t)roundup(value_sz, 8) * max_entries; map_sz = roundup(map_sz, page_sz); return map_sz; } +static int bpf_map_mmap_resize(struct bpf_map *map, size_t old_sz, size_t new_sz) +{ + void *mmaped; + + if (!map->mmaped) + return -EINVAL; + + if (old_sz == new_sz) + return 0; + + mmaped = mmap(NULL, new_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (mmaped == MAP_FAILED) + return -errno; + + memcpy(mmaped, map->mmaped, min(old_sz, new_sz)); + munmap(map->mmaped, old_sz); + map->mmaped = mmaped; + return 0; +} + static char *internal_map_name(struct bpf_object *obj, const char *real_name) { char map_name[BPF_OBJ_NAME_LEN], *p; @@ -1608,6 +1629,7 @@ bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type, { struct bpf_map_def *def; struct bpf_map *map; + size_t mmap_sz; int err; map = bpf_object__add_map(obj); @@ -1642,7 +1664,8 @@ bpf_object__init_internal_map(struct bpf_object *obj, enum libbpf_map_type type, pr_debug("map '%s' (global data): at sec_idx %d, offset %zu, flags %x.\n", map->name, map->sec_idx, map->sec_offset, def->map_flags); - map->mmaped = mmap(NULL, bpf_map_mmap_sz(map), PROT_READ | PROT_WRITE, + mmap_sz = bpf_map_mmap_sz(map->def.value_size, map->def.max_entries); + map->mmaped = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (map->mmaped == MAP_FAILED) { err = -errno; @@ -4329,7 +4352,7 @@ static int bpf_get_map_info_from_fdinfo(int fd, struct bpf_map_info *info) snprintf(file, sizeof(file), "/proc/%d/fdinfo/%d", getpid(), fd); memset(info, 0, sizeof(*info)); - fp = fopen(file, "r"); + fp = fopen(file, "re"); if (!fp) { err = -errno; pr_warn("failed to open %s: %d. No procfs support?\n", file, @@ -4392,18 +4415,17 @@ int bpf_map__reuse_fd(struct bpf_map *map, int fd) if (!new_name) return libbpf_err(-errno); - new_fd = open("/", O_RDONLY | O_CLOEXEC); + /* + * Like dup(), but make sure new FD is >= 3 and has O_CLOEXEC set. + * This is similar to what we do in ensure_good_fd(), but without + * closing original FD. + */ + new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); if (new_fd < 0) { err = -errno; goto err_free_new_name; } - new_fd = dup3(fd, new_fd, O_CLOEXEC); - if (new_fd < 0) { - err = -errno; - goto err_close_new_fd; - } - err = zclose(map->fd); if (err) { err = -errno; @@ -7433,7 +7455,7 @@ int libbpf_kallsyms_parse(kallsyms_cb_t cb, void *ctx) int ret, err = 0; FILE *f; - f = fopen("/proc/kallsyms", "r"); + f = fopen("/proc/kallsyms", "re"); if (!f) { err = -errno; pr_warn("failed to open /proc/kallsyms: %d\n", err); @@ -8294,7 +8316,10 @@ static void bpf_map__destroy(struct bpf_map *map) map->init_slots_sz = 0; if (map->mmaped) { - munmap(map->mmaped, bpf_map_mmap_sz(map)); + size_t mmap_sz; + + mmap_sz = bpf_map_mmap_sz(map->def.value_size, map->def.max_entries); + munmap(map->mmaped, mmap_sz); map->mmaped = NULL; } @@ -8712,7 +8737,7 @@ static const struct bpf_sec_def section_defs[] = { SEC_DEF("struct_ops+", STRUCT_OPS, 0, SEC_NONE), SEC_DEF("struct_ops.s+", STRUCT_OPS, 0, SEC_SLEEPABLE), SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE), - SEC_DEF("netfilter", NETFILTER, 0, SEC_NONE), + SEC_DEF("netfilter", NETFILTER, BPF_NETFILTER, SEC_NONE), }; static size_t custom_sec_def_cnt; @@ -9412,10 +9437,103 @@ __u32 bpf_map__value_size(const struct bpf_map *map) return map->def.value_size; } +static int map_btf_datasec_resize(struct bpf_map *map, __u32 size) +{ + struct btf *btf; + struct btf_type *datasec_type, *var_type; + struct btf_var_secinfo *var; + const struct btf_type *array_type; + const struct btf_array *array; + int vlen, element_sz, new_array_id; + __u32 nr_elements; + + /* check btf existence */ + btf = bpf_object__btf(map->obj); + if (!btf) + return -ENOENT; + + /* verify map is datasec */ + datasec_type = btf_type_by_id(btf, bpf_map__btf_value_type_id(map)); + if (!btf_is_datasec(datasec_type)) { + pr_warn("map '%s': cannot be resized, map value type is not a datasec\n", + bpf_map__name(map)); + return -EINVAL; + } + + /* verify datasec has at least one var */ + vlen = btf_vlen(datasec_type); + if (vlen == 0) { + pr_warn("map '%s': cannot be resized, map value datasec is empty\n", + bpf_map__name(map)); + return -EINVAL; + } + + /* verify last var in the datasec is an array */ + var = &btf_var_secinfos(datasec_type)[vlen - 1]; + var_type = btf_type_by_id(btf, var->type); + array_type = skip_mods_and_typedefs(btf, var_type->type, NULL); + if (!btf_is_array(array_type)) { + pr_warn("map '%s': cannot be resized, last var must be an array\n", + bpf_map__name(map)); + return -EINVAL; + } + + /* verify request size aligns with array */ + array = btf_array(array_type); + element_sz = btf__resolve_size(btf, array->type); + if (element_sz <= 0 || (size - var->offset) % element_sz != 0) { + pr_warn("map '%s': cannot be resized, element size (%d) doesn't align with new total size (%u)\n", + bpf_map__name(map), element_sz, size); + return -EINVAL; + } + + /* create a new array based on the existing array, but with new length */ + nr_elements = (size - var->offset) / element_sz; + new_array_id = btf__add_array(btf, array->index_type, array->type, nr_elements); + if (new_array_id < 0) + return new_array_id; + + /* adding a new btf type invalidates existing pointers to btf objects, + * so refresh pointers before proceeding + */ + datasec_type = btf_type_by_id(btf, map->btf_value_type_id); + var = &btf_var_secinfos(datasec_type)[vlen - 1]; + var_type = btf_type_by_id(btf, var->type); + + /* finally update btf info */ + datasec_type->size = size; + var->size = size - var->offset; + var_type->type = new_array_id; + + return 0; +} + int bpf_map__set_value_size(struct bpf_map *map, __u32 size) { if (map->fd >= 0) return libbpf_err(-EBUSY); + + if (map->mmaped) { + int err; + size_t mmap_old_sz, mmap_new_sz; + + mmap_old_sz = bpf_map_mmap_sz(map->def.value_size, map->def.max_entries); + mmap_new_sz = bpf_map_mmap_sz(size, map->def.max_entries); + err = bpf_map_mmap_resize(map, mmap_old_sz, mmap_new_sz); + if (err) { + pr_warn("map '%s': failed to resize memory-mapped region: %d\n", + bpf_map__name(map), err); + return err; + } + err = map_btf_datasec_resize(map, size); + if (err && err != -ENOENT) { + pr_warn("map '%s': failed to adjust resized BTF, clearing BTF key/value info: %d\n", + bpf_map__name(map), err); + map->btf_value_type_id = 0; + map->btf_key_type_id = 0; + } + } + map->def.value_size = size; return 0; } @@ -9441,7 +9559,7 @@ int bpf_map__set_initial_value(struct bpf_map *map, return 0; } -const void *bpf_map__initial_value(struct bpf_map *map, size_t *psize) +void *bpf_map__initial_value(struct bpf_map *map, size_t *psize) { if (!map->mmaped) return NULL; @@ -9957,7 +10075,7 @@ static int parse_uint_from_file(const char *file, const char *fmt) int err, ret; FILE *f; - f = fopen(file, "r"); + f = fopen(file, "re"); if (!f) { err = -errno; pr_debug("failed to open '%s': %s\n", file, @@ -12693,7 +12811,7 @@ int bpf_object__load_skeleton(struct bpf_object_skeleton *s) for (i = 0; i < s->map_cnt; i++) { struct bpf_map *map = *s->maps[i].map; - size_t mmap_sz = bpf_map_mmap_sz(map); + size_t mmap_sz = bpf_map_mmap_sz(map->def.value_size, map->def.max_entries); int prot, map_fd = bpf_map__fd(map); void **mmaped = s->maps[i].mmaped; @@ -12720,8 +12838,7 @@ int bpf_object__load_skeleton(struct bpf_object_skeleton *s) * as per normal clean up procedure, so we don't need to worry * about it from skeleton's clean up perspective. */ - *mmaped = mmap(map->mmaped, mmap_sz, prot, - MAP_SHARED | MAP_FIXED, map_fd, 0); + *mmaped = mmap(map->mmaped, mmap_sz, prot, MAP_SHARED | MAP_FIXED, map_fd, 0); if (*mmaped == MAP_FAILED) { err = -errno; *mmaped = NULL; diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 0b7362397ea3..754da73c643b 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -869,8 +869,22 @@ LIBBPF_API int bpf_map__set_numa_node(struct bpf_map *map, __u32 numa_node); /* get/set map key size */ LIBBPF_API __u32 bpf_map__key_size(const struct bpf_map *map); LIBBPF_API int bpf_map__set_key_size(struct bpf_map *map, __u32 size); -/* get/set map value size */ +/* get map value size */ LIBBPF_API __u32 bpf_map__value_size(const struct bpf_map *map); +/** + * @brief **bpf_map__set_value_size()** sets map value size. + * @param map the BPF map instance + * @return 0, on success; negative error, otherwise + * + * There is a special case for maps with associated memory-mapped regions, like + * the global data section maps (bss, data, rodata). When this function is used + * on such a map, the mapped region is resized. Afterward, an attempt is made to + * adjust the corresponding BTF info. This attempt is best-effort and can only + * succeed if the last variable of the data section map is an array. The array + * BTF type is replaced by a new BTF array type with a different length. + * Any previously existing pointers returned from bpf_map__initial_value() or + * corresponding data section skeleton pointer must be reinitialized. + */ LIBBPF_API int bpf_map__set_value_size(struct bpf_map *map, __u32 size); /* get map key/value BTF type IDs */ LIBBPF_API __u32 bpf_map__btf_key_type_id(const struct bpf_map *map); @@ -884,7 +898,7 @@ LIBBPF_API int bpf_map__set_map_extra(struct bpf_map *map, __u64 map_extra); LIBBPF_API int bpf_map__set_initial_value(struct bpf_map *map, const void *data, size_t size); -LIBBPF_API const void *bpf_map__initial_value(struct bpf_map *map, size_t *psize); +LIBBPF_API void *bpf_map__initial_value(struct bpf_map *map, size_t *psize); /** * @brief **bpf_map__is_internal()** tells the caller whether or not the diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index a5aa3a383d69..7521a2fb7626 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -391,3 +391,8 @@ LIBBPF_1.2.0 { bpf_map_get_info_by_fd; bpf_prog_get_info_by_fd; } LIBBPF_1.1.0; + +LIBBPF_1.3.0 { + global: + bpf_obj_pin_opts; +} LIBBPF_1.2.0; diff --git a/tools/lib/bpf/libbpf_probes.c b/tools/lib/bpf/libbpf_probes.c index 6065f408a59c..9c4db90b92b6 100644 --- a/tools/lib/bpf/libbpf_probes.c +++ b/tools/lib/bpf/libbpf_probes.c @@ -38,7 +38,7 @@ static __u32 get_ubuntu_kernel_version(void) if (faccessat(AT_FDCWD, ubuntu_kver_file, R_OK, AT_EACCESS) != 0) return 0; - f = fopen(ubuntu_kver_file, "r"); + f = fopen(ubuntu_kver_file, "re"); if (!f) return 0; @@ -180,7 +180,9 @@ static int probe_prog_load(enum bpf_prog_type prog_type, case BPF_PROG_TYPE_SK_REUSEPORT: case BPF_PROG_TYPE_FLOW_DISSECTOR: case BPF_PROG_TYPE_CGROUP_SYSCTL: + break; case BPF_PROG_TYPE_NETFILTER: + opts.expected_attach_type = BPF_NETFILTER; break; default: return -EOPNOTSUPP; diff --git a/tools/lib/bpf/libbpf_version.h b/tools/lib/bpf/libbpf_version.h index 1fd2eeac5cfc..290411ddb39e 100644 --- a/tools/lib/bpf/libbpf_version.h +++ b/tools/lib/bpf/libbpf_version.h @@ -4,6 +4,6 @@ #define __LIBBPF_VERSION_H #define LIBBPF_MAJOR_VERSION 1 -#define LIBBPF_MINOR_VERSION 2 +#define LIBBPF_MINOR_VERSION 3 #endif /* __LIBBPF_VERSION_H */ diff --git a/tools/lib/bpf/usdt.c b/tools/lib/bpf/usdt.c index 086eef355ab3..f1a141555f08 100644 --- a/tools/lib/bpf/usdt.c +++ b/tools/lib/bpf/usdt.c @@ -466,7 +466,7 @@ static int parse_vma_segs(int pid, const char *lib_path, struct elf_seg **segs, proceed: sprintf(line, "/proc/%d/maps", pid); - f = fopen(line, "r"); + f = fopen(line, "re"); if (!f) { err = -errno; pr_warn("usdt: failed to open '%s' to get base addr of '%s': %d\n", @@ -954,8 +954,7 @@ struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct spec_map_fd = bpf_map__fd(man->specs_map); ip_map_fd = bpf_map__fd(man->ip_to_spec_id_map); - /* TODO: perform path resolution similar to uprobe's */ - fd = open(path, O_RDONLY); + fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { err = -errno; pr_warn("usdt: failed to open ELF binary '%s': %d\n", path, err); diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h index 41b9b942504d..8e9147358a28 100644 --- a/tools/lib/subcmd/parse-options.h +++ b/tools/lib/subcmd/parse-options.h @@ -6,10 +6,6 @@ #include <stdbool.h> #include <stdint.h> -#ifndef NORETURN -#define NORETURN __attribute__((__noreturn__)) -#endif - enum parse_opt_type { /* special types */ OPTION_END, @@ -183,9 +179,9 @@ extern int parse_options_subcommand(int argc, const char **argv, const char *const subcommands[], const char *usagestr[], int flags); -extern NORETURN void usage_with_options(const char * const *usagestr, +extern __noreturn void usage_with_options(const char * const *usagestr, const struct option *options); -extern NORETURN __attribute__((format(printf,3,4))) +extern __noreturn __attribute__((format(printf,3,4))) void usage_with_options_msg(const char * const *usagestr, const struct option *options, const char *fmt, ...); diff --git a/tools/lib/subcmd/subcmd-util.h b/tools/lib/subcmd/subcmd-util.h index b2aec04fce8f..dfac76e35ac7 100644 --- a/tools/lib/subcmd/subcmd-util.h +++ b/tools/lib/subcmd/subcmd-util.h @@ -5,8 +5,7 @@ #include <stdarg.h> #include <stdlib.h> #include <stdio.h> - -#define NORETURN __attribute__((__noreturn__)) +#include <linux/compiler.h> static inline void report(const char *prefix, const char *err, va_list params) { @@ -15,7 +14,7 @@ static inline void report(const char *prefix, const char *err, va_list params) fprintf(stderr, " %s%s\n", prefix, msg); } -static NORETURN inline void die(const char *err, ...) +static __noreturn inline void die(const char *err, ...) { va_list params; diff --git a/tools/net/ynl/Makefile b/tools/net/ynl/Makefile new file mode 100644 index 000000000000..d664b36deb5b --- /dev/null +++ b/tools/net/ynl/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 + +SUBDIRS = lib generated samples + +all: $(SUBDIRS) + +$(SUBDIRS): + @if [ -f "$@/Makefile" ] ; then \ + $(MAKE) -C $@ ; \ + fi + +clean hardclean: + @for dir in $(SUBDIRS) ; do \ + if [ -f "$$dir/Makefile" ] ; then \ + $(MAKE) -C $$dir $@; \ + fi \ + done + +.PHONY: clean all $(SUBDIRS) diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps new file mode 100644 index 000000000000..f842bc66b967 --- /dev/null +++ b/tools/net/ynl/Makefile.deps @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Try to include uAPI headers from the kernel uapi/ path. +# Most code under tools/ requires the respective kernel uAPI headers +# to be copied to tools/include. The duplication is annoying. +# All the family headers should be self-contained. We avoid the copying +# by selectively including just the uAPI header of the family directly +# from the kernel sources. + +UAPI_PATH:=../../../../include/uapi/ + +# scripts/headers_install.sh strips "_UAPI" from header guards so we +# need the explicit -D matching what's in /usr, to avoid multiple definitions. + +get_hdr_inc=-D$(1) -include $(UAPI_PATH)/linux/$(2) + +CFLAGS_devlink:=$(call get_hdr_inc,_LINUX_DEVLINK_H_,devlink.h) +CFLAGS_ethtool:=$(call get_hdr_inc,_LINUX_ETHTOOL_NETLINK_H_,ethtool_netlink.h) +CFLAGS_handshake:=$(call get_hdr_inc,_LINUX_HANDSHAKE_H,handshake.h) +CFLAGS_netdev:=$(call get_hdr_inc,_LINUX_NETDEV_H,netdev.h) diff --git a/tools/net/ynl/generated/Makefile b/tools/net/ynl/generated/Makefile new file mode 100644 index 000000000000..f8817d2e56e4 --- /dev/null +++ b/tools/net/ynl/generated/Makefile @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0 + +CC=gcc +CFLAGS=-std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow \ + -I../lib/ -idirafter $(UAPI_PATH) +ifeq ("$(DEBUG)","1") + CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan +endif + +include ../Makefile.deps + +YNL_GEN_ARG_ethtool:=--user-header linux/ethtool_netlink.h \ + --exclude-op stats-get + +TOOL:=../ynl-gen-c.py + +GENS:=ethtool devlink handshake fou netdev +SRCS=$(patsubst %,%-user.c,${GENS}) +HDRS=$(patsubst %,%-user.h,${GENS}) +OBJS=$(patsubst %,%-user.o,${GENS}) + +all: protos.a $(HDRS) $(SRCS) $(KHDRS) $(KSRCS) $(UAPI) regen + +protos.a: $(OBJS) + @echo -e "\tAR $@" + @ar rcs $@ $(OBJS) + +%-user.h: ../../../../Documentation/netlink/specs/%.yaml $(TOOL) + @echo -e "\tGEN $@" + @$(TOOL) --mode user --header --spec $< $(YNL_GEN_ARG_$*) > $@ + +%-user.c: ../../../../Documentation/netlink/specs/%.yaml $(TOOL) + @echo -e "\tGEN $@" + @$(TOOL) --mode user --source --spec $< $(YNL_GEN_ARG_$*) > $@ + +%-user.o: %-user.c %-user.h + @echo -e "\tCC $@" + @$(COMPILE.c) $(CFLAGS_$*) -o $@ $< + +clean: + rm -f *.o + +hardclean: clean + rm -f *.c *.h *.a + +regen: + @../ynl-regen.sh + +.PHONY: all clean hardclean regen +.DEFAULT_GOAL: all diff --git a/tools/net/ynl/generated/devlink-user.c b/tools/net/ynl/generated/devlink-user.c new file mode 100644 index 000000000000..939bd45feaca --- /dev/null +++ b/tools/net/ynl/generated/devlink-user.c @@ -0,0 +1,721 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/devlink.yaml */ +/* YNL-GEN user source */ + +#include <stdlib.h> +#include <string.h> +#include "devlink-user.h" +#include "ynl.h" +#include <linux/devlink.h> + +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> + +/* Enums */ +static const char * const devlink_op_strmap[] = { + [3] = "get", + [DEVLINK_CMD_INFO_GET] = "info-get", +}; + +const char *devlink_op_str(int op) +{ + if (op < 0 || op >= (int)MNL_ARRAY_SIZE(devlink_op_strmap)) + return NULL; + return devlink_op_strmap[op]; +} + +/* Policies */ +struct ynl_policy_attr devlink_dl_info_version_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_INFO_VERSION_NAME] = { .name = "info-version-name", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_INFO_VERSION_VALUE] = { .name = "info-version-value", .type = YNL_PT_NUL_STR, }, +}; + +struct ynl_policy_nest devlink_dl_info_version_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_dl_info_version_policy, +}; + +struct ynl_policy_attr devlink_dl_reload_stats_entry_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_RELOAD_STATS_LIMIT] = { .name = "reload-stats-limit", .type = YNL_PT_U8, }, + [DEVLINK_ATTR_RELOAD_STATS_VALUE] = { .name = "reload-stats-value", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest devlink_dl_reload_stats_entry_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_dl_reload_stats_entry_policy, +}; + +struct ynl_policy_attr devlink_dl_reload_act_stats_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_RELOAD_STATS_ENTRY] = { .name = "reload-stats-entry", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_stats_entry_nest, }, +}; + +struct ynl_policy_nest devlink_dl_reload_act_stats_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_dl_reload_act_stats_policy, +}; + +struct ynl_policy_attr devlink_dl_reload_act_info_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_RELOAD_ACTION] = { .name = "reload-action", .type = YNL_PT_U8, }, + [DEVLINK_ATTR_RELOAD_ACTION_STATS] = { .name = "reload-action-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_act_stats_nest, }, +}; + +struct ynl_policy_nest devlink_dl_reload_act_info_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_dl_reload_act_info_policy, +}; + +struct ynl_policy_attr devlink_dl_reload_stats_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_RELOAD_ACTION_INFO] = { .name = "reload-action-info", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_act_info_nest, }, +}; + +struct ynl_policy_nest devlink_dl_reload_stats_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_dl_reload_stats_policy, +}; + +struct ynl_policy_attr devlink_dl_dev_stats_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_RELOAD_STATS] = { .name = "reload-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_stats_nest, }, + [DEVLINK_ATTR_REMOTE_RELOAD_STATS] = { .name = "remote-reload-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_stats_nest, }, +}; + +struct ynl_policy_nest devlink_dl_dev_stats_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_dl_dev_stats_policy, +}; + +struct ynl_policy_attr devlink_policy[DEVLINK_ATTR_MAX + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .name = "bus-name", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_DEV_NAME] = { .name = "dev-name", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_PORT_INDEX] = { .name = "port-index", .type = YNL_PT_U32, }, + [DEVLINK_ATTR_INFO_DRIVER_NAME] = { .name = "info-driver-name", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_INFO_SERIAL_NUMBER] = { .name = "info-serial-number", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_INFO_VERSION_FIXED] = { .name = "info-version-fixed", .type = YNL_PT_NEST, .nest = &devlink_dl_info_version_nest, }, + [DEVLINK_ATTR_INFO_VERSION_RUNNING] = { .name = "info-version-running", .type = YNL_PT_NEST, .nest = &devlink_dl_info_version_nest, }, + [DEVLINK_ATTR_INFO_VERSION_STORED] = { .name = "info-version-stored", .type = YNL_PT_NEST, .nest = &devlink_dl_info_version_nest, }, + [DEVLINK_ATTR_INFO_VERSION_NAME] = { .name = "info-version-name", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_INFO_VERSION_VALUE] = { .name = "info-version-value", .type = YNL_PT_NUL_STR, }, + [DEVLINK_ATTR_RELOAD_FAILED] = { .name = "reload-failed", .type = YNL_PT_U8, }, + [DEVLINK_ATTR_RELOAD_ACTION] = { .name = "reload-action", .type = YNL_PT_U8, }, + [DEVLINK_ATTR_DEV_STATS] = { .name = "dev-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_dev_stats_nest, }, + [DEVLINK_ATTR_RELOAD_STATS] = { .name = "reload-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_stats_nest, }, + [DEVLINK_ATTR_RELOAD_STATS_ENTRY] = { .name = "reload-stats-entry", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_stats_entry_nest, }, + [DEVLINK_ATTR_RELOAD_STATS_LIMIT] = { .name = "reload-stats-limit", .type = YNL_PT_U8, }, + [DEVLINK_ATTR_RELOAD_STATS_VALUE] = { .name = "reload-stats-value", .type = YNL_PT_U32, }, + [DEVLINK_ATTR_REMOTE_RELOAD_STATS] = { .name = "remote-reload-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_stats_nest, }, + [DEVLINK_ATTR_RELOAD_ACTION_INFO] = { .name = "reload-action-info", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_act_info_nest, }, + [DEVLINK_ATTR_RELOAD_ACTION_STATS] = { .name = "reload-action-stats", .type = YNL_PT_NEST, .nest = &devlink_dl_reload_act_stats_nest, }, +}; + +struct ynl_policy_nest devlink_nest = { + .max_attr = DEVLINK_ATTR_MAX, + .table = devlink_policy, +}; + +/* Common nested types */ +void devlink_dl_info_version_free(struct devlink_dl_info_version *obj) +{ + free(obj->info_version_name); + free(obj->info_version_value); +} + +int devlink_dl_info_version_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct devlink_dl_info_version *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_INFO_VERSION_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.info_version_name_len = len; + dst->info_version_name = malloc(len + 1); + memcpy(dst->info_version_name, mnl_attr_get_str(attr), len); + dst->info_version_name[len] = 0; + } else if (type == DEVLINK_ATTR_INFO_VERSION_VALUE) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.info_version_value_len = len; + dst->info_version_value = malloc(len + 1); + memcpy(dst->info_version_value, mnl_attr_get_str(attr), len); + dst->info_version_value[len] = 0; + } + } + + return 0; +} + +void +devlink_dl_reload_stats_entry_free(struct devlink_dl_reload_stats_entry *obj) +{ +} + +int devlink_dl_reload_stats_entry_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct devlink_dl_reload_stats_entry *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_RELOAD_STATS_LIMIT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reload_stats_limit = 1; + dst->reload_stats_limit = mnl_attr_get_u8(attr); + } else if (type == DEVLINK_ATTR_RELOAD_STATS_VALUE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reload_stats_value = 1; + dst->reload_stats_value = mnl_attr_get_u32(attr); + } + } + + return 0; +} + +void devlink_dl_reload_act_stats_free(struct devlink_dl_reload_act_stats *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_reload_stats_entry; i++) + devlink_dl_reload_stats_entry_free(&obj->reload_stats_entry[i]); + free(obj->reload_stats_entry); +} + +int devlink_dl_reload_act_stats_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct devlink_dl_reload_act_stats *dst = yarg->data; + unsigned int n_reload_stats_entry = 0; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + parg.ys = yarg->ys; + + if (dst->reload_stats_entry) + return ynl_error_parse(yarg, "attribute already present (dl-reload-act-stats.reload-stats-entry)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_RELOAD_STATS_ENTRY) { + n_reload_stats_entry++; + } + } + + if (n_reload_stats_entry) { + dst->reload_stats_entry = calloc(n_reload_stats_entry, sizeof(*dst->reload_stats_entry)); + dst->n_reload_stats_entry = n_reload_stats_entry; + i = 0; + parg.rsp_policy = &devlink_dl_reload_stats_entry_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == DEVLINK_ATTR_RELOAD_STATS_ENTRY) { + parg.data = &dst->reload_stats_entry[i]; + if (devlink_dl_reload_stats_entry_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void devlink_dl_reload_act_info_free(struct devlink_dl_reload_act_info *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_reload_action_stats; i++) + devlink_dl_reload_act_stats_free(&obj->reload_action_stats[i]); + free(obj->reload_action_stats); +} + +int devlink_dl_reload_act_info_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct devlink_dl_reload_act_info *dst = yarg->data; + unsigned int n_reload_action_stats = 0; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + parg.ys = yarg->ys; + + if (dst->reload_action_stats) + return ynl_error_parse(yarg, "attribute already present (dl-reload-act-info.reload-action-stats)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_RELOAD_ACTION) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reload_action = 1; + dst->reload_action = mnl_attr_get_u8(attr); + } else if (type == DEVLINK_ATTR_RELOAD_ACTION_STATS) { + n_reload_action_stats++; + } + } + + if (n_reload_action_stats) { + dst->reload_action_stats = calloc(n_reload_action_stats, sizeof(*dst->reload_action_stats)); + dst->n_reload_action_stats = n_reload_action_stats; + i = 0; + parg.rsp_policy = &devlink_dl_reload_act_stats_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == DEVLINK_ATTR_RELOAD_ACTION_STATS) { + parg.data = &dst->reload_action_stats[i]; + if (devlink_dl_reload_act_stats_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void devlink_dl_reload_stats_free(struct devlink_dl_reload_stats *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_reload_action_info; i++) + devlink_dl_reload_act_info_free(&obj->reload_action_info[i]); + free(obj->reload_action_info); +} + +int devlink_dl_reload_stats_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct devlink_dl_reload_stats *dst = yarg->data; + unsigned int n_reload_action_info = 0; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + parg.ys = yarg->ys; + + if (dst->reload_action_info) + return ynl_error_parse(yarg, "attribute already present (dl-reload-stats.reload-action-info)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_RELOAD_ACTION_INFO) { + n_reload_action_info++; + } + } + + if (n_reload_action_info) { + dst->reload_action_info = calloc(n_reload_action_info, sizeof(*dst->reload_action_info)); + dst->n_reload_action_info = n_reload_action_info; + i = 0; + parg.rsp_policy = &devlink_dl_reload_act_info_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == DEVLINK_ATTR_RELOAD_ACTION_INFO) { + parg.data = &dst->reload_action_info[i]; + if (devlink_dl_reload_act_info_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void devlink_dl_dev_stats_free(struct devlink_dl_dev_stats *obj) +{ + devlink_dl_reload_stats_free(&obj->reload_stats); + devlink_dl_reload_stats_free(&obj->remote_reload_stats); +} + +int devlink_dl_dev_stats_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct devlink_dl_dev_stats *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + parg.ys = yarg->ys; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_RELOAD_STATS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reload_stats = 1; + + parg.rsp_policy = &devlink_dl_reload_stats_nest; + parg.data = &dst->reload_stats; + if (devlink_dl_reload_stats_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == DEVLINK_ATTR_REMOTE_RELOAD_STATS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.remote_reload_stats = 1; + + parg.rsp_policy = &devlink_dl_reload_stats_nest; + parg.data = &dst->remote_reload_stats; + if (devlink_dl_reload_stats_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return 0; +} + +/* ============== DEVLINK_CMD_GET ============== */ +/* DEVLINK_CMD_GET - do */ +void devlink_get_req_free(struct devlink_get_req *req) +{ + free(req->bus_name); + free(req->dev_name); + free(req); +} + +void devlink_get_rsp_free(struct devlink_get_rsp *rsp) +{ + free(rsp->bus_name); + free(rsp->dev_name); + devlink_dl_dev_stats_free(&rsp->dev_stats); + free(rsp); +} + +int devlink_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct devlink_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_BUS_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.bus_name_len = len; + dst->bus_name = malloc(len + 1); + memcpy(dst->bus_name, mnl_attr_get_str(attr), len); + dst->bus_name[len] = 0; + } else if (type == DEVLINK_ATTR_DEV_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.dev_name_len = len; + dst->dev_name = malloc(len + 1); + memcpy(dst->dev_name, mnl_attr_get_str(attr), len); + dst->dev_name[len] = 0; + } else if (type == DEVLINK_ATTR_RELOAD_FAILED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reload_failed = 1; + dst->reload_failed = mnl_attr_get_u8(attr); + } else if (type == DEVLINK_ATTR_RELOAD_ACTION) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reload_action = 1; + dst->reload_action = mnl_attr_get_u8(attr); + } else if (type == DEVLINK_ATTR_DEV_STATS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.dev_stats = 1; + + parg.rsp_policy = &devlink_dl_dev_stats_nest; + parg.data = &dst->dev_stats; + if (devlink_dl_dev_stats_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct devlink_get_rsp * +devlink_get(struct ynl_sock *ys, struct devlink_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct devlink_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, DEVLINK_CMD_GET, 1); + ys->req_policy = &devlink_nest; + yrs.yarg.rsp_policy = &devlink_nest; + + if (req->_present.bus_name_len) + mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, req->bus_name); + if (req->_present.dev_name_len) + mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, req->dev_name); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = devlink_get_rsp_parse; + yrs.rsp_cmd = 3; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + devlink_get_rsp_free(rsp); + return NULL; +} + +/* DEVLINK_CMD_GET - dump */ +void devlink_get_list_free(struct devlink_get_list *rsp) +{ + struct devlink_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + free(rsp->obj.bus_name); + free(rsp->obj.dev_name); + devlink_dl_dev_stats_free(&rsp->obj.dev_stats); + free(rsp); + } +} + +struct devlink_get_list *devlink_get_dump(struct ynl_sock *ys) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct devlink_get_list); + yds.cb = devlink_get_rsp_parse; + yds.rsp_cmd = 3; + yds.rsp_policy = &devlink_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, DEVLINK_CMD_GET, 1); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + devlink_get_list_free(yds.first); + return NULL; +} + +/* ============== DEVLINK_CMD_INFO_GET ============== */ +/* DEVLINK_CMD_INFO_GET - do */ +void devlink_info_get_req_free(struct devlink_info_get_req *req) +{ + free(req->bus_name); + free(req->dev_name); + free(req); +} + +void devlink_info_get_rsp_free(struct devlink_info_get_rsp *rsp) +{ + unsigned int i; + + free(rsp->bus_name); + free(rsp->dev_name); + free(rsp->info_driver_name); + free(rsp->info_serial_number); + for (i = 0; i < rsp->n_info_version_fixed; i++) + devlink_dl_info_version_free(&rsp->info_version_fixed[i]); + free(rsp->info_version_fixed); + for (i = 0; i < rsp->n_info_version_running; i++) + devlink_dl_info_version_free(&rsp->info_version_running[i]); + free(rsp->info_version_running); + for (i = 0; i < rsp->n_info_version_stored; i++) + devlink_dl_info_version_free(&rsp->info_version_stored[i]); + free(rsp->info_version_stored); + free(rsp); +} + +int devlink_info_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + unsigned int n_info_version_running = 0; + unsigned int n_info_version_stored = 0; + unsigned int n_info_version_fixed = 0; + struct ynl_parse_arg *yarg = data; + struct devlink_info_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + dst = yarg->data; + parg.ys = yarg->ys; + + if (dst->info_version_fixed) + return ynl_error_parse(yarg, "attribute already present (devlink.info-version-fixed)"); + if (dst->info_version_running) + return ynl_error_parse(yarg, "attribute already present (devlink.info-version-running)"); + if (dst->info_version_stored) + return ynl_error_parse(yarg, "attribute already present (devlink.info-version-stored)"); + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == DEVLINK_ATTR_BUS_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.bus_name_len = len; + dst->bus_name = malloc(len + 1); + memcpy(dst->bus_name, mnl_attr_get_str(attr), len); + dst->bus_name[len] = 0; + } else if (type == DEVLINK_ATTR_DEV_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.dev_name_len = len; + dst->dev_name = malloc(len + 1); + memcpy(dst->dev_name, mnl_attr_get_str(attr), len); + dst->dev_name[len] = 0; + } else if (type == DEVLINK_ATTR_INFO_DRIVER_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.info_driver_name_len = len; + dst->info_driver_name = malloc(len + 1); + memcpy(dst->info_driver_name, mnl_attr_get_str(attr), len); + dst->info_driver_name[len] = 0; + } else if (type == DEVLINK_ATTR_INFO_SERIAL_NUMBER) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.info_serial_number_len = len; + dst->info_serial_number = malloc(len + 1); + memcpy(dst->info_serial_number, mnl_attr_get_str(attr), len); + dst->info_serial_number[len] = 0; + } else if (type == DEVLINK_ATTR_INFO_VERSION_FIXED) { + n_info_version_fixed++; + } else if (type == DEVLINK_ATTR_INFO_VERSION_RUNNING) { + n_info_version_running++; + } else if (type == DEVLINK_ATTR_INFO_VERSION_STORED) { + n_info_version_stored++; + } + } + + if (n_info_version_fixed) { + dst->info_version_fixed = calloc(n_info_version_fixed, sizeof(*dst->info_version_fixed)); + dst->n_info_version_fixed = n_info_version_fixed; + i = 0; + parg.rsp_policy = &devlink_dl_info_version_nest; + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(attr) == DEVLINK_ATTR_INFO_VERSION_FIXED) { + parg.data = &dst->info_version_fixed[i]; + if (devlink_dl_info_version_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + if (n_info_version_running) { + dst->info_version_running = calloc(n_info_version_running, sizeof(*dst->info_version_running)); + dst->n_info_version_running = n_info_version_running; + i = 0; + parg.rsp_policy = &devlink_dl_info_version_nest; + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(attr) == DEVLINK_ATTR_INFO_VERSION_RUNNING) { + parg.data = &dst->info_version_running[i]; + if (devlink_dl_info_version_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + if (n_info_version_stored) { + dst->info_version_stored = calloc(n_info_version_stored, sizeof(*dst->info_version_stored)); + dst->n_info_version_stored = n_info_version_stored; + i = 0; + parg.rsp_policy = &devlink_dl_info_version_nest; + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(attr) == DEVLINK_ATTR_INFO_VERSION_STORED) { + parg.data = &dst->info_version_stored[i]; + if (devlink_dl_info_version_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return MNL_CB_OK; +} + +struct devlink_info_get_rsp * +devlink_info_get(struct ynl_sock *ys, struct devlink_info_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct devlink_info_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, DEVLINK_CMD_INFO_GET, 1); + ys->req_policy = &devlink_nest; + yrs.yarg.rsp_policy = &devlink_nest; + + if (req->_present.bus_name_len) + mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, req->bus_name); + if (req->_present.dev_name_len) + mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, req->dev_name); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = devlink_info_get_rsp_parse; + yrs.rsp_cmd = DEVLINK_CMD_INFO_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + devlink_info_get_rsp_free(rsp); + return NULL; +} + +const struct ynl_family ynl_devlink_family = { + .name = "devlink", +}; diff --git a/tools/net/ynl/generated/devlink-user.h b/tools/net/ynl/generated/devlink-user.h new file mode 100644 index 000000000000..a008b99b6e24 --- /dev/null +++ b/tools/net/ynl/generated/devlink-user.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/devlink.yaml */ +/* YNL-GEN user header */ + +#ifndef _LINUX_DEVLINK_GEN_H +#define _LINUX_DEVLINK_GEN_H + +#include <stdlib.h> +#include <string.h> +#include <linux/types.h> +#include <linux/devlink.h> + +struct ynl_sock; + +extern const struct ynl_family ynl_devlink_family; + +/* Enums */ +const char *devlink_op_str(int op); + +/* Common nested types */ +struct devlink_dl_info_version { + struct { + __u32 info_version_name_len; + __u32 info_version_value_len; + } _present; + + char *info_version_name; + char *info_version_value; +}; + +struct devlink_dl_reload_stats_entry { + struct { + __u32 reload_stats_limit:1; + __u32 reload_stats_value:1; + } _present; + + __u8 reload_stats_limit; + __u32 reload_stats_value; +}; + +struct devlink_dl_reload_act_stats { + unsigned int n_reload_stats_entry; + struct devlink_dl_reload_stats_entry *reload_stats_entry; +}; + +struct devlink_dl_reload_act_info { + struct { + __u32 reload_action:1; + } _present; + + __u8 reload_action; + unsigned int n_reload_action_stats; + struct devlink_dl_reload_act_stats *reload_action_stats; +}; + +struct devlink_dl_reload_stats { + unsigned int n_reload_action_info; + struct devlink_dl_reload_act_info *reload_action_info; +}; + +struct devlink_dl_dev_stats { + struct { + __u32 reload_stats:1; + __u32 remote_reload_stats:1; + } _present; + + struct devlink_dl_reload_stats reload_stats; + struct devlink_dl_reload_stats remote_reload_stats; +}; + +/* ============== DEVLINK_CMD_GET ============== */ +/* DEVLINK_CMD_GET - do */ +struct devlink_get_req { + struct { + __u32 bus_name_len; + __u32 dev_name_len; + } _present; + + char *bus_name; + char *dev_name; +}; + +static inline struct devlink_get_req *devlink_get_req_alloc(void) +{ + return calloc(1, sizeof(struct devlink_get_req)); +} +void devlink_get_req_free(struct devlink_get_req *req); + +static inline void +devlink_get_req_set_bus_name(struct devlink_get_req *req, const char *bus_name) +{ + free(req->bus_name); + req->_present.bus_name_len = strlen(bus_name); + req->bus_name = malloc(req->_present.bus_name_len + 1); + memcpy(req->bus_name, bus_name, req->_present.bus_name_len); + req->bus_name[req->_present.bus_name_len] = 0; +} +static inline void +devlink_get_req_set_dev_name(struct devlink_get_req *req, const char *dev_name) +{ + free(req->dev_name); + req->_present.dev_name_len = strlen(dev_name); + req->dev_name = malloc(req->_present.dev_name_len + 1); + memcpy(req->dev_name, dev_name, req->_present.dev_name_len); + req->dev_name[req->_present.dev_name_len] = 0; +} + +struct devlink_get_rsp { + struct { + __u32 bus_name_len; + __u32 dev_name_len; + __u32 reload_failed:1; + __u32 reload_action:1; + __u32 dev_stats:1; + } _present; + + char *bus_name; + char *dev_name; + __u8 reload_failed; + __u8 reload_action; + struct devlink_dl_dev_stats dev_stats; +}; + +void devlink_get_rsp_free(struct devlink_get_rsp *rsp); + +/* + * Get devlink instances. + */ +struct devlink_get_rsp * +devlink_get(struct ynl_sock *ys, struct devlink_get_req *req); + +/* DEVLINK_CMD_GET - dump */ +struct devlink_get_list { + struct devlink_get_list *next; + struct devlink_get_rsp obj __attribute__ ((aligned (8))); +}; + +void devlink_get_list_free(struct devlink_get_list *rsp); + +struct devlink_get_list *devlink_get_dump(struct ynl_sock *ys); + +/* ============== DEVLINK_CMD_INFO_GET ============== */ +/* DEVLINK_CMD_INFO_GET - do */ +struct devlink_info_get_req { + struct { + __u32 bus_name_len; + __u32 dev_name_len; + } _present; + + char *bus_name; + char *dev_name; +}; + +static inline struct devlink_info_get_req *devlink_info_get_req_alloc(void) +{ + return calloc(1, sizeof(struct devlink_info_get_req)); +} +void devlink_info_get_req_free(struct devlink_info_get_req *req); + +static inline void +devlink_info_get_req_set_bus_name(struct devlink_info_get_req *req, + const char *bus_name) +{ + free(req->bus_name); + req->_present.bus_name_len = strlen(bus_name); + req->bus_name = malloc(req->_present.bus_name_len + 1); + memcpy(req->bus_name, bus_name, req->_present.bus_name_len); + req->bus_name[req->_present.bus_name_len] = 0; +} +static inline void +devlink_info_get_req_set_dev_name(struct devlink_info_get_req *req, + const char *dev_name) +{ + free(req->dev_name); + req->_present.dev_name_len = strlen(dev_name); + req->dev_name = malloc(req->_present.dev_name_len + 1); + memcpy(req->dev_name, dev_name, req->_present.dev_name_len); + req->dev_name[req->_present.dev_name_len] = 0; +} + +struct devlink_info_get_rsp { + struct { + __u32 bus_name_len; + __u32 dev_name_len; + __u32 info_driver_name_len; + __u32 info_serial_number_len; + } _present; + + char *bus_name; + char *dev_name; + char *info_driver_name; + char *info_serial_number; + unsigned int n_info_version_fixed; + struct devlink_dl_info_version *info_version_fixed; + unsigned int n_info_version_running; + struct devlink_dl_info_version *info_version_running; + unsigned int n_info_version_stored; + struct devlink_dl_info_version *info_version_stored; +}; + +void devlink_info_get_rsp_free(struct devlink_info_get_rsp *rsp); + +/* + * Get device information, like driver name, hardware and firmware versions etc. + */ +struct devlink_info_get_rsp * +devlink_info_get(struct ynl_sock *ys, struct devlink_info_get_req *req); + +#endif /* _LINUX_DEVLINK_GEN_H */ diff --git a/tools/net/ynl/generated/ethtool-user.c b/tools/net/ynl/generated/ethtool-user.c new file mode 100644 index 000000000000..74b883a14958 --- /dev/null +++ b/tools/net/ynl/generated/ethtool-user.c @@ -0,0 +1,6353 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/ethtool.yaml */ +/* YNL-GEN user source */ +/* YNL-ARG --user-header linux/ethtool_netlink.h --exclude-op stats-get */ + +#include <stdlib.h> +#include <string.h> +#include "ethtool-user.h" +#include "ynl.h" +#include <linux/ethtool.h> + +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> + +#include "linux/ethtool_netlink.h" + +/* Enums */ +static const char * const ethtool_op_strmap[] = { + [ETHTOOL_MSG_STRSET_GET] = "strset-get", + [ETHTOOL_MSG_LINKINFO_GET] = "linkinfo-get", + [3] = "linkinfo-ntf", + [ETHTOOL_MSG_LINKMODES_GET] = "linkmodes-get", + [5] = "linkmodes-ntf", + [ETHTOOL_MSG_LINKSTATE_GET] = "linkstate-get", + [ETHTOOL_MSG_DEBUG_GET] = "debug-get", + [8] = "debug-ntf", + [ETHTOOL_MSG_WOL_GET] = "wol-get", + [10] = "wol-ntf", + [ETHTOOL_MSG_FEATURES_GET] = "features-get", + [ETHTOOL_MSG_FEATURES_SET] = "features-set", + [13] = "features-ntf", + [14] = "privflags-get", + [15] = "privflags-ntf", + [16] = "rings-get", + [17] = "rings-ntf", + [18] = "channels-get", + [19] = "channels-ntf", + [20] = "coalesce-get", + [21] = "coalesce-ntf", + [22] = "pause-get", + [23] = "pause-ntf", + [24] = "eee-get", + [25] = "eee-ntf", + [26] = "tsinfo-get", + [27] = "cable-test-ntf", + [28] = "cable-test-tdr-ntf", + [29] = "tunnel-info-get", + [30] = "fec-get", + [31] = "fec-ntf", + [32] = "module-eeprom-get", + [34] = "phc-vclocks-get", + [35] = "module-get", + [36] = "module-ntf", + [37] = "pse-get", + [ETHTOOL_MSG_RSS_GET] = "rss-get", + [ETHTOOL_MSG_PLCA_GET_CFG] = "plca-get-cfg", + [40] = "plca-get-status", + [41] = "plca-ntf", + [ETHTOOL_MSG_MM_GET] = "mm-get", + [43] = "mm-ntf", +}; + +const char *ethtool_op_str(int op) +{ + if (op < 0 || op >= (int)MNL_ARRAY_SIZE(ethtool_op_strmap)) + return NULL; + return ethtool_op_strmap[op]; +} + +static const char * const ethtool_udp_tunnel_type_strmap[] = { + [0] = "vxlan", + [1] = "geneve", + [2] = "vxlan-gpe", +}; + +const char *ethtool_udp_tunnel_type_str(int value) +{ + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(ethtool_udp_tunnel_type_strmap)) + return NULL; + return ethtool_udp_tunnel_type_strmap[value]; +} + +static const char * const ethtool_stringset_strmap[] = { +}; + +const char *ethtool_stringset_str(enum ethtool_stringset value) +{ + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(ethtool_stringset_strmap)) + return NULL; + return ethtool_stringset_strmap[value]; +} + +/* Policies */ +struct ynl_policy_attr ethtool_header_policy[ETHTOOL_A_HEADER_MAX + 1] = { + [ETHTOOL_A_HEADER_DEV_INDEX] = { .name = "dev-index", .type = YNL_PT_U32, }, + [ETHTOOL_A_HEADER_DEV_NAME] = { .name = "dev-name", .type = YNL_PT_NUL_STR, }, + [ETHTOOL_A_HEADER_FLAGS] = { .name = "flags", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_header_nest = { + .max_attr = ETHTOOL_A_HEADER_MAX, + .table = ethtool_header_policy, +}; + +struct ynl_policy_attr ethtool_pause_stat_policy[ETHTOOL_A_PAUSE_STAT_MAX + 1] = { + [ETHTOOL_A_PAUSE_STAT_PAD] = { .name = "pad", .type = YNL_PT_IGNORE, }, + [ETHTOOL_A_PAUSE_STAT_TX_FRAMES] = { .name = "tx-frames", .type = YNL_PT_U64, }, + [ETHTOOL_A_PAUSE_STAT_RX_FRAMES] = { .name = "rx-frames", .type = YNL_PT_U64, }, +}; + +struct ynl_policy_nest ethtool_pause_stat_nest = { + .max_attr = ETHTOOL_A_PAUSE_STAT_MAX, + .table = ethtool_pause_stat_policy, +}; + +struct ynl_policy_attr ethtool_cable_test_tdr_cfg_policy[ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST] = { .name = "first", .type = YNL_PT_U32, }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST] = { .name = "last", .type = YNL_PT_U32, }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP] = { .name = "step", .type = YNL_PT_U32, }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR] = { .name = "pair", .type = YNL_PT_U8, }, +}; + +struct ynl_policy_nest ethtool_cable_test_tdr_cfg_nest = { + .max_attr = ETHTOOL_A_CABLE_TEST_TDR_CFG_MAX, + .table = ethtool_cable_test_tdr_cfg_policy, +}; + +struct ynl_policy_attr ethtool_fec_stat_policy[ETHTOOL_A_FEC_STAT_MAX + 1] = { + [ETHTOOL_A_FEC_STAT_PAD] = { .name = "pad", .type = YNL_PT_IGNORE, }, + [ETHTOOL_A_FEC_STAT_CORRECTED] = { .name = "corrected", .type = YNL_PT_BINARY,}, + [ETHTOOL_A_FEC_STAT_UNCORR] = { .name = "uncorr", .type = YNL_PT_BINARY,}, + [ETHTOOL_A_FEC_STAT_CORR_BITS] = { .name = "corr-bits", .type = YNL_PT_BINARY,}, +}; + +struct ynl_policy_nest ethtool_fec_stat_nest = { + .max_attr = ETHTOOL_A_FEC_STAT_MAX, + .table = ethtool_fec_stat_policy, +}; + +struct ynl_policy_attr ethtool_mm_stat_policy[ETHTOOL_A_MM_STAT_MAX + 1] = { + [ETHTOOL_A_MM_STAT_PAD] = { .name = "pad", .type = YNL_PT_IGNORE, }, + [ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS] = { .name = "reassembly-errors", .type = YNL_PT_U64, }, + [ETHTOOL_A_MM_STAT_SMD_ERRORS] = { .name = "smd-errors", .type = YNL_PT_U64, }, + [ETHTOOL_A_MM_STAT_REASSEMBLY_OK] = { .name = "reassembly-ok", .type = YNL_PT_U64, }, + [ETHTOOL_A_MM_STAT_RX_FRAG_COUNT] = { .name = "rx-frag-count", .type = YNL_PT_U64, }, + [ETHTOOL_A_MM_STAT_TX_FRAG_COUNT] = { .name = "tx-frag-count", .type = YNL_PT_U64, }, + [ETHTOOL_A_MM_STAT_HOLD_COUNT] = { .name = "hold-count", .type = YNL_PT_U64, }, +}; + +struct ynl_policy_nest ethtool_mm_stat_nest = { + .max_attr = ETHTOOL_A_MM_STAT_MAX, + .table = ethtool_mm_stat_policy, +}; + +struct ynl_policy_attr ethtool_cable_result_policy[ETHTOOL_A_CABLE_RESULT_MAX + 1] = { + [ETHTOOL_A_CABLE_RESULT_PAIR] = { .name = "pair", .type = YNL_PT_U8, }, + [ETHTOOL_A_CABLE_RESULT_CODE] = { .name = "code", .type = YNL_PT_U8, }, +}; + +struct ynl_policy_nest ethtool_cable_result_nest = { + .max_attr = ETHTOOL_A_CABLE_RESULT_MAX, + .table = ethtool_cable_result_policy, +}; + +struct ynl_policy_attr ethtool_cable_fault_length_policy[ETHTOOL_A_CABLE_FAULT_LENGTH_MAX + 1] = { + [ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR] = { .name = "pair", .type = YNL_PT_U8, }, + [ETHTOOL_A_CABLE_FAULT_LENGTH_CM] = { .name = "cm", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_cable_fault_length_nest = { + .max_attr = ETHTOOL_A_CABLE_FAULT_LENGTH_MAX, + .table = ethtool_cable_fault_length_policy, +}; + +struct ynl_policy_attr ethtool_bitset_bit_policy[ETHTOOL_A_BITSET_BIT_MAX + 1] = { + [ETHTOOL_A_BITSET_BIT_INDEX] = { .name = "index", .type = YNL_PT_U32, }, + [ETHTOOL_A_BITSET_BIT_NAME] = { .name = "name", .type = YNL_PT_NUL_STR, }, + [ETHTOOL_A_BITSET_BIT_VALUE] = { .name = "value", .type = YNL_PT_FLAG, }, +}; + +struct ynl_policy_nest ethtool_bitset_bit_nest = { + .max_attr = ETHTOOL_A_BITSET_BIT_MAX, + .table = ethtool_bitset_bit_policy, +}; + +struct ynl_policy_attr ethtool_tunnel_udp_entry_policy[ETHTOOL_A_TUNNEL_UDP_ENTRY_MAX + 1] = { + [ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT] = { .name = "port", .type = YNL_PT_U16, }, + [ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE] = { .name = "type", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_tunnel_udp_entry_nest = { + .max_attr = ETHTOOL_A_TUNNEL_UDP_ENTRY_MAX, + .table = ethtool_tunnel_udp_entry_policy, +}; + +struct ynl_policy_attr ethtool_string_policy[ETHTOOL_A_STRING_MAX + 1] = { + [ETHTOOL_A_STRING_INDEX] = { .name = "index", .type = YNL_PT_U32, }, + [ETHTOOL_A_STRING_VALUE] = { .name = "value", .type = YNL_PT_NUL_STR, }, +}; + +struct ynl_policy_nest ethtool_string_nest = { + .max_attr = ETHTOOL_A_STRING_MAX, + .table = ethtool_string_policy, +}; + +struct ynl_policy_attr ethtool_cable_nest_policy[ETHTOOL_A_CABLE_NEST_MAX + 1] = { + [ETHTOOL_A_CABLE_NEST_RESULT] = { .name = "result", .type = YNL_PT_NEST, .nest = ðtool_cable_result_nest, }, + [ETHTOOL_A_CABLE_NEST_FAULT_LENGTH] = { .name = "fault-length", .type = YNL_PT_NEST, .nest = ðtool_cable_fault_length_nest, }, +}; + +struct ynl_policy_nest ethtool_cable_nest_nest = { + .max_attr = ETHTOOL_A_CABLE_NEST_MAX, + .table = ethtool_cable_nest_policy, +}; + +struct ynl_policy_attr ethtool_bitset_bits_policy[ETHTOOL_A_BITSET_BITS_MAX + 1] = { + [ETHTOOL_A_BITSET_BITS_BIT] = { .name = "bit", .type = YNL_PT_NEST, .nest = ðtool_bitset_bit_nest, }, +}; + +struct ynl_policy_nest ethtool_bitset_bits_nest = { + .max_attr = ETHTOOL_A_BITSET_BITS_MAX, + .table = ethtool_bitset_bits_policy, +}; + +struct ynl_policy_attr ethtool_strings_policy[ETHTOOL_A_STRINGS_MAX + 1] = { + [ETHTOOL_A_STRINGS_STRING] = { .name = "string", .type = YNL_PT_NEST, .nest = ðtool_string_nest, }, +}; + +struct ynl_policy_nest ethtool_strings_nest = { + .max_attr = ETHTOOL_A_STRINGS_MAX, + .table = ethtool_strings_policy, +}; + +struct ynl_policy_attr ethtool_bitset_policy[ETHTOOL_A_BITSET_MAX + 1] = { + [ETHTOOL_A_BITSET_NOMASK] = { .name = "nomask", .type = YNL_PT_FLAG, }, + [ETHTOOL_A_BITSET_SIZE] = { .name = "size", .type = YNL_PT_U32, }, + [ETHTOOL_A_BITSET_BITS] = { .name = "bits", .type = YNL_PT_NEST, .nest = ðtool_bitset_bits_nest, }, +}; + +struct ynl_policy_nest ethtool_bitset_nest = { + .max_attr = ETHTOOL_A_BITSET_MAX, + .table = ethtool_bitset_policy, +}; + +struct ynl_policy_attr ethtool_stringset_policy[ETHTOOL_A_STRINGSET_MAX + 1] = { + [ETHTOOL_A_STRINGSET_ID] = { .name = "id", .type = YNL_PT_U32, }, + [ETHTOOL_A_STRINGSET_COUNT] = { .name = "count", .type = YNL_PT_U32, }, + [ETHTOOL_A_STRINGSET_STRINGS] = { .name = "strings", .type = YNL_PT_NEST, .nest = ðtool_strings_nest, }, +}; + +struct ynl_policy_nest ethtool_stringset_nest = { + .max_attr = ETHTOOL_A_STRINGSET_MAX, + .table = ethtool_stringset_policy, +}; + +struct ynl_policy_attr ethtool_tunnel_udp_table_policy[ETHTOOL_A_TUNNEL_UDP_TABLE_MAX + 1] = { + [ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE] = { .name = "size", .type = YNL_PT_U32, }, + [ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES] = { .name = "types", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY] = { .name = "entry", .type = YNL_PT_NEST, .nest = ðtool_tunnel_udp_entry_nest, }, +}; + +struct ynl_policy_nest ethtool_tunnel_udp_table_nest = { + .max_attr = ETHTOOL_A_TUNNEL_UDP_TABLE_MAX, + .table = ethtool_tunnel_udp_table_policy, +}; + +struct ynl_policy_attr ethtool_stringsets_policy[ETHTOOL_A_STRINGSETS_MAX + 1] = { + [ETHTOOL_A_STRINGSETS_STRINGSET] = { .name = "stringset", .type = YNL_PT_NEST, .nest = ðtool_stringset_nest, }, +}; + +struct ynl_policy_nest ethtool_stringsets_nest = { + .max_attr = ETHTOOL_A_STRINGSETS_MAX, + .table = ethtool_stringsets_policy, +}; + +struct ynl_policy_attr ethtool_tunnel_udp_policy[ETHTOOL_A_TUNNEL_UDP_MAX + 1] = { + [ETHTOOL_A_TUNNEL_UDP_TABLE] = { .name = "table", .type = YNL_PT_NEST, .nest = ðtool_tunnel_udp_table_nest, }, +}; + +struct ynl_policy_nest ethtool_tunnel_udp_nest = { + .max_attr = ETHTOOL_A_TUNNEL_UDP_MAX, + .table = ethtool_tunnel_udp_policy, +}; + +struct ynl_policy_attr ethtool_strset_policy[ETHTOOL_A_STRSET_MAX + 1] = { + [ETHTOOL_A_STRSET_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_STRSET_STRINGSETS] = { .name = "stringsets", .type = YNL_PT_NEST, .nest = ðtool_stringsets_nest, }, + [ETHTOOL_A_STRSET_COUNTS_ONLY] = { .name = "counts-only", .type = YNL_PT_FLAG, }, +}; + +struct ynl_policy_nest ethtool_strset_nest = { + .max_attr = ETHTOOL_A_STRSET_MAX, + .table = ethtool_strset_policy, +}; + +struct ynl_policy_attr ethtool_linkinfo_policy[ETHTOOL_A_LINKINFO_MAX + 1] = { + [ETHTOOL_A_LINKINFO_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_LINKINFO_PORT] = { .name = "port", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKINFO_PHYADDR] = { .name = "phyaddr", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKINFO_TP_MDIX] = { .name = "tp-mdix", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] = { .name = "tp-mdix-ctrl", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKINFO_TRANSCEIVER] = { .name = "transceiver", .type = YNL_PT_U8, }, +}; + +struct ynl_policy_nest ethtool_linkinfo_nest = { + .max_attr = ETHTOOL_A_LINKINFO_MAX, + .table = ethtool_linkinfo_policy, +}; + +struct ynl_policy_attr ethtool_linkmodes_policy[ETHTOOL_A_LINKMODES_MAX + 1] = { + [ETHTOOL_A_LINKMODES_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_LINKMODES_AUTONEG] = { .name = "autoneg", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKMODES_OURS] = { .name = "ours", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_LINKMODES_PEER] = { .name = "peer", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_LINKMODES_SPEED] = { .name = "speed", .type = YNL_PT_U32, }, + [ETHTOOL_A_LINKMODES_DUPLEX] = { .name = "duplex", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG] = { .name = "master-slave-cfg", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE] = { .name = "master-slave-state", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKMODES_LANES] = { .name = "lanes", .type = YNL_PT_U32, }, + [ETHTOOL_A_LINKMODES_RATE_MATCHING] = { .name = "rate-matching", .type = YNL_PT_U8, }, +}; + +struct ynl_policy_nest ethtool_linkmodes_nest = { + .max_attr = ETHTOOL_A_LINKMODES_MAX, + .table = ethtool_linkmodes_policy, +}; + +struct ynl_policy_attr ethtool_linkstate_policy[ETHTOOL_A_LINKSTATE_MAX + 1] = { + [ETHTOOL_A_LINKSTATE_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_LINKSTATE_LINK] = { .name = "link", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKSTATE_SQI] = { .name = "sqi", .type = YNL_PT_U32, }, + [ETHTOOL_A_LINKSTATE_SQI_MAX] = { .name = "sqi-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_LINKSTATE_EXT_STATE] = { .name = "ext-state", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKSTATE_EXT_SUBSTATE] = { .name = "ext-substate", .type = YNL_PT_U8, }, + [ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT] = { .name = "ext-down-cnt", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_linkstate_nest = { + .max_attr = ETHTOOL_A_LINKSTATE_MAX, + .table = ethtool_linkstate_policy, +}; + +struct ynl_policy_attr ethtool_debug_policy[ETHTOOL_A_DEBUG_MAX + 1] = { + [ETHTOOL_A_DEBUG_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_DEBUG_MSGMASK] = { .name = "msgmask", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, +}; + +struct ynl_policy_nest ethtool_debug_nest = { + .max_attr = ETHTOOL_A_DEBUG_MAX, + .table = ethtool_debug_policy, +}; + +struct ynl_policy_attr ethtool_wol_policy[ETHTOOL_A_WOL_MAX + 1] = { + [ETHTOOL_A_WOL_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_WOL_MODES] = { .name = "modes", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_WOL_SOPASS] = { .name = "sopass", .type = YNL_PT_BINARY,}, +}; + +struct ynl_policy_nest ethtool_wol_nest = { + .max_attr = ETHTOOL_A_WOL_MAX, + .table = ethtool_wol_policy, +}; + +struct ynl_policy_attr ethtool_features_policy[ETHTOOL_A_FEATURES_MAX + 1] = { + [ETHTOOL_A_FEATURES_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_FEATURES_HW] = { .name = "hw", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_FEATURES_WANTED] = { .name = "wanted", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_FEATURES_ACTIVE] = { .name = "active", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_FEATURES_NOCHANGE] = { .name = "nochange", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, +}; + +struct ynl_policy_nest ethtool_features_nest = { + .max_attr = ETHTOOL_A_FEATURES_MAX, + .table = ethtool_features_policy, +}; + +struct ynl_policy_attr ethtool_privflags_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = { + [ETHTOOL_A_PRIVFLAGS_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_PRIVFLAGS_FLAGS] = { .name = "flags", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, +}; + +struct ynl_policy_nest ethtool_privflags_nest = { + .max_attr = ETHTOOL_A_PRIVFLAGS_MAX, + .table = ethtool_privflags_policy, +}; + +struct ynl_policy_attr ethtool_rings_policy[ETHTOOL_A_RINGS_MAX + 1] = { + [ETHTOOL_A_RINGS_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_RINGS_RX_MAX] = { .name = "rx-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_RX_MINI_MAX] = { .name = "rx-mini-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_RX_JUMBO_MAX] = { .name = "rx-jumbo-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_TX_MAX] = { .name = "tx-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_RX] = { .name = "rx", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_RX_MINI] = { .name = "rx-mini", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_RX_JUMBO] = { .name = "rx-jumbo", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_TX] = { .name = "tx", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_RX_BUF_LEN] = { .name = "rx-buf-len", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_TCP_DATA_SPLIT] = { .name = "tcp-data-split", .type = YNL_PT_U8, }, + [ETHTOOL_A_RINGS_CQE_SIZE] = { .name = "cqe-size", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_TX_PUSH] = { .name = "tx-push", .type = YNL_PT_U8, }, + [ETHTOOL_A_RINGS_RX_PUSH] = { .name = "rx-push", .type = YNL_PT_U8, }, + [ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN] = { .name = "tx-push-buf-len", .type = YNL_PT_U32, }, + [ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX] = { .name = "tx-push-buf-len-max", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_rings_nest = { + .max_attr = ETHTOOL_A_RINGS_MAX, + .table = ethtool_rings_policy, +}; + +struct ynl_policy_attr ethtool_channels_policy[ETHTOOL_A_CHANNELS_MAX + 1] = { + [ETHTOOL_A_CHANNELS_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_CHANNELS_RX_MAX] = { .name = "rx-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_TX_MAX] = { .name = "tx-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_OTHER_MAX] = { .name = "other-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_COMBINED_MAX] = { .name = "combined-max", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_RX_COUNT] = { .name = "rx-count", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_TX_COUNT] = { .name = "tx-count", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_OTHER_COUNT] = { .name = "other-count", .type = YNL_PT_U32, }, + [ETHTOOL_A_CHANNELS_COMBINED_COUNT] = { .name = "combined-count", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_channels_nest = { + .max_attr = ETHTOOL_A_CHANNELS_MAX, + .table = ethtool_channels_policy, +}; + +struct ynl_policy_attr ethtool_coalesce_policy[ETHTOOL_A_COALESCE_MAX + 1] = { + [ETHTOOL_A_COALESCE_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_COALESCE_RX_USECS] = { .name = "rx-usecs", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_MAX_FRAMES] = { .name = "rx-max-frames", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_USECS_IRQ] = { .name = "rx-usecs-irq", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ] = { .name = "rx-max-frames-irq", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_USECS] = { .name = "tx-usecs", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_MAX_FRAMES] = { .name = "tx-max-frames", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_USECS_IRQ] = { .name = "tx-usecs-irq", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ] = { .name = "tx-max-frames-irq", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_STATS_BLOCK_USECS] = { .name = "stats-block-usecs", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX] = { .name = "use-adaptive-rx", .type = YNL_PT_U8, }, + [ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX] = { .name = "use-adaptive-tx", .type = YNL_PT_U8, }, + [ETHTOOL_A_COALESCE_PKT_RATE_LOW] = { .name = "pkt-rate-low", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_USECS_LOW] = { .name = "rx-usecs-low", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW] = { .name = "rx-max-frames-low", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_USECS_LOW] = { .name = "tx-usecs-low", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW] = { .name = "tx-max-frames-low", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_PKT_RATE_HIGH] = { .name = "pkt-rate-high", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_USECS_HIGH] = { .name = "rx-usecs-high", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH] = { .name = "rx-max-frames-high", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_USECS_HIGH] = { .name = "tx-usecs-high", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH] = { .name = "tx-max-frames-high", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL] = { .name = "rate-sample-interval", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_USE_CQE_MODE_TX] = { .name = "use-cqe-mode-tx", .type = YNL_PT_U8, }, + [ETHTOOL_A_COALESCE_USE_CQE_MODE_RX] = { .name = "use-cqe-mode-rx", .type = YNL_PT_U8, }, + [ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES] = { .name = "tx-aggr-max-bytes", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES] = { .name = "tx-aggr-max-frames", .type = YNL_PT_U32, }, + [ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS] = { .name = "tx-aggr-time-usecs", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_coalesce_nest = { + .max_attr = ETHTOOL_A_COALESCE_MAX, + .table = ethtool_coalesce_policy, +}; + +struct ynl_policy_attr ethtool_pause_policy[ETHTOOL_A_PAUSE_MAX + 1] = { + [ETHTOOL_A_PAUSE_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_PAUSE_AUTONEG] = { .name = "autoneg", .type = YNL_PT_U8, }, + [ETHTOOL_A_PAUSE_RX] = { .name = "rx", .type = YNL_PT_U8, }, + [ETHTOOL_A_PAUSE_TX] = { .name = "tx", .type = YNL_PT_U8, }, + [ETHTOOL_A_PAUSE_STATS] = { .name = "stats", .type = YNL_PT_NEST, .nest = ðtool_pause_stat_nest, }, + [ETHTOOL_A_PAUSE_STATS_SRC] = { .name = "stats-src", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_pause_nest = { + .max_attr = ETHTOOL_A_PAUSE_MAX, + .table = ethtool_pause_policy, +}; + +struct ynl_policy_attr ethtool_eee_policy[ETHTOOL_A_EEE_MAX + 1] = { + [ETHTOOL_A_EEE_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_EEE_MODES_OURS] = { .name = "modes-ours", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_EEE_MODES_PEER] = { .name = "modes-peer", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_EEE_ACTIVE] = { .name = "active", .type = YNL_PT_U8, }, + [ETHTOOL_A_EEE_ENABLED] = { .name = "enabled", .type = YNL_PT_U8, }, + [ETHTOOL_A_EEE_TX_LPI_ENABLED] = { .name = "tx-lpi-enabled", .type = YNL_PT_U8, }, + [ETHTOOL_A_EEE_TX_LPI_TIMER] = { .name = "tx-lpi-timer", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_eee_nest = { + .max_attr = ETHTOOL_A_EEE_MAX, + .table = ethtool_eee_policy, +}; + +struct ynl_policy_attr ethtool_tsinfo_policy[ETHTOOL_A_TSINFO_MAX + 1] = { + [ETHTOOL_A_TSINFO_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_TSINFO_TIMESTAMPING] = { .name = "timestamping", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_TSINFO_TX_TYPES] = { .name = "tx-types", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_TSINFO_RX_FILTERS] = { .name = "rx-filters", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_TSINFO_PHC_INDEX] = { .name = "phc-index", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_tsinfo_nest = { + .max_attr = ETHTOOL_A_TSINFO_MAX, + .table = ethtool_tsinfo_policy, +}; + +struct ynl_policy_attr ethtool_cable_test_policy[ETHTOOL_A_CABLE_TEST_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, +}; + +struct ynl_policy_nest ethtool_cable_test_nest = { + .max_attr = ETHTOOL_A_CABLE_TEST_MAX, + .table = ethtool_cable_test_policy, +}; + +struct ynl_policy_attr ethtool_cable_test_ntf_policy[ETHTOOL_A_CABLE_TEST_NTF_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_NTF_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_CABLE_TEST_NTF_STATUS] = { .name = "status", .type = YNL_PT_U8, }, + [ETHTOOL_A_CABLE_TEST_NTF_NEST] = { .name = "nest", .type = YNL_PT_NEST, .nest = ðtool_cable_nest_nest, }, +}; + +struct ynl_policy_nest ethtool_cable_test_ntf_nest = { + .max_attr = ETHTOOL_A_CABLE_TEST_NTF_MAX, + .table = ethtool_cable_test_ntf_policy, +}; + +struct ynl_policy_attr ethtool_cable_test_tdr_policy[ETHTOOL_A_CABLE_TEST_TDR_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_TDR_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_CABLE_TEST_TDR_CFG] = { .name = "cfg", .type = YNL_PT_NEST, .nest = ðtool_cable_test_tdr_cfg_nest, }, +}; + +struct ynl_policy_nest ethtool_cable_test_tdr_nest = { + .max_attr = ETHTOOL_A_CABLE_TEST_TDR_MAX, + .table = ethtool_cable_test_tdr_policy, +}; + +struct ynl_policy_attr ethtool_cable_test_tdr_ntf_policy[ETHTOOL_A_CABLE_TEST_TDR_NTF_MAX + 1] = { + [ETHTOOL_A_CABLE_TEST_TDR_NTF_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_CABLE_TEST_TDR_NTF_STATUS] = { .name = "status", .type = YNL_PT_U8, }, + [ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST] = { .name = "nest", .type = YNL_PT_NEST, .nest = ðtool_cable_nest_nest, }, +}; + +struct ynl_policy_nest ethtool_cable_test_tdr_ntf_nest = { + .max_attr = ETHTOOL_A_CABLE_TEST_TDR_NTF_MAX, + .table = ethtool_cable_test_tdr_ntf_policy, +}; + +struct ynl_policy_attr ethtool_tunnel_info_policy[ETHTOOL_A_TUNNEL_INFO_MAX + 1] = { + [ETHTOOL_A_TUNNEL_INFO_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_TUNNEL_INFO_UDP_PORTS] = { .name = "udp-ports", .type = YNL_PT_NEST, .nest = ðtool_tunnel_udp_nest, }, +}; + +struct ynl_policy_nest ethtool_tunnel_info_nest = { + .max_attr = ETHTOOL_A_TUNNEL_INFO_MAX, + .table = ethtool_tunnel_info_policy, +}; + +struct ynl_policy_attr ethtool_fec_policy[ETHTOOL_A_FEC_MAX + 1] = { + [ETHTOOL_A_FEC_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_FEC_MODES] = { .name = "modes", .type = YNL_PT_NEST, .nest = ðtool_bitset_nest, }, + [ETHTOOL_A_FEC_AUTO] = { .name = "auto", .type = YNL_PT_U8, }, + [ETHTOOL_A_FEC_ACTIVE] = { .name = "active", .type = YNL_PT_U32, }, + [ETHTOOL_A_FEC_STATS] = { .name = "stats", .type = YNL_PT_NEST, .nest = ðtool_fec_stat_nest, }, +}; + +struct ynl_policy_nest ethtool_fec_nest = { + .max_attr = ETHTOOL_A_FEC_MAX, + .table = ethtool_fec_policy, +}; + +struct ynl_policy_attr ethtool_module_eeprom_policy[ETHTOOL_A_MODULE_EEPROM_MAX + 1] = { + [ETHTOOL_A_MODULE_EEPROM_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_MODULE_EEPROM_OFFSET] = { .name = "offset", .type = YNL_PT_U32, }, + [ETHTOOL_A_MODULE_EEPROM_LENGTH] = { .name = "length", .type = YNL_PT_U32, }, + [ETHTOOL_A_MODULE_EEPROM_PAGE] = { .name = "page", .type = YNL_PT_U8, }, + [ETHTOOL_A_MODULE_EEPROM_BANK] = { .name = "bank", .type = YNL_PT_U8, }, + [ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS] = { .name = "i2c-address", .type = YNL_PT_U8, }, + [ETHTOOL_A_MODULE_EEPROM_DATA] = { .name = "data", .type = YNL_PT_BINARY,}, +}; + +struct ynl_policy_nest ethtool_module_eeprom_nest = { + .max_attr = ETHTOOL_A_MODULE_EEPROM_MAX, + .table = ethtool_module_eeprom_policy, +}; + +struct ynl_policy_attr ethtool_phc_vclocks_policy[ETHTOOL_A_PHC_VCLOCKS_MAX + 1] = { + [ETHTOOL_A_PHC_VCLOCKS_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_PHC_VCLOCKS_NUM] = { .name = "num", .type = YNL_PT_U32, }, + [ETHTOOL_A_PHC_VCLOCKS_INDEX] = { .name = "index", .type = YNL_PT_BINARY,}, +}; + +struct ynl_policy_nest ethtool_phc_vclocks_nest = { + .max_attr = ETHTOOL_A_PHC_VCLOCKS_MAX, + .table = ethtool_phc_vclocks_policy, +}; + +struct ynl_policy_attr ethtool_module_policy[ETHTOOL_A_MODULE_MAX + 1] = { + [ETHTOOL_A_MODULE_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_MODULE_POWER_MODE_POLICY] = { .name = "power-mode-policy", .type = YNL_PT_U8, }, + [ETHTOOL_A_MODULE_POWER_MODE] = { .name = "power-mode", .type = YNL_PT_U8, }, +}; + +struct ynl_policy_nest ethtool_module_nest = { + .max_attr = ETHTOOL_A_MODULE_MAX, + .table = ethtool_module_policy, +}; + +struct ynl_policy_attr ethtool_pse_policy[ETHTOOL_A_PSE_MAX + 1] = { + [ETHTOOL_A_PSE_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_PODL_PSE_ADMIN_STATE] = { .name = "admin-state", .type = YNL_PT_U32, }, + [ETHTOOL_A_PODL_PSE_ADMIN_CONTROL] = { .name = "admin-control", .type = YNL_PT_U32, }, + [ETHTOOL_A_PODL_PSE_PW_D_STATUS] = { .name = "pw-d-status", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_pse_nest = { + .max_attr = ETHTOOL_A_PSE_MAX, + .table = ethtool_pse_policy, +}; + +struct ynl_policy_attr ethtool_rss_policy[ETHTOOL_A_RSS_MAX + 1] = { + [ETHTOOL_A_RSS_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_RSS_CONTEXT] = { .name = "context", .type = YNL_PT_U32, }, + [ETHTOOL_A_RSS_HFUNC] = { .name = "hfunc", .type = YNL_PT_U32, }, + [ETHTOOL_A_RSS_INDIR] = { .name = "indir", .type = YNL_PT_BINARY,}, + [ETHTOOL_A_RSS_HKEY] = { .name = "hkey", .type = YNL_PT_BINARY,}, +}; + +struct ynl_policy_nest ethtool_rss_nest = { + .max_attr = ETHTOOL_A_RSS_MAX, + .table = ethtool_rss_policy, +}; + +struct ynl_policy_attr ethtool_plca_policy[ETHTOOL_A_PLCA_MAX + 1] = { + [ETHTOOL_A_PLCA_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_PLCA_VERSION] = { .name = "version", .type = YNL_PT_U16, }, + [ETHTOOL_A_PLCA_ENABLED] = { .name = "enabled", .type = YNL_PT_U8, }, + [ETHTOOL_A_PLCA_STATUS] = { .name = "status", .type = YNL_PT_U8, }, + [ETHTOOL_A_PLCA_NODE_CNT] = { .name = "node-cnt", .type = YNL_PT_U32, }, + [ETHTOOL_A_PLCA_NODE_ID] = { .name = "node-id", .type = YNL_PT_U32, }, + [ETHTOOL_A_PLCA_TO_TMR] = { .name = "to-tmr", .type = YNL_PT_U32, }, + [ETHTOOL_A_PLCA_BURST_CNT] = { .name = "burst-cnt", .type = YNL_PT_U32, }, + [ETHTOOL_A_PLCA_BURST_TMR] = { .name = "burst-tmr", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest ethtool_plca_nest = { + .max_attr = ETHTOOL_A_PLCA_MAX, + .table = ethtool_plca_policy, +}; + +struct ynl_policy_attr ethtool_mm_policy[ETHTOOL_A_MM_MAX + 1] = { + [ETHTOOL_A_MM_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = ðtool_header_nest, }, + [ETHTOOL_A_MM_PMAC_ENABLED] = { .name = "pmac-enabled", .type = YNL_PT_U8, }, + [ETHTOOL_A_MM_TX_ENABLED] = { .name = "tx-enabled", .type = YNL_PT_U8, }, + [ETHTOOL_A_MM_TX_ACTIVE] = { .name = "tx-active", .type = YNL_PT_U8, }, + [ETHTOOL_A_MM_TX_MIN_FRAG_SIZE] = { .name = "tx-min-frag-size", .type = YNL_PT_U32, }, + [ETHTOOL_A_MM_RX_MIN_FRAG_SIZE] = { .name = "rx-min-frag-size", .type = YNL_PT_U32, }, + [ETHTOOL_A_MM_VERIFY_ENABLED] = { .name = "verify-enabled", .type = YNL_PT_U8, }, + [ETHTOOL_A_MM_VERIFY_STATUS] = { .name = "verify-status", .type = YNL_PT_U8, }, + [ETHTOOL_A_MM_VERIFY_TIME] = { .name = "verify-time", .type = YNL_PT_U32, }, + [ETHTOOL_A_MM_MAX_VERIFY_TIME] = { .name = "max-verify-time", .type = YNL_PT_U32, }, + [ETHTOOL_A_MM_STATS] = { .name = "stats", .type = YNL_PT_NEST, .nest = ðtool_mm_stat_nest, }, +}; + +struct ynl_policy_nest ethtool_mm_nest = { + .max_attr = ETHTOOL_A_MM_MAX, + .table = ethtool_mm_policy, +}; + +/* Common nested types */ +void ethtool_header_free(struct ethtool_header *obj) +{ + free(obj->dev_name); +} + +int ethtool_header_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_header *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.dev_index) + mnl_attr_put_u32(nlh, ETHTOOL_A_HEADER_DEV_INDEX, obj->dev_index); + if (obj->_present.dev_name_len) + mnl_attr_put_strz(nlh, ETHTOOL_A_HEADER_DEV_NAME, obj->dev_name); + if (obj->_present.flags) + mnl_attr_put_u32(nlh, ETHTOOL_A_HEADER_FLAGS, obj->flags); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_header_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_header *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_HEADER_DEV_INDEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.dev_index = 1; + dst->dev_index = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_HEADER_DEV_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.dev_name_len = len; + dst->dev_name = malloc(len + 1); + memcpy(dst->dev_name, mnl_attr_get_str(attr), len); + dst->dev_name[len] = 0; + } else if (type == ETHTOOL_A_HEADER_FLAGS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.flags = 1; + dst->flags = mnl_attr_get_u32(attr); + } + } + + return 0; +} + +void ethtool_pause_stat_free(struct ethtool_pause_stat *obj) +{ +} + +int ethtool_pause_stat_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_pause_stat *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.tx_frames) + mnl_attr_put_u64(nlh, ETHTOOL_A_PAUSE_STAT_TX_FRAMES, obj->tx_frames); + if (obj->_present.rx_frames) + mnl_attr_put_u64(nlh, ETHTOOL_A_PAUSE_STAT_RX_FRAMES, obj->rx_frames); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_pause_stat_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_pause_stat *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PAUSE_STAT_TX_FRAMES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_frames = 1; + dst->tx_frames = mnl_attr_get_u64(attr); + } else if (type == ETHTOOL_A_PAUSE_STAT_RX_FRAMES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_frames = 1; + dst->rx_frames = mnl_attr_get_u64(attr); + } + } + + return 0; +} + +void ethtool_cable_test_tdr_cfg_free(struct ethtool_cable_test_tdr_cfg *obj) +{ +} + +void ethtool_fec_stat_free(struct ethtool_fec_stat *obj) +{ + free(obj->corrected); + free(obj->uncorr); + free(obj->corr_bits); +} + +int ethtool_fec_stat_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_fec_stat *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.corrected_len) + mnl_attr_put(nlh, ETHTOOL_A_FEC_STAT_CORRECTED, obj->_present.corrected_len, obj->corrected); + if (obj->_present.uncorr_len) + mnl_attr_put(nlh, ETHTOOL_A_FEC_STAT_UNCORR, obj->_present.uncorr_len, obj->uncorr); + if (obj->_present.corr_bits_len) + mnl_attr_put(nlh, ETHTOOL_A_FEC_STAT_CORR_BITS, obj->_present.corr_bits_len, obj->corr_bits); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_fec_stat_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_fec_stat *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_FEC_STAT_CORRECTED) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.corrected_len = len; + dst->corrected = malloc(len); + memcpy(dst->corrected, mnl_attr_get_payload(attr), len); + } else if (type == ETHTOOL_A_FEC_STAT_UNCORR) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.uncorr_len = len; + dst->uncorr = malloc(len); + memcpy(dst->uncorr, mnl_attr_get_payload(attr), len); + } else if (type == ETHTOOL_A_FEC_STAT_CORR_BITS) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.corr_bits_len = len; + dst->corr_bits = malloc(len); + memcpy(dst->corr_bits, mnl_attr_get_payload(attr), len); + } + } + + return 0; +} + +void ethtool_mm_stat_free(struct ethtool_mm_stat *obj) +{ +} + +int ethtool_mm_stat_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_mm_stat *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reassembly_errors = 1; + dst->reassembly_errors = mnl_attr_get_u64(attr); + } else if (type == ETHTOOL_A_MM_STAT_SMD_ERRORS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.smd_errors = 1; + dst->smd_errors = mnl_attr_get_u64(attr); + } else if (type == ETHTOOL_A_MM_STAT_REASSEMBLY_OK) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.reassembly_ok = 1; + dst->reassembly_ok = mnl_attr_get_u64(attr); + } else if (type == ETHTOOL_A_MM_STAT_RX_FRAG_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_frag_count = 1; + dst->rx_frag_count = mnl_attr_get_u64(attr); + } else if (type == ETHTOOL_A_MM_STAT_TX_FRAG_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_frag_count = 1; + dst->tx_frag_count = mnl_attr_get_u64(attr); + } else if (type == ETHTOOL_A_MM_STAT_HOLD_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.hold_count = 1; + dst->hold_count = mnl_attr_get_u64(attr); + } + } + + return 0; +} + +void ethtool_cable_result_free(struct ethtool_cable_result *obj) +{ +} + +int ethtool_cable_result_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_cable_result *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_CABLE_RESULT_PAIR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.pair = 1; + dst->pair = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_CABLE_RESULT_CODE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.code = 1; + dst->code = mnl_attr_get_u8(attr); + } + } + + return 0; +} + +void ethtool_cable_fault_length_free(struct ethtool_cable_fault_length *obj) +{ +} + +int ethtool_cable_fault_length_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_cable_fault_length *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.pair = 1; + dst->pair = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_CABLE_FAULT_LENGTH_CM) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.cm = 1; + dst->cm = mnl_attr_get_u32(attr); + } + } + + return 0; +} + +void ethtool_bitset_bit_free(struct ethtool_bitset_bit *obj) +{ + free(obj->name); +} + +int ethtool_bitset_bit_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_bitset_bit *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.index) + mnl_attr_put_u32(nlh, ETHTOOL_A_BITSET_BIT_INDEX, obj->index); + if (obj->_present.name_len) + mnl_attr_put_strz(nlh, ETHTOOL_A_BITSET_BIT_NAME, obj->name); + if (obj->_present.value) + mnl_attr_put(nlh, ETHTOOL_A_BITSET_BIT_VALUE, 0, NULL); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_bitset_bit_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_bitset_bit *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_BITSET_BIT_INDEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.index = 1; + dst->index = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_BITSET_BIT_NAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.name_len = len; + dst->name = malloc(len + 1); + memcpy(dst->name, mnl_attr_get_str(attr), len); + dst->name[len] = 0; + } else if (type == ETHTOOL_A_BITSET_BIT_VALUE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.value = 1; + } + } + + return 0; +} + +void ethtool_tunnel_udp_entry_free(struct ethtool_tunnel_udp_entry *obj) +{ +} + +int ethtool_tunnel_udp_entry_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_tunnel_udp_entry *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.port = 1; + dst->port = mnl_attr_get_u16(attr); + } else if (type == ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.type = 1; + dst->type = mnl_attr_get_u32(attr); + } + } + + return 0; +} + +void ethtool_string_free(struct ethtool_string *obj) +{ + free(obj->value); +} + +int ethtool_string_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_string *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.index) + mnl_attr_put_u32(nlh, ETHTOOL_A_STRING_INDEX, obj->index); + if (obj->_present.value_len) + mnl_attr_put_strz(nlh, ETHTOOL_A_STRING_VALUE, obj->value); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_string_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_string *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_STRING_INDEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.index = 1; + dst->index = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_STRING_VALUE) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.value_len = len; + dst->value = malloc(len + 1); + memcpy(dst->value, mnl_attr_get_str(attr), len); + dst->value[len] = 0; + } + } + + return 0; +} + +void ethtool_cable_nest_free(struct ethtool_cable_nest *obj) +{ + ethtool_cable_result_free(&obj->result); + ethtool_cable_fault_length_free(&obj->fault_length); +} + +int ethtool_cable_nest_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_cable_nest *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + parg.ys = yarg->ys; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_CABLE_NEST_RESULT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.result = 1; + + parg.rsp_policy = ðtool_cable_result_nest; + parg.data = &dst->result; + if (ethtool_cable_result_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_CABLE_NEST_FAULT_LENGTH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.fault_length = 1; + + parg.rsp_policy = ðtool_cable_fault_length_nest; + parg.data = &dst->fault_length; + if (ethtool_cable_fault_length_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return 0; +} + +void ethtool_bitset_bits_free(struct ethtool_bitset_bits *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_bit; i++) + ethtool_bitset_bit_free(&obj->bit[i]); + free(obj->bit); +} + +int ethtool_bitset_bits_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_bitset_bits *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + for (unsigned int i = 0; i < obj->n_bit; i++) + ethtool_bitset_bit_put(nlh, ETHTOOL_A_BITSET_BITS_BIT, &obj->bit[i]); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_bitset_bits_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_bitset_bits *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + unsigned int n_bit = 0; + int i; + + parg.ys = yarg->ys; + + if (dst->bit) + return ynl_error_parse(yarg, "attribute already present (bitset-bits.bit)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_BITSET_BITS_BIT) { + n_bit++; + } + } + + if (n_bit) { + dst->bit = calloc(n_bit, sizeof(*dst->bit)); + dst->n_bit = n_bit; + i = 0; + parg.rsp_policy = ðtool_bitset_bit_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == ETHTOOL_A_BITSET_BITS_BIT) { + parg.data = &dst->bit[i]; + if (ethtool_bitset_bit_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void ethtool_strings_free(struct ethtool_strings *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_string; i++) + ethtool_string_free(&obj->string[i]); + free(obj->string); +} + +int ethtool_strings_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_strings *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + for (unsigned int i = 0; i < obj->n_string; i++) + ethtool_string_put(nlh, ETHTOOL_A_STRINGS_STRING, &obj->string[i]); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_strings_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_strings *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + unsigned int n_string = 0; + int i; + + parg.ys = yarg->ys; + + if (dst->string) + return ynl_error_parse(yarg, "attribute already present (strings.string)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_STRINGS_STRING) { + n_string++; + } + } + + if (n_string) { + dst->string = calloc(n_string, sizeof(*dst->string)); + dst->n_string = n_string; + i = 0; + parg.rsp_policy = ðtool_string_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == ETHTOOL_A_STRINGS_STRING) { + parg.data = &dst->string[i]; + if (ethtool_string_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void ethtool_bitset_free(struct ethtool_bitset *obj) +{ + ethtool_bitset_bits_free(&obj->bits); +} + +int ethtool_bitset_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_bitset *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.nomask) + mnl_attr_put(nlh, ETHTOOL_A_BITSET_NOMASK, 0, NULL); + if (obj->_present.size) + mnl_attr_put_u32(nlh, ETHTOOL_A_BITSET_SIZE, obj->size); + if (obj->_present.bits) + ethtool_bitset_bits_put(nlh, ETHTOOL_A_BITSET_BITS, &obj->bits); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_bitset_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_bitset *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + parg.ys = yarg->ys; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_BITSET_NOMASK) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.nomask = 1; + } else if (type == ETHTOOL_A_BITSET_SIZE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.size = 1; + dst->size = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_BITSET_BITS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.bits = 1; + + parg.rsp_policy = ðtool_bitset_bits_nest; + parg.data = &dst->bits; + if (ethtool_bitset_bits_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return 0; +} + +void ethtool_stringset_free(struct ethtool_stringset_ *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_strings; i++) + ethtool_strings_free(&obj->strings[i]); + free(obj->strings); +} + +int ethtool_stringset_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_stringset_ *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + if (obj->_present.id) + mnl_attr_put_u32(nlh, ETHTOOL_A_STRINGSET_ID, obj->id); + if (obj->_present.count) + mnl_attr_put_u32(nlh, ETHTOOL_A_STRINGSET_COUNT, obj->count); + for (unsigned int i = 0; i < obj->n_strings; i++) + ethtool_strings_put(nlh, ETHTOOL_A_STRINGSET_STRINGS, &obj->strings[i]); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_stringset_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_stringset_ *dst = yarg->data; + unsigned int n_strings = 0; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + parg.ys = yarg->ys; + + if (dst->strings) + return ynl_error_parse(yarg, "attribute already present (stringset.strings)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_STRINGSET_ID) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.id = 1; + dst->id = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_STRINGSET_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.count = 1; + dst->count = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_STRINGSET_STRINGS) { + n_strings++; + } + } + + if (n_strings) { + dst->strings = calloc(n_strings, sizeof(*dst->strings)); + dst->n_strings = n_strings; + i = 0; + parg.rsp_policy = ðtool_strings_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == ETHTOOL_A_STRINGSET_STRINGS) { + parg.data = &dst->strings[i]; + if (ethtool_strings_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void ethtool_tunnel_udp_table_free(struct ethtool_tunnel_udp_table *obj) +{ + unsigned int i; + + ethtool_bitset_free(&obj->types); + for (i = 0; i < obj->n_entry; i++) + ethtool_tunnel_udp_entry_free(&obj->entry[i]); + free(obj->entry); +} + +int ethtool_tunnel_udp_table_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_tunnel_udp_table *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + unsigned int n_entry = 0; + int i; + + parg.ys = yarg->ys; + + if (dst->entry) + return ynl_error_parse(yarg, "attribute already present (tunnel-udp-table.entry)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.size = 1; + dst->size = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.types = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->types; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY) { + n_entry++; + } + } + + if (n_entry) { + dst->entry = calloc(n_entry, sizeof(*dst->entry)); + dst->n_entry = n_entry; + i = 0; + parg.rsp_policy = ðtool_tunnel_udp_entry_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY) { + parg.data = &dst->entry[i]; + if (ethtool_tunnel_udp_entry_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void ethtool_stringsets_free(struct ethtool_stringsets *obj) +{ + unsigned int i; + + for (i = 0; i < obj->n_stringset; i++) + ethtool_stringset_free(&obj->stringset[i]); + free(obj->stringset); +} + +int ethtool_stringsets_put(struct nlmsghdr *nlh, unsigned int attr_type, + struct ethtool_stringsets *obj) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, attr_type); + for (unsigned int i = 0; i < obj->n_stringset; i++) + ethtool_stringset_put(nlh, ETHTOOL_A_STRINGSETS_STRINGSET, &obj->stringset[i]); + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int ethtool_stringsets_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_stringsets *dst = yarg->data; + unsigned int n_stringset = 0; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + parg.ys = yarg->ys; + + if (dst->stringset) + return ynl_error_parse(yarg, "attribute already present (stringsets.stringset)"); + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_STRINGSETS_STRINGSET) { + n_stringset++; + } + } + + if (n_stringset) { + dst->stringset = calloc(n_stringset, sizeof(*dst->stringset)); + dst->n_stringset = n_stringset; + i = 0; + parg.rsp_policy = ðtool_stringset_nest; + mnl_attr_for_each_nested(attr, nested) { + if (mnl_attr_get_type(attr) == ETHTOOL_A_STRINGSETS_STRINGSET) { + parg.data = &dst->stringset[i]; + if (ethtool_stringset_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + + return 0; +} + +void ethtool_tunnel_udp_free(struct ethtool_tunnel_udp *obj) +{ + ethtool_tunnel_udp_table_free(&obj->table); +} + +int ethtool_tunnel_udp_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct ethtool_tunnel_udp *dst = yarg->data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + parg.ys = yarg->ys; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_TUNNEL_UDP_TABLE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.table = 1; + + parg.rsp_policy = ðtool_tunnel_udp_table_nest; + parg.data = &dst->table; + if (ethtool_tunnel_udp_table_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return 0; +} + +/* ============== ETHTOOL_MSG_STRSET_GET ============== */ +/* ETHTOOL_MSG_STRSET_GET - do */ +void ethtool_strset_get_req_free(struct ethtool_strset_get_req *req) +{ + ethtool_header_free(&req->header); + ethtool_stringsets_free(&req->stringsets); + free(req); +} + +void ethtool_strset_get_rsp_free(struct ethtool_strset_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_stringsets_free(&rsp->stringsets); + free(rsp); +} + +int ethtool_strset_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_strset_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_STRSET_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_STRSET_STRINGSETS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.stringsets = 1; + + parg.rsp_policy = ðtool_stringsets_nest; + parg.data = &dst->stringsets; + if (ethtool_stringsets_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_strset_get_rsp * +ethtool_strset_get(struct ynl_sock *ys, struct ethtool_strset_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_strset_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_STRSET_GET, 1); + ys->req_policy = ðtool_strset_nest; + yrs.yarg.rsp_policy = ðtool_strset_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_STRSET_HEADER, &req->header); + if (req->_present.stringsets) + ethtool_stringsets_put(nlh, ETHTOOL_A_STRSET_STRINGSETS, &req->stringsets); + if (req->_present.counts_only) + mnl_attr_put(nlh, ETHTOOL_A_STRSET_COUNTS_ONLY, 0, NULL); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_strset_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_STRSET_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_strset_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_STRSET_GET - dump */ +void ethtool_strset_get_list_free(struct ethtool_strset_get_list *rsp) +{ + struct ethtool_strset_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_stringsets_free(&rsp->obj.stringsets); + free(rsp); + } +} + +struct ethtool_strset_get_list * +ethtool_strset_get_dump(struct ynl_sock *ys, + struct ethtool_strset_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_strset_get_list); + yds.cb = ethtool_strset_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_STRSET_GET; + yds.rsp_policy = ðtool_strset_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_STRSET_GET, 1); + ys->req_policy = ðtool_strset_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_STRSET_HEADER, &req->header); + if (req->_present.stringsets) + ethtool_stringsets_put(nlh, ETHTOOL_A_STRSET_STRINGSETS, &req->stringsets); + if (req->_present.counts_only) + mnl_attr_put(nlh, ETHTOOL_A_STRSET_COUNTS_ONLY, 0, NULL); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_strset_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_LINKINFO_GET ============== */ +/* ETHTOOL_MSG_LINKINFO_GET - do */ +void ethtool_linkinfo_get_req_free(struct ethtool_linkinfo_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_linkinfo_get_rsp_free(struct ethtool_linkinfo_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_linkinfo_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_linkinfo_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_LINKINFO_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_LINKINFO_PORT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.port = 1; + dst->port = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKINFO_PHYADDR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.phyaddr = 1; + dst->phyaddr = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKINFO_TP_MDIX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tp_mdix = 1; + dst->tp_mdix = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKINFO_TP_MDIX_CTRL) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tp_mdix_ctrl = 1; + dst->tp_mdix_ctrl = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKINFO_TRANSCEIVER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.transceiver = 1; + dst->transceiver = mnl_attr_get_u8(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_linkinfo_get_rsp * +ethtool_linkinfo_get(struct ynl_sock *ys, struct ethtool_linkinfo_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_linkinfo_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_LINKINFO_GET, 1); + ys->req_policy = ðtool_linkinfo_nest; + yrs.yarg.rsp_policy = ðtool_linkinfo_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKINFO_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_linkinfo_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_LINKINFO_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_linkinfo_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_LINKINFO_GET - dump */ +void ethtool_linkinfo_get_list_free(struct ethtool_linkinfo_get_list *rsp) +{ + struct ethtool_linkinfo_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_linkinfo_get_list * +ethtool_linkinfo_get_dump(struct ynl_sock *ys, + struct ethtool_linkinfo_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_linkinfo_get_list); + yds.cb = ethtool_linkinfo_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_LINKINFO_GET; + yds.rsp_policy = ðtool_linkinfo_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_LINKINFO_GET, 1); + ys->req_policy = ðtool_linkinfo_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKINFO_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_linkinfo_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_LINKINFO_GET - notify */ +void ethtool_linkinfo_get_ntf_free(struct ethtool_linkinfo_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ============== ETHTOOL_MSG_LINKINFO_SET ============== */ +/* ETHTOOL_MSG_LINKINFO_SET - do */ +void ethtool_linkinfo_set_req_free(struct ethtool_linkinfo_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_linkinfo_set(struct ynl_sock *ys, + struct ethtool_linkinfo_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_LINKINFO_SET, 1); + ys->req_policy = ðtool_linkinfo_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKINFO_HEADER, &req->header); + if (req->_present.port) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKINFO_PORT, req->port); + if (req->_present.phyaddr) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKINFO_PHYADDR, req->phyaddr); + if (req->_present.tp_mdix) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKINFO_TP_MDIX, req->tp_mdix); + if (req->_present.tp_mdix_ctrl) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL, req->tp_mdix_ctrl); + if (req->_present.transceiver) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKINFO_TRANSCEIVER, req->transceiver); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_LINKMODES_GET ============== */ +/* ETHTOOL_MSG_LINKMODES_GET - do */ +void ethtool_linkmodes_get_req_free(struct ethtool_linkmodes_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_linkmodes_get_rsp_free(struct ethtool_linkmodes_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->ours); + ethtool_bitset_free(&rsp->peer); + free(rsp); +} + +int ethtool_linkmodes_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_linkmodes_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_LINKMODES_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_LINKMODES_AUTONEG) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.autoneg = 1; + dst->autoneg = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKMODES_OURS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ours = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->ours; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_LINKMODES_PEER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.peer = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->peer; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_LINKMODES_SPEED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.speed = 1; + dst->speed = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_LINKMODES_DUPLEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.duplex = 1; + dst->duplex = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.master_slave_cfg = 1; + dst->master_slave_cfg = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.master_slave_state = 1; + dst->master_slave_state = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKMODES_LANES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.lanes = 1; + dst->lanes = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_LINKMODES_RATE_MATCHING) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rate_matching = 1; + dst->rate_matching = mnl_attr_get_u8(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_linkmodes_get_rsp * +ethtool_linkmodes_get(struct ynl_sock *ys, + struct ethtool_linkmodes_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_linkmodes_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_LINKMODES_GET, 1); + ys->req_policy = ðtool_linkmodes_nest; + yrs.yarg.rsp_policy = ðtool_linkmodes_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKMODES_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_linkmodes_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_LINKMODES_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_linkmodes_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_LINKMODES_GET - dump */ +void ethtool_linkmodes_get_list_free(struct ethtool_linkmodes_get_list *rsp) +{ + struct ethtool_linkmodes_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.ours); + ethtool_bitset_free(&rsp->obj.peer); + free(rsp); + } +} + +struct ethtool_linkmodes_get_list * +ethtool_linkmodes_get_dump(struct ynl_sock *ys, + struct ethtool_linkmodes_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_linkmodes_get_list); + yds.cb = ethtool_linkmodes_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_LINKMODES_GET; + yds.rsp_policy = ðtool_linkmodes_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_LINKMODES_GET, 1); + ys->req_policy = ðtool_linkmodes_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKMODES_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_linkmodes_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_LINKMODES_GET - notify */ +void ethtool_linkmodes_get_ntf_free(struct ethtool_linkmodes_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.ours); + ethtool_bitset_free(&rsp->obj.peer); + free(rsp); +} + +/* ============== ETHTOOL_MSG_LINKMODES_SET ============== */ +/* ETHTOOL_MSG_LINKMODES_SET - do */ +void ethtool_linkmodes_set_req_free(struct ethtool_linkmodes_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->ours); + ethtool_bitset_free(&req->peer); + free(req); +} + +int ethtool_linkmodes_set(struct ynl_sock *ys, + struct ethtool_linkmodes_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_LINKMODES_SET, 1); + ys->req_policy = ðtool_linkmodes_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKMODES_HEADER, &req->header); + if (req->_present.autoneg) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKMODES_AUTONEG, req->autoneg); + if (req->_present.ours) + ethtool_bitset_put(nlh, ETHTOOL_A_LINKMODES_OURS, &req->ours); + if (req->_present.peer) + ethtool_bitset_put(nlh, ETHTOOL_A_LINKMODES_PEER, &req->peer); + if (req->_present.speed) + mnl_attr_put_u32(nlh, ETHTOOL_A_LINKMODES_SPEED, req->speed); + if (req->_present.duplex) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKMODES_DUPLEX, req->duplex); + if (req->_present.master_slave_cfg) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG, req->master_slave_cfg); + if (req->_present.master_slave_state) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE, req->master_slave_state); + if (req->_present.lanes) + mnl_attr_put_u32(nlh, ETHTOOL_A_LINKMODES_LANES, req->lanes); + if (req->_present.rate_matching) + mnl_attr_put_u8(nlh, ETHTOOL_A_LINKMODES_RATE_MATCHING, req->rate_matching); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_LINKSTATE_GET ============== */ +/* ETHTOOL_MSG_LINKSTATE_GET - do */ +void ethtool_linkstate_get_req_free(struct ethtool_linkstate_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_linkstate_get_rsp_free(struct ethtool_linkstate_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_linkstate_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_linkstate_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_LINKSTATE_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_LINKSTATE_LINK) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.link = 1; + dst->link = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKSTATE_SQI) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.sqi = 1; + dst->sqi = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_LINKSTATE_SQI_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.sqi_max = 1; + dst->sqi_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_LINKSTATE_EXT_STATE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ext_state = 1; + dst->ext_state = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKSTATE_EXT_SUBSTATE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ext_substate = 1; + dst->ext_substate = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ext_down_cnt = 1; + dst->ext_down_cnt = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_linkstate_get_rsp * +ethtool_linkstate_get(struct ynl_sock *ys, + struct ethtool_linkstate_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_linkstate_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_LINKSTATE_GET, 1); + ys->req_policy = ðtool_linkstate_nest; + yrs.yarg.rsp_policy = ðtool_linkstate_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKSTATE_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_linkstate_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_LINKSTATE_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_linkstate_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_LINKSTATE_GET - dump */ +void ethtool_linkstate_get_list_free(struct ethtool_linkstate_get_list *rsp) +{ + struct ethtool_linkstate_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_linkstate_get_list * +ethtool_linkstate_get_dump(struct ynl_sock *ys, + struct ethtool_linkstate_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_linkstate_get_list); + yds.cb = ethtool_linkstate_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_LINKSTATE_GET; + yds.rsp_policy = ðtool_linkstate_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_LINKSTATE_GET, 1); + ys->req_policy = ðtool_linkstate_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_LINKSTATE_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_linkstate_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_DEBUG_GET ============== */ +/* ETHTOOL_MSG_DEBUG_GET - do */ +void ethtool_debug_get_req_free(struct ethtool_debug_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_debug_get_rsp_free(struct ethtool_debug_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->msgmask); + free(rsp); +} + +int ethtool_debug_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_debug_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_DEBUG_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_DEBUG_MSGMASK) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.msgmask = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->msgmask; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_debug_get_rsp * +ethtool_debug_get(struct ynl_sock *ys, struct ethtool_debug_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_debug_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_DEBUG_GET, 1); + ys->req_policy = ðtool_debug_nest; + yrs.yarg.rsp_policy = ðtool_debug_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_DEBUG_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_debug_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_DEBUG_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_debug_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_DEBUG_GET - dump */ +void ethtool_debug_get_list_free(struct ethtool_debug_get_list *rsp) +{ + struct ethtool_debug_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.msgmask); + free(rsp); + } +} + +struct ethtool_debug_get_list * +ethtool_debug_get_dump(struct ynl_sock *ys, + struct ethtool_debug_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_debug_get_list); + yds.cb = ethtool_debug_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_DEBUG_GET; + yds.rsp_policy = ðtool_debug_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_DEBUG_GET, 1); + ys->req_policy = ðtool_debug_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_DEBUG_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_debug_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_DEBUG_GET - notify */ +void ethtool_debug_get_ntf_free(struct ethtool_debug_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.msgmask); + free(rsp); +} + +/* ============== ETHTOOL_MSG_DEBUG_SET ============== */ +/* ETHTOOL_MSG_DEBUG_SET - do */ +void ethtool_debug_set_req_free(struct ethtool_debug_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->msgmask); + free(req); +} + +int ethtool_debug_set(struct ynl_sock *ys, struct ethtool_debug_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_DEBUG_SET, 1); + ys->req_policy = ðtool_debug_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_DEBUG_HEADER, &req->header); + if (req->_present.msgmask) + ethtool_bitset_put(nlh, ETHTOOL_A_DEBUG_MSGMASK, &req->msgmask); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_WOL_GET ============== */ +/* ETHTOOL_MSG_WOL_GET - do */ +void ethtool_wol_get_req_free(struct ethtool_wol_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_wol_get_rsp_free(struct ethtool_wol_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->modes); + free(rsp->sopass); + free(rsp); +} + +int ethtool_wol_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ethtool_wol_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_WOL_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_WOL_MODES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.modes = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->modes; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_WOL_SOPASS) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.sopass_len = len; + dst->sopass = malloc(len); + memcpy(dst->sopass, mnl_attr_get_payload(attr), len); + } + } + + return MNL_CB_OK; +} + +struct ethtool_wol_get_rsp * +ethtool_wol_get(struct ynl_sock *ys, struct ethtool_wol_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_wol_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_WOL_GET, 1); + ys->req_policy = ðtool_wol_nest; + yrs.yarg.rsp_policy = ðtool_wol_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_WOL_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_wol_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_WOL_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_wol_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_WOL_GET - dump */ +void ethtool_wol_get_list_free(struct ethtool_wol_get_list *rsp) +{ + struct ethtool_wol_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.modes); + free(rsp->obj.sopass); + free(rsp); + } +} + +struct ethtool_wol_get_list * +ethtool_wol_get_dump(struct ynl_sock *ys, struct ethtool_wol_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_wol_get_list); + yds.cb = ethtool_wol_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_WOL_GET; + yds.rsp_policy = ðtool_wol_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_WOL_GET, 1); + ys->req_policy = ðtool_wol_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_WOL_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_wol_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_WOL_GET - notify */ +void ethtool_wol_get_ntf_free(struct ethtool_wol_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.modes); + free(rsp->obj.sopass); + free(rsp); +} + +/* ============== ETHTOOL_MSG_WOL_SET ============== */ +/* ETHTOOL_MSG_WOL_SET - do */ +void ethtool_wol_set_req_free(struct ethtool_wol_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->modes); + free(req->sopass); + free(req); +} + +int ethtool_wol_set(struct ynl_sock *ys, struct ethtool_wol_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_WOL_SET, 1); + ys->req_policy = ðtool_wol_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_WOL_HEADER, &req->header); + if (req->_present.modes) + ethtool_bitset_put(nlh, ETHTOOL_A_WOL_MODES, &req->modes); + if (req->_present.sopass_len) + mnl_attr_put(nlh, ETHTOOL_A_WOL_SOPASS, req->_present.sopass_len, req->sopass); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_FEATURES_GET ============== */ +/* ETHTOOL_MSG_FEATURES_GET - do */ +void ethtool_features_get_req_free(struct ethtool_features_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_features_get_rsp_free(struct ethtool_features_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->hw); + ethtool_bitset_free(&rsp->wanted); + ethtool_bitset_free(&rsp->active); + ethtool_bitset_free(&rsp->nochange); + free(rsp); +} + +int ethtool_features_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_features_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_FEATURES_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_HW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.hw = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->hw; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_WANTED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.wanted = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->wanted; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_ACTIVE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.active = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->active; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_NOCHANGE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.nochange = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->nochange; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_features_get_rsp * +ethtool_features_get(struct ynl_sock *ys, struct ethtool_features_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_features_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_FEATURES_GET, 1); + ys->req_policy = ðtool_features_nest; + yrs.yarg.rsp_policy = ðtool_features_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_FEATURES_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_features_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_FEATURES_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_features_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_FEATURES_GET - dump */ +void ethtool_features_get_list_free(struct ethtool_features_get_list *rsp) +{ + struct ethtool_features_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.hw); + ethtool_bitset_free(&rsp->obj.wanted); + ethtool_bitset_free(&rsp->obj.active); + ethtool_bitset_free(&rsp->obj.nochange); + free(rsp); + } +} + +struct ethtool_features_get_list * +ethtool_features_get_dump(struct ynl_sock *ys, + struct ethtool_features_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_features_get_list); + yds.cb = ethtool_features_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_FEATURES_GET; + yds.rsp_policy = ðtool_features_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_FEATURES_GET, 1); + ys->req_policy = ðtool_features_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_FEATURES_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_features_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_FEATURES_GET - notify */ +void ethtool_features_get_ntf_free(struct ethtool_features_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.hw); + ethtool_bitset_free(&rsp->obj.wanted); + ethtool_bitset_free(&rsp->obj.active); + ethtool_bitset_free(&rsp->obj.nochange); + free(rsp); +} + +/* ============== ETHTOOL_MSG_FEATURES_SET ============== */ +/* ETHTOOL_MSG_FEATURES_SET - do */ +void ethtool_features_set_req_free(struct ethtool_features_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->hw); + ethtool_bitset_free(&req->wanted); + ethtool_bitset_free(&req->active); + ethtool_bitset_free(&req->nochange); + free(req); +} + +void ethtool_features_set_rsp_free(struct ethtool_features_set_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->hw); + ethtool_bitset_free(&rsp->wanted); + ethtool_bitset_free(&rsp->active); + ethtool_bitset_free(&rsp->nochange); + free(rsp); +} + +int ethtool_features_set_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_features_set_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_FEATURES_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_HW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.hw = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->hw; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_WANTED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.wanted = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->wanted; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_ACTIVE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.active = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->active; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEATURES_NOCHANGE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.nochange = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->nochange; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_features_set_rsp * +ethtool_features_set(struct ynl_sock *ys, struct ethtool_features_set_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_features_set_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_FEATURES_SET, 1); + ys->req_policy = ðtool_features_nest; + yrs.yarg.rsp_policy = ðtool_features_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_FEATURES_HEADER, &req->header); + if (req->_present.hw) + ethtool_bitset_put(nlh, ETHTOOL_A_FEATURES_HW, &req->hw); + if (req->_present.wanted) + ethtool_bitset_put(nlh, ETHTOOL_A_FEATURES_WANTED, &req->wanted); + if (req->_present.active) + ethtool_bitset_put(nlh, ETHTOOL_A_FEATURES_ACTIVE, &req->active); + if (req->_present.nochange) + ethtool_bitset_put(nlh, ETHTOOL_A_FEATURES_NOCHANGE, &req->nochange); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_features_set_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_FEATURES_SET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_features_set_rsp_free(rsp); + return NULL; +} + +/* ============== ETHTOOL_MSG_PRIVFLAGS_GET ============== */ +/* ETHTOOL_MSG_PRIVFLAGS_GET - do */ +void ethtool_privflags_get_req_free(struct ethtool_privflags_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_privflags_get_rsp_free(struct ethtool_privflags_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->flags); + free(rsp); +} + +int ethtool_privflags_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_privflags_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PRIVFLAGS_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PRIVFLAGS_FLAGS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.flags = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->flags; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_privflags_get_rsp * +ethtool_privflags_get(struct ynl_sock *ys, + struct ethtool_privflags_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_privflags_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PRIVFLAGS_GET, 1); + ys->req_policy = ðtool_privflags_nest; + yrs.yarg.rsp_policy = ðtool_privflags_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PRIVFLAGS_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_privflags_get_rsp_parse; + yrs.rsp_cmd = 14; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_privflags_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_PRIVFLAGS_GET - dump */ +void ethtool_privflags_get_list_free(struct ethtool_privflags_get_list *rsp) +{ + struct ethtool_privflags_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.flags); + free(rsp); + } +} + +struct ethtool_privflags_get_list * +ethtool_privflags_get_dump(struct ynl_sock *ys, + struct ethtool_privflags_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_privflags_get_list); + yds.cb = ethtool_privflags_get_rsp_parse; + yds.rsp_cmd = 14; + yds.rsp_policy = ðtool_privflags_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PRIVFLAGS_GET, 1); + ys->req_policy = ðtool_privflags_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PRIVFLAGS_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_privflags_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_PRIVFLAGS_GET - notify */ +void ethtool_privflags_get_ntf_free(struct ethtool_privflags_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.flags); + free(rsp); +} + +/* ============== ETHTOOL_MSG_PRIVFLAGS_SET ============== */ +/* ETHTOOL_MSG_PRIVFLAGS_SET - do */ +void ethtool_privflags_set_req_free(struct ethtool_privflags_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->flags); + free(req); +} + +int ethtool_privflags_set(struct ynl_sock *ys, + struct ethtool_privflags_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PRIVFLAGS_SET, 1); + ys->req_policy = ðtool_privflags_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PRIVFLAGS_HEADER, &req->header); + if (req->_present.flags) + ethtool_bitset_put(nlh, ETHTOOL_A_PRIVFLAGS_FLAGS, &req->flags); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_RINGS_GET ============== */ +/* ETHTOOL_MSG_RINGS_GET - do */ +void ethtool_rings_get_req_free(struct ethtool_rings_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_rings_get_rsp_free(struct ethtool_rings_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_rings_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_rings_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_RINGS_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_RINGS_RX_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_max = 1; + dst->rx_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_RX_MINI_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_mini_max = 1; + dst->rx_mini_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_RX_JUMBO_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_jumbo_max = 1; + dst->rx_jumbo_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_TX_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_max = 1; + dst->tx_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_RX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx = 1; + dst->rx = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_RX_MINI) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_mini = 1; + dst->rx_mini = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_RX_JUMBO) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_jumbo = 1; + dst->rx_jumbo = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_TX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx = 1; + dst->tx = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_RX_BUF_LEN) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_buf_len = 1; + dst->rx_buf_len = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_TCP_DATA_SPLIT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tcp_data_split = 1; + dst->tcp_data_split = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_RINGS_CQE_SIZE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.cqe_size = 1; + dst->cqe_size = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_TX_PUSH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_push = 1; + dst->tx_push = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_RINGS_RX_PUSH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_push = 1; + dst->rx_push = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_push_buf_len = 1; + dst->tx_push_buf_len = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_push_buf_len_max = 1; + dst->tx_push_buf_len_max = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_rings_get_rsp * +ethtool_rings_get(struct ynl_sock *ys, struct ethtool_rings_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_rings_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_RINGS_GET, 1); + ys->req_policy = ðtool_rings_nest; + yrs.yarg.rsp_policy = ðtool_rings_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_RINGS_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_rings_get_rsp_parse; + yrs.rsp_cmd = 16; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_rings_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_RINGS_GET - dump */ +void ethtool_rings_get_list_free(struct ethtool_rings_get_list *rsp) +{ + struct ethtool_rings_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_rings_get_list * +ethtool_rings_get_dump(struct ynl_sock *ys, + struct ethtool_rings_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_rings_get_list); + yds.cb = ethtool_rings_get_rsp_parse; + yds.rsp_cmd = 16; + yds.rsp_policy = ðtool_rings_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_RINGS_GET, 1); + ys->req_policy = ðtool_rings_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_RINGS_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_rings_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_RINGS_GET - notify */ +void ethtool_rings_get_ntf_free(struct ethtool_rings_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ============== ETHTOOL_MSG_RINGS_SET ============== */ +/* ETHTOOL_MSG_RINGS_SET - do */ +void ethtool_rings_set_req_free(struct ethtool_rings_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_rings_set(struct ynl_sock *ys, struct ethtool_rings_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_RINGS_SET, 1); + ys->req_policy = ðtool_rings_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_RINGS_HEADER, &req->header); + if (req->_present.rx_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX_MAX, req->rx_max); + if (req->_present.rx_mini_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX_MINI_MAX, req->rx_mini_max); + if (req->_present.rx_jumbo_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX_JUMBO_MAX, req->rx_jumbo_max); + if (req->_present.tx_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_TX_MAX, req->tx_max); + if (req->_present.rx) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX, req->rx); + if (req->_present.rx_mini) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX_MINI, req->rx_mini); + if (req->_present.rx_jumbo) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX_JUMBO, req->rx_jumbo); + if (req->_present.tx) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_TX, req->tx); + if (req->_present.rx_buf_len) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_RX_BUF_LEN, req->rx_buf_len); + if (req->_present.tcp_data_split) + mnl_attr_put_u8(nlh, ETHTOOL_A_RINGS_TCP_DATA_SPLIT, req->tcp_data_split); + if (req->_present.cqe_size) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_CQE_SIZE, req->cqe_size); + if (req->_present.tx_push) + mnl_attr_put_u8(nlh, ETHTOOL_A_RINGS_TX_PUSH, req->tx_push); + if (req->_present.rx_push) + mnl_attr_put_u8(nlh, ETHTOOL_A_RINGS_RX_PUSH, req->rx_push); + if (req->_present.tx_push_buf_len) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN, req->tx_push_buf_len); + if (req->_present.tx_push_buf_len_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX, req->tx_push_buf_len_max); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_CHANNELS_GET ============== */ +/* ETHTOOL_MSG_CHANNELS_GET - do */ +void ethtool_channels_get_req_free(struct ethtool_channels_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_channels_get_rsp_free(struct ethtool_channels_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_channels_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_channels_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_CHANNELS_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_CHANNELS_RX_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_max = 1; + dst->rx_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_TX_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_max = 1; + dst->tx_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_OTHER_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.other_max = 1; + dst->other_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_COMBINED_MAX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.combined_max = 1; + dst->combined_max = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_RX_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_count = 1; + dst->rx_count = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_TX_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_count = 1; + dst->tx_count = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_OTHER_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.other_count = 1; + dst->other_count = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_CHANNELS_COMBINED_COUNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.combined_count = 1; + dst->combined_count = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_channels_get_rsp * +ethtool_channels_get(struct ynl_sock *ys, struct ethtool_channels_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_channels_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_CHANNELS_GET, 1); + ys->req_policy = ðtool_channels_nest; + yrs.yarg.rsp_policy = ðtool_channels_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_CHANNELS_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_channels_get_rsp_parse; + yrs.rsp_cmd = 18; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_channels_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_CHANNELS_GET - dump */ +void ethtool_channels_get_list_free(struct ethtool_channels_get_list *rsp) +{ + struct ethtool_channels_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_channels_get_list * +ethtool_channels_get_dump(struct ynl_sock *ys, + struct ethtool_channels_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_channels_get_list); + yds.cb = ethtool_channels_get_rsp_parse; + yds.rsp_cmd = 18; + yds.rsp_policy = ðtool_channels_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_CHANNELS_GET, 1); + ys->req_policy = ðtool_channels_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_CHANNELS_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_channels_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_CHANNELS_GET - notify */ +void ethtool_channels_get_ntf_free(struct ethtool_channels_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ============== ETHTOOL_MSG_CHANNELS_SET ============== */ +/* ETHTOOL_MSG_CHANNELS_SET - do */ +void ethtool_channels_set_req_free(struct ethtool_channels_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_channels_set(struct ynl_sock *ys, + struct ethtool_channels_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_CHANNELS_SET, 1); + ys->req_policy = ðtool_channels_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_CHANNELS_HEADER, &req->header); + if (req->_present.rx_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_RX_MAX, req->rx_max); + if (req->_present.tx_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_TX_MAX, req->tx_max); + if (req->_present.other_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_OTHER_MAX, req->other_max); + if (req->_present.combined_max) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_COMBINED_MAX, req->combined_max); + if (req->_present.rx_count) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_RX_COUNT, req->rx_count); + if (req->_present.tx_count) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_TX_COUNT, req->tx_count); + if (req->_present.other_count) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_OTHER_COUNT, req->other_count); + if (req->_present.combined_count) + mnl_attr_put_u32(nlh, ETHTOOL_A_CHANNELS_COMBINED_COUNT, req->combined_count); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_COALESCE_GET ============== */ +/* ETHTOOL_MSG_COALESCE_GET - do */ +void ethtool_coalesce_get_req_free(struct ethtool_coalesce_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_coalesce_get_rsp_free(struct ethtool_coalesce_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_coalesce_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_coalesce_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_COALESCE_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_COALESCE_RX_USECS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_usecs = 1; + dst->rx_usecs = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_MAX_FRAMES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_max_frames = 1; + dst->rx_max_frames = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_USECS_IRQ) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_usecs_irq = 1; + dst->rx_usecs_irq = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_max_frames_irq = 1; + dst->rx_max_frames_irq = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_USECS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_usecs = 1; + dst->tx_usecs = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_MAX_FRAMES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_max_frames = 1; + dst->tx_max_frames = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_USECS_IRQ) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_usecs_irq = 1; + dst->tx_usecs_irq = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_max_frames_irq = 1; + dst->tx_max_frames_irq = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_STATS_BLOCK_USECS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.stats_block_usecs = 1; + dst->stats_block_usecs = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.use_adaptive_rx = 1; + dst->use_adaptive_rx = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.use_adaptive_tx = 1; + dst->use_adaptive_tx = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_COALESCE_PKT_RATE_LOW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.pkt_rate_low = 1; + dst->pkt_rate_low = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_USECS_LOW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_usecs_low = 1; + dst->rx_usecs_low = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_max_frames_low = 1; + dst->rx_max_frames_low = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_USECS_LOW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_usecs_low = 1; + dst->tx_usecs_low = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_max_frames_low = 1; + dst->tx_max_frames_low = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_PKT_RATE_HIGH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.pkt_rate_high = 1; + dst->pkt_rate_high = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_USECS_HIGH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_usecs_high = 1; + dst->rx_usecs_high = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_max_frames_high = 1; + dst->rx_max_frames_high = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_USECS_HIGH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_usecs_high = 1; + dst->tx_usecs_high = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_max_frames_high = 1; + dst->tx_max_frames_high = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rate_sample_interval = 1; + dst->rate_sample_interval = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_USE_CQE_MODE_TX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.use_cqe_mode_tx = 1; + dst->use_cqe_mode_tx = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_COALESCE_USE_CQE_MODE_RX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.use_cqe_mode_rx = 1; + dst->use_cqe_mode_rx = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_aggr_max_bytes = 1; + dst->tx_aggr_max_bytes = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_aggr_max_frames = 1; + dst->tx_aggr_max_frames = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_aggr_time_usecs = 1; + dst->tx_aggr_time_usecs = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_coalesce_get_rsp * +ethtool_coalesce_get(struct ynl_sock *ys, struct ethtool_coalesce_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_coalesce_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_COALESCE_GET, 1); + ys->req_policy = ðtool_coalesce_nest; + yrs.yarg.rsp_policy = ðtool_coalesce_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_COALESCE_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_coalesce_get_rsp_parse; + yrs.rsp_cmd = 20; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_coalesce_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_COALESCE_GET - dump */ +void ethtool_coalesce_get_list_free(struct ethtool_coalesce_get_list *rsp) +{ + struct ethtool_coalesce_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_coalesce_get_list * +ethtool_coalesce_get_dump(struct ynl_sock *ys, + struct ethtool_coalesce_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_coalesce_get_list); + yds.cb = ethtool_coalesce_get_rsp_parse; + yds.rsp_cmd = 20; + yds.rsp_policy = ðtool_coalesce_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_COALESCE_GET, 1); + ys->req_policy = ðtool_coalesce_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_COALESCE_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_coalesce_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_COALESCE_GET - notify */ +void ethtool_coalesce_get_ntf_free(struct ethtool_coalesce_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ============== ETHTOOL_MSG_COALESCE_SET ============== */ +/* ETHTOOL_MSG_COALESCE_SET - do */ +void ethtool_coalesce_set_req_free(struct ethtool_coalesce_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_coalesce_set(struct ynl_sock *ys, + struct ethtool_coalesce_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_COALESCE_SET, 1); + ys->req_policy = ðtool_coalesce_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_COALESCE_HEADER, &req->header); + if (req->_present.rx_usecs) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_USECS, req->rx_usecs); + if (req->_present.rx_max_frames) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_MAX_FRAMES, req->rx_max_frames); + if (req->_present.rx_usecs_irq) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_USECS_IRQ, req->rx_usecs_irq); + if (req->_present.rx_max_frames_irq) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ, req->rx_max_frames_irq); + if (req->_present.tx_usecs) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_USECS, req->tx_usecs); + if (req->_present.tx_max_frames) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_MAX_FRAMES, req->tx_max_frames); + if (req->_present.tx_usecs_irq) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_USECS_IRQ, req->tx_usecs_irq); + if (req->_present.tx_max_frames_irq) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ, req->tx_max_frames_irq); + if (req->_present.stats_block_usecs) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_STATS_BLOCK_USECS, req->stats_block_usecs); + if (req->_present.use_adaptive_rx) + mnl_attr_put_u8(nlh, ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX, req->use_adaptive_rx); + if (req->_present.use_adaptive_tx) + mnl_attr_put_u8(nlh, ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX, req->use_adaptive_tx); + if (req->_present.pkt_rate_low) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_PKT_RATE_LOW, req->pkt_rate_low); + if (req->_present.rx_usecs_low) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_USECS_LOW, req->rx_usecs_low); + if (req->_present.rx_max_frames_low) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW, req->rx_max_frames_low); + if (req->_present.tx_usecs_low) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_USECS_LOW, req->tx_usecs_low); + if (req->_present.tx_max_frames_low) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW, req->tx_max_frames_low); + if (req->_present.pkt_rate_high) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_PKT_RATE_HIGH, req->pkt_rate_high); + if (req->_present.rx_usecs_high) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_USECS_HIGH, req->rx_usecs_high); + if (req->_present.rx_max_frames_high) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH, req->rx_max_frames_high); + if (req->_present.tx_usecs_high) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_USECS_HIGH, req->tx_usecs_high); + if (req->_present.tx_max_frames_high) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH, req->tx_max_frames_high); + if (req->_present.rate_sample_interval) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL, req->rate_sample_interval); + if (req->_present.use_cqe_mode_tx) + mnl_attr_put_u8(nlh, ETHTOOL_A_COALESCE_USE_CQE_MODE_TX, req->use_cqe_mode_tx); + if (req->_present.use_cqe_mode_rx) + mnl_attr_put_u8(nlh, ETHTOOL_A_COALESCE_USE_CQE_MODE_RX, req->use_cqe_mode_rx); + if (req->_present.tx_aggr_max_bytes) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES, req->tx_aggr_max_bytes); + if (req->_present.tx_aggr_max_frames) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES, req->tx_aggr_max_frames); + if (req->_present.tx_aggr_time_usecs) + mnl_attr_put_u32(nlh, ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS, req->tx_aggr_time_usecs); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_PAUSE_GET ============== */ +/* ETHTOOL_MSG_PAUSE_GET - do */ +void ethtool_pause_get_req_free(struct ethtool_pause_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_pause_get_rsp_free(struct ethtool_pause_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_pause_stat_free(&rsp->stats); + free(rsp); +} + +int ethtool_pause_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_pause_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PAUSE_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PAUSE_AUTONEG) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.autoneg = 1; + dst->autoneg = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PAUSE_RX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx = 1; + dst->rx = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PAUSE_TX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx = 1; + dst->tx = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PAUSE_STATS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.stats = 1; + + parg.rsp_policy = ðtool_pause_stat_nest; + parg.data = &dst->stats; + if (ethtool_pause_stat_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PAUSE_STATS_SRC) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.stats_src = 1; + dst->stats_src = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_pause_get_rsp * +ethtool_pause_get(struct ynl_sock *ys, struct ethtool_pause_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_pause_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PAUSE_GET, 1); + ys->req_policy = ðtool_pause_nest; + yrs.yarg.rsp_policy = ðtool_pause_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PAUSE_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_pause_get_rsp_parse; + yrs.rsp_cmd = 22; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_pause_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_PAUSE_GET - dump */ +void ethtool_pause_get_list_free(struct ethtool_pause_get_list *rsp) +{ + struct ethtool_pause_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_pause_stat_free(&rsp->obj.stats); + free(rsp); + } +} + +struct ethtool_pause_get_list * +ethtool_pause_get_dump(struct ynl_sock *ys, + struct ethtool_pause_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_pause_get_list); + yds.cb = ethtool_pause_get_rsp_parse; + yds.rsp_cmd = 22; + yds.rsp_policy = ðtool_pause_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PAUSE_GET, 1); + ys->req_policy = ðtool_pause_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PAUSE_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_pause_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_PAUSE_GET - notify */ +void ethtool_pause_get_ntf_free(struct ethtool_pause_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_pause_stat_free(&rsp->obj.stats); + free(rsp); +} + +/* ============== ETHTOOL_MSG_PAUSE_SET ============== */ +/* ETHTOOL_MSG_PAUSE_SET - do */ +void ethtool_pause_set_req_free(struct ethtool_pause_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_pause_stat_free(&req->stats); + free(req); +} + +int ethtool_pause_set(struct ynl_sock *ys, struct ethtool_pause_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PAUSE_SET, 1); + ys->req_policy = ðtool_pause_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PAUSE_HEADER, &req->header); + if (req->_present.autoneg) + mnl_attr_put_u8(nlh, ETHTOOL_A_PAUSE_AUTONEG, req->autoneg); + if (req->_present.rx) + mnl_attr_put_u8(nlh, ETHTOOL_A_PAUSE_RX, req->rx); + if (req->_present.tx) + mnl_attr_put_u8(nlh, ETHTOOL_A_PAUSE_TX, req->tx); + if (req->_present.stats) + ethtool_pause_stat_put(nlh, ETHTOOL_A_PAUSE_STATS, &req->stats); + if (req->_present.stats_src) + mnl_attr_put_u32(nlh, ETHTOOL_A_PAUSE_STATS_SRC, req->stats_src); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_EEE_GET ============== */ +/* ETHTOOL_MSG_EEE_GET - do */ +void ethtool_eee_get_req_free(struct ethtool_eee_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_eee_get_rsp_free(struct ethtool_eee_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->modes_ours); + ethtool_bitset_free(&rsp->modes_peer); + free(rsp); +} + +int ethtool_eee_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ethtool_eee_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_EEE_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_EEE_MODES_OURS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.modes_ours = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->modes_ours; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_EEE_MODES_PEER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.modes_peer = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->modes_peer; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_EEE_ACTIVE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.active = 1; + dst->active = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_EEE_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.enabled = 1; + dst->enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_EEE_TX_LPI_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_lpi_enabled = 1; + dst->tx_lpi_enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_EEE_TX_LPI_TIMER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_lpi_timer = 1; + dst->tx_lpi_timer = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_eee_get_rsp * +ethtool_eee_get(struct ynl_sock *ys, struct ethtool_eee_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_eee_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_EEE_GET, 1); + ys->req_policy = ðtool_eee_nest; + yrs.yarg.rsp_policy = ðtool_eee_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_EEE_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_eee_get_rsp_parse; + yrs.rsp_cmd = 24; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_eee_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_EEE_GET - dump */ +void ethtool_eee_get_list_free(struct ethtool_eee_get_list *rsp) +{ + struct ethtool_eee_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.modes_ours); + ethtool_bitset_free(&rsp->obj.modes_peer); + free(rsp); + } +} + +struct ethtool_eee_get_list * +ethtool_eee_get_dump(struct ynl_sock *ys, struct ethtool_eee_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_eee_get_list); + yds.cb = ethtool_eee_get_rsp_parse; + yds.rsp_cmd = 24; + yds.rsp_policy = ðtool_eee_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_EEE_GET, 1); + ys->req_policy = ðtool_eee_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_EEE_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_eee_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_EEE_GET - notify */ +void ethtool_eee_get_ntf_free(struct ethtool_eee_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.modes_ours); + ethtool_bitset_free(&rsp->obj.modes_peer); + free(rsp); +} + +/* ============== ETHTOOL_MSG_EEE_SET ============== */ +/* ETHTOOL_MSG_EEE_SET - do */ +void ethtool_eee_set_req_free(struct ethtool_eee_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->modes_ours); + ethtool_bitset_free(&req->modes_peer); + free(req); +} + +int ethtool_eee_set(struct ynl_sock *ys, struct ethtool_eee_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_EEE_SET, 1); + ys->req_policy = ðtool_eee_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_EEE_HEADER, &req->header); + if (req->_present.modes_ours) + ethtool_bitset_put(nlh, ETHTOOL_A_EEE_MODES_OURS, &req->modes_ours); + if (req->_present.modes_peer) + ethtool_bitset_put(nlh, ETHTOOL_A_EEE_MODES_PEER, &req->modes_peer); + if (req->_present.active) + mnl_attr_put_u8(nlh, ETHTOOL_A_EEE_ACTIVE, req->active); + if (req->_present.enabled) + mnl_attr_put_u8(nlh, ETHTOOL_A_EEE_ENABLED, req->enabled); + if (req->_present.tx_lpi_enabled) + mnl_attr_put_u8(nlh, ETHTOOL_A_EEE_TX_LPI_ENABLED, req->tx_lpi_enabled); + if (req->_present.tx_lpi_timer) + mnl_attr_put_u32(nlh, ETHTOOL_A_EEE_TX_LPI_TIMER, req->tx_lpi_timer); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_TSINFO_GET ============== */ +/* ETHTOOL_MSG_TSINFO_GET - do */ +void ethtool_tsinfo_get_req_free(struct ethtool_tsinfo_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_tsinfo_get_rsp_free(struct ethtool_tsinfo_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->timestamping); + ethtool_bitset_free(&rsp->tx_types); + ethtool_bitset_free(&rsp->rx_filters); + free(rsp); +} + +int ethtool_tsinfo_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_tsinfo_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_TSINFO_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_TSINFO_TIMESTAMPING) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.timestamping = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->timestamping; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_TSINFO_TX_TYPES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_types = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->tx_types; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_TSINFO_RX_FILTERS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_filters = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->rx_filters; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_TSINFO_PHC_INDEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.phc_index = 1; + dst->phc_index = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_tsinfo_get_rsp * +ethtool_tsinfo_get(struct ynl_sock *ys, struct ethtool_tsinfo_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_tsinfo_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_TSINFO_GET, 1); + ys->req_policy = ðtool_tsinfo_nest; + yrs.yarg.rsp_policy = ðtool_tsinfo_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_TSINFO_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_tsinfo_get_rsp_parse; + yrs.rsp_cmd = 26; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_tsinfo_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_TSINFO_GET - dump */ +void ethtool_tsinfo_get_list_free(struct ethtool_tsinfo_get_list *rsp) +{ + struct ethtool_tsinfo_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.timestamping); + ethtool_bitset_free(&rsp->obj.tx_types); + ethtool_bitset_free(&rsp->obj.rx_filters); + free(rsp); + } +} + +struct ethtool_tsinfo_get_list * +ethtool_tsinfo_get_dump(struct ynl_sock *ys, + struct ethtool_tsinfo_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_tsinfo_get_list); + yds.cb = ethtool_tsinfo_get_rsp_parse; + yds.rsp_cmd = 26; + yds.rsp_policy = ðtool_tsinfo_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_TSINFO_GET, 1); + ys->req_policy = ðtool_tsinfo_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_TSINFO_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_tsinfo_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_CABLE_TEST_ACT ============== */ +/* ETHTOOL_MSG_CABLE_TEST_ACT - do */ +void ethtool_cable_test_act_req_free(struct ethtool_cable_test_act_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_cable_test_act(struct ynl_sock *ys, + struct ethtool_cable_test_act_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_CABLE_TEST_ACT, 1); + ys->req_policy = ðtool_cable_test_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_CABLE_TEST_HEADER, &req->header); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_CABLE_TEST_TDR_ACT ============== */ +/* ETHTOOL_MSG_CABLE_TEST_TDR_ACT - do */ +void +ethtool_cable_test_tdr_act_req_free(struct ethtool_cable_test_tdr_act_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_cable_test_tdr_act(struct ynl_sock *ys, + struct ethtool_cable_test_tdr_act_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_CABLE_TEST_TDR_ACT, 1); + ys->req_policy = ðtool_cable_test_tdr_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_CABLE_TEST_TDR_HEADER, &req->header); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_TUNNEL_INFO_GET ============== */ +/* ETHTOOL_MSG_TUNNEL_INFO_GET - do */ +void ethtool_tunnel_info_get_req_free(struct ethtool_tunnel_info_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_tunnel_info_get_rsp_free(struct ethtool_tunnel_info_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_tunnel_udp_free(&rsp->udp_ports); + free(rsp); +} + +int ethtool_tunnel_info_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_tunnel_info_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_TUNNEL_INFO_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_TUNNEL_INFO_UDP_PORTS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.udp_ports = 1; + + parg.rsp_policy = ðtool_tunnel_udp_nest; + parg.data = &dst->udp_ports; + if (ethtool_tunnel_udp_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_tunnel_info_get_rsp * +ethtool_tunnel_info_get(struct ynl_sock *ys, + struct ethtool_tunnel_info_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_tunnel_info_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_TUNNEL_INFO_GET, 1); + ys->req_policy = ðtool_tunnel_info_nest; + yrs.yarg.rsp_policy = ðtool_tunnel_info_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_TUNNEL_INFO_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_tunnel_info_get_rsp_parse; + yrs.rsp_cmd = 29; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_tunnel_info_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_TUNNEL_INFO_GET - dump */ +void +ethtool_tunnel_info_get_list_free(struct ethtool_tunnel_info_get_list *rsp) +{ + struct ethtool_tunnel_info_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_tunnel_udp_free(&rsp->obj.udp_ports); + free(rsp); + } +} + +struct ethtool_tunnel_info_get_list * +ethtool_tunnel_info_get_dump(struct ynl_sock *ys, + struct ethtool_tunnel_info_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_tunnel_info_get_list); + yds.cb = ethtool_tunnel_info_get_rsp_parse; + yds.rsp_cmd = 29; + yds.rsp_policy = ðtool_tunnel_info_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_TUNNEL_INFO_GET, 1); + ys->req_policy = ðtool_tunnel_info_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_TUNNEL_INFO_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_tunnel_info_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_FEC_GET ============== */ +/* ETHTOOL_MSG_FEC_GET - do */ +void ethtool_fec_get_req_free(struct ethtool_fec_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_fec_get_rsp_free(struct ethtool_fec_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_bitset_free(&rsp->modes); + ethtool_fec_stat_free(&rsp->stats); + free(rsp); +} + +int ethtool_fec_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ethtool_fec_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_FEC_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEC_MODES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.modes = 1; + + parg.rsp_policy = ðtool_bitset_nest; + parg.data = &dst->modes; + if (ethtool_bitset_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_FEC_AUTO) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.auto_ = 1; + dst->auto_ = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_FEC_ACTIVE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.active = 1; + dst->active = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_FEC_STATS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.stats = 1; + + parg.rsp_policy = ðtool_fec_stat_nest; + parg.data = &dst->stats; + if (ethtool_fec_stat_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_fec_get_rsp * +ethtool_fec_get(struct ynl_sock *ys, struct ethtool_fec_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_fec_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_FEC_GET, 1); + ys->req_policy = ðtool_fec_nest; + yrs.yarg.rsp_policy = ðtool_fec_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_FEC_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_fec_get_rsp_parse; + yrs.rsp_cmd = 30; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_fec_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_FEC_GET - dump */ +void ethtool_fec_get_list_free(struct ethtool_fec_get_list *rsp) +{ + struct ethtool_fec_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.modes); + ethtool_fec_stat_free(&rsp->obj.stats); + free(rsp); + } +} + +struct ethtool_fec_get_list * +ethtool_fec_get_dump(struct ynl_sock *ys, struct ethtool_fec_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_fec_get_list); + yds.cb = ethtool_fec_get_rsp_parse; + yds.rsp_cmd = 30; + yds.rsp_policy = ðtool_fec_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_FEC_GET, 1); + ys->req_policy = ðtool_fec_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_FEC_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_fec_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_FEC_GET - notify */ +void ethtool_fec_get_ntf_free(struct ethtool_fec_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_bitset_free(&rsp->obj.modes); + ethtool_fec_stat_free(&rsp->obj.stats); + free(rsp); +} + +/* ============== ETHTOOL_MSG_FEC_SET ============== */ +/* ETHTOOL_MSG_FEC_SET - do */ +void ethtool_fec_set_req_free(struct ethtool_fec_set_req *req) +{ + ethtool_header_free(&req->header); + ethtool_bitset_free(&req->modes); + ethtool_fec_stat_free(&req->stats); + free(req); +} + +int ethtool_fec_set(struct ynl_sock *ys, struct ethtool_fec_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_FEC_SET, 1); + ys->req_policy = ðtool_fec_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_FEC_HEADER, &req->header); + if (req->_present.modes) + ethtool_bitset_put(nlh, ETHTOOL_A_FEC_MODES, &req->modes); + if (req->_present.auto_) + mnl_attr_put_u8(nlh, ETHTOOL_A_FEC_AUTO, req->auto_); + if (req->_present.active) + mnl_attr_put_u32(nlh, ETHTOOL_A_FEC_ACTIVE, req->active); + if (req->_present.stats) + ethtool_fec_stat_put(nlh, ETHTOOL_A_FEC_STATS, &req->stats); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_MODULE_EEPROM_GET ============== */ +/* ETHTOOL_MSG_MODULE_EEPROM_GET - do */ +void +ethtool_module_eeprom_get_req_free(struct ethtool_module_eeprom_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void +ethtool_module_eeprom_get_rsp_free(struct ethtool_module_eeprom_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp->data); + free(rsp); +} + +int ethtool_module_eeprom_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_module_eeprom_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_MODULE_EEPROM_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_MODULE_EEPROM_OFFSET) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.offset = 1; + dst->offset = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_MODULE_EEPROM_LENGTH) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.length = 1; + dst->length = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_MODULE_EEPROM_PAGE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.page = 1; + dst->page = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MODULE_EEPROM_BANK) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.bank = 1; + dst->bank = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.i2c_address = 1; + dst->i2c_address = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MODULE_EEPROM_DATA) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.data_len = len; + dst->data = malloc(len); + memcpy(dst->data, mnl_attr_get_payload(attr), len); + } + } + + return MNL_CB_OK; +} + +struct ethtool_module_eeprom_get_rsp * +ethtool_module_eeprom_get(struct ynl_sock *ys, + struct ethtool_module_eeprom_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_module_eeprom_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_MODULE_EEPROM_GET, 1); + ys->req_policy = ðtool_module_eeprom_nest; + yrs.yarg.rsp_policy = ðtool_module_eeprom_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MODULE_EEPROM_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_module_eeprom_get_rsp_parse; + yrs.rsp_cmd = 32; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_module_eeprom_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_MODULE_EEPROM_GET - dump */ +void +ethtool_module_eeprom_get_list_free(struct ethtool_module_eeprom_get_list *rsp) +{ + struct ethtool_module_eeprom_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp->obj.data); + free(rsp); + } +} + +struct ethtool_module_eeprom_get_list * +ethtool_module_eeprom_get_dump(struct ynl_sock *ys, + struct ethtool_module_eeprom_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_module_eeprom_get_list); + yds.cb = ethtool_module_eeprom_get_rsp_parse; + yds.rsp_cmd = 32; + yds.rsp_policy = ðtool_module_eeprom_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_MODULE_EEPROM_GET, 1); + ys->req_policy = ðtool_module_eeprom_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MODULE_EEPROM_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_module_eeprom_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_PHC_VCLOCKS_GET ============== */ +/* ETHTOOL_MSG_PHC_VCLOCKS_GET - do */ +void ethtool_phc_vclocks_get_req_free(struct ethtool_phc_vclocks_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_phc_vclocks_get_rsp_free(struct ethtool_phc_vclocks_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_phc_vclocks_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_phc_vclocks_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PHC_VCLOCKS_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PHC_VCLOCKS_NUM) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.num = 1; + dst->num = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_phc_vclocks_get_rsp * +ethtool_phc_vclocks_get(struct ynl_sock *ys, + struct ethtool_phc_vclocks_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_phc_vclocks_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PHC_VCLOCKS_GET, 1); + ys->req_policy = ðtool_phc_vclocks_nest; + yrs.yarg.rsp_policy = ðtool_phc_vclocks_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PHC_VCLOCKS_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_phc_vclocks_get_rsp_parse; + yrs.rsp_cmd = 34; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_phc_vclocks_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_PHC_VCLOCKS_GET - dump */ +void +ethtool_phc_vclocks_get_list_free(struct ethtool_phc_vclocks_get_list *rsp) +{ + struct ethtool_phc_vclocks_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_phc_vclocks_get_list * +ethtool_phc_vclocks_get_dump(struct ynl_sock *ys, + struct ethtool_phc_vclocks_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_phc_vclocks_get_list); + yds.cb = ethtool_phc_vclocks_get_rsp_parse; + yds.rsp_cmd = 34; + yds.rsp_policy = ðtool_phc_vclocks_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PHC_VCLOCKS_GET, 1); + ys->req_policy = ðtool_phc_vclocks_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PHC_VCLOCKS_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_phc_vclocks_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_MODULE_GET ============== */ +/* ETHTOOL_MSG_MODULE_GET - do */ +void ethtool_module_get_req_free(struct ethtool_module_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_module_get_rsp_free(struct ethtool_module_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_module_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_module_get_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_MODULE_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_MODULE_POWER_MODE_POLICY) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.power_mode_policy = 1; + dst->power_mode_policy = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MODULE_POWER_MODE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.power_mode = 1; + dst->power_mode = mnl_attr_get_u8(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_module_get_rsp * +ethtool_module_get(struct ynl_sock *ys, struct ethtool_module_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_module_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_MODULE_GET, 1); + ys->req_policy = ðtool_module_nest; + yrs.yarg.rsp_policy = ðtool_module_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MODULE_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_module_get_rsp_parse; + yrs.rsp_cmd = 35; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_module_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_MODULE_GET - dump */ +void ethtool_module_get_list_free(struct ethtool_module_get_list *rsp) +{ + struct ethtool_module_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_module_get_list * +ethtool_module_get_dump(struct ynl_sock *ys, + struct ethtool_module_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_module_get_list); + yds.cb = ethtool_module_get_rsp_parse; + yds.rsp_cmd = 35; + yds.rsp_policy = ðtool_module_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_MODULE_GET, 1); + ys->req_policy = ðtool_module_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MODULE_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_module_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_MODULE_GET - notify */ +void ethtool_module_get_ntf_free(struct ethtool_module_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ============== ETHTOOL_MSG_MODULE_SET ============== */ +/* ETHTOOL_MSG_MODULE_SET - do */ +void ethtool_module_set_req_free(struct ethtool_module_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_module_set(struct ynl_sock *ys, struct ethtool_module_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_MODULE_SET, 1); + ys->req_policy = ðtool_module_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MODULE_HEADER, &req->header); + if (req->_present.power_mode_policy) + mnl_attr_put_u8(nlh, ETHTOOL_A_MODULE_POWER_MODE_POLICY, req->power_mode_policy); + if (req->_present.power_mode) + mnl_attr_put_u8(nlh, ETHTOOL_A_MODULE_POWER_MODE, req->power_mode); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_PSE_GET ============== */ +/* ETHTOOL_MSG_PSE_GET - do */ +void ethtool_pse_get_req_free(struct ethtool_pse_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_pse_get_rsp_free(struct ethtool_pse_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_pse_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ethtool_pse_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PSE_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PODL_PSE_ADMIN_STATE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.admin_state = 1; + dst->admin_state = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PODL_PSE_ADMIN_CONTROL) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.admin_control = 1; + dst->admin_control = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PODL_PSE_PW_D_STATUS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.pw_d_status = 1; + dst->pw_d_status = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_pse_get_rsp * +ethtool_pse_get(struct ynl_sock *ys, struct ethtool_pse_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_pse_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PSE_GET, 1); + ys->req_policy = ðtool_pse_nest; + yrs.yarg.rsp_policy = ðtool_pse_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PSE_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_pse_get_rsp_parse; + yrs.rsp_cmd = 37; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_pse_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_PSE_GET - dump */ +void ethtool_pse_get_list_free(struct ethtool_pse_get_list *rsp) +{ + struct ethtool_pse_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_pse_get_list * +ethtool_pse_get_dump(struct ynl_sock *ys, struct ethtool_pse_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_pse_get_list); + yds.cb = ethtool_pse_get_rsp_parse; + yds.rsp_cmd = 37; + yds.rsp_policy = ðtool_pse_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PSE_GET, 1); + ys->req_policy = ðtool_pse_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PSE_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_pse_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_PSE_SET ============== */ +/* ETHTOOL_MSG_PSE_SET - do */ +void ethtool_pse_set_req_free(struct ethtool_pse_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_pse_set(struct ynl_sock *ys, struct ethtool_pse_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PSE_SET, 1); + ys->req_policy = ðtool_pse_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PSE_HEADER, &req->header); + if (req->_present.admin_state) + mnl_attr_put_u32(nlh, ETHTOOL_A_PODL_PSE_ADMIN_STATE, req->admin_state); + if (req->_present.admin_control) + mnl_attr_put_u32(nlh, ETHTOOL_A_PODL_PSE_ADMIN_CONTROL, req->admin_control); + if (req->_present.pw_d_status) + mnl_attr_put_u32(nlh, ETHTOOL_A_PODL_PSE_PW_D_STATUS, req->pw_d_status); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_RSS_GET ============== */ +/* ETHTOOL_MSG_RSS_GET - do */ +void ethtool_rss_get_req_free(struct ethtool_rss_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_rss_get_rsp_free(struct ethtool_rss_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp->indir); + free(rsp->hkey); + free(rsp); +} + +int ethtool_rss_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ethtool_rss_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_RSS_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_RSS_CONTEXT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.context = 1; + dst->context = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RSS_HFUNC) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.hfunc = 1; + dst->hfunc = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_RSS_INDIR) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.indir_len = len; + dst->indir = malloc(len); + memcpy(dst->indir, mnl_attr_get_payload(attr), len); + } else if (type == ETHTOOL_A_RSS_HKEY) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.hkey_len = len; + dst->hkey = malloc(len); + memcpy(dst->hkey, mnl_attr_get_payload(attr), len); + } + } + + return MNL_CB_OK; +} + +struct ethtool_rss_get_rsp * +ethtool_rss_get(struct ynl_sock *ys, struct ethtool_rss_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_rss_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_RSS_GET, 1); + ys->req_policy = ðtool_rss_nest; + yrs.yarg.rsp_policy = ðtool_rss_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_RSS_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_rss_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_RSS_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_rss_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_RSS_GET - dump */ +void ethtool_rss_get_list_free(struct ethtool_rss_get_list *rsp) +{ + struct ethtool_rss_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp->obj.indir); + free(rsp->obj.hkey); + free(rsp); + } +} + +struct ethtool_rss_get_list * +ethtool_rss_get_dump(struct ynl_sock *ys, struct ethtool_rss_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_rss_get_list); + yds.cb = ethtool_rss_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_RSS_GET; + yds.rsp_policy = ðtool_rss_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_RSS_GET, 1); + ys->req_policy = ðtool_rss_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_RSS_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_rss_get_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_PLCA_GET_CFG ============== */ +/* ETHTOOL_MSG_PLCA_GET_CFG - do */ +void ethtool_plca_get_cfg_req_free(struct ethtool_plca_get_cfg_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_plca_get_cfg_rsp_free(struct ethtool_plca_get_cfg_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_plca_get_cfg_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_plca_get_cfg_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PLCA_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PLCA_VERSION) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.version = 1; + dst->version = mnl_attr_get_u16(attr); + } else if (type == ETHTOOL_A_PLCA_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.enabled = 1; + dst->enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PLCA_STATUS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.status = 1; + dst->status = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PLCA_NODE_CNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.node_cnt = 1; + dst->node_cnt = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_NODE_ID) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.node_id = 1; + dst->node_id = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_TO_TMR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.to_tmr = 1; + dst->to_tmr = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_BURST_CNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.burst_cnt = 1; + dst->burst_cnt = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_BURST_TMR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.burst_tmr = 1; + dst->burst_tmr = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_plca_get_cfg_rsp * +ethtool_plca_get_cfg(struct ynl_sock *ys, struct ethtool_plca_get_cfg_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_plca_get_cfg_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PLCA_GET_CFG, 1); + ys->req_policy = ðtool_plca_nest; + yrs.yarg.rsp_policy = ðtool_plca_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PLCA_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_plca_get_cfg_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_PLCA_GET_CFG; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_plca_get_cfg_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_PLCA_GET_CFG - dump */ +void ethtool_plca_get_cfg_list_free(struct ethtool_plca_get_cfg_list *rsp) +{ + struct ethtool_plca_get_cfg_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_plca_get_cfg_list * +ethtool_plca_get_cfg_dump(struct ynl_sock *ys, + struct ethtool_plca_get_cfg_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_plca_get_cfg_list); + yds.cb = ethtool_plca_get_cfg_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_PLCA_GET_CFG; + yds.rsp_policy = ðtool_plca_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PLCA_GET_CFG, 1); + ys->req_policy = ðtool_plca_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PLCA_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_plca_get_cfg_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_PLCA_GET_CFG - notify */ +void ethtool_plca_get_cfg_ntf_free(struct ethtool_plca_get_cfg_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ============== ETHTOOL_MSG_PLCA_SET_CFG ============== */ +/* ETHTOOL_MSG_PLCA_SET_CFG - do */ +void ethtool_plca_set_cfg_req_free(struct ethtool_plca_set_cfg_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_plca_set_cfg(struct ynl_sock *ys, + struct ethtool_plca_set_cfg_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PLCA_SET_CFG, 1); + ys->req_policy = ðtool_plca_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PLCA_HEADER, &req->header); + if (req->_present.version) + mnl_attr_put_u16(nlh, ETHTOOL_A_PLCA_VERSION, req->version); + if (req->_present.enabled) + mnl_attr_put_u8(nlh, ETHTOOL_A_PLCA_ENABLED, req->enabled); + if (req->_present.status) + mnl_attr_put_u8(nlh, ETHTOOL_A_PLCA_STATUS, req->status); + if (req->_present.node_cnt) + mnl_attr_put_u32(nlh, ETHTOOL_A_PLCA_NODE_CNT, req->node_cnt); + if (req->_present.node_id) + mnl_attr_put_u32(nlh, ETHTOOL_A_PLCA_NODE_ID, req->node_id); + if (req->_present.to_tmr) + mnl_attr_put_u32(nlh, ETHTOOL_A_PLCA_TO_TMR, req->to_tmr); + if (req->_present.burst_cnt) + mnl_attr_put_u32(nlh, ETHTOOL_A_PLCA_BURST_CNT, req->burst_cnt); + if (req->_present.burst_tmr) + mnl_attr_put_u32(nlh, ETHTOOL_A_PLCA_BURST_TMR, req->burst_tmr); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== ETHTOOL_MSG_PLCA_GET_STATUS ============== */ +/* ETHTOOL_MSG_PLCA_GET_STATUS - do */ +void ethtool_plca_get_status_req_free(struct ethtool_plca_get_status_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_plca_get_status_rsp_free(struct ethtool_plca_get_status_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + free(rsp); +} + +int ethtool_plca_get_status_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_plca_get_status_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_PLCA_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_PLCA_VERSION) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.version = 1; + dst->version = mnl_attr_get_u16(attr); + } else if (type == ETHTOOL_A_PLCA_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.enabled = 1; + dst->enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PLCA_STATUS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.status = 1; + dst->status = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_PLCA_NODE_CNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.node_cnt = 1; + dst->node_cnt = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_NODE_ID) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.node_id = 1; + dst->node_id = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_TO_TMR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.to_tmr = 1; + dst->to_tmr = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_BURST_CNT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.burst_cnt = 1; + dst->burst_cnt = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_PLCA_BURST_TMR) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.burst_tmr = 1; + dst->burst_tmr = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct ethtool_plca_get_status_rsp * +ethtool_plca_get_status(struct ynl_sock *ys, + struct ethtool_plca_get_status_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_plca_get_status_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PLCA_GET_STATUS, 1); + ys->req_policy = ðtool_plca_nest; + yrs.yarg.rsp_policy = ðtool_plca_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PLCA_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_plca_get_status_rsp_parse; + yrs.rsp_cmd = 40; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_plca_get_status_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_PLCA_GET_STATUS - dump */ +void +ethtool_plca_get_status_list_free(struct ethtool_plca_get_status_list *rsp) +{ + struct ethtool_plca_get_status_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + free(rsp); + } +} + +struct ethtool_plca_get_status_list * +ethtool_plca_get_status_dump(struct ynl_sock *ys, + struct ethtool_plca_get_status_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_plca_get_status_list); + yds.cb = ethtool_plca_get_status_rsp_parse; + yds.rsp_cmd = 40; + yds.rsp_policy = ðtool_plca_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PLCA_GET_STATUS, 1); + ys->req_policy = ðtool_plca_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_PLCA_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_plca_get_status_list_free(yds.first); + return NULL; +} + +/* ============== ETHTOOL_MSG_MM_GET ============== */ +/* ETHTOOL_MSG_MM_GET - do */ +void ethtool_mm_get_req_free(struct ethtool_mm_get_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +void ethtool_mm_get_rsp_free(struct ethtool_mm_get_rsp *rsp) +{ + ethtool_header_free(&rsp->header); + ethtool_mm_stat_free(&rsp->stats); + free(rsp); +} + +int ethtool_mm_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ethtool_mm_get_rsp *dst; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_MM_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_MM_PMAC_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.pmac_enabled = 1; + dst->pmac_enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MM_TX_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_enabled = 1; + dst->tx_enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MM_TX_ACTIVE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_active = 1; + dst->tx_active = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MM_TX_MIN_FRAG_SIZE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.tx_min_frag_size = 1; + dst->tx_min_frag_size = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_MM_RX_MIN_FRAG_SIZE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.rx_min_frag_size = 1; + dst->rx_min_frag_size = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_MM_VERIFY_ENABLED) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.verify_enabled = 1; + dst->verify_enabled = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_MM_VERIFY_TIME) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.verify_time = 1; + dst->verify_time = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_MM_MAX_VERIFY_TIME) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.max_verify_time = 1; + dst->max_verify_time = mnl_attr_get_u32(attr); + } else if (type == ETHTOOL_A_MM_STATS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.stats = 1; + + parg.rsp_policy = ðtool_mm_stat_nest; + parg.data = &dst->stats; + if (ethtool_mm_stat_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +struct ethtool_mm_get_rsp * +ethtool_mm_get(struct ynl_sock *ys, struct ethtool_mm_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct ethtool_mm_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_MM_GET, 1); + ys->req_policy = ðtool_mm_nest; + yrs.yarg.rsp_policy = ðtool_mm_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MM_HEADER, &req->header); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = ethtool_mm_get_rsp_parse; + yrs.rsp_cmd = ETHTOOL_MSG_MM_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + ethtool_mm_get_rsp_free(rsp); + return NULL; +} + +/* ETHTOOL_MSG_MM_GET - dump */ +void ethtool_mm_get_list_free(struct ethtool_mm_get_list *rsp) +{ + struct ethtool_mm_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + ethtool_header_free(&rsp->obj.header); + ethtool_mm_stat_free(&rsp->obj.stats); + free(rsp); + } +} + +struct ethtool_mm_get_list * +ethtool_mm_get_dump(struct ynl_sock *ys, struct ethtool_mm_get_req_dump *req) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct ethtool_mm_get_list); + yds.cb = ethtool_mm_get_rsp_parse; + yds.rsp_cmd = ETHTOOL_MSG_MM_GET; + yds.rsp_policy = ðtool_mm_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_MM_GET, 1); + ys->req_policy = ðtool_mm_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MM_HEADER, &req->header); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + ethtool_mm_get_list_free(yds.first); + return NULL; +} + +/* ETHTOOL_MSG_MM_GET - notify */ +void ethtool_mm_get_ntf_free(struct ethtool_mm_get_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_mm_stat_free(&rsp->obj.stats); + free(rsp); +} + +/* ============== ETHTOOL_MSG_MM_SET ============== */ +/* ETHTOOL_MSG_MM_SET - do */ +void ethtool_mm_set_req_free(struct ethtool_mm_set_req *req) +{ + ethtool_header_free(&req->header); + free(req); +} + +int ethtool_mm_set(struct ynl_sock *ys, struct ethtool_mm_set_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_MM_SET, 1); + ys->req_policy = ðtool_mm_nest; + + if (req->_present.header) + ethtool_header_put(nlh, ETHTOOL_A_MM_HEADER, &req->header); + if (req->_present.verify_enabled) + mnl_attr_put_u8(nlh, ETHTOOL_A_MM_VERIFY_ENABLED, req->verify_enabled); + if (req->_present.verify_time) + mnl_attr_put_u32(nlh, ETHTOOL_A_MM_VERIFY_TIME, req->verify_time); + if (req->_present.tx_enabled) + mnl_attr_put_u8(nlh, ETHTOOL_A_MM_TX_ENABLED, req->tx_enabled); + if (req->_present.pmac_enabled) + mnl_attr_put_u8(nlh, ETHTOOL_A_MM_PMAC_ENABLED, req->pmac_enabled); + if (req->_present.tx_min_frag_size) + mnl_attr_put_u32(nlh, ETHTOOL_A_MM_TX_MIN_FRAG_SIZE, req->tx_min_frag_size); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ETHTOOL_MSG_CABLE_TEST_NTF - event */ +int ethtool_cable_test_ntf_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ethtool_cable_test_ntf_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_CABLE_TEST_NTF_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_CABLE_TEST_NTF_STATUS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.status = 1; + dst->status = mnl_attr_get_u8(attr); + } + } + + return MNL_CB_OK; +} + +void ethtool_cable_test_ntf_free(struct ethtool_cable_test_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + free(rsp); +} + +/* ETHTOOL_MSG_CABLE_TEST_TDR_NTF - event */ +int ethtool_cable_test_tdr_ntf_rsp_parse(const struct nlmsghdr *nlh, + void *data) +{ + struct ethtool_cable_test_tdr_ntf_rsp *dst; + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct ynl_parse_arg parg; + + dst = yarg->data; + parg.ys = yarg->ys; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == ETHTOOL_A_CABLE_TEST_TDR_NTF_HEADER) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.header = 1; + + parg.rsp_policy = ðtool_header_nest; + parg.data = &dst->header; + if (ethtool_header_parse(&parg, attr)) + return MNL_CB_ERROR; + } else if (type == ETHTOOL_A_CABLE_TEST_TDR_NTF_STATUS) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.status = 1; + dst->status = mnl_attr_get_u8(attr); + } else if (type == ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.nest = 1; + + parg.rsp_policy = ðtool_cable_nest_nest; + parg.data = &dst->nest; + if (ethtool_cable_nest_parse(&parg, attr)) + return MNL_CB_ERROR; + } + } + + return MNL_CB_OK; +} + +void ethtool_cable_test_tdr_ntf_free(struct ethtool_cable_test_tdr_ntf *rsp) +{ + ethtool_header_free(&rsp->obj.header); + ethtool_cable_nest_free(&rsp->obj.nest); + free(rsp); +} + +static const struct ynl_ntf_info ethtool_ntf_info[] = { + [ETHTOOL_MSG_LINKINFO_NTF] = { + .alloc_sz = sizeof(struct ethtool_linkinfo_get_ntf), + .cb = ethtool_linkinfo_get_rsp_parse, + .policy = ðtool_linkinfo_nest, + .free = (void *)ethtool_linkinfo_get_ntf_free, + }, + [ETHTOOL_MSG_LINKMODES_NTF] = { + .alloc_sz = sizeof(struct ethtool_linkmodes_get_ntf), + .cb = ethtool_linkmodes_get_rsp_parse, + .policy = ðtool_linkmodes_nest, + .free = (void *)ethtool_linkmodes_get_ntf_free, + }, + [ETHTOOL_MSG_DEBUG_NTF] = { + .alloc_sz = sizeof(struct ethtool_debug_get_ntf), + .cb = ethtool_debug_get_rsp_parse, + .policy = ðtool_debug_nest, + .free = (void *)ethtool_debug_get_ntf_free, + }, + [ETHTOOL_MSG_WOL_NTF] = { + .alloc_sz = sizeof(struct ethtool_wol_get_ntf), + .cb = ethtool_wol_get_rsp_parse, + .policy = ðtool_wol_nest, + .free = (void *)ethtool_wol_get_ntf_free, + }, + [ETHTOOL_MSG_FEATURES_NTF] = { + .alloc_sz = sizeof(struct ethtool_features_get_ntf), + .cb = ethtool_features_get_rsp_parse, + .policy = ðtool_features_nest, + .free = (void *)ethtool_features_get_ntf_free, + }, + [ETHTOOL_MSG_PRIVFLAGS_NTF] = { + .alloc_sz = sizeof(struct ethtool_privflags_get_ntf), + .cb = ethtool_privflags_get_rsp_parse, + .policy = ðtool_privflags_nest, + .free = (void *)ethtool_privflags_get_ntf_free, + }, + [ETHTOOL_MSG_RINGS_NTF] = { + .alloc_sz = sizeof(struct ethtool_rings_get_ntf), + .cb = ethtool_rings_get_rsp_parse, + .policy = ðtool_rings_nest, + .free = (void *)ethtool_rings_get_ntf_free, + }, + [ETHTOOL_MSG_CHANNELS_NTF] = { + .alloc_sz = sizeof(struct ethtool_channels_get_ntf), + .cb = ethtool_channels_get_rsp_parse, + .policy = ðtool_channels_nest, + .free = (void *)ethtool_channels_get_ntf_free, + }, + [ETHTOOL_MSG_COALESCE_NTF] = { + .alloc_sz = sizeof(struct ethtool_coalesce_get_ntf), + .cb = ethtool_coalesce_get_rsp_parse, + .policy = ðtool_coalesce_nest, + .free = (void *)ethtool_coalesce_get_ntf_free, + }, + [ETHTOOL_MSG_PAUSE_NTF] = { + .alloc_sz = sizeof(struct ethtool_pause_get_ntf), + .cb = ethtool_pause_get_rsp_parse, + .policy = ðtool_pause_nest, + .free = (void *)ethtool_pause_get_ntf_free, + }, + [ETHTOOL_MSG_EEE_NTF] = { + .alloc_sz = sizeof(struct ethtool_eee_get_ntf), + .cb = ethtool_eee_get_rsp_parse, + .policy = ðtool_eee_nest, + .free = (void *)ethtool_eee_get_ntf_free, + }, + [ETHTOOL_MSG_CABLE_TEST_NTF] = { + .alloc_sz = sizeof(struct ethtool_cable_test_ntf), + .cb = ethtool_cable_test_ntf_rsp_parse, + .policy = ðtool_cable_test_ntf_nest, + .free = (void *)ethtool_cable_test_ntf_free, + }, + [ETHTOOL_MSG_CABLE_TEST_TDR_NTF] = { + .alloc_sz = sizeof(struct ethtool_cable_test_tdr_ntf), + .cb = ethtool_cable_test_tdr_ntf_rsp_parse, + .policy = ðtool_cable_test_tdr_ntf_nest, + .free = (void *)ethtool_cable_test_tdr_ntf_free, + }, + [ETHTOOL_MSG_FEC_NTF] = { + .alloc_sz = sizeof(struct ethtool_fec_get_ntf), + .cb = ethtool_fec_get_rsp_parse, + .policy = ðtool_fec_nest, + .free = (void *)ethtool_fec_get_ntf_free, + }, + [ETHTOOL_MSG_MODULE_NTF] = { + .alloc_sz = sizeof(struct ethtool_module_get_ntf), + .cb = ethtool_module_get_rsp_parse, + .policy = ðtool_module_nest, + .free = (void *)ethtool_module_get_ntf_free, + }, + [ETHTOOL_MSG_PLCA_NTF] = { + .alloc_sz = sizeof(struct ethtool_plca_get_cfg_ntf), + .cb = ethtool_plca_get_cfg_rsp_parse, + .policy = ðtool_plca_nest, + .free = (void *)ethtool_plca_get_cfg_ntf_free, + }, + [ETHTOOL_MSG_MM_NTF] = { + .alloc_sz = sizeof(struct ethtool_mm_get_ntf), + .cb = ethtool_mm_get_rsp_parse, + .policy = ðtool_mm_nest, + .free = (void *)ethtool_mm_get_ntf_free, + }, +}; + +const struct ynl_family ynl_ethtool_family = { + .name = "ethtool", + .ntf_info = ethtool_ntf_info, + .ntf_info_size = MNL_ARRAY_SIZE(ethtool_ntf_info), +}; diff --git a/tools/net/ynl/generated/ethtool-user.h b/tools/net/ynl/generated/ethtool-user.h new file mode 100644 index 000000000000..d7d4ba855f43 --- /dev/null +++ b/tools/net/ynl/generated/ethtool-user.h @@ -0,0 +1,5531 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/ethtool.yaml */ +/* YNL-GEN user header */ +/* YNL-ARG --user-header linux/ethtool_netlink.h --exclude-op stats-get */ + +#ifndef _LINUX_ETHTOOL_GEN_H +#define _LINUX_ETHTOOL_GEN_H + +#include <stdlib.h> +#include <string.h> +#include <linux/types.h> +#include <linux/ethtool.h> + +struct ynl_sock; + +extern const struct ynl_family ynl_ethtool_family; + +/* Enums */ +const char *ethtool_op_str(int op); +const char *ethtool_udp_tunnel_type_str(int value); +const char *ethtool_stringset_str(enum ethtool_stringset value); + +/* Common nested types */ +struct ethtool_header { + struct { + __u32 dev_index:1; + __u32 dev_name_len; + __u32 flags:1; + } _present; + + __u32 dev_index; + char *dev_name; + __u32 flags; +}; + +struct ethtool_pause_stat { + struct { + __u32 tx_frames:1; + __u32 rx_frames:1; + } _present; + + __u64 tx_frames; + __u64 rx_frames; +}; + +struct ethtool_cable_test_tdr_cfg { + struct { + __u32 first:1; + __u32 last:1; + __u32 step:1; + __u32 pair:1; + } _present; + + __u32 first; + __u32 last; + __u32 step; + __u8 pair; +}; + +struct ethtool_fec_stat { + struct { + __u32 corrected_len; + __u32 uncorr_len; + __u32 corr_bits_len; + } _present; + + void *corrected; + void *uncorr; + void *corr_bits; +}; + +struct ethtool_mm_stat { + struct { + __u32 reassembly_errors:1; + __u32 smd_errors:1; + __u32 reassembly_ok:1; + __u32 rx_frag_count:1; + __u32 tx_frag_count:1; + __u32 hold_count:1; + } _present; + + __u64 reassembly_errors; + __u64 smd_errors; + __u64 reassembly_ok; + __u64 rx_frag_count; + __u64 tx_frag_count; + __u64 hold_count; +}; + +struct ethtool_cable_result { + struct { + __u32 pair:1; + __u32 code:1; + } _present; + + __u8 pair; + __u8 code; +}; + +struct ethtool_cable_fault_length { + struct { + __u32 pair:1; + __u32 cm:1; + } _present; + + __u8 pair; + __u32 cm; +}; + +struct ethtool_bitset_bit { + struct { + __u32 index:1; + __u32 name_len; + __u32 value:1; + } _present; + + __u32 index; + char *name; +}; + +struct ethtool_tunnel_udp_entry { + struct { + __u32 port:1; + __u32 type:1; + } _present; + + __u16 port /* big-endian */; + __u32 type; +}; + +struct ethtool_string { + struct { + __u32 index:1; + __u32 value_len; + } _present; + + __u32 index; + char *value; +}; + +struct ethtool_cable_nest { + struct { + __u32 result:1; + __u32 fault_length:1; + } _present; + + struct ethtool_cable_result result; + struct ethtool_cable_fault_length fault_length; +}; + +struct ethtool_bitset_bits { + unsigned int n_bit; + struct ethtool_bitset_bit *bit; +}; + +struct ethtool_strings { + unsigned int n_string; + struct ethtool_string *string; +}; + +struct ethtool_bitset { + struct { + __u32 nomask:1; + __u32 size:1; + __u32 bits:1; + } _present; + + __u32 size; + struct ethtool_bitset_bits bits; +}; + +struct ethtool_stringset_ { + struct { + __u32 id:1; + __u32 count:1; + } _present; + + __u32 id; + __u32 count; + unsigned int n_strings; + struct ethtool_strings *strings; +}; + +struct ethtool_tunnel_udp_table { + struct { + __u32 size:1; + __u32 types:1; + } _present; + + __u32 size; + struct ethtool_bitset types; + unsigned int n_entry; + struct ethtool_tunnel_udp_entry *entry; +}; + +struct ethtool_stringsets { + unsigned int n_stringset; + struct ethtool_stringset_ *stringset; +}; + +struct ethtool_tunnel_udp { + struct { + __u32 table:1; + } _present; + + struct ethtool_tunnel_udp_table table; +}; + +/* ============== ETHTOOL_MSG_STRSET_GET ============== */ +/* ETHTOOL_MSG_STRSET_GET - do */ +struct ethtool_strset_get_req { + struct { + __u32 header:1; + __u32 stringsets:1; + __u32 counts_only:1; + } _present; + + struct ethtool_header header; + struct ethtool_stringsets stringsets; +}; + +static inline struct ethtool_strset_get_req *ethtool_strset_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_strset_get_req)); +} +void ethtool_strset_get_req_free(struct ethtool_strset_get_req *req); + +static inline void +ethtool_strset_get_req_set_header_dev_index(struct ethtool_strset_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_strset_get_req_set_header_dev_name(struct ethtool_strset_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_strset_get_req_set_header_flags(struct ethtool_strset_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +__ethtool_strset_get_req_set_stringsets_stringset(struct ethtool_strset_get_req *req, + struct ethtool_stringset_ *stringset, + unsigned int n_stringset) +{ + free(req->stringsets.stringset); + req->stringsets.stringset = stringset; + req->stringsets.n_stringset = n_stringset; +} +static inline void +ethtool_strset_get_req_set_counts_only(struct ethtool_strset_get_req *req) +{ + req->_present.counts_only = 1; +} + +struct ethtool_strset_get_rsp { + struct { + __u32 header:1; + __u32 stringsets:1; + } _present; + + struct ethtool_header header; + struct ethtool_stringsets stringsets; +}; + +void ethtool_strset_get_rsp_free(struct ethtool_strset_get_rsp *rsp); + +/* + * Get string set from the kernel. + */ +struct ethtool_strset_get_rsp * +ethtool_strset_get(struct ynl_sock *ys, struct ethtool_strset_get_req *req); + +/* ETHTOOL_MSG_STRSET_GET - dump */ +struct ethtool_strset_get_req_dump { + struct { + __u32 header:1; + __u32 stringsets:1; + __u32 counts_only:1; + } _present; + + struct ethtool_header header; + struct ethtool_stringsets stringsets; +}; + +static inline struct ethtool_strset_get_req_dump * +ethtool_strset_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_strset_get_req_dump)); +} +void ethtool_strset_get_req_dump_free(struct ethtool_strset_get_req_dump *req); + +static inline void +ethtool_strset_get_req_dump_set_header_dev_index(struct ethtool_strset_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_strset_get_req_dump_set_header_dev_name(struct ethtool_strset_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_strset_get_req_dump_set_header_flags(struct ethtool_strset_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +__ethtool_strset_get_req_dump_set_stringsets_stringset(struct ethtool_strset_get_req_dump *req, + struct ethtool_stringset_ *stringset, + unsigned int n_stringset) +{ + free(req->stringsets.stringset); + req->stringsets.stringset = stringset; + req->stringsets.n_stringset = n_stringset; +} +static inline void +ethtool_strset_get_req_dump_set_counts_only(struct ethtool_strset_get_req_dump *req) +{ + req->_present.counts_only = 1; +} + +struct ethtool_strset_get_list { + struct ethtool_strset_get_list *next; + struct ethtool_strset_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_strset_get_list_free(struct ethtool_strset_get_list *rsp); + +struct ethtool_strset_get_list * +ethtool_strset_get_dump(struct ynl_sock *ys, + struct ethtool_strset_get_req_dump *req); + +/* ============== ETHTOOL_MSG_LINKINFO_GET ============== */ +/* ETHTOOL_MSG_LINKINFO_GET - do */ +struct ethtool_linkinfo_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_linkinfo_get_req * +ethtool_linkinfo_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkinfo_get_req)); +} +void ethtool_linkinfo_get_req_free(struct ethtool_linkinfo_get_req *req); + +static inline void +ethtool_linkinfo_get_req_set_header_dev_index(struct ethtool_linkinfo_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkinfo_get_req_set_header_dev_name(struct ethtool_linkinfo_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkinfo_get_req_set_header_flags(struct ethtool_linkinfo_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_linkinfo_get_rsp { + struct { + __u32 header:1; + __u32 port:1; + __u32 phyaddr:1; + __u32 tp_mdix:1; + __u32 tp_mdix_ctrl:1; + __u32 transceiver:1; + } _present; + + struct ethtool_header header; + __u8 port; + __u8 phyaddr; + __u8 tp_mdix; + __u8 tp_mdix_ctrl; + __u8 transceiver; +}; + +void ethtool_linkinfo_get_rsp_free(struct ethtool_linkinfo_get_rsp *rsp); + +/* + * Get link info. + */ +struct ethtool_linkinfo_get_rsp * +ethtool_linkinfo_get(struct ynl_sock *ys, struct ethtool_linkinfo_get_req *req); + +/* ETHTOOL_MSG_LINKINFO_GET - dump */ +struct ethtool_linkinfo_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_linkinfo_get_req_dump * +ethtool_linkinfo_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkinfo_get_req_dump)); +} +void +ethtool_linkinfo_get_req_dump_free(struct ethtool_linkinfo_get_req_dump *req); + +static inline void +ethtool_linkinfo_get_req_dump_set_header_dev_index(struct ethtool_linkinfo_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkinfo_get_req_dump_set_header_dev_name(struct ethtool_linkinfo_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkinfo_get_req_dump_set_header_flags(struct ethtool_linkinfo_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_linkinfo_get_list { + struct ethtool_linkinfo_get_list *next; + struct ethtool_linkinfo_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_linkinfo_get_list_free(struct ethtool_linkinfo_get_list *rsp); + +struct ethtool_linkinfo_get_list * +ethtool_linkinfo_get_dump(struct ynl_sock *ys, + struct ethtool_linkinfo_get_req_dump *req); + +/* ETHTOOL_MSG_LINKINFO_GET - notify */ +struct ethtool_linkinfo_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_linkinfo_get_ntf *ntf); + struct ethtool_linkinfo_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_linkinfo_get_ntf_free(struct ethtool_linkinfo_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_LINKINFO_SET ============== */ +/* ETHTOOL_MSG_LINKINFO_SET - do */ +struct ethtool_linkinfo_set_req { + struct { + __u32 header:1; + __u32 port:1; + __u32 phyaddr:1; + __u32 tp_mdix:1; + __u32 tp_mdix_ctrl:1; + __u32 transceiver:1; + } _present; + + struct ethtool_header header; + __u8 port; + __u8 phyaddr; + __u8 tp_mdix; + __u8 tp_mdix_ctrl; + __u8 transceiver; +}; + +static inline struct ethtool_linkinfo_set_req * +ethtool_linkinfo_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkinfo_set_req)); +} +void ethtool_linkinfo_set_req_free(struct ethtool_linkinfo_set_req *req); + +static inline void +ethtool_linkinfo_set_req_set_header_dev_index(struct ethtool_linkinfo_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkinfo_set_req_set_header_dev_name(struct ethtool_linkinfo_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkinfo_set_req_set_header_flags(struct ethtool_linkinfo_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_linkinfo_set_req_set_port(struct ethtool_linkinfo_set_req *req, + __u8 port) +{ + req->_present.port = 1; + req->port = port; +} +static inline void +ethtool_linkinfo_set_req_set_phyaddr(struct ethtool_linkinfo_set_req *req, + __u8 phyaddr) +{ + req->_present.phyaddr = 1; + req->phyaddr = phyaddr; +} +static inline void +ethtool_linkinfo_set_req_set_tp_mdix(struct ethtool_linkinfo_set_req *req, + __u8 tp_mdix) +{ + req->_present.tp_mdix = 1; + req->tp_mdix = tp_mdix; +} +static inline void +ethtool_linkinfo_set_req_set_tp_mdix_ctrl(struct ethtool_linkinfo_set_req *req, + __u8 tp_mdix_ctrl) +{ + req->_present.tp_mdix_ctrl = 1; + req->tp_mdix_ctrl = tp_mdix_ctrl; +} +static inline void +ethtool_linkinfo_set_req_set_transceiver(struct ethtool_linkinfo_set_req *req, + __u8 transceiver) +{ + req->_present.transceiver = 1; + req->transceiver = transceiver; +} + +/* + * Set link info. + */ +int ethtool_linkinfo_set(struct ynl_sock *ys, + struct ethtool_linkinfo_set_req *req); + +/* ============== ETHTOOL_MSG_LINKMODES_GET ============== */ +/* ETHTOOL_MSG_LINKMODES_GET - do */ +struct ethtool_linkmodes_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_linkmodes_get_req * +ethtool_linkmodes_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkmodes_get_req)); +} +void ethtool_linkmodes_get_req_free(struct ethtool_linkmodes_get_req *req); + +static inline void +ethtool_linkmodes_get_req_set_header_dev_index(struct ethtool_linkmodes_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkmodes_get_req_set_header_dev_name(struct ethtool_linkmodes_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkmodes_get_req_set_header_flags(struct ethtool_linkmodes_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_linkmodes_get_rsp { + struct { + __u32 header:1; + __u32 autoneg:1; + __u32 ours:1; + __u32 peer:1; + __u32 speed:1; + __u32 duplex:1; + __u32 master_slave_cfg:1; + __u32 master_slave_state:1; + __u32 lanes:1; + __u32 rate_matching:1; + } _present; + + struct ethtool_header header; + __u8 autoneg; + struct ethtool_bitset ours; + struct ethtool_bitset peer; + __u32 speed; + __u8 duplex; + __u8 master_slave_cfg; + __u8 master_slave_state; + __u32 lanes; + __u8 rate_matching; +}; + +void ethtool_linkmodes_get_rsp_free(struct ethtool_linkmodes_get_rsp *rsp); + +/* + * Get link modes. + */ +struct ethtool_linkmodes_get_rsp * +ethtool_linkmodes_get(struct ynl_sock *ys, + struct ethtool_linkmodes_get_req *req); + +/* ETHTOOL_MSG_LINKMODES_GET - dump */ +struct ethtool_linkmodes_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_linkmodes_get_req_dump * +ethtool_linkmodes_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkmodes_get_req_dump)); +} +void +ethtool_linkmodes_get_req_dump_free(struct ethtool_linkmodes_get_req_dump *req); + +static inline void +ethtool_linkmodes_get_req_dump_set_header_dev_index(struct ethtool_linkmodes_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkmodes_get_req_dump_set_header_dev_name(struct ethtool_linkmodes_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkmodes_get_req_dump_set_header_flags(struct ethtool_linkmodes_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_linkmodes_get_list { + struct ethtool_linkmodes_get_list *next; + struct ethtool_linkmodes_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_linkmodes_get_list_free(struct ethtool_linkmodes_get_list *rsp); + +struct ethtool_linkmodes_get_list * +ethtool_linkmodes_get_dump(struct ynl_sock *ys, + struct ethtool_linkmodes_get_req_dump *req); + +/* ETHTOOL_MSG_LINKMODES_GET - notify */ +struct ethtool_linkmodes_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_linkmodes_get_ntf *ntf); + struct ethtool_linkmodes_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_linkmodes_get_ntf_free(struct ethtool_linkmodes_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_LINKMODES_SET ============== */ +/* ETHTOOL_MSG_LINKMODES_SET - do */ +struct ethtool_linkmodes_set_req { + struct { + __u32 header:1; + __u32 autoneg:1; + __u32 ours:1; + __u32 peer:1; + __u32 speed:1; + __u32 duplex:1; + __u32 master_slave_cfg:1; + __u32 master_slave_state:1; + __u32 lanes:1; + __u32 rate_matching:1; + } _present; + + struct ethtool_header header; + __u8 autoneg; + struct ethtool_bitset ours; + struct ethtool_bitset peer; + __u32 speed; + __u8 duplex; + __u8 master_slave_cfg; + __u8 master_slave_state; + __u32 lanes; + __u8 rate_matching; +}; + +static inline struct ethtool_linkmodes_set_req * +ethtool_linkmodes_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkmodes_set_req)); +} +void ethtool_linkmodes_set_req_free(struct ethtool_linkmodes_set_req *req); + +static inline void +ethtool_linkmodes_set_req_set_header_dev_index(struct ethtool_linkmodes_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkmodes_set_req_set_header_dev_name(struct ethtool_linkmodes_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkmodes_set_req_set_header_flags(struct ethtool_linkmodes_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_linkmodes_set_req_set_autoneg(struct ethtool_linkmodes_set_req *req, + __u8 autoneg) +{ + req->_present.autoneg = 1; + req->autoneg = autoneg; +} +static inline void +ethtool_linkmodes_set_req_set_ours_nomask(struct ethtool_linkmodes_set_req *req) +{ + req->_present.ours = 1; + req->ours._present.nomask = 1; +} +static inline void +ethtool_linkmodes_set_req_set_ours_size(struct ethtool_linkmodes_set_req *req, + __u32 size) +{ + req->_present.ours = 1; + req->ours._present.size = 1; + req->ours.size = size; +} +static inline void +__ethtool_linkmodes_set_req_set_ours_bits_bit(struct ethtool_linkmodes_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->ours.bits.bit); + req->ours.bits.bit = bit; + req->ours.bits.n_bit = n_bit; +} +static inline void +ethtool_linkmodes_set_req_set_peer_nomask(struct ethtool_linkmodes_set_req *req) +{ + req->_present.peer = 1; + req->peer._present.nomask = 1; +} +static inline void +ethtool_linkmodes_set_req_set_peer_size(struct ethtool_linkmodes_set_req *req, + __u32 size) +{ + req->_present.peer = 1; + req->peer._present.size = 1; + req->peer.size = size; +} +static inline void +__ethtool_linkmodes_set_req_set_peer_bits_bit(struct ethtool_linkmodes_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->peer.bits.bit); + req->peer.bits.bit = bit; + req->peer.bits.n_bit = n_bit; +} +static inline void +ethtool_linkmodes_set_req_set_speed(struct ethtool_linkmodes_set_req *req, + __u32 speed) +{ + req->_present.speed = 1; + req->speed = speed; +} +static inline void +ethtool_linkmodes_set_req_set_duplex(struct ethtool_linkmodes_set_req *req, + __u8 duplex) +{ + req->_present.duplex = 1; + req->duplex = duplex; +} +static inline void +ethtool_linkmodes_set_req_set_master_slave_cfg(struct ethtool_linkmodes_set_req *req, + __u8 master_slave_cfg) +{ + req->_present.master_slave_cfg = 1; + req->master_slave_cfg = master_slave_cfg; +} +static inline void +ethtool_linkmodes_set_req_set_master_slave_state(struct ethtool_linkmodes_set_req *req, + __u8 master_slave_state) +{ + req->_present.master_slave_state = 1; + req->master_slave_state = master_slave_state; +} +static inline void +ethtool_linkmodes_set_req_set_lanes(struct ethtool_linkmodes_set_req *req, + __u32 lanes) +{ + req->_present.lanes = 1; + req->lanes = lanes; +} +static inline void +ethtool_linkmodes_set_req_set_rate_matching(struct ethtool_linkmodes_set_req *req, + __u8 rate_matching) +{ + req->_present.rate_matching = 1; + req->rate_matching = rate_matching; +} + +/* + * Set link modes. + */ +int ethtool_linkmodes_set(struct ynl_sock *ys, + struct ethtool_linkmodes_set_req *req); + +/* ============== ETHTOOL_MSG_LINKSTATE_GET ============== */ +/* ETHTOOL_MSG_LINKSTATE_GET - do */ +struct ethtool_linkstate_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_linkstate_get_req * +ethtool_linkstate_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkstate_get_req)); +} +void ethtool_linkstate_get_req_free(struct ethtool_linkstate_get_req *req); + +static inline void +ethtool_linkstate_get_req_set_header_dev_index(struct ethtool_linkstate_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkstate_get_req_set_header_dev_name(struct ethtool_linkstate_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkstate_get_req_set_header_flags(struct ethtool_linkstate_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_linkstate_get_rsp { + struct { + __u32 header:1; + __u32 link:1; + __u32 sqi:1; + __u32 sqi_max:1; + __u32 ext_state:1; + __u32 ext_substate:1; + __u32 ext_down_cnt:1; + } _present; + + struct ethtool_header header; + __u8 link; + __u32 sqi; + __u32 sqi_max; + __u8 ext_state; + __u8 ext_substate; + __u32 ext_down_cnt; +}; + +void ethtool_linkstate_get_rsp_free(struct ethtool_linkstate_get_rsp *rsp); + +/* + * Get link state. + */ +struct ethtool_linkstate_get_rsp * +ethtool_linkstate_get(struct ynl_sock *ys, + struct ethtool_linkstate_get_req *req); + +/* ETHTOOL_MSG_LINKSTATE_GET - dump */ +struct ethtool_linkstate_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_linkstate_get_req_dump * +ethtool_linkstate_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_linkstate_get_req_dump)); +} +void +ethtool_linkstate_get_req_dump_free(struct ethtool_linkstate_get_req_dump *req); + +static inline void +ethtool_linkstate_get_req_dump_set_header_dev_index(struct ethtool_linkstate_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_linkstate_get_req_dump_set_header_dev_name(struct ethtool_linkstate_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_linkstate_get_req_dump_set_header_flags(struct ethtool_linkstate_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_linkstate_get_list { + struct ethtool_linkstate_get_list *next; + struct ethtool_linkstate_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_linkstate_get_list_free(struct ethtool_linkstate_get_list *rsp); + +struct ethtool_linkstate_get_list * +ethtool_linkstate_get_dump(struct ynl_sock *ys, + struct ethtool_linkstate_get_req_dump *req); + +/* ============== ETHTOOL_MSG_DEBUG_GET ============== */ +/* ETHTOOL_MSG_DEBUG_GET - do */ +struct ethtool_debug_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_debug_get_req *ethtool_debug_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_debug_get_req)); +} +void ethtool_debug_get_req_free(struct ethtool_debug_get_req *req); + +static inline void +ethtool_debug_get_req_set_header_dev_index(struct ethtool_debug_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_debug_get_req_set_header_dev_name(struct ethtool_debug_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_debug_get_req_set_header_flags(struct ethtool_debug_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_debug_get_rsp { + struct { + __u32 header:1; + __u32 msgmask:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset msgmask; +}; + +void ethtool_debug_get_rsp_free(struct ethtool_debug_get_rsp *rsp); + +/* + * Get debug message mask. + */ +struct ethtool_debug_get_rsp * +ethtool_debug_get(struct ynl_sock *ys, struct ethtool_debug_get_req *req); + +/* ETHTOOL_MSG_DEBUG_GET - dump */ +struct ethtool_debug_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_debug_get_req_dump * +ethtool_debug_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_debug_get_req_dump)); +} +void ethtool_debug_get_req_dump_free(struct ethtool_debug_get_req_dump *req); + +static inline void +ethtool_debug_get_req_dump_set_header_dev_index(struct ethtool_debug_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_debug_get_req_dump_set_header_dev_name(struct ethtool_debug_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_debug_get_req_dump_set_header_flags(struct ethtool_debug_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_debug_get_list { + struct ethtool_debug_get_list *next; + struct ethtool_debug_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_debug_get_list_free(struct ethtool_debug_get_list *rsp); + +struct ethtool_debug_get_list * +ethtool_debug_get_dump(struct ynl_sock *ys, + struct ethtool_debug_get_req_dump *req); + +/* ETHTOOL_MSG_DEBUG_GET - notify */ +struct ethtool_debug_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_debug_get_ntf *ntf); + struct ethtool_debug_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_debug_get_ntf_free(struct ethtool_debug_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_DEBUG_SET ============== */ +/* ETHTOOL_MSG_DEBUG_SET - do */ +struct ethtool_debug_set_req { + struct { + __u32 header:1; + __u32 msgmask:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset msgmask; +}; + +static inline struct ethtool_debug_set_req *ethtool_debug_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_debug_set_req)); +} +void ethtool_debug_set_req_free(struct ethtool_debug_set_req *req); + +static inline void +ethtool_debug_set_req_set_header_dev_index(struct ethtool_debug_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_debug_set_req_set_header_dev_name(struct ethtool_debug_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_debug_set_req_set_header_flags(struct ethtool_debug_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_debug_set_req_set_msgmask_nomask(struct ethtool_debug_set_req *req) +{ + req->_present.msgmask = 1; + req->msgmask._present.nomask = 1; +} +static inline void +ethtool_debug_set_req_set_msgmask_size(struct ethtool_debug_set_req *req, + __u32 size) +{ + req->_present.msgmask = 1; + req->msgmask._present.size = 1; + req->msgmask.size = size; +} +static inline void +__ethtool_debug_set_req_set_msgmask_bits_bit(struct ethtool_debug_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->msgmask.bits.bit); + req->msgmask.bits.bit = bit; + req->msgmask.bits.n_bit = n_bit; +} + +/* + * Set debug message mask. + */ +int ethtool_debug_set(struct ynl_sock *ys, struct ethtool_debug_set_req *req); + +/* ============== ETHTOOL_MSG_WOL_GET ============== */ +/* ETHTOOL_MSG_WOL_GET - do */ +struct ethtool_wol_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_wol_get_req *ethtool_wol_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_wol_get_req)); +} +void ethtool_wol_get_req_free(struct ethtool_wol_get_req *req); + +static inline void +ethtool_wol_get_req_set_header_dev_index(struct ethtool_wol_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_wol_get_req_set_header_dev_name(struct ethtool_wol_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_wol_get_req_set_header_flags(struct ethtool_wol_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_wol_get_rsp { + struct { + __u32 header:1; + __u32 modes:1; + __u32 sopass_len; + } _present; + + struct ethtool_header header; + struct ethtool_bitset modes; + void *sopass; +}; + +void ethtool_wol_get_rsp_free(struct ethtool_wol_get_rsp *rsp); + +/* + * Get WOL params. + */ +struct ethtool_wol_get_rsp * +ethtool_wol_get(struct ynl_sock *ys, struct ethtool_wol_get_req *req); + +/* ETHTOOL_MSG_WOL_GET - dump */ +struct ethtool_wol_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_wol_get_req_dump * +ethtool_wol_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_wol_get_req_dump)); +} +void ethtool_wol_get_req_dump_free(struct ethtool_wol_get_req_dump *req); + +static inline void +ethtool_wol_get_req_dump_set_header_dev_index(struct ethtool_wol_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_wol_get_req_dump_set_header_dev_name(struct ethtool_wol_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_wol_get_req_dump_set_header_flags(struct ethtool_wol_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_wol_get_list { + struct ethtool_wol_get_list *next; + struct ethtool_wol_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_wol_get_list_free(struct ethtool_wol_get_list *rsp); + +struct ethtool_wol_get_list * +ethtool_wol_get_dump(struct ynl_sock *ys, struct ethtool_wol_get_req_dump *req); + +/* ETHTOOL_MSG_WOL_GET - notify */ +struct ethtool_wol_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_wol_get_ntf *ntf); + struct ethtool_wol_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_wol_get_ntf_free(struct ethtool_wol_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_WOL_SET ============== */ +/* ETHTOOL_MSG_WOL_SET - do */ +struct ethtool_wol_set_req { + struct { + __u32 header:1; + __u32 modes:1; + __u32 sopass_len; + } _present; + + struct ethtool_header header; + struct ethtool_bitset modes; + void *sopass; +}; + +static inline struct ethtool_wol_set_req *ethtool_wol_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_wol_set_req)); +} +void ethtool_wol_set_req_free(struct ethtool_wol_set_req *req); + +static inline void +ethtool_wol_set_req_set_header_dev_index(struct ethtool_wol_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_wol_set_req_set_header_dev_name(struct ethtool_wol_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_wol_set_req_set_header_flags(struct ethtool_wol_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_wol_set_req_set_modes_nomask(struct ethtool_wol_set_req *req) +{ + req->_present.modes = 1; + req->modes._present.nomask = 1; +} +static inline void +ethtool_wol_set_req_set_modes_size(struct ethtool_wol_set_req *req, __u32 size) +{ + req->_present.modes = 1; + req->modes._present.size = 1; + req->modes.size = size; +} +static inline void +__ethtool_wol_set_req_set_modes_bits_bit(struct ethtool_wol_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->modes.bits.bit); + req->modes.bits.bit = bit; + req->modes.bits.n_bit = n_bit; +} +static inline void +ethtool_wol_set_req_set_sopass(struct ethtool_wol_set_req *req, + const void *sopass, size_t len) +{ + free(req->sopass); + req->sopass = malloc(req->_present.sopass_len); + memcpy(req->sopass, sopass, req->_present.sopass_len); +} + +/* + * Set WOL params. + */ +int ethtool_wol_set(struct ynl_sock *ys, struct ethtool_wol_set_req *req); + +/* ============== ETHTOOL_MSG_FEATURES_GET ============== */ +/* ETHTOOL_MSG_FEATURES_GET - do */ +struct ethtool_features_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_features_get_req * +ethtool_features_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_features_get_req)); +} +void ethtool_features_get_req_free(struct ethtool_features_get_req *req); + +static inline void +ethtool_features_get_req_set_header_dev_index(struct ethtool_features_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_features_get_req_set_header_dev_name(struct ethtool_features_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_features_get_req_set_header_flags(struct ethtool_features_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_features_get_rsp { + struct { + __u32 header:1; + __u32 hw:1; + __u32 wanted:1; + __u32 active:1; + __u32 nochange:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset hw; + struct ethtool_bitset wanted; + struct ethtool_bitset active; + struct ethtool_bitset nochange; +}; + +void ethtool_features_get_rsp_free(struct ethtool_features_get_rsp *rsp); + +/* + * Get features. + */ +struct ethtool_features_get_rsp * +ethtool_features_get(struct ynl_sock *ys, struct ethtool_features_get_req *req); + +/* ETHTOOL_MSG_FEATURES_GET - dump */ +struct ethtool_features_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_features_get_req_dump * +ethtool_features_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_features_get_req_dump)); +} +void +ethtool_features_get_req_dump_free(struct ethtool_features_get_req_dump *req); + +static inline void +ethtool_features_get_req_dump_set_header_dev_index(struct ethtool_features_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_features_get_req_dump_set_header_dev_name(struct ethtool_features_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_features_get_req_dump_set_header_flags(struct ethtool_features_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_features_get_list { + struct ethtool_features_get_list *next; + struct ethtool_features_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_features_get_list_free(struct ethtool_features_get_list *rsp); + +struct ethtool_features_get_list * +ethtool_features_get_dump(struct ynl_sock *ys, + struct ethtool_features_get_req_dump *req); + +/* ETHTOOL_MSG_FEATURES_GET - notify */ +struct ethtool_features_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_features_get_ntf *ntf); + struct ethtool_features_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_features_get_ntf_free(struct ethtool_features_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_FEATURES_SET ============== */ +/* ETHTOOL_MSG_FEATURES_SET - do */ +struct ethtool_features_set_req { + struct { + __u32 header:1; + __u32 hw:1; + __u32 wanted:1; + __u32 active:1; + __u32 nochange:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset hw; + struct ethtool_bitset wanted; + struct ethtool_bitset active; + struct ethtool_bitset nochange; +}; + +static inline struct ethtool_features_set_req * +ethtool_features_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_features_set_req)); +} +void ethtool_features_set_req_free(struct ethtool_features_set_req *req); + +static inline void +ethtool_features_set_req_set_header_dev_index(struct ethtool_features_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_features_set_req_set_header_dev_name(struct ethtool_features_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_features_set_req_set_header_flags(struct ethtool_features_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_features_set_req_set_hw_nomask(struct ethtool_features_set_req *req) +{ + req->_present.hw = 1; + req->hw._present.nomask = 1; +} +static inline void +ethtool_features_set_req_set_hw_size(struct ethtool_features_set_req *req, + __u32 size) +{ + req->_present.hw = 1; + req->hw._present.size = 1; + req->hw.size = size; +} +static inline void +__ethtool_features_set_req_set_hw_bits_bit(struct ethtool_features_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->hw.bits.bit); + req->hw.bits.bit = bit; + req->hw.bits.n_bit = n_bit; +} +static inline void +ethtool_features_set_req_set_wanted_nomask(struct ethtool_features_set_req *req) +{ + req->_present.wanted = 1; + req->wanted._present.nomask = 1; +} +static inline void +ethtool_features_set_req_set_wanted_size(struct ethtool_features_set_req *req, + __u32 size) +{ + req->_present.wanted = 1; + req->wanted._present.size = 1; + req->wanted.size = size; +} +static inline void +__ethtool_features_set_req_set_wanted_bits_bit(struct ethtool_features_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->wanted.bits.bit); + req->wanted.bits.bit = bit; + req->wanted.bits.n_bit = n_bit; +} +static inline void +ethtool_features_set_req_set_active_nomask(struct ethtool_features_set_req *req) +{ + req->_present.active = 1; + req->active._present.nomask = 1; +} +static inline void +ethtool_features_set_req_set_active_size(struct ethtool_features_set_req *req, + __u32 size) +{ + req->_present.active = 1; + req->active._present.size = 1; + req->active.size = size; +} +static inline void +__ethtool_features_set_req_set_active_bits_bit(struct ethtool_features_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->active.bits.bit); + req->active.bits.bit = bit; + req->active.bits.n_bit = n_bit; +} +static inline void +ethtool_features_set_req_set_nochange_nomask(struct ethtool_features_set_req *req) +{ + req->_present.nochange = 1; + req->nochange._present.nomask = 1; +} +static inline void +ethtool_features_set_req_set_nochange_size(struct ethtool_features_set_req *req, + __u32 size) +{ + req->_present.nochange = 1; + req->nochange._present.size = 1; + req->nochange.size = size; +} +static inline void +__ethtool_features_set_req_set_nochange_bits_bit(struct ethtool_features_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->nochange.bits.bit); + req->nochange.bits.bit = bit; + req->nochange.bits.n_bit = n_bit; +} + +struct ethtool_features_set_rsp { + struct { + __u32 header:1; + __u32 hw:1; + __u32 wanted:1; + __u32 active:1; + __u32 nochange:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset hw; + struct ethtool_bitset wanted; + struct ethtool_bitset active; + struct ethtool_bitset nochange; +}; + +void ethtool_features_set_rsp_free(struct ethtool_features_set_rsp *rsp); + +/* + * Set features. + */ +struct ethtool_features_set_rsp * +ethtool_features_set(struct ynl_sock *ys, struct ethtool_features_set_req *req); + +/* ============== ETHTOOL_MSG_PRIVFLAGS_GET ============== */ +/* ETHTOOL_MSG_PRIVFLAGS_GET - do */ +struct ethtool_privflags_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_privflags_get_req * +ethtool_privflags_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_privflags_get_req)); +} +void ethtool_privflags_get_req_free(struct ethtool_privflags_get_req *req); + +static inline void +ethtool_privflags_get_req_set_header_dev_index(struct ethtool_privflags_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_privflags_get_req_set_header_dev_name(struct ethtool_privflags_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_privflags_get_req_set_header_flags(struct ethtool_privflags_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_privflags_get_rsp { + struct { + __u32 header:1; + __u32 flags:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset flags; +}; + +void ethtool_privflags_get_rsp_free(struct ethtool_privflags_get_rsp *rsp); + +/* + * Get device private flags. + */ +struct ethtool_privflags_get_rsp * +ethtool_privflags_get(struct ynl_sock *ys, + struct ethtool_privflags_get_req *req); + +/* ETHTOOL_MSG_PRIVFLAGS_GET - dump */ +struct ethtool_privflags_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_privflags_get_req_dump * +ethtool_privflags_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_privflags_get_req_dump)); +} +void +ethtool_privflags_get_req_dump_free(struct ethtool_privflags_get_req_dump *req); + +static inline void +ethtool_privflags_get_req_dump_set_header_dev_index(struct ethtool_privflags_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_privflags_get_req_dump_set_header_dev_name(struct ethtool_privflags_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_privflags_get_req_dump_set_header_flags(struct ethtool_privflags_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_privflags_get_list { + struct ethtool_privflags_get_list *next; + struct ethtool_privflags_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_privflags_get_list_free(struct ethtool_privflags_get_list *rsp); + +struct ethtool_privflags_get_list * +ethtool_privflags_get_dump(struct ynl_sock *ys, + struct ethtool_privflags_get_req_dump *req); + +/* ETHTOOL_MSG_PRIVFLAGS_GET - notify */ +struct ethtool_privflags_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_privflags_get_ntf *ntf); + struct ethtool_privflags_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_privflags_get_ntf_free(struct ethtool_privflags_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_PRIVFLAGS_SET ============== */ +/* ETHTOOL_MSG_PRIVFLAGS_SET - do */ +struct ethtool_privflags_set_req { + struct { + __u32 header:1; + __u32 flags:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset flags; +}; + +static inline struct ethtool_privflags_set_req * +ethtool_privflags_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_privflags_set_req)); +} +void ethtool_privflags_set_req_free(struct ethtool_privflags_set_req *req); + +static inline void +ethtool_privflags_set_req_set_header_dev_index(struct ethtool_privflags_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_privflags_set_req_set_header_dev_name(struct ethtool_privflags_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_privflags_set_req_set_header_flags(struct ethtool_privflags_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_privflags_set_req_set_flags_nomask(struct ethtool_privflags_set_req *req) +{ + req->_present.flags = 1; + req->flags._present.nomask = 1; +} +static inline void +ethtool_privflags_set_req_set_flags_size(struct ethtool_privflags_set_req *req, + __u32 size) +{ + req->_present.flags = 1; + req->flags._present.size = 1; + req->flags.size = size; +} +static inline void +__ethtool_privflags_set_req_set_flags_bits_bit(struct ethtool_privflags_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->flags.bits.bit); + req->flags.bits.bit = bit; + req->flags.bits.n_bit = n_bit; +} + +/* + * Set device private flags. + */ +int ethtool_privflags_set(struct ynl_sock *ys, + struct ethtool_privflags_set_req *req); + +/* ============== ETHTOOL_MSG_RINGS_GET ============== */ +/* ETHTOOL_MSG_RINGS_GET - do */ +struct ethtool_rings_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_rings_get_req *ethtool_rings_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_rings_get_req)); +} +void ethtool_rings_get_req_free(struct ethtool_rings_get_req *req); + +static inline void +ethtool_rings_get_req_set_header_dev_index(struct ethtool_rings_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_rings_get_req_set_header_dev_name(struct ethtool_rings_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_rings_get_req_set_header_flags(struct ethtool_rings_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_rings_get_rsp { + struct { + __u32 header:1; + __u32 rx_max:1; + __u32 rx_mini_max:1; + __u32 rx_jumbo_max:1; + __u32 tx_max:1; + __u32 rx:1; + __u32 rx_mini:1; + __u32 rx_jumbo:1; + __u32 tx:1; + __u32 rx_buf_len:1; + __u32 tcp_data_split:1; + __u32 cqe_size:1; + __u32 tx_push:1; + __u32 rx_push:1; + __u32 tx_push_buf_len:1; + __u32 tx_push_buf_len_max:1; + } _present; + + struct ethtool_header header; + __u32 rx_max; + __u32 rx_mini_max; + __u32 rx_jumbo_max; + __u32 tx_max; + __u32 rx; + __u32 rx_mini; + __u32 rx_jumbo; + __u32 tx; + __u32 rx_buf_len; + __u8 tcp_data_split; + __u32 cqe_size; + __u8 tx_push; + __u8 rx_push; + __u32 tx_push_buf_len; + __u32 tx_push_buf_len_max; +}; + +void ethtool_rings_get_rsp_free(struct ethtool_rings_get_rsp *rsp); + +/* + * Get ring params. + */ +struct ethtool_rings_get_rsp * +ethtool_rings_get(struct ynl_sock *ys, struct ethtool_rings_get_req *req); + +/* ETHTOOL_MSG_RINGS_GET - dump */ +struct ethtool_rings_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_rings_get_req_dump * +ethtool_rings_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_rings_get_req_dump)); +} +void ethtool_rings_get_req_dump_free(struct ethtool_rings_get_req_dump *req); + +static inline void +ethtool_rings_get_req_dump_set_header_dev_index(struct ethtool_rings_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_rings_get_req_dump_set_header_dev_name(struct ethtool_rings_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_rings_get_req_dump_set_header_flags(struct ethtool_rings_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_rings_get_list { + struct ethtool_rings_get_list *next; + struct ethtool_rings_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_rings_get_list_free(struct ethtool_rings_get_list *rsp); + +struct ethtool_rings_get_list * +ethtool_rings_get_dump(struct ynl_sock *ys, + struct ethtool_rings_get_req_dump *req); + +/* ETHTOOL_MSG_RINGS_GET - notify */ +struct ethtool_rings_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_rings_get_ntf *ntf); + struct ethtool_rings_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_rings_get_ntf_free(struct ethtool_rings_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_RINGS_SET ============== */ +/* ETHTOOL_MSG_RINGS_SET - do */ +struct ethtool_rings_set_req { + struct { + __u32 header:1; + __u32 rx_max:1; + __u32 rx_mini_max:1; + __u32 rx_jumbo_max:1; + __u32 tx_max:1; + __u32 rx:1; + __u32 rx_mini:1; + __u32 rx_jumbo:1; + __u32 tx:1; + __u32 rx_buf_len:1; + __u32 tcp_data_split:1; + __u32 cqe_size:1; + __u32 tx_push:1; + __u32 rx_push:1; + __u32 tx_push_buf_len:1; + __u32 tx_push_buf_len_max:1; + } _present; + + struct ethtool_header header; + __u32 rx_max; + __u32 rx_mini_max; + __u32 rx_jumbo_max; + __u32 tx_max; + __u32 rx; + __u32 rx_mini; + __u32 rx_jumbo; + __u32 tx; + __u32 rx_buf_len; + __u8 tcp_data_split; + __u32 cqe_size; + __u8 tx_push; + __u8 rx_push; + __u32 tx_push_buf_len; + __u32 tx_push_buf_len_max; +}; + +static inline struct ethtool_rings_set_req *ethtool_rings_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_rings_set_req)); +} +void ethtool_rings_set_req_free(struct ethtool_rings_set_req *req); + +static inline void +ethtool_rings_set_req_set_header_dev_index(struct ethtool_rings_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_rings_set_req_set_header_dev_name(struct ethtool_rings_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_rings_set_req_set_header_flags(struct ethtool_rings_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_rings_set_req_set_rx_max(struct ethtool_rings_set_req *req, + __u32 rx_max) +{ + req->_present.rx_max = 1; + req->rx_max = rx_max; +} +static inline void +ethtool_rings_set_req_set_rx_mini_max(struct ethtool_rings_set_req *req, + __u32 rx_mini_max) +{ + req->_present.rx_mini_max = 1; + req->rx_mini_max = rx_mini_max; +} +static inline void +ethtool_rings_set_req_set_rx_jumbo_max(struct ethtool_rings_set_req *req, + __u32 rx_jumbo_max) +{ + req->_present.rx_jumbo_max = 1; + req->rx_jumbo_max = rx_jumbo_max; +} +static inline void +ethtool_rings_set_req_set_tx_max(struct ethtool_rings_set_req *req, + __u32 tx_max) +{ + req->_present.tx_max = 1; + req->tx_max = tx_max; +} +static inline void +ethtool_rings_set_req_set_rx(struct ethtool_rings_set_req *req, __u32 rx) +{ + req->_present.rx = 1; + req->rx = rx; +} +static inline void +ethtool_rings_set_req_set_rx_mini(struct ethtool_rings_set_req *req, + __u32 rx_mini) +{ + req->_present.rx_mini = 1; + req->rx_mini = rx_mini; +} +static inline void +ethtool_rings_set_req_set_rx_jumbo(struct ethtool_rings_set_req *req, + __u32 rx_jumbo) +{ + req->_present.rx_jumbo = 1; + req->rx_jumbo = rx_jumbo; +} +static inline void +ethtool_rings_set_req_set_tx(struct ethtool_rings_set_req *req, __u32 tx) +{ + req->_present.tx = 1; + req->tx = tx; +} +static inline void +ethtool_rings_set_req_set_rx_buf_len(struct ethtool_rings_set_req *req, + __u32 rx_buf_len) +{ + req->_present.rx_buf_len = 1; + req->rx_buf_len = rx_buf_len; +} +static inline void +ethtool_rings_set_req_set_tcp_data_split(struct ethtool_rings_set_req *req, + __u8 tcp_data_split) +{ + req->_present.tcp_data_split = 1; + req->tcp_data_split = tcp_data_split; +} +static inline void +ethtool_rings_set_req_set_cqe_size(struct ethtool_rings_set_req *req, + __u32 cqe_size) +{ + req->_present.cqe_size = 1; + req->cqe_size = cqe_size; +} +static inline void +ethtool_rings_set_req_set_tx_push(struct ethtool_rings_set_req *req, + __u8 tx_push) +{ + req->_present.tx_push = 1; + req->tx_push = tx_push; +} +static inline void +ethtool_rings_set_req_set_rx_push(struct ethtool_rings_set_req *req, + __u8 rx_push) +{ + req->_present.rx_push = 1; + req->rx_push = rx_push; +} +static inline void +ethtool_rings_set_req_set_tx_push_buf_len(struct ethtool_rings_set_req *req, + __u32 tx_push_buf_len) +{ + req->_present.tx_push_buf_len = 1; + req->tx_push_buf_len = tx_push_buf_len; +} +static inline void +ethtool_rings_set_req_set_tx_push_buf_len_max(struct ethtool_rings_set_req *req, + __u32 tx_push_buf_len_max) +{ + req->_present.tx_push_buf_len_max = 1; + req->tx_push_buf_len_max = tx_push_buf_len_max; +} + +/* + * Set ring params. + */ +int ethtool_rings_set(struct ynl_sock *ys, struct ethtool_rings_set_req *req); + +/* ============== ETHTOOL_MSG_CHANNELS_GET ============== */ +/* ETHTOOL_MSG_CHANNELS_GET - do */ +struct ethtool_channels_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_channels_get_req * +ethtool_channels_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_channels_get_req)); +} +void ethtool_channels_get_req_free(struct ethtool_channels_get_req *req); + +static inline void +ethtool_channels_get_req_set_header_dev_index(struct ethtool_channels_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_channels_get_req_set_header_dev_name(struct ethtool_channels_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_channels_get_req_set_header_flags(struct ethtool_channels_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_channels_get_rsp { + struct { + __u32 header:1; + __u32 rx_max:1; + __u32 tx_max:1; + __u32 other_max:1; + __u32 combined_max:1; + __u32 rx_count:1; + __u32 tx_count:1; + __u32 other_count:1; + __u32 combined_count:1; + } _present; + + struct ethtool_header header; + __u32 rx_max; + __u32 tx_max; + __u32 other_max; + __u32 combined_max; + __u32 rx_count; + __u32 tx_count; + __u32 other_count; + __u32 combined_count; +}; + +void ethtool_channels_get_rsp_free(struct ethtool_channels_get_rsp *rsp); + +/* + * Get channel params. + */ +struct ethtool_channels_get_rsp * +ethtool_channels_get(struct ynl_sock *ys, struct ethtool_channels_get_req *req); + +/* ETHTOOL_MSG_CHANNELS_GET - dump */ +struct ethtool_channels_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_channels_get_req_dump * +ethtool_channels_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_channels_get_req_dump)); +} +void +ethtool_channels_get_req_dump_free(struct ethtool_channels_get_req_dump *req); + +static inline void +ethtool_channels_get_req_dump_set_header_dev_index(struct ethtool_channels_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_channels_get_req_dump_set_header_dev_name(struct ethtool_channels_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_channels_get_req_dump_set_header_flags(struct ethtool_channels_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_channels_get_list { + struct ethtool_channels_get_list *next; + struct ethtool_channels_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_channels_get_list_free(struct ethtool_channels_get_list *rsp); + +struct ethtool_channels_get_list * +ethtool_channels_get_dump(struct ynl_sock *ys, + struct ethtool_channels_get_req_dump *req); + +/* ETHTOOL_MSG_CHANNELS_GET - notify */ +struct ethtool_channels_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_channels_get_ntf *ntf); + struct ethtool_channels_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_channels_get_ntf_free(struct ethtool_channels_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_CHANNELS_SET ============== */ +/* ETHTOOL_MSG_CHANNELS_SET - do */ +struct ethtool_channels_set_req { + struct { + __u32 header:1; + __u32 rx_max:1; + __u32 tx_max:1; + __u32 other_max:1; + __u32 combined_max:1; + __u32 rx_count:1; + __u32 tx_count:1; + __u32 other_count:1; + __u32 combined_count:1; + } _present; + + struct ethtool_header header; + __u32 rx_max; + __u32 tx_max; + __u32 other_max; + __u32 combined_max; + __u32 rx_count; + __u32 tx_count; + __u32 other_count; + __u32 combined_count; +}; + +static inline struct ethtool_channels_set_req * +ethtool_channels_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_channels_set_req)); +} +void ethtool_channels_set_req_free(struct ethtool_channels_set_req *req); + +static inline void +ethtool_channels_set_req_set_header_dev_index(struct ethtool_channels_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_channels_set_req_set_header_dev_name(struct ethtool_channels_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_channels_set_req_set_header_flags(struct ethtool_channels_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_channels_set_req_set_rx_max(struct ethtool_channels_set_req *req, + __u32 rx_max) +{ + req->_present.rx_max = 1; + req->rx_max = rx_max; +} +static inline void +ethtool_channels_set_req_set_tx_max(struct ethtool_channels_set_req *req, + __u32 tx_max) +{ + req->_present.tx_max = 1; + req->tx_max = tx_max; +} +static inline void +ethtool_channels_set_req_set_other_max(struct ethtool_channels_set_req *req, + __u32 other_max) +{ + req->_present.other_max = 1; + req->other_max = other_max; +} +static inline void +ethtool_channels_set_req_set_combined_max(struct ethtool_channels_set_req *req, + __u32 combined_max) +{ + req->_present.combined_max = 1; + req->combined_max = combined_max; +} +static inline void +ethtool_channels_set_req_set_rx_count(struct ethtool_channels_set_req *req, + __u32 rx_count) +{ + req->_present.rx_count = 1; + req->rx_count = rx_count; +} +static inline void +ethtool_channels_set_req_set_tx_count(struct ethtool_channels_set_req *req, + __u32 tx_count) +{ + req->_present.tx_count = 1; + req->tx_count = tx_count; +} +static inline void +ethtool_channels_set_req_set_other_count(struct ethtool_channels_set_req *req, + __u32 other_count) +{ + req->_present.other_count = 1; + req->other_count = other_count; +} +static inline void +ethtool_channels_set_req_set_combined_count(struct ethtool_channels_set_req *req, + __u32 combined_count) +{ + req->_present.combined_count = 1; + req->combined_count = combined_count; +} + +/* + * Set channel params. + */ +int ethtool_channels_set(struct ynl_sock *ys, + struct ethtool_channels_set_req *req); + +/* ============== ETHTOOL_MSG_COALESCE_GET ============== */ +/* ETHTOOL_MSG_COALESCE_GET - do */ +struct ethtool_coalesce_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_coalesce_get_req * +ethtool_coalesce_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_coalesce_get_req)); +} +void ethtool_coalesce_get_req_free(struct ethtool_coalesce_get_req *req); + +static inline void +ethtool_coalesce_get_req_set_header_dev_index(struct ethtool_coalesce_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_coalesce_get_req_set_header_dev_name(struct ethtool_coalesce_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_coalesce_get_req_set_header_flags(struct ethtool_coalesce_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_coalesce_get_rsp { + struct { + __u32 header:1; + __u32 rx_usecs:1; + __u32 rx_max_frames:1; + __u32 rx_usecs_irq:1; + __u32 rx_max_frames_irq:1; + __u32 tx_usecs:1; + __u32 tx_max_frames:1; + __u32 tx_usecs_irq:1; + __u32 tx_max_frames_irq:1; + __u32 stats_block_usecs:1; + __u32 use_adaptive_rx:1; + __u32 use_adaptive_tx:1; + __u32 pkt_rate_low:1; + __u32 rx_usecs_low:1; + __u32 rx_max_frames_low:1; + __u32 tx_usecs_low:1; + __u32 tx_max_frames_low:1; + __u32 pkt_rate_high:1; + __u32 rx_usecs_high:1; + __u32 rx_max_frames_high:1; + __u32 tx_usecs_high:1; + __u32 tx_max_frames_high:1; + __u32 rate_sample_interval:1; + __u32 use_cqe_mode_tx:1; + __u32 use_cqe_mode_rx:1; + __u32 tx_aggr_max_bytes:1; + __u32 tx_aggr_max_frames:1; + __u32 tx_aggr_time_usecs:1; + } _present; + + struct ethtool_header header; + __u32 rx_usecs; + __u32 rx_max_frames; + __u32 rx_usecs_irq; + __u32 rx_max_frames_irq; + __u32 tx_usecs; + __u32 tx_max_frames; + __u32 tx_usecs_irq; + __u32 tx_max_frames_irq; + __u32 stats_block_usecs; + __u8 use_adaptive_rx; + __u8 use_adaptive_tx; + __u32 pkt_rate_low; + __u32 rx_usecs_low; + __u32 rx_max_frames_low; + __u32 tx_usecs_low; + __u32 tx_max_frames_low; + __u32 pkt_rate_high; + __u32 rx_usecs_high; + __u32 rx_max_frames_high; + __u32 tx_usecs_high; + __u32 tx_max_frames_high; + __u32 rate_sample_interval; + __u8 use_cqe_mode_tx; + __u8 use_cqe_mode_rx; + __u32 tx_aggr_max_bytes; + __u32 tx_aggr_max_frames; + __u32 tx_aggr_time_usecs; +}; + +void ethtool_coalesce_get_rsp_free(struct ethtool_coalesce_get_rsp *rsp); + +/* + * Get coalesce params. + */ +struct ethtool_coalesce_get_rsp * +ethtool_coalesce_get(struct ynl_sock *ys, struct ethtool_coalesce_get_req *req); + +/* ETHTOOL_MSG_COALESCE_GET - dump */ +struct ethtool_coalesce_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_coalesce_get_req_dump * +ethtool_coalesce_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_coalesce_get_req_dump)); +} +void +ethtool_coalesce_get_req_dump_free(struct ethtool_coalesce_get_req_dump *req); + +static inline void +ethtool_coalesce_get_req_dump_set_header_dev_index(struct ethtool_coalesce_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_coalesce_get_req_dump_set_header_dev_name(struct ethtool_coalesce_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_coalesce_get_req_dump_set_header_flags(struct ethtool_coalesce_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_coalesce_get_list { + struct ethtool_coalesce_get_list *next; + struct ethtool_coalesce_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_coalesce_get_list_free(struct ethtool_coalesce_get_list *rsp); + +struct ethtool_coalesce_get_list * +ethtool_coalesce_get_dump(struct ynl_sock *ys, + struct ethtool_coalesce_get_req_dump *req); + +/* ETHTOOL_MSG_COALESCE_GET - notify */ +struct ethtool_coalesce_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_coalesce_get_ntf *ntf); + struct ethtool_coalesce_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_coalesce_get_ntf_free(struct ethtool_coalesce_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_COALESCE_SET ============== */ +/* ETHTOOL_MSG_COALESCE_SET - do */ +struct ethtool_coalesce_set_req { + struct { + __u32 header:1; + __u32 rx_usecs:1; + __u32 rx_max_frames:1; + __u32 rx_usecs_irq:1; + __u32 rx_max_frames_irq:1; + __u32 tx_usecs:1; + __u32 tx_max_frames:1; + __u32 tx_usecs_irq:1; + __u32 tx_max_frames_irq:1; + __u32 stats_block_usecs:1; + __u32 use_adaptive_rx:1; + __u32 use_adaptive_tx:1; + __u32 pkt_rate_low:1; + __u32 rx_usecs_low:1; + __u32 rx_max_frames_low:1; + __u32 tx_usecs_low:1; + __u32 tx_max_frames_low:1; + __u32 pkt_rate_high:1; + __u32 rx_usecs_high:1; + __u32 rx_max_frames_high:1; + __u32 tx_usecs_high:1; + __u32 tx_max_frames_high:1; + __u32 rate_sample_interval:1; + __u32 use_cqe_mode_tx:1; + __u32 use_cqe_mode_rx:1; + __u32 tx_aggr_max_bytes:1; + __u32 tx_aggr_max_frames:1; + __u32 tx_aggr_time_usecs:1; + } _present; + + struct ethtool_header header; + __u32 rx_usecs; + __u32 rx_max_frames; + __u32 rx_usecs_irq; + __u32 rx_max_frames_irq; + __u32 tx_usecs; + __u32 tx_max_frames; + __u32 tx_usecs_irq; + __u32 tx_max_frames_irq; + __u32 stats_block_usecs; + __u8 use_adaptive_rx; + __u8 use_adaptive_tx; + __u32 pkt_rate_low; + __u32 rx_usecs_low; + __u32 rx_max_frames_low; + __u32 tx_usecs_low; + __u32 tx_max_frames_low; + __u32 pkt_rate_high; + __u32 rx_usecs_high; + __u32 rx_max_frames_high; + __u32 tx_usecs_high; + __u32 tx_max_frames_high; + __u32 rate_sample_interval; + __u8 use_cqe_mode_tx; + __u8 use_cqe_mode_rx; + __u32 tx_aggr_max_bytes; + __u32 tx_aggr_max_frames; + __u32 tx_aggr_time_usecs; +}; + +static inline struct ethtool_coalesce_set_req * +ethtool_coalesce_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_coalesce_set_req)); +} +void ethtool_coalesce_set_req_free(struct ethtool_coalesce_set_req *req); + +static inline void +ethtool_coalesce_set_req_set_header_dev_index(struct ethtool_coalesce_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_coalesce_set_req_set_header_dev_name(struct ethtool_coalesce_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_coalesce_set_req_set_header_flags(struct ethtool_coalesce_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_coalesce_set_req_set_rx_usecs(struct ethtool_coalesce_set_req *req, + __u32 rx_usecs) +{ + req->_present.rx_usecs = 1; + req->rx_usecs = rx_usecs; +} +static inline void +ethtool_coalesce_set_req_set_rx_max_frames(struct ethtool_coalesce_set_req *req, + __u32 rx_max_frames) +{ + req->_present.rx_max_frames = 1; + req->rx_max_frames = rx_max_frames; +} +static inline void +ethtool_coalesce_set_req_set_rx_usecs_irq(struct ethtool_coalesce_set_req *req, + __u32 rx_usecs_irq) +{ + req->_present.rx_usecs_irq = 1; + req->rx_usecs_irq = rx_usecs_irq; +} +static inline void +ethtool_coalesce_set_req_set_rx_max_frames_irq(struct ethtool_coalesce_set_req *req, + __u32 rx_max_frames_irq) +{ + req->_present.rx_max_frames_irq = 1; + req->rx_max_frames_irq = rx_max_frames_irq; +} +static inline void +ethtool_coalesce_set_req_set_tx_usecs(struct ethtool_coalesce_set_req *req, + __u32 tx_usecs) +{ + req->_present.tx_usecs = 1; + req->tx_usecs = tx_usecs; +} +static inline void +ethtool_coalesce_set_req_set_tx_max_frames(struct ethtool_coalesce_set_req *req, + __u32 tx_max_frames) +{ + req->_present.tx_max_frames = 1; + req->tx_max_frames = tx_max_frames; +} +static inline void +ethtool_coalesce_set_req_set_tx_usecs_irq(struct ethtool_coalesce_set_req *req, + __u32 tx_usecs_irq) +{ + req->_present.tx_usecs_irq = 1; + req->tx_usecs_irq = tx_usecs_irq; +} +static inline void +ethtool_coalesce_set_req_set_tx_max_frames_irq(struct ethtool_coalesce_set_req *req, + __u32 tx_max_frames_irq) +{ + req->_present.tx_max_frames_irq = 1; + req->tx_max_frames_irq = tx_max_frames_irq; +} +static inline void +ethtool_coalesce_set_req_set_stats_block_usecs(struct ethtool_coalesce_set_req *req, + __u32 stats_block_usecs) +{ + req->_present.stats_block_usecs = 1; + req->stats_block_usecs = stats_block_usecs; +} +static inline void +ethtool_coalesce_set_req_set_use_adaptive_rx(struct ethtool_coalesce_set_req *req, + __u8 use_adaptive_rx) +{ + req->_present.use_adaptive_rx = 1; + req->use_adaptive_rx = use_adaptive_rx; +} +static inline void +ethtool_coalesce_set_req_set_use_adaptive_tx(struct ethtool_coalesce_set_req *req, + __u8 use_adaptive_tx) +{ + req->_present.use_adaptive_tx = 1; + req->use_adaptive_tx = use_adaptive_tx; +} +static inline void +ethtool_coalesce_set_req_set_pkt_rate_low(struct ethtool_coalesce_set_req *req, + __u32 pkt_rate_low) +{ + req->_present.pkt_rate_low = 1; + req->pkt_rate_low = pkt_rate_low; +} +static inline void +ethtool_coalesce_set_req_set_rx_usecs_low(struct ethtool_coalesce_set_req *req, + __u32 rx_usecs_low) +{ + req->_present.rx_usecs_low = 1; + req->rx_usecs_low = rx_usecs_low; +} +static inline void +ethtool_coalesce_set_req_set_rx_max_frames_low(struct ethtool_coalesce_set_req *req, + __u32 rx_max_frames_low) +{ + req->_present.rx_max_frames_low = 1; + req->rx_max_frames_low = rx_max_frames_low; +} +static inline void +ethtool_coalesce_set_req_set_tx_usecs_low(struct ethtool_coalesce_set_req *req, + __u32 tx_usecs_low) +{ + req->_present.tx_usecs_low = 1; + req->tx_usecs_low = tx_usecs_low; +} +static inline void +ethtool_coalesce_set_req_set_tx_max_frames_low(struct ethtool_coalesce_set_req *req, + __u32 tx_max_frames_low) +{ + req->_present.tx_max_frames_low = 1; + req->tx_max_frames_low = tx_max_frames_low; +} +static inline void +ethtool_coalesce_set_req_set_pkt_rate_high(struct ethtool_coalesce_set_req *req, + __u32 pkt_rate_high) +{ + req->_present.pkt_rate_high = 1; + req->pkt_rate_high = pkt_rate_high; +} +static inline void +ethtool_coalesce_set_req_set_rx_usecs_high(struct ethtool_coalesce_set_req *req, + __u32 rx_usecs_high) +{ + req->_present.rx_usecs_high = 1; + req->rx_usecs_high = rx_usecs_high; +} +static inline void +ethtool_coalesce_set_req_set_rx_max_frames_high(struct ethtool_coalesce_set_req *req, + __u32 rx_max_frames_high) +{ + req->_present.rx_max_frames_high = 1; + req->rx_max_frames_high = rx_max_frames_high; +} +static inline void +ethtool_coalesce_set_req_set_tx_usecs_high(struct ethtool_coalesce_set_req *req, + __u32 tx_usecs_high) +{ + req->_present.tx_usecs_high = 1; + req->tx_usecs_high = tx_usecs_high; +} +static inline void +ethtool_coalesce_set_req_set_tx_max_frames_high(struct ethtool_coalesce_set_req *req, + __u32 tx_max_frames_high) +{ + req->_present.tx_max_frames_high = 1; + req->tx_max_frames_high = tx_max_frames_high; +} +static inline void +ethtool_coalesce_set_req_set_rate_sample_interval(struct ethtool_coalesce_set_req *req, + __u32 rate_sample_interval) +{ + req->_present.rate_sample_interval = 1; + req->rate_sample_interval = rate_sample_interval; +} +static inline void +ethtool_coalesce_set_req_set_use_cqe_mode_tx(struct ethtool_coalesce_set_req *req, + __u8 use_cqe_mode_tx) +{ + req->_present.use_cqe_mode_tx = 1; + req->use_cqe_mode_tx = use_cqe_mode_tx; +} +static inline void +ethtool_coalesce_set_req_set_use_cqe_mode_rx(struct ethtool_coalesce_set_req *req, + __u8 use_cqe_mode_rx) +{ + req->_present.use_cqe_mode_rx = 1; + req->use_cqe_mode_rx = use_cqe_mode_rx; +} +static inline void +ethtool_coalesce_set_req_set_tx_aggr_max_bytes(struct ethtool_coalesce_set_req *req, + __u32 tx_aggr_max_bytes) +{ + req->_present.tx_aggr_max_bytes = 1; + req->tx_aggr_max_bytes = tx_aggr_max_bytes; +} +static inline void +ethtool_coalesce_set_req_set_tx_aggr_max_frames(struct ethtool_coalesce_set_req *req, + __u32 tx_aggr_max_frames) +{ + req->_present.tx_aggr_max_frames = 1; + req->tx_aggr_max_frames = tx_aggr_max_frames; +} +static inline void +ethtool_coalesce_set_req_set_tx_aggr_time_usecs(struct ethtool_coalesce_set_req *req, + __u32 tx_aggr_time_usecs) +{ + req->_present.tx_aggr_time_usecs = 1; + req->tx_aggr_time_usecs = tx_aggr_time_usecs; +} + +/* + * Set coalesce params. + */ +int ethtool_coalesce_set(struct ynl_sock *ys, + struct ethtool_coalesce_set_req *req); + +/* ============== ETHTOOL_MSG_PAUSE_GET ============== */ +/* ETHTOOL_MSG_PAUSE_GET - do */ +struct ethtool_pause_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_pause_get_req *ethtool_pause_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_pause_get_req)); +} +void ethtool_pause_get_req_free(struct ethtool_pause_get_req *req); + +static inline void +ethtool_pause_get_req_set_header_dev_index(struct ethtool_pause_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_pause_get_req_set_header_dev_name(struct ethtool_pause_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_pause_get_req_set_header_flags(struct ethtool_pause_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_pause_get_rsp { + struct { + __u32 header:1; + __u32 autoneg:1; + __u32 rx:1; + __u32 tx:1; + __u32 stats:1; + __u32 stats_src:1; + } _present; + + struct ethtool_header header; + __u8 autoneg; + __u8 rx; + __u8 tx; + struct ethtool_pause_stat stats; + __u32 stats_src; +}; + +void ethtool_pause_get_rsp_free(struct ethtool_pause_get_rsp *rsp); + +/* + * Get pause params. + */ +struct ethtool_pause_get_rsp * +ethtool_pause_get(struct ynl_sock *ys, struct ethtool_pause_get_req *req); + +/* ETHTOOL_MSG_PAUSE_GET - dump */ +struct ethtool_pause_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_pause_get_req_dump * +ethtool_pause_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_pause_get_req_dump)); +} +void ethtool_pause_get_req_dump_free(struct ethtool_pause_get_req_dump *req); + +static inline void +ethtool_pause_get_req_dump_set_header_dev_index(struct ethtool_pause_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_pause_get_req_dump_set_header_dev_name(struct ethtool_pause_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_pause_get_req_dump_set_header_flags(struct ethtool_pause_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_pause_get_list { + struct ethtool_pause_get_list *next; + struct ethtool_pause_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_pause_get_list_free(struct ethtool_pause_get_list *rsp); + +struct ethtool_pause_get_list * +ethtool_pause_get_dump(struct ynl_sock *ys, + struct ethtool_pause_get_req_dump *req); + +/* ETHTOOL_MSG_PAUSE_GET - notify */ +struct ethtool_pause_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_pause_get_ntf *ntf); + struct ethtool_pause_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_pause_get_ntf_free(struct ethtool_pause_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_PAUSE_SET ============== */ +/* ETHTOOL_MSG_PAUSE_SET - do */ +struct ethtool_pause_set_req { + struct { + __u32 header:1; + __u32 autoneg:1; + __u32 rx:1; + __u32 tx:1; + __u32 stats:1; + __u32 stats_src:1; + } _present; + + struct ethtool_header header; + __u8 autoneg; + __u8 rx; + __u8 tx; + struct ethtool_pause_stat stats; + __u32 stats_src; +}; + +static inline struct ethtool_pause_set_req *ethtool_pause_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_pause_set_req)); +} +void ethtool_pause_set_req_free(struct ethtool_pause_set_req *req); + +static inline void +ethtool_pause_set_req_set_header_dev_index(struct ethtool_pause_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_pause_set_req_set_header_dev_name(struct ethtool_pause_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_pause_set_req_set_header_flags(struct ethtool_pause_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_pause_set_req_set_autoneg(struct ethtool_pause_set_req *req, + __u8 autoneg) +{ + req->_present.autoneg = 1; + req->autoneg = autoneg; +} +static inline void +ethtool_pause_set_req_set_rx(struct ethtool_pause_set_req *req, __u8 rx) +{ + req->_present.rx = 1; + req->rx = rx; +} +static inline void +ethtool_pause_set_req_set_tx(struct ethtool_pause_set_req *req, __u8 tx) +{ + req->_present.tx = 1; + req->tx = tx; +} +static inline void +ethtool_pause_set_req_set_stats_tx_frames(struct ethtool_pause_set_req *req, + __u64 tx_frames) +{ + req->_present.stats = 1; + req->stats._present.tx_frames = 1; + req->stats.tx_frames = tx_frames; +} +static inline void +ethtool_pause_set_req_set_stats_rx_frames(struct ethtool_pause_set_req *req, + __u64 rx_frames) +{ + req->_present.stats = 1; + req->stats._present.rx_frames = 1; + req->stats.rx_frames = rx_frames; +} +static inline void +ethtool_pause_set_req_set_stats_src(struct ethtool_pause_set_req *req, + __u32 stats_src) +{ + req->_present.stats_src = 1; + req->stats_src = stats_src; +} + +/* + * Set pause params. + */ +int ethtool_pause_set(struct ynl_sock *ys, struct ethtool_pause_set_req *req); + +/* ============== ETHTOOL_MSG_EEE_GET ============== */ +/* ETHTOOL_MSG_EEE_GET - do */ +struct ethtool_eee_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_eee_get_req *ethtool_eee_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_eee_get_req)); +} +void ethtool_eee_get_req_free(struct ethtool_eee_get_req *req); + +static inline void +ethtool_eee_get_req_set_header_dev_index(struct ethtool_eee_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_eee_get_req_set_header_dev_name(struct ethtool_eee_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_eee_get_req_set_header_flags(struct ethtool_eee_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_eee_get_rsp { + struct { + __u32 header:1; + __u32 modes_ours:1; + __u32 modes_peer:1; + __u32 active:1; + __u32 enabled:1; + __u32 tx_lpi_enabled:1; + __u32 tx_lpi_timer:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset modes_ours; + struct ethtool_bitset modes_peer; + __u8 active; + __u8 enabled; + __u8 tx_lpi_enabled; + __u32 tx_lpi_timer; +}; + +void ethtool_eee_get_rsp_free(struct ethtool_eee_get_rsp *rsp); + +/* + * Get eee params. + */ +struct ethtool_eee_get_rsp * +ethtool_eee_get(struct ynl_sock *ys, struct ethtool_eee_get_req *req); + +/* ETHTOOL_MSG_EEE_GET - dump */ +struct ethtool_eee_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_eee_get_req_dump * +ethtool_eee_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_eee_get_req_dump)); +} +void ethtool_eee_get_req_dump_free(struct ethtool_eee_get_req_dump *req); + +static inline void +ethtool_eee_get_req_dump_set_header_dev_index(struct ethtool_eee_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_eee_get_req_dump_set_header_dev_name(struct ethtool_eee_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_eee_get_req_dump_set_header_flags(struct ethtool_eee_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_eee_get_list { + struct ethtool_eee_get_list *next; + struct ethtool_eee_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_eee_get_list_free(struct ethtool_eee_get_list *rsp); + +struct ethtool_eee_get_list * +ethtool_eee_get_dump(struct ynl_sock *ys, struct ethtool_eee_get_req_dump *req); + +/* ETHTOOL_MSG_EEE_GET - notify */ +struct ethtool_eee_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_eee_get_ntf *ntf); + struct ethtool_eee_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_eee_get_ntf_free(struct ethtool_eee_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_EEE_SET ============== */ +/* ETHTOOL_MSG_EEE_SET - do */ +struct ethtool_eee_set_req { + struct { + __u32 header:1; + __u32 modes_ours:1; + __u32 modes_peer:1; + __u32 active:1; + __u32 enabled:1; + __u32 tx_lpi_enabled:1; + __u32 tx_lpi_timer:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset modes_ours; + struct ethtool_bitset modes_peer; + __u8 active; + __u8 enabled; + __u8 tx_lpi_enabled; + __u32 tx_lpi_timer; +}; + +static inline struct ethtool_eee_set_req *ethtool_eee_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_eee_set_req)); +} +void ethtool_eee_set_req_free(struct ethtool_eee_set_req *req); + +static inline void +ethtool_eee_set_req_set_header_dev_index(struct ethtool_eee_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_eee_set_req_set_header_dev_name(struct ethtool_eee_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_eee_set_req_set_header_flags(struct ethtool_eee_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_eee_set_req_set_modes_ours_nomask(struct ethtool_eee_set_req *req) +{ + req->_present.modes_ours = 1; + req->modes_ours._present.nomask = 1; +} +static inline void +ethtool_eee_set_req_set_modes_ours_size(struct ethtool_eee_set_req *req, + __u32 size) +{ + req->_present.modes_ours = 1; + req->modes_ours._present.size = 1; + req->modes_ours.size = size; +} +static inline void +__ethtool_eee_set_req_set_modes_ours_bits_bit(struct ethtool_eee_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->modes_ours.bits.bit); + req->modes_ours.bits.bit = bit; + req->modes_ours.bits.n_bit = n_bit; +} +static inline void +ethtool_eee_set_req_set_modes_peer_nomask(struct ethtool_eee_set_req *req) +{ + req->_present.modes_peer = 1; + req->modes_peer._present.nomask = 1; +} +static inline void +ethtool_eee_set_req_set_modes_peer_size(struct ethtool_eee_set_req *req, + __u32 size) +{ + req->_present.modes_peer = 1; + req->modes_peer._present.size = 1; + req->modes_peer.size = size; +} +static inline void +__ethtool_eee_set_req_set_modes_peer_bits_bit(struct ethtool_eee_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->modes_peer.bits.bit); + req->modes_peer.bits.bit = bit; + req->modes_peer.bits.n_bit = n_bit; +} +static inline void +ethtool_eee_set_req_set_active(struct ethtool_eee_set_req *req, __u8 active) +{ + req->_present.active = 1; + req->active = active; +} +static inline void +ethtool_eee_set_req_set_enabled(struct ethtool_eee_set_req *req, __u8 enabled) +{ + req->_present.enabled = 1; + req->enabled = enabled; +} +static inline void +ethtool_eee_set_req_set_tx_lpi_enabled(struct ethtool_eee_set_req *req, + __u8 tx_lpi_enabled) +{ + req->_present.tx_lpi_enabled = 1; + req->tx_lpi_enabled = tx_lpi_enabled; +} +static inline void +ethtool_eee_set_req_set_tx_lpi_timer(struct ethtool_eee_set_req *req, + __u32 tx_lpi_timer) +{ + req->_present.tx_lpi_timer = 1; + req->tx_lpi_timer = tx_lpi_timer; +} + +/* + * Set eee params. + */ +int ethtool_eee_set(struct ynl_sock *ys, struct ethtool_eee_set_req *req); + +/* ============== ETHTOOL_MSG_TSINFO_GET ============== */ +/* ETHTOOL_MSG_TSINFO_GET - do */ +struct ethtool_tsinfo_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_tsinfo_get_req *ethtool_tsinfo_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_tsinfo_get_req)); +} +void ethtool_tsinfo_get_req_free(struct ethtool_tsinfo_get_req *req); + +static inline void +ethtool_tsinfo_get_req_set_header_dev_index(struct ethtool_tsinfo_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_tsinfo_get_req_set_header_dev_name(struct ethtool_tsinfo_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_tsinfo_get_req_set_header_flags(struct ethtool_tsinfo_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_tsinfo_get_rsp { + struct { + __u32 header:1; + __u32 timestamping:1; + __u32 tx_types:1; + __u32 rx_filters:1; + __u32 phc_index:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset timestamping; + struct ethtool_bitset tx_types; + struct ethtool_bitset rx_filters; + __u32 phc_index; +}; + +void ethtool_tsinfo_get_rsp_free(struct ethtool_tsinfo_get_rsp *rsp); + +/* + * Get tsinfo params. + */ +struct ethtool_tsinfo_get_rsp * +ethtool_tsinfo_get(struct ynl_sock *ys, struct ethtool_tsinfo_get_req *req); + +/* ETHTOOL_MSG_TSINFO_GET - dump */ +struct ethtool_tsinfo_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_tsinfo_get_req_dump * +ethtool_tsinfo_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_tsinfo_get_req_dump)); +} +void ethtool_tsinfo_get_req_dump_free(struct ethtool_tsinfo_get_req_dump *req); + +static inline void +ethtool_tsinfo_get_req_dump_set_header_dev_index(struct ethtool_tsinfo_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_tsinfo_get_req_dump_set_header_dev_name(struct ethtool_tsinfo_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_tsinfo_get_req_dump_set_header_flags(struct ethtool_tsinfo_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_tsinfo_get_list { + struct ethtool_tsinfo_get_list *next; + struct ethtool_tsinfo_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_tsinfo_get_list_free(struct ethtool_tsinfo_get_list *rsp); + +struct ethtool_tsinfo_get_list * +ethtool_tsinfo_get_dump(struct ynl_sock *ys, + struct ethtool_tsinfo_get_req_dump *req); + +/* ============== ETHTOOL_MSG_CABLE_TEST_ACT ============== */ +/* ETHTOOL_MSG_CABLE_TEST_ACT - do */ +struct ethtool_cable_test_act_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_cable_test_act_req * +ethtool_cable_test_act_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_cable_test_act_req)); +} +void ethtool_cable_test_act_req_free(struct ethtool_cable_test_act_req *req); + +static inline void +ethtool_cable_test_act_req_set_header_dev_index(struct ethtool_cable_test_act_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_cable_test_act_req_set_header_dev_name(struct ethtool_cable_test_act_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_cable_test_act_req_set_header_flags(struct ethtool_cable_test_act_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +/* + * Cable test. + */ +int ethtool_cable_test_act(struct ynl_sock *ys, + struct ethtool_cable_test_act_req *req); + +/* ============== ETHTOOL_MSG_CABLE_TEST_TDR_ACT ============== */ +/* ETHTOOL_MSG_CABLE_TEST_TDR_ACT - do */ +struct ethtool_cable_test_tdr_act_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_cable_test_tdr_act_req * +ethtool_cable_test_tdr_act_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_cable_test_tdr_act_req)); +} +void +ethtool_cable_test_tdr_act_req_free(struct ethtool_cable_test_tdr_act_req *req); + +static inline void +ethtool_cable_test_tdr_act_req_set_header_dev_index(struct ethtool_cable_test_tdr_act_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_cable_test_tdr_act_req_set_header_dev_name(struct ethtool_cable_test_tdr_act_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_cable_test_tdr_act_req_set_header_flags(struct ethtool_cable_test_tdr_act_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +/* + * Cable test TDR. + */ +int ethtool_cable_test_tdr_act(struct ynl_sock *ys, + struct ethtool_cable_test_tdr_act_req *req); + +/* ============== ETHTOOL_MSG_TUNNEL_INFO_GET ============== */ +/* ETHTOOL_MSG_TUNNEL_INFO_GET - do */ +struct ethtool_tunnel_info_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_tunnel_info_get_req * +ethtool_tunnel_info_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_tunnel_info_get_req)); +} +void ethtool_tunnel_info_get_req_free(struct ethtool_tunnel_info_get_req *req); + +static inline void +ethtool_tunnel_info_get_req_set_header_dev_index(struct ethtool_tunnel_info_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_tunnel_info_get_req_set_header_dev_name(struct ethtool_tunnel_info_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_tunnel_info_get_req_set_header_flags(struct ethtool_tunnel_info_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_tunnel_info_get_rsp { + struct { + __u32 header:1; + __u32 udp_ports:1; + } _present; + + struct ethtool_header header; + struct ethtool_tunnel_udp udp_ports; +}; + +void ethtool_tunnel_info_get_rsp_free(struct ethtool_tunnel_info_get_rsp *rsp); + +/* + * Get tsinfo params. + */ +struct ethtool_tunnel_info_get_rsp * +ethtool_tunnel_info_get(struct ynl_sock *ys, + struct ethtool_tunnel_info_get_req *req); + +/* ETHTOOL_MSG_TUNNEL_INFO_GET - dump */ +struct ethtool_tunnel_info_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_tunnel_info_get_req_dump * +ethtool_tunnel_info_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_tunnel_info_get_req_dump)); +} +void +ethtool_tunnel_info_get_req_dump_free(struct ethtool_tunnel_info_get_req_dump *req); + +static inline void +ethtool_tunnel_info_get_req_dump_set_header_dev_index(struct ethtool_tunnel_info_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_tunnel_info_get_req_dump_set_header_dev_name(struct ethtool_tunnel_info_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_tunnel_info_get_req_dump_set_header_flags(struct ethtool_tunnel_info_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_tunnel_info_get_list { + struct ethtool_tunnel_info_get_list *next; + struct ethtool_tunnel_info_get_rsp obj __attribute__ ((aligned (8))); +}; + +void +ethtool_tunnel_info_get_list_free(struct ethtool_tunnel_info_get_list *rsp); + +struct ethtool_tunnel_info_get_list * +ethtool_tunnel_info_get_dump(struct ynl_sock *ys, + struct ethtool_tunnel_info_get_req_dump *req); + +/* ============== ETHTOOL_MSG_FEC_GET ============== */ +/* ETHTOOL_MSG_FEC_GET - do */ +struct ethtool_fec_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_fec_get_req *ethtool_fec_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_fec_get_req)); +} +void ethtool_fec_get_req_free(struct ethtool_fec_get_req *req); + +static inline void +ethtool_fec_get_req_set_header_dev_index(struct ethtool_fec_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_fec_get_req_set_header_dev_name(struct ethtool_fec_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_fec_get_req_set_header_flags(struct ethtool_fec_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_fec_get_rsp { + struct { + __u32 header:1; + __u32 modes:1; + __u32 auto_:1; + __u32 active:1; + __u32 stats:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset modes; + __u8 auto_; + __u32 active; + struct ethtool_fec_stat stats; +}; + +void ethtool_fec_get_rsp_free(struct ethtool_fec_get_rsp *rsp); + +/* + * Get FEC params. + */ +struct ethtool_fec_get_rsp * +ethtool_fec_get(struct ynl_sock *ys, struct ethtool_fec_get_req *req); + +/* ETHTOOL_MSG_FEC_GET - dump */ +struct ethtool_fec_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_fec_get_req_dump * +ethtool_fec_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_fec_get_req_dump)); +} +void ethtool_fec_get_req_dump_free(struct ethtool_fec_get_req_dump *req); + +static inline void +ethtool_fec_get_req_dump_set_header_dev_index(struct ethtool_fec_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_fec_get_req_dump_set_header_dev_name(struct ethtool_fec_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_fec_get_req_dump_set_header_flags(struct ethtool_fec_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_fec_get_list { + struct ethtool_fec_get_list *next; + struct ethtool_fec_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_fec_get_list_free(struct ethtool_fec_get_list *rsp); + +struct ethtool_fec_get_list * +ethtool_fec_get_dump(struct ynl_sock *ys, struct ethtool_fec_get_req_dump *req); + +/* ETHTOOL_MSG_FEC_GET - notify */ +struct ethtool_fec_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_fec_get_ntf *ntf); + struct ethtool_fec_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_fec_get_ntf_free(struct ethtool_fec_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_FEC_SET ============== */ +/* ETHTOOL_MSG_FEC_SET - do */ +struct ethtool_fec_set_req { + struct { + __u32 header:1; + __u32 modes:1; + __u32 auto_:1; + __u32 active:1; + __u32 stats:1; + } _present; + + struct ethtool_header header; + struct ethtool_bitset modes; + __u8 auto_; + __u32 active; + struct ethtool_fec_stat stats; +}; + +static inline struct ethtool_fec_set_req *ethtool_fec_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_fec_set_req)); +} +void ethtool_fec_set_req_free(struct ethtool_fec_set_req *req); + +static inline void +ethtool_fec_set_req_set_header_dev_index(struct ethtool_fec_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_fec_set_req_set_header_dev_name(struct ethtool_fec_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_fec_set_req_set_header_flags(struct ethtool_fec_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_fec_set_req_set_modes_nomask(struct ethtool_fec_set_req *req) +{ + req->_present.modes = 1; + req->modes._present.nomask = 1; +} +static inline void +ethtool_fec_set_req_set_modes_size(struct ethtool_fec_set_req *req, __u32 size) +{ + req->_present.modes = 1; + req->modes._present.size = 1; + req->modes.size = size; +} +static inline void +__ethtool_fec_set_req_set_modes_bits_bit(struct ethtool_fec_set_req *req, + struct ethtool_bitset_bit *bit, + unsigned int n_bit) +{ + free(req->modes.bits.bit); + req->modes.bits.bit = bit; + req->modes.bits.n_bit = n_bit; +} +static inline void +ethtool_fec_set_req_set_auto_(struct ethtool_fec_set_req *req, __u8 auto_) +{ + req->_present.auto_ = 1; + req->auto_ = auto_; +} +static inline void +ethtool_fec_set_req_set_active(struct ethtool_fec_set_req *req, __u32 active) +{ + req->_present.active = 1; + req->active = active; +} +static inline void +ethtool_fec_set_req_set_stats_corrected(struct ethtool_fec_set_req *req, + const void *corrected, size_t len) +{ + free(req->stats.corrected); + req->stats.corrected = malloc(req->stats._present.corrected_len); + memcpy(req->stats.corrected, corrected, req->stats._present.corrected_len); +} +static inline void +ethtool_fec_set_req_set_stats_uncorr(struct ethtool_fec_set_req *req, + const void *uncorr, size_t len) +{ + free(req->stats.uncorr); + req->stats.uncorr = malloc(req->stats._present.uncorr_len); + memcpy(req->stats.uncorr, uncorr, req->stats._present.uncorr_len); +} +static inline void +ethtool_fec_set_req_set_stats_corr_bits(struct ethtool_fec_set_req *req, + const void *corr_bits, size_t len) +{ + free(req->stats.corr_bits); + req->stats.corr_bits = malloc(req->stats._present.corr_bits_len); + memcpy(req->stats.corr_bits, corr_bits, req->stats._present.corr_bits_len); +} + +/* + * Set FEC params. + */ +int ethtool_fec_set(struct ynl_sock *ys, struct ethtool_fec_set_req *req); + +/* ============== ETHTOOL_MSG_MODULE_EEPROM_GET ============== */ +/* ETHTOOL_MSG_MODULE_EEPROM_GET - do */ +struct ethtool_module_eeprom_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_module_eeprom_get_req * +ethtool_module_eeprom_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_module_eeprom_get_req)); +} +void +ethtool_module_eeprom_get_req_free(struct ethtool_module_eeprom_get_req *req); + +static inline void +ethtool_module_eeprom_get_req_set_header_dev_index(struct ethtool_module_eeprom_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_module_eeprom_get_req_set_header_dev_name(struct ethtool_module_eeprom_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_module_eeprom_get_req_set_header_flags(struct ethtool_module_eeprom_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_module_eeprom_get_rsp { + struct { + __u32 header:1; + __u32 offset:1; + __u32 length:1; + __u32 page:1; + __u32 bank:1; + __u32 i2c_address:1; + __u32 data_len; + } _present; + + struct ethtool_header header; + __u32 offset; + __u32 length; + __u8 page; + __u8 bank; + __u8 i2c_address; + void *data; +}; + +void +ethtool_module_eeprom_get_rsp_free(struct ethtool_module_eeprom_get_rsp *rsp); + +/* + * Get module EEPROM params. + */ +struct ethtool_module_eeprom_get_rsp * +ethtool_module_eeprom_get(struct ynl_sock *ys, + struct ethtool_module_eeprom_get_req *req); + +/* ETHTOOL_MSG_MODULE_EEPROM_GET - dump */ +struct ethtool_module_eeprom_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_module_eeprom_get_req_dump * +ethtool_module_eeprom_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_module_eeprom_get_req_dump)); +} +void +ethtool_module_eeprom_get_req_dump_free(struct ethtool_module_eeprom_get_req_dump *req); + +static inline void +ethtool_module_eeprom_get_req_dump_set_header_dev_index(struct ethtool_module_eeprom_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_module_eeprom_get_req_dump_set_header_dev_name(struct ethtool_module_eeprom_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_module_eeprom_get_req_dump_set_header_flags(struct ethtool_module_eeprom_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_module_eeprom_get_list { + struct ethtool_module_eeprom_get_list *next; + struct ethtool_module_eeprom_get_rsp obj __attribute__ ((aligned (8))); +}; + +void +ethtool_module_eeprom_get_list_free(struct ethtool_module_eeprom_get_list *rsp); + +struct ethtool_module_eeprom_get_list * +ethtool_module_eeprom_get_dump(struct ynl_sock *ys, + struct ethtool_module_eeprom_get_req_dump *req); + +/* ============== ETHTOOL_MSG_PHC_VCLOCKS_GET ============== */ +/* ETHTOOL_MSG_PHC_VCLOCKS_GET - do */ +struct ethtool_phc_vclocks_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_phc_vclocks_get_req * +ethtool_phc_vclocks_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_phc_vclocks_get_req)); +} +void ethtool_phc_vclocks_get_req_free(struct ethtool_phc_vclocks_get_req *req); + +static inline void +ethtool_phc_vclocks_get_req_set_header_dev_index(struct ethtool_phc_vclocks_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_phc_vclocks_get_req_set_header_dev_name(struct ethtool_phc_vclocks_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_phc_vclocks_get_req_set_header_flags(struct ethtool_phc_vclocks_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_phc_vclocks_get_rsp { + struct { + __u32 header:1; + __u32 num:1; + } _present; + + struct ethtool_header header; + __u32 num; +}; + +void ethtool_phc_vclocks_get_rsp_free(struct ethtool_phc_vclocks_get_rsp *rsp); + +/* + * Get PHC VCLOCKs. + */ +struct ethtool_phc_vclocks_get_rsp * +ethtool_phc_vclocks_get(struct ynl_sock *ys, + struct ethtool_phc_vclocks_get_req *req); + +/* ETHTOOL_MSG_PHC_VCLOCKS_GET - dump */ +struct ethtool_phc_vclocks_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_phc_vclocks_get_req_dump * +ethtool_phc_vclocks_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_phc_vclocks_get_req_dump)); +} +void +ethtool_phc_vclocks_get_req_dump_free(struct ethtool_phc_vclocks_get_req_dump *req); + +static inline void +ethtool_phc_vclocks_get_req_dump_set_header_dev_index(struct ethtool_phc_vclocks_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_phc_vclocks_get_req_dump_set_header_dev_name(struct ethtool_phc_vclocks_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_phc_vclocks_get_req_dump_set_header_flags(struct ethtool_phc_vclocks_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_phc_vclocks_get_list { + struct ethtool_phc_vclocks_get_list *next; + struct ethtool_phc_vclocks_get_rsp obj __attribute__ ((aligned (8))); +}; + +void +ethtool_phc_vclocks_get_list_free(struct ethtool_phc_vclocks_get_list *rsp); + +struct ethtool_phc_vclocks_get_list * +ethtool_phc_vclocks_get_dump(struct ynl_sock *ys, + struct ethtool_phc_vclocks_get_req_dump *req); + +/* ============== ETHTOOL_MSG_MODULE_GET ============== */ +/* ETHTOOL_MSG_MODULE_GET - do */ +struct ethtool_module_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_module_get_req *ethtool_module_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_module_get_req)); +} +void ethtool_module_get_req_free(struct ethtool_module_get_req *req); + +static inline void +ethtool_module_get_req_set_header_dev_index(struct ethtool_module_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_module_get_req_set_header_dev_name(struct ethtool_module_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_module_get_req_set_header_flags(struct ethtool_module_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_module_get_rsp { + struct { + __u32 header:1; + __u32 power_mode_policy:1; + __u32 power_mode:1; + } _present; + + struct ethtool_header header; + __u8 power_mode_policy; + __u8 power_mode; +}; + +void ethtool_module_get_rsp_free(struct ethtool_module_get_rsp *rsp); + +/* + * Get module params. + */ +struct ethtool_module_get_rsp * +ethtool_module_get(struct ynl_sock *ys, struct ethtool_module_get_req *req); + +/* ETHTOOL_MSG_MODULE_GET - dump */ +struct ethtool_module_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_module_get_req_dump * +ethtool_module_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_module_get_req_dump)); +} +void ethtool_module_get_req_dump_free(struct ethtool_module_get_req_dump *req); + +static inline void +ethtool_module_get_req_dump_set_header_dev_index(struct ethtool_module_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_module_get_req_dump_set_header_dev_name(struct ethtool_module_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_module_get_req_dump_set_header_flags(struct ethtool_module_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_module_get_list { + struct ethtool_module_get_list *next; + struct ethtool_module_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_module_get_list_free(struct ethtool_module_get_list *rsp); + +struct ethtool_module_get_list * +ethtool_module_get_dump(struct ynl_sock *ys, + struct ethtool_module_get_req_dump *req); + +/* ETHTOOL_MSG_MODULE_GET - notify */ +struct ethtool_module_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_module_get_ntf *ntf); + struct ethtool_module_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_module_get_ntf_free(struct ethtool_module_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_MODULE_SET ============== */ +/* ETHTOOL_MSG_MODULE_SET - do */ +struct ethtool_module_set_req { + struct { + __u32 header:1; + __u32 power_mode_policy:1; + __u32 power_mode:1; + } _present; + + struct ethtool_header header; + __u8 power_mode_policy; + __u8 power_mode; +}; + +static inline struct ethtool_module_set_req *ethtool_module_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_module_set_req)); +} +void ethtool_module_set_req_free(struct ethtool_module_set_req *req); + +static inline void +ethtool_module_set_req_set_header_dev_index(struct ethtool_module_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_module_set_req_set_header_dev_name(struct ethtool_module_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_module_set_req_set_header_flags(struct ethtool_module_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_module_set_req_set_power_mode_policy(struct ethtool_module_set_req *req, + __u8 power_mode_policy) +{ + req->_present.power_mode_policy = 1; + req->power_mode_policy = power_mode_policy; +} +static inline void +ethtool_module_set_req_set_power_mode(struct ethtool_module_set_req *req, + __u8 power_mode) +{ + req->_present.power_mode = 1; + req->power_mode = power_mode; +} + +/* + * Set module params. + */ +int ethtool_module_set(struct ynl_sock *ys, struct ethtool_module_set_req *req); + +/* ============== ETHTOOL_MSG_PSE_GET ============== */ +/* ETHTOOL_MSG_PSE_GET - do */ +struct ethtool_pse_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_pse_get_req *ethtool_pse_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_pse_get_req)); +} +void ethtool_pse_get_req_free(struct ethtool_pse_get_req *req); + +static inline void +ethtool_pse_get_req_set_header_dev_index(struct ethtool_pse_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_pse_get_req_set_header_dev_name(struct ethtool_pse_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_pse_get_req_set_header_flags(struct ethtool_pse_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_pse_get_rsp { + struct { + __u32 header:1; + __u32 admin_state:1; + __u32 admin_control:1; + __u32 pw_d_status:1; + } _present; + + struct ethtool_header header; + __u32 admin_state; + __u32 admin_control; + __u32 pw_d_status; +}; + +void ethtool_pse_get_rsp_free(struct ethtool_pse_get_rsp *rsp); + +/* + * Get Power Sourcing Equipment params. + */ +struct ethtool_pse_get_rsp * +ethtool_pse_get(struct ynl_sock *ys, struct ethtool_pse_get_req *req); + +/* ETHTOOL_MSG_PSE_GET - dump */ +struct ethtool_pse_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_pse_get_req_dump * +ethtool_pse_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_pse_get_req_dump)); +} +void ethtool_pse_get_req_dump_free(struct ethtool_pse_get_req_dump *req); + +static inline void +ethtool_pse_get_req_dump_set_header_dev_index(struct ethtool_pse_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_pse_get_req_dump_set_header_dev_name(struct ethtool_pse_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_pse_get_req_dump_set_header_flags(struct ethtool_pse_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_pse_get_list { + struct ethtool_pse_get_list *next; + struct ethtool_pse_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_pse_get_list_free(struct ethtool_pse_get_list *rsp); + +struct ethtool_pse_get_list * +ethtool_pse_get_dump(struct ynl_sock *ys, struct ethtool_pse_get_req_dump *req); + +/* ============== ETHTOOL_MSG_PSE_SET ============== */ +/* ETHTOOL_MSG_PSE_SET - do */ +struct ethtool_pse_set_req { + struct { + __u32 header:1; + __u32 admin_state:1; + __u32 admin_control:1; + __u32 pw_d_status:1; + } _present; + + struct ethtool_header header; + __u32 admin_state; + __u32 admin_control; + __u32 pw_d_status; +}; + +static inline struct ethtool_pse_set_req *ethtool_pse_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_pse_set_req)); +} +void ethtool_pse_set_req_free(struct ethtool_pse_set_req *req); + +static inline void +ethtool_pse_set_req_set_header_dev_index(struct ethtool_pse_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_pse_set_req_set_header_dev_name(struct ethtool_pse_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_pse_set_req_set_header_flags(struct ethtool_pse_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_pse_set_req_set_admin_state(struct ethtool_pse_set_req *req, + __u32 admin_state) +{ + req->_present.admin_state = 1; + req->admin_state = admin_state; +} +static inline void +ethtool_pse_set_req_set_admin_control(struct ethtool_pse_set_req *req, + __u32 admin_control) +{ + req->_present.admin_control = 1; + req->admin_control = admin_control; +} +static inline void +ethtool_pse_set_req_set_pw_d_status(struct ethtool_pse_set_req *req, + __u32 pw_d_status) +{ + req->_present.pw_d_status = 1; + req->pw_d_status = pw_d_status; +} + +/* + * Set Power Sourcing Equipment params. + */ +int ethtool_pse_set(struct ynl_sock *ys, struct ethtool_pse_set_req *req); + +/* ============== ETHTOOL_MSG_RSS_GET ============== */ +/* ETHTOOL_MSG_RSS_GET - do */ +struct ethtool_rss_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_rss_get_req *ethtool_rss_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_rss_get_req)); +} +void ethtool_rss_get_req_free(struct ethtool_rss_get_req *req); + +static inline void +ethtool_rss_get_req_set_header_dev_index(struct ethtool_rss_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_rss_get_req_set_header_dev_name(struct ethtool_rss_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_rss_get_req_set_header_flags(struct ethtool_rss_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_rss_get_rsp { + struct { + __u32 header:1; + __u32 context:1; + __u32 hfunc:1; + __u32 indir_len; + __u32 hkey_len; + } _present; + + struct ethtool_header header; + __u32 context; + __u32 hfunc; + void *indir; + void *hkey; +}; + +void ethtool_rss_get_rsp_free(struct ethtool_rss_get_rsp *rsp); + +/* + * Get RSS params. + */ +struct ethtool_rss_get_rsp * +ethtool_rss_get(struct ynl_sock *ys, struct ethtool_rss_get_req *req); + +/* ETHTOOL_MSG_RSS_GET - dump */ +struct ethtool_rss_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_rss_get_req_dump * +ethtool_rss_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_rss_get_req_dump)); +} +void ethtool_rss_get_req_dump_free(struct ethtool_rss_get_req_dump *req); + +static inline void +ethtool_rss_get_req_dump_set_header_dev_index(struct ethtool_rss_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_rss_get_req_dump_set_header_dev_name(struct ethtool_rss_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_rss_get_req_dump_set_header_flags(struct ethtool_rss_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_rss_get_list { + struct ethtool_rss_get_list *next; + struct ethtool_rss_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_rss_get_list_free(struct ethtool_rss_get_list *rsp); + +struct ethtool_rss_get_list * +ethtool_rss_get_dump(struct ynl_sock *ys, struct ethtool_rss_get_req_dump *req); + +/* ============== ETHTOOL_MSG_PLCA_GET_CFG ============== */ +/* ETHTOOL_MSG_PLCA_GET_CFG - do */ +struct ethtool_plca_get_cfg_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_plca_get_cfg_req * +ethtool_plca_get_cfg_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_plca_get_cfg_req)); +} +void ethtool_plca_get_cfg_req_free(struct ethtool_plca_get_cfg_req *req); + +static inline void +ethtool_plca_get_cfg_req_set_header_dev_index(struct ethtool_plca_get_cfg_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_plca_get_cfg_req_set_header_dev_name(struct ethtool_plca_get_cfg_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_plca_get_cfg_req_set_header_flags(struct ethtool_plca_get_cfg_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_plca_get_cfg_rsp { + struct { + __u32 header:1; + __u32 version:1; + __u32 enabled:1; + __u32 status:1; + __u32 node_cnt:1; + __u32 node_id:1; + __u32 to_tmr:1; + __u32 burst_cnt:1; + __u32 burst_tmr:1; + } _present; + + struct ethtool_header header; + __u16 version; + __u8 enabled; + __u8 status; + __u32 node_cnt; + __u32 node_id; + __u32 to_tmr; + __u32 burst_cnt; + __u32 burst_tmr; +}; + +void ethtool_plca_get_cfg_rsp_free(struct ethtool_plca_get_cfg_rsp *rsp); + +/* + * Get PLCA params. + */ +struct ethtool_plca_get_cfg_rsp * +ethtool_plca_get_cfg(struct ynl_sock *ys, struct ethtool_plca_get_cfg_req *req); + +/* ETHTOOL_MSG_PLCA_GET_CFG - dump */ +struct ethtool_plca_get_cfg_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_plca_get_cfg_req_dump * +ethtool_plca_get_cfg_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_plca_get_cfg_req_dump)); +} +void +ethtool_plca_get_cfg_req_dump_free(struct ethtool_plca_get_cfg_req_dump *req); + +static inline void +ethtool_plca_get_cfg_req_dump_set_header_dev_index(struct ethtool_plca_get_cfg_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_plca_get_cfg_req_dump_set_header_dev_name(struct ethtool_plca_get_cfg_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_plca_get_cfg_req_dump_set_header_flags(struct ethtool_plca_get_cfg_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_plca_get_cfg_list { + struct ethtool_plca_get_cfg_list *next; + struct ethtool_plca_get_cfg_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_plca_get_cfg_list_free(struct ethtool_plca_get_cfg_list *rsp); + +struct ethtool_plca_get_cfg_list * +ethtool_plca_get_cfg_dump(struct ynl_sock *ys, + struct ethtool_plca_get_cfg_req_dump *req); + +/* ETHTOOL_MSG_PLCA_GET_CFG - notify */ +struct ethtool_plca_get_cfg_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_plca_get_cfg_ntf *ntf); + struct ethtool_plca_get_cfg_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_plca_get_cfg_ntf_free(struct ethtool_plca_get_cfg_ntf *rsp); + +/* ============== ETHTOOL_MSG_PLCA_SET_CFG ============== */ +/* ETHTOOL_MSG_PLCA_SET_CFG - do */ +struct ethtool_plca_set_cfg_req { + struct { + __u32 header:1; + __u32 version:1; + __u32 enabled:1; + __u32 status:1; + __u32 node_cnt:1; + __u32 node_id:1; + __u32 to_tmr:1; + __u32 burst_cnt:1; + __u32 burst_tmr:1; + } _present; + + struct ethtool_header header; + __u16 version; + __u8 enabled; + __u8 status; + __u32 node_cnt; + __u32 node_id; + __u32 to_tmr; + __u32 burst_cnt; + __u32 burst_tmr; +}; + +static inline struct ethtool_plca_set_cfg_req * +ethtool_plca_set_cfg_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_plca_set_cfg_req)); +} +void ethtool_plca_set_cfg_req_free(struct ethtool_plca_set_cfg_req *req); + +static inline void +ethtool_plca_set_cfg_req_set_header_dev_index(struct ethtool_plca_set_cfg_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_plca_set_cfg_req_set_header_dev_name(struct ethtool_plca_set_cfg_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_plca_set_cfg_req_set_header_flags(struct ethtool_plca_set_cfg_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_plca_set_cfg_req_set_version(struct ethtool_plca_set_cfg_req *req, + __u16 version) +{ + req->_present.version = 1; + req->version = version; +} +static inline void +ethtool_plca_set_cfg_req_set_enabled(struct ethtool_plca_set_cfg_req *req, + __u8 enabled) +{ + req->_present.enabled = 1; + req->enabled = enabled; +} +static inline void +ethtool_plca_set_cfg_req_set_status(struct ethtool_plca_set_cfg_req *req, + __u8 status) +{ + req->_present.status = 1; + req->status = status; +} +static inline void +ethtool_plca_set_cfg_req_set_node_cnt(struct ethtool_plca_set_cfg_req *req, + __u32 node_cnt) +{ + req->_present.node_cnt = 1; + req->node_cnt = node_cnt; +} +static inline void +ethtool_plca_set_cfg_req_set_node_id(struct ethtool_plca_set_cfg_req *req, + __u32 node_id) +{ + req->_present.node_id = 1; + req->node_id = node_id; +} +static inline void +ethtool_plca_set_cfg_req_set_to_tmr(struct ethtool_plca_set_cfg_req *req, + __u32 to_tmr) +{ + req->_present.to_tmr = 1; + req->to_tmr = to_tmr; +} +static inline void +ethtool_plca_set_cfg_req_set_burst_cnt(struct ethtool_plca_set_cfg_req *req, + __u32 burst_cnt) +{ + req->_present.burst_cnt = 1; + req->burst_cnt = burst_cnt; +} +static inline void +ethtool_plca_set_cfg_req_set_burst_tmr(struct ethtool_plca_set_cfg_req *req, + __u32 burst_tmr) +{ + req->_present.burst_tmr = 1; + req->burst_tmr = burst_tmr; +} + +/* + * Set PLCA params. + */ +int ethtool_plca_set_cfg(struct ynl_sock *ys, + struct ethtool_plca_set_cfg_req *req); + +/* ============== ETHTOOL_MSG_PLCA_GET_STATUS ============== */ +/* ETHTOOL_MSG_PLCA_GET_STATUS - do */ +struct ethtool_plca_get_status_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_plca_get_status_req * +ethtool_plca_get_status_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_plca_get_status_req)); +} +void ethtool_plca_get_status_req_free(struct ethtool_plca_get_status_req *req); + +static inline void +ethtool_plca_get_status_req_set_header_dev_index(struct ethtool_plca_get_status_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_plca_get_status_req_set_header_dev_name(struct ethtool_plca_get_status_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_plca_get_status_req_set_header_flags(struct ethtool_plca_get_status_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_plca_get_status_rsp { + struct { + __u32 header:1; + __u32 version:1; + __u32 enabled:1; + __u32 status:1; + __u32 node_cnt:1; + __u32 node_id:1; + __u32 to_tmr:1; + __u32 burst_cnt:1; + __u32 burst_tmr:1; + } _present; + + struct ethtool_header header; + __u16 version; + __u8 enabled; + __u8 status; + __u32 node_cnt; + __u32 node_id; + __u32 to_tmr; + __u32 burst_cnt; + __u32 burst_tmr; +}; + +void ethtool_plca_get_status_rsp_free(struct ethtool_plca_get_status_rsp *rsp); + +/* + * Get PLCA status params. + */ +struct ethtool_plca_get_status_rsp * +ethtool_plca_get_status(struct ynl_sock *ys, + struct ethtool_plca_get_status_req *req); + +/* ETHTOOL_MSG_PLCA_GET_STATUS - dump */ +struct ethtool_plca_get_status_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_plca_get_status_req_dump * +ethtool_plca_get_status_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_plca_get_status_req_dump)); +} +void +ethtool_plca_get_status_req_dump_free(struct ethtool_plca_get_status_req_dump *req); + +static inline void +ethtool_plca_get_status_req_dump_set_header_dev_index(struct ethtool_plca_get_status_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_plca_get_status_req_dump_set_header_dev_name(struct ethtool_plca_get_status_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_plca_get_status_req_dump_set_header_flags(struct ethtool_plca_get_status_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_plca_get_status_list { + struct ethtool_plca_get_status_list *next; + struct ethtool_plca_get_status_rsp obj __attribute__ ((aligned (8))); +}; + +void +ethtool_plca_get_status_list_free(struct ethtool_plca_get_status_list *rsp); + +struct ethtool_plca_get_status_list * +ethtool_plca_get_status_dump(struct ynl_sock *ys, + struct ethtool_plca_get_status_req_dump *req); + +/* ============== ETHTOOL_MSG_MM_GET ============== */ +/* ETHTOOL_MSG_MM_GET - do */ +struct ethtool_mm_get_req { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_mm_get_req *ethtool_mm_get_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_mm_get_req)); +} +void ethtool_mm_get_req_free(struct ethtool_mm_get_req *req); + +static inline void +ethtool_mm_get_req_set_header_dev_index(struct ethtool_mm_get_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_mm_get_req_set_header_dev_name(struct ethtool_mm_get_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_mm_get_req_set_header_flags(struct ethtool_mm_get_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_mm_get_rsp { + struct { + __u32 header:1; + __u32 pmac_enabled:1; + __u32 tx_enabled:1; + __u32 tx_active:1; + __u32 tx_min_frag_size:1; + __u32 rx_min_frag_size:1; + __u32 verify_enabled:1; + __u32 verify_time:1; + __u32 max_verify_time:1; + __u32 stats:1; + } _present; + + struct ethtool_header header; + __u8 pmac_enabled; + __u8 tx_enabled; + __u8 tx_active; + __u32 tx_min_frag_size; + __u32 rx_min_frag_size; + __u8 verify_enabled; + __u32 verify_time; + __u32 max_verify_time; + struct ethtool_mm_stat stats; +}; + +void ethtool_mm_get_rsp_free(struct ethtool_mm_get_rsp *rsp); + +/* + * Get MAC Merge configuration and state + */ +struct ethtool_mm_get_rsp * +ethtool_mm_get(struct ynl_sock *ys, struct ethtool_mm_get_req *req); + +/* ETHTOOL_MSG_MM_GET - dump */ +struct ethtool_mm_get_req_dump { + struct { + __u32 header:1; + } _present; + + struct ethtool_header header; +}; + +static inline struct ethtool_mm_get_req_dump * +ethtool_mm_get_req_dump_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_mm_get_req_dump)); +} +void ethtool_mm_get_req_dump_free(struct ethtool_mm_get_req_dump *req); + +static inline void +ethtool_mm_get_req_dump_set_header_dev_index(struct ethtool_mm_get_req_dump *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_mm_get_req_dump_set_header_dev_name(struct ethtool_mm_get_req_dump *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_mm_get_req_dump_set_header_flags(struct ethtool_mm_get_req_dump *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} + +struct ethtool_mm_get_list { + struct ethtool_mm_get_list *next; + struct ethtool_mm_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_mm_get_list_free(struct ethtool_mm_get_list *rsp); + +struct ethtool_mm_get_list * +ethtool_mm_get_dump(struct ynl_sock *ys, struct ethtool_mm_get_req_dump *req); + +/* ETHTOOL_MSG_MM_GET - notify */ +struct ethtool_mm_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_mm_get_ntf *ntf); + struct ethtool_mm_get_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_mm_get_ntf_free(struct ethtool_mm_get_ntf *rsp); + +/* ============== ETHTOOL_MSG_MM_SET ============== */ +/* ETHTOOL_MSG_MM_SET - do */ +struct ethtool_mm_set_req { + struct { + __u32 header:1; + __u32 verify_enabled:1; + __u32 verify_time:1; + __u32 tx_enabled:1; + __u32 pmac_enabled:1; + __u32 tx_min_frag_size:1; + } _present; + + struct ethtool_header header; + __u8 verify_enabled; + __u32 verify_time; + __u8 tx_enabled; + __u8 pmac_enabled; + __u32 tx_min_frag_size; +}; + +static inline struct ethtool_mm_set_req *ethtool_mm_set_req_alloc(void) +{ + return calloc(1, sizeof(struct ethtool_mm_set_req)); +} +void ethtool_mm_set_req_free(struct ethtool_mm_set_req *req); + +static inline void +ethtool_mm_set_req_set_header_dev_index(struct ethtool_mm_set_req *req, + __u32 dev_index) +{ + req->_present.header = 1; + req->header._present.dev_index = 1; + req->header.dev_index = dev_index; +} +static inline void +ethtool_mm_set_req_set_header_dev_name(struct ethtool_mm_set_req *req, + const char *dev_name) +{ + free(req->header.dev_name); + req->header._present.dev_name_len = strlen(dev_name); + req->header.dev_name = malloc(req->header._present.dev_name_len + 1); + memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len); + req->header.dev_name[req->header._present.dev_name_len] = 0; +} +static inline void +ethtool_mm_set_req_set_header_flags(struct ethtool_mm_set_req *req, + __u32 flags) +{ + req->_present.header = 1; + req->header._present.flags = 1; + req->header.flags = flags; +} +static inline void +ethtool_mm_set_req_set_verify_enabled(struct ethtool_mm_set_req *req, + __u8 verify_enabled) +{ + req->_present.verify_enabled = 1; + req->verify_enabled = verify_enabled; +} +static inline void +ethtool_mm_set_req_set_verify_time(struct ethtool_mm_set_req *req, + __u32 verify_time) +{ + req->_present.verify_time = 1; + req->verify_time = verify_time; +} +static inline void +ethtool_mm_set_req_set_tx_enabled(struct ethtool_mm_set_req *req, + __u8 tx_enabled) +{ + req->_present.tx_enabled = 1; + req->tx_enabled = tx_enabled; +} +static inline void +ethtool_mm_set_req_set_pmac_enabled(struct ethtool_mm_set_req *req, + __u8 pmac_enabled) +{ + req->_present.pmac_enabled = 1; + req->pmac_enabled = pmac_enabled; +} +static inline void +ethtool_mm_set_req_set_tx_min_frag_size(struct ethtool_mm_set_req *req, + __u32 tx_min_frag_size) +{ + req->_present.tx_min_frag_size = 1; + req->tx_min_frag_size = tx_min_frag_size; +} + +/* + * Set MAC Merge configuration + */ +int ethtool_mm_set(struct ynl_sock *ys, struct ethtool_mm_set_req *req); + +/* ETHTOOL_MSG_CABLE_TEST_NTF - event */ +struct ethtool_cable_test_ntf_rsp { + struct { + __u32 header:1; + __u32 status:1; + } _present; + + struct ethtool_header header; + __u8 status; +}; + +struct ethtool_cable_test_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_cable_test_ntf *ntf); + struct ethtool_cable_test_ntf_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_cable_test_ntf_free(struct ethtool_cable_test_ntf *rsp); + +/* ETHTOOL_MSG_CABLE_TEST_TDR_NTF - event */ +struct ethtool_cable_test_tdr_ntf_rsp { + struct { + __u32 header:1; + __u32 status:1; + __u32 nest:1; + } _present; + + struct ethtool_header header; + __u8 status; + struct ethtool_cable_nest nest; +}; + +struct ethtool_cable_test_tdr_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ethtool_cable_test_tdr_ntf *ntf); + struct ethtool_cable_test_tdr_ntf_rsp obj __attribute__ ((aligned (8))); +}; + +void ethtool_cable_test_tdr_ntf_free(struct ethtool_cable_test_tdr_ntf *rsp); + +#endif /* _LINUX_ETHTOOL_GEN_H */ diff --git a/tools/net/ynl/generated/fou-user.c b/tools/net/ynl/generated/fou-user.c new file mode 100644 index 000000000000..4271b5d43c58 --- /dev/null +++ b/tools/net/ynl/generated/fou-user.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/fou.yaml */ +/* YNL-GEN user source */ + +#include <stdlib.h> +#include <string.h> +#include "fou-user.h" +#include "ynl.h" +#include <linux/fou.h> + +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> + +/* Enums */ +static const char * const fou_op_strmap[] = { + [FOU_CMD_ADD] = "add", + [FOU_CMD_DEL] = "del", + [FOU_CMD_GET] = "get", +}; + +const char *fou_op_str(int op) +{ + if (op < 0 || op >= (int)MNL_ARRAY_SIZE(fou_op_strmap)) + return NULL; + return fou_op_strmap[op]; +} + +static const char * const fou_encap_type_strmap[] = { + [0] = "unspec", + [1] = "direct", + [2] = "gue", +}; + +const char *fou_encap_type_str(int value) +{ + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(fou_encap_type_strmap)) + return NULL; + return fou_encap_type_strmap[value]; +} + +/* Policies */ +struct ynl_policy_attr fou_policy[FOU_ATTR_MAX + 1] = { + [FOU_ATTR_UNSPEC] = { .name = "unspec", .type = YNL_PT_REJECT, }, + [FOU_ATTR_PORT] = { .name = "port", .type = YNL_PT_U16, }, + [FOU_ATTR_AF] = { .name = "af", .type = YNL_PT_U8, }, + [FOU_ATTR_IPPROTO] = { .name = "ipproto", .type = YNL_PT_U8, }, + [FOU_ATTR_TYPE] = { .name = "type", .type = YNL_PT_U8, }, + [FOU_ATTR_REMCSUM_NOPARTIAL] = { .name = "remcsum_nopartial", .type = YNL_PT_FLAG, }, + [FOU_ATTR_LOCAL_V4] = { .name = "local_v4", .type = YNL_PT_U32, }, + [FOU_ATTR_LOCAL_V6] = { .name = "local_v6", .type = YNL_PT_BINARY,}, + [FOU_ATTR_PEER_V4] = { .name = "peer_v4", .type = YNL_PT_U32, }, + [FOU_ATTR_PEER_V6] = { .name = "peer_v6", .type = YNL_PT_BINARY,}, + [FOU_ATTR_PEER_PORT] = { .name = "peer_port", .type = YNL_PT_U16, }, + [FOU_ATTR_IFINDEX] = { .name = "ifindex", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest fou_nest = { + .max_attr = FOU_ATTR_MAX, + .table = fou_policy, +}; + +/* Common nested types */ +/* ============== FOU_CMD_ADD ============== */ +/* FOU_CMD_ADD - do */ +void fou_add_req_free(struct fou_add_req *req) +{ + free(req->local_v6); + free(req->peer_v6); + free(req); +} + +int fou_add(struct ynl_sock *ys, struct fou_add_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, FOU_CMD_ADD, 1); + ys->req_policy = &fou_nest; + + if (req->_present.port) + mnl_attr_put_u16(nlh, FOU_ATTR_PORT, req->port); + if (req->_present.ipproto) + mnl_attr_put_u8(nlh, FOU_ATTR_IPPROTO, req->ipproto); + if (req->_present.type) + mnl_attr_put_u8(nlh, FOU_ATTR_TYPE, req->type); + if (req->_present.remcsum_nopartial) + mnl_attr_put(nlh, FOU_ATTR_REMCSUM_NOPARTIAL, 0, NULL); + if (req->_present.local_v4) + mnl_attr_put_u32(nlh, FOU_ATTR_LOCAL_V4, req->local_v4); + if (req->_present.peer_v4) + mnl_attr_put_u32(nlh, FOU_ATTR_PEER_V4, req->peer_v4); + if (req->_present.local_v6_len) + mnl_attr_put(nlh, FOU_ATTR_LOCAL_V6, req->_present.local_v6_len, req->local_v6); + if (req->_present.peer_v6_len) + mnl_attr_put(nlh, FOU_ATTR_PEER_V6, req->_present.peer_v6_len, req->peer_v6); + if (req->_present.peer_port) + mnl_attr_put_u16(nlh, FOU_ATTR_PEER_PORT, req->peer_port); + if (req->_present.ifindex) + mnl_attr_put_u32(nlh, FOU_ATTR_IFINDEX, req->ifindex); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== FOU_CMD_DEL ============== */ +/* FOU_CMD_DEL - do */ +void fou_del_req_free(struct fou_del_req *req) +{ + free(req->local_v6); + free(req->peer_v6); + free(req); +} + +int fou_del(struct ynl_sock *ys, struct fou_del_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, FOU_CMD_DEL, 1); + ys->req_policy = &fou_nest; + + if (req->_present.af) + mnl_attr_put_u8(nlh, FOU_ATTR_AF, req->af); + if (req->_present.ifindex) + mnl_attr_put_u32(nlh, FOU_ATTR_IFINDEX, req->ifindex); + if (req->_present.port) + mnl_attr_put_u16(nlh, FOU_ATTR_PORT, req->port); + if (req->_present.peer_port) + mnl_attr_put_u16(nlh, FOU_ATTR_PEER_PORT, req->peer_port); + if (req->_present.local_v4) + mnl_attr_put_u32(nlh, FOU_ATTR_LOCAL_V4, req->local_v4); + if (req->_present.peer_v4) + mnl_attr_put_u32(nlh, FOU_ATTR_PEER_V4, req->peer_v4); + if (req->_present.local_v6_len) + mnl_attr_put(nlh, FOU_ATTR_LOCAL_V6, req->_present.local_v6_len, req->local_v6); + if (req->_present.peer_v6_len) + mnl_attr_put(nlh, FOU_ATTR_PEER_V6, req->_present.peer_v6_len, req->peer_v6); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +/* ============== FOU_CMD_GET ============== */ +/* FOU_CMD_GET - do */ +void fou_get_req_free(struct fou_get_req *req) +{ + free(req->local_v6); + free(req->peer_v6); + free(req); +} + +void fou_get_rsp_free(struct fou_get_rsp *rsp) +{ + free(rsp->local_v6); + free(rsp->peer_v6); + free(rsp); +} + +int fou_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + const struct nlattr *attr; + struct fou_get_rsp *dst; + + dst = yarg->data; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == FOU_ATTR_PORT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.port = 1; + dst->port = mnl_attr_get_u16(attr); + } else if (type == FOU_ATTR_IPPROTO) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ipproto = 1; + dst->ipproto = mnl_attr_get_u8(attr); + } else if (type == FOU_ATTR_TYPE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.type = 1; + dst->type = mnl_attr_get_u8(attr); + } else if (type == FOU_ATTR_REMCSUM_NOPARTIAL) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.remcsum_nopartial = 1; + } else if (type == FOU_ATTR_LOCAL_V4) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.local_v4 = 1; + dst->local_v4 = mnl_attr_get_u32(attr); + } else if (type == FOU_ATTR_PEER_V4) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.peer_v4 = 1; + dst->peer_v4 = mnl_attr_get_u32(attr); + } else if (type == FOU_ATTR_LOCAL_V6) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.local_v6_len = len; + dst->local_v6 = malloc(len); + memcpy(dst->local_v6, mnl_attr_get_payload(attr), len); + } else if (type == FOU_ATTR_PEER_V6) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = mnl_attr_get_payload_len(attr); + dst->_present.peer_v6_len = len; + dst->peer_v6 = malloc(len); + memcpy(dst->peer_v6, mnl_attr_get_payload(attr), len); + } else if (type == FOU_ATTR_PEER_PORT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.peer_port = 1; + dst->peer_port = mnl_attr_get_u16(attr); + } else if (type == FOU_ATTR_IFINDEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ifindex = 1; + dst->ifindex = mnl_attr_get_u32(attr); + } + } + + return MNL_CB_OK; +} + +struct fou_get_rsp *fou_get(struct ynl_sock *ys, struct fou_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct fou_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, FOU_CMD_GET, 1); + ys->req_policy = &fou_nest; + yrs.yarg.rsp_policy = &fou_nest; + + if (req->_present.af) + mnl_attr_put_u8(nlh, FOU_ATTR_AF, req->af); + if (req->_present.ifindex) + mnl_attr_put_u32(nlh, FOU_ATTR_IFINDEX, req->ifindex); + if (req->_present.port) + mnl_attr_put_u16(nlh, FOU_ATTR_PORT, req->port); + if (req->_present.peer_port) + mnl_attr_put_u16(nlh, FOU_ATTR_PEER_PORT, req->peer_port); + if (req->_present.local_v4) + mnl_attr_put_u32(nlh, FOU_ATTR_LOCAL_V4, req->local_v4); + if (req->_present.peer_v4) + mnl_attr_put_u32(nlh, FOU_ATTR_PEER_V4, req->peer_v4); + if (req->_present.local_v6_len) + mnl_attr_put(nlh, FOU_ATTR_LOCAL_V6, req->_present.local_v6_len, req->local_v6); + if (req->_present.peer_v6_len) + mnl_attr_put(nlh, FOU_ATTR_PEER_V6, req->_present.peer_v6_len, req->peer_v6); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = fou_get_rsp_parse; + yrs.rsp_cmd = FOU_CMD_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + fou_get_rsp_free(rsp); + return NULL; +} + +/* FOU_CMD_GET - dump */ +void fou_get_list_free(struct fou_get_list *rsp) +{ + struct fou_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + free(rsp->obj.local_v6); + free(rsp->obj.peer_v6); + free(rsp); + } +} + +struct fou_get_list *fou_get_dump(struct ynl_sock *ys) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct fou_get_list); + yds.cb = fou_get_rsp_parse; + yds.rsp_cmd = FOU_CMD_GET; + yds.rsp_policy = &fou_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, FOU_CMD_GET, 1); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + fou_get_list_free(yds.first); + return NULL; +} + +const struct ynl_family ynl_fou_family = { + .name = "fou", +}; diff --git a/tools/net/ynl/generated/fou-user.h b/tools/net/ynl/generated/fou-user.h new file mode 100644 index 000000000000..d8ab50579cd1 --- /dev/null +++ b/tools/net/ynl/generated/fou-user.h @@ -0,0 +1,337 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/fou.yaml */ +/* YNL-GEN user header */ + +#ifndef _LINUX_FOU_GEN_H +#define _LINUX_FOU_GEN_H + +#include <stdlib.h> +#include <string.h> +#include <linux/types.h> +#include <linux/fou.h> + +struct ynl_sock; + +extern const struct ynl_family ynl_fou_family; + +/* Enums */ +const char *fou_op_str(int op); +const char *fou_encap_type_str(int value); + +/* Common nested types */ +/* ============== FOU_CMD_ADD ============== */ +/* FOU_CMD_ADD - do */ +struct fou_add_req { + struct { + __u32 port:1; + __u32 ipproto:1; + __u32 type:1; + __u32 remcsum_nopartial:1; + __u32 local_v4:1; + __u32 peer_v4:1; + __u32 local_v6_len; + __u32 peer_v6_len; + __u32 peer_port:1; + __u32 ifindex:1; + } _present; + + __u16 port /* big-endian */; + __u8 ipproto; + __u8 type; + __u32 local_v4; + __u32 peer_v4; + void *local_v6; + void *peer_v6; + __u16 peer_port /* big-endian */; + __s32 ifindex; +}; + +static inline struct fou_add_req *fou_add_req_alloc(void) +{ + return calloc(1, sizeof(struct fou_add_req)); +} +void fou_add_req_free(struct fou_add_req *req); + +static inline void +fou_add_req_set_port(struct fou_add_req *req, __u16 port /* big-endian */) +{ + req->_present.port = 1; + req->port = port; +} +static inline void +fou_add_req_set_ipproto(struct fou_add_req *req, __u8 ipproto) +{ + req->_present.ipproto = 1; + req->ipproto = ipproto; +} +static inline void fou_add_req_set_type(struct fou_add_req *req, __u8 type) +{ + req->_present.type = 1; + req->type = type; +} +static inline void fou_add_req_set_remcsum_nopartial(struct fou_add_req *req) +{ + req->_present.remcsum_nopartial = 1; +} +static inline void +fou_add_req_set_local_v4(struct fou_add_req *req, __u32 local_v4) +{ + req->_present.local_v4 = 1; + req->local_v4 = local_v4; +} +static inline void +fou_add_req_set_peer_v4(struct fou_add_req *req, __u32 peer_v4) +{ + req->_present.peer_v4 = 1; + req->peer_v4 = peer_v4; +} +static inline void +fou_add_req_set_local_v6(struct fou_add_req *req, const void *local_v6, + size_t len) +{ + free(req->local_v6); + req->local_v6 = malloc(req->_present.local_v6_len); + memcpy(req->local_v6, local_v6, req->_present.local_v6_len); +} +static inline void +fou_add_req_set_peer_v6(struct fou_add_req *req, const void *peer_v6, + size_t len) +{ + free(req->peer_v6); + req->peer_v6 = malloc(req->_present.peer_v6_len); + memcpy(req->peer_v6, peer_v6, req->_present.peer_v6_len); +} +static inline void +fou_add_req_set_peer_port(struct fou_add_req *req, + __u16 peer_port /* big-endian */) +{ + req->_present.peer_port = 1; + req->peer_port = peer_port; +} +static inline void +fou_add_req_set_ifindex(struct fou_add_req *req, __s32 ifindex) +{ + req->_present.ifindex = 1; + req->ifindex = ifindex; +} + +/* + * Add port. + */ +int fou_add(struct ynl_sock *ys, struct fou_add_req *req); + +/* ============== FOU_CMD_DEL ============== */ +/* FOU_CMD_DEL - do */ +struct fou_del_req { + struct { + __u32 af:1; + __u32 ifindex:1; + __u32 port:1; + __u32 peer_port:1; + __u32 local_v4:1; + __u32 peer_v4:1; + __u32 local_v6_len; + __u32 peer_v6_len; + } _present; + + __u8 af; + __s32 ifindex; + __u16 port /* big-endian */; + __u16 peer_port /* big-endian */; + __u32 local_v4; + __u32 peer_v4; + void *local_v6; + void *peer_v6; +}; + +static inline struct fou_del_req *fou_del_req_alloc(void) +{ + return calloc(1, sizeof(struct fou_del_req)); +} +void fou_del_req_free(struct fou_del_req *req); + +static inline void fou_del_req_set_af(struct fou_del_req *req, __u8 af) +{ + req->_present.af = 1; + req->af = af; +} +static inline void +fou_del_req_set_ifindex(struct fou_del_req *req, __s32 ifindex) +{ + req->_present.ifindex = 1; + req->ifindex = ifindex; +} +static inline void +fou_del_req_set_port(struct fou_del_req *req, __u16 port /* big-endian */) +{ + req->_present.port = 1; + req->port = port; +} +static inline void +fou_del_req_set_peer_port(struct fou_del_req *req, + __u16 peer_port /* big-endian */) +{ + req->_present.peer_port = 1; + req->peer_port = peer_port; +} +static inline void +fou_del_req_set_local_v4(struct fou_del_req *req, __u32 local_v4) +{ + req->_present.local_v4 = 1; + req->local_v4 = local_v4; +} +static inline void +fou_del_req_set_peer_v4(struct fou_del_req *req, __u32 peer_v4) +{ + req->_present.peer_v4 = 1; + req->peer_v4 = peer_v4; +} +static inline void +fou_del_req_set_local_v6(struct fou_del_req *req, const void *local_v6, + size_t len) +{ + free(req->local_v6); + req->local_v6 = malloc(req->_present.local_v6_len); + memcpy(req->local_v6, local_v6, req->_present.local_v6_len); +} +static inline void +fou_del_req_set_peer_v6(struct fou_del_req *req, const void *peer_v6, + size_t len) +{ + free(req->peer_v6); + req->peer_v6 = malloc(req->_present.peer_v6_len); + memcpy(req->peer_v6, peer_v6, req->_present.peer_v6_len); +} + +/* + * Delete port. + */ +int fou_del(struct ynl_sock *ys, struct fou_del_req *req); + +/* ============== FOU_CMD_GET ============== */ +/* FOU_CMD_GET - do */ +struct fou_get_req { + struct { + __u32 af:1; + __u32 ifindex:1; + __u32 port:1; + __u32 peer_port:1; + __u32 local_v4:1; + __u32 peer_v4:1; + __u32 local_v6_len; + __u32 peer_v6_len; + } _present; + + __u8 af; + __s32 ifindex; + __u16 port /* big-endian */; + __u16 peer_port /* big-endian */; + __u32 local_v4; + __u32 peer_v4; + void *local_v6; + void *peer_v6; +}; + +static inline struct fou_get_req *fou_get_req_alloc(void) +{ + return calloc(1, sizeof(struct fou_get_req)); +} +void fou_get_req_free(struct fou_get_req *req); + +static inline void fou_get_req_set_af(struct fou_get_req *req, __u8 af) +{ + req->_present.af = 1; + req->af = af; +} +static inline void +fou_get_req_set_ifindex(struct fou_get_req *req, __s32 ifindex) +{ + req->_present.ifindex = 1; + req->ifindex = ifindex; +} +static inline void +fou_get_req_set_port(struct fou_get_req *req, __u16 port /* big-endian */) +{ + req->_present.port = 1; + req->port = port; +} +static inline void +fou_get_req_set_peer_port(struct fou_get_req *req, + __u16 peer_port /* big-endian */) +{ + req->_present.peer_port = 1; + req->peer_port = peer_port; +} +static inline void +fou_get_req_set_local_v4(struct fou_get_req *req, __u32 local_v4) +{ + req->_present.local_v4 = 1; + req->local_v4 = local_v4; +} +static inline void +fou_get_req_set_peer_v4(struct fou_get_req *req, __u32 peer_v4) +{ + req->_present.peer_v4 = 1; + req->peer_v4 = peer_v4; +} +static inline void +fou_get_req_set_local_v6(struct fou_get_req *req, const void *local_v6, + size_t len) +{ + free(req->local_v6); + req->local_v6 = malloc(req->_present.local_v6_len); + memcpy(req->local_v6, local_v6, req->_present.local_v6_len); +} +static inline void +fou_get_req_set_peer_v6(struct fou_get_req *req, const void *peer_v6, + size_t len) +{ + free(req->peer_v6); + req->peer_v6 = malloc(req->_present.peer_v6_len); + memcpy(req->peer_v6, peer_v6, req->_present.peer_v6_len); +} + +struct fou_get_rsp { + struct { + __u32 port:1; + __u32 ipproto:1; + __u32 type:1; + __u32 remcsum_nopartial:1; + __u32 local_v4:1; + __u32 peer_v4:1; + __u32 local_v6_len; + __u32 peer_v6_len; + __u32 peer_port:1; + __u32 ifindex:1; + } _present; + + __u16 port /* big-endian */; + __u8 ipproto; + __u8 type; + __u32 local_v4; + __u32 peer_v4; + void *local_v6; + void *peer_v6; + __u16 peer_port /* big-endian */; + __s32 ifindex; +}; + +void fou_get_rsp_free(struct fou_get_rsp *rsp); + +/* + * Get tunnel info. + */ +struct fou_get_rsp *fou_get(struct ynl_sock *ys, struct fou_get_req *req); + +/* FOU_CMD_GET - dump */ +struct fou_get_list { + struct fou_get_list *next; + struct fou_get_rsp obj __attribute__ ((aligned (8))); +}; + +void fou_get_list_free(struct fou_get_list *rsp); + +struct fou_get_list *fou_get_dump(struct ynl_sock *ys); + +#endif /* _LINUX_FOU_GEN_H */ diff --git a/tools/net/ynl/generated/handshake-user.c b/tools/net/ynl/generated/handshake-user.c new file mode 100644 index 000000000000..7c67765daf90 --- /dev/null +++ b/tools/net/ynl/generated/handshake-user.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN user source */ + +#include <stdlib.h> +#include <string.h> +#include "handshake-user.h" +#include "ynl.h" +#include <linux/handshake.h> + +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> + +/* Enums */ +static const char * const handshake_op_strmap[] = { + [HANDSHAKE_CMD_READY] = "ready", + [HANDSHAKE_CMD_ACCEPT] = "accept", + [HANDSHAKE_CMD_DONE] = "done", +}; + +const char *handshake_op_str(int op) +{ + if (op < 0 || op >= (int)MNL_ARRAY_SIZE(handshake_op_strmap)) + return NULL; + return handshake_op_strmap[op]; +} + +static const char * const handshake_handler_class_strmap[] = { + [0] = "none", + [1] = "tlshd", + [2] = "max", +}; + +const char *handshake_handler_class_str(enum handshake_handler_class value) +{ + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(handshake_handler_class_strmap)) + return NULL; + return handshake_handler_class_strmap[value]; +} + +static const char * const handshake_msg_type_strmap[] = { + [0] = "unspec", + [1] = "clienthello", + [2] = "serverhello", +}; + +const char *handshake_msg_type_str(enum handshake_msg_type value) +{ + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(handshake_msg_type_strmap)) + return NULL; + return handshake_msg_type_strmap[value]; +} + +static const char * const handshake_auth_strmap[] = { + [0] = "unspec", + [1] = "unauth", + [2] = "psk", + [3] = "x509", +}; + +const char *handshake_auth_str(enum handshake_auth value) +{ + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(handshake_auth_strmap)) + return NULL; + return handshake_auth_strmap[value]; +} + +/* Policies */ +struct ynl_policy_attr handshake_x509_policy[HANDSHAKE_A_X509_MAX + 1] = { + [HANDSHAKE_A_X509_CERT] = { .name = "cert", .type = YNL_PT_U32, }, + [HANDSHAKE_A_X509_PRIVKEY] = { .name = "privkey", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest handshake_x509_nest = { + .max_attr = HANDSHAKE_A_X509_MAX, + .table = handshake_x509_policy, +}; + +struct ynl_policy_attr handshake_accept_policy[HANDSHAKE_A_ACCEPT_MAX + 1] = { + [HANDSHAKE_A_ACCEPT_SOCKFD] = { .name = "sockfd", .type = YNL_PT_U32, }, + [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = { .name = "handler-class", .type = YNL_PT_U32, }, + [HANDSHAKE_A_ACCEPT_MESSAGE_TYPE] = { .name = "message-type", .type = YNL_PT_U32, }, + [HANDSHAKE_A_ACCEPT_TIMEOUT] = { .name = "timeout", .type = YNL_PT_U32, }, + [HANDSHAKE_A_ACCEPT_AUTH_MODE] = { .name = "auth-mode", .type = YNL_PT_U32, }, + [HANDSHAKE_A_ACCEPT_PEER_IDENTITY] = { .name = "peer-identity", .type = YNL_PT_U32, }, + [HANDSHAKE_A_ACCEPT_CERTIFICATE] = { .name = "certificate", .type = YNL_PT_NEST, .nest = &handshake_x509_nest, }, + [HANDSHAKE_A_ACCEPT_PEERNAME] = { .name = "peername", .type = YNL_PT_NUL_STR, }, +}; + +struct ynl_policy_nest handshake_accept_nest = { + .max_attr = HANDSHAKE_A_ACCEPT_MAX, + .table = handshake_accept_policy, +}; + +struct ynl_policy_attr handshake_done_policy[HANDSHAKE_A_DONE_MAX + 1] = { + [HANDSHAKE_A_DONE_STATUS] = { .name = "status", .type = YNL_PT_U32, }, + [HANDSHAKE_A_DONE_SOCKFD] = { .name = "sockfd", .type = YNL_PT_U32, }, + [HANDSHAKE_A_DONE_REMOTE_AUTH] = { .name = "remote-auth", .type = YNL_PT_U32, }, +}; + +struct ynl_policy_nest handshake_done_nest = { + .max_attr = HANDSHAKE_A_DONE_MAX, + .table = handshake_done_policy, +}; + +/* Common nested types */ +void handshake_x509_free(struct handshake_x509 *obj) +{ +} + +int handshake_x509_parse(struct ynl_parse_arg *yarg, + const struct nlattr *nested) +{ + struct handshake_x509 *dst = yarg->data; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == HANDSHAKE_A_X509_CERT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.cert = 1; + dst->cert = mnl_attr_get_u32(attr); + } else if (type == HANDSHAKE_A_X509_PRIVKEY) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.privkey = 1; + dst->privkey = mnl_attr_get_u32(attr); + } + } + + return 0; +} + +/* ============== HANDSHAKE_CMD_ACCEPT ============== */ +/* HANDSHAKE_CMD_ACCEPT - do */ +void handshake_accept_req_free(struct handshake_accept_req *req) +{ + free(req); +} + +void handshake_accept_rsp_free(struct handshake_accept_rsp *rsp) +{ + unsigned int i; + + free(rsp->peer_identity); + for (i = 0; i < rsp->n_certificate; i++) + handshake_x509_free(&rsp->certificate[i]); + free(rsp->certificate); + free(rsp->peername); + free(rsp); +} + +int handshake_accept_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct handshake_accept_rsp *dst; + unsigned int n_peer_identity = 0; + unsigned int n_certificate = 0; + const struct nlattr *attr; + struct ynl_parse_arg parg; + int i; + + dst = yarg->data; + parg.ys = yarg->ys; + + if (dst->certificate) + return ynl_error_parse(yarg, "attribute already present (accept.certificate)"); + if (dst->peer_identity) + return ynl_error_parse(yarg, "attribute already present (accept.peer-identity)"); + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == HANDSHAKE_A_ACCEPT_SOCKFD) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.sockfd = 1; + dst->sockfd = mnl_attr_get_u32(attr); + } else if (type == HANDSHAKE_A_ACCEPT_MESSAGE_TYPE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.message_type = 1; + dst->message_type = mnl_attr_get_u32(attr); + } else if (type == HANDSHAKE_A_ACCEPT_TIMEOUT) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.timeout = 1; + dst->timeout = mnl_attr_get_u32(attr); + } else if (type == HANDSHAKE_A_ACCEPT_AUTH_MODE) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.auth_mode = 1; + dst->auth_mode = mnl_attr_get_u32(attr); + } else if (type == HANDSHAKE_A_ACCEPT_PEER_IDENTITY) { + n_peer_identity++; + } else if (type == HANDSHAKE_A_ACCEPT_CERTIFICATE) { + n_certificate++; + } else if (type == HANDSHAKE_A_ACCEPT_PEERNAME) { + unsigned int len; + + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + + len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr)); + dst->_present.peername_len = len; + dst->peername = malloc(len + 1); + memcpy(dst->peername, mnl_attr_get_str(attr), len); + dst->peername[len] = 0; + } + } + + if (n_certificate) { + dst->certificate = calloc(n_certificate, sizeof(*dst->certificate)); + dst->n_certificate = n_certificate; + i = 0; + parg.rsp_policy = &handshake_x509_nest; + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(attr) == HANDSHAKE_A_ACCEPT_CERTIFICATE) { + parg.data = &dst->certificate[i]; + if (handshake_x509_parse(&parg, attr)) + return MNL_CB_ERROR; + i++; + } + } + } + if (n_peer_identity) { + dst->peer_identity = calloc(n_peer_identity, sizeof(*dst->peer_identity)); + dst->n_peer_identity = n_peer_identity; + i = 0; + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(attr) == HANDSHAKE_A_ACCEPT_PEER_IDENTITY) { + dst->peer_identity[i] = mnl_attr_get_u32(attr); + i++; + } + } + } + + return MNL_CB_OK; +} + +struct handshake_accept_rsp * +handshake_accept(struct ynl_sock *ys, struct handshake_accept_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct handshake_accept_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, HANDSHAKE_CMD_ACCEPT, 1); + ys->req_policy = &handshake_accept_nest; + yrs.yarg.rsp_policy = &handshake_accept_nest; + + if (req->_present.handler_class) + mnl_attr_put_u32(nlh, HANDSHAKE_A_ACCEPT_HANDLER_CLASS, req->handler_class); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = handshake_accept_rsp_parse; + yrs.rsp_cmd = HANDSHAKE_CMD_ACCEPT; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + handshake_accept_rsp_free(rsp); + return NULL; +} + +/* HANDSHAKE_CMD_ACCEPT - notify */ +void handshake_accept_ntf_free(struct handshake_accept_ntf *rsp) +{ + unsigned int i; + + free(rsp->obj.peer_identity); + for (i = 0; i < rsp->obj.n_certificate; i++) + handshake_x509_free(&rsp->obj.certificate[i]); + free(rsp->obj.certificate); + free(rsp->obj.peername); + free(rsp); +} + +/* ============== HANDSHAKE_CMD_DONE ============== */ +/* HANDSHAKE_CMD_DONE - do */ +void handshake_done_req_free(struct handshake_done_req *req) +{ + free(req->remote_auth); + free(req); +} + +int handshake_done(struct ynl_sock *ys, struct handshake_done_req *req) +{ + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, HANDSHAKE_CMD_DONE, 1); + ys->req_policy = &handshake_done_nest; + + if (req->_present.status) + mnl_attr_put_u32(nlh, HANDSHAKE_A_DONE_STATUS, req->status); + if (req->_present.sockfd) + mnl_attr_put_u32(nlh, HANDSHAKE_A_DONE_SOCKFD, req->sockfd); + for (unsigned int i = 0; i < req->n_remote_auth; i++) + mnl_attr_put_u32(nlh, HANDSHAKE_A_DONE_REMOTE_AUTH, req->remote_auth[i]); + + err = ynl_exec(ys, nlh, NULL); + if (err < 0) + return -1; + + return 0; +} + +static const struct ynl_ntf_info handshake_ntf_info[] = { + [HANDSHAKE_CMD_READY] = { + .alloc_sz = sizeof(struct handshake_accept_ntf), + .cb = handshake_accept_rsp_parse, + .policy = &handshake_accept_nest, + .free = (void *)handshake_accept_ntf_free, + }, +}; + +const struct ynl_family ynl_handshake_family = { + .name = "handshake", + .ntf_info = handshake_ntf_info, + .ntf_info_size = MNL_ARRAY_SIZE(handshake_ntf_info), +}; diff --git a/tools/net/ynl/generated/handshake-user.h b/tools/net/ynl/generated/handshake-user.h new file mode 100644 index 000000000000..47646bb91cea --- /dev/null +++ b/tools/net/ynl/generated/handshake-user.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN user header */ + +#ifndef _LINUX_HANDSHAKE_GEN_H +#define _LINUX_HANDSHAKE_GEN_H + +#include <stdlib.h> +#include <string.h> +#include <linux/types.h> +#include <linux/handshake.h> + +struct ynl_sock; + +extern const struct ynl_family ynl_handshake_family; + +/* Enums */ +const char *handshake_op_str(int op); +const char *handshake_handler_class_str(enum handshake_handler_class value); +const char *handshake_msg_type_str(enum handshake_msg_type value); +const char *handshake_auth_str(enum handshake_auth value); + +/* Common nested types */ +struct handshake_x509 { + struct { + __u32 cert:1; + __u32 privkey:1; + } _present; + + __u32 cert; + __u32 privkey; +}; + +/* ============== HANDSHAKE_CMD_ACCEPT ============== */ +/* HANDSHAKE_CMD_ACCEPT - do */ +struct handshake_accept_req { + struct { + __u32 handler_class:1; + } _present; + + enum handshake_handler_class handler_class; +}; + +static inline struct handshake_accept_req *handshake_accept_req_alloc(void) +{ + return calloc(1, sizeof(struct handshake_accept_req)); +} +void handshake_accept_req_free(struct handshake_accept_req *req); + +static inline void +handshake_accept_req_set_handler_class(struct handshake_accept_req *req, + enum handshake_handler_class handler_class) +{ + req->_present.handler_class = 1; + req->handler_class = handler_class; +} + +struct handshake_accept_rsp { + struct { + __u32 sockfd:1; + __u32 message_type:1; + __u32 timeout:1; + __u32 auth_mode:1; + __u32 peername_len; + } _present; + + __u32 sockfd; + enum handshake_msg_type message_type; + __u32 timeout; + enum handshake_auth auth_mode; + unsigned int n_peer_identity; + __u32 *peer_identity; + unsigned int n_certificate; + struct handshake_x509 *certificate; + char *peername; +}; + +void handshake_accept_rsp_free(struct handshake_accept_rsp *rsp); + +/* + * Handler retrieves next queued handshake request + */ +struct handshake_accept_rsp * +handshake_accept(struct ynl_sock *ys, struct handshake_accept_req *req); + +/* HANDSHAKE_CMD_ACCEPT - notify */ +struct handshake_accept_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct handshake_accept_ntf *ntf); + struct handshake_accept_rsp obj __attribute__ ((aligned (8))); +}; + +void handshake_accept_ntf_free(struct handshake_accept_ntf *rsp); + +/* ============== HANDSHAKE_CMD_DONE ============== */ +/* HANDSHAKE_CMD_DONE - do */ +struct handshake_done_req { + struct { + __u32 status:1; + __u32 sockfd:1; + } _present; + + __u32 status; + __u32 sockfd; + unsigned int n_remote_auth; + __u32 *remote_auth; +}; + +static inline struct handshake_done_req *handshake_done_req_alloc(void) +{ + return calloc(1, sizeof(struct handshake_done_req)); +} +void handshake_done_req_free(struct handshake_done_req *req); + +static inline void +handshake_done_req_set_status(struct handshake_done_req *req, __u32 status) +{ + req->_present.status = 1; + req->status = status; +} +static inline void +handshake_done_req_set_sockfd(struct handshake_done_req *req, __u32 sockfd) +{ + req->_present.sockfd = 1; + req->sockfd = sockfd; +} +static inline void +__handshake_done_req_set_remote_auth(struct handshake_done_req *req, + __u32 *remote_auth, + unsigned int n_remote_auth) +{ + free(req->remote_auth); + req->remote_auth = remote_auth; + req->n_remote_auth = n_remote_auth; +} + +/* + * Handler reports handshake completion + */ +int handshake_done(struct ynl_sock *ys, struct handshake_done_req *req); + +#endif /* _LINUX_HANDSHAKE_GEN_H */ diff --git a/tools/net/ynl/generated/netdev-user.c b/tools/net/ynl/generated/netdev-user.c new file mode 100644 index 000000000000..4eb8aefef0cd --- /dev/null +++ b/tools/net/ynl/generated/netdev-user.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/netdev.yaml */ +/* YNL-GEN user source */ + +#include <stdlib.h> +#include <string.h> +#include "netdev-user.h" +#include "ynl.h" +#include <linux/netdev.h> + +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> + +/* Enums */ +static const char * const netdev_op_strmap[] = { + [NETDEV_CMD_DEV_GET] = "dev-get", + [NETDEV_CMD_DEV_ADD_NTF] = "dev-add-ntf", + [NETDEV_CMD_DEV_DEL_NTF] = "dev-del-ntf", + [NETDEV_CMD_DEV_CHANGE_NTF] = "dev-change-ntf", +}; + +const char *netdev_op_str(int op) +{ + if (op < 0 || op >= (int)MNL_ARRAY_SIZE(netdev_op_strmap)) + return NULL; + return netdev_op_strmap[op]; +} + +static const char * const netdev_xdp_act_strmap[] = { + [0] = "basic", + [1] = "redirect", + [2] = "ndo-xmit", + [3] = "xsk-zerocopy", + [4] = "hw-offload", + [5] = "rx-sg", + [6] = "ndo-xmit-sg", +}; + +const char *netdev_xdp_act_str(enum netdev_xdp_act value) +{ + value = ffs(value) - 1; + if (value < 0 || value >= (int)MNL_ARRAY_SIZE(netdev_xdp_act_strmap)) + return NULL; + return netdev_xdp_act_strmap[value]; +} + +/* Policies */ +struct ynl_policy_attr netdev_dev_policy[NETDEV_A_DEV_MAX + 1] = { + [NETDEV_A_DEV_IFINDEX] = { .name = "ifindex", .type = YNL_PT_U32, }, + [NETDEV_A_DEV_PAD] = { .name = "pad", .type = YNL_PT_IGNORE, }, + [NETDEV_A_DEV_XDP_FEATURES] = { .name = "xdp-features", .type = YNL_PT_U64, }, +}; + +struct ynl_policy_nest netdev_dev_nest = { + .max_attr = NETDEV_A_DEV_MAX, + .table = netdev_dev_policy, +}; + +/* Common nested types */ +/* ============== NETDEV_CMD_DEV_GET ============== */ +/* NETDEV_CMD_DEV_GET - do */ +void netdev_dev_get_req_free(struct netdev_dev_get_req *req) +{ + free(req); +} + +void netdev_dev_get_rsp_free(struct netdev_dev_get_rsp *rsp) +{ + free(rsp); +} + +int netdev_dev_get_rsp_parse(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct netdev_dev_get_rsp *dst; + const struct nlattr *attr; + + dst = yarg->data; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + unsigned int type = mnl_attr_get_type(attr); + + if (type == NETDEV_A_DEV_IFINDEX) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.ifindex = 1; + dst->ifindex = mnl_attr_get_u32(attr); + } else if (type == NETDEV_A_DEV_XDP_FEATURES) { + if (ynl_attr_validate(yarg, attr)) + return MNL_CB_ERROR; + dst->_present.xdp_features = 1; + dst->xdp_features = mnl_attr_get_u64(attr); + } + } + + return MNL_CB_OK; +} + +struct netdev_dev_get_rsp * +netdev_dev_get(struct ynl_sock *ys, struct netdev_dev_get_req *req) +{ + struct ynl_req_state yrs = { .yarg = { .ys = ys, }, }; + struct netdev_dev_get_rsp *rsp; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, ys->family_id, NETDEV_CMD_DEV_GET, 1); + ys->req_policy = &netdev_dev_nest; + yrs.yarg.rsp_policy = &netdev_dev_nest; + + if (req->_present.ifindex) + mnl_attr_put_u32(nlh, NETDEV_A_DEV_IFINDEX, req->ifindex); + + rsp = calloc(1, sizeof(*rsp)); + yrs.yarg.data = rsp; + yrs.cb = netdev_dev_get_rsp_parse; + yrs.rsp_cmd = NETDEV_CMD_DEV_GET; + + err = ynl_exec(ys, nlh, &yrs); + if (err < 0) + goto err_free; + + return rsp; + +err_free: + netdev_dev_get_rsp_free(rsp); + return NULL; +} + +/* NETDEV_CMD_DEV_GET - dump */ +void netdev_dev_get_list_free(struct netdev_dev_get_list *rsp) +{ + struct netdev_dev_get_list *next = rsp; + + while ((void *)next != YNL_LIST_END) { + rsp = next; + next = rsp->next; + + free(rsp); + } +} + +struct netdev_dev_get_list *netdev_dev_get_dump(struct ynl_sock *ys) +{ + struct ynl_dump_state yds = {}; + struct nlmsghdr *nlh; + int err; + + yds.ys = ys; + yds.alloc_sz = sizeof(struct netdev_dev_get_list); + yds.cb = netdev_dev_get_rsp_parse; + yds.rsp_cmd = NETDEV_CMD_DEV_GET; + yds.rsp_policy = &netdev_dev_nest; + + nlh = ynl_gemsg_start_dump(ys, ys->family_id, NETDEV_CMD_DEV_GET, 1); + + err = ynl_exec_dump(ys, nlh, &yds); + if (err < 0) + goto free_list; + + return yds.first; + +free_list: + netdev_dev_get_list_free(yds.first); + return NULL; +} + +/* NETDEV_CMD_DEV_GET - notify */ +void netdev_dev_get_ntf_free(struct netdev_dev_get_ntf *rsp) +{ + free(rsp); +} + +static const struct ynl_ntf_info netdev_ntf_info[] = { + [NETDEV_CMD_DEV_ADD_NTF] = { + .alloc_sz = sizeof(struct netdev_dev_get_ntf), + .cb = netdev_dev_get_rsp_parse, + .policy = &netdev_dev_nest, + .free = (void *)netdev_dev_get_ntf_free, + }, + [NETDEV_CMD_DEV_DEL_NTF] = { + .alloc_sz = sizeof(struct netdev_dev_get_ntf), + .cb = netdev_dev_get_rsp_parse, + .policy = &netdev_dev_nest, + .free = (void *)netdev_dev_get_ntf_free, + }, + [NETDEV_CMD_DEV_CHANGE_NTF] = { + .alloc_sz = sizeof(struct netdev_dev_get_ntf), + .cb = netdev_dev_get_rsp_parse, + .policy = &netdev_dev_nest, + .free = (void *)netdev_dev_get_ntf_free, + }, +}; + +const struct ynl_family ynl_netdev_family = { + .name = "netdev", + .ntf_info = netdev_ntf_info, + .ntf_info_size = MNL_ARRAY_SIZE(netdev_ntf_info), +}; diff --git a/tools/net/ynl/generated/netdev-user.h b/tools/net/ynl/generated/netdev-user.h new file mode 100644 index 000000000000..5554dc69bb9c --- /dev/null +++ b/tools/net/ynl/generated/netdev-user.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/netdev.yaml */ +/* YNL-GEN user header */ + +#ifndef _LINUX_NETDEV_GEN_H +#define _LINUX_NETDEV_GEN_H + +#include <stdlib.h> +#include <string.h> +#include <linux/types.h> +#include <linux/netdev.h> + +struct ynl_sock; + +extern const struct ynl_family ynl_netdev_family; + +/* Enums */ +const char *netdev_op_str(int op); +const char *netdev_xdp_act_str(enum netdev_xdp_act value); + +/* Common nested types */ +/* ============== NETDEV_CMD_DEV_GET ============== */ +/* NETDEV_CMD_DEV_GET - do */ +struct netdev_dev_get_req { + struct { + __u32 ifindex:1; + } _present; + + __u32 ifindex; +}; + +static inline struct netdev_dev_get_req *netdev_dev_get_req_alloc(void) +{ + return calloc(1, sizeof(struct netdev_dev_get_req)); +} +void netdev_dev_get_req_free(struct netdev_dev_get_req *req); + +static inline void +netdev_dev_get_req_set_ifindex(struct netdev_dev_get_req *req, __u32 ifindex) +{ + req->_present.ifindex = 1; + req->ifindex = ifindex; +} + +struct netdev_dev_get_rsp { + struct { + __u32 ifindex:1; + __u32 xdp_features:1; + } _present; + + __u32 ifindex; + __u64 xdp_features; +}; + +void netdev_dev_get_rsp_free(struct netdev_dev_get_rsp *rsp); + +/* + * Get / dump information about a netdev. + */ +struct netdev_dev_get_rsp * +netdev_dev_get(struct ynl_sock *ys, struct netdev_dev_get_req *req); + +/* NETDEV_CMD_DEV_GET - dump */ +struct netdev_dev_get_list { + struct netdev_dev_get_list *next; + struct netdev_dev_get_rsp obj __attribute__ ((aligned (8))); +}; + +void netdev_dev_get_list_free(struct netdev_dev_get_list *rsp); + +struct netdev_dev_get_list *netdev_dev_get_dump(struct ynl_sock *ys); + +/* NETDEV_CMD_DEV_GET - notify */ +struct netdev_dev_get_ntf { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct netdev_dev_get_ntf *ntf); + struct netdev_dev_get_rsp obj __attribute__ ((aligned (8))); +}; + +void netdev_dev_get_ntf_free(struct netdev_dev_get_ntf *rsp); + +#endif /* _LINUX_NETDEV_GEN_H */ diff --git a/tools/net/ynl/lib/Makefile b/tools/net/ynl/lib/Makefile new file mode 100644 index 000000000000..d2e50fd0a52d --- /dev/null +++ b/tools/net/ynl/lib/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 + +CC=gcc +CFLAGS=-std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow +ifeq ("$(DEBUG)","1") + CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan +endif + +SRCS=$(wildcard *.c) +OBJS=$(patsubst %.c,%.o,${SRCS}) + +include $(wildcard *.d) + +all: ynl.a + +ynl.a: $(OBJS) + ar rcs $@ $(OBJS) +clean: + rm -f *.o *.d *~ + +hardclean: clean + rm -f *.a + +%.o: %.c + $(COMPILE.c) -MMD -c -o $@ $< + +.PHONY: all clean +.DEFAULT_GOAL=all diff --git a/tools/net/ynl/lib/nlspec.py b/tools/net/ynl/lib/nlspec.py index a0241add3839..0ff0d18666b2 100644 --- a/tools/net/ynl/lib/nlspec.py +++ b/tools/net/ynl/lib/nlspec.py @@ -154,6 +154,9 @@ class SpecAttr(SpecElement): is_multi bool, attr may repeat multiple times struct_name string, name of struct definition sub_type string, name of sub type + len integer, optional byte length of binary types + display_hint string, hint to help choose format specifier + when displaying the value """ def __init__(self, family, attr_set, yaml, value): super().__init__(family, yaml) @@ -164,6 +167,8 @@ class SpecAttr(SpecElement): self.struct_name = yaml.get('struct') self.sub_type = yaml.get('sub-type') self.byte_order = yaml.get('byte-order') + self.len = yaml.get('len') + self.display_hint = yaml.get('display-hint') class SpecAttrSet(SpecElement): @@ -226,11 +231,20 @@ class SpecStructMember(SpecElement): Represents a single struct member attribute. Attributes: - type string, type of the member attribute + type string, type of the member attribute + byte_order string or None for native byte order + enum string, name of the enum definition + len integer, optional byte length of binary types + display_hint string, hint to help choose format specifier + when displaying the value """ def __init__(self, family, yaml): super().__init__(family, yaml) self.type = yaml['type'] + self.byte_order = yaml.get('byte-order') + self.enum = yaml.get('enum') + self.len = yaml.get('len') + self.display_hint = yaml.get('display-hint') class SpecStruct(SpecElement): @@ -320,16 +334,17 @@ class SpecFamily(SpecElement): Attributes: proto protocol type (e.g. genetlink) + msg_id_model enum-model for operations (unified, directional etc.) license spec license (loaded from an SPDX tag on the spec) attr_sets dict of attribute sets msgs dict of all messages (index by name) - msgs_by_value dict of all messages (indexed by name) ops dict of all valid requests / responses + ntfs dict of all async events consts dict of all constants/enums fixed_header string, optional name of family default fixed header struct """ - def __init__(self, spec_path, schema_path=None): + def __init__(self, spec_path, schema_path=None, exclude_ops=None): with open(spec_path, "r") as stream: prefix = '# SPDX-License-Identifier: ' first = stream.readline().strip() @@ -344,7 +359,10 @@ class SpecFamily(SpecElement): super().__init__(self, spec) + self._exclude_ops = exclude_ops if exclude_ops else [] + self.proto = self.yaml.get('protocol', 'genetlink') + self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') if schema_path is None: schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' @@ -364,6 +382,7 @@ class SpecFamily(SpecElement): self.req_by_value = collections.OrderedDict() self.rsp_by_value = collections.OrderedDict() self.ops = collections.OrderedDict() + self.ntfs = collections.OrderedDict() self.consts = collections.OrderedDict() last_exception = None @@ -416,7 +435,7 @@ class SpecFamily(SpecElement): self.fixed_header = self.yaml['operations'].get('fixed-header') req_val = rsp_val = 1 for elem in self.yaml['operations']['list']: - if 'notify' in elem: + if 'notify' in elem or 'event' in elem: if 'value' in elem: rsp_val = elem['value'] req_val_next = req_val @@ -438,7 +457,17 @@ class SpecFamily(SpecElement): else: raise Exception("Can't parse directional ops") - op = self.new_operation(elem, req_val, rsp_val) + if req_val == req_val_next: + req_val = None + if rsp_val == rsp_val_next: + rsp_val = None + + skip = False + for exclude in self._exclude_ops: + skip |= bool(exclude.match(elem['name'])) + if not skip: + op = self.new_operation(elem, req_val, rsp_val) + req_val = req_val_next rsp_val = rsp_val_next @@ -469,10 +498,9 @@ class SpecFamily(SpecElement): attr_set = self.new_attr_set(elem) self.attr_sets[elem['name']] = attr_set - msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - if msg_id_model == 'unified': + if self.msg_id_model == 'unified': self._dictify_ops_unified() - elif msg_id_model == 'directional': + elif self.msg_id_model == 'directional': self._dictify_ops_directional() for op in self.msgs.values(): @@ -482,3 +510,5 @@ class SpecFamily(SpecElement): self.rsp_by_value[op.rsp_value] = op if not op.is_async and 'attribute-set' in op: self.ops[op.name] = op + elif op.is_async: + self.ntfs[op.name] = op diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c new file mode 100644 index 000000000000..514e0d69e731 --- /dev/null +++ b/tools/net/ynl/lib/ynl.c @@ -0,0 +1,901 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +#include <errno.h> +#include <poll.h> +#include <string.h> +#include <stdlib.h> +#include <linux/types.h> + +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> + +#include "ynl.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*arr)) + +#define __yerr_msg(yse, _msg...) \ + ({ \ + struct ynl_error *_yse = (yse); \ + \ + if (_yse) { \ + snprintf(_yse->msg, sizeof(_yse->msg) - 1, _msg); \ + _yse->msg[sizeof(_yse->msg) - 1] = 0; \ + } \ + }) + +#define __yerr_code(yse, _code...) \ + ({ \ + struct ynl_error *_yse = (yse); \ + \ + if (_yse) { \ + _yse->code = _code; \ + } \ + }) + +#define __yerr(yse, _code, _msg...) \ + ({ \ + __yerr_msg(yse, _msg); \ + __yerr_code(yse, _code); \ + }) + +#define __perr(yse, _msg) __yerr(yse, errno, _msg) + +#define yerr_msg(_ys, _msg...) __yerr_msg(&(_ys)->err, _msg) +#define yerr(_ys, _code, _msg...) __yerr(&(_ys)->err, _code, _msg) +#define perr(_ys, _msg) __yerr(&(_ys)->err, errno, _msg) + +/* -- Netlink boiler plate */ +static int +ynl_err_walk_report_one(struct ynl_policy_nest *policy, unsigned int type, + char *str, int str_sz, int *n) +{ + if (!policy) { + if (*n < str_sz) + *n += snprintf(str, str_sz, "!policy"); + return 1; + } + + if (type > policy->max_attr) { + if (*n < str_sz) + *n += snprintf(str, str_sz, "!oob"); + return 1; + } + + if (!policy->table[type].name) { + if (*n < str_sz) + *n += snprintf(str, str_sz, "!name"); + return 1; + } + + if (*n < str_sz) + *n += snprintf(str, str_sz - *n, + ".%s", policy->table[type].name); + return 0; +} + +static int +ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off, + struct ynl_policy_nest *policy, char *str, int str_sz, + struct ynl_policy_nest **nest_pol) +{ + unsigned int astart_off, aend_off; + const struct nlattr *attr; + unsigned int data_len; + unsigned int type; + bool found = false; + int n = 0; + + if (!policy) { + if (n < str_sz) + n += snprintf(str, str_sz, "!policy"); + return n; + } + + data_len = end - start; + + mnl_attr_for_each_payload(start, data_len) { + astart_off = (char *)attr - (char *)start; + aend_off = astart_off + mnl_attr_get_payload_len(attr); + if (aend_off <= off) + continue; + + found = true; + break; + } + if (!found) + return 0; + + off -= astart_off; + + type = mnl_attr_get_type(attr); + + if (ynl_err_walk_report_one(policy, type, str, str_sz, &n)) + return n; + + if (!off) { + if (nest_pol) + *nest_pol = policy->table[type].nest; + return n; + } + + if (!policy->table[type].nest) { + if (n < str_sz) + n += snprintf(str, str_sz, "!nest"); + return n; + } + + off -= sizeof(struct nlattr); + start = mnl_attr_get_payload(attr); + end = start + mnl_attr_get_payload_len(attr); + + return n + ynl_err_walk(ys, start, end, off, policy->table[type].nest, + &str[n], str_sz - n, nest_pol); +} + +#define NLMSGERR_ATTR_MISS_TYPE (NLMSGERR_ATTR_POLICY + 1) +#define NLMSGERR_ATTR_MISS_NEST (NLMSGERR_ATTR_POLICY + 2) +#define NLMSGERR_ATTR_MAX (NLMSGERR_ATTR_MAX + 2) + +static int +ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh, + unsigned int hlen) +{ + const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {}; + char miss_attr[sizeof(ys->err.msg)]; + char bad_attr[sizeof(ys->err.msg)]; + const struct nlattr *attr; + const char *str = NULL; + + if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) + return MNL_CB_OK; + + mnl_attr_for_each(attr, nlh, hlen) { + unsigned int len, type; + + len = mnl_attr_get_payload_len(attr); + type = mnl_attr_get_type(attr); + + if (type > NLMSGERR_ATTR_MAX) + continue; + + tb[type] = attr; + + switch (type) { + case NLMSGERR_ATTR_OFFS: + case NLMSGERR_ATTR_MISS_TYPE: + case NLMSGERR_ATTR_MISS_NEST: + if (len != sizeof(__u32)) + return MNL_CB_ERROR; + break; + case NLMSGERR_ATTR_MSG: + str = mnl_attr_get_payload(attr); + if (str[len - 1]) + return MNL_CB_ERROR; + break; + default: + break; + } + } + + bad_attr[0] = '\0'; + miss_attr[0] = '\0'; + + if (tb[NLMSGERR_ATTR_OFFS]) { + unsigned int n, off; + void *start, *end; + + ys->err.attr_offs = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]); + + n = snprintf(bad_attr, sizeof(bad_attr), "%sbad attribute: ", + str ? " (" : ""); + + start = mnl_nlmsg_get_payload_offset(ys->nlh, + sizeof(struct genlmsghdr)); + end = mnl_nlmsg_get_payload_tail(ys->nlh); + + off = ys->err.attr_offs; + off -= sizeof(struct nlmsghdr); + off -= sizeof(struct genlmsghdr); + + n += ynl_err_walk(ys, start, end, off, ys->req_policy, + &bad_attr[n], sizeof(bad_attr) - n, NULL); + + if (n >= sizeof(bad_attr)) + n = sizeof(bad_attr) - 1; + bad_attr[n] = '\0'; + } + if (tb[NLMSGERR_ATTR_MISS_TYPE]) { + struct ynl_policy_nest *nest_pol = NULL; + unsigned int n, off, type; + void *start, *end; + int n2; + + type = mnl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_TYPE]); + + n = snprintf(miss_attr, sizeof(miss_attr), "%smissing attribute: ", + bad_attr[0] ? ", " : (str ? " (" : "")); + + start = mnl_nlmsg_get_payload_offset(ys->nlh, + sizeof(struct genlmsghdr)); + end = mnl_nlmsg_get_payload_tail(ys->nlh); + + nest_pol = ys->req_policy; + if (tb[NLMSGERR_ATTR_MISS_NEST]) { + off = mnl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_NEST]); + off -= sizeof(struct nlmsghdr); + off -= sizeof(struct genlmsghdr); + + n += ynl_err_walk(ys, start, end, off, ys->req_policy, + &miss_attr[n], sizeof(miss_attr) - n, + &nest_pol); + } + + n2 = 0; + ynl_err_walk_report_one(nest_pol, type, &miss_attr[n], + sizeof(miss_attr) - n, &n2); + n += n2; + + if (n >= sizeof(miss_attr)) + n = sizeof(miss_attr) - 1; + miss_attr[n] = '\0'; + } + + /* Implicitly depend on ys->err.code already set */ + if (str) + yerr_msg(ys, "Kernel %s: '%s'%s%s%s", + ys->err.code ? "error" : "warning", + str, bad_attr, miss_attr, + bad_attr[0] || miss_attr[0] ? ")" : ""); + else if (bad_attr[0] || miss_attr[0]) + yerr_msg(ys, "Kernel %s: %s%s", + ys->err.code ? "error" : "warning", + bad_attr, miss_attr); + + return MNL_CB_OK; +} + +static int ynl_cb_error(const struct nlmsghdr *nlh, void *data) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + struct ynl_parse_arg *yarg = data; + unsigned int hlen; + int code; + + code = err->error >= 0 ? err->error : -err->error; + yarg->ys->err.code = code; + errno = code; + + hlen = sizeof(*err); + if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + hlen += mnl_nlmsg_get_payload_len(&err->msg); + + ynl_ext_ack_check(yarg->ys, nlh, hlen); + + return code ? MNL_CB_ERROR : MNL_CB_STOP; +} + +static int ynl_cb_done(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + int err; + + err = *(int *)NLMSG_DATA(nlh); + if (err < 0) { + yarg->ys->err.code = -err; + errno = -err; + + ynl_ext_ack_check(yarg->ys, nlh, sizeof(int)); + + return MNL_CB_ERROR; + } + return MNL_CB_STOP; +} + +static int ynl_cb_noop(const struct nlmsghdr *nlh, void *data) +{ + return MNL_CB_OK; +} + +mnl_cb_t ynl_cb_array[NLMSG_MIN_TYPE] = { + [NLMSG_NOOP] = ynl_cb_noop, + [NLMSG_ERROR] = ynl_cb_error, + [NLMSG_DONE] = ynl_cb_done, + [NLMSG_OVERRUN] = ynl_cb_noop, +}; + +/* Attribute validation */ + +int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr) +{ + struct ynl_policy_attr *policy; + unsigned int type, len; + unsigned char *data; + + data = mnl_attr_get_payload(attr); + len = mnl_attr_get_payload_len(attr); + type = mnl_attr_get_type(attr); + if (type > yarg->rsp_policy->max_attr) { + yerr(yarg->ys, YNL_ERROR_INTERNAL, + "Internal error, validating unknown attribute"); + return -1; + } + + policy = &yarg->rsp_policy->table[type]; + + switch (policy->type) { + case YNL_PT_REJECT: + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Rejected attribute (%s)", policy->name); + return -1; + case YNL_PT_IGNORE: + break; + case YNL_PT_U8: + if (len == sizeof(__u8)) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (u8 %s)", policy->name); + return -1; + case YNL_PT_U16: + if (len == sizeof(__u16)) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (u16 %s)", policy->name); + return -1; + case YNL_PT_U32: + if (len == sizeof(__u32)) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (u32 %s)", policy->name); + return -1; + case YNL_PT_U64: + if (len == sizeof(__u64)) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (u64 %s)", policy->name); + return -1; + case YNL_PT_FLAG: + /* Let flags grow into real attrs, why not.. */ + break; + case YNL_PT_NEST: + if (!len || len >= sizeof(*attr)) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (nest %s)", policy->name); + return -1; + case YNL_PT_BINARY: + if (!policy->len || len == policy->len) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (binary %s)", policy->name); + return -1; + case YNL_PT_NUL_STR: + if ((!policy->len || len <= policy->len) && !data[len - 1]) + break; + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (string %s)", policy->name); + return -1; + default: + yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, + "Invalid attribute (unknown %s)", policy->name); + return -1; + } + + return 0; +} + +/* Generic code */ + +static void ynl_err_reset(struct ynl_sock *ys) +{ + ys->err.code = 0; + ys->err.attr_offs = 0; + ys->err.msg[0] = 0; +} + +struct nlmsghdr *ynl_msg_start(struct ynl_sock *ys, __u32 id, __u16 flags) +{ + struct nlmsghdr *nlh; + + ynl_err_reset(ys); + + nlh = ys->nlh = mnl_nlmsg_put_header(ys->tx_buf); + nlh->nlmsg_type = id; + nlh->nlmsg_flags = flags; + nlh->nlmsg_seq = ++ys->seq; + + return nlh; +} + +struct nlmsghdr * +ynl_gemsg_start(struct ynl_sock *ys, __u32 id, __u16 flags, + __u8 cmd, __u8 version) +{ + struct genlmsghdr gehdr; + struct nlmsghdr *nlh; + void *data; + + nlh = ynl_msg_start(ys, id, flags); + + memset(&gehdr, 0, sizeof(gehdr)); + gehdr.cmd = cmd; + gehdr.version = version; + + data = mnl_nlmsg_put_extra_header(nlh, sizeof(gehdr)); + memcpy(data, &gehdr, sizeof(gehdr)); + + return nlh; +} + +void ynl_msg_start_req(struct ynl_sock *ys, __u32 id) +{ + ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK); +} + +void ynl_msg_start_dump(struct ynl_sock *ys, __u32 id) +{ + ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); +} + +struct nlmsghdr * +ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version) +{ + return ynl_gemsg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK, cmd, version); +} + +struct nlmsghdr * +ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version) +{ + return ynl_gemsg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, + cmd, version); +} + +int ynl_recv_ack(struct ynl_sock *ys, int ret) +{ + if (!ret) { + yerr(ys, YNL_ERROR_EXPECT_ACK, + "Expecting an ACK but nothing received"); + return -1; + } + + ret = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE); + if (ret < 0) { + perr(ys, "Socket receive failed"); + return ret; + } + return mnl_cb_run(ys->rx_buf, ret, ys->seq, ys->portid, + ynl_cb_null, ys); +} + +int ynl_cb_null(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + + yerr(yarg->ys, YNL_ERROR_UNEXPECT_MSG, + "Received a message when none were expected"); + + return MNL_CB_ERROR; +} + +/* Init/fini and genetlink boiler plate */ +static int +ynl_get_family_info_mcast(struct ynl_sock *ys, const struct nlattr *mcasts) +{ + const struct nlattr *entry, *attr; + unsigned int i; + + mnl_attr_for_each_nested(attr, mcasts) + ys->n_mcast_groups++; + + if (!ys->n_mcast_groups) + return 0; + + ys->mcast_groups = calloc(ys->n_mcast_groups, + sizeof(*ys->mcast_groups)); + if (!ys->mcast_groups) + return MNL_CB_ERROR; + + i = 0; + mnl_attr_for_each_nested(entry, mcasts) { + mnl_attr_for_each_nested(attr, entry) { + if (mnl_attr_get_type(attr) == CTRL_ATTR_MCAST_GRP_ID) + ys->mcast_groups[i].id = mnl_attr_get_u32(attr); + if (mnl_attr_get_type(attr) == CTRL_ATTR_MCAST_GRP_NAME) { + strncpy(ys->mcast_groups[i].name, + mnl_attr_get_str(attr), + GENL_NAMSIZ - 1); + ys->mcast_groups[i].name[GENL_NAMSIZ - 1] = 0; + } + } + } + + return 0; +} + +static int ynl_get_family_info_cb(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_parse_arg *yarg = data; + struct ynl_sock *ys = yarg->ys; + const struct nlattr *attr; + bool found_id = true; + + mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(attr) == CTRL_ATTR_MCAST_GROUPS) + if (ynl_get_family_info_mcast(ys, attr)) + return MNL_CB_ERROR; + + if (mnl_attr_get_type(attr) != CTRL_ATTR_FAMILY_ID) + continue; + + if (mnl_attr_get_payload_len(attr) != sizeof(__u16)) { + yerr(ys, YNL_ERROR_ATTR_INVALID, "Invalid family ID"); + return MNL_CB_ERROR; + } + + ys->family_id = mnl_attr_get_u16(attr); + found_id = true; + } + + if (!found_id) { + yerr(ys, YNL_ERROR_ATTR_MISSING, "Family ID missing"); + return MNL_CB_ERROR; + } + return MNL_CB_OK; +} + +static int ynl_sock_read_family(struct ynl_sock *ys, const char *family_name) +{ + struct ynl_parse_arg yarg = { .ys = ys, }; + struct nlmsghdr *nlh; + int err; + + nlh = ynl_gemsg_start_req(ys, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 1); + mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name); + + err = mnl_socket_sendto(ys->sock, nlh, nlh->nlmsg_len); + if (err < 0) { + perr(ys, "failed to request socket family info"); + return err; + } + + err = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE); + if (err <= 0) { + perr(ys, "failed to receive the socket family info"); + return err; + } + err = mnl_cb_run2(ys->rx_buf, err, ys->seq, ys->portid, + ynl_get_family_info_cb, &yarg, + ynl_cb_array, ARRAY_SIZE(ynl_cb_array)); + if (err < 0) { + free(ys->mcast_groups); + perr(ys, "failed to receive the socket family info - no such family?"); + return err; + } + + return ynl_recv_ack(ys, err); +} + +struct ynl_sock * +ynl_sock_create(const struct ynl_family *yf, struct ynl_error *yse) +{ + struct ynl_sock *ys; + int one = 1; + + ys = malloc(sizeof(*ys) + 2 * MNL_SOCKET_BUFFER_SIZE); + if (!ys) + return NULL; + memset(ys, 0, sizeof(*ys)); + + ys->family = yf; + ys->tx_buf = &ys->raw_buf[0]; + ys->rx_buf = &ys->raw_buf[MNL_SOCKET_BUFFER_SIZE]; + ys->ntf_last_next = &ys->ntf_first; + + ys->sock = mnl_socket_open(NETLINK_GENERIC); + if (!ys->sock) { + __perr(yse, "failed to create a netlink socket"); + goto err_free_sock; + } + + if (mnl_socket_setsockopt(ys->sock, NETLINK_CAP_ACK, + &one, sizeof(one))) { + __perr(yse, "failed to enable netlink ACK"); + goto err_close_sock; + } + if (mnl_socket_setsockopt(ys->sock, NETLINK_EXT_ACK, + &one, sizeof(one))) { + __perr(yse, "failed to enable netlink ext ACK"); + goto err_close_sock; + } + + ys->seq = random(); + ys->portid = mnl_socket_get_portid(ys->sock); + + if (ynl_sock_read_family(ys, yf->name)) { + if (yse) + memcpy(yse, &ys->err, sizeof(*yse)); + goto err_close_sock; + } + + return ys; + +err_close_sock: + mnl_socket_close(ys->sock); +err_free_sock: + free(ys); + return NULL; +} + +void ynl_sock_destroy(struct ynl_sock *ys) +{ + struct ynl_ntf_base_type *ntf; + + mnl_socket_close(ys->sock); + while ((ntf = ynl_ntf_dequeue(ys))) + ynl_ntf_free(ntf); + free(ys->mcast_groups); + free(ys); +} + +/* YNL multicast handling */ + +void ynl_ntf_free(struct ynl_ntf_base_type *ntf) +{ + ntf->free(ntf); +} + +int ynl_subscribe(struct ynl_sock *ys, const char *grp_name) +{ + unsigned int i; + int err; + + for (i = 0; i < ys->n_mcast_groups; i++) + if (!strcmp(ys->mcast_groups[i].name, grp_name)) + break; + if (i == ys->n_mcast_groups) { + yerr(ys, ENOENT, "Multicast group '%s' not found", grp_name); + return -1; + } + + err = mnl_socket_setsockopt(ys->sock, NETLINK_ADD_MEMBERSHIP, + &ys->mcast_groups[i].id, + sizeof(ys->mcast_groups[i].id)); + if (err < 0) { + perr(ys, "Subscribing to multicast group failed"); + return -1; + } + + return 0; +} + +int ynl_socket_get_fd(struct ynl_sock *ys) +{ + return mnl_socket_get_fd(ys->sock); +} + +struct ynl_ntf_base_type *ynl_ntf_dequeue(struct ynl_sock *ys) +{ + struct ynl_ntf_base_type *ntf; + + if (!ynl_has_ntf(ys)) + return NULL; + + ntf = ys->ntf_first; + ys->ntf_first = ntf->next; + if (ys->ntf_last_next == &ntf->next) + ys->ntf_last_next = &ys->ntf_first; + + return ntf; +} + +static int ynl_ntf_parse(struct ynl_sock *ys, const struct nlmsghdr *nlh) +{ + struct ynl_parse_arg yarg = { .ys = ys, }; + const struct ynl_ntf_info *info; + struct ynl_ntf_base_type *rsp; + struct genlmsghdr *gehdr; + int ret; + + gehdr = mnl_nlmsg_get_payload(nlh); + if (gehdr->cmd >= ys->family->ntf_info_size) + return MNL_CB_ERROR; + info = &ys->family->ntf_info[gehdr->cmd]; + if (!info->cb) + return MNL_CB_ERROR; + + rsp = calloc(1, info->alloc_sz); + rsp->free = info->free; + yarg.data = rsp->data; + yarg.rsp_policy = info->policy; + + ret = info->cb(nlh, &yarg); + if (ret <= MNL_CB_STOP) + goto err_free; + + rsp->family = nlh->nlmsg_type; + rsp->cmd = gehdr->cmd; + + *ys->ntf_last_next = rsp; + ys->ntf_last_next = &rsp->next; + + return MNL_CB_OK; + +err_free: + info->free(rsp); + return MNL_CB_ERROR; +} + +static int ynl_ntf_trampoline(const struct nlmsghdr *nlh, void *data) +{ + return ynl_ntf_parse((struct ynl_sock *)data, nlh); +} + +int ynl_ntf_check(struct ynl_sock *ys) +{ + ssize_t len; + int err; + + do { + /* libmnl doesn't let us pass flags to the recv to make + * it non-blocking so we need to poll() or peek() :| + */ + struct pollfd pfd = { }; + + pfd.fd = mnl_socket_get_fd(ys->sock); + pfd.events = POLLIN; + err = poll(&pfd, 1, 1); + if (err < 1) + return err; + + len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, + MNL_SOCKET_BUFFER_SIZE); + if (len < 0) + return len; + + err = mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid, + ynl_ntf_trampoline, ys, + ynl_cb_array, NLMSG_MIN_TYPE); + if (err < 0) + return err; + } while (err > 0); + + return 0; +} + +/* YNL specific helpers used by the auto-generated code */ + +struct ynl_dump_list_type *YNL_LIST_END = (void *)(0xb4d123); + +void ynl_error_unknown_notification(struct ynl_sock *ys, __u8 cmd) +{ + yerr(ys, YNL_ERROR_UNKNOWN_NTF, + "Unknown notification message type '%d'", cmd); +} + +int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg) +{ + yerr(yarg->ys, YNL_ERROR_INV_RESP, "Error parsing response: %s", msg); + return MNL_CB_ERROR; +} + +static int +ynl_check_alien(struct ynl_sock *ys, const struct nlmsghdr *nlh, __u32 rsp_cmd) +{ + struct genlmsghdr *gehdr; + + if (mnl_nlmsg_get_payload_len(nlh) < sizeof(*gehdr)) { + yerr(ys, YNL_ERROR_INV_RESP, + "Kernel responded with truncated message"); + return -1; + } + + gehdr = mnl_nlmsg_get_payload(nlh); + if (gehdr->cmd != rsp_cmd) + return ynl_ntf_parse(ys, nlh); + + return 0; +} + +static int ynl_req_trampoline(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_req_state *yrs = data; + int ret; + + ret = ynl_check_alien(yrs->yarg.ys, nlh, yrs->rsp_cmd); + if (ret) + return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK; + + return yrs->cb(nlh, &yrs->yarg); +} + +int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh, + struct ynl_req_state *yrs) +{ + ssize_t len; + int err; + + err = mnl_socket_sendto(ys->sock, req_nlh, req_nlh->nlmsg_len); + if (err < 0) + return err; + + do { + len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, + MNL_SOCKET_BUFFER_SIZE); + if (len < 0) + return len; + + err = mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid, + ynl_req_trampoline, yrs, + ynl_cb_array, NLMSG_MIN_TYPE); + if (err < 0) + return err; + } while (err > 0); + + return 0; +} + +static int ynl_dump_trampoline(const struct nlmsghdr *nlh, void *data) +{ + struct ynl_dump_state *ds = data; + struct ynl_dump_list_type *obj; + struct ynl_parse_arg yarg = {}; + int ret; + + ret = ynl_check_alien(ds->ys, nlh, ds->rsp_cmd); + if (ret) + return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK; + + obj = calloc(1, ds->alloc_sz); + if (!obj) + return MNL_CB_ERROR; + + if (!ds->first) + ds->first = obj; + if (ds->last) + ds->last->next = obj; + ds->last = obj; + + yarg.ys = ds->ys; + yarg.rsp_policy = ds->rsp_policy; + yarg.data = &obj->data; + + return ds->cb(nlh, &yarg); +} + +static void *ynl_dump_end(struct ynl_dump_state *ds) +{ + if (!ds->first) + return YNL_LIST_END; + + ds->last->next = YNL_LIST_END; + return ds->first; +} + +int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh, + struct ynl_dump_state *yds) +{ + ssize_t len; + int err; + + err = mnl_socket_sendto(ys->sock, req_nlh, req_nlh->nlmsg_len); + if (err < 0) + return err; + + do { + len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, + MNL_SOCKET_BUFFER_SIZE); + if (len < 0) + goto err_close_list; + + err = mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid, + ynl_dump_trampoline, yds, + ynl_cb_array, NLMSG_MIN_TYPE); + if (err < 0) + goto err_close_list; + } while (err > 0); + + yds->first = ynl_dump_end(yds); + return 0; + +err_close_list: + yds->first = ynl_dump_end(yds); + return -1; +} diff --git a/tools/net/ynl/lib/ynl.h b/tools/net/ynl/lib/ynl.h new file mode 100644 index 000000000000..9eafa3552c16 --- /dev/null +++ b/tools/net/ynl/lib/ynl.h @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +#ifndef __YNL_C_H +#define __YNL_C_H 1 + +#include <stddef.h> +#include <libmnl/libmnl.h> +#include <linux/genetlink.h> +#include <linux/types.h> + +struct mnl_socket; +struct nlmsghdr; + +/* + * User facing code + */ + +struct ynl_ntf_base_type; +struct ynl_ntf_info; +struct ynl_sock; + +enum ynl_error_code { + YNL_ERROR_NONE = 0, + __YNL_ERRNO_END = 4096, + YNL_ERROR_INTERNAL, + YNL_ERROR_EXPECT_ACK, + YNL_ERROR_EXPECT_MSG, + YNL_ERROR_UNEXPECT_MSG, + YNL_ERROR_ATTR_MISSING, + YNL_ERROR_ATTR_INVALID, + YNL_ERROR_UNKNOWN_NTF, + YNL_ERROR_INV_RESP, +}; + +/** + * struct ynl_error - error encountered by YNL + * @code: errno (low values) or YNL error code (enum ynl_error_code) + * @attr_offs: offset of bad attribute (for very advanced users) + * @msg: error message + * + * Error information for when YNL operations fail. + * Users should interact with the err member of struct ynl_sock directly. + * The main exception to that rule is ynl_sock_create(). + */ +struct ynl_error { + enum ynl_error_code code; + unsigned int attr_offs; + char msg[512]; +}; + +/** + * struct ynl_family - YNL family info + * Family description generated by codegen. Pass to ynl_sock_create(). + */ +struct ynl_family { +/* private: */ + const char *name; + const struct ynl_ntf_info *ntf_info; + unsigned int ntf_info_size; +}; + +/** + * struct ynl_sock - YNL wrapped netlink socket + * @err: YNL error descriptor, cleared on every request. + */ +struct ynl_sock { + struct ynl_error err; + +/* private: */ + const struct ynl_family *family; + struct mnl_socket *sock; + __u32 seq; + __u32 portid; + __u16 family_id; + + unsigned int n_mcast_groups; + struct { + unsigned int id; + char name[GENL_NAMSIZ]; + } *mcast_groups; + + struct ynl_ntf_base_type *ntf_first; + struct ynl_ntf_base_type **ntf_last_next; + + struct nlmsghdr *nlh; + struct ynl_policy_nest *req_policy; + unsigned char *tx_buf; + unsigned char *rx_buf; + unsigned char raw_buf[]; +}; + +struct ynl_sock * +ynl_sock_create(const struct ynl_family *yf, struct ynl_error *e); +void ynl_sock_destroy(struct ynl_sock *ys); + +#define ynl_dump_foreach(dump, iter) \ + for (typeof(dump->obj) *iter = &dump->obj; \ + !ynl_dump_obj_is_last(iter); \ + iter = ynl_dump_obj_next(iter)) + +int ynl_subscribe(struct ynl_sock *ys, const char *grp_name); +int ynl_socket_get_fd(struct ynl_sock *ys); +int ynl_ntf_check(struct ynl_sock *ys); + +/** + * ynl_has_ntf() - check if socket has *parsed* notifications + * @ys: active YNL socket + * + * Note that this does not take into account notifications sitting + * in netlink socket, just the notifications which have already been + * read and parsed (e.g. during a ynl_ntf_check() call). + */ +static inline bool ynl_has_ntf(struct ynl_sock *ys) +{ + return ys->ntf_last_next != &ys->ntf_first; +} +struct ynl_ntf_base_type *ynl_ntf_dequeue(struct ynl_sock *ys); + +void ynl_ntf_free(struct ynl_ntf_base_type *ntf); + +/* + * YNL internals / low level stuff + */ + +/* Generic mnl helper code */ + +enum ynl_policy_type { + YNL_PT_REJECT = 1, + YNL_PT_IGNORE, + YNL_PT_NEST, + YNL_PT_FLAG, + YNL_PT_BINARY, + YNL_PT_U8, + YNL_PT_U16, + YNL_PT_U32, + YNL_PT_U64, + YNL_PT_NUL_STR, +}; + +struct ynl_policy_attr { + enum ynl_policy_type type; + unsigned int len; + const char *name; + struct ynl_policy_nest *nest; +}; + +struct ynl_policy_nest { + unsigned int max_attr; + struct ynl_policy_attr *table; +}; + +struct ynl_parse_arg { + struct ynl_sock *ys; + struct ynl_policy_nest *rsp_policy; + void *data; +}; + +struct ynl_dump_list_type { + struct ynl_dump_list_type *next; + unsigned char data[] __attribute__ ((aligned (8))); +}; +extern struct ynl_dump_list_type *YNL_LIST_END; + +static inline bool ynl_dump_obj_is_last(void *obj) +{ + unsigned long uptr = (unsigned long)obj; + + uptr -= offsetof(struct ynl_dump_list_type, data); + return uptr == (unsigned long)YNL_LIST_END; +} + +static inline void *ynl_dump_obj_next(void *obj) +{ + unsigned long uptr = (unsigned long)obj; + struct ynl_dump_list_type *list; + + uptr -= offsetof(struct ynl_dump_list_type, data); + list = (void *)uptr; + uptr = (unsigned long)list->next; + uptr += offsetof(struct ynl_dump_list_type, data); + + return (void *)uptr; +} + +struct ynl_ntf_base_type { + __u16 family; + __u8 cmd; + struct ynl_ntf_base_type *next; + void (*free)(struct ynl_ntf_base_type *ntf); + unsigned char data[] __attribute__ ((aligned (8))); +}; + +extern mnl_cb_t ynl_cb_array[NLMSG_MIN_TYPE]; + +struct nlmsghdr * +ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version); +struct nlmsghdr * +ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version); + +int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr); + +int ynl_recv_ack(struct ynl_sock *ys, int ret); +int ynl_cb_null(const struct nlmsghdr *nlh, void *data); + +/* YNL specific helpers used by the auto-generated code */ + +struct ynl_req_state { + struct ynl_parse_arg yarg; + mnl_cb_t cb; + __u32 rsp_cmd; +}; + +struct ynl_dump_state { + struct ynl_sock *ys; + struct ynl_policy_nest *rsp_policy; + void *first; + struct ynl_dump_list_type *last; + size_t alloc_sz; + mnl_cb_t cb; + __u32 rsp_cmd; +}; + +struct ynl_ntf_info { + struct ynl_policy_nest *policy; + mnl_cb_t cb; + size_t alloc_sz; + void (*free)(struct ynl_ntf_base_type *ntf); +}; + +int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh, + struct ynl_req_state *yrs); +int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh, + struct ynl_dump_state *yds); + +void ynl_error_unknown_notification(struct ynl_sock *ys, __u8 cmd); +int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg); + +#endif diff --git a/tools/net/ynl/lib/ynl.py b/tools/net/ynl/lib/ynl.py index aa77bcae4807..1b3a36fbb1c3 100644 --- a/tools/net/ynl/lib/ynl.py +++ b/tools/net/ynl/lib/ynl.py @@ -1,11 +1,15 @@ # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +from collections import namedtuple import functools import os import random import socket import struct +from struct import Struct import yaml +import ipaddress +import uuid from .nlspec import SpecFamily @@ -76,10 +80,17 @@ class NlError(Exception): class NlAttr: - type_formats = { 'u8' : ('B', 1), 's8' : ('b', 1), - 'u16': ('H', 2), 's16': ('h', 2), - 'u32': ('I', 4), 's32': ('i', 4), - 'u64': ('Q', 8), 's64': ('q', 8) } + ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) + type_formats = { + 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), + 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), + 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("<H")), + 's16': ScalarFormat(Struct('h'), Struct(">h"), Struct("<h")), + 'u32': ScalarFormat(Struct('I'), Struct(">I"), Struct("<I")), + 's32': ScalarFormat(Struct('i'), Struct(">i"), Struct("<i")), + 'u64': ScalarFormat(Struct('Q'), Struct(">Q"), Struct("<Q")), + 's64': ScalarFormat(Struct('q'), Struct(">q"), Struct("<q")) + } def __init__(self, raw, offset): self._len, self._type = struct.unpack("HH", raw[offset:offset + 4]) @@ -88,25 +99,31 @@ class NlAttr: self.full_len = (self.payload_len + 3) & ~3 self.raw = raw[offset + 4:offset + self.payload_len] - def format_byte_order(byte_order): + @classmethod + def get_format(cls, attr_type, byte_order=None): + format = cls.type_formats[attr_type] if byte_order: - return ">" if byte_order == "big-endian" else "<" - return "" - - def as_u8(self): - return struct.unpack("B", self.raw)[0] - - def as_u16(self, byte_order=None): - endian = NlAttr.format_byte_order(byte_order) - return struct.unpack(f"{endian}H", self.raw)[0] - - def as_u32(self, byte_order=None): - endian = NlAttr.format_byte_order(byte_order) - return struct.unpack(f"{endian}I", self.raw)[0] + return format.big if byte_order == "big-endian" \ + else format.little + return format.native + + @classmethod + def formatted_string(cls, raw, display_hint): + if display_hint == 'mac': + formatted = ':'.join('%02x' % b for b in raw) + elif display_hint == 'hex': + formatted = bytes.hex(raw, ' ') + elif display_hint in [ 'ipv4', 'ipv6' ]: + formatted = format(ipaddress.ip_address(raw)) + elif display_hint == 'uuid': + formatted = str(uuid.UUID(bytes=raw)) + else: + formatted = raw + return formatted - def as_u64(self, byte_order=None): - endian = NlAttr.format_byte_order(byte_order) - return struct.unpack(f"{endian}Q", self.raw)[0] + def as_scalar(self, attr_type, byte_order=None): + format = self.get_format(attr_type, byte_order) + return format.unpack(self.raw)[0] def as_strz(self): return self.raw.decode('ascii')[:-1] @@ -115,18 +132,24 @@ class NlAttr: return self.raw def as_c_array(self, type): - format, _ = self.type_formats[type] - return list({ x[0] for x in struct.iter_unpack(format, self.raw) }) + format = self.get_format(type) + return [ x[0] for x in format.iter_unpack(self.raw) ] def as_struct(self, members): value = dict() offset = 0 for m in members: # TODO: handle non-scalar members - format, size = self.type_formats[m.type] - decoded = struct.unpack_from(format, self.raw, offset) - offset += size - value[m.name] = decoded[0] + if m.type == 'binary': + decoded = self.raw[offset:offset+m['len']] + offset += m['len'] + elif m.type in NlAttr.type_formats: + format = self.get_format(m.type, m.byte_order) + [ decoded ] = format.unpack_from(self.raw, offset) + offset += format.size + if m.display_hint: + decoded = self.formatted_string(decoded, m.display_hint) + value[m.name] = decoded return value def __repr__(self): @@ -184,11 +207,11 @@ class NlMsg: if extack.type == Netlink.NLMSGERR_ATTR_MSG: self.extack['msg'] = extack.as_strz() elif extack.type == Netlink.NLMSGERR_ATTR_MISS_TYPE: - self.extack['miss-type'] = extack.as_u32() + self.extack['miss-type'] = extack.as_scalar('u32') elif extack.type == Netlink.NLMSGERR_ATTR_MISS_NEST: - self.extack['miss-nest'] = extack.as_u32() + self.extack['miss-nest'] = extack.as_scalar('u32') elif extack.type == Netlink.NLMSGERR_ATTR_OFFS: - self.extack['bad-attr-offs'] = extack.as_u32() + self.extack['bad-attr-offs'] = extack.as_scalar('u32') else: if 'unknown' not in self.extack: self.extack['unknown'] = [] @@ -272,11 +295,11 @@ def _genl_load_families(): fam = dict() for attr in gm.raw_attrs: if attr.type == Netlink.CTRL_ATTR_FAMILY_ID: - fam['id'] = attr.as_u16() + fam['id'] = attr.as_scalar('u16') elif attr.type == Netlink.CTRL_ATTR_FAMILY_NAME: fam['name'] = attr.as_strz() elif attr.type == Netlink.CTRL_ATTR_MAXATTR: - fam['maxattr'] = attr.as_u32() + fam['maxattr'] = attr.as_scalar('u32') elif attr.type == Netlink.CTRL_ATTR_MCAST_GROUPS: fam['mcast'] = dict() for entry in NlAttrs(attr.raw): @@ -286,7 +309,7 @@ def _genl_load_families(): if entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_NAME: mcast_name = entry_attr.as_strz() elif entry_attr.type == Netlink.CTRL_ATTR_MCAST_GRP_ID: - mcast_id = entry_attr.as_u32() + mcast_id = entry_attr.as_scalar('u32') if mcast_name and mcast_id is not None: fam['mcast'][mcast_name] = mcast_id if 'name' in fam and 'id' in fam: @@ -304,9 +327,9 @@ class GenlMsg: self.fixed_header_attrs = dict() for m in fixed_header_members: - format, size = NlAttr.type_formats[m.type] - decoded = struct.unpack_from(format, nl_msg.raw, offset) - offset += size + format = NlAttr.get_format(m.type, m.byte_order) + decoded = format.unpack_from(nl_msg.raw, offset) + offset += format.size self.fixed_header_attrs[m.name] = decoded[0] self.raw = nl_msg.raw[offset:] @@ -381,21 +404,13 @@ class YnlFamily(SpecFamily): attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) elif attr["type"] == 'flag': attr_payload = b'' - elif attr["type"] == 'u8': - attr_payload = struct.pack("B", int(value)) - elif attr["type"] == 'u16': - endian = NlAttr.format_byte_order(attr.byte_order) - attr_payload = struct.pack(f"{endian}H", int(value)) - elif attr["type"] == 'u32': - endian = NlAttr.format_byte_order(attr.byte_order) - attr_payload = struct.pack(f"{endian}I", int(value)) - elif attr["type"] == 'u64': - endian = NlAttr.format_byte_order(attr.byte_order) - attr_payload = struct.pack(f"{endian}Q", int(value)) elif attr["type"] == 'string': attr_payload = str(value).encode('ascii') + b'\x00' elif attr["type"] == 'binary': - attr_payload = value + attr_payload = bytes.fromhex(value) + elif attr['type'] in NlAttr.type_formats: + format = NlAttr.get_format(attr['type'], attr.byte_order) + attr_payload = format.pack(int(value)) else: raise Exception(f'Unknown type at {space} {name} {value} {attr["type"]}') @@ -419,11 +434,17 @@ class YnlFamily(SpecFamily): def _decode_binary(self, attr, attr_spec): if attr_spec.struct_name: - decoded = attr.as_struct(self.consts[attr_spec.struct_name]) + members = self.consts[attr_spec.struct_name] + decoded = attr.as_struct(members) + for m in members: + if m.enum: + self._decode_enum(decoded, m) elif attr_spec.sub_type: decoded = attr.as_c_array(attr_spec.sub_type) else: decoded = attr.as_bin() + if attr_spec.display_hint: + decoded = NlAttr.formatted_string(decoded, attr_spec.display_hint) return decoded def _decode(self, attrs, space): @@ -434,22 +455,16 @@ class YnlFamily(SpecFamily): if attr_spec["type"] == 'nest': subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) decoded = subdict - elif attr_spec['type'] == 'u8': - decoded = attr.as_u8() - elif attr_spec['type'] == 'u16': - decoded = attr.as_u16(attr_spec.byte_order) - elif attr_spec['type'] == 'u32': - decoded = attr.as_u32(attr_spec.byte_order) - elif attr_spec['type'] == 'u64': - decoded = attr.as_u64(attr_spec.byte_order) elif attr_spec["type"] == 'string': decoded = attr.as_strz() elif attr_spec["type"] == 'binary': decoded = self._decode_binary(attr, attr_spec) elif attr_spec["type"] == 'flag': decoded = True + elif attr_spec["type"] in NlAttr.type_formats: + decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) else: - raise Exception(f'Unknown {attr.type} {attr_spec["name"]} {attr_spec["type"]}') + raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') if not attr_spec.is_multi: rsp[attr_spec['name']] = decoded @@ -554,9 +569,9 @@ class YnlFamily(SpecFamily): if op.fixed_header: fixed_header_members = self.consts[op.fixed_header].members for m in fixed_header_members: - value = vals.pop(m.name) - format, _ = NlAttr.type_formats[m.type] - msg += struct.pack(format, value) + value = vals.pop(m.name) if m.name in vals else 0 + format = NlAttr.get_format(m.type, m.byte_order) + msg += format.pack(value) for name, value in vals.items(): msg += self._add_attr(op.attr_set.name, name, value) msg = _genl_msg_finalize(msg) @@ -591,8 +606,9 @@ class YnlFamily(SpecFamily): print('Unexpected message: ' + repr(gm)) continue - rsp.append(self._decode(gm.raw_attrs, op.attr_set.name) - | gm.fixed_header_attrs) + rsp_msg = self._decode(gm.raw_attrs, op.attr_set.name) + rsp_msg.update(gm.fixed_header_attrs) + rsp.append(rsp_msg) if not rsp: return None diff --git a/tools/net/ynl/samples/.gitignore b/tools/net/ynl/samples/.gitignore new file mode 100644 index 000000000000..2aae60c4829f --- /dev/null +++ b/tools/net/ynl/samples/.gitignore @@ -0,0 +1,3 @@ +ethtool +devlink +netdev diff --git a/tools/net/ynl/samples/Makefile b/tools/net/ynl/samples/Makefile new file mode 100644 index 000000000000..f2db8bb78309 --- /dev/null +++ b/tools/net/ynl/samples/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 + +include ../Makefile.deps + +CC=gcc +CFLAGS=-std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow \ + -I../lib/ -I../generated/ -idirafter $(UAPI_PATH) +ifeq ("$(DEBUG)","1") + CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan +endif + +LDLIBS=-lmnl ../lib/ynl.a ../generated/protos.a + +SRCS=$(wildcard *.c) +BINS=$(patsubst %.c,%,${SRCS}) + +include $(wildcard *.d) + +all: $(BINS) + +$(BINS): ../lib/ynl.a ../generated/protos.a + +clean: + rm -f *.o *.d *~ + +hardclean: clean + rm -f $(BINS) + +.PHONY: all clean +.DEFAULT_GOAL=all diff --git a/tools/net/ynl/samples/devlink.c b/tools/net/ynl/samples/devlink.c new file mode 100644 index 000000000000..d2611d7ebab4 --- /dev/null +++ b/tools/net/ynl/samples/devlink.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include "devlink-user.h" + +int main(int argc, char **argv) +{ + struct devlink_get_list *devs; + struct ynl_sock *ys; + + ys = ynl_sock_create(&ynl_devlink_family, NULL); + if (!ys) + return 1; + + devs = devlink_get_dump(ys); + if (!devs) + goto err_close; + + ynl_dump_foreach(devs, d) { + struct devlink_info_get_req *info_req; + struct devlink_info_get_rsp *info_rsp; + + printf("%s/%s:\n", d->bus_name, d->dev_name); + + info_req = devlink_info_get_req_alloc(); + devlink_info_get_req_set_bus_name(info_req, d->bus_name); + devlink_info_get_req_set_dev_name(info_req, d->dev_name); + + info_rsp = devlink_info_get(ys, info_req); + devlink_info_get_req_free(info_req); + if (!info_rsp) + goto err_free_devs; + + if (info_rsp->_present.info_driver_name_len) + printf(" driver: %s\n", info_rsp->info_driver_name); + if (info_rsp->n_info_version_running) + printf(" running fw:\n"); + for (unsigned i = 0; i < info_rsp->n_info_version_running; i++) + printf(" %s: %s\n", + info_rsp->info_version_running[i].info_version_name, + info_rsp->info_version_running[i].info_version_value); + printf(" ...\n"); + devlink_info_get_rsp_free(info_rsp); + } + devlink_get_list_free(devs); + + ynl_sock_destroy(ys); + + return 0; + +err_free_devs: + devlink_get_list_free(devs); +err_close: + fprintf(stderr, "YNL: %s\n", ys->err.msg); + ynl_sock_destroy(ys); + return 2; +} diff --git a/tools/net/ynl/samples/ethtool.c b/tools/net/ynl/samples/ethtool.c new file mode 100644 index 000000000000..a7ebbd1b98db --- /dev/null +++ b/tools/net/ynl/samples/ethtool.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <net/if.h> + +#include "ethtool-user.h" + +int main(int argc, char **argv) +{ + struct ethtool_channels_get_req_dump creq = {}; + struct ethtool_rings_get_req_dump rreq = {}; + struct ethtool_channels_get_list *channels; + struct ethtool_rings_get_list *rings; + struct ynl_sock *ys; + + ys = ynl_sock_create(&ynl_ethtool_family, NULL); + if (!ys) + return 1; + + creq._present.header = 1; /* ethtool needs an empty nest, sigh */ + channels = ethtool_channels_get_dump(ys, &creq); + if (!channels) + goto err_close; + + printf("Channels:\n"); + ynl_dump_foreach(channels, dev) { + printf(" %8s: ", dev->header.dev_name); + if (dev->_present.rx_count) + printf("rx %d ", dev->rx_count); + if (dev->_present.tx_count) + printf("tx %d ", dev->tx_count); + if (dev->_present.combined_count) + printf("combined %d ", dev->combined_count); + printf("\n"); + } + ethtool_channels_get_list_free(channels); + + rreq._present.header = 1; /* ethtool needs an empty nest.. */ + rings = ethtool_rings_get_dump(ys, &rreq); + if (!rings) + goto err_close; + + printf("Rings:\n"); + ynl_dump_foreach(rings, dev) { + printf(" %8s: ", dev->header.dev_name); + if (dev->_present.rx) + printf("rx %d ", dev->rx); + if (dev->_present.tx) + printf("tx %d ", dev->tx); + printf("\n"); + } + ethtool_rings_get_list_free(rings); + + ynl_sock_destroy(ys); + + return 0; + +err_close: + fprintf(stderr, "YNL (%d): %s\n", ys->err.code, ys->err.msg); + ynl_sock_destroy(ys); + return 2; +} diff --git a/tools/net/ynl/samples/netdev.c b/tools/net/ynl/samples/netdev.c new file mode 100644 index 000000000000..d31268aa47c5 --- /dev/null +++ b/tools/net/ynl/samples/netdev.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <net/if.h> + +#include "netdev-user.h" + +/* netdev genetlink family code sample + * This sample shows off basics of the netdev family but also notification + * handling, hence the somewhat odd UI. We subscribe to notifications first + * then wait for ifc selection, so the socket may already accumulate + * notifications as we wait. This allows us to test that YNL can handle + * requests and notifications getting interleaved. + */ + +static void netdev_print_device(struct netdev_dev_get_rsp *d, unsigned int op) +{ + char ifname[IF_NAMESIZE]; + const char *name; + + if (!d->_present.ifindex) + return; + + name = if_indextoname(d->ifindex, ifname); + if (name) + printf("%8s", name); + printf("[%d]\t", d->ifindex); + + if (!d->_present.xdp_features) + return; + + printf("%llx:", d->xdp_features); + for (int i = 0; d->xdp_features > 1U << i; i++) { + if (d->xdp_features & (1U << i)) + printf(" %s", netdev_xdp_act_str(1 << i)); + } + + name = netdev_op_str(op); + if (name) + printf(" (ntf: %s)", name); + printf("\n"); +} + +int main(int argc, char **argv) +{ + struct netdev_dev_get_list *devs; + struct ynl_ntf_base_type *ntf; + struct ynl_error yerr; + struct ynl_sock *ys; + int ifindex = 0; + + if (argc > 1) + ifindex = strtol(argv[1], NULL, 0); + + ys = ynl_sock_create(&ynl_netdev_family, &yerr); + if (!ys) { + fprintf(stderr, "YNL: %s\n", yerr.msg); + return 1; + } + + if (ynl_subscribe(ys, "mgmt")) + goto err_close; + + printf("Select ifc ($ifindex; or 0 = dump; or -2 ntf check): "); + scanf("%d", &ifindex); + + if (ifindex > 0) { + struct netdev_dev_get_req *req; + struct netdev_dev_get_rsp *d; + + req = netdev_dev_get_req_alloc(); + netdev_dev_get_req_set_ifindex(req, ifindex); + + d = netdev_dev_get(ys, req); + netdev_dev_get_req_free(req); + if (!d) + goto err_close; + + netdev_print_device(d, 0); + netdev_dev_get_rsp_free(d); + } else if (!ifindex) { + devs = netdev_dev_get_dump(ys); + if (!devs) + goto err_close; + + ynl_dump_foreach(devs, d) + netdev_print_device(d, 0); + netdev_dev_get_list_free(devs); + } else if (ifindex == -2) { + ynl_ntf_check(ys); + } + while ((ntf = ynl_ntf_dequeue(ys))) { + netdev_print_device((struct netdev_dev_get_rsp *)&ntf->data, + ntf->cmd); + ynl_ntf_free(ntf); + } + + ynl_sock_destroy(ys); + return 0; + +err_close: + fprintf(stderr, "YNL: %s\n", ys->err.msg); + ynl_sock_destroy(ys); + return 2; +} diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py index cc2f8c945340..71c5e79e877f 100755 --- a/tools/net/ynl/ynl-gen-c.py +++ b/tools/net/ynl/ynl-gen-c.py @@ -4,6 +4,7 @@ import argparse import collections import os +import re import yaml from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry @@ -48,6 +49,11 @@ class Type(SpecAttr): else: self.nested_render_name = f"{family.name}_{c_lower(self.nested_attrs)}" + if self.nested_attrs in self.family.consts: + self.nested_struct_type = 'struct ' + self.nested_render_name + '_' + else: + self.nested_struct_type = 'struct ' + self.nested_render_name + self.c_name = c_lower(self.name) if self.c_name in _C_KW: self.c_name += '_' @@ -57,8 +63,11 @@ class Type(SpecAttr): delattr(self, "enum_name") def resolve(self): - self.enum_name = f"{self.attr_set.name_prefix}{self.name}" - self.enum_name = c_upper(self.enum_name) + if 'name-prefix' in self.attr: + enum_name = f"{self.attr['name-prefix']}{self.name}" + else: + enum_name = f"{self.attr_set.name_prefix}{self.name}" + self.enum_name = c_upper(enum_name) def is_multi_val(self): return None @@ -94,7 +103,10 @@ class Type(SpecAttr): def arg_member(self, ri): member = self._complex_member_type(ri) if member: - return [member + ' *' + self.c_name] + arg = [member + ' *' + self.c_name] + if self.presence_type() == 'count': + arg += ['unsigned int n_' + self.c_name] + return arg raise Exception(f"Struct member not implemented for class type {self.type}") def struct_member(self, ri): @@ -150,7 +162,7 @@ class Type(SpecAttr): init_lines = [init_lines] kw = 'if' if first else 'else if' - ri.cw.block_start(line=f"{kw} (mnl_attr_get_type(attr) == {self.enum_name})") + ri.cw.block_start(line=f"{kw} (type == {self.enum_name})") if local_vars: for local in local_vars: ri.cw.p(local) @@ -170,6 +182,7 @@ class Type(SpecAttr): for line in lines: ri.cw.p(line) ri.cw.block_end() + return True def _setter_lines(self, ri, member, presence): raise Exception(f"Setter not implemented for class type {self.type}") @@ -187,9 +200,12 @@ class Type(SpecAttr): code.append(presence + ' = 1;') code += self._setter_lines(ri, member, presence) - ri.cw.write_func('static inline void', - f"{op_prefix(ri, direction, deref=deref)}_set_{'_'.join(ref)}", - body=code, + func_name = f"{op_prefix(ri, direction, deref=deref)}_set_{'_'.join(ref)}" + free = bool([x for x in code if 'free(' in x]) + alloc = bool([x for x in code if 'alloc(' in x]) + if free and not alloc: + func_name = '__' + func_name + ri.cw.write_func('static inline void', func_name, body=code, args=[f'{type_name(ri, direction, deref=deref)} *{var}'] + self.arg_member(ri)) @@ -197,6 +213,12 @@ class TypeUnused(Type): def presence_type(self): return '' + def arg_member(self, ri): + return [] + + def _attr_get(self, ri, var): + return ['return MNL_CB_ERROR;'], None, None + def _attr_typol(self): return '.type = YNL_PT_REJECT, ' @@ -208,12 +230,24 @@ class TypePad(Type): def presence_type(self): return '' + def arg_member(self, ri): + return [] + def _attr_typol(self): - return '.type = YNL_PT_REJECT, ' + return '.type = YNL_PT_IGNORE, ' + + def attr_put(self, ri, var): + pass + + def attr_get(self, ri, var, first): + pass def attr_policy(self, cw): pass + def setter(self, ri, space, direction, deref=False, ref=None): + pass + class TypeScalar(Type): def __init__(self, family, attr_set, attr, value): @@ -239,7 +273,8 @@ class TypeScalar(Type): else: self.is_bitfield = False - if 'enum' in self.attr and not self.is_bitfield: + maybe_enum = not self.is_bitfield and 'enum' in self.attr + if maybe_enum and self.family.consts[self.attr['enum']].enum_name: self.type_name = f"enum {self.family.name}_{c_lower(self.attr['enum'])}" else: self.type_name = '__' + self.type @@ -265,8 +300,10 @@ class TypeScalar(Type): return f"NLA_POLICY_MIN({policy}, {self.checks['min']})" elif 'enum' in self.attr: enum = self.family.consts[self.attr['enum']] - cnt = len(enum['entries']) - return f"NLA_POLICY_MAX({policy}, {cnt - 1})" + low, high = enum.value_range() + if low == 0: + return f"NLA_POLICY_MAX({policy}, {high})" + return f"NLA_POLICY_RANGE({policy}, {low}, {high})" return super()._attr_policy(policy) def _attr_typol(self): @@ -395,7 +432,7 @@ class TypeBinary(Type): class TypeNest(Type): def _complex_member_type(self, ri): - return f"struct {self.nested_render_name}" + return self.nested_struct_type def free(self, ri, var, ref): ri.cw.p(f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name});') @@ -411,7 +448,8 @@ class TypeNest(Type): f"{self.enum_name}, &{var}->{self.c_name})") def _attr_get(self, ri, var): - get_lines = [f"{self.nested_render_name}_parse(&parg, attr);"] + get_lines = [f"if ({self.nested_render_name}_parse(&parg, attr))", + "return MNL_CB_ERROR;"] init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", f"parg.data = &{var}->{self.c_name};"] return get_lines, init_lines, None @@ -424,15 +462,27 @@ class TypeNest(Type): class TypeMultiAttr(Type): + def __init__(self, family, attr_set, attr, value, base_type): + super().__init__(family, attr_set, attr, value) + + self.base_type = base_type + def is_multi_val(self): return True def presence_type(self): return 'count' + def _mnl_type(self): + t = self.type + # mnl does not have a helper for signed types + if t[0] == 's': + t = 'u' + t[1:] + return t + def _complex_member_type(self, ri): if 'type' not in self.attr or self.attr['type'] == 'nest': - return f"struct {self.nested_render_name}" + return self.nested_struct_type elif self.attr['type'] in scalars: scalar_pfx = '__' if ri.ku_space == 'user' else '' return scalar_pfx + self.attr['type'] @@ -443,20 +493,42 @@ class TypeMultiAttr(Type): return 'type' not in self.attr or self.attr['type'] == 'nest' def free(self, ri, var, ref): - if 'type' not in self.attr or self.attr['type'] == 'nest': + if self.attr['type'] in scalars: + ri.cw.p(f"free({var}->{ref}{self.c_name});") + elif 'type' not in self.attr or self.attr['type'] == 'nest': ri.cw.p(f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)") ri.cw.p(f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);') + ri.cw.p(f"free({var}->{ref}{self.c_name});") + else: + raise Exception(f"Free of MultiAttr sub-type {self.attr['type']} not supported yet") + + def _attr_policy(self, policy): + return self.base_type._attr_policy(policy) def _attr_typol(self): - if 'type' not in self.attr or self.attr['type'] == 'nest': - return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' - elif self.attr['type'] in scalars: - return f".type = YNL_PT_U{self.attr['type'][1:]}, " - else: - raise Exception(f"Sub-type {self.attr['type']} not supported yet") + return self.base_type._attr_typol() def _attr_get(self, ri, var): - return f'{var}->n_{self.c_name}++;', None, None + return f'n_{self.c_name}++;', None, None + + def attr_put(self, ri, var): + if self.attr['type'] in scalars: + put_type = self._mnl_type() + ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)") + ri.cw.p(f"mnl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);") + elif 'type' not in self.attr or self.attr['type'] == 'nest': + ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)") + self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " + + f"{self.enum_name}, &{var}->{self.c_name}[i])") + else: + raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet") + + def _setter_lines(self, ri, member, presence): + # For multi-attr we have a count, not presence, hack up the presence + presence = presence[:-(len('_present.') + len(self.c_name))] + "n_" + self.c_name + return [f"free({member});", + f"{member} = {self.c_name};", + f"{presence} = n_{self.c_name};"] class TypeArrayNest(Type): @@ -468,7 +540,7 @@ class TypeArrayNest(Type): def _complex_member_type(self, ri): if 'sub-type' not in self.attr or self.attr['sub-type'] == 'nest': - return f"struct {self.nested_render_name}" + return self.nested_struct_type elif self.attr['sub-type'] in scalars: scalar_pfx = '__' if ri.ku_space == 'user' else '' return scalar_pfx + self.attr['sub-type'] @@ -488,7 +560,7 @@ class TypeArrayNest(Type): class TypeNestTypeValue(Type): def _complex_member_type(self, ri): - return f"struct {self.nested_render_name}" + return self.nested_struct_type def _attr_typol(self): return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' @@ -531,6 +603,8 @@ class Struct: else: self.render_name = f"{family.name}_{c_lower(space_name)}" self.struct_name = 'struct ' + self.render_name + if self.nested and space_name in family.consts: + self.struct_name += '_' self.ptr_name = self.struct_name + ' *' self.request = False @@ -591,7 +665,14 @@ class EnumEntry(SpecEnumEntry): class EnumSet(SpecEnumSet): def __init__(self, family, yaml): self.render_name = c_lower(family.name + '-' + yaml['name']) - self.enum_name = 'enum ' + self.render_name + + if 'enum-name' in yaml: + if yaml['enum-name']: + self.enum_name = 'enum ' + c_lower(yaml['enum-name']) + else: + self.enum_name = None + else: + self.enum_name = 'enum ' + self.render_name self.value_pfx = yaml.get('name-prefix', f"{family.name}-{yaml['name']}-") @@ -600,6 +681,15 @@ class EnumSet(SpecEnumSet): def new_entry(self, entry, prev_entry, value_start): return EnumEntry(self, entry, prev_entry, value_start) + def value_range(self): + low = min([x.value for x in self.entries.values()]) + high = max([x.value for x in self.entries.values()]) + + if high - low + 1 != len(self.entries): + raise Exception("Can't get value range for a noncontiguous enum") + + return low, high + class AttrSet(SpecAttrSet): def __init__(self, family, yaml): @@ -630,42 +720,44 @@ class AttrSet(SpecAttrSet): self.c_name = '' def new_attr(self, elem, value): - if 'multi-attr' in elem and elem['multi-attr']: - return TypeMultiAttr(self.family, self, elem, value) - elif elem['type'] in scalars: - return TypeScalar(self.family, self, elem, value) + if elem['type'] in scalars: + t = TypeScalar(self.family, self, elem, value) elif elem['type'] == 'unused': - return TypeUnused(self.family, self, elem, value) + t = TypeUnused(self.family, self, elem, value) elif elem['type'] == 'pad': - return TypePad(self.family, self, elem, value) + t = TypePad(self.family, self, elem, value) elif elem['type'] == 'flag': - return TypeFlag(self.family, self, elem, value) + t = TypeFlag(self.family, self, elem, value) elif elem['type'] == 'string': - return TypeString(self.family, self, elem, value) + t = TypeString(self.family, self, elem, value) elif elem['type'] == 'binary': - return TypeBinary(self.family, self, elem, value) + t = TypeBinary(self.family, self, elem, value) elif elem['type'] == 'nest': - return TypeNest(self.family, self, elem, value) + t = TypeNest(self.family, self, elem, value) elif elem['type'] == 'array-nest': - return TypeArrayNest(self.family, self, elem, value) + t = TypeArrayNest(self.family, self, elem, value) elif elem['type'] == 'nest-type-value': - return TypeNestTypeValue(self.family, self, elem, value) + t = TypeNestTypeValue(self.family, self, elem, value) else: raise Exception(f"No typed class for type {elem['type']}") + if 'multi-attr' in elem and elem['multi-attr']: + t = TypeMultiAttr(self.family, self, elem, value, t) + + return t + class Operation(SpecOperation): def __init__(self, family, yaml, req_value, rsp_value): super().__init__(family, yaml, req_value, rsp_value) - if req_value != rsp_value: - raise Exception("Directional messages not supported by codegen") - self.render_name = family.name + '_' + c_lower(self.name) self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \ ('dump' in yaml and 'request' in yaml['dump']) + self.has_ntf = False + # Added by resolve: self.enum_name = None delattr(self, "enum_name") @@ -678,16 +770,12 @@ class Operation(SpecOperation): else: self.enum_name = self.family.async_op_prefix + c_upper(self.name) - def add_notification(self, op): - if 'notify' not in self.yaml: - self.yaml['notify'] = dict() - self.yaml['notify']['reply'] = self.yaml['do']['reply'] - self.yaml['notify']['cmds'] = [] - self.yaml['notify']['cmds'].append(op) + def mark_has_ntf(self): + self.has_ntf = True class Family(SpecFamily): - def __init__(self, file_name): + def __init__(self, file_name, exclude_ops): # Added by resolve: self.c_name = None delattr(self, "c_name") @@ -702,7 +790,7 @@ class Family(SpecFamily): self.hooks = None delattr(self, "hooks") - super().__init__(file_name) + super().__init__(file_name, exclude_ops=exclude_ops) self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME')) self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION')) @@ -745,14 +833,12 @@ class Family(SpecFamily): self.root_sets = dict() # dict space-name -> set('request', 'reply') self.pure_nested_structs = dict() - self.all_notify = dict() + self._mark_notify() self._mock_up_events() - self._dictify() self._load_root_sets() self._load_nested_sets() - self._load_all_notify() self._load_hooks() self.kernel_policy = self.yaml.get('kernel-policy', 'split') @@ -768,6 +854,11 @@ class Family(SpecFamily): def new_operation(self, elem, req_value, rsp_value): return Operation(self, elem, req_value, rsp_value) + def _mark_notify(self): + for op in self.msgs.values(): + if 'notify' in op: + self.ops[op['notify']].mark_has_ntf() + # Fake a 'do' equivalent of all events, so that we can render their response parsing def _mock_up_events(self): for op in self.yaml['operations']['list']: @@ -778,16 +869,8 @@ class Family(SpecFamily): } } - def _dictify(self): - ntf = [] - for msg in self.msgs.values(): - if 'notify' in msg: - ntf.append(msg) - for n in ntf: - self.ops[n['notify']].add_notification(n) - def _load_root_sets(self): - for op_name, op in self.ops.items(): + for op_name, op in self.msgs.items(): if 'attribute-set' not in op: continue @@ -798,6 +881,8 @@ class Family(SpecFamily): req_attrs.update(set(op[op_mode]['request']['attributes'])) if op_mode in op and 'reply' in op[op_mode]: rsp_attrs.update(set(op[op_mode]['reply']['attributes'])) + if 'event' in op: + rsp_attrs.update(set(op['event']['attributes'])) if op['attribute-set'] not in self.root_sets: self.root_sets[op['attribute-set']] = {'request': req_attrs, 'reply': rsp_attrs} @@ -806,33 +891,73 @@ class Family(SpecFamily): self.root_sets[op['attribute-set']]['reply'].update(rsp_attrs) def _load_nested_sets(self): + attr_set_queue = list(self.root_sets.keys()) + attr_set_seen = set(self.root_sets.keys()) + + while len(attr_set_queue): + a_set = attr_set_queue.pop(0) + for attr, spec in self.attr_sets[a_set].items(): + if 'nested-attributes' not in spec: + continue + + nested = spec['nested-attributes'] + if nested not in attr_set_seen: + attr_set_queue.append(nested) + attr_set_seen.add(nested) + + inherit = set() + if nested not in self.root_sets: + if nested not in self.pure_nested_structs: + self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) + else: + raise Exception(f'Using attr set as root and nested not supported - {nested}') + + if 'type-value' in spec: + if nested in self.root_sets: + raise Exception("Inheriting members to a space used as root not supported") + inherit.update(set(spec['type-value'])) + elif spec['type'] == 'array-nest': + inherit.add('idx') + self.pure_nested_structs[nested].set_inherited(inherit) + for root_set, rs_members in self.root_sets.items(): for attr, spec in self.attr_sets[root_set].items(): if 'nested-attributes' in spec: - inherit = set() nested = spec['nested-attributes'] - if nested not in self.root_sets: - self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) if attr in rs_members['request']: self.pure_nested_structs[nested].request = True if attr in rs_members['reply']: self.pure_nested_structs[nested].reply = True - if 'type-value' in spec: - if nested in self.root_sets: - raise Exception("Inheriting members to a space used as root not supported") - inherit.update(set(spec['type-value'])) - elif spec['type'] == 'array-nest': - inherit.add('idx') - self.pure_nested_structs[nested].set_inherited(inherit) - - def _load_all_notify(self): - for op_name, op in self.ops.items(): - if not op: - continue - - if 'notify' in op: - self.all_notify[op_name] = op['notify']['cmds'] + # Try to reorder according to dependencies + pns_key_list = list(self.pure_nested_structs.keys()) + pns_key_seen = set() + rounds = len(pns_key_list)**2 # it's basically bubble sort + for _ in range(rounds): + if len(pns_key_list) == 0: + break + name = pns_key_list.pop(0) + finished = True + for _, spec in self.attr_sets[name].items(): + if 'nested-attributes' in spec: + if spec['nested-attributes'] not in pns_key_seen: + # Dicts are sorted, this will make struct last + struct = self.pure_nested_structs.pop(name) + self.pure_nested_structs[name] = struct + finished = False + break + if finished: + pns_key_seen.add(name) + else: + pns_key_list.append(name) + # Propagate the request / reply + for attr_set, struct in reversed(self.pure_nested_structs.items()): + for _, spec in self.attr_sets[attr_set].items(): + if 'nested-attributes' in spec: + child = self.pure_nested_structs.get(spec['nested-attributes']) + if child: + child.request |= struct.request + child.reply |= struct.reply def _load_global_policy(self): global_set = set() @@ -874,33 +999,38 @@ class Family(SpecFamily): class RenderInfo: - def __init__(self, cw, family, ku_space, op, op_name, op_mode, attr_set=None): + def __init__(self, cw, family, ku_space, op, op_mode, attr_set=None): self.family = family self.nl = cw.nlib self.ku_space = ku_space - self.op = op - self.op_name = op_name self.op_mode = op_mode + self.op = op # 'do' and 'dump' response parsing is identical - if op_mode != 'do' and 'dump' in op and 'do' in op and 'reply' in op['do'] and \ - op["do"]["reply"] == op["dump"]["reply"]: - self.type_consistent = True - else: - self.type_consistent = op_mode == 'event' + self.type_consistent = True + if op_mode != 'do' and 'dump' in op and 'do' in op: + if ('reply' in op['do']) != ('reply' in op["dump"]): + self.type_consistent = False + elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]: + self.type_consistent = False self.attr_set = attr_set if not self.attr_set: self.attr_set = op['attribute-set'] + self.type_name_conflict = False if op: - self.type_name = c_lower(op_name) + self.type_name = c_lower(op.name) else: self.type_name = c_lower(attr_set) + if attr_set in family.consts: + self.type_name_conflict = True self.cw = cw self.struct = dict() + if op_mode == 'notify': + op_mode = 'do' for op_dir in ['request', 'reply']: if op and op_dir in op[op_mode]: self.struct[op_dir] = Struct(family, self.attr_set, @@ -914,6 +1044,7 @@ class CodeWriter: self.nlib = nlib self._nl = False + self._block_end = False self._silent_block = False self._ind = 0 self._out = out_file @@ -923,9 +1054,17 @@ class CodeWriter: return line.startswith('if') or line.startswith('while') or line.startswith('for') def p(self, line, add_ind=0): + if self._block_end: + self._block_end = False + if line.startswith('else'): + line = '} ' + line + else: + self._out.write('\t' * self._ind + '}\n') + if self._nl: self._out.write('\n') self._nl = False + ind = self._ind if line[-1] == ':': ind -= 1 @@ -949,7 +1088,14 @@ class CodeWriter: if line and line[0] not in {';', ','}: line = ' ' + line self._ind -= 1 - self.p('}' + line) + self._nl = False + if not line: + # Delay printing closing bracket in case "else" comes next + if self._block_end: + self._out.write('\t' * (self._ind + 1) + '}\n') + self._block_end = True + else: + self.p('}' + line) def write_doc_line(self, doc, indent=True): words = doc.split() @@ -1068,7 +1214,40 @@ op_mode_to_wrapper = { } _C_KW = { - 'do' + 'auto', + 'bool', + 'break', + 'case', + 'char', + 'const', + 'continue', + 'default', + 'do', + 'double', + 'else', + 'enum', + 'extern', + 'float', + 'for', + 'goto', + 'if', + 'inline', + 'int', + 'long', + 'register', + 'return', + 'short', + 'signed', + 'sizeof', + 'static', + 'struct', + 'switch', + 'typedef', + 'union', + 'unsigned', + 'void', + 'volatile', + 'while' } @@ -1131,10 +1310,6 @@ def print_dump_prototype(ri): print_prototype(ri, "request") -def put_typol_fwd(cw, struct): - cw.p(f'extern struct ynl_policy_nest {struct.render_name}_nest;') - - def put_typol(cw, struct): type_max = struct.attr_set.max_name cw.block_start(line=f'struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =') @@ -1152,6 +1327,58 @@ def put_typol(cw, struct): cw.nl() +def _put_enum_to_str_helper(cw, render_name, map_name, arg_name, enum=None): + args = [f'int {arg_name}'] + if enum and not ('enum-name' in enum and not enum['enum-name']): + args = [f'enum {render_name} {arg_name}'] + cw.write_func_prot('const char *', f'{render_name}_str', args) + cw.block_start() + if enum and enum.type == 'flags': + cw.p(f'{arg_name} = ffs({arg_name}) - 1;') + cw.p(f'if ({arg_name} < 0 || {arg_name} >= (int)MNL_ARRAY_SIZE({map_name}))') + cw.p('return NULL;') + cw.p(f'return {map_name}[{arg_name}];') + cw.block_end() + cw.nl() + + +def put_op_name_fwd(family, cw): + cw.write_func_prot('const char *', f'{family.name}_op_str', ['int op'], suffix=';') + + +def put_op_name(family, cw): + map_name = f'{family.name}_op_strmap' + cw.block_start(line=f"static const char * const {map_name}[] =") + for op_name, op in family.msgs.items(): + if op.rsp_value: + if op.req_value == op.rsp_value: + cw.p(f'[{op.enum_name}] = "{op_name}",') + else: + cw.p(f'[{op.rsp_value}] = "{op_name}",') + cw.block_end(line=';') + cw.nl() + + _put_enum_to_str_helper(cw, family.name + '_op', map_name, 'op') + + +def put_enum_to_str_fwd(family, cw, enum): + args = [f'enum {enum.render_name} value'] + if 'enum-name' in enum and not enum['enum-name']: + args = ['int value'] + cw.write_func_prot('const char *', f'{enum.render_name}_str', args, suffix=';') + + +def put_enum_to_str(family, cw, enum): + map_name = f'{enum.render_name}_strmap' + cw.block_start(line=f"static const char * const {map_name}[] =") + for entry in enum.entries.values(): + cw.p(f'[{entry.value}] = "{entry.name}",') + cw.block_end(line=';') + cw.nl() + + _put_enum_to_str_helper(cw, enum.render_name, map_name, 'value', enum=enum) + + def put_req_nested(ri, struct): func_args = ['struct nlmsghdr *nlh', 'unsigned int attr_type', @@ -1196,6 +1423,11 @@ def _multi_parse(ri, struct, init_lines, local_vars): local_vars.append('struct ynl_parse_arg parg;') init_lines.append('parg.ys = yarg->ys;') + all_multi = array_nests | multi_attrs + + for anest in sorted(all_multi): + local_vars.append(f"unsigned int n_{struct[anest].c_name} = 0;") + ri.cw.block_start() ri.cw.write_func_lvar(local_vars) @@ -1206,13 +1438,21 @@ def _multi_parse(ri, struct, init_lines, local_vars): for arg in struct.inherited: ri.cw.p(f'dst->{arg} = {arg};') + for anest in sorted(all_multi): + aspec = struct[anest] + ri.cw.p(f"if (dst->{aspec.c_name})") + ri.cw.p(f'return ynl_error_parse(yarg, "attribute already present ({struct.attr_set.name}.{aspec.name})");') + ri.cw.nl() ri.cw.block_start(line=iter_line) + ri.cw.p('unsigned int type = mnl_attr_get_type(attr);') + ri.cw.nl() first = True for _, arg in struct.member_list(): - arg.attr_get(ri, 'dst', first=first) - first = False + good = arg.attr_get(ri, 'dst', first=first) + # First may be 'unused' or 'pad', ignore those + first &= not good ri.cw.block_end() ri.cw.nl() @@ -1220,8 +1460,9 @@ def _multi_parse(ri, struct, init_lines, local_vars): for anest in sorted(array_nests): aspec = struct[anest] - ri.cw.block_start(line=f"if (dst->n_{aspec.c_name})") - ri.cw.p(f"dst->{aspec.c_name} = calloc(dst->n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.block_start(line=f"if (n_{aspec.c_name})") + ri.cw.p(f"dst->{aspec.c_name} = calloc({aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") ri.cw.block_start(line=f"mnl_attr_for_each_nested(attr, attr_{aspec.c_name})") @@ -1235,8 +1476,9 @@ def _multi_parse(ri, struct, init_lines, local_vars): for anest in sorted(multi_attrs): aspec = struct[anest] - ri.cw.block_start(line=f"if (dst->n_{aspec.c_name})") - ri.cw.p(f"dst->{aspec.c_name} = calloc(dst->n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.block_start(line=f"if (n_{aspec.c_name})") + ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") @@ -1304,13 +1546,13 @@ def print_req(ri): ret_err = '-1' direction = "request" local_vars = ['struct nlmsghdr *nlh;', - 'int len, err;'] + 'int err;'] if 'reply' in ri.op[ri.op_mode]: ret_ok = 'rsp' ret_err = 'NULL' local_vars += [f'{type_name(ri, rdir(direction))} *rsp;', - 'struct ynl_parse_arg yarg = { .ys = ys, };'] + 'struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };'] print_prototype(ri, direction, terminate=False) ri.cw.block_start() @@ -1320,41 +1562,39 @@ def print_req(ri): ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") if 'reply' in ri.op[ri.op_mode]: - ri.cw.p(f"yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") + ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") ri.cw.nl() for _, attr in ri.struct["request"].member_list(): attr.attr_put(ri, "req") ri.cw.nl() - ri.cw.p('err = mnl_socket_sendto(ys->sock, nlh, nlh->nlmsg_len);') - ri.cw.p('if (err < 0)') - ri.cw.p(f"return {ret_err};") - ri.cw.nl() - ri.cw.p('len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);') - ri.cw.p('if (len < 0)') - ri.cw.p(f"return {ret_err};") - ri.cw.nl() - + parse_arg = "NULL" if 'reply' in ri.op[ri.op_mode]: ri.cw.p('rsp = calloc(1, sizeof(*rsp));') - ri.cw.p('yarg.data = rsp;') + ri.cw.p('yrs.yarg.data = rsp;') + ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;") + if ri.op.value is not None: + ri.cw.p(f'yrs.rsp_cmd = {ri.op.enum_name};') + else: + ri.cw.p(f'yrs.rsp_cmd = {ri.op.rsp_value};') ri.cw.nl() - ri.cw.p(f"err = {ri.nl.parse_cb_run(op_prefix(ri, 'reply') + '_parse', '&yarg', False)};") - ri.cw.p('if (err < 0)') + parse_arg = '&yrs' + ri.cw.p(f"err = ynl_exec(ys, nlh, {parse_arg});") + ri.cw.p('if (err < 0)') + if 'reply' in ri.op[ri.op_mode]: ri.cw.p('goto err_free;') - ri.cw.nl() - - ri.cw.p('err = ynl_recv_ack(ys, err);') - ri.cw.p('if (err)') - ri.cw.p('goto err_free;') + else: + ri.cw.p('return -1;') ri.cw.nl() + ri.cw.p(f"return {ret_ok};") ri.cw.nl() - ri.cw.p('err_free:') if 'reply' in ri.op[ri.op_mode]: + ri.cw.p('err_free:') ri.cw.p(f"{call_free(ri, rdir(direction), 'rsp')}") - ri.cw.p(f"return {ret_err};") + ri.cw.p(f"return {ret_err};") + ri.cw.block_end() @@ -1364,7 +1604,7 @@ def print_dump(ri): ri.cw.block_start() local_vars = ['struct ynl_dump_state yds = {};', 'struct nlmsghdr *nlh;', - 'int len, err;'] + 'int err;'] for var in local_vars: ri.cw.p(f'{var}') @@ -1373,6 +1613,10 @@ def print_dump(ri): ri.cw.p('yds.ys = ys;') ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});") ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;") + if ri.op.value is not None: + ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};') + else: + ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};') ri.cw.p(f"yds.rsp_policy = &{ri.struct['reply'].render_name}_nest;") ri.cw.nl() ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") @@ -1384,20 +1628,9 @@ def print_dump(ri): attr.attr_put(ri, "req") ri.cw.nl() - ri.cw.p('err = mnl_socket_sendto(ys->sock, nlh, nlh->nlmsg_len);') - ri.cw.p('if (err < 0)') - ri.cw.p('return NULL;') - ri.cw.nl() - - ri.cw.block_start(line='do') - ri.cw.p('len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);') - ri.cw.p('if (len < 0)') - ri.cw.p('goto free_list;') - ri.cw.nl() - ri.cw.p(f"err = {ri.nl.parse_cb_run('ynl_dump_trampoline', '&yds', False, indent=2)};") + ri.cw.p('err = ynl_exec_dump(ys, nlh, &yds);') ri.cw.p('if (err < 0)') ri.cw.p('goto free_list;') - ri.cw.block_end(line='while (err > 0);') ri.cw.nl() ri.cw.p('return yds.first;') @@ -1418,14 +1651,27 @@ def free_arg_name(direction): return 'obj' +def print_alloc_wrapper(ri, direction): + name = op_prefix(ri, direction) + ri.cw.write_func_prot(f'static inline struct {name} *', f"{name}_alloc", [f"void"]) + ri.cw.block_start() + ri.cw.p(f'return calloc(1, sizeof(struct {name}));') + ri.cw.block_end() + + def print_free_prototype(ri, direction, suffix=';'): name = op_prefix(ri, direction) + struct_name = name + if ri.type_name_conflict: + struct_name += '_' arg = free_arg_name(direction) - ri.cw.write_func_prot('void', f"{name}_free", [f"struct {name} *{arg}"], suffix=suffix) + ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix) def _print_type(ri, direction, struct): suffix = f'_{ri.type_name}{direction_to_suffix[direction]}' + if not direction and ri.type_name_conflict: + suffix += '_' if ri.op_mode == 'dump': suffix += '_dump' @@ -1465,6 +1711,7 @@ def print_type_full(ri, struct): def print_type_helpers(ri, direction, deref=False): print_free_prototype(ri, direction) + ri.cw.nl() if ri.ku_space == 'user' and direction == 'request': for _, attr in ri.struct[direction].member_list(): @@ -1473,6 +1720,7 @@ def print_type_helpers(ri, direction, deref=False): def print_req_type_helpers(ri): + print_alloc_wrapper(ri, "request") print_type_helpers(ri, "request") @@ -1496,6 +1744,12 @@ def print_req_type(ri): print_type(ri, "request") +def print_req_free(ri): + if 'request' not in ri.op[ri.op_mode]: + return + _free_type(ri, 'request', ri.struct['request']) + + def print_rsp_type(ri): if (ri.op_mode == 'do' or ri.op_mode == 'dump') and 'reply' in ri.op[ri.op_mode]: direction = 'reply' @@ -1513,6 +1767,7 @@ def print_wrapped_type(ri): elif ri.op_mode == 'notify' or ri.op_mode == 'event': ri.cw.p('__u16 family;') ri.cw.p('__u8 cmd;') + ri.cw.p('struct ynl_ntf_base_type *next;') ri.cw.p(f"void (*free)({type_name(ri, 'reply')} *ntf);") ri.cw.p(f"{type_name(ri, 'reply', deref=True)} obj __attribute__ ((aligned (8)));") ri.cw.block_end(line=';') @@ -1564,7 +1819,7 @@ def print_dump_type_free(ri): ri.cw.block_start() ri.cw.p(f"{sub_type} *next = rsp;") ri.cw.nl() - ri.cw.block_start(line='while (next)') + ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)') _free_type_members_iter(ri, ri.struct['reply']) ri.cw.p('rsp = next;') ri.cw.p('next = rsp->next;') @@ -1587,70 +1842,6 @@ def print_ntf_type_free(ri): ri.cw.nl() -def print_ntf_parse_prototype(family, cw, suffix=';'): - cw.write_func_prot('struct ynl_ntf_base_type *', f"{family['name']}_ntf_parse", - ['struct ynl_sock *ys'], suffix=suffix) - - -def print_ntf_type_parse(family, cw, ku_mode): - print_ntf_parse_prototype(family, cw, suffix='') - cw.block_start() - cw.write_func_lvar(['struct genlmsghdr *genlh;', - 'struct nlmsghdr *nlh;', - 'struct ynl_parse_arg yarg = { .ys = ys, };', - 'struct ynl_ntf_base_type *rsp;', - 'int len, err;', - 'mnl_cb_t parse;']) - cw.p('len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);') - cw.p('if (len < (ssize_t)(sizeof(*nlh) + sizeof(*genlh)))') - cw.p('return NULL;') - cw.nl() - cw.p('nlh = (struct nlmsghdr *)ys->rx_buf;') - cw.p('genlh = mnl_nlmsg_get_payload(nlh);') - cw.nl() - cw.block_start(line='switch (genlh->cmd)') - for ntf_op in sorted(family.all_notify.keys()): - op = family.ops[ntf_op] - ri = RenderInfo(cw, family, ku_mode, op, ntf_op, "notify") - for ntf in op['notify']['cmds']: - cw.p(f"case {ntf.enum_name}:") - cw.p(f"rsp = calloc(1, sizeof({type_name(ri, 'notify')}));") - cw.p(f"parse = {op_prefix(ri, 'reply', deref=True)}_parse;") - cw.p(f"yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") - cw.p(f"rsp->free = (void *){op_prefix(ri, 'notify')}_free;") - cw.p('break;') - for op_name, op in family.ops.items(): - if 'event' not in op: - continue - ri = RenderInfo(cw, family, ku_mode, op, op_name, "event") - cw.p(f"case {op.enum_name}:") - cw.p(f"rsp = calloc(1, sizeof({type_name(ri, 'event')}));") - cw.p(f"parse = {op_prefix(ri, 'reply', deref=True)}_parse;") - cw.p(f"yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") - cw.p(f"rsp->free = (void *){op_prefix(ri, 'notify')}_free;") - cw.p('break;') - cw.p('default:') - cw.p('ynl_error_unknown_notification(ys, genlh->cmd);') - cw.p('return NULL;') - cw.block_end() - cw.nl() - cw.p('yarg.data = rsp->data;') - cw.nl() - cw.p(f"err = {cw.nlib.parse_cb_run('parse', '&yarg', True)};") - cw.p('if (err < 0)') - cw.p('goto err_free;') - cw.nl() - cw.p('rsp->family = nlh->nlmsg_type;') - cw.p('rsp->cmd = genlh->cmd;') - cw.p('return rsp;') - cw.nl() - cw.p('err_free:') - cw.p('free(rsp);') - cw.p('return NULL;') - cw.block_end() - cw.nl() - - def print_req_policy_fwd(cw, struct, ri=None, terminate=True): if terminate and ri and kernel_can_gen_family_struct(struct.family): return @@ -2035,6 +2226,48 @@ def render_uapi(family, cw): cw.p(f'#endif /* {hdr_prot} */') +def _render_user_ntf_entry(ri, op): + ri.cw.block_start(line=f"[{op.enum_name}] = ") + ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),") + ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,") + ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,") + ri.cw.p(f".free\t\t= (void *){op_prefix(ri, 'notify')}_free,") + ri.cw.block_end(line=',') + + +def render_user_family(family, cw, prototype): + symbol = f'const struct ynl_family ynl_{family.c_name}_family' + if prototype: + cw.p(f'extern {symbol};') + return + + if family.ntfs: + cw.block_start(line=f"static const struct ynl_ntf_info {family['name']}_ntf_info[] = ") + for ntf_op_name, ntf_op in family.ntfs.items(): + if 'notify' in ntf_op: + op = family.ops[ntf_op['notify']] + ri = RenderInfo(cw, family, "user", op, "notify") + elif 'event' in ntf_op: + ri = RenderInfo(cw, family, "user", ntf_op, "event") + else: + raise Exception('Invalid notification ' + ntf_op_name) + _render_user_ntf_entry(ri, ntf_op) + for op_name, op in family.ops.items(): + if 'event' not in op: + continue + ri = RenderInfo(cw, family, "user", op, "event") + _render_user_ntf_entry(ri, op) + cw.block_end(line=";") + cw.nl() + + cw.block_start(f'{symbol} = ') + cw.p(f'.name\t\t= "{family.name}",') + if family.ntfs: + cw.p(f".ntf_info\t= {family['name']}_ntf_info,") + cw.p(f".ntf_info_size\t= MNL_ARRAY_SIZE({family['name']}_ntf_info),") + cw.block_end(line=';') + + def find_kernel_root(full_path): sub_path = '' while True: @@ -2052,6 +2285,7 @@ def main(): parser.add_argument('--header', dest='header', action='store_true', default=None) parser.add_argument('--source', dest='header', action='store_false') parser.add_argument('--user-header', nargs='+', default=[]) + parser.add_argument('--exclude-op', action='append', default=[]) parser.add_argument('-o', dest='out_file', type=str) args = parser.parse_args() @@ -2060,8 +2294,10 @@ def main(): if args.header is None: parser.error("--header or --source is required") + exclude_ops = [re.compile(expr) for expr in args.exclude_op] + try: - parsed = Family(args.spec) + parsed = Family(args.spec, exclude_ops) if parsed.license != '((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)': print('Spec license:', parsed.license) print('License must be: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)') @@ -2071,6 +2307,13 @@ def main(): os.sys.exit(1) return + supported_models = ['unified'] + if args.mode == 'user': + supported_models += ['directional'] + if parsed.msg_id_model not in supported_models: + print(f'Message enum-model {parsed.msg_id_model} not supported for {args.mode} generation') + os.sys.exit(1) + cw = CodeWriter(BaseNlLib(), out_file) _, spec_kernel = find_kernel_root(args.spec) @@ -2081,6 +2324,11 @@ def main(): cw.p("/* Do not edit directly, auto-generated from: */") cw.p(f"/*\t{spec_kernel} */") cw.p(f"/* YNL-GEN {args.mode} {'header' if args.header else 'source'} */") + if args.exclude_op or args.user_header: + line = '' + line += ' --user-header '.join([''] + args.user_header) + line += ' --exclude-op '.join([''] + args.exclude_op) + cw.p(f'/* YNL-ARG{line} */') cw.nl() if args.mode == 'uapi': @@ -2101,7 +2349,16 @@ def main(): if args.out_file: cw.p(f'#include "{os.path.basename(args.out_file[:-2])}.h"') cw.nl() - headers = [parsed.uapi_header] + headers = ['uapi/' + parsed.uapi_header] + else: + cw.p('#include <stdlib.h>') + cw.p('#include <string.h>') + if args.header: + cw.p('#include <linux/types.h>') + else: + cw.p(f'#include "{parsed.name}-user.h"') + cw.p('#include "ynl.h"') + headers = [parsed.uapi_header] for definition in parsed['definitions']: if 'header' in definition: headers.append(definition['header']) @@ -2111,9 +2368,6 @@ def main(): if args.mode == "user": if not args.header: - cw.p("#include <stdlib.h>") - cw.p("#include <stdio.h>") - cw.p("#include <string.h>") cw.p("#include <libmnl/libmnl.h>") cw.p("#include <linux/genetlink.h>") cw.nl() @@ -2121,6 +2375,8 @@ def main(): cw.p(f'#include "{one}"') else: cw.p('struct ynl_sock;') + cw.nl() + render_user_family(parsed, cw, True) cw.nl() if args.mode == "kernel": @@ -2144,7 +2400,7 @@ def main(): if parsed.kernel_policy in {'per-op', 'split'}: for op_name, op in parsed.ops.items(): if 'do' in op and 'event' not in op: - ri = RenderInfo(cw, parsed, args.mode, op, op_name, "do") + ri = RenderInfo(cw, parsed, args.mode, op, "do") print_req_policy_fwd(cw, ri.struct['request'], ri=ri) cw.nl() @@ -2173,7 +2429,7 @@ def main(): for op_mode in ['do', 'dump']: if op_mode in op and 'request' in op[op_mode]: cw.p(f"/* {op.enum_name} - {op_mode} */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, op_mode) + ri = RenderInfo(cw, parsed, args.mode, op, op_mode) print_req_policy(cw, ri.struct['request'], ri=ri) cw.nl() @@ -2182,11 +2438,18 @@ def main(): print_kernel_family_struct_src(parsed, cw) if args.mode == "user": - has_ntf = False if args.header: + cw.p('/* Enums */') + put_op_name_fwd(parsed, cw) + + for name, const in parsed.consts.items(): + if isinstance(const, EnumSet): + put_enum_to_str_fwd(parsed, cw, const) + cw.nl() + cw.p('/* Common nested types */') - for attr_set, struct in sorted(parsed.pure_nested_structs.items()): - ri = RenderInfo(cw, parsed, args.mode, "", "", "", attr_set) + for attr_set, struct in parsed.pure_nested_structs.items(): + ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) print_type_full(ri, struct) for op_name, op in parsed.ops.items(): @@ -2194,7 +2457,7 @@ def main(): if 'do' in op and 'event' not in op: cw.p(f"/* {op.enum_name} - do */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, "do") + ri = RenderInfo(cw, parsed, args.mode, op, "do") print_req_type(ri) print_req_type_helpers(ri) cw.nl() @@ -2206,7 +2469,7 @@ def main(): if 'dump' in op: cw.p(f"/* {op.enum_name} - dump */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, 'dump') + ri = RenderInfo(cw, parsed, args.mode, op, 'dump') if 'request' in op['dump']: print_req_type(ri) print_req_type_helpers(ri) @@ -2216,39 +2479,41 @@ def main(): print_dump_prototype(ri) cw.nl() - if 'notify' in op: + if op.has_ntf: cw.p(f"/* {op.enum_name} - notify */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, 'notify') - has_ntf = True + ri = RenderInfo(cw, parsed, args.mode, op, 'notify') if not ri.type_consistent: - raise Exception('Only notifications with consistent types supported') + raise Exception(f'Only notifications with consistent types supported ({op.name})') print_wrapped_type(ri) + for op_name, op in parsed.ntfs.items(): if 'event' in op: - ri = RenderInfo(cw, parsed, args.mode, op, op_name, 'event') + ri = RenderInfo(cw, parsed, args.mode, op, 'event') cw.p(f"/* {op.enum_name} - event */") print_rsp_type(ri) cw.nl() print_wrapped_type(ri) - - if has_ntf: - cw.p('/* --------------- Common notification parsing --------------- */') - print_ntf_parse_prototype(parsed, cw) cw.nl() else: - cw.p('/* Policies */') - for name, _ in parsed.attr_sets.items(): - struct = Struct(parsed, name) - put_typol_fwd(cw, struct) + cw.p('/* Enums */') + put_op_name(parsed, cw) + + for name, const in parsed.consts.items(): + if isinstance(const, EnumSet): + put_enum_to_str(parsed, cw, const) cw.nl() - for name, _ in parsed.attr_sets.items(): + cw.p('/* Policies */') + for name in parsed.pure_nested_structs: + struct = Struct(parsed, name) + put_typol(cw, struct) + for name in parsed.root_sets: struct = Struct(parsed, name) put_typol(cw, struct) cw.p('/* Common nested types */') - for attr_set, struct in sorted(parsed.pure_nested_structs.items()): - ri = RenderInfo(cw, parsed, args.mode, "", "", "", attr_set) + for attr_set, struct in parsed.pure_nested_structs.items(): + ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) free_rsp_nested(ri, struct) if struct.request: @@ -2260,7 +2525,8 @@ def main(): cw.p(f"/* ============== {op.enum_name} ============== */") if 'do' in op and 'event' not in op: cw.p(f"/* {op.enum_name} - do */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, "do") + ri = RenderInfo(cw, parsed, args.mode, op, "do") + print_req_free(ri) print_rsp_free(ri) parse_rsp_msg(ri) print_req(ri) @@ -2268,34 +2534,31 @@ def main(): if 'dump' in op: cw.p(f"/* {op.enum_name} - dump */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, "dump") + ri = RenderInfo(cw, parsed, args.mode, op, "dump") if not ri.type_consistent: parse_rsp_msg(ri, deref=True) print_dump_type_free(ri) print_dump(ri) cw.nl() - if 'notify' in op: + if op.has_ntf: cw.p(f"/* {op.enum_name} - notify */") - ri = RenderInfo(cw, parsed, args.mode, op, op_name, 'notify') - has_ntf = True + ri = RenderInfo(cw, parsed, args.mode, op, 'notify') if not ri.type_consistent: - raise Exception('Only notifications with consistent types supported') + raise Exception(f'Only notifications with consistent types supported ({op.name})') print_ntf_type_free(ri) + for op_name, op in parsed.ntfs.items(): if 'event' in op: cw.p(f"/* {op.enum_name} - event */") - has_ntf = True - ri = RenderInfo(cw, parsed, args.mode, op, op_name, "do") + ri = RenderInfo(cw, parsed, args.mode, op, "do") parse_rsp_msg(ri) - ri = RenderInfo(cw, parsed, args.mode, op, op_name, "event") + ri = RenderInfo(cw, parsed, args.mode, op, "event") print_ntf_type_free(ri) - - if has_ntf: - cw.p('/* --------------- Common notification parsing --------------- */') - print_ntf_type_parse(parsed, cw, args.mode) + cw.nl() + render_user_family(parsed, cw, False) if args.header: cw.p(f'#endif /* {hdr_prot} */') diff --git a/tools/net/ynl/ynl-regen.sh b/tools/net/ynl/ynl-regen.sh index 74f5de1c2399..8d4ca6a50582 100755 --- a/tools/net/ynl/ynl-regen.sh +++ b/tools/net/ynl/ynl-regen.sh @@ -14,11 +14,12 @@ done KDIR=$(dirname $(dirname $(dirname $(dirname $(realpath $0))))) -files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\)') +files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\)') for f in $files; do # params: 0 1 2 3 # $YAML YNL-GEN kernel $mode params=( $(git grep -B1 -h '/\* YNL-GEN' $f | sed 's@/\*\(.*\)\*/@\1@') ) + args=$(sed -n 's@/\* YNL-ARG \(.*\) \*/@\1@p' $f) if [ $f -nt ${params[0]} -a -z "$force" ]; then echo -e "\tSKIP $f" @@ -26,5 +27,6 @@ for f in $files; do fi echo -e "\tGEN ${params[2]}\t$f" - $TOOL --mode ${params[2]} --${params[3]} --spec $KDIR/${params[0]} -o $f + $TOOL --mode ${params[2]} --${params[3]} --spec $KDIR/${params[0]} \ + $args -o $f done diff --git a/tools/objtool/Documentation/objtool.txt b/tools/objtool/Documentation/objtool.txt index 744db4218e7a..fe39c2a8ef0d 100644 --- a/tools/objtool/Documentation/objtool.txt +++ b/tools/objtool/Documentation/objtool.txt @@ -244,6 +244,11 @@ To achieve the validation, objtool enforces the following rules: Objtool warnings ---------------- +NOTE: When requesting help with an objtool warning, please recreate with +OBJTOOL_VERBOSE=1 (e.g., "make OBJTOOL_VERBOSE=1") and send the full +output, including any disassembly or backtrace below the warning, to the +objtool maintainers. + For asm files, if you're getting an error which doesn't make sense, first make sure that the affected code follows the above rules. @@ -298,6 +303,11 @@ the objtool maintainers. If it's not actually in a callable function (e.g. kernel entry code), change ENDPROC to END. +3. file.o: warning: objtool: foo+0x48c: bar() is missing a __noreturn annotation + + The call from foo() to bar() doesn't return, but bar() is missing the + __noreturn annotation. NOTE: In addition to annotating the function + with __noreturn, please also add it to tools/objtool/noreturns.h. 4. file.o: warning: objtool: func(): can't find starting instruction or diff --git a/tools/objtool/arch/powerpc/include/arch/elf.h b/tools/objtool/arch/powerpc/include/arch/elf.h index 73f9ae172fe5..66814fa28024 100644 --- a/tools/objtool/arch/powerpc/include/arch/elf.h +++ b/tools/objtool/arch/powerpc/include/arch/elf.h @@ -1,10 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ - #ifndef _OBJTOOL_ARCH_ELF #define _OBJTOOL_ARCH_ELF -#define R_NONE R_PPC_NONE -#define R_ABS64 R_PPC64_ADDR64 -#define R_ABS32 R_PPC_ADDR32 +#define R_NONE R_PPC_NONE +#define R_ABS64 R_PPC64_ADDR64 +#define R_ABS32 R_PPC_ADDR32 +#define R_DATA32 R_PPC_REL32 +#define R_DATA64 R_PPC64_REL64 +#define R_TEXT32 R_PPC_REL32 +#define R_TEXT64 R_PPC64_REL32 #endif /* _OBJTOOL_ARCH_ELF */ diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 9ef024fd648c..2e1caabecb18 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -84,7 +84,7 @@ bool arch_pc_relative_reloc(struct reloc *reloc) * All relocation types where P (the address of the target) * is included in the computation. */ - switch (reloc->type) { + switch (reloc_type(reloc)) { case R_X86_64_PC8: case R_X86_64_PC16: case R_X86_64_PC32: @@ -623,11 +623,11 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec if (!immr || strcmp(immr->sym->name, "pv_ops")) break; - idx = (immr->addend + 8) / sizeof(void *); + idx = (reloc_addend(immr) + 8) / sizeof(void *); func = disp->sym; if (disp->sym->type == STT_SECTION) - func = find_symbol_by_offset(disp->sym->sec, disp->addend); + func = find_symbol_by_offset(disp->sym->sec, reloc_addend(disp)); if (!func) { WARN("no func for pv_ops[]"); return -1; diff --git a/tools/objtool/arch/x86/include/arch/elf.h b/tools/objtool/arch/x86/include/arch/elf.h index ac14987cf687..7131f7f51a4e 100644 --- a/tools/objtool/arch/x86/include/arch/elf.h +++ b/tools/objtool/arch/x86/include/arch/elf.h @@ -1,8 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef _OBJTOOL_ARCH_ELF #define _OBJTOOL_ARCH_ELF -#define R_NONE R_X86_64_NONE -#define R_ABS64 R_X86_64_64 -#define R_ABS32 R_X86_64_32 +#define R_NONE R_X86_64_NONE +#define R_ABS32 R_X86_64_32 +#define R_ABS64 R_X86_64_64 +#define R_DATA32 R_X86_64_PC32 +#define R_DATA64 R_X86_64_PC32 +#define R_TEXT32 R_X86_64_PC32 +#define R_TEXT64 R_X86_64_PC32 #endif /* _OBJTOOL_ARCH_ELF */ diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c index 7c97b7391279..29e949579ede 100644 --- a/tools/objtool/arch/x86/special.c +++ b/tools/objtool/arch/x86/special.c @@ -42,13 +42,7 @@ bool arch_support_alt_relocation(struct special_alt *special_alt, struct instruction *insn, struct reloc *reloc) { - /* - * The x86 alternatives code adjusts the offsets only when it - * encounters a branch instruction at the very beginning of the - * replacement group. - */ - return insn->offset == special_alt->new_off && - (insn->type == INSN_CALL || is_jump(insn)); + return true; } /* @@ -105,10 +99,10 @@ struct reloc *arch_find_switch_table(struct objtool_file *file, !text_reloc->sym->sec->rodata) return NULL; - table_offset = text_reloc->addend; + table_offset = reloc_addend(text_reloc); table_sec = text_reloc->sym->sec; - if (text_reloc->type == R_X86_64_PC32) + if (reloc_type(text_reloc) == R_X86_64_PC32) table_offset += 4; /* @@ -138,7 +132,7 @@ struct reloc *arch_find_switch_table(struct objtool_file *file, * indicates a rare GCC quirk/bug which can leave dead * code behind. */ - if (text_reloc->type == R_X86_64_PC32) + if (reloc_type(text_reloc) == R_X86_64_PC32) file->ignore_unreachables = true; return rodata_reloc; diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 7c175198d09f..5e21cfb7661d 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -93,6 +93,7 @@ static const struct option check_options[] = { OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"), OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"), OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), + OPT_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"), OPT_END(), }; @@ -118,6 +119,10 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[]) parse_options(envc, envv, check_options, env_usage, 0); } + env = getenv("OBJTOOL_VERBOSE"); + if (env && !strcmp(env, "1")) + opts.verbose = true; + argc = parse_options(argc, argv, check_options, usage, 0); if (argc != 1) usage_with_options(usage, check_options); diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0fcf99c91400..8936a05f0e5a 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -8,7 +8,6 @@ #include <inttypes.h> #include <sys/mman.h> -#include <arch/elf.h> #include <objtool/builtin.h> #include <objtool/cfi.h> #include <objtool/arch.h> @@ -33,6 +32,7 @@ static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache; static struct cfi_init_state initial_func_cfi; static struct cfi_state init_cfi; static struct cfi_state func_cfi; +static struct cfi_state force_undefined_cfi; struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) @@ -192,51 +192,11 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, struct instruction *insn; bool empty = true; - /* - * Unfortunately these have to be hard coded because the noreturn - * attribute isn't provided in ELF data. Keep 'em sorted. - */ +#define NORETURN(func) __stringify(func), static const char * const global_noreturns[] = { - "__invalid_creds", - "__module_put_and_kthread_exit", - "__reiserfs_panic", - "__stack_chk_fail", - "__ubsan_handle_builtin_unreachable", - "arch_call_rest_init", - "arch_cpu_idle_dead", - "btrfs_assertfail", - "cpu_bringup_and_idle", - "cpu_startup_entry", - "do_exit", - "do_group_exit", - "do_task_dead", - "ex_handler_msr_mce", - "fortify_panic", - "hlt_play_dead", - "hv_ghcb_terminate", - "kthread_complete_and_exit", - "kthread_exit", - "kunit_try_catch_throw", - "lbug_with_loc", - "machine_real_restart", - "make_task_dead", - "mpt_halt_firmware", - "nmi_panic_self_stop", - "panic", - "panic_smp_self_stop", - "rest_init", - "resume_play_dead", - "rewind_stack_and_make_dead", - "sev_es_terminate", - "snp_abort", - "start_kernel", - "stop_this_cpu", - "usercopy_abort", - "x86_64_start_kernel", - "x86_64_start_reservations", - "xen_cpu_bringup_again", - "xen_start_kernel", +#include "noreturns.h" }; +#undef NORETURN if (!func) return false; @@ -533,7 +493,7 @@ static int add_pv_ops(struct objtool_file *file, const char *symname) { struct symbol *sym, *func; unsigned long off, end; - struct reloc *rel; + struct reloc *reloc; int idx; sym = find_symbol_by_name(file->elf, symname); @@ -543,19 +503,20 @@ static int add_pv_ops(struct objtool_file *file, const char *symname) off = sym->offset; end = off + sym->len; for (;;) { - rel = find_reloc_by_dest_range(file->elf, sym->sec, off, end - off); - if (!rel) + reloc = find_reloc_by_dest_range(file->elf, sym->sec, off, end - off); + if (!reloc) break; - func = rel->sym; + func = reloc->sym; if (func->type == STT_SECTION) - func = find_symbol_by_offset(rel->sym->sec, rel->addend); + func = find_symbol_by_offset(reloc->sym->sec, + reloc_addend(reloc)); - idx = (rel->offset - sym->offset) / sizeof(unsigned long); + idx = (reloc_offset(reloc) - sym->offset) / sizeof(unsigned long); objtool_pv_add(file, idx, func); - off = rel->offset + 1; + off = reloc_offset(reloc) + 1; if (off > end) break; } @@ -620,35 +581,40 @@ static struct instruction *find_last_insn(struct objtool_file *file, */ static int add_dead_ends(struct objtool_file *file) { - struct section *sec; + struct section *rsec; struct reloc *reloc; struct instruction *insn; + s64 addend; /* * Check for manually annotated dead ends. */ - sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.unreachable"); + if (!rsec) goto reachable; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { + if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + + addend = reloc_addend(reloc); + + insn = find_insn(file, reloc->sym->sec, addend); if (insn) insn = prev_insn_same_sec(file, insn); - else if (reloc->addend == reloc->sym->sec->sh.sh_size) { + else if (addend == reloc->sym->sec->sh.sh_size) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { WARN("can't find unreachable insn at %s+0x%" PRIx64, - reloc->sym->sec->name, reloc->addend); + reloc->sym->sec->name, addend); return -1; } } else { WARN("can't find unreachable insn at %s+0x%" PRIx64, - reloc->sym->sec->name, reloc->addend); + reloc->sym->sec->name, addend); return -1; } @@ -662,28 +628,32 @@ reachable: * GCC doesn't know the "ud2" is fatal, so it generates code as if it's * not a dead end. */ - sec = find_section_by_name(file->elf, ".rela.discard.reachable"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.reachable"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { + if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + + addend = reloc_addend(reloc); + + insn = find_insn(file, reloc->sym->sec, addend); if (insn) insn = prev_insn_same_sec(file, insn); - else if (reloc->addend == reloc->sym->sec->sh.sh_size) { + else if (addend == reloc->sym->sec->sh.sh_size) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { WARN("can't find reachable insn at %s+0x%" PRIx64, - reloc->sym->sec->name, reloc->addend); + reloc->sym->sec->name, addend); return -1; } } else { WARN("can't find reachable insn at %s+0x%" PRIx64, - reloc->sym->sec->name, reloc->addend); + reloc->sym->sec->name, addend); return -1; } @@ -695,8 +665,8 @@ reachable: static int create_static_call_sections(struct objtool_file *file) { - struct section *sec; struct static_call_site *site; + struct section *sec; struct instruction *insn; struct symbol *key_sym; char *key_name, *tmp; @@ -716,22 +686,21 @@ static int create_static_call_sections(struct objtool_file *file) list_for_each_entry(insn, &file->static_call_list, call_node) idx++; - sec = elf_create_section(file->elf, ".static_call_sites", SHF_WRITE, - sizeof(struct static_call_site), idx); + sec = elf_create_section_pair(file->elf, ".static_call_sites", + sizeof(*site), idx, idx * 2); if (!sec) return -1; + /* Allow modules to modify the low bits of static_call_site::key */ + sec->sh.sh_flags |= SHF_WRITE; + idx = 0; list_for_each_entry(insn, &file->static_call_list, call_node) { - site = (struct static_call_site *)sec->data->d_buf + idx; - memset(site, 0, sizeof(struct static_call_site)); - /* populate reloc for 'addr' */ - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(struct static_call_site), - R_X86_64_PC32, - insn->sec, insn->offset)) + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(*site), idx * 2, + insn->sec, insn->offset)) return -1; /* find key symbol */ @@ -771,10 +740,10 @@ static int create_static_call_sections(struct objtool_file *file) free(key_name); /* populate reloc for 'key' */ - if (elf_add_reloc(file->elf, sec, - idx * sizeof(struct static_call_site) + 4, - R_X86_64_PC32, key_sym, - is_sibling_call(insn) * STATIC_CALL_SITE_TAIL)) + if (!elf_init_reloc_data_sym(file->elf, sec, + idx * sizeof(*site) + 4, + (idx * 2) + 1, key_sym, + is_sibling_call(insn) * STATIC_CALL_SITE_TAIL)) return -1; idx++; @@ -802,26 +771,18 @@ static int create_retpoline_sites_sections(struct objtool_file *file) if (!idx) return 0; - sec = elf_create_section(file->elf, ".retpoline_sites", 0, - sizeof(int), idx); - if (!sec) { - WARN("elf_create_section: .retpoline_sites"); + sec = elf_create_section_pair(file->elf, ".retpoline_sites", + sizeof(int), idx, idx); + if (!sec) return -1; - } idx = 0; list_for_each_entry(insn, &file->retpoline_call_list, call_node) { - int *site = (int *)sec->data->d_buf + idx; - *site = 0; - - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(int), - R_X86_64_PC32, - insn->sec, insn->offset)) { - WARN("elf_add_reloc_to_insn: .retpoline_sites"); + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(int), idx, + insn->sec, insn->offset)) return -1; - } idx++; } @@ -848,26 +809,18 @@ static int create_return_sites_sections(struct objtool_file *file) if (!idx) return 0; - sec = elf_create_section(file->elf, ".return_sites", 0, - sizeof(int), idx); - if (!sec) { - WARN("elf_create_section: .return_sites"); + sec = elf_create_section_pair(file->elf, ".return_sites", + sizeof(int), idx, idx); + if (!sec) return -1; - } idx = 0; list_for_each_entry(insn, &file->return_thunk_list, call_node) { - int *site = (int *)sec->data->d_buf + idx; - *site = 0; - - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(int), - R_X86_64_PC32, - insn->sec, insn->offset)) { - WARN("elf_add_reloc_to_insn: .return_sites"); + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(int), idx, + insn->sec, insn->offset)) return -1; - } idx++; } @@ -900,12 +853,10 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file) if (!idx) return 0; - sec = elf_create_section(file->elf, ".ibt_endbr_seal", 0, - sizeof(int), idx); - if (!sec) { - WARN("elf_create_section: .ibt_endbr_seal"); + sec = elf_create_section_pair(file->elf, ".ibt_endbr_seal", + sizeof(int), idx, idx); + if (!sec) return -1; - } idx = 0; list_for_each_entry(insn, &file->endbr_list, call_node) { @@ -920,13 +871,10 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file) !strcmp(sym->name, "cleanup_module"))) WARN("%s(): not an indirect call target", sym->name); - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(int), - R_X86_64_PC32, - insn->sec, insn->offset)) { - WARN("elf_add_reloc_to_insn: .ibt_endbr_seal"); + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(int), idx, + insn->sec, insn->offset)) return -1; - } idx++; } @@ -938,7 +886,6 @@ static int create_cfi_sections(struct objtool_file *file) { struct section *sec; struct symbol *sym; - unsigned int *loc; int idx; sec = find_section_by_name(file->elf, ".cfi_sites"); @@ -959,7 +906,8 @@ static int create_cfi_sections(struct objtool_file *file) idx++; } - sec = elf_create_section(file->elf, ".cfi_sites", 0, sizeof(unsigned int), idx); + sec = elf_create_section_pair(file->elf, ".cfi_sites", + sizeof(unsigned int), idx, idx); if (!sec) return -1; @@ -971,13 +919,9 @@ static int create_cfi_sections(struct objtool_file *file) if (strncmp(sym->name, "__cfi_", 6)) continue; - loc = (unsigned int *)sec->data->d_buf + idx; - memset(loc, 0, sizeof(unsigned int)); - - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(unsigned int), - R_X86_64_PC32, - sym->sec, sym->offset)) + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(unsigned int), idx, + sym->sec, sym->offset)) return -1; idx++; @@ -988,7 +932,7 @@ static int create_cfi_sections(struct objtool_file *file) static int create_mcount_loc_sections(struct objtool_file *file) { - int addrsize = elf_class_addrsize(file->elf); + size_t addr_size = elf_addr_size(file->elf); struct instruction *insn; struct section *sec; int idx; @@ -1007,25 +951,26 @@ static int create_mcount_loc_sections(struct objtool_file *file) list_for_each_entry(insn, &file->mcount_loc_list, call_node) idx++; - sec = elf_create_section(file->elf, "__mcount_loc", 0, addrsize, idx); + sec = elf_create_section_pair(file->elf, "__mcount_loc", addr_size, + idx, idx); if (!sec) return -1; - sec->sh.sh_addralign = addrsize; + sec->sh.sh_addralign = addr_size; idx = 0; list_for_each_entry(insn, &file->mcount_loc_list, call_node) { - void *loc; - loc = sec->data->d_buf + idx; - memset(loc, 0, addrsize); + struct reloc *reloc; - if (elf_add_reloc_to_insn(file->elf, sec, idx, - addrsize == sizeof(u64) ? R_ABS64 : R_ABS32, - insn->sec, insn->offset)) + reloc = elf_init_reloc_text_sym(file->elf, sec, idx * addr_size, idx, + insn->sec, insn->offset); + if (!reloc) return -1; - idx += addrsize; + set_reloc_type(file->elf, reloc, addr_size == 8 ? R_ABS64 : R_ABS32); + + idx++; } return 0; @@ -1035,7 +980,6 @@ static int create_direct_call_sections(struct objtool_file *file) { struct instruction *insn; struct section *sec; - unsigned int *loc; int idx; sec = find_section_by_name(file->elf, ".call_sites"); @@ -1052,20 +996,17 @@ static int create_direct_call_sections(struct objtool_file *file) list_for_each_entry(insn, &file->call_list, call_node) idx++; - sec = elf_create_section(file->elf, ".call_sites", 0, sizeof(unsigned int), idx); + sec = elf_create_section_pair(file->elf, ".call_sites", + sizeof(unsigned int), idx, idx); if (!sec) return -1; idx = 0; list_for_each_entry(insn, &file->call_list, call_node) { - loc = (unsigned int *)sec->data->d_buf + idx; - memset(loc, 0, sizeof(unsigned int)); - - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(unsigned int), - R_X86_64_PC32, - insn->sec, insn->offset)) + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(unsigned int), idx, + insn->sec, insn->offset)) return -1; idx++; @@ -1080,28 +1021,29 @@ static int create_direct_call_sections(struct objtool_file *file) static void add_ignores(struct objtool_file *file) { struct instruction *insn; - struct section *sec; + struct section *rsec; struct symbol *func; struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.func_stack_frame_non_standard"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.func_stack_frame_non_standard"); + if (!rsec) return; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { switch (reloc->sym->type) { case STT_FUNC: func = reloc->sym; break; case STT_SECTION: - func = find_func_by_offset(reloc->sym->sec, reloc->addend); + func = find_func_by_offset(reloc->sym->sec, reloc_addend(reloc)); if (!func) continue; break; default: - WARN("unexpected relocation symbol type in %s: %d", sec->name, reloc->sym->type); + WARN("unexpected relocation symbol type in %s: %d", + rsec->name, reloc->sym->type); continue; } @@ -1320,21 +1262,21 @@ static void add_uaccess_safe(struct objtool_file *file) */ static int add_ignore_alternatives(struct objtool_file *file) { - struct section *sec; + struct section *rsec; struct reloc *reloc; struct instruction *insn; - sec = find_section_by_name(file->elf, ".rela.discard.ignore_alts"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.ignore_alts"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("bad .discard.ignore_alts entry"); return -1; @@ -1421,10 +1363,8 @@ static void annotate_call_site(struct objtool_file *file, * noinstr text. */ if (opts.hack_noinstr && insn->sec->noinstr && sym->profiling_func) { - if (reloc) { - reloc->type = R_NONE; - elf_write_reloc(file->elf, reloc); - } + if (reloc) + set_reloc_type(file->elf, reloc, R_NONE); elf_write_insn(file->elf, insn->sec, insn->offset, insn->len, @@ -1450,10 +1390,8 @@ static void annotate_call_site(struct objtool_file *file, if (sibling) WARN_INSN(insn, "tail call to __fentry__ !?!?"); if (opts.mnop) { - if (reloc) { - reloc->type = R_NONE; - elf_write_reloc(file->elf, reloc); - } + if (reloc) + set_reloc_type(file->elf, reloc, R_NONE); elf_write_insn(file->elf, insn->sec, insn->offset, insn->len, @@ -1610,7 +1548,7 @@ static int add_jump_destinations(struct objtool_file *file) dest_off = arch_jump_destination(insn); } else if (reloc->sym->type == STT_SECTION) { dest_sec = reloc->sym->sec; - dest_off = arch_dest_reloc_offset(reloc->addend); + dest_off = arch_dest_reloc_offset(reloc_addend(reloc)); } else if (reloc->sym->retpoline_thunk) { add_retpoline_call(file, insn); continue; @@ -1627,7 +1565,7 @@ static int add_jump_destinations(struct objtool_file *file) } else if (reloc->sym->sec->idx) { dest_sec = reloc->sym->sec; dest_off = reloc->sym->sym.st_value + - arch_dest_reloc_offset(reloc->addend); + arch_dest_reloc_offset(reloc_addend(reloc)); } else { /* non-func asm code jumping to another file */ continue; @@ -1744,7 +1682,7 @@ static int add_call_destinations(struct objtool_file *file) } } else if (reloc->sym->type == STT_SECTION) { - dest_off = arch_dest_reloc_offset(reloc->addend); + dest_off = arch_dest_reloc_offset(reloc_addend(reloc)); dest = find_call_destination(reloc->sym->sec, dest_off); if (!dest) { WARN_INSN(insn, "can't find call dest symbol at %s+0x%lx", @@ -1932,10 +1870,8 @@ static int handle_jump_alt(struct objtool_file *file, if (opts.hack_jump_label && special_alt->key_addend & 2) { struct reloc *reloc = insn_reloc(file, orig_insn); - if (reloc) { - reloc->type = R_NONE; - elf_write_reloc(file->elf, reloc); - } + if (reloc) + set_reloc_type(file->elf, reloc, R_NONE); elf_write_insn(file->elf, orig_insn->sec, orig_insn->offset, orig_insn->len, arch_nop_insn(orig_insn->len)); @@ -2047,34 +1983,35 @@ out: } static int add_jump_table(struct objtool_file *file, struct instruction *insn, - struct reloc *table) + struct reloc *next_table) { - struct reloc *reloc = table; - struct instruction *dest_insn; - struct alternative *alt; struct symbol *pfunc = insn_func(insn)->pfunc; + struct reloc *table = insn_jump_table(insn); + struct instruction *dest_insn; unsigned int prev_offset = 0; + struct reloc *reloc = table; + struct alternative *alt; /* * Each @reloc is a switch table relocation which points to the target * instruction. */ - list_for_each_entry_from(reloc, &table->sec->reloc_list, list) { + for_each_reloc_from(table->sec, reloc) { /* Check for the end of the table: */ - if (reloc != table && reloc->jump_table_start) + if (reloc != table && reloc == next_table) break; /* Make sure the table entries are consecutive: */ - if (prev_offset && reloc->offset != prev_offset + 8) + if (prev_offset && reloc_offset(reloc) != prev_offset + 8) break; /* Detect function pointers from contiguous objects: */ if (reloc->sym->sec == pfunc->sec && - reloc->addend == pfunc->offset) + reloc_addend(reloc) == pfunc->offset) break; - dest_insn = find_insn(file, reloc->sym->sec, reloc->addend); + dest_insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!dest_insn) break; @@ -2091,7 +2028,7 @@ static int add_jump_table(struct objtool_file *file, struct instruction *insn, alt->insn = dest_insn; alt->next = insn->alts; insn->alts = alt; - prev_offset = reloc->offset; + prev_offset = reloc_offset(reloc); } if (!prev_offset) { @@ -2135,7 +2072,7 @@ static struct reloc *find_jump_table(struct objtool_file *file, table_reloc = arch_find_switch_table(file, insn); if (!table_reloc) continue; - dest_insn = find_insn(file, table_reloc->sym->sec, table_reloc->addend); + dest_insn = find_insn(file, table_reloc->sym->sec, reloc_addend(table_reloc)); if (!dest_insn || !insn_func(dest_insn) || insn_func(dest_insn)->pfunc != func) continue; @@ -2177,29 +2114,39 @@ static void mark_func_jump_tables(struct objtool_file *file, continue; reloc = find_jump_table(file, func, insn); - if (reloc) { - reloc->jump_table_start = true; + if (reloc) insn->_jump_table = reloc; - } } } static int add_func_jump_tables(struct objtool_file *file, struct symbol *func) { - struct instruction *insn; - int ret; + struct instruction *insn, *insn_t1 = NULL, *insn_t2; + int ret = 0; func_for_each_insn(file, func, insn) { if (!insn_jump_table(insn)) continue; - ret = add_jump_table(file, insn, insn_jump_table(insn)); + if (!insn_t1) { + insn_t1 = insn; + continue; + } + + insn_t2 = insn; + + ret = add_jump_table(file, insn_t1, insn_jump_table(insn_t2)); if (ret) return ret; + + insn_t1 = insn_t2; } - return 0; + if (insn_t1) + ret = add_jump_table(file, insn_t1, NULL); + + return ret; } /* @@ -2240,7 +2187,7 @@ static void set_func_state(struct cfi_state *state) static int read_unwind_hints(struct objtool_file *file) { struct cfi_state cfi = init_cfi; - struct section *sec, *relocsec; + struct section *sec; struct unwind_hint *hint; struct instruction *insn; struct reloc *reloc; @@ -2250,8 +2197,7 @@ static int read_unwind_hints(struct objtool_file *file) if (!sec) return 0; - relocsec = sec->reloc; - if (!relocsec) { + if (!sec->rsec) { WARN("missing .rela.discard.unwind_hints section"); return -1; } @@ -2272,7 +2218,7 @@ static int read_unwind_hints(struct objtool_file *file) return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("can't find insn for unwind_hints[%d]", i); return -1; @@ -2280,6 +2226,11 @@ static int read_unwind_hints(struct objtool_file *file) insn->hint = true; + if (hint->type == UNWIND_HINT_TYPE_UNDEFINED) { + insn->cfi = &force_undefined_cfi; + continue; + } + if (hint->type == UNWIND_HINT_TYPE_SAVE) { insn->hint = false; insn->save = true; @@ -2326,16 +2277,17 @@ static int read_unwind_hints(struct objtool_file *file) static int read_noendbr_hints(struct objtool_file *file) { - struct section *sec; struct instruction *insn; + struct section *rsec; struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.noendbr"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.noendbr"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { - insn = find_insn(file, reloc->sym->sec, reloc->sym->offset + reloc->addend); + for_each_reloc(rsec, reloc) { + insn = find_insn(file, reloc->sym->sec, + reloc->sym->offset + reloc_addend(reloc)); if (!insn) { WARN("bad .discard.noendbr entry"); return -1; @@ -2349,21 +2301,21 @@ static int read_noendbr_hints(struct objtool_file *file) static int read_retpoline_hints(struct objtool_file *file) { - struct section *sec; + struct section *rsec; struct instruction *insn; struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.retpoline_safe"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.retpoline_safe"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("bad .discard.retpoline_safe entry"); return -1; @@ -2385,21 +2337,21 @@ static int read_retpoline_hints(struct objtool_file *file) static int read_instr_hints(struct objtool_file *file) { - struct section *sec; + struct section *rsec; struct instruction *insn; struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.instr_end"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.instr_end"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("bad .discard.instr_end entry"); return -1; @@ -2408,17 +2360,17 @@ static int read_instr_hints(struct objtool_file *file) insn->instr--; } - sec = find_section_by_name(file->elf, ".rela.discard.instr_begin"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.instr_begin"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("bad .discard.instr_begin entry"); return -1; @@ -2432,21 +2384,21 @@ static int read_instr_hints(struct objtool_file *file) static int read_validate_unret_hints(struct objtool_file *file) { - struct section *sec; + struct section *rsec; struct instruction *insn; struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.validate_unret"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.validate_unret"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { if (reloc->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + WARN("unexpected relocation symbol type in %s", rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("bad .discard.instr_end entry"); return -1; @@ -2461,23 +2413,23 @@ static int read_validate_unret_hints(struct objtool_file *file) static int read_intra_function_calls(struct objtool_file *file) { struct instruction *insn; - struct section *sec; + struct section *rsec; struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.intra_function_calls"); - if (!sec) + rsec = find_section_by_name(file->elf, ".rela.discard.intra_function_calls"); + if (!rsec) return 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { + for_each_reloc(rsec, reloc) { unsigned long dest_off; if (reloc->sym->type != STT_SECTION) { WARN("unexpected relocation symbol type in %s", - sec->name); + rsec->name); return -1; } - insn = find_insn(file, reloc->sym->sec, reloc->addend); + insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc)); if (!insn) { WARN("bad .discard.intra_function_call entry"); return -1; @@ -2833,6 +2785,10 @@ static int update_cfi_state(struct instruction *insn, struct cfi_reg *cfa = &cfi->cfa; struct cfi_reg *regs = cfi->regs; + /* ignore UNWIND_HINT_UNDEFINED regions */ + if (cfi->force_undefined) + return 0; + /* stack operations don't make sense with an undefined CFA */ if (cfa->base == CFI_UNDEFINED) { if (insn_func(insn)) { @@ -3369,15 +3325,15 @@ static inline bool func_uaccess_safe(struct symbol *func) static inline const char *call_dest_name(struct instruction *insn) { static char pvname[19]; - struct reloc *rel; + struct reloc *reloc; int idx; if (insn_call_dest(insn)) return insn_call_dest(insn)->name; - rel = insn_reloc(NULL, insn); - if (rel && !strcmp(rel->sym->name, "pv_ops")) { - idx = (rel->addend / sizeof(void *)); + reloc = insn_reloc(NULL, insn); + if (reloc && !strcmp(reloc->sym->name, "pv_ops")) { + idx = (reloc_addend(reloc) / sizeof(void *)); snprintf(pvname, sizeof(pvname), "pv_ops[%d]", idx); return pvname; } @@ -3388,14 +3344,14 @@ static inline const char *call_dest_name(struct instruction *insn) static bool pv_call_dest(struct objtool_file *file, struct instruction *insn) { struct symbol *target; - struct reloc *rel; + struct reloc *reloc; int idx; - rel = insn_reloc(file, insn); - if (!rel || strcmp(rel->sym->name, "pv_ops")) + reloc = insn_reloc(file, insn); + if (!reloc || strcmp(reloc->sym->name, "pv_ops")) return false; - idx = (arch_dest_reloc_offset(rel->addend) / sizeof(void *)); + idx = (arch_dest_reloc_offset(reloc_addend(reloc)) / sizeof(void *)); if (file->pv_ops[idx].clean) return true; @@ -3657,8 +3613,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, ret = validate_branch(file, func, alt->insn, state); if (ret) { - if (opts.backtrace) - BT_FUNC("(alt)", insn); + BT_INSN(insn, "(alt)"); return ret; } } @@ -3703,8 +3658,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, ret = validate_branch(file, func, insn->jump_dest, state); if (ret) { - if (opts.backtrace) - BT_FUNC("(branch)", insn); + BT_INSN(insn, "(branch)"); return ret; } } @@ -3802,8 +3756,8 @@ static int validate_unwind_hint(struct objtool_file *file, { if (insn->hint && !insn->visited && !insn->ignore) { int ret = validate_branch(file, insn_func(insn), insn, *state); - if (ret && opts.backtrace) - BT_FUNC("<=== (hint)", insn); + if (ret) + BT_INSN(insn, "<=== (hint)"); return ret; } @@ -3841,7 +3795,7 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec) static int validate_unret(struct objtool_file *file, struct instruction *insn) { struct instruction *next, *dest; - int ret, warnings = 0; + int ret; for (;;) { next = next_insn_to_validate(file, insn); @@ -3861,8 +3815,7 @@ static int validate_unret(struct objtool_file *file, struct instruction *insn) ret = validate_unret(file, alt->insn); if (ret) { - if (opts.backtrace) - BT_FUNC("(alt)", insn); + BT_INSN(insn, "(alt)"); return ret; } } @@ -3888,10 +3841,8 @@ static int validate_unret(struct objtool_file *file, struct instruction *insn) } ret = validate_unret(file, insn->jump_dest); if (ret) { - if (opts.backtrace) { - BT_FUNC("(branch%s)", insn, - insn->type == INSN_JUMP_CONDITIONAL ? "-cond" : ""); - } + BT_INSN(insn, "(branch%s)", + insn->type == INSN_JUMP_CONDITIONAL ? "-cond" : ""); return ret; } @@ -3913,8 +3864,7 @@ static int validate_unret(struct objtool_file *file, struct instruction *insn) ret = validate_unret(file, dest); if (ret) { - if (opts.backtrace) - BT_FUNC("(call)", insn); + BT_INSN(insn, "(call)"); return ret; } /* @@ -3943,7 +3893,7 @@ static int validate_unret(struct objtool_file *file, struct instruction *insn) insn = next; } - return warnings; + return 0; } /* @@ -4178,7 +4128,6 @@ static int add_prefix_symbols(struct objtool_file *file) { struct section *sec; struct symbol *func; - int warnings = 0; for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) @@ -4192,7 +4141,7 @@ static int add_prefix_symbols(struct objtool_file *file) } } - return warnings; + return 0; } static int validate_symbol(struct objtool_file *file, struct section *sec, @@ -4216,8 +4165,8 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, state->uaccess = sym->uaccess_safe; ret = validate_branch(file, insn_func(insn), insn, *state); - if (ret && opts.backtrace) - BT_FUNC("<=== (sym)", insn); + if (ret) + BT_INSN(insn, "<=== (sym)"); return ret; } @@ -4333,8 +4282,8 @@ static int validate_ibt_insn(struct objtool_file *file, struct instruction *insn for (reloc = insn_reloc(file, insn); reloc; reloc = find_reloc_by_dest_range(file->elf, insn->sec, - reloc->offset + 1, - (insn->offset + insn->len) - (reloc->offset + 1))) { + reloc_offset(reloc) + 1, + (insn->offset + insn->len) - (reloc_offset(reloc) + 1))) { /* * static_call_update() references the trampoline, which @@ -4344,10 +4293,11 @@ static int validate_ibt_insn(struct objtool_file *file, struct instruction *insn continue; off = reloc->sym->offset; - if (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32) - off += arch_dest_reloc_offset(reloc->addend); + if (reloc_type(reloc) == R_X86_64_PC32 || + reloc_type(reloc) == R_X86_64_PLT32) + off += arch_dest_reloc_offset(reloc_addend(reloc)); else - off += reloc->addend; + off += reloc_addend(reloc); dest = find_insn(file, reloc->sym->sec, off); if (!dest) @@ -4404,7 +4354,7 @@ static int validate_ibt_data_reloc(struct objtool_file *file, struct instruction *dest; dest = find_insn(file, reloc->sym->sec, - reloc->sym->offset + reloc->addend); + reloc->sym->offset + reloc_addend(reloc)); if (!dest) return 0; @@ -4417,7 +4367,7 @@ static int validate_ibt_data_reloc(struct objtool_file *file, return 0; WARN_FUNC("data relocation to !ENDBR: %s", - reloc->sec->base, reloc->offset, + reloc->sec->base, reloc_offset(reloc), offstr(dest->sec, dest->offset)); return 1; @@ -4444,7 +4394,7 @@ static int validate_ibt(struct objtool_file *file) if (sec->sh.sh_flags & SHF_EXECINSTR) continue; - if (!sec->reloc) + if (!sec->rsec) continue; /* @@ -4471,7 +4421,7 @@ static int validate_ibt(struct objtool_file *file) strstr(sec->name, "__patchable_function_entries")) continue; - list_for_each_entry(reloc, &sec->reloc->reloc_list, list) + for_each_reloc(sec->rsec, reloc) warnings += validate_ibt_data_reloc(file, reloc); } @@ -4511,9 +4461,40 @@ static int validate_sls(struct objtool_file *file) return warnings; } +static bool ignore_noreturn_call(struct instruction *insn) +{ + struct symbol *call_dest = insn_call_dest(insn); + + /* + * FIXME: hack, we need a real noreturn solution + * + * Problem is, exc_double_fault() may or may not return, depending on + * whether CONFIG_X86_ESPFIX64 is set. But objtool has no visibility + * to the kernel config. + * + * Other potential ways to fix it: + * + * - have compiler communicate __noreturn functions somehow + * - remove CONFIG_X86_ESPFIX64 + * - read the .config file + * - add a cmdline option + * - create a generic objtool annotation format (vs a bunch of custom + * formats) and annotate it + */ + if (!strcmp(call_dest->name, "exc_double_fault")) { + /* prevent further unreachable warnings for the caller */ + insn->sym->warned = 1; + return true; + } + + return false; +} + static int validate_reachable_instructions(struct objtool_file *file) { - struct instruction *insn; + struct instruction *insn, *prev_insn; + struct symbol *call_dest; + int warnings = 0; if (file->ignore_unreachables) return 0; @@ -4522,13 +4503,127 @@ static int validate_reachable_instructions(struct objtool_file *file) if (insn->visited || ignore_unreachable_insn(file, insn)) continue; + prev_insn = prev_insn_same_sec(file, insn); + if (prev_insn && prev_insn->dead_end) { + call_dest = insn_call_dest(prev_insn); + if (call_dest && !ignore_noreturn_call(prev_insn)) { + WARN_INSN(insn, "%s() is missing a __noreturn annotation", + call_dest->name); + warnings++; + continue; + } + } + WARN_INSN(insn, "unreachable instruction"); - return 1; + warnings++; + } + + return warnings; +} + +/* 'funcs' is a space-separated list of function names */ +static int disas_funcs(const char *funcs) +{ + const char *objdump_str, *cross_compile; + int size, ret; + char *cmd; + + cross_compile = getenv("CROSS_COMPILE"); + + objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '" + "BEGIN { split(_funcs, funcs); }" + "/^$/ { func_match = 0; }" + "/<.*>:/ { " + "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);" + "for (i in funcs) {" + "if (funcs[i] == f) {" + "func_match = 1;" + "base = strtonum(\"0x\" $1);" + "break;" + "}" + "}" + "}" + "{" + "if (func_match) {" + "addr = strtonum(\"0x\" $1);" + "printf(\"%%04x \", addr - base);" + "print;" + "}" + "}' 1>&2"; + + /* fake snprintf() to calculate the size */ + size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1; + if (size <= 0) { + WARN("objdump string size calculation failed"); + return -1; + } + + cmd = malloc(size); + + /* real snprintf() */ + snprintf(cmd, size, objdump_str, cross_compile, objname, funcs); + ret = system(cmd); + if (ret) { + WARN("disassembly failed: %d", ret); + return -1; } return 0; } +static int disas_warned_funcs(struct objtool_file *file) +{ + struct symbol *sym; + char *funcs = NULL, *tmp; + + for_each_sym(file, sym) { + if (sym->warned) { + if (!funcs) { + funcs = malloc(strlen(sym->name) + 1); + strcpy(funcs, sym->name); + } else { + tmp = malloc(strlen(funcs) + strlen(sym->name) + 2); + sprintf(tmp, "%s %s", funcs, sym->name); + free(funcs); + funcs = tmp; + } + } + } + + if (funcs) + disas_funcs(funcs); + + return 0; +} + +struct insn_chunk { + void *addr; + struct insn_chunk *next; +}; + +/* + * Reduce peak RSS usage by freeing insns memory before writing the ELF file, + * which can trigger more allocations for .debug_* sections whose data hasn't + * been read yet. + */ +static void free_insns(struct objtool_file *file) +{ + struct instruction *insn; + struct insn_chunk *chunks = NULL, *chunk; + + for_each_insn(file, insn) { + if (!insn->idx) { + chunk = malloc(sizeof(*chunk)); + chunk->addr = insn; + chunk->next = chunks; + chunks = chunk; + } + } + + for (chunk = chunks; chunk; chunk = chunk->next) + free(chunk->addr); +} + int check(struct objtool_file *file) { int ret, warnings = 0; @@ -4537,6 +4632,8 @@ int check(struct objtool_file *file) init_cfi_state(&init_cfi); init_cfi_state(&func_cfi); set_func_state(&func_cfi); + init_cfi_state(&force_undefined_cfi); + force_undefined_cfi.force_undefined = true; if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) goto out; @@ -4673,6 +4770,10 @@ int check(struct objtool_file *file) warnings += ret; } + free_insns(file); + + if (opts.verbose) + disas_warned_funcs(file); if (opts.stats) { printf("nr_insns_visited: %ld\n", nr_insns_visited); diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 500e92979a31..d420b5d2e2b6 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -32,16 +32,52 @@ static inline u32 str_hash(const char *str) #define __elf_table(name) (elf->name##_hash) #define __elf_bits(name) (elf->name##_bits) -#define elf_hash_add(name, node, key) \ - hlist_add_head(node, &__elf_table(name)[hash_min(key, __elf_bits(name))]) +#define __elf_table_entry(name, key) \ + __elf_table(name)[hash_min(key, __elf_bits(name))] + +#define elf_hash_add(name, node, key) \ +({ \ + struct elf_hash_node *__node = node; \ + __node->next = __elf_table_entry(name, key); \ + __elf_table_entry(name, key) = __node; \ +}) + +static inline void __elf_hash_del(struct elf_hash_node *node, + struct elf_hash_node **head) +{ + struct elf_hash_node *cur, *prev; + + if (node == *head) { + *head = node->next; + return; + } + + for (prev = NULL, cur = *head; cur; prev = cur, cur = cur->next) { + if (cur == node) { + prev->next = cur->next; + break; + } + } +} -#define elf_hash_for_each_possible(name, obj, member, key) \ - hlist_for_each_entry(obj, &__elf_table(name)[hash_min(key, __elf_bits(name))], member) +#define elf_hash_del(name, node, key) \ + __elf_hash_del(node, &__elf_table_entry(name, key)) + +#define elf_list_entry(ptr, type, member) \ +({ \ + typeof(ptr) __ptr = (ptr); \ + __ptr ? container_of(__ptr, type, member) : NULL; \ +}) + +#define elf_hash_for_each_possible(name, obj, member, key) \ + for (obj = elf_list_entry(__elf_table_entry(name, key), typeof(*obj), member); \ + obj; \ + obj = elf_list_entry(obj->member.next, typeof(*(obj)), member)) #define elf_alloc_hash(name, size) \ ({ \ __elf_bits(name) = max(10, ilog2(size)); \ - __elf_table(name) = mmap(NULL, sizeof(struct hlist_head) << __elf_bits(name), \ + __elf_table(name) = mmap(NULL, sizeof(struct elf_hash_node *) << __elf_bits(name), \ PROT_READ|PROT_WRITE, \ MAP_PRIVATE|MAP_ANON, -1, 0); \ if (__elf_table(name) == (void *)-1L) { \ @@ -233,21 +269,22 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se unsigned long offset, unsigned int len) { struct reloc *reloc, *r = NULL; + struct section *rsec; unsigned long o; - if (!sec->reloc) + rsec = sec->rsec; + if (!rsec) return NULL; - sec = sec->reloc; - for_offset_range(o, offset, offset + len) { elf_hash_for_each_possible(reloc, reloc, hash, - sec_offset_hash(sec, o)) { - if (reloc->sec != sec) + sec_offset_hash(rsec, o)) { + if (reloc->sec != rsec) continue; - if (reloc->offset >= offset && reloc->offset < offset + len) { - if (!r || reloc->offset < r->offset) + if (reloc_offset(reloc) >= offset && + reloc_offset(reloc) < offset + len) { + if (!r || reloc_offset(reloc) < reloc_offset(r)) r = reloc; } } @@ -263,6 +300,11 @@ struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, uns return find_reloc_by_dest_range(elf, sec, offset, 1); } +static bool is_dwarf_section(struct section *sec) +{ + return !strncmp(sec->name, ".debug_", 7); +} + static int read_sections(struct elf *elf) { Elf_Scn *s = NULL; @@ -293,7 +335,6 @@ static int read_sections(struct elf *elf) sec = &elf->section_data[i]; INIT_LIST_HEAD(&sec->symbol_list); - INIT_LIST_HEAD(&sec->reloc_list); s = elf_getscn(elf->elf, i); if (!s) { @@ -314,7 +355,7 @@ static int read_sections(struct elf *elf) return -1; } - if (sec->sh.sh_size != 0) { + if (sec->sh.sh_size != 0 && !is_dwarf_section(sec)) { sec->data = elf_getdata(s, NULL); if (!sec->data) { WARN_ELF("elf_getdata"); @@ -328,12 +369,12 @@ static int read_sections(struct elf *elf) } } - if (sec->sh.sh_flags & SHF_EXECINSTR) - elf->text_size += sec->sh.sh_size; - list_add_tail(&sec->list, &elf->sections); elf_hash_add(section, &sec->hash, sec->idx); elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); + + if (is_reloc_sec(sec)) + elf->num_relocs += sec_num_entries(sec); } if (opts.stats) { @@ -356,7 +397,6 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym) struct rb_node *pnode; struct symbol *iter; - INIT_LIST_HEAD(&sym->reloc_list); INIT_LIST_HEAD(&sym->pv_target); sym->alias = sym; @@ -407,7 +447,7 @@ static int read_symbols(struct elf *elf) if (symtab_shndx) shndx_data = symtab_shndx->data; - symbols_nr = symtab->sh.sh_size / symtab->sh.sh_entsize; + symbols_nr = sec_num_entries(symtab); } else { /* * A missing symbol table is actually possible if it's an empty @@ -533,52 +573,17 @@ err: return -1; } -static struct section *elf_create_reloc_section(struct elf *elf, - struct section *base, - int reltype); - -int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, s64 addend) -{ - struct reloc *reloc; - - if (!sec->reloc && !elf_create_reloc_section(elf, sec, SHT_RELA)) - return -1; - - reloc = malloc(sizeof(*reloc)); - if (!reloc) { - perror("malloc"); - return -1; - } - memset(reloc, 0, sizeof(*reloc)); - - reloc->sec = sec->reloc; - reloc->offset = offset; - reloc->type = type; - reloc->sym = sym; - reloc->addend = addend; - - list_add_tail(&reloc->sym_reloc_entry, &sym->reloc_list); - list_add_tail(&reloc->list, &sec->reloc->reloc_list); - elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); - - sec->reloc->sh.sh_size += sec->reloc->sh.sh_entsize; - sec->reloc->changed = true; - - return 0; -} - /* - * Ensure that any reloc section containing references to @sym is marked - * changed such that it will get re-generated in elf_rebuild_reloc_sections() - * with the new symbol index. + * @sym's idx has changed. Update the relocs which reference it. */ -static void elf_dirty_reloc_sym(struct elf *elf, struct symbol *sym) +static int elf_update_sym_relocs(struct elf *elf, struct symbol *sym) { struct reloc *reloc; - list_for_each_entry(reloc, &sym->reloc_list, sym_reloc_entry) - reloc->sec->changed = true; + for (reloc = sym->relocs; reloc; reloc = reloc->sym_next_reloc) + set_reloc_sym(elf, reloc, reloc->sym->idx); + + return 0; } /* @@ -655,7 +660,7 @@ static int elf_update_symbol(struct elf *elf, struct section *symtab, symtab_data->d_align = 1; symtab_data->d_type = ELF_T_SYM; - symtab->changed = true; + mark_sec_changed(elf, symtab, true); symtab->truncate = true; if (t) { @@ -670,7 +675,7 @@ static int elf_update_symbol(struct elf *elf, struct section *symtab, shndx_data->d_align = sizeof(Elf32_Word); shndx_data->d_type = ELF_T_WORD; - symtab_shndx->changed = true; + mark_sec_changed(elf, symtab_shndx, true); symtab_shndx->truncate = true; } @@ -734,7 +739,7 @@ __elf_create_symbol(struct elf *elf, struct symbol *sym) return NULL; } - new_idx = symtab->sh.sh_size / symtab->sh.sh_entsize; + new_idx = sec_num_entries(symtab); if (GELF_ST_BIND(sym->sym.st_info) != STB_LOCAL) goto non_local; @@ -746,18 +751,19 @@ __elf_create_symbol(struct elf *elf, struct symbol *sym) first_non_local = symtab->sh.sh_info; old = find_symbol_by_index(elf, first_non_local); if (old) { - old->idx = new_idx; - hlist_del(&old->hash); - elf_hash_add(symbol, &old->hash, old->idx); - - elf_dirty_reloc_sym(elf, old); + elf_hash_del(symbol, &old->hash, old->idx); + elf_hash_add(symbol, &old->hash, new_idx); + old->idx = new_idx; if (elf_update_symbol(elf, symtab, symtab_shndx, old)) { WARN("elf_update_symbol move"); return NULL; } + if (elf_update_sym_relocs(elf, old)) + return NULL; + new_idx = first_non_local; } @@ -774,11 +780,11 @@ non_local: } symtab->sh.sh_size += symtab->sh.sh_entsize; - symtab->changed = true; + mark_sec_changed(elf, symtab, true); if (symtab_shndx) { symtab_shndx->sh.sh_size += sizeof(Elf32_Word); - symtab_shndx->changed = true; + mark_sec_changed(elf, symtab_shndx, true); } return sym; @@ -841,13 +847,57 @@ elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size) return sym; } -int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, - unsigned long offset, unsigned int type, - struct section *insn_sec, unsigned long insn_off) +static struct reloc *elf_init_reloc(struct elf *elf, struct section *rsec, + unsigned int reloc_idx, + unsigned long offset, struct symbol *sym, + s64 addend, unsigned int type) +{ + struct reloc *reloc, empty = { 0 }; + + if (reloc_idx >= sec_num_entries(rsec)) { + WARN("%s: bad reloc_idx %u for %s with %d relocs", + __func__, reloc_idx, rsec->name, sec_num_entries(rsec)); + return NULL; + } + + reloc = &rsec->relocs[reloc_idx]; + + if (memcmp(reloc, &empty, sizeof(empty))) { + WARN("%s: %s: reloc %d already initialized!", + __func__, rsec->name, reloc_idx); + return NULL; + } + + reloc->sec = rsec; + reloc->sym = sym; + + set_reloc_offset(elf, reloc, offset); + set_reloc_sym(elf, reloc, sym->idx); + set_reloc_type(elf, reloc, type); + set_reloc_addend(elf, reloc, addend); + + elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); + reloc->sym_next_reloc = sym->relocs; + sym->relocs = reloc; + + return reloc; +} + +struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec, + unsigned long offset, + unsigned int reloc_idx, + struct section *insn_sec, + unsigned long insn_off) { struct symbol *sym = insn_sec->sym; int addend = insn_off; + if (!(insn_sec->sh.sh_flags & SHF_EXECINSTR)) { + WARN("bad call to %s() for data symbol %s", + __func__, sym->name); + return NULL; + } + if (!sym) { /* * Due to how weak functions work, we must use section based @@ -857,108 +907,86 @@ int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, */ sym = elf_create_section_symbol(elf, insn_sec); if (!sym) - return -1; + return NULL; insn_sec->sym = sym; } - return elf_add_reloc(elf, sec, offset, type, sym, addend); + return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, + elf_text_rela_type(elf)); } -static int read_rel_reloc(struct section *sec, int i, struct reloc *reloc, unsigned int *symndx) +struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec, + unsigned long offset, + unsigned int reloc_idx, + struct symbol *sym, + s64 addend) { - if (!gelf_getrel(sec->data, i, &reloc->rel)) { - WARN_ELF("gelf_getrel"); - return -1; + if (sym->sec && (sec->sh.sh_flags & SHF_EXECINSTR)) { + WARN("bad call to %s() for text symbol %s", + __func__, sym->name); + return NULL; } - reloc->type = GELF_R_TYPE(reloc->rel.r_info); - reloc->addend = 0; - reloc->offset = reloc->rel.r_offset; - *symndx = GELF_R_SYM(reloc->rel.r_info); - return 0; -} -static int read_rela_reloc(struct section *sec, int i, struct reloc *reloc, unsigned int *symndx) -{ - if (!gelf_getrela(sec->data, i, &reloc->rela)) { - WARN_ELF("gelf_getrela"); - return -1; - } - reloc->type = GELF_R_TYPE(reloc->rela.r_info); - reloc->addend = reloc->rela.r_addend; - reloc->offset = reloc->rela.r_offset; - *symndx = GELF_R_SYM(reloc->rela.r_info); - return 0; + return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, + elf_data_rela_type(elf)); } static int read_relocs(struct elf *elf) { - unsigned long nr_reloc, max_reloc = 0, tot_reloc = 0; - struct section *sec; + unsigned long nr_reloc, max_reloc = 0; + struct section *rsec; struct reloc *reloc; unsigned int symndx; struct symbol *sym; int i; - if (!elf_alloc_hash(reloc, elf->text_size / 16)) + if (!elf_alloc_hash(reloc, elf->num_relocs)) return -1; - list_for_each_entry(sec, &elf->sections, list) { - if ((sec->sh.sh_type != SHT_RELA) && - (sec->sh.sh_type != SHT_REL)) + list_for_each_entry(rsec, &elf->sections, list) { + if (!is_reloc_sec(rsec)) continue; - sec->base = find_section_by_index(elf, sec->sh.sh_info); - if (!sec->base) { + rsec->base = find_section_by_index(elf, rsec->sh.sh_info); + if (!rsec->base) { WARN("can't find base section for reloc section %s", - sec->name); + rsec->name); return -1; } - sec->base->reloc = sec; + rsec->base->rsec = rsec; nr_reloc = 0; - sec->reloc_data = calloc(sec->sh.sh_size / sec->sh.sh_entsize, sizeof(*reloc)); - if (!sec->reloc_data) { + rsec->relocs = calloc(sec_num_entries(rsec), sizeof(*reloc)); + if (!rsec->relocs) { perror("calloc"); return -1; } - for (i = 0; i < sec->sh.sh_size / sec->sh.sh_entsize; i++) { - reloc = &sec->reloc_data[i]; - switch (sec->sh.sh_type) { - case SHT_REL: - if (read_rel_reloc(sec, i, reloc, &symndx)) - return -1; - break; - case SHT_RELA: - if (read_rela_reloc(sec, i, reloc, &symndx)) - return -1; - break; - default: return -1; - } + for (i = 0; i < sec_num_entries(rsec); i++) { + reloc = &rsec->relocs[i]; - reloc->sec = sec; - reloc->idx = i; + reloc->sec = rsec; + symndx = reloc_sym(reloc); reloc->sym = sym = find_symbol_by_index(elf, symndx); if (!reloc->sym) { WARN("can't find reloc entry symbol %d for %s", - symndx, sec->name); + symndx, rsec->name); return -1; } - list_add_tail(&reloc->sym_reloc_entry, &sym->reloc_list); - list_add_tail(&reloc->list, &sec->reloc_list); elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); + reloc->sym_next_reloc = sym->relocs; + sym->relocs = reloc; nr_reloc++; } max_reloc = max(max_reloc, nr_reloc); - tot_reloc += nr_reloc; } if (opts.stats) { printf("max_reloc: %lu\n", max_reloc); - printf("tot_reloc: %lu\n", tot_reloc); + printf("num_relocs: %lu\n", elf->num_relocs); printf("reloc_bits: %d\n", elf->reloc_bits); } @@ -1053,13 +1081,14 @@ static int elf_add_string(struct elf *elf, struct section *strtab, char *str) len = strtab->sh.sh_size; strtab->sh.sh_size += data->d_size; - strtab->changed = true; + + mark_sec_changed(elf, strtab, true); return len; } struct section *elf_create_section(struct elf *elf, const char *name, - unsigned int sh_flags, size_t entsize, int nr) + size_t entsize, unsigned int nr) { struct section *sec, *shstrtab; size_t size = entsize * nr; @@ -1073,7 +1102,6 @@ struct section *elf_create_section(struct elf *elf, const char *name, memset(sec, 0, sizeof(*sec)); INIT_LIST_HEAD(&sec->symbol_list); - INIT_LIST_HEAD(&sec->reloc_list); s = elf_newscn(elf->elf); if (!s) { @@ -1088,7 +1116,6 @@ struct section *elf_create_section(struct elf *elf, const char *name, } sec->idx = elf_ndxscn(s); - sec->changed = true; sec->data = elf_newdata(s); if (!sec->data) { @@ -1117,7 +1144,7 @@ struct section *elf_create_section(struct elf *elf, const char *name, sec->sh.sh_entsize = entsize; sec->sh.sh_type = SHT_PROGBITS; sec->sh.sh_addralign = 1; - sec->sh.sh_flags = SHF_ALLOC | sh_flags; + sec->sh.sh_flags = SHF_ALLOC; /* Add section name to .shstrtab (or .strtab for Clang) */ shstrtab = find_section_by_name(elf, ".shstrtab"); @@ -1135,158 +1162,66 @@ struct section *elf_create_section(struct elf *elf, const char *name, elf_hash_add(section, &sec->hash, sec->idx); elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); - elf->changed = true; + mark_sec_changed(elf, sec, true); return sec; } -static struct section *elf_create_rel_reloc_section(struct elf *elf, struct section *base) +static struct section *elf_create_rela_section(struct elf *elf, + struct section *sec, + unsigned int reloc_nr) { - char *relocname; - struct section *sec; + struct section *rsec; + char *rsec_name; - relocname = malloc(strlen(base->name) + strlen(".rel") + 1); - if (!relocname) { + rsec_name = malloc(strlen(sec->name) + strlen(".rela") + 1); + if (!rsec_name) { perror("malloc"); return NULL; } - strcpy(relocname, ".rel"); - strcat(relocname, base->name); + strcpy(rsec_name, ".rela"); + strcat(rsec_name, sec->name); - sec = elf_create_section(elf, relocname, 0, sizeof(GElf_Rel), 0); - free(relocname); - if (!sec) + rsec = elf_create_section(elf, rsec_name, elf_rela_size(elf), reloc_nr); + free(rsec_name); + if (!rsec) return NULL; - base->reloc = sec; - sec->base = base; + rsec->data->d_type = ELF_T_RELA; + rsec->sh.sh_type = SHT_RELA; + rsec->sh.sh_addralign = elf_addr_size(elf); + rsec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; + rsec->sh.sh_info = sec->idx; + rsec->sh.sh_flags = SHF_INFO_LINK; + + rsec->relocs = calloc(sec_num_entries(rsec), sizeof(struct reloc)); + if (!rsec->relocs) { + perror("calloc"); + return NULL; + } - sec->sh.sh_type = SHT_REL; - sec->sh.sh_addralign = 8; - sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; - sec->sh.sh_info = base->idx; - sec->sh.sh_flags = SHF_INFO_LINK; + sec->rsec = rsec; + rsec->base = sec; - return sec; + return rsec; } -static struct section *elf_create_rela_reloc_section(struct elf *elf, struct section *base) +struct section *elf_create_section_pair(struct elf *elf, const char *name, + size_t entsize, unsigned int nr, + unsigned int reloc_nr) { - char *relocname; struct section *sec; - int addrsize = elf_class_addrsize(elf); - - relocname = malloc(strlen(base->name) + strlen(".rela") + 1); - if (!relocname) { - perror("malloc"); - return NULL; - } - strcpy(relocname, ".rela"); - strcat(relocname, base->name); - if (addrsize == sizeof(u32)) - sec = elf_create_section(elf, relocname, 0, sizeof(Elf32_Rela), 0); - else - sec = elf_create_section(elf, relocname, 0, sizeof(GElf_Rela), 0); - free(relocname); + sec = elf_create_section(elf, name, entsize, nr); if (!sec) return NULL; - base->reloc = sec; - sec->base = base; - - sec->sh.sh_type = SHT_RELA; - sec->sh.sh_addralign = addrsize; - sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; - sec->sh.sh_info = base->idx; - sec->sh.sh_flags = SHF_INFO_LINK; + if (!elf_create_rela_section(elf, sec, reloc_nr)) + return NULL; return sec; } -static struct section *elf_create_reloc_section(struct elf *elf, - struct section *base, - int reltype) -{ - switch (reltype) { - case SHT_REL: return elf_create_rel_reloc_section(elf, base); - case SHT_RELA: return elf_create_rela_reloc_section(elf, base); - default: return NULL; - } -} - -static int elf_rebuild_rel_reloc_section(struct section *sec) -{ - struct reloc *reloc; - int idx = 0; - void *buf; - - /* Allocate a buffer for relocations */ - buf = malloc(sec->sh.sh_size); - if (!buf) { - perror("malloc"); - return -1; - } - - sec->data->d_buf = buf; - sec->data->d_size = sec->sh.sh_size; - sec->data->d_type = ELF_T_REL; - - idx = 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { - reloc->rel.r_offset = reloc->offset; - reloc->rel.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - if (!gelf_update_rel(sec->data, idx, &reloc->rel)) { - WARN_ELF("gelf_update_rel"); - return -1; - } - idx++; - } - - return 0; -} - -static int elf_rebuild_rela_reloc_section(struct section *sec) -{ - struct reloc *reloc; - int idx = 0; - void *buf; - - /* Allocate a buffer for relocations with addends */ - buf = malloc(sec->sh.sh_size); - if (!buf) { - perror("malloc"); - return -1; - } - - sec->data->d_buf = buf; - sec->data->d_size = sec->sh.sh_size; - sec->data->d_type = ELF_T_RELA; - - idx = 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { - reloc->rela.r_offset = reloc->offset; - reloc->rela.r_addend = reloc->addend; - reloc->rela.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - if (!gelf_update_rela(sec->data, idx, &reloc->rela)) { - WARN_ELF("gelf_update_rela"); - return -1; - } - idx++; - } - - return 0; -} - -static int elf_rebuild_reloc_section(struct elf *elf, struct section *sec) -{ - switch (sec->sh.sh_type) { - case SHT_REL: return elf_rebuild_rel_reloc_section(sec); - case SHT_RELA: return elf_rebuild_rela_reloc_section(sec); - default: return -1; - } -} - int elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset, unsigned int len, const char *insn) @@ -1299,37 +1234,8 @@ int elf_write_insn(struct elf *elf, struct section *sec, } memcpy(data->d_buf + offset, insn, len); - elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY); - elf->changed = true; - - return 0; -} - -int elf_write_reloc(struct elf *elf, struct reloc *reloc) -{ - struct section *sec = reloc->sec; - - if (sec->sh.sh_type == SHT_REL) { - reloc->rel.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - reloc->rel.r_offset = reloc->offset; - - if (!gelf_update_rel(sec->data, reloc->idx, &reloc->rel)) { - WARN_ELF("gelf_update_rel"); - return -1; - } - } else { - reloc->rela.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - reloc->rela.r_addend = reloc->addend; - reloc->rela.r_offset = reloc->offset; - - if (!gelf_update_rela(sec->data, reloc->idx, &reloc->rela)) { - WARN_ELF("gelf_update_rela"); - return -1; - } - } - - elf->changed = true; + mark_sec_changed(elf, sec, true); return 0; } @@ -1401,25 +1307,20 @@ int elf_write(struct elf *elf) if (sec->truncate) elf_truncate_section(elf, sec); - if (sec->changed) { + if (sec_changed(sec)) { s = elf_getscn(elf->elf, sec->idx); if (!s) { WARN_ELF("elf_getscn"); return -1; } + + /* Note this also flags the section dirty */ if (!gelf_update_shdr(s, &sec->sh)) { WARN_ELF("gelf_update_shdr"); return -1; } - if (sec->base && - elf_rebuild_reloc_section(elf, sec)) { - WARN("elf_rebuild_reloc_section"); - return -1; - } - - sec->changed = false; - elf->changed = true; + mark_sec_changed(elf, sec, false); } } @@ -1439,30 +1340,14 @@ int elf_write(struct elf *elf) void elf_close(struct elf *elf) { - struct section *sec, *tmpsec; - struct symbol *sym, *tmpsym; - struct reloc *reloc, *tmpreloc; - if (elf->elf) elf_end(elf->elf); if (elf->fd > 0) close(elf->fd); - list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) { - list_for_each_entry_safe(sym, tmpsym, &sec->symbol_list, list) { - list_del(&sym->list); - hash_del(&sym->hash); - } - list_for_each_entry_safe(reloc, tmpreloc, &sec->reloc_list, list) { - list_del(&reloc->list); - hash_del(&reloc->hash); - } - list_del(&sec->list); - free(sec->reloc_data); - } - - free(elf->symbol_data); - free(elf->section_data); - free(elf); + /* + * NOTE: All remaining allocations are leaked on purpose. Objtool is + * about to exit anyway. + */ } diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 2a108e648b7a..fcca6662c8b4 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -37,6 +37,7 @@ struct opts { bool no_unreachable; bool sec_address; bool stats; + bool verbose; }; extern struct opts opts; diff --git a/tools/objtool/include/objtool/cfi.h b/tools/objtool/include/objtool/cfi.h index b1258e79a1b7..c8a6bec4f6b9 100644 --- a/tools/objtool/include/objtool/cfi.h +++ b/tools/objtool/include/objtool/cfi.h @@ -36,6 +36,7 @@ struct cfi_state { bool drap; bool signal; bool end; + bool force_undefined; }; #endif /* _OBJTOOL_CFI_H */ diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index e1ca588eb69d..c532d70864dc 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -12,6 +12,7 @@ #include <linux/hashtable.h> #include <linux/rbtree.h> #include <linux/jhash.h> +#include <arch/elf.h> #ifdef LIBELF_USE_DEPRECATED # define elf_getshdrnum elf_getshnum @@ -25,28 +26,31 @@ #define ELF_C_READ_MMAP ELF_C_READ #endif +struct elf_hash_node { + struct elf_hash_node *next; +}; + struct section { struct list_head list; - struct hlist_node hash; - struct hlist_node name_hash; + struct elf_hash_node hash; + struct elf_hash_node name_hash; GElf_Shdr sh; struct rb_root_cached symbol_tree; struct list_head symbol_list; - struct list_head reloc_list; - struct section *base, *reloc; + struct section *base, *rsec; struct symbol *sym; Elf_Data *data; char *name; int idx; - bool changed, text, rodata, noinstr, init, truncate; - struct reloc *reloc_data; + bool _changed, text, rodata, noinstr, init, truncate; + struct reloc *relocs; }; struct symbol { struct list_head list; struct rb_node node; - struct hlist_node hash; - struct hlist_node name_hash; + struct elf_hash_node hash; + struct elf_hash_node name_hash; GElf_Sym sym; struct section *sec; char *name; @@ -61,37 +65,27 @@ struct symbol { u8 return_thunk : 1; u8 fentry : 1; u8 profiling_func : 1; + u8 warned : 1; struct list_head pv_target; - struct list_head reloc_list; + struct reloc *relocs; }; struct reloc { - struct list_head list; - struct hlist_node hash; - union { - GElf_Rela rela; - GElf_Rel rel; - }; + struct elf_hash_node hash; struct section *sec; struct symbol *sym; - struct list_head sym_reloc_entry; - unsigned long offset; - unsigned int type; - s64 addend; - int idx; - bool jump_table_start; + struct reloc *sym_next_reloc; }; -#define ELF_HASH_BITS 20 - struct elf { Elf *elf; GElf_Ehdr ehdr; int fd; bool changed; char *name; - unsigned int text_size, num_files; + unsigned int num_files; struct list_head sections; + unsigned long num_relocs; int symbol_bits; int symbol_name_bits; @@ -99,44 +93,54 @@ struct elf { int section_name_bits; int reloc_bits; - struct hlist_head *symbol_hash; - struct hlist_head *symbol_name_hash; - struct hlist_head *section_hash; - struct hlist_head *section_name_hash; - struct hlist_head *reloc_hash; + struct elf_hash_node **symbol_hash; + struct elf_hash_node **symbol_name_hash; + struct elf_hash_node **section_hash; + struct elf_hash_node **section_name_hash; + struct elf_hash_node **reloc_hash; struct section *section_data; struct symbol *symbol_data; }; -#define OFFSET_STRIDE_BITS 4 -#define OFFSET_STRIDE (1UL << OFFSET_STRIDE_BITS) -#define OFFSET_STRIDE_MASK (~(OFFSET_STRIDE - 1)) - -#define for_offset_range(_offset, _start, _end) \ - for (_offset = ((_start) & OFFSET_STRIDE_MASK); \ - _offset >= ((_start) & OFFSET_STRIDE_MASK) && \ - _offset <= ((_end) & OFFSET_STRIDE_MASK); \ - _offset += OFFSET_STRIDE) +struct elf *elf_open_read(const char *name, int flags); -static inline u32 sec_offset_hash(struct section *sec, unsigned long offset) -{ - u32 ol, oh, idx = sec->idx; +struct section *elf_create_section(struct elf *elf, const char *name, + size_t entsize, unsigned int nr); +struct section *elf_create_section_pair(struct elf *elf, const char *name, + size_t entsize, unsigned int nr, + unsigned int reloc_nr); - offset &= OFFSET_STRIDE_MASK; +struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size); - ol = offset; - oh = (offset >> 16) >> 16; +struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec, + unsigned long offset, + unsigned int reloc_idx, + struct section *insn_sec, + unsigned long insn_off); - __jhash_mix(ol, oh, idx); +struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec, + unsigned long offset, + unsigned int reloc_idx, + struct symbol *sym, + s64 addend); - return ol; -} +int elf_write_insn(struct elf *elf, struct section *sec, + unsigned long offset, unsigned int len, + const char *insn); +int elf_write(struct elf *elf); +void elf_close(struct elf *elf); -static inline u32 reloc_hash(struct reloc *reloc) -{ - return sec_offset_hash(reloc->sec, reloc->offset); -} +struct section *find_section_by_name(const struct elf *elf, const char *name); +struct symbol *find_func_by_offset(struct section *sec, unsigned long offset); +struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); +struct symbol *find_symbol_by_name(const struct elf *elf, const char *name); +struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset); +int find_symbol_hole_containing(const struct section *sec, unsigned long offset); +struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); +struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, + unsigned long offset, unsigned int len); +struct symbol *find_func_containing(struct section *sec, unsigned long offset); /* * Try to see if it's a whole archive (vmlinux.o or module). @@ -148,42 +152,147 @@ static inline bool has_multiple_files(struct elf *elf) return elf->num_files > 1; } -static inline int elf_class_addrsize(struct elf *elf) +static inline size_t elf_addr_size(struct elf *elf) { - if (elf->ehdr.e_ident[EI_CLASS] == ELFCLASS32) - return sizeof(u32); - else - return sizeof(u64); + return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8; } -struct elf *elf_open_read(const char *name, int flags); -struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr); +static inline size_t elf_rela_size(struct elf *elf) +{ + return elf_addr_size(elf) == 4 ? sizeof(Elf32_Rela) : sizeof(Elf64_Rela); +} -struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size); +static inline unsigned int elf_data_rela_type(struct elf *elf) +{ + return elf_addr_size(elf) == 4 ? R_DATA32 : R_DATA64; +} -int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, s64 addend); -int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, - unsigned long offset, unsigned int type, - struct section *insn_sec, unsigned long insn_off); +static inline unsigned int elf_text_rela_type(struct elf *elf) +{ + return elf_addr_size(elf) == 4 ? R_TEXT32 : R_TEXT64; +} -int elf_write_insn(struct elf *elf, struct section *sec, - unsigned long offset, unsigned int len, - const char *insn); -int elf_write_reloc(struct elf *elf, struct reloc *reloc); -int elf_write(struct elf *elf); -void elf_close(struct elf *elf); +static inline bool is_reloc_sec(struct section *sec) +{ + return sec->sh.sh_type == SHT_RELA || sec->sh.sh_type == SHT_REL; +} -struct section *find_section_by_name(const struct elf *elf, const char *name); -struct symbol *find_func_by_offset(struct section *sec, unsigned long offset); -struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); -struct symbol *find_symbol_by_name(const struct elf *elf, const char *name); -struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset); -int find_symbol_hole_containing(const struct section *sec, unsigned long offset); -struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); -struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, - unsigned long offset, unsigned int len); -struct symbol *find_func_containing(struct section *sec, unsigned long offset); +static inline bool sec_changed(struct section *sec) +{ + return sec->_changed; +} + +static inline void mark_sec_changed(struct elf *elf, struct section *sec, + bool changed) +{ + sec->_changed = changed; + elf->changed |= changed; +} + +static inline unsigned int sec_num_entries(struct section *sec) +{ + return sec->sh.sh_size / sec->sh.sh_entsize; +} + +static inline unsigned int reloc_idx(struct reloc *reloc) +{ + return reloc - reloc->sec->relocs; +} + +static inline void *reloc_rel(struct reloc *reloc) +{ + struct section *rsec = reloc->sec; + + return rsec->data->d_buf + (reloc_idx(reloc) * rsec->sh.sh_entsize); +} + +static inline bool is_32bit_reloc(struct reloc *reloc) +{ + /* + * Elf32_Rel: 8 bytes + * Elf32_Rela: 12 bytes + * Elf64_Rel: 16 bytes + * Elf64_Rela: 24 bytes + */ + return reloc->sec->sh.sh_entsize < 16; +} + +#define __get_reloc_field(reloc, field) \ +({ \ + is_32bit_reloc(reloc) ? \ + ((Elf32_Rela *)reloc_rel(reloc))->field : \ + ((Elf64_Rela *)reloc_rel(reloc))->field; \ +}) + +#define __set_reloc_field(reloc, field, val) \ +({ \ + if (is_32bit_reloc(reloc)) \ + ((Elf32_Rela *)reloc_rel(reloc))->field = val; \ + else \ + ((Elf64_Rela *)reloc_rel(reloc))->field = val; \ +}) + +static inline u64 reloc_offset(struct reloc *reloc) +{ + return __get_reloc_field(reloc, r_offset); +} + +static inline void set_reloc_offset(struct elf *elf, struct reloc *reloc, u64 offset) +{ + __set_reloc_field(reloc, r_offset, offset); + mark_sec_changed(elf, reloc->sec, true); +} + +static inline s64 reloc_addend(struct reloc *reloc) +{ + return __get_reloc_field(reloc, r_addend); +} + +static inline void set_reloc_addend(struct elf *elf, struct reloc *reloc, s64 addend) +{ + __set_reloc_field(reloc, r_addend, addend); + mark_sec_changed(elf, reloc->sec, true); +} + + +static inline unsigned int reloc_sym(struct reloc *reloc) +{ + u64 info = __get_reloc_field(reloc, r_info); + + return is_32bit_reloc(reloc) ? + ELF32_R_SYM(info) : + ELF64_R_SYM(info); +} + +static inline unsigned int reloc_type(struct reloc *reloc) +{ + u64 info = __get_reloc_field(reloc, r_info); + + return is_32bit_reloc(reloc) ? + ELF32_R_TYPE(info) : + ELF64_R_TYPE(info); +} + +static inline void set_reloc_sym(struct elf *elf, struct reloc *reloc, unsigned int sym) +{ + u64 info = is_32bit_reloc(reloc) ? + ELF32_R_INFO(sym, reloc_type(reloc)) : + ELF64_R_INFO(sym, reloc_type(reloc)); + + __set_reloc_field(reloc, r_info, info); + + mark_sec_changed(elf, reloc->sec, true); +} +static inline void set_reloc_type(struct elf *elf, struct reloc *reloc, unsigned int type) +{ + u64 info = is_32bit_reloc(reloc) ? + ELF32_R_INFO(reloc_sym(reloc), type) : + ELF64_R_INFO(reloc_sym(reloc), type); + + __set_reloc_field(reloc, r_info, info); + + mark_sec_changed(elf, reloc->sec, true); +} #define for_each_sec(file, sec) \ list_for_each_entry(sec, &file->elf->sections, list) @@ -197,4 +306,44 @@ struct symbol *find_func_containing(struct section *sec, unsigned long offset); for_each_sec(file, __sec) \ sec_for_each_sym(__sec, sym) +#define for_each_reloc(rsec, reloc) \ + for (int __i = 0, __fake = 1; __fake; __fake = 0) \ + for (reloc = rsec->relocs; \ + __i < sec_num_entries(rsec); \ + __i++, reloc++) + +#define for_each_reloc_from(rsec, reloc) \ + for (int __i = reloc_idx(reloc); \ + __i < sec_num_entries(rsec); \ + __i++, reloc++) + +#define OFFSET_STRIDE_BITS 4 +#define OFFSET_STRIDE (1UL << OFFSET_STRIDE_BITS) +#define OFFSET_STRIDE_MASK (~(OFFSET_STRIDE - 1)) + +#define for_offset_range(_offset, _start, _end) \ + for (_offset = ((_start) & OFFSET_STRIDE_MASK); \ + _offset >= ((_start) & OFFSET_STRIDE_MASK) && \ + _offset <= ((_end) & OFFSET_STRIDE_MASK); \ + _offset += OFFSET_STRIDE) + +static inline u32 sec_offset_hash(struct section *sec, unsigned long offset) +{ + u32 ol, oh, idx = sec->idx; + + offset &= OFFSET_STRIDE_MASK; + + ol = offset; + oh = (offset >> 16) >> 16; + + __jhash_mix(ol, oh, idx); + + return ol; +} + +static inline u32 reloc_hash(struct reloc *reloc) +{ + return sec_offset_hash(reloc->sec, reloc_offset(reloc)); +} + #endif /* _OBJTOOL_ELF_H */ diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index b1c920dc9516..ac04d3fe4dd9 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -55,15 +55,22 @@ static inline char *offstr(struct section *sec, unsigned long offset) #define WARN_INSN(insn, format, ...) \ ({ \ - WARN_FUNC(format, insn->sec, insn->offset, ##__VA_ARGS__); \ + struct instruction *_insn = (insn); \ + if (!_insn->sym || !_insn->sym->warned) \ + WARN_FUNC(format, _insn->sec, _insn->offset, \ + ##__VA_ARGS__); \ + if (_insn->sym) \ + _insn->sym->warned = 1; \ }) -#define BT_FUNC(format, insn, ...) \ -({ \ - struct instruction *_insn = (insn); \ - char *_str = offstr(_insn->sec, _insn->offset); \ - WARN(" %s: " format, _str, ##__VA_ARGS__); \ - free(_str); \ +#define BT_INSN(insn, format, ...) \ +({ \ + if (opts.verbose || opts.backtrace) { \ + struct instruction *_insn = (insn); \ + char *_str = offstr(_insn->sec, _insn->offset); \ + WARN(" %s: " format, _str, ##__VA_ARGS__); \ + free(_str); \ + } \ }) #define WARN_ELF(format, ...) \ diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h new file mode 100644 index 000000000000..e45c7cb1d5bc --- /dev/null +++ b/tools/objtool/noreturns.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * This is a (sorted!) list of all known __noreturn functions in the kernel. + * It's needed for objtool to properly reverse-engineer the control flow graph. + * + * Yes, this is unfortunate. A better solution is in the works. + */ +NORETURN(__invalid_creds) +NORETURN(__kunit_abort) +NORETURN(__module_put_and_kthread_exit) +NORETURN(__reiserfs_panic) +NORETURN(__stack_chk_fail) +NORETURN(__ubsan_handle_builtin_unreachable) +NORETURN(arch_call_rest_init) +NORETURN(arch_cpu_idle_dead) +NORETURN(cpu_bringup_and_idle) +NORETURN(cpu_startup_entry) +NORETURN(do_exit) +NORETURN(do_group_exit) +NORETURN(do_task_dead) +NORETURN(ex_handler_msr_mce) +NORETURN(fortify_panic) +NORETURN(hlt_play_dead) +NORETURN(hv_ghcb_terminate) +NORETURN(kthread_complete_and_exit) +NORETURN(kthread_exit) +NORETURN(kunit_try_catch_throw) +NORETURN(machine_real_restart) +NORETURN(make_task_dead) +NORETURN(mpt_halt_firmware) +NORETURN(nmi_panic_self_stop) +NORETURN(panic) +NORETURN(panic_smp_self_stop) +NORETURN(rest_init) +NORETURN(rewind_stack_and_make_dead) +NORETURN(sev_es_terminate) +NORETURN(snp_abort) +NORETURN(start_kernel) +NORETURN(stop_this_cpu) +NORETURN(usercopy_abort) +NORETURN(x86_64_start_kernel) +NORETURN(x86_64_start_reservations) +NORETURN(xen_cpu_bringup_again) +NORETURN(xen_start_kernel) diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c index 48efd1e2f00d..bae343908867 100644 --- a/tools/objtool/orc_gen.c +++ b/tools/objtool/orc_gen.c @@ -118,8 +118,8 @@ static int write_orc_entry(struct elf *elf, struct section *orc_sec, orc->bp_offset = bswap_if_needed(elf, orc->bp_offset); /* populate reloc for ip */ - if (elf_add_reloc_to_insn(elf, ip_sec, idx * sizeof(int), R_X86_64_PC32, - insn_sec, insn_off)) + if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx, + insn_sec, insn_off)) return -1; return 0; @@ -237,12 +237,12 @@ int orc_create(struct objtool_file *file) WARN("file already has .orc_unwind section, skipping"); return -1; } - orc_sec = elf_create_section(file->elf, ".orc_unwind", 0, + orc_sec = elf_create_section(file->elf, ".orc_unwind", sizeof(struct orc_entry), nr); if (!orc_sec) return -1; - sec = elf_create_section(file->elf, ".orc_unwind_ip", 0, sizeof(int), nr); + sec = elf_create_section_pair(file->elf, ".orc_unwind_ip", sizeof(int), nr, nr); if (!sec) return -1; diff --git a/tools/objtool/special.c b/tools/objtool/special.c index baa85c31526b..91b1950f5bd8 100644 --- a/tools/objtool/special.c +++ b/tools/objtool/special.c @@ -62,7 +62,7 @@ static void reloc_to_sec_off(struct reloc *reloc, struct section **sec, unsigned long *off) { *sec = reloc->sym->sec; - *off = reloc->sym->offset + reloc->addend; + *off = reloc->sym->offset + reloc_addend(reloc); } static int get_alt_entry(struct elf *elf, const struct special_entry *entry, @@ -126,7 +126,7 @@ static int get_alt_entry(struct elf *elf, const struct special_entry *entry, sec, offset + entry->key); return -1; } - alt->key_addend = key_reloc->addend; + alt->key_addend = reloc_addend(key_reloc); } return 0; diff --git a/tools/perf/arch/x86/include/arch-tests.h b/tools/perf/arch/x86/include/arch-tests.h index df133020d582..c0421a26b875 100644 --- a/tools/perf/arch/x86/include/arch-tests.h +++ b/tools/perf/arch/x86/include/arch-tests.h @@ -13,6 +13,7 @@ int test__intel_pt_pkt_decoder(struct test_suite *test, int subtest); int test__intel_pt_hybrid_compat(struct test_suite *test, int subtest); int test__bp_modify(struct test_suite *test, int subtest); int test__x86_sample_parsing(struct test_suite *test, int subtest); +int test__amd_ibs_via_core_pmu(struct test_suite *test, int subtest); int test__hybrid(struct test_suite *test, int subtest); extern struct test_suite *arch_tests[]; diff --git a/tools/perf/arch/x86/tests/Build b/tools/perf/arch/x86/tests/Build index 394771c00dca..b87f46e5feea 100644 --- a/tools/perf/arch/x86/tests/Build +++ b/tools/perf/arch/x86/tests/Build @@ -9,3 +9,4 @@ ifeq ($(CONFIG_EXTRA_TESTS),y) perf-$(CONFIG_AUXTRACE) += insn-x86.o endif perf-$(CONFIG_X86_64) += bp-modify.o +perf-y += amd-ibs-via-core-pmu.o diff --git a/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c b/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c new file mode 100644 index 000000000000..78b1902f6f59 --- /dev/null +++ b/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "arch-tests.h" +#include "linux/perf_event.h" +#include "tests/tests.h" +#include "pmu.h" +#include "pmus.h" +#include "../perf-sys.h" +#include "debug.h" + +#define NR_SUB_TESTS 5 + +static struct sub_tests { + int type; + unsigned long config; + bool valid; +} sub_tests[NR_SUB_TESTS] = { + { PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, true }, + { PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, false }, + { PERF_TYPE_RAW, 0x076, true }, + { PERF_TYPE_RAW, 0x0C1, true }, + { PERF_TYPE_RAW, 0x012, false }, +}; + +static int event_open(int type, unsigned long config) +{ + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(struct perf_event_attr)); + attr.type = type; + attr.size = sizeof(struct perf_event_attr); + attr.config = config; + attr.disabled = 1; + attr.precise_ip = 1; + attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID; + attr.sample_period = 100000; + + return sys_perf_event_open(&attr, -1, 0, -1, 0); +} + +int test__amd_ibs_via_core_pmu(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_pmu *ibs_pmu; + int ret = TEST_OK; + int fd, i; + + ibs_pmu = perf_pmus__find("ibs_op"); + if (!ibs_pmu) + return TEST_SKIP; + + for (i = 0; i < NR_SUB_TESTS; i++) { + fd = event_open(sub_tests[i].type, sub_tests[i].config); + pr_debug("type: 0x%x, config: 0x%lx, fd: %d - ", sub_tests[i].type, + sub_tests[i].config, fd); + if ((sub_tests[i].valid && fd == -1) || + (!sub_tests[i].valid && fd > 0)) { + pr_debug("Fail\n"); + ret = TEST_FAIL; + } else { + pr_debug("Pass\n"); + } + + if (fd > 0) + close(fd); + } + + return ret; +} diff --git a/tools/perf/arch/x86/tests/arch-tests.c b/tools/perf/arch/x86/tests/arch-tests.c index 3f2b90c59f92..a216a5d172ed 100644 --- a/tools/perf/arch/x86/tests/arch-tests.c +++ b/tools/perf/arch/x86/tests/arch-tests.c @@ -24,6 +24,7 @@ struct test_suite suite__intel_pt = { DEFINE_SUITE("x86 bp modify", bp_modify); #endif DEFINE_SUITE("x86 Sample parsing", x86_sample_parsing); +DEFINE_SUITE("AMD IBS via core pmu", amd_ibs_via_core_pmu); static struct test_case hybrid_tests[] = { TEST_CASE_REASON("x86 hybrid event parsing", hybrid, "not hybrid"), { .name = NULL, } @@ -48,6 +49,7 @@ struct test_suite *arch_tests[] = { &suite__bp_modify, #endif &suite__x86_sample_parsing, + &suite__amd_ibs_via_core_pmu, &suite__hybrid, NULL, }; diff --git a/tools/perf/trace/beauty/include/linux/socket.h b/tools/perf/trace/beauty/include/linux/socket.h index 13c3a237b9c9..3bef212a24d7 100644 --- a/tools/perf/trace/beauty/include/linux/socket.h +++ b/tools/perf/trace/beauty/include/linux/socket.h @@ -318,7 +318,6 @@ struct ucred { #define MSG_MORE 0x8000 /* Sender will send more */ #define MSG_WAITFORONE 0x10000 /* recvmmsg(): block until 1+ packets avail */ #define MSG_SENDPAGE_NOPOLICY 0x10000 /* sendpage() internal : do no apply policy */ -#define MSG_SENDPAGE_NOTLAST 0x20000 /* sendpage() internal : not the last page */ #define MSG_BATCH 0x40000 /* sendmmsg(): more messages coming */ #define MSG_EOF MSG_FIN #define MSG_NO_SHARED_FRAGS 0x80000 /* sendpage() internal : page frags are not shared */ diff --git a/tools/perf/trace/beauty/msg_flags.c b/tools/perf/trace/beauty/msg_flags.c index ea68db08b8e7..aa9934020232 100644 --- a/tools/perf/trace/beauty/msg_flags.c +++ b/tools/perf/trace/beauty/msg_flags.c @@ -8,8 +8,8 @@ #ifndef MSG_WAITFORONE #define MSG_WAITFORONE 0x10000 #endif -#ifndef MSG_SENDPAGE_NOTLAST -#define MSG_SENDPAGE_NOTLAST 0x20000 +#ifndef MSG_SPLICE_PAGES +#define MSG_SPLICE_PAGES 0x8000000 #endif #ifndef MSG_FASTOPEN #define MSG_FASTOPEN 0x20000000 @@ -50,7 +50,7 @@ static size_t syscall_arg__scnprintf_msg_flags(char *bf, size_t size, P_MSG_FLAG(NOSIGNAL); P_MSG_FLAG(MORE); P_MSG_FLAG(WAITFORONE); - P_MSG_FLAG(SENDPAGE_NOTLAST); + P_MSG_FLAG(SPLICE_PAGES); P_MSG_FLAG(FASTOPEN); P_MSG_FLAG(CMSG_CLOEXEC); #undef P_MSG_FLAG diff --git a/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c b/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c index f3918f290df5..ba807071d3c1 100644 --- a/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c +++ b/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c @@ -51,7 +51,7 @@ static u64 arm_spe_calc_ip(int index, u64 payload) * (bits [63:56]) is assigned as top-byte tag; so we only can * retrieve address value from bits [55:0]. * - * According to Documentation/arm64/memory.rst, if detects the + * According to Documentation/arch/arm64/memory.rst, if detects the * specific pattern in bits [55:52] of payload which falls in * the kernel space, should fixup the top byte and this allows * perf tool to parse DSO symbol for data address correctly. diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index 2ca0cedd418f..a73346e854b8 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -15,7 +15,7 @@ struct process_cmd_struct { int arg; }; -static const char *version_str = "v1.15"; +static const char *version_str = "v1.16"; static const int supported_api_ver = 2; static struct isst_if_platform_info isst_platform_info; @@ -2113,7 +2113,6 @@ static void set_fact_enable(int arg) else for_each_online_power_domain_in_set(set_fact_for_cpu, NULL, NULL, NULL, &enable); - isst_ctdp_display_information_end(outf); if (!fact_enable_fail && enable && auto_mode) { /* @@ -2192,10 +2191,13 @@ static void set_fact_enable(int arg) isst_display_result(&id, outf, "turbo-freq --auto", "enable", 0); } + isst_ctdp_display_information_end(outf); + return; error_disp: isst_display_result(&id, outf, "turbo-freq --auto", "enable", ret); + isst_ctdp_display_information_end(outf); } @@ -2261,9 +2263,6 @@ static void dump_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2, struct isst_clos_config clos_config; int ret; - if (id->cpu < 0) - return; - ret = isst_pm_get_clos(id, current_clos, &clos_config); if (ret) isst_display_error_info_message(1, "isst_pm_get_clos failed", 0, 0); @@ -2437,12 +2436,16 @@ static void set_clos_assoc(int arg) isst_display_error_info_message(1, "Invalid clos id\n", 0, 0); exit(0); } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) for_each_online_target_cpu_in_set(set_clos_assoc_for_cpu, NULL, NULL, NULL, NULL); else { isst_display_error_info_message(1, "Invalid target cpu. Specify with [-c|--cpu]", 0, 0); } + isst_ctdp_display_information_end(outf); } static void get_clos_assoc_for_cpu(struct isst_id *id, void *arg1, void *arg2, void *arg3, diff --git a/tools/power/x86/intel-speed-select/isst-core-tpmi.c b/tools/power/x86/intel-speed-select/isst-core-tpmi.c index 19caa9c78d41..3458768562e5 100644 --- a/tools/power/x86/intel-speed-select/isst-core-tpmi.c +++ b/tools/power/x86/intel-speed-select/isst-core-tpmi.c @@ -641,16 +641,30 @@ static int tpmi_pm_qos_config(struct isst_id *id, int enable_clos, int priority_type) { struct isst_core_power info; - int ret; + int i, ret, saved_punit; info.get_set = 1; info.socket_id = id->pkg; info.power_domain_id = id->punit; info.enable = enable_clos; info.priority_type = priority_type; - ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info); - if (ret == -1) - return ret; + + saved_punit = id->punit; + + /* Set for all other dies also. This is per package setting */ + for (i = 0; i < MAX_PUNIT_PER_DIE; i++) { + id->punit = i; + if (isst_is_punit_valid(id)) { + info.power_domain_id = i; + ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info); + if (ret == -1) { + id->punit = saved_punit; + return ret; + } + } + } + + id->punit = saved_punit; return 0; } @@ -686,7 +700,7 @@ int tpmi_set_clos(struct isst_id *id, int clos, struct isst_clos_config *clos_config) { struct isst_clos_param info; - int ret; + int i, ret, saved_punit; info.get_set = 1; info.socket_id = id->pkg; @@ -702,9 +716,22 @@ int tpmi_set_clos(struct isst_id *id, int clos, if (info.max_freq_mhz <= 0xff) info.max_freq_mhz *= 100; - ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info); - if (ret == -1) - return ret; + saved_punit = id->punit; + + /* Set for all other dies also. This is per package setting */ + for (i = 0; i < MAX_PUNIT_PER_DIE; i++) { + id->punit = i; + if (isst_is_punit_valid(id)) { + info.power_domain_id = i; + ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info); + if (ret == -1) { + id->punit = saved_punit; + return ret; + } + } + } + + id->punit = saved_punit; debug_printf("set cpu:%d clos:%d min:%d max:%d\n", id->cpu, clos, clos_config->clos_min, clos_config->clos_max); diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index b0ca44c70e83..9179942d7f15 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -172,28 +172,37 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-DsbdlHOLC3vpNR24SI]\n", prog); - puts(" -D --device device to use (default /dev/spidev1.1)\n" - " -s --speed max speed (Hz)\n" - " -d --delay delay (usec)\n" - " -b --bpw bits per word\n" - " -i --input input data from a file (e.g. \"test.bin\")\n" - " -o --output output data to a file (e.g. \"results.bin\")\n" - " -l --loop loopback\n" - " -H --cpha clock phase\n" - " -O --cpol clock polarity\n" - " -L --lsb least significant bit first\n" - " -C --cs-high chip select active high\n" - " -3 --3wire SI/SO signals shared\n" - " -v --verbose Verbose (show tx buffer)\n" - " -p Send data (e.g. \"1234\\xde\\xad\")\n" - " -N --no-cs no chip select\n" - " -R --ready slave pulls low to pause\n" - " -2 --dual dual transfer\n" - " -4 --quad quad transfer\n" - " -8 --octal octal transfer\n" - " -S --size transfer size\n" - " -I --iter iterations\n"); + printf("Usage: %s [-2348CDFHILMNORSZbdilopsv]\n", prog); + puts("general device settings:\n" + " -D --device device to use (default /dev/spidev1.1)\n" + " -s --speed max speed (Hz)\n" + " -d --delay delay (usec)\n" + " -l --loop loopback\n" + "spi mode:\n" + " -H --cpha clock phase\n" + " -O --cpol clock polarity\n" + " -F --rx-cpha-flip flip CPHA on Rx only xfer\n" + "number of wires for transmission:\n" + " -2 --dual dual transfer\n" + " -4 --quad quad transfer\n" + " -8 --octal octal transfer\n" + " -3 --3wire SI/SO signals shared\n" + " -Z --3wire-hiz high impedance turnaround\n" + "data:\n" + " -i --input input data from a file (e.g. \"test.bin\")\n" + " -o --output output data to a file (e.g. \"results.bin\")\n" + " -p Send data (e.g. \"1234\\xde\\xad\")\n" + " -S --size transfer size\n" + " -I --iter iterations\n" + "additional parameters:\n" + " -b --bpw bits per word\n" + " -L --lsb least significant bit first\n" + " -C --cs-high chip select active high\n" + " -N --no-cs no chip select\n" + " -R --ready slave pulls low to pause\n" + " -M --mosi-idle-low leave mosi line low when idle\n" + "misc:\n" + " -v --verbose Verbose (show tx buffer)\n"); exit(1); } @@ -201,31 +210,34 @@ static void parse_opts(int argc, char *argv[]) { while (1) { static const struct option lopts[] = { - { "device", 1, 0, 'D' }, - { "speed", 1, 0, 's' }, - { "delay", 1, 0, 'd' }, - { "bpw", 1, 0, 'b' }, - { "input", 1, 0, 'i' }, - { "output", 1, 0, 'o' }, - { "loop", 0, 0, 'l' }, - { "cpha", 0, 0, 'H' }, - { "cpol", 0, 0, 'O' }, - { "lsb", 0, 0, 'L' }, - { "cs-high", 0, 0, 'C' }, - { "3wire", 0, 0, '3' }, - { "no-cs", 0, 0, 'N' }, - { "ready", 0, 0, 'R' }, - { "dual", 0, 0, '2' }, - { "verbose", 0, 0, 'v' }, - { "quad", 0, 0, '4' }, - { "octal", 0, 0, '8' }, - { "size", 1, 0, 'S' }, - { "iter", 1, 0, 'I' }, + { "device", 1, 0, 'D' }, + { "speed", 1, 0, 's' }, + { "delay", 1, 0, 'd' }, + { "loop", 0, 0, 'l' }, + { "cpha", 0, 0, 'H' }, + { "cpol", 0, 0, 'O' }, + { "rx-cpha-flip", 0, 0, 'F' }, + { "dual", 0, 0, '2' }, + { "quad", 0, 0, '4' }, + { "octal", 0, 0, '8' }, + { "3wire", 0, 0, '3' }, + { "3wire-hiz", 0, 0, 'Z' }, + { "input", 1, 0, 'i' }, + { "output", 1, 0, 'o' }, + { "size", 1, 0, 'S' }, + { "iter", 1, 0, 'I' }, + { "bpw", 1, 0, 'b' }, + { "lsb", 0, 0, 'L' }, + { "cs-high", 0, 0, 'C' }, + { "no-cs", 0, 0, 'N' }, + { "ready", 0, 0, 'R' }, + { "mosi-idle-low", 0, 0, 'M' }, + { "verbose", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int c; - c = getopt_long(argc, argv, "D:s:d:b:i:o:lHOLC3NR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:b:i:o:lHOLC3ZFMNR248p:vS:I:", lopts, NULL); if (c == -1) @@ -268,6 +280,15 @@ static void parse_opts(int argc, char *argv[]) case '3': mode |= SPI_3WIRE; break; + case 'Z': + mode |= SPI_3WIRE_HIZ; + break; + case 'F': + mode |= SPI_RX_CPHA_FLIP; + break; + case 'M': + mode |= SPI_MOSI_IDLE_LOW; + break; case 'N': mode |= SPI_NO_CS; break; diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild index fba7bec96acd..90f3c9802ffb 100644 --- a/tools/testing/cxl/Kbuild +++ b/tools/testing/cxl/Kbuild @@ -11,7 +11,8 @@ ldflags-y += --wrap=devm_cxl_enumerate_decoders ldflags-y += --wrap=cxl_await_media_ready ldflags-y += --wrap=cxl_hdm_decode_init ldflags-y += --wrap=cxl_dvsec_rr_decode -ldflags-y += --wrap=cxl_rcrb_to_component +ldflags-y += --wrap=devm_cxl_add_rch_dport +ldflags-y += --wrap=cxl_rcd_component_reg_phys DRIVERS := ../../../drivers CXL_SRC := $(DRIVERS)/cxl @@ -56,6 +57,7 @@ cxl_core-y += $(CXL_CORE_SRC)/memdev.o cxl_core-y += $(CXL_CORE_SRC)/mbox.o cxl_core-y += $(CXL_CORE_SRC)/pci.o cxl_core-y += $(CXL_CORE_SRC)/hdm.o +cxl_core-y += $(CXL_CORE_SRC)/pmu.o cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o cxl_core-y += config_check.o diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c index bf00dc52fe96..0e78d8e19895 100644 --- a/tools/testing/cxl/test/cxl.c +++ b/tools/testing/cxl/test/cxl.c @@ -713,7 +713,7 @@ static void default_mock_decoder(struct cxl_decoder *cxld) cxld->interleave_ways = 1; cxld->interleave_granularity = 256; - cxld->target_type = CXL_DECODER_EXPANDER; + cxld->target_type = CXL_DECODER_HOSTONLYMEM; cxld->commit = mock_decoder_commit; cxld->reset = mock_decoder_reset; } @@ -754,7 +754,7 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) /* check is endpoint is attach to host-bridge0 */ port = cxled_to_port(cxled); do { - if (port->uport == &cxl_host_bridge[0]->dev) { + if (port->uport_dev == &cxl_host_bridge[0]->dev) { hb0 = true; break; } @@ -787,7 +787,7 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) cxld->interleave_ways = 2; eig_to_granularity(window->granularity, &cxld->interleave_granularity); - cxld->target_type = CXL_DECODER_EXPANDER; + cxld->target_type = CXL_DECODER_HOSTONLYMEM; cxld->flags = CXL_DECODER_F_ENABLE; cxled->state = CXL_DECODER_STATE_AUTO; port->commit_end = cxld->id; @@ -820,7 +820,7 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) } else cxlsd->target[0] = dport; cxld = &cxlsd->cxld; - cxld->target_type = CXL_DECODER_EXPANDER; + cxld->target_type = CXL_DECODER_HOSTONLYMEM; cxld->flags = CXL_DECODER_F_ENABLE; iter->commit_end = 0; /* @@ -889,7 +889,7 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, mock_init_hdm_decoder(cxld); if (target_count) { - rc = device_for_each_child(port->uport, &ctx, + rc = device_for_each_child(port->uport_dev, &ctx, map_targets); if (rc) { put_device(&cxld->dev); @@ -919,29 +919,29 @@ static int mock_cxl_port_enumerate_dports(struct cxl_port *port) int i, array_size; if (port->depth == 1) { - if (is_multi_bridge(port->uport)) { + if (is_multi_bridge(port->uport_dev)) { array_size = ARRAY_SIZE(cxl_root_port); array = cxl_root_port; - } else if (is_single_bridge(port->uport)) { + } else if (is_single_bridge(port->uport_dev)) { array_size = ARRAY_SIZE(cxl_root_single); array = cxl_root_single; } else { dev_dbg(&port->dev, "%s: unknown bridge type\n", - dev_name(port->uport)); + dev_name(port->uport_dev)); return -ENXIO; } } else if (port->depth == 2) { struct cxl_port *parent = to_cxl_port(port->dev.parent); - if (is_multi_bridge(parent->uport)) { + if (is_multi_bridge(parent->uport_dev)) { array_size = ARRAY_SIZE(cxl_switch_dport); array = cxl_switch_dport; - } else if (is_single_bridge(parent->uport)) { + } else if (is_single_bridge(parent->uport_dev)) { array_size = ARRAY_SIZE(cxl_swd_single); array = cxl_swd_single; } else { dev_dbg(&port->dev, "%s: unknown bridge type\n", - dev_name(port->uport)); + dev_name(port->uport_dev)); return -ENXIO; } } else { @@ -954,9 +954,9 @@ static int mock_cxl_port_enumerate_dports(struct cxl_port *port) struct platform_device *pdev = array[i]; struct cxl_dport *dport; - if (pdev->dev.parent != port->uport) { + if (pdev->dev.parent != port->uport_dev) { dev_dbg(&port->dev, "%s: mismatch parent %s\n", - dev_name(port->uport), + dev_name(port->uport_dev), dev_name(pdev->dev.parent)); continue; } @@ -971,15 +971,6 @@ static int mock_cxl_port_enumerate_dports(struct cxl_port *port) return 0; } -resource_size_t mock_cxl_rcrb_to_component(struct device *dev, - resource_size_t rcrb, - enum cxl_rcrb which) -{ - dev_dbg(dev, "rcrb: %pa which: %d\n", &rcrb, which); - - return (resource_size_t) which + 1; -} - static struct cxl_mock_ops cxl_mock_ops = { .is_mock_adev = is_mock_adev, .is_mock_bridge = is_mock_bridge, @@ -988,7 +979,6 @@ static struct cxl_mock_ops cxl_mock_ops = { .is_mock_dev = is_mock_dev, .acpi_table_parse_cedt = mock_acpi_table_parse_cedt, .acpi_evaluate_integer = mock_acpi_evaluate_integer, - .cxl_rcrb_to_component = mock_cxl_rcrb_to_component, .acpi_pci_find_root = mock_acpi_pci_find_root, .devm_cxl_port_enumerate_dports = mock_cxl_port_enumerate_dports, .devm_cxl_setup_hdm = mock_cxl_setup_hdm, diff --git a/tools/testing/cxl/test/mem.c b/tools/testing/cxl/test/mem.c index ba572d03c687..464fc39ed277 100644 --- a/tools/testing/cxl/test/mem.c +++ b/tools/testing/cxl/test/mem.c @@ -8,11 +8,14 @@ #include <linux/sizes.h> #include <linux/bits.h> #include <asm/unaligned.h> +#include <crypto/sha2.h> #include <cxlmem.h> #include "trace.h" #define LSA_SIZE SZ_128K +#define FW_SIZE SZ_64M +#define FW_SLOTS 3 #define DEV_SIZE SZ_2G #define EFFECT(x) (1U << x) @@ -21,42 +24,70 @@ static unsigned int poison_inject_dev_max = MOCK_INJECT_DEV_MAX; +enum cxl_command_effects { + CONF_CHANGE_COLD_RESET = 0, + CONF_CHANGE_IMMEDIATE, + DATA_CHANGE_IMMEDIATE, + POLICY_CHANGE_IMMEDIATE, + LOG_CHANGE_IMMEDIATE, + SECURITY_CHANGE_IMMEDIATE, + BACKGROUND_OP, + SECONDARY_MBOX_SUPPORTED, +}; + +#define CXL_CMD_EFFECT_NONE cpu_to_le16(0) + static struct cxl_cel_entry mock_cel[] = { { .opcode = cpu_to_le16(CXL_MBOX_OP_GET_SUPPORTED_LOGS), - .effect = cpu_to_le16(0), + .effect = CXL_CMD_EFFECT_NONE, }, { .opcode = cpu_to_le16(CXL_MBOX_OP_IDENTIFY), - .effect = cpu_to_le16(0), + .effect = CXL_CMD_EFFECT_NONE, }, { .opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA), - .effect = cpu_to_le16(0), + .effect = CXL_CMD_EFFECT_NONE, }, { .opcode = cpu_to_le16(CXL_MBOX_OP_GET_PARTITION_INFO), - .effect = cpu_to_le16(0), + .effect = CXL_CMD_EFFECT_NONE, }, { .opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA), - .effect = cpu_to_le16(EFFECT(1) | EFFECT(2)), + .effect = cpu_to_le16(EFFECT(CONF_CHANGE_IMMEDIATE) | + EFFECT(DATA_CHANGE_IMMEDIATE)), }, { .opcode = cpu_to_le16(CXL_MBOX_OP_GET_HEALTH_INFO), - .effect = cpu_to_le16(0), + .effect = CXL_CMD_EFFECT_NONE, }, { .opcode = cpu_to_le16(CXL_MBOX_OP_GET_POISON), - .effect = cpu_to_le16(0), + .effect = CXL_CMD_EFFECT_NONE, }, { .opcode = cpu_to_le16(CXL_MBOX_OP_INJECT_POISON), - .effect = cpu_to_le16(0), + .effect = cpu_to_le16(EFFECT(DATA_CHANGE_IMMEDIATE)), }, { .opcode = cpu_to_le16(CXL_MBOX_OP_CLEAR_POISON), - .effect = cpu_to_le16(0), + .effect = cpu_to_le16(EFFECT(DATA_CHANGE_IMMEDIATE)), + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_FW_INFO), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_TRANSFER_FW), + .effect = cpu_to_le16(EFFECT(CONF_CHANGE_COLD_RESET) | + EFFECT(BACKGROUND_OP)), + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_ACTIVATE_FW), + .effect = cpu_to_le16(EFFECT(CONF_CHANGE_COLD_RESET) | + EFFECT(CONF_CHANGE_IMMEDIATE)), }, }; @@ -102,13 +133,17 @@ struct mock_event_log { }; struct mock_event_store { - struct cxl_dev_state *cxlds; + struct cxl_memdev_state *mds; struct mock_event_log mock_logs[CXL_EVENT_TYPE_MAX]; u32 ev_status; }; struct cxl_mockmem_data { void *lsa; + void *fw; + int fw_slot; + int fw_staged; + size_t fw_size; u32 security_state; u8 user_pass[NVDIMM_PASSPHRASE_LEN]; u8 master_pass[NVDIMM_PASSPHRASE_LEN]; @@ -180,8 +215,7 @@ static void mes_add_event(struct mock_event_store *mes, log->nr_events++; } -static int mock_get_event(struct cxl_dev_state *cxlds, - struct cxl_mbox_cmd *cmd) +static int mock_get_event(struct device *dev, struct cxl_mbox_cmd *cmd) { struct cxl_get_event_payload *pl; struct mock_event_log *log; @@ -201,7 +235,7 @@ static int mock_get_event(struct cxl_dev_state *cxlds, memset(cmd->payload_out, 0, cmd->size_out); - log = event_find_log(cxlds->dev, log_type); + log = event_find_log(dev, log_type); if (!log || event_log_empty(log)) return 0; @@ -234,8 +268,7 @@ static int mock_get_event(struct cxl_dev_state *cxlds, return 0; } -static int mock_clear_event(struct cxl_dev_state *cxlds, - struct cxl_mbox_cmd *cmd) +static int mock_clear_event(struct device *dev, struct cxl_mbox_cmd *cmd) { struct cxl_mbox_clear_event_payload *pl = cmd->payload_in; struct mock_event_log *log; @@ -246,7 +279,7 @@ static int mock_clear_event(struct cxl_dev_state *cxlds, if (log_type >= CXL_EVENT_TYPE_MAX) return -EINVAL; - log = event_find_log(cxlds->dev, log_type); + log = event_find_log(dev, log_type); if (!log) return 0; /* No mock data in this log */ @@ -256,7 +289,7 @@ static int mock_clear_event(struct cxl_dev_state *cxlds, * However, this is not good behavior for the host so test it. */ if (log->clear_idx + pl->nr_recs > log->cur_idx) { - dev_err(cxlds->dev, + dev_err(dev, "Attempting to clear more events than returned!\n"); return -EINVAL; } @@ -266,7 +299,7 @@ static int mock_clear_event(struct cxl_dev_state *cxlds, nr < pl->nr_recs; nr++, handle++) { if (handle != le16_to_cpu(pl->handles[nr])) { - dev_err(cxlds->dev, "Clearing events out of order\n"); + dev_err(dev, "Clearing events out of order\n"); return -EINVAL; } } @@ -293,7 +326,7 @@ static void cxl_mock_event_trigger(struct device *dev) event_reset_log(log); } - cxl_mem_get_event_records(mes->cxlds, mes->ev_status); + cxl_mem_get_event_records(mes->mds, mes->ev_status); } struct cxl_event_record_raw maint_needed = { @@ -453,7 +486,7 @@ static int mock_gsl(struct cxl_mbox_cmd *cmd) return 0; } -static int mock_get_log(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_get_log(struct cxl_memdev_state *mds, struct cxl_mbox_cmd *cmd) { struct cxl_mbox_get_log *gl = cmd->payload_in; u32 offset = le32_to_cpu(gl->offset); @@ -463,7 +496,7 @@ static int mock_get_log(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) if (cmd->size_in < sizeof(*gl)) return -EINVAL; - if (length > cxlds->payload_size) + if (length > mds->payload_size) return -EINVAL; if (offset + length > sizeof(mock_cel)) return -EINVAL; @@ -477,7 +510,7 @@ static int mock_get_log(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) return 0; } -static int mock_rcd_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_rcd_id(struct cxl_mbox_cmd *cmd) { struct cxl_mbox_identify id = { .fw_revision = { "mock fw v1 " }, @@ -495,7 +528,7 @@ static int mock_rcd_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) return 0; } -static int mock_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_id(struct cxl_mbox_cmd *cmd) { struct cxl_mbox_identify id = { .fw_revision = { "mock fw v1 " }, @@ -517,8 +550,7 @@ static int mock_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) return 0; } -static int mock_partition_info(struct cxl_dev_state *cxlds, - struct cxl_mbox_cmd *cmd) +static int mock_partition_info(struct cxl_mbox_cmd *cmd) { struct cxl_mbox_get_partition_info pi = { .active_volatile_cap = @@ -535,11 +567,52 @@ static int mock_partition_info(struct cxl_dev_state *cxlds, return 0; } -static int mock_get_security_state(struct cxl_dev_state *cxlds, - struct cxl_mbox_cmd *cmd) +static int mock_sanitize(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); + if (cmd->size_in != 0) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + if (mdata->security_state & CXL_PMEM_SEC_STATE_LOCKED) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + return 0; /* assume less than 2 secs, no bg */ +} + +static int mock_secure_erase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_in != 0) + return -EINVAL; + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (mdata->security_state & CXL_PMEM_SEC_STATE_LOCKED) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + return 0; +} + +static int mock_get_security_state(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ if (cmd->size_in) return -EINVAL; @@ -569,9 +642,9 @@ static void user_plimit_check(struct cxl_mockmem_data *mdata) mdata->security_state |= CXL_PMEM_SEC_STATE_USER_PLIMIT; } -static int mock_set_passphrase(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_set_passphrase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); struct cxl_set_pass *set_pass; if (cmd->size_in != sizeof(*set_pass)) @@ -629,9 +702,9 @@ static int mock_set_passphrase(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd return -EINVAL; } -static int mock_disable_passphrase(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_disable_passphrase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); struct cxl_disable_pass *dis_pass; if (cmd->size_in != sizeof(*dis_pass)) @@ -700,10 +773,9 @@ static int mock_disable_passphrase(struct cxl_dev_state *cxlds, struct cxl_mbox_ return 0; } -static int mock_freeze_security(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_freeze_security(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); - if (cmd->size_in != 0) return -EINVAL; @@ -717,10 +789,9 @@ static int mock_freeze_security(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd return 0; } -static int mock_unlock_security(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_unlock_security(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); - if (cmd->size_in != NVDIMM_PASSPHRASE_LEN) return -EINVAL; @@ -759,10 +830,9 @@ static int mock_unlock_security(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd return 0; } -static int mock_passphrase_secure_erase(struct cxl_dev_state *cxlds, +static int mock_passphrase_secure_erase(struct cxl_mockmem_data *mdata, struct cxl_mbox_cmd *cmd) { - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); struct cxl_pass_erase *erase; if (cmd->size_in != sizeof(*erase)) @@ -858,10 +928,10 @@ static int mock_passphrase_secure_erase(struct cxl_dev_state *cxlds, return 0; } -static int mock_get_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_get_lsa(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { struct cxl_mbox_get_lsa *get_lsa = cmd->payload_in; - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); void *lsa = mdata->lsa; u32 offset, length; @@ -878,10 +948,10 @@ static int mock_get_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) return 0; } -static int mock_set_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_set_lsa(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { struct cxl_mbox_set_lsa *set_lsa = cmd->payload_in; - struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); void *lsa = mdata->lsa; u32 offset, length; @@ -896,8 +966,7 @@ static int mock_set_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) return 0; } -static int mock_health_info(struct cxl_dev_state *cxlds, - struct cxl_mbox_cmd *cmd) +static int mock_health_info(struct cxl_mbox_cmd *cmd) { struct cxl_mbox_health_info health_info = { /* set flags for maint needed, perf degraded, hw replacement */ @@ -1114,9 +1183,90 @@ static struct attribute *cxl_mock_mem_core_attrs[] = { }; ATTRIBUTE_GROUPS(cxl_mock_mem_core); -static int cxl_mock_mbox_send(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) +static int mock_fw_info(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_get_fw_info fw_info = { + .num_slots = FW_SLOTS, + .slot_info = (mdata->fw_slot & 0x7) | + ((mdata->fw_staged & 0x7) << 3), + .activation_cap = 0, + }; + + strcpy(fw_info.slot_1_revision, "cxl_test_fw_001"); + strcpy(fw_info.slot_2_revision, "cxl_test_fw_002"); + strcpy(fw_info.slot_3_revision, "cxl_test_fw_003"); + strcpy(fw_info.slot_4_revision, ""); + + if (cmd->size_out < sizeof(fw_info)) + return -EINVAL; + + memcpy(cmd->payload_out, &fw_info, sizeof(fw_info)); + return 0; +} + +static int mock_transfer_fw(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) { + struct cxl_mbox_transfer_fw *transfer = cmd->payload_in; + void *fw = mdata->fw; + size_t offset, length; + + offset = le32_to_cpu(transfer->offset) * CXL_FW_TRANSFER_ALIGNMENT; + length = cmd->size_in - sizeof(*transfer); + if (offset + length > FW_SIZE) + return -EINVAL; + + switch (transfer->action) { + case CXL_FW_TRANSFER_ACTION_FULL: + if (offset != 0) + return -EINVAL; + fallthrough; + case CXL_FW_TRANSFER_ACTION_END: + if (transfer->slot == 0 || transfer->slot > FW_SLOTS) + return -EINVAL; + mdata->fw_size = offset + length; + break; + case CXL_FW_TRANSFER_ACTION_INITIATE: + case CXL_FW_TRANSFER_ACTION_CONTINUE: + break; + case CXL_FW_TRANSFER_ACTION_ABORT: + return 0; + default: + return -EINVAL; + } + + memcpy(fw + offset, transfer->data, length); + return 0; +} + +static int mock_activate_fw(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_activate_fw *activate = cmd->payload_in; + + if (activate->slot == 0 || activate->slot > FW_SLOTS) + return -EINVAL; + + switch (activate->action) { + case CXL_FW_ACTIVATE_ONLINE: + mdata->fw_slot = activate->slot; + mdata->fw_staged = 0; + return 0; + case CXL_FW_ACTIVATE_OFFLINE: + mdata->fw_staged = activate->slot; + return 0; + } + + return -EINVAL; +} + +static int cxl_mock_mbox_send(struct cxl_memdev_state *mds, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_dev_state *cxlds = &mds->cxlds; struct device *dev = cxlds->dev; + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); int rc = -EIO; switch (cmd->opcode) { @@ -1127,49 +1277,55 @@ static int cxl_mock_mbox_send(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd * rc = mock_gsl(cmd); break; case CXL_MBOX_OP_GET_LOG: - rc = mock_get_log(cxlds, cmd); + rc = mock_get_log(mds, cmd); break; case CXL_MBOX_OP_IDENTIFY: if (cxlds->rcd) - rc = mock_rcd_id(cxlds, cmd); + rc = mock_rcd_id(cmd); else - rc = mock_id(cxlds, cmd); + rc = mock_id(cmd); break; case CXL_MBOX_OP_GET_LSA: - rc = mock_get_lsa(cxlds, cmd); + rc = mock_get_lsa(mdata, cmd); break; case CXL_MBOX_OP_GET_PARTITION_INFO: - rc = mock_partition_info(cxlds, cmd); + rc = mock_partition_info(cmd); break; case CXL_MBOX_OP_GET_EVENT_RECORD: - rc = mock_get_event(cxlds, cmd); + rc = mock_get_event(dev, cmd); break; case CXL_MBOX_OP_CLEAR_EVENT_RECORD: - rc = mock_clear_event(cxlds, cmd); + rc = mock_clear_event(dev, cmd); break; case CXL_MBOX_OP_SET_LSA: - rc = mock_set_lsa(cxlds, cmd); + rc = mock_set_lsa(mdata, cmd); break; case CXL_MBOX_OP_GET_HEALTH_INFO: - rc = mock_health_info(cxlds, cmd); + rc = mock_health_info(cmd); + break; + case CXL_MBOX_OP_SANITIZE: + rc = mock_sanitize(mdata, cmd); + break; + case CXL_MBOX_OP_SECURE_ERASE: + rc = mock_secure_erase(mdata, cmd); break; case CXL_MBOX_OP_GET_SECURITY_STATE: - rc = mock_get_security_state(cxlds, cmd); + rc = mock_get_security_state(mdata, cmd); break; case CXL_MBOX_OP_SET_PASSPHRASE: - rc = mock_set_passphrase(cxlds, cmd); + rc = mock_set_passphrase(mdata, cmd); break; case CXL_MBOX_OP_DISABLE_PASSPHRASE: - rc = mock_disable_passphrase(cxlds, cmd); + rc = mock_disable_passphrase(mdata, cmd); break; case CXL_MBOX_OP_FREEZE_SECURITY: - rc = mock_freeze_security(cxlds, cmd); + rc = mock_freeze_security(mdata, cmd); break; case CXL_MBOX_OP_UNLOCK: - rc = mock_unlock_security(cxlds, cmd); + rc = mock_unlock_security(mdata, cmd); break; case CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE: - rc = mock_passphrase_secure_erase(cxlds, cmd); + rc = mock_passphrase_secure_erase(mdata, cmd); break; case CXL_MBOX_OP_GET_POISON: rc = mock_get_poison(cxlds, cmd); @@ -1180,6 +1336,15 @@ static int cxl_mock_mbox_send(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd * case CXL_MBOX_OP_CLEAR_POISON: rc = mock_clear_poison(cxlds, cmd); break; + case CXL_MBOX_OP_GET_FW_INFO: + rc = mock_fw_info(mdata, cmd); + break; + case CXL_MBOX_OP_TRANSFER_FW: + rc = mock_transfer_fw(mdata, cmd); + break; + case CXL_MBOX_OP_ACTIVATE_FW: + rc = mock_activate_fw(mdata, cmd); + break; default: break; } @@ -1195,6 +1360,11 @@ static void label_area_release(void *lsa) vfree(lsa); } +static void fw_buf_release(void *buf) +{ + vfree(buf); +} + static bool is_rcd(struct platform_device *pdev) { const struct platform_device_id *id = platform_get_device_id(pdev); @@ -1215,6 +1385,7 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct cxl_memdev *cxlmd; + struct cxl_memdev_state *mds; struct cxl_dev_state *cxlds; struct cxl_mockmem_data *mdata; int rc; @@ -1227,51 +1398,67 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) mdata->lsa = vmalloc(LSA_SIZE); if (!mdata->lsa) return -ENOMEM; + mdata->fw = vmalloc(FW_SIZE); + if (!mdata->fw) + return -ENOMEM; + mdata->fw_slot = 2; + rc = devm_add_action_or_reset(dev, label_area_release, mdata->lsa); if (rc) return rc; - cxlds = cxl_dev_state_create(dev); - if (IS_ERR(cxlds)) - return PTR_ERR(cxlds); + rc = devm_add_action_or_reset(dev, fw_buf_release, mdata->fw); + if (rc) + return rc; + + mds = cxl_memdev_state_create(dev); + if (IS_ERR(mds)) + return PTR_ERR(mds); + mds->mbox_send = cxl_mock_mbox_send; + mds->payload_size = SZ_4K; + mds->event.buf = (struct cxl_get_event_payload *) mdata->event_buf; + + cxlds = &mds->cxlds; cxlds->serial = pdev->id; - cxlds->mbox_send = cxl_mock_mbox_send; - cxlds->payload_size = SZ_4K; - cxlds->event.buf = (struct cxl_get_event_payload *) mdata->event_buf; if (is_rcd(pdev)) { cxlds->rcd = true; cxlds->component_reg_phys = CXL_RESOURCE_NONE; } - rc = cxl_enumerate_cmds(cxlds); + rc = cxl_enumerate_cmds(mds); if (rc) return rc; - rc = cxl_poison_state_init(cxlds); + rc = cxl_poison_state_init(mds); if (rc) return rc; - rc = cxl_set_timestamp(cxlds); + rc = cxl_set_timestamp(mds); if (rc) return rc; - rc = cxl_dev_state_identify(cxlds); + cxlds->media_ready = true; + rc = cxl_dev_state_identify(mds); if (rc) return rc; - rc = cxl_mem_create_range_info(cxlds); + rc = cxl_mem_create_range_info(mds); if (rc) return rc; - mdata->mes.cxlds = cxlds; + mdata->mes.mds = mds; cxl_mock_add_event_logs(&mdata->mes); cxlmd = devm_cxl_add_memdev(cxlds); if (IS_ERR(cxlmd)) return PTR_ERR(cxlmd); - cxl_mem_get_event_records(cxlds, CXLDEV_EVENT_STATUS_ALL); + rc = cxl_memdev_setup_fw_upload(mds); + if (rc) + return rc; + + cxl_mem_get_event_records(mds, CXLDEV_EVENT_STATUS_ALL); return 0; } @@ -1309,9 +1496,40 @@ static ssize_t security_lock_store(struct device *dev, struct device_attribute * static DEVICE_ATTR_RW(security_lock); +static ssize_t fw_buf_checksum_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + u8 hash[SHA256_DIGEST_SIZE]; + unsigned char *hstr, *hptr; + struct sha256_state sctx; + ssize_t written = 0; + int i; + + sha256_init(&sctx); + sha256_update(&sctx, mdata->fw, mdata->fw_size); + sha256_final(&sctx, hash); + + hstr = kzalloc((SHA256_DIGEST_SIZE * 2) + 1, GFP_KERNEL); + if (!hstr) + return -ENOMEM; + + hptr = hstr; + for (i = 0; i < SHA256_DIGEST_SIZE; i++) + hptr += sprintf(hptr, "%02x", hash[i]); + + written = sysfs_emit(buf, "%s\n", hstr); + + kfree(hstr); + return written; +} + +static DEVICE_ATTR_RO(fw_buf_checksum); + static struct attribute *cxl_mock_mem_attrs[] = { &dev_attr_security_lock.attr, &dev_attr_event_trigger.attr, + &dev_attr_fw_buf_checksum.attr, NULL }; ATTRIBUTE_GROUPS(cxl_mock_mem); diff --git a/tools/testing/cxl/test/mock.c b/tools/testing/cxl/test/mock.c index de3933a776fd..1a61e68e3095 100644 --- a/tools/testing/cxl/test/mock.c +++ b/tools/testing/cxl/test/mock.c @@ -139,7 +139,7 @@ struct cxl_hdm *__wrap_devm_cxl_setup_hdm(struct cxl_port *port, struct cxl_hdm *cxlhdm; struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); - if (ops && ops->is_mock_port(port->uport)) + if (ops && ops->is_mock_port(port->uport_dev)) cxlhdm = ops->devm_cxl_setup_hdm(port, info); else cxlhdm = devm_cxl_setup_hdm(port, info); @@ -154,7 +154,7 @@ int __wrap_devm_cxl_add_passthrough_decoder(struct cxl_port *port) int rc, index; struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); - if (ops && ops->is_mock_port(port->uport)) + if (ops && ops->is_mock_port(port->uport_dev)) rc = ops->devm_cxl_add_passthrough_decoder(port); else rc = devm_cxl_add_passthrough_decoder(port); @@ -171,7 +171,7 @@ int __wrap_devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, struct cxl_port *port = cxlhdm->port; struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); - if (ops && ops->is_mock_port(port->uport)) + if (ops && ops->is_mock_port(port->uport_dev)) rc = ops->devm_cxl_enumerate_decoders(cxlhdm, info); else rc = devm_cxl_enumerate_decoders(cxlhdm, info); @@ -186,7 +186,7 @@ int __wrap_devm_cxl_port_enumerate_dports(struct cxl_port *port) int rc, index; struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); - if (ops && ops->is_mock_port(port->uport)) + if (ops && ops->is_mock_port(port->uport_dev)) rc = ops->devm_cxl_port_enumerate_dports(port); else rc = devm_cxl_port_enumerate_dports(port); @@ -244,24 +244,46 @@ int __wrap_cxl_dvsec_rr_decode(struct device *dev, int dvsec, } EXPORT_SYMBOL_NS_GPL(__wrap_cxl_dvsec_rr_decode, CXL); -resource_size_t __wrap_cxl_rcrb_to_component(struct device *dev, - resource_size_t rcrb, - enum cxl_rcrb which) +struct cxl_dport *__wrap_devm_cxl_add_rch_dport(struct cxl_port *port, + struct device *dport_dev, + int port_id, + resource_size_t rcrb) +{ + int index; + struct cxl_dport *dport; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(dport_dev)) { + dport = devm_cxl_add_dport(port, dport_dev, port_id, + CXL_RESOURCE_NONE); + if (!IS_ERR(dport)) { + dport->rcrb.base = rcrb; + dport->rch = true; + } + } else + dport = devm_cxl_add_rch_dport(port, dport_dev, port_id, rcrb); + put_cxl_mock_ops(index); + + return dport; +} +EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_add_rch_dport, CXL); + +resource_size_t __wrap_cxl_rcd_component_reg_phys(struct device *dev, + struct cxl_dport *dport) { int index; resource_size_t component_reg_phys; struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); if (ops && ops->is_mock_port(dev)) - component_reg_phys = - ops->cxl_rcrb_to_component(dev, rcrb, which); + component_reg_phys = CXL_RESOURCE_NONE; else - component_reg_phys = cxl_rcrb_to_component(dev, rcrb, which); + component_reg_phys = cxl_rcd_component_reg_phys(dev, dport); put_cxl_mock_ops(index); return component_reg_phys; } -EXPORT_SYMBOL_NS_GPL(__wrap_cxl_rcrb_to_component, CXL); +EXPORT_SYMBOL_NS_GPL(__wrap_cxl_rcd_component_reg_phys, CXL); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS(ACPI); diff --git a/tools/testing/cxl/test/mock.h b/tools/testing/cxl/test/mock.h index bef8817b01f2..a94223750346 100644 --- a/tools/testing/cxl/test/mock.h +++ b/tools/testing/cxl/test/mock.h @@ -15,9 +15,6 @@ struct cxl_mock_ops { acpi_string pathname, struct acpi_object_list *arguments, unsigned long long *data); - resource_size_t (*cxl_rcrb_to_component)(struct device *dev, - resource_size_t rcrb, - enum cxl_rcrb which); struct acpi_pci_root *(*acpi_pci_find_root)(acpi_handle handle); bool (*is_mock_bus)(struct pci_bus *bus); bool (*is_mock_port)(struct device *dev); diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config index f990cbb73250..0393940c706a 100644 --- a/tools/testing/kunit/configs/all_tests.config +++ b/tools/testing/kunit/configs/all_tests.config @@ -9,6 +9,8 @@ CONFIG_KUNIT=y CONFIG_KUNIT_EXAMPLE_TEST=y CONFIG_KUNIT_ALL_TESTS=y +CONFIG_FORTIFY_SOURCE=y + CONFIG_IIO=y CONFIG_EXT4_FS=y diff --git a/tools/testing/kunit/configs/arch_uml.config b/tools/testing/kunit/configs/arch_uml.config index e824ce43b05a..54ad8972681a 100644 --- a/tools/testing/kunit/configs/arch_uml.config +++ b/tools/testing/kunit/configs/arch_uml.config @@ -3,3 +3,6 @@ # Enable virtio/pci, as a lot of tests require it. CONFIG_VIRTIO_UML=y CONFIG_UML_PCI_OVER_VIRTIO=y + +# Enable FORTIFY_SOURCE for wider checking. +CONFIG_FORTIFY_SOURCE=y diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py index f01f94106129..7f648802caf6 100644 --- a/tools/testing/kunit/kunit_kernel.py +++ b/tools/testing/kunit/kunit_kernel.py @@ -92,7 +92,7 @@ class LinuxSourceTreeOperations: if stderr: # likely only due to build warnings print(stderr.decode()) - def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: + def start(self, params: List[str], build_dir: str) -> subprocess.Popen: raise RuntimeError('not implemented!') @@ -113,7 +113,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): kconfig.merge_in_entries(base_kunitconfig) return kconfig - def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: + def start(self, params: List[str], build_dir: str) -> subprocess.Popen: kernel_path = os.path.join(build_dir, self._kernel_path) qemu_command = ['qemu-system-' + self._qemu_arch, '-nodefaults', @@ -142,7 +142,7 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): kconfig.merge_in_entries(base_kunitconfig) return kconfig - def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: + def start(self, params: List[str], build_dir: str) -> subprocess.Popen: """Runs the Linux UML binary. Must be named 'linux'.""" linux_bin = os.path.join(build_dir, 'linux') params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) diff --git a/tools/testing/kunit/mypy.ini b/tools/testing/kunit/mypy.ini new file mode 100644 index 000000000000..ddd288309efa --- /dev/null +++ b/tools/testing/kunit/mypy.ini @@ -0,0 +1,6 @@ +[mypy] +strict = True + +# E.g. we can't write subprocess.Popen[str] until Python 3.9+. +# But kunit.py tries to support Python 3.7+, so let's disable it. +disable_error_code = type-arg diff --git a/tools/testing/kunit/run_checks.py b/tools/testing/kunit/run_checks.py index 8208c3b3135e..c6d494ea3373 100755 --- a/tools/testing/kunit/run_checks.py +++ b/tools/testing/kunit/run_checks.py @@ -23,7 +23,7 @@ commands: Dict[str, Sequence[str]] = { 'kunit_tool_test.py': ['./kunit_tool_test.py'], 'kunit smoke test': ['./kunit.py', 'run', '--kunitconfig=lib/kunit', '--build_dir=kunit_run_checks'], 'pytype': ['/bin/sh', '-c', 'pytype *.py'], - 'mypy': ['mypy', '--strict', '--exclude', '_test.py$', '--exclude', 'qemu_configs/', '.'], + 'mypy': ['mypy', '--config-file', 'mypy.ini', '--exclude', '_test.py$', '--exclude', 'qemu_configs/', '.'], } # The user might not have mypy or pytype installed, skip them if so. diff --git a/tools/testing/memblock/tests/alloc_nid_api.c b/tools/testing/memblock/tests/alloc_nid_api.c index 49ef68cccd6f..49bb416d34ff 100644 --- a/tools/testing/memblock/tests/alloc_nid_api.c +++ b/tools/testing/memblock/tests/alloc_nid_api.c @@ -2494,6 +2494,35 @@ static int alloc_nid_numa_split_all_reserved_generic_check(void) return 0; } +/* + * A simple test that tries to allocate a memory region through the + * memblock_alloc_node() on a NUMA node with id `nid`. Expected to have the + * correct NUMA node set for the new region. + */ +static int alloc_node_on_correct_nid(void) +{ + int nid_req = 2; + void *allocated_ptr = NULL; +#ifdef CONFIG_NUMA + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; +#endif + phys_addr_t size = SZ_512; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + allocated_ptr = memblock_alloc_node(size, SMP_CACHE_BYTES, nid_req); + + ASSERT_NE(allocated_ptr, NULL); +#ifdef CONFIG_NUMA + ASSERT_EQ(nid_req, req_node->nid); +#endif + + test_pass_pop(); + + return 0; +} + /* Test case wrappers for NUMA tests */ static int alloc_nid_numa_simple_check(void) { @@ -2632,6 +2661,15 @@ static int alloc_nid_numa_split_all_reserved_check(void) return 0; } +static int alloc_node_numa_on_correct_nid(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_node_on_correct_nid); + run_bottom_up(alloc_node_on_correct_nid); + + return 0; +} + int __memblock_alloc_nid_numa_checks(void) { test_print("Running %s NUMA tests...\n", @@ -2652,6 +2690,8 @@ int __memblock_alloc_nid_numa_checks(void) alloc_nid_numa_reserved_full_merge_check(); alloc_nid_numa_split_all_reserved_check(); + alloc_node_numa_on_correct_nid(); + return 0; } diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index e4e2d1650dd5..005043bd9623 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c @@ -3240,11 +3240,6 @@ static int nfit_test_probe(struct platform_device *pdev) return 0; } -static int nfit_test_remove(struct platform_device *pdev) -{ - return 0; -} - static void nfit_test_release(struct device *dev) { struct nfit_test *nfit_test = to_nfit_test(dev); @@ -3259,7 +3254,6 @@ static const struct platform_device_id nfit_test_id[] = { static struct platform_driver nfit_test_driver = { .probe = nfit_test_probe, - .remove = nfit_test_remove, .driver = { .name = KBUILD_MODNAME, }, diff --git a/tools/testing/nvdimm/test/nfit_test.h b/tools/testing/nvdimm/test/nfit_test.h index b5f7a996c4d0..b00583d1eace 100644 --- a/tools/testing/nvdimm/test/nfit_test.h +++ b/tools/testing/nvdimm/test/nfit_test.h @@ -207,7 +207,36 @@ typedef struct nfit_test_resource *(*nfit_test_lookup_fn)(resource_size_t); typedef union acpi_object *(*nfit_test_evaluate_dsm_fn)(acpi_handle handle, const guid_t *guid, u64 rev, u64 func, union acpi_object *argv4); +void __iomem *__wrap_devm_ioremap(struct device *dev, + resource_size_t offset, unsigned long size); +void *__wrap_devm_memremap(struct device *dev, resource_size_t offset, + size_t size, unsigned long flags); +void *__wrap_devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap); +pfn_t __wrap_phys_to_pfn_t(phys_addr_t addr, unsigned long flags); +void *__wrap_memremap(resource_size_t offset, size_t size, + unsigned long flags); +void __wrap_devm_memunmap(struct device *dev, void *addr); +void __iomem *__wrap_ioremap(resource_size_t offset, unsigned long size); +void __iomem *__wrap_ioremap_wc(resource_size_t offset, unsigned long size); void __wrap_iounmap(volatile void __iomem *addr); +void __wrap_memunmap(void *addr); +struct resource *__wrap___request_region(struct resource *parent, + resource_size_t start, resource_size_t n, const char *name, + int flags); +int __wrap_insert_resource(struct resource *parent, struct resource *res); +int __wrap_remove_resource(struct resource *res); +struct resource *__wrap___devm_request_region(struct device *dev, + struct resource *parent, resource_size_t start, + resource_size_t n, const char *name); +void __wrap___release_region(struct resource *parent, resource_size_t start, + resource_size_t n); +void __wrap___devm_release_region(struct device *dev, struct resource *parent, + resource_size_t start, resource_size_t n); +acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path, + struct acpi_object_list *p, struct acpi_buffer *buf); +union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, + u64 rev, u64 func, union acpi_object *argv4); + void nfit_test_setup(nfit_test_lookup_fn lookup, nfit_test_evaluate_dsm_fn evaluate); void nfit_test_teardown(void); diff --git a/tools/testing/radix-tree/Makefile b/tools/testing/radix-tree/Makefile index caf32a9b9608..7527f738b4a1 100644 --- a/tools/testing/radix-tree/Makefile +++ b/tools/testing/radix-tree/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 -CFLAGS += -I. -I../../include -g -Og -Wall -D_LGPL_SOURCE -fsanitize=address \ - -fsanitize=undefined +CFLAGS += -I. -I../../include -I../../../lib -g -Og -Wall \ + -D_LGPL_SOURCE -fsanitize=address -fsanitize=undefined LDFLAGS += -fsanitize=address -fsanitize=undefined LDLIBS+= -lpthread -lurcu TARGETS = main idr-test multiorder xarray maple @@ -49,6 +49,7 @@ $(OFILES): Makefile *.h */*.h generated/map-shift.h generated/bit-length.h \ ../../../include/linux/xarray.h \ ../../../include/linux/maple_tree.h \ ../../../include/linux/radix-tree.h \ + ../../../lib/radix-tree.h \ ../../../include/linux/idr.h radix-tree.c: ../../../lib/radix-tree.c diff --git a/tools/testing/radix-tree/linux/init.h b/tools/testing/radix-tree/linux/init.h index 1bb0afc21309..81563c3dfce7 100644 --- a/tools/testing/radix-tree/linux/init.h +++ b/tools/testing/radix-tree/linux/init.h @@ -1 +1,2 @@ #define __init +#define __exit diff --git a/tools/testing/radix-tree/maple.c b/tools/testing/radix-tree/maple.c index 9286d3baa12d..03539d86cdf0 100644 --- a/tools/testing/radix-tree/maple.c +++ b/tools/testing/radix-tree/maple.c @@ -14,6 +14,7 @@ #include "test.h" #include <stdlib.h> #include <time.h> +#include "linux/init.h" #define module_init(x) #define module_exit(x) @@ -22,7 +23,6 @@ #define dump_stack() assert(0) #include "../../../lib/maple_tree.c" -#undef CONFIG_DEBUG_MAPLE_TREE #include "../../../lib/test_maple_tree.c" #define RCU_RANGE_COUNT 1000 @@ -81,7 +81,7 @@ static void check_mas_alloc_node_count(struct ma_state *mas) * check_new_node() - Check the creation of new nodes and error path * verification. */ -static noinline void check_new_node(struct maple_tree *mt) +static noinline void __init check_new_node(struct maple_tree *mt) { struct maple_node *mn, *mn2, *mn3; @@ -455,7 +455,7 @@ static noinline void check_new_node(struct maple_tree *mt) /* * Check erasing including RCU. */ -static noinline void check_erase(struct maple_tree *mt, unsigned long index, +static noinline void __init check_erase(struct maple_tree *mt, unsigned long index, void *ptr) { MT_BUG_ON(mt, mtree_test_erase(mt, index) != ptr); @@ -465,24 +465,24 @@ static noinline void check_erase(struct maple_tree *mt, unsigned long index, #define erase_check_insert(mt, i) check_insert(mt, set[i], entry[i%2]) #define erase_check_erase(mt, i) check_erase(mt, set[i], entry[i%2]) -static noinline void check_erase_testset(struct maple_tree *mt) +static noinline void __init check_erase_testset(struct maple_tree *mt) { - unsigned long set[] = { 5015, 5014, 5017, 25, 1000, - 1001, 1002, 1003, 1005, 0, - 6003, 6002, 6008, 6012, 6015, - 7003, 7002, 7008, 7012, 7015, - 8003, 8002, 8008, 8012, 8015, - 9003, 9002, 9008, 9012, 9015, - 10003, 10002, 10008, 10012, 10015, - 11003, 11002, 11008, 11012, 11015, - 12003, 12002, 12008, 12012, 12015, - 13003, 13002, 13008, 13012, 13015, - 14003, 14002, 14008, 14012, 14015, - 15003, 15002, 15008, 15012, 15015, - }; - - - void *ptr = &set; + static const unsigned long set[] = { 5015, 5014, 5017, 25, 1000, + 1001, 1002, 1003, 1005, 0, + 6003, 6002, 6008, 6012, 6015, + 7003, 7002, 7008, 7012, 7015, + 8003, 8002, 8008, 8012, 8015, + 9003, 9002, 9008, 9012, 9015, + 10003, 10002, 10008, 10012, 10015, + 11003, 11002, 11008, 11012, 11015, + 12003, 12002, 12008, 12012, 12015, + 13003, 13002, 13008, 13012, 13015, + 14003, 14002, 14008, 14012, 14015, + 15003, 15002, 15008, 15012, 15015, + }; + + + void *ptr = &check_erase_testset; void *entry[2] = { ptr, mt }; void *root_node; @@ -739,7 +739,7 @@ static noinline void check_erase_testset(struct maple_tree *mt) int mas_ce2_over_count(struct ma_state *mas_start, struct ma_state *mas_end, void *s_entry, unsigned long s_min, void *e_entry, unsigned long e_max, - unsigned long *set, int i, bool null_entry) + const unsigned long *set, int i, bool null_entry) { int count = 0, span = 0; unsigned long retry = 0; @@ -969,8 +969,8 @@ retry: } #if defined(CONFIG_64BIT) -static noinline void check_erase2_testset(struct maple_tree *mt, - unsigned long *set, unsigned long size) +static noinline void __init check_erase2_testset(struct maple_tree *mt, + const unsigned long *set, unsigned long size) { int entry_count = 0; int check = 0; @@ -1054,7 +1054,7 @@ static noinline void check_erase2_testset(struct maple_tree *mt, if (entry_count) MT_BUG_ON(mt, !mt_height(mt)); #if check_erase2_debug > 1 - mt_dump(mt); + mt_dump(mt, mt_dump_hex); #endif #if check_erase2_debug pr_err("Done\n"); @@ -1085,7 +1085,7 @@ static noinline void check_erase2_testset(struct maple_tree *mt, mas_for_each(&mas, foo, ULONG_MAX) { if (xa_is_zero(foo)) { if (addr == mas.index) { - mt_dump(mas.tree); + mt_dump(mas.tree, mt_dump_hex); pr_err("retry failed %lu - %lu\n", mas.index, mas.last); MT_BUG_ON(mt, 1); @@ -1114,11 +1114,11 @@ static noinline void check_erase2_testset(struct maple_tree *mt, /* These tests were pulled from KVM tree modifications which failed. */ -static noinline void check_erase2_sets(struct maple_tree *mt) +static noinline void __init check_erase2_sets(struct maple_tree *mt) { void *entry; unsigned long start = 0; - unsigned long set[] = { + static const unsigned long set[] = { STORE, 140737488347136, 140737488351231, STORE, 140721266458624, 140737488351231, ERASE, 140721266458624, 140737488351231, @@ -1136,7 +1136,7 @@ ERASE, 140253902692352, 140253902864383, STORE, 140253902692352, 140253902696447, STORE, 140253902696448, 140253902864383, }; - unsigned long set2[] = { + static const unsigned long set2[] = { STORE, 140737488347136, 140737488351231, STORE, 140735933583360, 140737488351231, ERASE, 140735933583360, 140737488351231, @@ -1160,7 +1160,7 @@ STORE, 140277094813696, 140277094821887, STORE, 140277094821888, 140277094825983, STORE, 140735933906944, 140735933911039, }; - unsigned long set3[] = { + static const unsigned long set3[] = { STORE, 140737488347136, 140737488351231, STORE, 140735790264320, 140737488351231, ERASE, 140735790264320, 140737488351231, @@ -1203,7 +1203,7 @@ STORE, 47135835840512, 47135835885567, STORE, 47135835885568, 47135835893759, }; - unsigned long set4[] = { + static const unsigned long set4[] = { STORE, 140737488347136, 140737488351231, STORE, 140728251703296, 140737488351231, ERASE, 140728251703296, 140737488351231, @@ -1224,7 +1224,7 @@ ERASE, 47646523277312, 47646523445247, STORE, 47646523277312, 47646523400191, }; - unsigned long set5[] = { + static const unsigned long set5[] = { STORE, 140737488347136, 140737488351231, STORE, 140726874062848, 140737488351231, ERASE, 140726874062848, 140737488351231, @@ -1357,7 +1357,7 @@ STORE, 47884791619584, 47884791623679, STORE, 47884791623680, 47884791627775, }; - unsigned long set6[] = { + static const unsigned long set6[] = { STORE, 140737488347136, 140737488351231, STORE, 140722999021568, 140737488351231, ERASE, 140722999021568, 140737488351231, @@ -1489,7 +1489,7 @@ ERASE, 47430432014336, 47430432022527, STORE, 47430432014336, 47430432018431, STORE, 47430432018432, 47430432022527, }; - unsigned long set7[] = { + static const unsigned long set7[] = { STORE, 140737488347136, 140737488351231, STORE, 140729808330752, 140737488351231, ERASE, 140729808330752, 140737488351231, @@ -1621,7 +1621,7 @@ ERASE, 47439987130368, 47439987138559, STORE, 47439987130368, 47439987134463, STORE, 47439987134464, 47439987138559, }; - unsigned long set8[] = { + static const unsigned long set8[] = { STORE, 140737488347136, 140737488351231, STORE, 140722482974720, 140737488351231, ERASE, 140722482974720, 140737488351231, @@ -1754,7 +1754,7 @@ STORE, 47708488638464, 47708488642559, STORE, 47708488642560, 47708488646655, }; - unsigned long set9[] = { + static const unsigned long set9[] = { STORE, 140737488347136, 140737488351231, STORE, 140736427839488, 140737488351231, ERASE, 140736427839488, 140736427839488, @@ -5620,7 +5620,7 @@ ERASE, 47906195480576, 47906195480576, STORE, 94641242615808, 94641242750975, }; - unsigned long set10[] = { + static const unsigned long set10[] = { STORE, 140737488347136, 140737488351231, STORE, 140736427839488, 140737488351231, ERASE, 140736427839488, 140736427839488, @@ -9484,7 +9484,7 @@ STORE, 139726599680000, 139726599684095, ERASE, 47906195480576, 47906195480576, STORE, 94641242615808, 94641242750975, }; - unsigned long set11[] = { + static const unsigned long set11[] = { STORE, 140737488347136, 140737488351231, STORE, 140732658499584, 140737488351231, ERASE, 140732658499584, 140732658499584, @@ -9510,7 +9510,7 @@ STORE, 140732658565120, 140732658569215, STORE, 140732658552832, 140732658565119, }; - unsigned long set12[] = { /* contains 12 values. */ + static const unsigned long set12[] = { /* contains 12 values. */ STORE, 140737488347136, 140737488351231, STORE, 140732658499584, 140737488351231, ERASE, 140732658499584, 140732658499584, @@ -9537,7 +9537,7 @@ STORE, 140732658552832, 140732658565119, STORE, 140014592741375, 140014592741375, /* contrived */ STORE, 140014592733184, 140014592741376, /* creates first entry retry. */ }; - unsigned long set13[] = { + static const unsigned long set13[] = { STORE, 140373516247040, 140373516251135,/*: ffffa2e7b0e10d80 */ STORE, 140373516251136, 140373516255231,/*: ffffa2e7b1195d80 */ STORE, 140373516255232, 140373516443647,/*: ffffa2e7b0e109c0 */ @@ -9550,7 +9550,7 @@ STORE, 140373518684160, 140373518688254,/*: ffffa2e7b05fec00 */ STORE, 140373518688256, 140373518692351,/*: ffffa2e7bfbdcd80 */ STORE, 140373518692352, 140373518696447,/*: ffffa2e7b0749e40 */ }; - unsigned long set14[] = { + static const unsigned long set14[] = { STORE, 140737488347136, 140737488351231, STORE, 140731667996672, 140737488351231, SNULL, 140731668000767, 140737488351231, @@ -9834,7 +9834,7 @@ SNULL, 139826136543232, 139826136809471, STORE, 139826136809472, 139826136842239, STORE, 139826136543232, 139826136809471, }; - unsigned long set15[] = { + static const unsigned long set15[] = { STORE, 140737488347136, 140737488351231, STORE, 140722061451264, 140737488351231, SNULL, 140722061455359, 140737488351231, @@ -10119,7 +10119,7 @@ STORE, 139906808958976, 139906808991743, STORE, 139906808692736, 139906808958975, }; - unsigned long set16[] = { + static const unsigned long set16[] = { STORE, 94174808662016, 94174809321471, STORE, 94174811414528, 94174811426815, STORE, 94174811426816, 94174811430911, @@ -10330,7 +10330,7 @@ STORE, 139921865613312, 139921865617407, STORE, 139921865547776, 139921865564159, }; - unsigned long set17[] = { + static const unsigned long set17[] = { STORE, 94397057224704, 94397057646591, STORE, 94397057650688, 94397057691647, STORE, 94397057691648, 94397057695743, @@ -10392,7 +10392,7 @@ STORE, 140720477511680, 140720477646847, STORE, 140720478302208, 140720478314495, STORE, 140720478314496, 140720478318591, }; - unsigned long set18[] = { + static const unsigned long set18[] = { STORE, 140737488347136, 140737488351231, STORE, 140724953673728, 140737488351231, SNULL, 140724953677823, 140737488351231, @@ -10425,7 +10425,7 @@ STORE, 140222970597376, 140222970605567, ERASE, 140222970597376, 140222970605567, STORE, 140222970597376, 140222970605567, }; - unsigned long set19[] = { + static const unsigned long set19[] = { STORE, 140737488347136, 140737488351231, STORE, 140725182459904, 140737488351231, SNULL, 140725182463999, 140737488351231, @@ -10694,7 +10694,7 @@ STORE, 140656836775936, 140656836780031, STORE, 140656787476480, 140656791920639, ERASE, 140656774639616, 140656779083775, }; - unsigned long set20[] = { + static const unsigned long set20[] = { STORE, 140737488347136, 140737488351231, STORE, 140735952392192, 140737488351231, SNULL, 140735952396287, 140737488351231, @@ -10850,7 +10850,7 @@ STORE, 140590386819072, 140590386823167, STORE, 140590386823168, 140590386827263, SNULL, 140590376591359, 140590376595455, }; - unsigned long set21[] = { + static const unsigned long set21[] = { STORE, 93874710941696, 93874711363583, STORE, 93874711367680, 93874711408639, STORE, 93874711408640, 93874711412735, @@ -10920,7 +10920,7 @@ ERASE, 140708393312256, 140708393316351, ERASE, 140708393308160, 140708393312255, ERASE, 140708393291776, 140708393308159, }; - unsigned long set22[] = { + static const unsigned long set22[] = { STORE, 93951397134336, 93951397183487, STORE, 93951397183488, 93951397728255, STORE, 93951397728256, 93951397826559, @@ -11047,7 +11047,7 @@ STORE, 140551361253376, 140551361519615, ERASE, 140551361253376, 140551361519615, }; - unsigned long set23[] = { + static const unsigned long set23[] = { STORE, 94014447943680, 94014448156671, STORE, 94014450253824, 94014450257919, STORE, 94014450257920, 94014450266111, @@ -14371,7 +14371,7 @@ SNULL, 140175956627455, 140175985139711, STORE, 140175927242752, 140175956627455, STORE, 140175956627456, 140175985139711, }; - unsigned long set24[] = { + static const unsigned long set24[] = { STORE, 140737488347136, 140737488351231, STORE, 140735281639424, 140737488351231, SNULL, 140735281643519, 140737488351231, @@ -15533,7 +15533,7 @@ ERASE, 139635393024000, 139635401412607, ERASE, 139635384627200, 139635384631295, ERASE, 139635384631296, 139635393019903, }; - unsigned long set25[] = { + static const unsigned long set25[] = { STORE, 140737488347136, 140737488351231, STORE, 140737488343040, 140737488351231, STORE, 140722547441664, 140737488351231, @@ -22321,7 +22321,7 @@ STORE, 140249652703232, 140249682087935, STORE, 140249682087936, 140249710600191, }; - unsigned long set26[] = { + static const unsigned long set26[] = { STORE, 140737488347136, 140737488351231, STORE, 140729464770560, 140737488351231, SNULL, 140729464774655, 140737488351231, @@ -22345,7 +22345,7 @@ ERASE, 140109040951296, 140109040959487, STORE, 140109040955392, 140109040959487, ERASE, 140109040955392, 140109040959487, }; - unsigned long set27[] = { + static const unsigned long set27[] = { STORE, 140737488347136, 140737488351231, STORE, 140726128070656, 140737488351231, SNULL, 140726128074751, 140737488351231, @@ -22741,7 +22741,7 @@ STORE, 140415509696512, 140415535910911, ERASE, 140415537422336, 140415562588159, STORE, 140415482433536, 140415509696511, }; - unsigned long set28[] = { + static const unsigned long set28[] = { STORE, 140737488347136, 140737488351231, STORE, 140722475622400, 140737488351231, SNULL, 140722475626495, 140737488351231, @@ -22809,7 +22809,7 @@ STORE, 139918413348864, 139918413352959, ERASE, 139918413316096, 139918413344767, STORE, 93865848528896, 93865848664063, }; - unsigned long set29[] = { + static const unsigned long set29[] = { STORE, 140737488347136, 140737488351231, STORE, 140734467944448, 140737488351231, SNULL, 140734467948543, 140737488351231, @@ -23684,7 +23684,7 @@ ERASE, 140143079972864, 140143088361471, ERASE, 140143205793792, 140143205797887, ERASE, 140143205797888, 140143214186495, }; - unsigned long set30[] = { + static const unsigned long set30[] = { STORE, 140737488347136, 140737488351231, STORE, 140733436743680, 140737488351231, SNULL, 140733436747775, 140737488351231, @@ -24566,7 +24566,7 @@ ERASE, 140165225893888, 140165225897983, ERASE, 140165225897984, 140165234286591, ERASE, 140165058105344, 140165058109439, }; - unsigned long set31[] = { + static const unsigned long set31[] = { STORE, 140737488347136, 140737488351231, STORE, 140730890784768, 140737488351231, SNULL, 140730890788863, 140737488351231, @@ -25379,7 +25379,7 @@ ERASE, 140623906590720, 140623914979327, ERASE, 140622950277120, 140622950281215, ERASE, 140622950281216, 140622958669823, }; - unsigned long set32[] = { + static const unsigned long set32[] = { STORE, 140737488347136, 140737488351231, STORE, 140731244212224, 140737488351231, SNULL, 140731244216319, 140737488351231, @@ -26175,7 +26175,7 @@ ERASE, 140400417288192, 140400425676799, ERASE, 140400283066368, 140400283070463, ERASE, 140400283070464, 140400291459071, }; - unsigned long set33[] = { + static const unsigned long set33[] = { STORE, 140737488347136, 140737488351231, STORE, 140734562918400, 140737488351231, SNULL, 140734562922495, 140737488351231, @@ -26317,7 +26317,7 @@ STORE, 140582961786880, 140583003750399, ERASE, 140582961786880, 140583003750399, }; - unsigned long set34[] = { + static const unsigned long set34[] = { STORE, 140737488347136, 140737488351231, STORE, 140731327180800, 140737488351231, SNULL, 140731327184895, 140737488351231, @@ -27198,7 +27198,7 @@ ERASE, 140012522094592, 140012530483199, ERASE, 140012033142784, 140012033146879, ERASE, 140012033146880, 140012041535487, }; - unsigned long set35[] = { + static const unsigned long set35[] = { STORE, 140737488347136, 140737488351231, STORE, 140730536939520, 140737488351231, SNULL, 140730536943615, 140737488351231, @@ -27955,7 +27955,7 @@ ERASE, 140474471936000, 140474480324607, ERASE, 140474396430336, 140474396434431, ERASE, 140474396434432, 140474404823039, }; - unsigned long set36[] = { + static const unsigned long set36[] = { STORE, 140737488347136, 140737488351231, STORE, 140723893125120, 140737488351231, SNULL, 140723893129215, 140737488351231, @@ -28816,7 +28816,7 @@ ERASE, 140121890357248, 140121898745855, ERASE, 140121269587968, 140121269592063, ERASE, 140121269592064, 140121277980671, }; - unsigned long set37[] = { + static const unsigned long set37[] = { STORE, 140737488347136, 140737488351231, STORE, 140722404016128, 140737488351231, SNULL, 140722404020223, 140737488351231, @@ -28942,7 +28942,7 @@ STORE, 139759821246464, 139759888355327, ERASE, 139759821246464, 139759888355327, ERASE, 139759888355328, 139759955464191, }; - unsigned long set38[] = { + static const unsigned long set38[] = { STORE, 140737488347136, 140737488351231, STORE, 140730666221568, 140737488351231, SNULL, 140730666225663, 140737488351231, @@ -29752,7 +29752,7 @@ ERASE, 140613504712704, 140613504716799, ERASE, 140613504716800, 140613513105407, }; - unsigned long set39[] = { + static const unsigned long set39[] = { STORE, 140737488347136, 140737488351231, STORE, 140736271417344, 140737488351231, SNULL, 140736271421439, 140737488351231, @@ -30124,7 +30124,7 @@ STORE, 140325364428800, 140325372821503, STORE, 140325356036096, 140325364428799, SNULL, 140325364432895, 140325372821503, }; - unsigned long set40[] = { + static const unsigned long set40[] = { STORE, 140737488347136, 140737488351231, STORE, 140734309167104, 140737488351231, SNULL, 140734309171199, 140737488351231, @@ -30875,7 +30875,7 @@ ERASE, 140320289300480, 140320289304575, ERASE, 140320289304576, 140320297693183, ERASE, 140320163409920, 140320163414015, }; - unsigned long set41[] = { + static const unsigned long set41[] = { STORE, 140737488347136, 140737488351231, STORE, 140728157171712, 140737488351231, SNULL, 140728157175807, 140737488351231, @@ -31185,7 +31185,7 @@ STORE, 94376135090176, 94376135094271, STORE, 94376135094272, 94376135098367, SNULL, 94376135094272, 94377208836095, }; - unsigned long set42[] = { + static const unsigned long set42[] = { STORE, 314572800, 1388314623, STORE, 1462157312, 1462169599, STORE, 1462169600, 1462185983, @@ -33862,7 +33862,7 @@ SNULL, 3798999040, 3799101439, */ }; - unsigned long set43[] = { + static const unsigned long set43[] = { STORE, 140737488347136, 140737488351231, STORE, 140734187720704, 140737488351231, SNULL, 140734187724800, 140737488351231, @@ -34513,7 +34513,7 @@ static void *rcu_reader_rev(void *ptr) if (mas.index != r_start) { alt = xa_mk_value(index + i * 2 + 1 + RCU_RANGE_COUNT); - mt_dump(test->mt); + mt_dump(test->mt, mt_dump_dec); printk("Error: %lu-%lu %p != %lu-%lu %p %p line %d i %d\n", mas.index, mas.last, entry, r_start, r_end, expected, alt, @@ -34996,7 +34996,7 @@ void run_check_rcu_slowread(struct maple_tree *mt, struct rcu_test_struct *vals) MT_BUG_ON(mt, !vals->seen_entry3); MT_BUG_ON(mt, !vals->seen_both); } -static noinline void check_rcu_simulated(struct maple_tree *mt) +static noinline void __init check_rcu_simulated(struct maple_tree *mt) { unsigned long i, nr_entries = 1000; unsigned long target = 4320; @@ -35157,7 +35157,7 @@ static noinline void check_rcu_simulated(struct maple_tree *mt) rcu_unregister_thread(); } -static noinline void check_rcu_threaded(struct maple_tree *mt) +static noinline void __init check_rcu_threaded(struct maple_tree *mt) { unsigned long i, nr_entries = 1000; struct rcu_test_struct vals; @@ -35259,6 +35259,7 @@ static void mas_dfs_preorder(struct ma_state *mas) struct maple_enode *prev; unsigned char end, slot = 0; + unsigned long *pivots; if (mas->node == MAS_START) { mas_start(mas); @@ -35291,6 +35292,9 @@ walk_up: mas_ascend(mas); goto walk_up; } + pivots = ma_pivots(mte_to_node(prev), mte_node_type(prev)); + mas->max = mas_safe_pivot(mas, pivots, slot, mte_node_type(prev)); + mas->min = mas_safe_min(mas, pivots, slot); return; done: @@ -35366,7 +35370,7 @@ static void check_dfs_preorder(struct maple_tree *mt) /* End of depth first search tests */ /* Preallocation testing */ -static noinline void check_prealloc(struct maple_tree *mt) +static noinline void __init check_prealloc(struct maple_tree *mt) { unsigned long i, max = 100; unsigned long allocated; @@ -35494,7 +35498,7 @@ static noinline void check_prealloc(struct maple_tree *mt) /* End of preallocation testing */ /* Spanning writes, writes that span nodes and layers of the tree */ -static noinline void check_spanning_write(struct maple_tree *mt) +static noinline void __init check_spanning_write(struct maple_tree *mt) { unsigned long i, max = 5000; MA_STATE(mas, mt, 1200, 2380); @@ -35662,7 +35666,7 @@ static noinline void check_spanning_write(struct maple_tree *mt) /* End of spanning write testing */ /* Writes to a NULL area that are adjacent to other NULLs */ -static noinline void check_null_expand(struct maple_tree *mt) +static noinline void __init check_null_expand(struct maple_tree *mt) { unsigned long i, max = 100; unsigned char data_end; @@ -35723,7 +35727,7 @@ static noinline void check_null_expand(struct maple_tree *mt) /* End of NULL area expansions */ /* Checking for no memory is best done outside the kernel */ -static noinline void check_nomem(struct maple_tree *mt) +static noinline void __init check_nomem(struct maple_tree *mt) { MA_STATE(ms, mt, 1, 1); @@ -35758,7 +35762,7 @@ static noinline void check_nomem(struct maple_tree *mt) mtree_destroy(mt); } -static noinline void check_locky(struct maple_tree *mt) +static noinline void __init check_locky(struct maple_tree *mt) { MA_STATE(ms, mt, 2, 2); MA_STATE(reader, mt, 2, 2); @@ -35780,10 +35784,10 @@ void farmer_tests(void) struct maple_node *node; DEFINE_MTREE(tree); - mt_dump(&tree); + mt_dump(&tree, mt_dump_dec); tree.ma_root = xa_mk_value(0); - mt_dump(&tree); + mt_dump(&tree, mt_dump_dec); node = mt_alloc_one(GFP_KERNEL); node->parent = (void *)((unsigned long)(&tree) | 1); @@ -35793,7 +35797,7 @@ void farmer_tests(void) node->mr64.pivot[1] = 1; node->mr64.pivot[2] = 0; tree.ma_root = mt_mk_node(node, maple_leaf_64); - mt_dump(&tree); + mt_dump(&tree, mt_dump_dec); node->parent = ma_parent_ptr(node); ma_free_rcu(node); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 90a62cf75008..666b56f22a41 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -4,6 +4,7 @@ TARGETS += amd-pstate TARGETS += arm64 TARGETS += bpf TARGETS += breakpoints +TARGETS += cachestat TARGETS += capabilities TARGETS += cgroup TARGETS += clone3 @@ -86,6 +87,7 @@ TARGETS += timers endif TARGETS += tmpfs TARGETS += tpm2 +TARGETS += tty TARGETS += user TARGETS += vDSO TARGETS += mm @@ -144,10 +146,12 @@ ifneq ($(KBUILD_OUTPUT),) abs_objtree := $(realpath $(abs_objtree)) BUILD := $(abs_objtree)/kselftest KHDR_INCLUDES := -isystem ${abs_objtree}/usr/include + KHDR_DIR := ${abs_objtree}/usr/include else BUILD := $(CURDIR) abs_srctree := $(shell cd $(top_srcdir) && pwd) KHDR_INCLUDES := -isystem ${abs_srctree}/usr/include + KHDR_DIR := ${abs_srctree}/usr/include DEFAULT_INSTALL_HDR_PATH := 1 endif @@ -161,7 +165,7 @@ export KHDR_INCLUDES # all isn't the first target in the file. .DEFAULT_GOAL := all -all: +all: kernel_header_files @ret=1; \ for TARGET in $(TARGETS); do \ BUILD_TARGET=$$BUILD/$$TARGET; \ @@ -172,6 +176,23 @@ all: ret=$$((ret * $$?)); \ done; exit $$ret; +kernel_header_files: + @ls $(KHDR_DIR)/linux/*.h >/dev/null 2>/dev/null; \ + if [ $$? -ne 0 ]; then \ + RED='\033[1;31m'; \ + NOCOLOR='\033[0m'; \ + echo; \ + echo -e "$${RED}error$${NOCOLOR}: missing kernel header files."; \ + echo "Please run this and try again:"; \ + echo; \ + echo " cd $(top_srcdir)"; \ + echo " make headers"; \ + echo; \ + exit 1; \ + fi + +.PHONY: kernel_header_files + run_tests: all @for TARGET in $(TARGETS); do \ BUILD_TARGET=$$BUILD/$$TARGET; \ diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 901949db80ad..5af9ba8a4645 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -12,7 +12,7 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := mixer-test pcm-test +TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver TEST_GEN_PROGS_EXTENDED := libatest.so diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index 3e390fe67eb9..b7eef32addb4 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -381,7 +381,7 @@ __format: goto __close; } if (rrate != rate) { - snprintf(msg, sizeof(msg), "rate mismatch %ld != %ld", rate, rrate); + snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate); goto __close; } rperiod_size = period_size; @@ -447,24 +447,24 @@ __format: frames = snd_pcm_writei(handle, samples, rate); if (frames < 0) { snprintf(msg, sizeof(msg), - "Write failed: expected %d, wrote %li", rate, frames); + "Write failed: expected %ld, wrote %li", rate, frames); goto __close; } if (frames < rate) { snprintf(msg, sizeof(msg), - "expected %d, wrote %li", rate, frames); + "expected %ld, wrote %li", rate, frames); goto __close; } } else { frames = snd_pcm_readi(handle, samples, rate); if (frames < 0) { snprintf(msg, sizeof(msg), - "expected %d, wrote %li", rate, frames); + "expected %ld, wrote %li", rate, frames); goto __close; } if (frames < rate) { snprintf(msg, sizeof(msg), - "expected %d, wrote %li", rate, frames); + "expected %ld, wrote %li", rate, frames); goto __close; } } diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c new file mode 100644 index 000000000000..71931b240a83 --- /dev/null +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This is the test which covers PCM middle layer data transferring using + * the virtual pcm test driver (snd-pcmtest). + * + * Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com> + */ +#include <string.h> +#include <alsa/asoundlib.h> +#include "../kselftest_harness.h" + +#define CH_NUM 4 + +struct pattern_buf { + char buf[1024]; + int len; +}; + +struct pattern_buf patterns[CH_NUM]; + +struct pcmtest_test_params { + unsigned long buffer_size; + unsigned long period_size; + unsigned long channels; + unsigned int rate; + snd_pcm_access_t access; + size_t sec_buf_len; + size_t sample_size; + int time; + snd_pcm_format_t format; +}; + +static int read_patterns(void) +{ + FILE *fp, *fpl; + int i; + char pf[64]; + char plf[64]; + + for (i = 0; i < CH_NUM; i++) { + sprintf(plf, "/sys/kernel/debug/pcmtest/fill_pattern%d_len", i); + fpl = fopen(plf, "r"); + if (!fpl) + return -1; + fscanf(fpl, "%u", &patterns[i].len); + fclose(fpl); + + sprintf(pf, "/sys/kernel/debug/pcmtest/fill_pattern%d", i); + fp = fopen(pf, "r"); + if (!fp) { + fclose(fpl); + return -1; + } + fread(patterns[i].buf, 1, patterns[i].len, fp); + fclose(fp); + } + + return 0; +} + +static int get_test_results(char *debug_name) +{ + int result; + FILE *f; + char fname[128]; + + sprintf(fname, "/sys/kernel/debug/pcmtest/%s", debug_name); + + f = fopen(fname, "r"); + if (!f) { + printf("Failed to open file\n"); + return -1; + } + fscanf(f, "%d", &result); + fclose(f); + + return result; +} + +static size_t get_sec_buf_len(unsigned int rate, unsigned long channels, snd_pcm_format_t format) +{ + return rate * channels * snd_pcm_format_physical_width(format) / 8; +} + +static int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams, + snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params, + int card, snd_pcm_stream_t stream) +{ + char pcm_name[32]; + int err; + + sprintf(pcm_name, "hw:%d,0,0", card); + err = snd_pcm_open(handle, pcm_name, stream, 0); + if (err < 0) + return err; + snd_pcm_hw_params_any(*handle, hwparams); + snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); + snd_pcm_hw_params_set_access(*handle, hwparams, params->access); + snd_pcm_hw_params_set_format(*handle, hwparams, params->format); + snd_pcm_hw_params_set_channels(*handle, hwparams, params->channels); + snd_pcm_hw_params_set_rate_near(*handle, hwparams, ¶ms->rate, 0); + snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); + snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); + snd_pcm_hw_params(*handle, hwparams); + snd_pcm_sw_params_current(*handle, swparams); + + snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); + snd_pcm_sw_params_set_avail_min(*handle, swparams, params->period_size); + snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); + snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); + snd_pcm_sw_params(*handle, swparams); + snd_pcm_hw_params(*handle, hwparams); + + return 0; +} + +FIXTURE(pcmtest) { + int card; + snd_pcm_sw_params_t *swparams; + snd_pcm_hw_params_t *hwparams; + struct pcmtest_test_params params; +}; + +FIXTURE_TEARDOWN(pcmtest) { +} + +FIXTURE_SETUP(pcmtest) { + char *card_name; + int err; + + if (geteuid()) + SKIP(exit(-1), "This test needs root to run!"); + + err = read_patterns(); + if (err) + SKIP(exit(-1), "Can't read patterns. Probably, module isn't loaded"); + + card_name = malloc(127); + ASSERT_NE(card_name, NULL); + self->params.buffer_size = 16384; + self->params.period_size = 4096; + self->params.channels = CH_NUM; + self->params.rate = 8000; + self->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; + self->params.format = SND_PCM_FORMAT_S16_LE; + self->card = -1; + self->params.sample_size = snd_pcm_format_physical_width(self->params.format) / 8; + + self->params.sec_buf_len = get_sec_buf_len(self->params.rate, self->params.channels, + self->params.format); + self->params.time = 4; + + while (snd_card_next(&self->card) >= 0) { + if (self->card == -1) + break; + snd_card_get_name(self->card, &card_name); + if (!strcmp(card_name, "PCM-Test")) + break; + } + free(card_name); + ASSERT_NE(self->card, -1); +} + +/* + * Here we are trying to send the looped monotonically increasing sequence of bytes to the driver. + * If our data isn't corrupted, the driver will set the content of 'pc_test' debugfs file to '1' + */ +TEST_F(pcmtest, playback) { + snd_pcm_t *handle; + unsigned char *it; + size_t write_res; + int test_results; + int i, cur_ch, pos_in_ch; + void *samples; + struct pcmtest_test_params *params = &self->params; + + samples = calloc(self->params.sec_buf_len * self->params.time, 1); + ASSERT_NE(samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, + self->card, SND_PCM_STREAM_PLAYBACK), 0); + snd_pcm_format_set_silence(params->format, samples, + params->rate * params->channels * params->time); + it = samples; + for (i = 0; i < self->params.sec_buf_len * params->time; i++) { + cur_ch = (i / params->sample_size) % CH_NUM; + pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size + + (i % params->sample_size); + it[i] = patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]; + } + write_res = snd_pcm_writei(handle, samples, params->rate * params->time); + ASSERT_GE(write_res, 0); + + snd_pcm_close(handle); + free(samples); + test_results = get_test_results("pc_test"); + ASSERT_EQ(test_results, 1); +} + +/* + * Here we test that the virtual alsa driver returns looped and monotonically increasing sequence + * of bytes. In the interleaved mode the buffer will contain samples in the following order: + * C0, C1, C2, C3, C0, C1, ... + */ +TEST_F(pcmtest, capture) { + snd_pcm_t *handle; + unsigned char *it; + size_t read_res; + int i, cur_ch, pos_in_ch; + void *samples; + struct pcmtest_test_params *params = &self->params; + + samples = calloc(self->params.sec_buf_len * self->params.time, 1); + ASSERT_NE(samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, + params, self->card, SND_PCM_STREAM_CAPTURE), 0); + snd_pcm_format_set_silence(params->format, samples, + params->rate * params->channels * params->time); + read_res = snd_pcm_readi(handle, samples, params->rate * params->time); + ASSERT_GE(read_res, 0); + snd_pcm_close(handle); + it = (unsigned char *)samples; + for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) { + cur_ch = (i / params->sample_size) % CH_NUM; + pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size + + (i % params->sample_size); + ASSERT_EQ(it[i], patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]); + } + free(samples); +} + +// Test capture in the non-interleaved access mode. The are buffers for each recorded channel +TEST_F(pcmtest, ni_capture) { + snd_pcm_t *handle; + struct pcmtest_test_params params = self->params; + char **chan_samples; + size_t i, j, read_res; + + chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); + ASSERT_NE(chan_samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, + ¶ms, self->card, SND_PCM_STREAM_CAPTURE), 0); + + for (i = 0; i < CH_NUM; i++) + chan_samples[i] = calloc(params.sec_buf_len * params.time, 1); + + for (i = 0; i < 1; i++) { + read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time); + ASSERT_GE(read_res, 0); + } + snd_pcm_close(handle); + + for (i = 0; i < CH_NUM; i++) { + for (j = 0; j < params.rate * params.time; j++) + ASSERT_EQ(chan_samples[i][j], patterns[i].buf[j % patterns[i].len]); + free(chan_samples[i]); + } + free(chan_samples); +} + +TEST_F(pcmtest, ni_playback) { + snd_pcm_t *handle; + struct pcmtest_test_params params = self->params; + char **chan_samples; + size_t i, j, read_res; + int test_res; + + chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); + ASSERT_NE(chan_samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, + ¶ms, self->card, SND_PCM_STREAM_PLAYBACK), 0); + + for (i = 0; i < CH_NUM; i++) { + chan_samples[i] = calloc(params.sec_buf_len * params.time, 1); + for (j = 0; j < params.sec_buf_len * params.time; j++) + chan_samples[i][j] = patterns[i].buf[j % patterns[i].len]; + } + + for (i = 0; i < 1; i++) { + read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time); + ASSERT_GE(read_res, 0); + } + + snd_pcm_close(handle); + test_res = get_test_results("pc_test"); + ASSERT_EQ(test_res, 1); + + for (i = 0; i < CH_NUM; i++) + free(chan_samples[i]); + free(chan_samples); +} + +/* + * Here we are testing the custom ioctl definition inside the virtual driver. If it triggers + * successfully, the driver sets the content of 'ioctl_test' debugfs file to '1'. + */ +TEST_F(pcmtest, reset_ioctl) { + snd_pcm_t *handle; + unsigned char *it; + int test_res; + struct pcmtest_test_params *params = &self->params; + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, + self->card, SND_PCM_STREAM_CAPTURE), 0); + snd_pcm_reset(handle); + test_res = get_test_results("ioctl_test"); + ASSERT_EQ(test_res, 1); + snd_pcm_close(handle); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/arm64/abi/hwcap.c b/tools/testing/selftests/arm64/abi/hwcap.c index 93333a90bf3a..d4ad813fed10 100644 --- a/tools/testing/selftests/arm64/abi/hwcap.c +++ b/tools/testing/selftests/arm64/abi/hwcap.c @@ -39,6 +39,20 @@ static void cssc_sigill(void) asm volatile(".inst 0xdac01c00" : : : "x0"); } +static void mops_sigill(void) +{ + char dst[1], src[1]; + register char *dstp asm ("x0") = dst; + register char *srcp asm ("x1") = src; + register long size asm ("x2") = 1; + + /* CPYP [x0]!, [x1]!, x2! */ + asm volatile(".inst 0x1d010440" + : "+r" (dstp), "+r" (srcp), "+r" (size) + : + : "cc", "memory"); +} + static void rng_sigill(void) { asm volatile("mrs x0, S3_3_C2_C4_0" : : : "x0"); @@ -210,6 +224,14 @@ static const struct hwcap_data { .sigill_fn = cssc_sigill, }, { + .name = "MOPS", + .at_hwcap = AT_HWCAP2, + .hwcap_bit = HWCAP2_MOPS, + .cpuinfo = "mops", + .sigill_fn = mops_sigill, + .sigill_reliable = true, + }, + { .name = "RNG", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_RNG, diff --git a/tools/testing/selftests/arm64/abi/ptrace.c b/tools/testing/selftests/arm64/abi/ptrace.c index be952511af22..abe4d58d731d 100644 --- a/tools/testing/selftests/arm64/abi/ptrace.c +++ b/tools/testing/selftests/arm64/abi/ptrace.c @@ -20,7 +20,7 @@ #include "../../kselftest.h" -#define EXPECTED_TESTS 7 +#define EXPECTED_TESTS 11 #define MAX_TPIDRS 2 @@ -132,6 +132,34 @@ static void test_tpidr(pid_t child) } } +static void test_hw_debug(pid_t child, int type, const char *type_name) +{ + struct user_hwdebug_state state; + struct iovec iov; + int slots, arch, ret; + + iov.iov_len = sizeof(state); + iov.iov_base = &state; + + /* Should be able to read the values */ + ret = ptrace(PTRACE_GETREGSET, child, type, &iov); + ksft_test_result(ret == 0, "read_%s\n", type_name); + + if (ret == 0) { + /* Low 8 bits is the number of slots, next 4 bits the arch */ + slots = state.dbg_info & 0xff; + arch = (state.dbg_info >> 8) & 0xf; + + ksft_print_msg("%s version %d with %d slots\n", type_name, + arch, slots); + + /* Zero is not currently architecturally valid */ + ksft_test_result(arch, "%s_arch_set\n", type_name); + } else { + ksft_test_result_skip("%s_arch_set\n"); + } +} + static int do_child(void) { if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) @@ -207,6 +235,8 @@ static int do_parent(pid_t child) ksft_print_msg("Parent is %d, child is %d\n", getpid(), child); test_tpidr(child); + test_hw_debug(child, NT_ARM_HW_WATCH, "NT_ARM_HW_WATCH"); + test_hw_debug(child, NT_ARM_HW_BREAK, "NT_ARM_HW_BREAK"); ret = EXIT_SUCCESS; diff --git a/tools/testing/selftests/arm64/signal/.gitignore b/tools/testing/selftests/arm64/signal/.gitignore index 8ab4c86837fd..839e3a252629 100644 --- a/tools/testing/selftests/arm64/signal/.gitignore +++ b/tools/testing/selftests/arm64/signal/.gitignore @@ -4,7 +4,7 @@ fake_sigreturn_* sme_* ssve_* sve_* -tpidr2_siginfo +tpidr2_* za_* zt_* !*.[ch] diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.c b/tools/testing/selftests/arm64/signal/test_signals_utils.c index 40be8443949d..0dc948db3a4a 100644 --- a/tools/testing/selftests/arm64/signal/test_signals_utils.c +++ b/tools/testing/selftests/arm64/signal/test_signals_utils.c @@ -249,7 +249,8 @@ static void default_handler(int signum, siginfo_t *si, void *uc) fprintf(stderr, "-- Timeout !\n"); } else { fprintf(stderr, - "-- RX UNEXPECTED SIGNAL: %d\n", signum); + "-- RX UNEXPECTED SIGNAL: %d code %d address %p\n", + signum, si->si_code, si->si_addr); } default_result(current, 1); } diff --git a/tools/testing/selftests/arm64/signal/testcases/tpidr2_restore.c b/tools/testing/selftests/arm64/signal/testcases/tpidr2_restore.c new file mode 100644 index 000000000000..f9a86c00c28c --- /dev/null +++ b/tools/testing/selftests/arm64/signal/testcases/tpidr2_restore.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 ARM Limited + * + * Verify that the TPIDR2 register context in signal frames is restored. + */ + +#include <signal.h> +#include <ucontext.h> +#include <sys/auxv.h> +#include <sys/prctl.h> +#include <unistd.h> +#include <asm/sigcontext.h> + +#include "test_signals_utils.h" +#include "testcases.h" + +#define SYS_TPIDR2 "S3_3_C13_C0_5" + +static uint64_t get_tpidr2(void) +{ + uint64_t val; + + asm volatile ( + "mrs %0, " SYS_TPIDR2 "\n" + : "=r"(val) + : + : "cc"); + + return val; +} + +static void set_tpidr2(uint64_t val) +{ + asm volatile ( + "msr " SYS_TPIDR2 ", %0\n" + : + : "r"(val) + : "cc"); +} + + +static uint64_t initial_tpidr2; + +static bool save_tpidr2(struct tdescr *td) +{ + initial_tpidr2 = get_tpidr2(); + fprintf(stderr, "Initial TPIDR2: %lx\n", initial_tpidr2); + + return true; +} + +static int modify_tpidr2(struct tdescr *td, siginfo_t *si, ucontext_t *uc) +{ + uint64_t my_tpidr2 = get_tpidr2(); + + my_tpidr2++; + fprintf(stderr, "Setting TPIDR2 to %lx\n", my_tpidr2); + set_tpidr2(my_tpidr2); + + return 0; +} + +static void check_tpidr2(struct tdescr *td) +{ + uint64_t tpidr2 = get_tpidr2(); + + td->pass = tpidr2 == initial_tpidr2; + + if (td->pass) + fprintf(stderr, "TPIDR2 restored\n"); + else + fprintf(stderr, "TPIDR2 was %lx but is now %lx\n", + initial_tpidr2, tpidr2); +} + +struct tdescr tde = { + .name = "TPIDR2 restore", + .descr = "Validate that TPIDR2 is restored from the sigframe", + .feats_required = FEAT_SME, + .timeout = 3, + .sig_trig = SIGUSR1, + .init = save_tpidr2, + .run = modify_tpidr2, + .check_result = check_tpidr2, +}; diff --git a/tools/testing/selftests/bpf/DENYLIST.aarch64 b/tools/testing/selftests/bpf/DENYLIST.aarch64 index 0a6837f97c32..08adc805878b 100644 --- a/tools/testing/selftests/bpf/DENYLIST.aarch64 +++ b/tools/testing/selftests/bpf/DENYLIST.aarch64 @@ -1,33 +1,6 @@ -bloom_filter_map # libbpf: prog 'check_bloom': failed to attach: ERROR: strerror_r(-524)=22 -bpf_cookie/lsm -bpf_cookie/multi_kprobe_attach_api -bpf_cookie/multi_kprobe_link_api -bpf_cookie/trampoline -bpf_loop/check_callback_fn_stop # link unexpected error: -524 -bpf_loop/check_invalid_flags -bpf_loop/check_nested_calls -bpf_loop/check_non_constant_callback -bpf_loop/check_nr_loops -bpf_loop/check_null_callback_ctx -bpf_loop/check_stack -bpf_mod_race # bpf_mod_kfunc_race__attach unexpected error: -524 (errno 524) -bpf_tcp_ca/dctcp_fallback -btf_dump/btf_dump: var_data # find type id unexpected find type id: actual -2 < expected 0 -cgroup_hierarchical_stats # attach unexpected error: -524 (errno 524) -d_path/basic # setup attach failed: -524 -deny_namespace # attach unexpected error: -524 (errno 524) -fentry_fexit # fentry_attach unexpected error: -1 (errno 524) -fentry_test # fentry_attach unexpected error: -1 (errno 524) -fexit_sleep # fexit_attach fexit attach failed: -1 -fexit_stress # fexit attach unexpected fexit attach: actual -524 < expected 0 -fexit_test # fexit_attach unexpected error: -1 (errno 524) -get_func_args_test # get_func_args_test__attach unexpected error: -524 (errno 524) (trampoline) -get_func_ip_test # get_func_ip_test__attach unexpected error: -524 (errno 524) (trampoline) -htab_update/reenter_update -kfree_skb # attach fentry unexpected error: -524 (trampoline) -kfunc_call/subprog # extern (var ksym) 'bpf_prog_active': not found in kernel BTF -kfunc_call/subprog_lskel # skel unexpected error: -2 -kfunc_dynptr_param/dynptr_data_null # libbpf: prog 'dynptr_data_null': failed to attach: ERROR: strerror_r(-524)=22 +bpf_cookie/multi_kprobe_attach_api # kprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3 +bpf_cookie/multi_kprobe_link_api # kprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3 +fexit_sleep # The test never returns. The remaining tests cannot start. kprobe_multi_bench_attach # bpf_program__attach_kprobe_multi_opts unexpected error: -95 kprobe_multi_test/attach_api_addrs # bpf_program__attach_kprobe_multi_opts unexpected error: -95 kprobe_multi_test/attach_api_pattern # bpf_program__attach_kprobe_multi_opts unexpected error: -95 @@ -35,51 +8,5 @@ kprobe_multi_test/attach_api_syms # bpf_program__attach_kprobe_mu kprobe_multi_test/bench_attach # bpf_program__attach_kprobe_multi_opts unexpected error: -95 kprobe_multi_test/link_api_addrs # link_fd unexpected link_fd: actual -95 < expected 0 kprobe_multi_test/link_api_syms # link_fd unexpected link_fd: actual -95 < expected 0 -kprobe_multi_test/skel_api # kprobe_multi__attach unexpected error: -524 (errno 524) -ksyms_module/libbpf # 'bpf_testmod_ksym_percpu': not found in kernel BTF -ksyms_module/lskel # test_ksyms_module_lskel__open_and_load unexpected error: -2 -libbpf_get_fd_by_id_opts # test_libbpf_get_fd_by_id_opts__attach unexpected error: -524 (errno 524) -linked_list -lookup_key # test_lookup_key__attach unexpected error: -524 (errno 524) -lru_bug # lru_bug__attach unexpected error: -524 (errno 524) -modify_return # modify_return__attach failed unexpected error: -524 (errno 524) -module_attach # skel_attach skeleton attach failed: -524 -module_fentry_shadow # bpf_link_create unexpected bpf_link_create: actual -524 < expected 0 -mptcp/base # run_test mptcp unexpected error: -524 (errno 524) -netcnt # packets unexpected packets: actual 10001 != expected 10000 -rcu_read_lock # failed to attach: ERROR: strerror_r(-524)=22 -recursion # skel_attach unexpected error: -524 (errno 524) -ringbuf # skel_attach skeleton attachment failed: -1 -setget_sockopt # attach_cgroup unexpected error: -524 -sk_storage_tracing # test_sk_storage_tracing__attach unexpected error: -524 (errno 524) -skc_to_unix_sock # could not attach BPF object unexpected error: -524 (errno 524) -socket_cookie # prog_attach unexpected error: -524 -stacktrace_build_id # compare_stack_ips stackmap vs. stack_amap err -1 errno 2 -task_local_storage/exit_creds # skel_attach unexpected error: -524 (errno 524) -task_local_storage/recursion # skel_attach unexpected error: -524 (errno 524) -test_bprm_opts # attach attach failed: -524 -test_ima # attach attach failed: -524 -test_local_storage # attach lsm attach failed: -524 -test_lsm # test_lsm_first_attach unexpected error: -524 (errno 524) -test_overhead # attach_fentry unexpected error: -524 -timer # timer unexpected error: -524 (errno 524) -timer_crash # timer_crash__attach unexpected error: -524 (errno 524) -timer_mim # timer_mim unexpected error: -524 (errno 524) -trace_printk # trace_printk__attach unexpected error: -1 (errno 524) -trace_vprintk # trace_vprintk__attach unexpected error: -1 (errno 524) -tracing_struct # tracing_struct__attach unexpected error: -524 (errno 524) -trampoline_count # attach_prog unexpected error: -524 -unpriv_bpf_disabled # skel_attach unexpected error: -524 (errno 524) -user_ringbuf/test_user_ringbuf_post_misaligned # misaligned_skel unexpected error: -524 (errno 524) -user_ringbuf/test_user_ringbuf_post_producer_wrong_offset -user_ringbuf/test_user_ringbuf_post_larger_than_ringbuf_sz -user_ringbuf/test_user_ringbuf_basic # ringbuf_basic_skel unexpected error: -524 (errno 524) -user_ringbuf/test_user_ringbuf_sample_full_ring_buffer -user_ringbuf/test_user_ringbuf_post_alignment_autoadjust -user_ringbuf/test_user_ringbuf_overfill -user_ringbuf/test_user_ringbuf_discards_properly_ignored -user_ringbuf/test_user_ringbuf_loop -user_ringbuf/test_user_ringbuf_msg_protocol -user_ringbuf/test_user_ringbuf_blocking_reserve -verify_pkcs7_sig # test_verify_pkcs7_sig__attach unexpected error: -524 (errno 524) -vmlinux # skel_attach skeleton attach failed: -524 +kprobe_multi_test/skel_api # libbpf: failed to load BPF skeleton 'kprobe_multi': -3 +module_attach # prog 'kprobe_multi': failed to auto-attach: -95 diff --git a/tools/testing/selftests/bpf/DENYLIST.s390x b/tools/testing/selftests/bpf/DENYLIST.s390x index c7463f3ec3c0..5061d9e24c16 100644 --- a/tools/testing/selftests/bpf/DENYLIST.s390x +++ b/tools/testing/selftests/bpf/DENYLIST.s390x @@ -26,3 +26,4 @@ user_ringbuf # failed to find kernel BTF type ID of verif_stats # trace_vprintk__open_and_load unexpected error: -9 (?) xdp_bonding # failed to auto-attach program 'trace_on_entry': -524 (trampoline) xdp_metadata # JIT does not support calling kernel function (kfunc) +test_task_under_cgroup # JIT does not support calling kernel function (kfunc) diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 28d2c77262be..538df8fb8c42 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -88,8 +88,7 @@ TEST_GEN_PROGS_EXTENDED = test_sock_addr test_skb_cgroup_id_user \ xskxceiver xdp_redirect_multi xdp_synproxy veristat xdp_hw_metadata \ xdp_features -TEST_CUSTOM_PROGS = $(OUTPUT)/urandom_read $(OUTPUT)/sign-file -TEST_GEN_FILES += liburandom_read.so +TEST_GEN_FILES += liburandom_read.so urandom_read sign-file # Emit succinct information message describing current building step # $1 - generic step name (e.g., CC, LINK, etc); diff --git a/tools/testing/selftests/bpf/bench.c b/tools/testing/selftests/bpf/bench.c index d9c080ac1796..41fe5a82b88b 100644 --- a/tools/testing/selftests/bpf/bench.c +++ b/tools/testing/selftests/bpf/bench.c @@ -17,7 +17,7 @@ struct env env = { .duration_sec = 5, .affinity = false, .quiet = false, - .consumer_cnt = 1, + .consumer_cnt = 0, .producer_cnt = 1, }; @@ -441,12 +441,14 @@ static void setup_timer() static void set_thread_affinity(pthread_t thread, int cpu) { cpu_set_t cpuset; + int err; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); - if (pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset)) { + err = pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset); + if (err) { fprintf(stderr, "setting affinity to CPU #%d failed: %d\n", - cpu, errno); + cpu, -err); exit(1); } } @@ -467,7 +469,7 @@ static int next_cpu(struct cpu_set *cpu_set) exit(1); } - return cpu_set->next_cpu++; + return cpu_set->next_cpu++ % env.nr_cpus; } static struct bench_state { @@ -605,7 +607,7 @@ static void setup_benchmark(void) bench->consumer_thread, (void *)(long)i); if (err) { fprintf(stderr, "failed to create consumer thread #%d: %d\n", - i, -errno); + i, -err); exit(1); } if (env.affinity) @@ -624,7 +626,7 @@ static void setup_benchmark(void) bench->producer_thread, (void *)(long)i); if (err) { fprintf(stderr, "failed to create producer thread #%d: %d\n", - i, -errno); + i, -err); exit(1); } if (env.affinity) @@ -657,6 +659,7 @@ static void collect_measurements(long delta_ns) { int main(int argc, char **argv) { + env.nr_cpus = get_nprocs(); parse_cmdline_args_init(argc, argv); if (env.list) { diff --git a/tools/testing/selftests/bpf/bench.h b/tools/testing/selftests/bpf/bench.h index 402729c6a3ac..7ff32be3d730 100644 --- a/tools/testing/selftests/bpf/bench.h +++ b/tools/testing/selftests/bpf/bench.h @@ -27,6 +27,7 @@ struct env { bool quiet; int consumer_cnt; int producer_cnt; + int nr_cpus; struct cpu_set prod_cpus; struct cpu_set cons_cpus; }; diff --git a/tools/testing/selftests/bpf/benchs/bench_bloom_filter_map.c b/tools/testing/selftests/bpf/benchs/bench_bloom_filter_map.c index 7c8ccc108313..e289dd1a14ee 100644 --- a/tools/testing/selftests/bpf/benchs/bench_bloom_filter_map.c +++ b/tools/testing/selftests/bpf/benchs/bench_bloom_filter_map.c @@ -107,9 +107,9 @@ const struct argp bench_bloom_map_argp = { static void validate(void) { - if (env.consumer_cnt != 1) { + if (env.consumer_cnt != 0) { fprintf(stderr, - "The bloom filter benchmarks do not support multi-consumer use\n"); + "The bloom filter benchmarks do not support consumer\n"); exit(1); } } @@ -421,18 +421,12 @@ static void measure(struct bench_res *res) last_false_hits = total_false_hits; } -static void *consumer(void *input) -{ - return NULL; -} - const struct bench bench_bloom_lookup = { .name = "bloom-lookup", .argp = &bench_bloom_map_argp, .validate = validate, .setup = bloom_lookup_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -444,7 +438,6 @@ const struct bench bench_bloom_update = { .validate = validate, .setup = bloom_update_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -456,7 +449,6 @@ const struct bench bench_bloom_false_positive = { .validate = validate, .setup = false_positive_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = false_hits_report_progress, .report_final = false_hits_report_final, @@ -468,7 +460,6 @@ const struct bench bench_hashmap_without_bloom = { .validate = validate, .setup = hashmap_no_bloom_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -480,7 +471,6 @@ const struct bench bench_hashmap_with_bloom = { .validate = validate, .setup = hashmap_with_bloom_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_full_update.c b/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_full_update.c index 75abe8137b6c..ee1dc12c5e5e 100644 --- a/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_full_update.c +++ b/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_full_update.c @@ -14,8 +14,8 @@ static struct ctx { static void validate(void) { - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } } @@ -30,11 +30,6 @@ static void *producer(void *input) return NULL; } -static void *consumer(void *input) -{ - return NULL; -} - static void measure(struct bench_res *res) { } @@ -88,7 +83,6 @@ const struct bench bench_bpf_hashmap_full_update = { .validate = validate, .setup = setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = NULL, .report_final = hashmap_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_lookup.c b/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_lookup.c index 8dbb02f75cff..279ff1b8b5b2 100644 --- a/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_lookup.c +++ b/tools/testing/selftests/bpf/benchs/bench_bpf_hashmap_lookup.c @@ -113,8 +113,8 @@ const struct argp bench_hashmap_lookup_argp = { static void validate(void) { - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } @@ -134,11 +134,6 @@ static void *producer(void *input) return NULL; } -static void *consumer(void *input) -{ - return NULL; -} - static void measure(struct bench_res *res) { } @@ -276,7 +271,6 @@ const struct bench bench_bpf_hashmap_lookup = { .validate = validate, .setup = setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = NULL, .report_final = hashmap_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_bpf_loop.c b/tools/testing/selftests/bpf/benchs/bench_bpf_loop.c index d8a0394e10b1..a705cfb2bccc 100644 --- a/tools/testing/selftests/bpf/benchs/bench_bpf_loop.c +++ b/tools/testing/selftests/bpf/benchs/bench_bpf_loop.c @@ -47,8 +47,8 @@ const struct argp bench_bpf_loop_argp = { static void validate(void) { - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } } @@ -62,11 +62,6 @@ static void *producer(void *input) return NULL; } -static void *consumer(void *input) -{ - return NULL; -} - static void measure(struct bench_res *res) { res->hits = atomic_swap(&ctx.skel->bss->hits, 0); @@ -99,7 +94,6 @@ const struct bench bench_bpf_loop = { .validate = validate, .setup = setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = ops_report_progress, .report_final = ops_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_count.c b/tools/testing/selftests/bpf/benchs/bench_count.c index 078972ce208e..ba89ed3936b7 100644 --- a/tools/testing/selftests/bpf/benchs/bench_count.c +++ b/tools/testing/selftests/bpf/benchs/bench_count.c @@ -18,11 +18,6 @@ static void *count_global_producer(void *input) return NULL; } -static void *count_global_consumer(void *input) -{ - return NULL; -} - static void count_global_measure(struct bench_res *res) { struct count_global_ctx *ctx = &count_global_ctx; @@ -40,7 +35,7 @@ static void count_local_setup(void) { struct count_local_ctx *ctx = &count_local_ctx; - ctx->hits = calloc(env.consumer_cnt, sizeof(*ctx->hits)); + ctx->hits = calloc(env.producer_cnt, sizeof(*ctx->hits)); if (!ctx->hits) exit(1); } @@ -56,11 +51,6 @@ static void *count_local_producer(void *input) return NULL; } -static void *count_local_consumer(void *input) -{ - return NULL; -} - static void count_local_measure(struct bench_res *res) { struct count_local_ctx *ctx = &count_local_ctx; @@ -74,7 +64,6 @@ static void count_local_measure(struct bench_res *res) const struct bench bench_count_global = { .name = "count-global", .producer_thread = count_global_producer, - .consumer_thread = count_global_consumer, .measure = count_global_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -84,7 +73,6 @@ const struct bench bench_count_local = { .name = "count-local", .setup = count_local_setup, .producer_thread = count_local_producer, - .consumer_thread = count_local_consumer, .measure = count_local_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_local_storage.c b/tools/testing/selftests/bpf/benchs/bench_local_storage.c index d4b2817306d4..452499428ceb 100644 --- a/tools/testing/selftests/bpf/benchs/bench_local_storage.c +++ b/tools/testing/selftests/bpf/benchs/bench_local_storage.c @@ -74,8 +74,8 @@ static void validate(void) fprintf(stderr, "benchmark doesn't support multi-producer!\n"); exit(1); } - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } @@ -230,11 +230,6 @@ static inline void trigger_bpf_program(void) syscall(__NR_getpgid); } -static void *consumer(void *input) -{ - return NULL; -} - static void *producer(void *input) { while (true) @@ -259,7 +254,6 @@ const struct bench bench_local_storage_cache_seq_get = { .validate = validate, .setup = local_storage_cache_get_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = local_storage_report_progress, .report_final = local_storage_report_final, @@ -271,7 +265,6 @@ const struct bench bench_local_storage_cache_interleaved_get = { .validate = validate, .setup = local_storage_cache_get_interleaved_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = local_storage_report_progress, .report_final = local_storage_report_final, @@ -283,7 +276,6 @@ const struct bench bench_local_storage_cache_hashmap_control = { .validate = validate, .setup = hashmap_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = local_storage_report_progress, .report_final = local_storage_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_local_storage_create.c b/tools/testing/selftests/bpf/benchs/bench_local_storage_create.c index cff703f90e95..b36de42ee4d9 100644 --- a/tools/testing/selftests/bpf/benchs/bench_local_storage_create.c +++ b/tools/testing/selftests/bpf/benchs/bench_local_storage_create.c @@ -71,7 +71,7 @@ const struct argp bench_local_storage_create_argp = { static void validate(void) { - if (env.consumer_cnt > 1) { + if (env.consumer_cnt != 0) { fprintf(stderr, "local-storage-create benchmark does not need consumer\n"); exit(1); @@ -143,11 +143,6 @@ static void measure(struct bench_res *res) res->drops = atomic_swap(&skel->bss->kmalloc_cnts, 0); } -static void *consumer(void *input) -{ - return NULL; -} - static void *sk_producer(void *input) { struct thread *t = &threads[(long)(input)]; @@ -257,7 +252,6 @@ const struct bench bench_local_storage_create = { .validate = validate, .setup = setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = report_progress, .report_final = report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_local_storage_rcu_tasks_trace.c b/tools/testing/selftests/bpf/benchs/bench_local_storage_rcu_tasks_trace.c index d5eb5587f2aa..edf0b00418c1 100644 --- a/tools/testing/selftests/bpf/benchs/bench_local_storage_rcu_tasks_trace.c +++ b/tools/testing/selftests/bpf/benchs/bench_local_storage_rcu_tasks_trace.c @@ -72,8 +72,8 @@ static void validate(void) fprintf(stderr, "benchmark doesn't support multi-producer!\n"); exit(1); } - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } @@ -197,11 +197,6 @@ static void measure(struct bench_res *res) ctx.prev_kthread_stime = ticks; } -static void *consumer(void *input) -{ - return NULL; -} - static void *producer(void *input) { while (true) @@ -262,7 +257,6 @@ const struct bench bench_local_storage_tasks_trace = { .validate = validate, .setup = local_storage_tasks_trace_setup, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = report_progress, .report_final = report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_rename.c b/tools/testing/selftests/bpf/benchs/bench_rename.c index 3c203b6d6a6e..bf66893c7a33 100644 --- a/tools/testing/selftests/bpf/benchs/bench_rename.c +++ b/tools/testing/selftests/bpf/benchs/bench_rename.c @@ -17,8 +17,8 @@ static void validate(void) fprintf(stderr, "benchmark doesn't support multi-producer!\n"); exit(1); } - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } } @@ -106,17 +106,11 @@ static void setup_fexit(void) attach_bpf(ctx.skel->progs.prog5); } -static void *consumer(void *input) -{ - return NULL; -} - const struct bench bench_rename_base = { .name = "rename-base", .validate = validate, .setup = setup_base, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -127,7 +121,6 @@ const struct bench bench_rename_kprobe = { .validate = validate, .setup = setup_kprobe, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -138,7 +131,6 @@ const struct bench bench_rename_kretprobe = { .validate = validate, .setup = setup_kretprobe, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -149,7 +141,6 @@ const struct bench bench_rename_rawtp = { .validate = validate, .setup = setup_rawtp, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -160,7 +151,6 @@ const struct bench bench_rename_fentry = { .validate = validate, .setup = setup_fentry, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -171,7 +161,6 @@ const struct bench bench_rename_fexit = { .validate = validate, .setup = setup_fexit, .producer_thread = producer, - .consumer_thread = consumer, .measure = measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_ringbufs.c b/tools/testing/selftests/bpf/benchs/bench_ringbufs.c index fc91fdac4faa..3ca14ad36607 100644 --- a/tools/testing/selftests/bpf/benchs/bench_ringbufs.c +++ b/tools/testing/selftests/bpf/benchs/bench_ringbufs.c @@ -96,7 +96,7 @@ static inline void bufs_trigger_batch(void) static void bufs_validate(void) { if (env.consumer_cnt != 1) { - fprintf(stderr, "rb-libbpf benchmark doesn't support multi-consumer!\n"); + fprintf(stderr, "rb-libbpf benchmark needs one consumer!\n"); exit(1); } diff --git a/tools/testing/selftests/bpf/benchs/bench_strncmp.c b/tools/testing/selftests/bpf/benchs/bench_strncmp.c index d3fad2ba6916..a5e1428fd7a0 100644 --- a/tools/testing/selftests/bpf/benchs/bench_strncmp.c +++ b/tools/testing/selftests/bpf/benchs/bench_strncmp.c @@ -50,8 +50,8 @@ const struct argp bench_strncmp_argp = { static void strncmp_validate(void) { - if (env.consumer_cnt != 1) { - fprintf(stderr, "strncmp benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "strncmp benchmark doesn't support consumer!\n"); exit(1); } } @@ -128,11 +128,6 @@ static void *strncmp_producer(void *ctx) return NULL; } -static void *strncmp_consumer(void *ctx) -{ - return NULL; -} - static void strncmp_measure(struct bench_res *res) { res->hits = atomic_swap(&ctx.skel->bss->hits, 0); @@ -144,7 +139,6 @@ const struct bench bench_strncmp_no_helper = { .validate = strncmp_validate, .setup = strncmp_no_helper_setup, .producer_thread = strncmp_producer, - .consumer_thread = strncmp_consumer, .measure = strncmp_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -156,7 +150,6 @@ const struct bench bench_strncmp_helper = { .validate = strncmp_validate, .setup = strncmp_helper_setup, .producer_thread = strncmp_producer, - .consumer_thread = strncmp_consumer, .measure = strncmp_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, diff --git a/tools/testing/selftests/bpf/benchs/bench_trigger.c b/tools/testing/selftests/bpf/benchs/bench_trigger.c index 0c481de2833d..dbd362771d6a 100644 --- a/tools/testing/selftests/bpf/benchs/bench_trigger.c +++ b/tools/testing/selftests/bpf/benchs/bench_trigger.c @@ -13,8 +13,8 @@ static struct counter base_hits; static void trigger_validate(void) { - if (env.consumer_cnt != 1) { - fprintf(stderr, "benchmark doesn't support multi-consumer!\n"); + if (env.consumer_cnt != 0) { + fprintf(stderr, "benchmark doesn't support consumer!\n"); exit(1); } } @@ -103,11 +103,6 @@ static void trigger_fmodret_setup(void) attach_bpf(ctx.skel->progs.bench_trigger_fmodret); } -static void *trigger_consumer(void *input) -{ - return NULL; -} - /* make sure call is not inlined and not avoided by compiler, so __weak and * inline asm volatile in the body of the function * @@ -205,7 +200,6 @@ const struct bench bench_trig_base = { .name = "trig-base", .validate = trigger_validate, .producer_thread = trigger_base_producer, - .consumer_thread = trigger_consumer, .measure = trigger_base_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -216,7 +210,6 @@ const struct bench bench_trig_tp = { .validate = trigger_validate, .setup = trigger_tp_setup, .producer_thread = trigger_producer, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -227,7 +220,6 @@ const struct bench bench_trig_rawtp = { .validate = trigger_validate, .setup = trigger_rawtp_setup, .producer_thread = trigger_producer, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -238,7 +230,6 @@ const struct bench bench_trig_kprobe = { .validate = trigger_validate, .setup = trigger_kprobe_setup, .producer_thread = trigger_producer, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -249,7 +240,6 @@ const struct bench bench_trig_fentry = { .validate = trigger_validate, .setup = trigger_fentry_setup, .producer_thread = trigger_producer, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -260,7 +250,6 @@ const struct bench bench_trig_fentry_sleep = { .validate = trigger_validate, .setup = trigger_fentry_sleep_setup, .producer_thread = trigger_producer, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -271,7 +260,6 @@ const struct bench bench_trig_fmodret = { .validate = trigger_validate, .setup = trigger_fmodret_setup, .producer_thread = trigger_producer, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -281,7 +269,6 @@ const struct bench bench_trig_uprobe_base = { .name = "trig-uprobe-base", .setup = NULL, /* no uprobe/uretprobe is attached */ .producer_thread = uprobe_base_producer, - .consumer_thread = trigger_consumer, .measure = trigger_base_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -291,7 +278,6 @@ const struct bench bench_trig_uprobe_with_nop = { .name = "trig-uprobe-with-nop", .setup = uprobe_setup_with_nop, .producer_thread = uprobe_producer_with_nop, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -301,7 +287,6 @@ const struct bench bench_trig_uretprobe_with_nop = { .name = "trig-uretprobe-with-nop", .setup = uretprobe_setup_with_nop, .producer_thread = uprobe_producer_with_nop, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -311,7 +296,6 @@ const struct bench bench_trig_uprobe_without_nop = { .name = "trig-uprobe-without-nop", .setup = uprobe_setup_without_nop, .producer_thread = uprobe_producer_without_nop, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, @@ -321,7 +305,6 @@ const struct bench bench_trig_uretprobe_without_nop = { .name = "trig-uretprobe-without-nop", .setup = uretprobe_setup_without_nop, .producer_thread = uprobe_producer_without_nop, - .consumer_thread = trigger_consumer, .measure = trigger_measure, .report_progress = hits_drops_report_progress, .report_final = hits_drops_report_final, diff --git a/tools/testing/selftests/bpf/benchs/run_bench_ringbufs.sh b/tools/testing/selftests/bpf/benchs/run_bench_ringbufs.sh index ada028aa9007..91e3567962ff 100755 --- a/tools/testing/selftests/bpf/benchs/run_bench_ringbufs.sh +++ b/tools/testing/selftests/bpf/benchs/run_bench_ringbufs.sh @@ -4,46 +4,48 @@ source ./benchs/run_common.sh set -eufo pipefail +RUN_RB_BENCH="$RUN_BENCH -c1" + header "Single-producer, parallel producer" for b in rb-libbpf rb-custom pb-libbpf pb-custom; do - summarize $b "$($RUN_BENCH $b)" + summarize $b "$($RUN_RB_BENCH $b)" done header "Single-producer, parallel producer, sampled notification" for b in rb-libbpf rb-custom pb-libbpf pb-custom; do - summarize $b "$($RUN_BENCH --rb-sampled $b)" + summarize $b "$($RUN_RB_BENCH --rb-sampled $b)" done header "Single-producer, back-to-back mode" for b in rb-libbpf rb-custom pb-libbpf pb-custom; do - summarize $b "$($RUN_BENCH --rb-b2b $b)" - summarize $b-sampled "$($RUN_BENCH --rb-sampled --rb-b2b $b)" + summarize $b "$($RUN_RB_BENCH --rb-b2b $b)" + summarize $b-sampled "$($RUN_RB_BENCH --rb-sampled --rb-b2b $b)" done header "Ringbuf back-to-back, effect of sample rate" for b in 1 5 10 25 50 100 250 500 1000 2000 3000; do - summarize "rb-sampled-$b" "$($RUN_BENCH --rb-b2b --rb-batch-cnt $b --rb-sampled --rb-sample-rate $b rb-custom)" + summarize "rb-sampled-$b" "$($RUN_RB_BENCH --rb-b2b --rb-batch-cnt $b --rb-sampled --rb-sample-rate $b rb-custom)" done header "Perfbuf back-to-back, effect of sample rate" for b in 1 5 10 25 50 100 250 500 1000 2000 3000; do - summarize "pb-sampled-$b" "$($RUN_BENCH --rb-b2b --rb-batch-cnt $b --rb-sampled --rb-sample-rate $b pb-custom)" + summarize "pb-sampled-$b" "$($RUN_RB_BENCH --rb-b2b --rb-batch-cnt $b --rb-sampled --rb-sample-rate $b pb-custom)" done header "Ringbuf back-to-back, reserve+commit vs output" -summarize "reserve" "$($RUN_BENCH --rb-b2b rb-custom)" -summarize "output" "$($RUN_BENCH --rb-b2b --rb-use-output rb-custom)" +summarize "reserve" "$($RUN_RB_BENCH --rb-b2b rb-custom)" +summarize "output" "$($RUN_RB_BENCH --rb-b2b --rb-use-output rb-custom)" header "Ringbuf sampled, reserve+commit vs output" -summarize "reserve-sampled" "$($RUN_BENCH --rb-sampled rb-custom)" -summarize "output-sampled" "$($RUN_BENCH --rb-sampled --rb-use-output rb-custom)" +summarize "reserve-sampled" "$($RUN_RB_BENCH --rb-sampled rb-custom)" +summarize "output-sampled" "$($RUN_RB_BENCH --rb-sampled --rb-use-output rb-custom)" header "Single-producer, consumer/producer competing on the same CPU, low batch count" for b in rb-libbpf rb-custom pb-libbpf pb-custom; do - summarize $b "$($RUN_BENCH --rb-batch-cnt 1 --rb-sample-rate 1 --prod-affinity 0 --cons-affinity 0 $b)" + summarize $b "$($RUN_RB_BENCH --rb-batch-cnt 1 --rb-sample-rate 1 --prod-affinity 0 --cons-affinity 0 $b)" done header "Ringbuf, multi-producer contention" for b in 1 2 3 4 8 12 16 20 24 28 32 36 40 44 48 52; do - summarize "rb-libbpf nr_prod $b" "$($RUN_BENCH -p$b --rb-batch-cnt 50 rb-libbpf)" + summarize "rb-libbpf nr_prod $b" "$($RUN_RB_BENCH -p$b --rb-batch-cnt 50 rb-libbpf)" done diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h index 8c993ec8ceea..642dda0e758a 100644 --- a/tools/testing/selftests/bpf/bpf_kfuncs.h +++ b/tools/testing/selftests/bpf/bpf_kfuncs.h @@ -35,4 +35,10 @@ extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, __u32 offset, extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, __u32 offset, void *buffer, __u32 buffer__szk) __ksym; +extern int bpf_dynptr_adjust(const struct bpf_dynptr *ptr, __u32 start, __u32 end) __ksym; +extern bool bpf_dynptr_is_null(const struct bpf_dynptr *ptr) __ksym; +extern bool bpf_dynptr_is_rdonly(const struct bpf_dynptr *ptr) __ksym; +extern __u32 bpf_dynptr_size(const struct bpf_dynptr *ptr) __ksym; +extern int bpf_dynptr_clone(const struct bpf_dynptr *ptr, struct bpf_dynptr *clone__init) __ksym; + #endif diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c index 52785ba671e6..aaf6ef1201c7 100644 --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c @@ -9,6 +9,7 @@ #include <linux/sysfs.h> #include <linux/tracepoint.h> #include "bpf_testmod.h" +#include "bpf_testmod_kfunc.h" #define CREATE_TRACE_POINTS #include "bpf_testmod-events.h" @@ -190,8 +191,6 @@ noinline int bpf_testmod_fentry_test3(char a, int b, u64 c) return a + b + c; } -__diag_pop(); - int bpf_testmod_fentry_ok; noinline ssize_t @@ -272,6 +271,14 @@ bpf_testmod_test_write(struct file *file, struct kobject *kobj, EXPORT_SYMBOL(bpf_testmod_test_write); ALLOW_ERROR_INJECTION(bpf_testmod_test_write, ERRNO); +noinline int bpf_fentry_shadow_test(int a) +{ + return a + 2; +} +EXPORT_SYMBOL_GPL(bpf_fentry_shadow_test); + +__diag_pop(); + static struct bin_attribute bin_attr_bpf_testmod_file __ro_after_init = { .attr = { .name = "bpf_testmod", .mode = 0666, }, .read = bpf_testmod_test_read, @@ -289,8 +296,171 @@ static const struct btf_kfunc_id_set bpf_testmod_common_kfunc_set = { .set = &bpf_testmod_common_kfunc_ids, }; +__bpf_kfunc u64 bpf_kfunc_call_test1(struct sock *sk, u32 a, u64 b, u32 c, u64 d) +{ + return a + b + c + d; +} + +__bpf_kfunc int bpf_kfunc_call_test2(struct sock *sk, u32 a, u32 b) +{ + return a + b; +} + +__bpf_kfunc struct sock *bpf_kfunc_call_test3(struct sock *sk) +{ + return sk; +} + +__bpf_kfunc long noinline bpf_kfunc_call_test4(signed char a, short b, int c, long d) +{ + /* Provoke the compiler to assume that the caller has sign-extended a, + * b and c on platforms where this is required (e.g. s390x). + */ + return (long)a + (long)b + (long)c + d; +} + +static struct prog_test_ref_kfunc prog_test_struct = { + .a = 42, + .b = 108, + .next = &prog_test_struct, + .cnt = REFCOUNT_INIT(1), +}; + +__bpf_kfunc struct prog_test_ref_kfunc * +bpf_kfunc_call_test_acquire(unsigned long *scalar_ptr) +{ + refcount_inc(&prog_test_struct.cnt); + return &prog_test_struct; +} + +__bpf_kfunc void bpf_kfunc_call_test_offset(struct prog_test_ref_kfunc *p) +{ + WARN_ON_ONCE(1); +} + +__bpf_kfunc struct prog_test_member * +bpf_kfunc_call_memb_acquire(void) +{ + WARN_ON_ONCE(1); + return NULL; +} + +__bpf_kfunc void bpf_kfunc_call_memb1_release(struct prog_test_member1 *p) +{ + WARN_ON_ONCE(1); +} + +static int *__bpf_kfunc_call_test_get_mem(struct prog_test_ref_kfunc *p, const int size) +{ + if (size > 2 * sizeof(int)) + return NULL; + + return (int *)p; +} + +__bpf_kfunc int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, + const int rdwr_buf_size) +{ + return __bpf_kfunc_call_test_get_mem(p, rdwr_buf_size); +} + +__bpf_kfunc int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, + const int rdonly_buf_size) +{ + return __bpf_kfunc_call_test_get_mem(p, rdonly_buf_size); +} + +/* the next 2 ones can't be really used for testing expect to ensure + * that the verifier rejects the call. + * Acquire functions must return struct pointers, so these ones are + * failing. + */ +__bpf_kfunc int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, + const int rdonly_buf_size) +{ + return __bpf_kfunc_call_test_get_mem(p, rdonly_buf_size); +} + +__bpf_kfunc void bpf_kfunc_call_int_mem_release(int *p) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_pass_ctx(struct __sk_buff *skb) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_pass1(struct prog_test_pass1 *p) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_pass2(struct prog_test_pass2 *p) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_fail1(struct prog_test_fail1 *p) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_fail2(struct prog_test_fail2 *p) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_fail3(struct prog_test_fail3 *p) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_mem_len_pass1(void *mem, int mem__sz) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_mem_len_fail1(void *mem, int len) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_mem_len_fail2(u64 *mem, int len) +{ +} + +__bpf_kfunc void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) +{ + /* p != NULL, but p->cnt could be 0 */ +} + +__bpf_kfunc void bpf_kfunc_call_test_destructive(void) +{ +} + +__bpf_kfunc static u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) +{ + return arg; +} + BTF_SET8_START(bpf_testmod_check_kfunc_ids) BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc) +BTF_ID_FLAGS(func, bpf_kfunc_call_test1) +BTF_ID_FLAGS(func, bpf_kfunc_call_test2) +BTF_ID_FLAGS(func, bpf_kfunc_call_test3) +BTF_ID_FLAGS(func, bpf_kfunc_call_test4) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail1) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail2) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_acquire, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_kfunc_call_memb_acquire, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_kfunc_call_memb1_release, KF_RELEASE) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_get_rdwr_mem, KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_get_rdonly_mem, KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_acq_rdonly_mem, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_kfunc_call_int_mem_release, KF_RELEASE) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass_ctx) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass1) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass2) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail1) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail2) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail3) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_ref, KF_TRUSTED_ARGS | KF_RCU) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_destructive, KF_DESTRUCTIVE) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_static_unused_arg) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_offset) BTF_SET8_END(bpf_testmod_check_kfunc_ids) static const struct btf_kfunc_id_set bpf_testmod_kfunc_set = { @@ -298,12 +468,6 @@ static const struct btf_kfunc_id_set bpf_testmod_kfunc_set = { .set = &bpf_testmod_check_kfunc_ids, }; -noinline int bpf_fentry_shadow_test(int a) -{ - return a + 2; -} -EXPORT_SYMBOL_GPL(bpf_fentry_shadow_test); - extern int bpf_fentry_test1(int a); static int bpf_testmod_init(void) @@ -312,6 +476,8 @@ static int bpf_testmod_init(void) ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &bpf_testmod_common_kfunc_set); ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_testmod_kfunc_set); + ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_testmod_kfunc_set); + ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &bpf_testmod_kfunc_set); if (ret < 0) return ret; if (bpf_fentry_test1(0) < 0) diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h new file mode 100644 index 000000000000..f5c5b1375c24 --- /dev/null +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _BPF_TESTMOD_KFUNC_H +#define _BPF_TESTMOD_KFUNC_H + +#ifndef __KERNEL__ +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> +#else +#define __ksym +struct prog_test_member1 { + int a; +}; + +struct prog_test_member { + struct prog_test_member1 m; + int c; +}; + +struct prog_test_ref_kfunc { + int a; + int b; + struct prog_test_member memb; + struct prog_test_ref_kfunc *next; + refcount_t cnt; +}; +#endif + +struct prog_test_pass1 { + int x0; + struct { + int x1; + struct { + int x2; + struct { + int x3; + }; + }; + }; +}; + +struct prog_test_pass2 { + int len; + short arr1[4]; + struct { + char arr2[4]; + unsigned long arr3[8]; + } x; +}; + +struct prog_test_fail1 { + void *p; + int x; +}; + +struct prog_test_fail2 { + int x8; + struct prog_test_pass1 x; +}; + +struct prog_test_fail3 { + int len; + char arr1[2]; + char arr2[]; +}; + +struct prog_test_ref_kfunc * +bpf_kfunc_call_test_acquire(unsigned long *scalar_ptr) __ksym; +void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; +void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym; + +void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym; +int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym; +int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym; +int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym; +void bpf_kfunc_call_int_mem_release(int *p) __ksym; + +/* The bpf_kfunc_call_test_static_unused_arg is defined as static, + * but bpf program compilation needs to see it as global symbol. + */ +#ifndef __KERNEL__ +u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym; +#endif + +void bpf_testmod_test_mod_kfunc(int i) __ksym; + +__u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b, + __u32 c, __u64 d) __ksym; +int bpf_kfunc_call_test2(struct sock *sk, __u32 a, __u32 b) __ksym; +struct sock *bpf_kfunc_call_test3(struct sock *sk) __ksym; +long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym; + +void bpf_kfunc_call_test_pass_ctx(struct __sk_buff *skb) __ksym; +void bpf_kfunc_call_test_pass1(struct prog_test_pass1 *p) __ksym; +void bpf_kfunc_call_test_pass2(struct prog_test_pass2 *p) __ksym; +void bpf_kfunc_call_test_mem_len_fail2(__u64 *mem, int len) __ksym; + +void bpf_kfunc_call_test_destructive(void) __ksym; + +void bpf_kfunc_call_test_offset(struct prog_test_ref_kfunc *p); +struct prog_test_member *bpf_kfunc_call_memb_acquire(void); +void bpf_kfunc_call_memb1_release(struct prog_test_member1 *p); +void bpf_kfunc_call_test_fail1(struct prog_test_fail1 *p); +void bpf_kfunc_call_test_fail2(struct prog_test_fail2 *p); +void bpf_kfunc_call_test_fail3(struct prog_test_fail3 *p); +void bpf_kfunc_call_test_mem_len_fail1(void *mem, int len); +#endif /* _BPF_TESTMOD_KFUNC_H */ diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config index 63cd4ab70171..3b350bc31343 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -13,6 +13,9 @@ CONFIG_CGROUP_BPF=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_SHA256=y CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_DEBUG_INFO_DWARF4=y CONFIG_DYNAMIC_FTRACE=y CONFIG_FPROBE=y CONFIG_FTRACE_SYSCALLS=y @@ -60,6 +63,7 @@ CONFIG_NET_SCH_INGRESS=y CONFIG_NET_SCHED=y CONFIG_NETDEVSIM=y CONFIG_NETFILTER=y +CONFIG_NETFILTER_ADVANCED=y CONFIG_NETFILTER_SYNPROXY=y CONFIG_NETFILTER_XT_CONNMARK=y CONFIG_NETFILTER_XT_MATCH_STATE=y diff --git a/tools/testing/selftests/bpf/network_helpers.c b/tools/testing/selftests/bpf/network_helpers.c index 596caa176582..a105c0cd008a 100644 --- a/tools/testing/selftests/bpf/network_helpers.c +++ b/tools/testing/selftests/bpf/network_helpers.c @@ -427,3 +427,26 @@ void close_netns(struct nstoken *token) close(token->orig_netns_fd); free(token); } + +int get_socket_local_port(int sock_fd) +{ + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int err; + + err = getsockname(sock_fd, (struct sockaddr *)&addr, &addrlen); + if (err < 0) + return err; + + if (addr.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&addr; + + return sin->sin_port; + } else if (addr.ss_family == AF_INET6) { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&addr; + + return sin->sin6_port; + } + + return -1; +} diff --git a/tools/testing/selftests/bpf/network_helpers.h b/tools/testing/selftests/bpf/network_helpers.h index f882c691b790..694185644da6 100644 --- a/tools/testing/selftests/bpf/network_helpers.h +++ b/tools/testing/selftests/bpf/network_helpers.h @@ -56,6 +56,7 @@ int fastopen_connect(int server_fd, const char *data, unsigned int data_len, int make_sockaddr(int family, const char *addr_str, __u16 port, struct sockaddr_storage *addr, socklen_t *len); char *ping_command(int family); +int get_socket_local_port(int sock_fd); struct nstoken; /** diff --git a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c index b17bfa0e0aac..bb143de68875 100644 --- a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c +++ b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c @@ -96,12 +96,80 @@ static void test_parse_test_list(void) goto error; ASSERT_OK(strcmp("*bpf_cookie*", set.tests[0].name), "test name"); ASSERT_OK(strcmp("*trace*", set.tests[0].subtests[0]), "subtest name"); + free_test_filter_set(&set); + + ASSERT_OK(parse_test_list("t/subtest1,t/subtest2", &set, true), + "parsing"); + if (!ASSERT_EQ(set.cnt, 1, "count of test filters")) + goto error; + if (!ASSERT_OK_PTR(set.tests, "test filters initialized")) + goto error; + if (!ASSERT_EQ(set.tests[0].subtest_cnt, 2, "subtest filters count")) + goto error; + ASSERT_OK(strcmp("t", set.tests[0].name), "test name"); + ASSERT_OK(strcmp("subtest1", set.tests[0].subtests[0]), "subtest name"); + ASSERT_OK(strcmp("subtest2", set.tests[0].subtests[1]), "subtest name"); error: free_test_filter_set(&set); } +static void test_parse_test_list_file(void) +{ + struct test_filter_set set; + char tmpfile[80]; + FILE *fp; + int fd; + + snprintf(tmpfile, sizeof(tmpfile), "/tmp/bpf_arg_parsing_test.XXXXXX"); + fd = mkstemp(tmpfile); + if (!ASSERT_GE(fd, 0, "create tmp")) + return; + + fp = fdopen(fd, "w"); + if (!ASSERT_NEQ(fp, NULL, "fdopen tmp")) { + close(fd); + goto out_remove; + } + + fprintf(fp, "# comment\n"); + fprintf(fp, " test_with_spaces \n"); + fprintf(fp, "testA/subtest # comment\n"); + fprintf(fp, "testB#comment with no space\n"); + fprintf(fp, "testB # duplicate\n"); + fprintf(fp, "testA/subtest # subtest duplicate\n"); + fprintf(fp, "testA/subtest2\n"); + fprintf(fp, "testC_no_eof_newline"); + fflush(fp); + + if (!ASSERT_OK(ferror(fp), "prepare tmp")) + goto out_fclose; + + init_test_filter_set(&set); + + ASSERT_OK(parse_test_list_file(tmpfile, &set, true), "parse file"); + + ASSERT_EQ(set.cnt, 4, "test count"); + ASSERT_OK(strcmp("test_with_spaces", set.tests[0].name), "test 0 name"); + ASSERT_EQ(set.tests[0].subtest_cnt, 0, "test 0 subtest count"); + ASSERT_OK(strcmp("testA", set.tests[1].name), "test 1 name"); + ASSERT_EQ(set.tests[1].subtest_cnt, 2, "test 1 subtest count"); + ASSERT_OK(strcmp("subtest", set.tests[1].subtests[0]), "test 1 subtest 0"); + ASSERT_OK(strcmp("subtest2", set.tests[1].subtests[1]), "test 1 subtest 1"); + ASSERT_OK(strcmp("testB", set.tests[2].name), "test 2 name"); + ASSERT_OK(strcmp("testC_no_eof_newline", set.tests[3].name), "test 3 name"); + + free_test_filter_set(&set); + +out_fclose: + fclose(fp); +out_remove: + remove(tmpfile); +} + void test_arg_parsing(void) { if (test__start_subtest("test_parse_test_list")) test_parse_test_list(); + if (test__start_subtest("test_parse_test_list_file")) + test_parse_test_list_file(); } diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c index a4d0cc9d3367..fe2c502e5089 100644 --- a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c +++ b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c @@ -11,6 +11,7 @@ #include "ksym_race.skel.h" #include "bpf_mod_race.skel.h" #include "kfunc_call_race.skel.h" +#include "testing_helpers.h" /* This test crafts a race between btf_try_get_module and do_init_module, and * checks whether btf_try_get_module handles the invocation for a well-formed @@ -44,35 +45,10 @@ enum bpf_test_state { static _Atomic enum bpf_test_state state = _TS_INVALID; -static int sys_finit_module(int fd, const char *param_values, int flags) -{ - return syscall(__NR_finit_module, fd, param_values, flags); -} - -static int sys_delete_module(const char *name, unsigned int flags) -{ - return syscall(__NR_delete_module, name, flags); -} - -static int load_module(const char *mod) -{ - int ret, fd; - - fd = open("bpf_testmod.ko", O_RDONLY); - if (fd < 0) - return fd; - - ret = sys_finit_module(fd, "", 0); - close(fd); - if (ret < 0) - return ret; - return 0; -} - static void *load_module_thread(void *p) { - if (!ASSERT_NEQ(load_module("bpf_testmod.ko"), 0, "load_module_thread must fail")) + if (!ASSERT_NEQ(load_bpf_testmod(false), 0, "load_module_thread must fail")) atomic_store(&state, TS_MODULE_LOAD); else atomic_store(&state, TS_MODULE_LOAD_FAIL); @@ -124,7 +100,7 @@ static void test_bpf_mod_race_config(const struct test_config *config) if (!ASSERT_NEQ(fault_addr, MAP_FAILED, "mmap for uffd registration")) return; - if (!ASSERT_OK(sys_delete_module("bpf_testmod", 0), "unload bpf_testmod")) + if (!ASSERT_OK(unload_bpf_testmod(false), "unload bpf_testmod")) goto end_mmap; skel = bpf_mod_race__open(); @@ -202,8 +178,8 @@ end_destroy: bpf_mod_race__destroy(skel); ASSERT_OK(kern_sync_rcu(), "kern_sync_rcu"); end_module: - sys_delete_module("bpf_testmod", 0); - ASSERT_OK(load_module("bpf_testmod.ko"), "restore bpf_testmod"); + unload_bpf_testmod(false); + ASSERT_OK(load_bpf_testmod(false), "restore bpf_testmod"); end_mmap: munmap(fault_addr, 4096); atomic_store(&state, _TS_INVALID); diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_obj_pinning.c b/tools/testing/selftests/bpf/prog_tests/bpf_obj_pinning.c new file mode 100644 index 000000000000..31f1e815f671 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/bpf_obj_pinning.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ +#define _GNU_SOURCE +#include <test_progs.h> +#include <bpf/btf.h> +#include <fcntl.h> +#include <unistd.h> +#include <linux/unistd.h> +#include <linux/mount.h> +#include <sys/syscall.h> + +static inline int sys_fsopen(const char *fsname, unsigned flags) +{ + return syscall(__NR_fsopen, fsname, flags); +} + +static inline int sys_fsconfig(int fs_fd, unsigned cmd, const char *key, const void *val, int aux) +{ + return syscall(__NR_fsconfig, fs_fd, cmd, key, val, aux); +} + +static inline int sys_fsmount(int fs_fd, unsigned flags, unsigned ms_flags) +{ + return syscall(__NR_fsmount, fs_fd, flags, ms_flags); +} + +__attribute__((unused)) +static inline int sys_move_mount(int from_dfd, const char *from_path, + int to_dfd, const char *to_path, + unsigned int ms_flags) +{ + return syscall(__NR_move_mount, from_dfd, from_path, to_dfd, to_path, ms_flags); +} + +static void bpf_obj_pinning_detached(void) +{ + LIBBPF_OPTS(bpf_obj_pin_opts, pin_opts); + LIBBPF_OPTS(bpf_obj_get_opts, get_opts); + int fs_fd = -1, mnt_fd = -1; + int map_fd = -1, map_fd2 = -1; + int zero = 0, src_value, dst_value, err; + const char *map_name = "fsmount_map"; + + /* A bunch of below UAPI calls are constructed based on reading: + * https://brauner.io/2023/02/28/mounting-into-mount-namespaces.html + */ + + /* create VFS context */ + fs_fd = sys_fsopen("bpf", 0); + if (!ASSERT_GE(fs_fd, 0, "fs_fd")) + goto cleanup; + + /* instantiate FS object */ + err = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + if (!ASSERT_OK(err, "fs_create")) + goto cleanup; + + /* create O_PATH fd for detached mount */ + mnt_fd = sys_fsmount(fs_fd, 0, 0); + if (!ASSERT_GE(mnt_fd, 0, "mnt_fd")) + goto cleanup; + + /* If we wanted to expose detached mount in the file system, we'd do + * something like below. But the whole point is that we actually don't + * even have to expose BPF FS in the file system to be able to work + * (pin/get objects) with it. + * + * err = sys_move_mount(mnt_fd, "", -EBADF, mnt_path, MOVE_MOUNT_F_EMPTY_PATH); + * if (!ASSERT_OK(err, "move_mount")) + * goto cleanup; + */ + + /* create BPF map to pin */ + map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, 4, 4, 1, NULL); + if (!ASSERT_GE(map_fd, 0, "map_fd")) + goto cleanup; + + /* pin BPF map into detached BPF FS through mnt_fd */ + pin_opts.file_flags = BPF_F_PATH_FD; + pin_opts.path_fd = mnt_fd; + err = bpf_obj_pin_opts(map_fd, map_name, &pin_opts); + if (!ASSERT_OK(err, "map_pin")) + goto cleanup; + + /* get BPF map from detached BPF FS through mnt_fd */ + get_opts.file_flags = BPF_F_PATH_FD; + get_opts.path_fd = mnt_fd; + map_fd2 = bpf_obj_get_opts(map_name, &get_opts); + if (!ASSERT_GE(map_fd2, 0, "map_get")) + goto cleanup; + + /* update map through one FD */ + src_value = 0xcafebeef; + err = bpf_map_update_elem(map_fd, &zero, &src_value, 0); + ASSERT_OK(err, "map_update"); + + /* check values written/read through different FDs do match */ + dst_value = 0; + err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value); + ASSERT_OK(err, "map_lookup"); + ASSERT_EQ(dst_value, src_value, "map_value_eq1"); + ASSERT_EQ(dst_value, 0xcafebeef, "map_value_eq2"); + +cleanup: + if (map_fd >= 0) + ASSERT_OK(close(map_fd), "close_map_fd"); + if (map_fd2 >= 0) + ASSERT_OK(close(map_fd2), "close_map_fd2"); + if (fs_fd >= 0) + ASSERT_OK(close(fs_fd), "close_fs_fd"); + if (mnt_fd >= 0) + ASSERT_OK(close(mnt_fd), "close_mnt_fd"); +} + +enum path_kind +{ + PATH_STR_ABS, + PATH_STR_REL, + PATH_FD_REL, +}; + +static void validate_pin(int map_fd, const char *map_name, int src_value, + enum path_kind path_kind) +{ + LIBBPF_OPTS(bpf_obj_pin_opts, pin_opts); + char abs_path[PATH_MAX], old_cwd[PATH_MAX]; + const char *pin_path = NULL; + int zero = 0, dst_value, map_fd2, err; + + snprintf(abs_path, sizeof(abs_path), "/sys/fs/bpf/%s", map_name); + old_cwd[0] = '\0'; + + switch (path_kind) { + case PATH_STR_ABS: + /* absolute path */ + pin_path = abs_path; + break; + case PATH_STR_REL: + /* cwd + relative path */ + ASSERT_OK_PTR(getcwd(old_cwd, sizeof(old_cwd)), "getcwd"); + ASSERT_OK(chdir("/sys/fs/bpf"), "chdir"); + pin_path = map_name; + break; + case PATH_FD_REL: + /* dir fd + relative path */ + pin_opts.file_flags = BPF_F_PATH_FD; + pin_opts.path_fd = open("/sys/fs/bpf", O_PATH); + ASSERT_GE(pin_opts.path_fd, 0, "path_fd"); + pin_path = map_name; + break; + } + + /* pin BPF map using specified path definition */ + err = bpf_obj_pin_opts(map_fd, pin_path, &pin_opts); + ASSERT_OK(err, "obj_pin"); + + /* cleanup */ + if (pin_opts.path_fd >= 0) + close(pin_opts.path_fd); + if (old_cwd[0]) + ASSERT_OK(chdir(old_cwd), "restore_cwd"); + + map_fd2 = bpf_obj_get(abs_path); + if (!ASSERT_GE(map_fd2, 0, "map_get")) + goto cleanup; + + /* update map through one FD */ + err = bpf_map_update_elem(map_fd, &zero, &src_value, 0); + ASSERT_OK(err, "map_update"); + + /* check values written/read through different FDs do match */ + dst_value = 0; + err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value); + ASSERT_OK(err, "map_lookup"); + ASSERT_EQ(dst_value, src_value, "map_value_eq"); +cleanup: + if (map_fd2 >= 0) + ASSERT_OK(close(map_fd2), "close_map_fd2"); + unlink(abs_path); +} + +static void validate_get(int map_fd, const char *map_name, int src_value, + enum path_kind path_kind) +{ + LIBBPF_OPTS(bpf_obj_get_opts, get_opts); + char abs_path[PATH_MAX], old_cwd[PATH_MAX]; + const char *pin_path = NULL; + int zero = 0, dst_value, map_fd2, err; + + snprintf(abs_path, sizeof(abs_path), "/sys/fs/bpf/%s", map_name); + /* pin BPF map using specified path definition */ + err = bpf_obj_pin(map_fd, abs_path); + if (!ASSERT_OK(err, "pin_map")) + return; + + old_cwd[0] = '\0'; + + switch (path_kind) { + case PATH_STR_ABS: + /* absolute path */ + pin_path = abs_path; + break; + case PATH_STR_REL: + /* cwd + relative path */ + ASSERT_OK_PTR(getcwd(old_cwd, sizeof(old_cwd)), "getcwd"); + ASSERT_OK(chdir("/sys/fs/bpf"), "chdir"); + pin_path = map_name; + break; + case PATH_FD_REL: + /* dir fd + relative path */ + get_opts.file_flags = BPF_F_PATH_FD; + get_opts.path_fd = open("/sys/fs/bpf", O_PATH); + ASSERT_GE(get_opts.path_fd, 0, "path_fd"); + pin_path = map_name; + break; + } + + map_fd2 = bpf_obj_get_opts(pin_path, &get_opts); + if (!ASSERT_GE(map_fd2, 0, "map_get")) + goto cleanup; + + /* cleanup */ + if (get_opts.path_fd >= 0) + close(get_opts.path_fd); + if (old_cwd[0]) + ASSERT_OK(chdir(old_cwd), "restore_cwd"); + + /* update map through one FD */ + err = bpf_map_update_elem(map_fd, &zero, &src_value, 0); + ASSERT_OK(err, "map_update"); + + /* check values written/read through different FDs do match */ + dst_value = 0; + err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value); + ASSERT_OK(err, "map_lookup"); + ASSERT_EQ(dst_value, src_value, "map_value_eq"); +cleanup: + if (map_fd2 >= 0) + ASSERT_OK(close(map_fd2), "close_map_fd2"); + unlink(abs_path); +} + +static void bpf_obj_pinning_mounted(enum path_kind path_kind) +{ + const char *map_name = "mounted_map"; + int map_fd; + + /* create BPF map to pin */ + map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, 4, 4, 1, NULL); + if (!ASSERT_GE(map_fd, 0, "map_fd")) + return; + + validate_pin(map_fd, map_name, 100 + (int)path_kind, path_kind); + validate_get(map_fd, map_name, 200 + (int)path_kind, path_kind); + ASSERT_OK(close(map_fd), "close_map_fd"); +} + +void test_bpf_obj_pinning() +{ + if (test__start_subtest("detached")) + bpf_obj_pinning_detached(); + if (test__start_subtest("mounted-str-abs")) + bpf_obj_pinning_mounted(PATH_STR_ABS); + if (test__start_subtest("mounted-str-rel")) + bpf_obj_pinning_mounted(PATH_STR_REL); + if (test__start_subtest("mounted-fd-rel")) + bpf_obj_pinning_mounted(PATH_FD_REL); +} diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c index 210d643fda6c..4e0cdb593318 100644 --- a/tools/testing/selftests/bpf/prog_tests/btf.c +++ b/tools/testing/selftests/bpf/prog_tests/btf.c @@ -3991,6 +3991,46 @@ static struct btf_raw_test raw_tests[] = { .err_str = "Invalid arg#1", }, { + .descr = "decl_tag test #18, decl_tag as the map key type", + .raw_types = { + BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */ + BTF_STRUCT_ENC(0, 2, 8), /* [2] */ + BTF_MEMBER_ENC(NAME_TBD, 1, 0), + BTF_MEMBER_ENC(NAME_TBD, 1, 32), + BTF_DECL_TAG_ENC(NAME_TBD, 2, -1), /* [3] */ + BTF_END_RAW, + }, + BTF_STR_SEC("\0m1\0m2\0tag"), + .map_type = BPF_MAP_TYPE_HASH, + .map_name = "tag_type_check_btf", + .key_size = 8, + .value_size = 4, + .key_type_id = 3, + .value_type_id = 1, + .max_entries = 1, + .map_create_err = true, +}, +{ + .descr = "decl_tag test #19, decl_tag as the map value type", + .raw_types = { + BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */ + BTF_STRUCT_ENC(0, 2, 8), /* [2] */ + BTF_MEMBER_ENC(NAME_TBD, 1, 0), + BTF_MEMBER_ENC(NAME_TBD, 1, 32), + BTF_DECL_TAG_ENC(NAME_TBD, 2, -1), /* [3] */ + BTF_END_RAW, + }, + BTF_STR_SEC("\0m1\0m2\0tag"), + .map_type = BPF_MAP_TYPE_HASH, + .map_name = "tag_type_check_btf", + .key_size = 4, + .value_size = 8, + .key_type_id = 1, + .value_type_id = 3, + .max_entries = 1, + .map_create_err = true, +}, +{ .descr = "type_tag test #1", .raw_types = { BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */ diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_getset_retval.c b/tools/testing/selftests/bpf/prog_tests/cgroup_getset_retval.c index 4d2fa99273d8..2bb5773d6f99 100644 --- a/tools/testing/selftests/bpf/prog_tests/cgroup_getset_retval.c +++ b/tools/testing/selftests/bpf/prog_tests/cgroup_getset_retval.c @@ -25,6 +25,8 @@ static void test_setsockopt_set(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that sets EUNATCH, assert that * we actually get that error when we run setsockopt() */ @@ -59,6 +61,8 @@ static void test_setsockopt_set_and_get(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that sets EUNATCH, and one that gets the * previously set errno. Assert that we get the same errno back. */ @@ -100,6 +104,8 @@ static void test_setsockopt_default_zero(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that gets the previously set errno. * Assert that, without anything setting one, we get 0. */ @@ -134,6 +140,8 @@ static void test_setsockopt_default_zero_and_set(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that gets the previously set errno, and then * one that sets the errno to EUNATCH. Assert that the get does not * see EUNATCH set later, and does not prevent EUNATCH from being set. @@ -177,6 +185,8 @@ static void test_setsockopt_override(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that sets EUNATCH, then one that sets EISCONN, * and then one that gets the exported errno. Assert both the syscall * and the helper sees the last set errno. @@ -224,6 +234,8 @@ static void test_setsockopt_legacy_eperm(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that return a reject without setting errno * (legacy reject), and one that gets the errno. Assert that for * backward compatibility the syscall result in EPERM, and this @@ -268,6 +280,8 @@ static void test_setsockopt_legacy_no_override(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach setsockopt that sets EUNATCH, then one that return a reject * without setting errno, and then one that gets the exported errno. * Assert both the syscall and the helper's errno are unaffected by @@ -319,6 +333,8 @@ static void test_getsockopt_get(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach getsockopt that gets previously set errno. Assert that the * error from kernel is in both ctx_retval_value and retval_value. */ @@ -359,6 +375,8 @@ static void test_getsockopt_override(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach getsockopt that sets retval to -EISCONN. Assert that this * overrides the value from kernel. */ @@ -396,6 +414,8 @@ static void test_getsockopt_retval_sync(int cgroup_fd, int sock_fd) if (!ASSERT_OK_PTR(obj, "skel-load")) return; + obj->bss->page_size = sysconf(_SC_PAGESIZE); + /* Attach getsockopt that sets retval to -EISCONN, and one that clears * ctx retval. Assert that the clearing ctx retval is synced to helper * and clears any errors both from kernel and BPF.. diff --git a/tools/testing/selftests/bpf/prog_tests/check_mtu.c b/tools/testing/selftests/bpf/prog_tests/check_mtu.c index 5338d2ea0460..2a9a30650350 100644 --- a/tools/testing/selftests/bpf/prog_tests/check_mtu.c +++ b/tools/testing/selftests/bpf/prog_tests/check_mtu.c @@ -183,7 +183,7 @@ cleanup: void serial_test_check_mtu(void) { - __u32 mtu_lo; + int mtu_lo; if (test__start_subtest("bpf_check_mtu XDP-attach")) test_check_mtu_xdp_attach(); diff --git a/tools/testing/selftests/bpf/prog_tests/cpumask.c b/tools/testing/selftests/bpf/prog_tests/cpumask.c index cdf4acc18e4c..756ea8b590b6 100644 --- a/tools/testing/selftests/bpf/prog_tests/cpumask.c +++ b/tools/testing/selftests/bpf/prog_tests/cpumask.c @@ -10,6 +10,7 @@ static const char * const cpumask_success_testcases[] = { "test_set_clear_cpu", "test_setall_clear_cpu", "test_first_firstzero_cpu", + "test_firstand_nocpu", "test_test_and_set_clear", "test_and_or_xor", "test_intersects_subset", @@ -70,5 +71,6 @@ void test_cpumask(void) verify_success(cpumask_success_testcases[i]); } + RUN_TESTS(cpumask_success); RUN_TESTS(cpumask_failure); } diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c index d176c34a7d2e..7cfac53c0d58 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -20,6 +20,14 @@ static struct { {"test_ringbuf", SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", SETUP_SKB_PROG}, {"test_dynptr_skb_data", SETUP_SKB_PROG}, + {"test_adjust", SETUP_SYSCALL_SLEEP}, + {"test_adjust_err", SETUP_SYSCALL_SLEEP}, + {"test_zero_size_dynptr", SETUP_SYSCALL_SLEEP}, + {"test_dynptr_is_null", SETUP_SYSCALL_SLEEP}, + {"test_dynptr_is_rdonly", SETUP_SKB_PROG}, + {"test_dynptr_clone", SETUP_SKB_PROG}, + {"test_dynptr_skb_no_buff", SETUP_SKB_PROG}, + {"test_dynptr_skb_strcmp", SETUP_SKB_PROG}, }; static void verify_success(const char *prog_name, enum test_setup_type setup_type) diff --git a/tools/testing/selftests/bpf/prog_tests/fib_lookup.c b/tools/testing/selftests/bpf/prog_tests/fib_lookup.c index a1e712105811..2fd05649bad1 100644 --- a/tools/testing/selftests/bpf/prog_tests/fib_lookup.c +++ b/tools/testing/selftests/bpf/prog_tests/fib_lookup.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ +#include <linux/rtnetlink.h> #include <sys/types.h> #include <net/if.h> @@ -15,14 +16,23 @@ #define IPV4_IFACE_ADDR "10.0.0.254" #define IPV4_NUD_FAILED_ADDR "10.0.0.1" #define IPV4_NUD_STALE_ADDR "10.0.0.2" +#define IPV4_TBID_ADDR "172.0.0.254" +#define IPV4_TBID_NET "172.0.0.0" +#define IPV4_TBID_DST "172.0.0.2" +#define IPV6_TBID_ADDR "fd00::FFFF" +#define IPV6_TBID_NET "fd00::" +#define IPV6_TBID_DST "fd00::2" #define DMAC "11:11:11:11:11:11" #define DMAC_INIT { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } +#define DMAC2 "01:01:01:01:01:01" +#define DMAC_INIT2 { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, } struct fib_lookup_test { const char *desc; const char *daddr; int expected_ret; int lookup_flags; + __u32 tbid; __u8 dmac[6]; }; @@ -43,6 +53,22 @@ static const struct fib_lookup_test tests[] = { { .desc = "IPv4 skip neigh", .daddr = IPV4_NUD_FAILED_ADDR, .expected_ret = BPF_FIB_LKUP_RET_SUCCESS, .lookup_flags = BPF_FIB_LOOKUP_SKIP_NEIGH, }, + { .desc = "IPv4 TBID lookup failure", + .daddr = IPV4_TBID_DST, .expected_ret = BPF_FIB_LKUP_RET_NOT_FWDED, + .lookup_flags = BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_TBID, + .tbid = RT_TABLE_MAIN, }, + { .desc = "IPv4 TBID lookup success", + .daddr = IPV4_TBID_DST, .expected_ret = BPF_FIB_LKUP_RET_SUCCESS, + .lookup_flags = BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_TBID, .tbid = 100, + .dmac = DMAC_INIT2, }, + { .desc = "IPv6 TBID lookup failure", + .daddr = IPV6_TBID_DST, .expected_ret = BPF_FIB_LKUP_RET_NOT_FWDED, + .lookup_flags = BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_TBID, + .tbid = RT_TABLE_MAIN, }, + { .desc = "IPv6 TBID lookup success", + .daddr = IPV6_TBID_DST, .expected_ret = BPF_FIB_LKUP_RET_SUCCESS, + .lookup_flags = BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_TBID, .tbid = 100, + .dmac = DMAC_INIT2, }, }; static int ifindex; @@ -53,6 +79,7 @@ static int setup_netns(void) SYS(fail, "ip link add veth1 type veth peer name veth2"); SYS(fail, "ip link set dev veth1 up"); + SYS(fail, "ip link set dev veth2 up"); err = write_sysctl("/proc/sys/net/ipv4/neigh/veth1/gc_stale_time", "900"); if (!ASSERT_OK(err, "write_sysctl(net.ipv4.neigh.veth1.gc_stale_time)")) @@ -70,6 +97,17 @@ static int setup_netns(void) SYS(fail, "ip neigh add %s dev veth1 nud failed", IPV4_NUD_FAILED_ADDR); SYS(fail, "ip neigh add %s dev veth1 lladdr %s nud stale", IPV4_NUD_STALE_ADDR, DMAC); + /* Setup for tbid lookup tests */ + SYS(fail, "ip addr add %s/24 dev veth2", IPV4_TBID_ADDR); + SYS(fail, "ip route del %s/24 dev veth2", IPV4_TBID_NET); + SYS(fail, "ip route add table 100 %s/24 dev veth2", IPV4_TBID_NET); + SYS(fail, "ip neigh add %s dev veth2 lladdr %s nud stale", IPV4_TBID_DST, DMAC2); + + SYS(fail, "ip addr add %s/64 dev veth2", IPV6_TBID_ADDR); + SYS(fail, "ip -6 route del %s/64 dev veth2", IPV6_TBID_NET); + SYS(fail, "ip -6 route add table 100 %s/64 dev veth2", IPV6_TBID_NET); + SYS(fail, "ip neigh add %s dev veth2 lladdr %s nud stale", IPV6_TBID_DST, DMAC2); + err = write_sysctl("/proc/sys/net/ipv4/conf/veth1/forwarding", "1"); if (!ASSERT_OK(err, "write_sysctl(net.ipv4.conf.veth1.forwarding)")) goto fail; @@ -83,7 +121,7 @@ fail: return -1; } -static int set_lookup_params(struct bpf_fib_lookup *params, const char *daddr) +static int set_lookup_params(struct bpf_fib_lookup *params, const struct fib_lookup_test *test) { int ret; @@ -91,8 +129,9 @@ static int set_lookup_params(struct bpf_fib_lookup *params, const char *daddr) params->l4_protocol = IPPROTO_TCP; params->ifindex = ifindex; + params->tbid = test->tbid; - if (inet_pton(AF_INET6, daddr, params->ipv6_dst) == 1) { + if (inet_pton(AF_INET6, test->daddr, params->ipv6_dst) == 1) { params->family = AF_INET6; ret = inet_pton(AF_INET6, IPV6_IFACE_ADDR, params->ipv6_src); if (!ASSERT_EQ(ret, 1, "inet_pton(IPV6_IFACE_ADDR)")) @@ -100,7 +139,7 @@ static int set_lookup_params(struct bpf_fib_lookup *params, const char *daddr) return 0; } - ret = inet_pton(AF_INET, daddr, ¶ms->ipv4_dst); + ret = inet_pton(AF_INET, test->daddr, ¶ms->ipv4_dst); if (!ASSERT_EQ(ret, 1, "convert IP[46] address")) return -1; params->family = AF_INET; @@ -154,13 +193,12 @@ void test_fib_lookup(void) fib_params = &skel->bss->fib_params; for (i = 0; i < ARRAY_SIZE(tests); i++) { - printf("Testing %s\n", tests[i].desc); + printf("Testing %s ", tests[i].desc); - if (set_lookup_params(fib_params, tests[i].daddr)) + if (set_lookup_params(fib_params, &tests[i])) continue; skel->bss->fib_lookup_ret = -1; - skel->bss->lookup_flags = BPF_FIB_LOOKUP_OUTPUT | - tests[i].lookup_flags; + skel->bss->lookup_flags = tests[i].lookup_flags; err = bpf_prog_test_run_opts(prog_fd, &run_opts); if (!ASSERT_OK(err, "bpf_prog_test_run_opts")) @@ -175,7 +213,14 @@ void test_fib_lookup(void) mac_str(expected, tests[i].dmac); mac_str(actual, fib_params->dmac); - printf("dmac expected %s actual %s\n", expected, actual); + printf("dmac expected %s actual %s ", expected, actual); + } + + // ensure tbid is zero'd out after fib lookup. + if (tests[i].lookup_flags & BPF_FIB_LOOKUP_DIRECT) { + if (!ASSERT_EQ(skel->bss->fib_params.tbid, 0, + "expected fib_params.tbid to be zero")) + goto fail; } } diff --git a/tools/testing/selftests/bpf/prog_tests/global_map_resize.c b/tools/testing/selftests/bpf/prog_tests/global_map_resize.c new file mode 100644 index 000000000000..fd41425d2e5c --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/global_map_resize.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ +#include <errno.h> +#include <sys/syscall.h> +#include <unistd.h> +#include "test_global_map_resize.skel.h" +#include "test_progs.h" + +static void run_prog_bss_array_sum(void) +{ + (void)syscall(__NR_getpid); +} + +static void run_prog_data_array_sum(void) +{ + (void)syscall(__NR_getuid); +} + +static void global_map_resize_bss_subtest(void) +{ + int err; + struct test_global_map_resize *skel; + struct bpf_map *map; + const __u32 desired_sz = sizeof(skel->bss->sum) + sysconf(_SC_PAGE_SIZE) * 2; + size_t array_len, actual_sz; + + skel = test_global_map_resize__open(); + if (!ASSERT_OK_PTR(skel, "test_global_map_resize__open")) + goto teardown; + + /* set some initial value before resizing. + * it is expected this non-zero value will be preserved + * while resizing. + */ + skel->bss->array[0] = 1; + + /* resize map value and verify the new size */ + map = skel->maps.bss; + err = bpf_map__set_value_size(map, desired_sz); + if (!ASSERT_OK(err, "bpf_map__set_value_size")) + goto teardown; + if (!ASSERT_EQ(bpf_map__value_size(map), desired_sz, "resize")) + goto teardown; + + /* set the expected number of elements based on the resized array */ + array_len = (desired_sz - sizeof(skel->bss->sum)) / sizeof(skel->bss->array[0]); + if (!ASSERT_GT(array_len, 1, "array_len")) + goto teardown; + + skel->bss = bpf_map__initial_value(skel->maps.bss, &actual_sz); + if (!ASSERT_OK_PTR(skel->bss, "bpf_map__initial_value (ptr)")) + goto teardown; + if (!ASSERT_EQ(actual_sz, desired_sz, "bpf_map__initial_value (size)")) + goto teardown; + + /* fill the newly resized array with ones, + * skipping the first element which was previously set + */ + for (int i = 1; i < array_len; i++) + skel->bss->array[i] = 1; + + /* set global const values before loading */ + skel->rodata->pid = getpid(); + skel->rodata->bss_array_len = array_len; + skel->rodata->data_array_len = 1; + + err = test_global_map_resize__load(skel); + if (!ASSERT_OK(err, "test_global_map_resize__load")) + goto teardown; + err = test_global_map_resize__attach(skel); + if (!ASSERT_OK(err, "test_global_map_resize__attach")) + goto teardown; + + /* run the bpf program which will sum the contents of the array. + * since the array was filled with ones,verify the sum equals array_len + */ + run_prog_bss_array_sum(); + if (!ASSERT_EQ(skel->bss->sum, array_len, "sum")) + goto teardown; + +teardown: + test_global_map_resize__destroy(skel); +} + +static void global_map_resize_data_subtest(void) +{ + int err; + struct test_global_map_resize *skel; + struct bpf_map *map; + const __u32 desired_sz = sysconf(_SC_PAGE_SIZE) * 2; + size_t array_len, actual_sz; + + skel = test_global_map_resize__open(); + if (!ASSERT_OK_PTR(skel, "test_global_map_resize__open")) + goto teardown; + + /* set some initial value before resizing. + * it is expected this non-zero value will be preserved + * while resizing. + */ + skel->data_custom->my_array[0] = 1; + + /* resize map value and verify the new size */ + map = skel->maps.data_custom; + err = bpf_map__set_value_size(map, desired_sz); + if (!ASSERT_OK(err, "bpf_map__set_value_size")) + goto teardown; + if (!ASSERT_EQ(bpf_map__value_size(map), desired_sz, "resize")) + goto teardown; + + /* set the expected number of elements based on the resized array */ + array_len = (desired_sz - sizeof(skel->bss->sum)) / sizeof(skel->data_custom->my_array[0]); + if (!ASSERT_GT(array_len, 1, "array_len")) + goto teardown; + + skel->data_custom = bpf_map__initial_value(skel->maps.data_custom, &actual_sz); + if (!ASSERT_OK_PTR(skel->data_custom, "bpf_map__initial_value (ptr)")) + goto teardown; + if (!ASSERT_EQ(actual_sz, desired_sz, "bpf_map__initial_value (size)")) + goto teardown; + + /* fill the newly resized array with ones, + * skipping the first element which was previously set + */ + for (int i = 1; i < array_len; i++) + skel->data_custom->my_array[i] = 1; + + /* set global const values before loading */ + skel->rodata->pid = getpid(); + skel->rodata->bss_array_len = 1; + skel->rodata->data_array_len = array_len; + + err = test_global_map_resize__load(skel); + if (!ASSERT_OK(err, "test_global_map_resize__load")) + goto teardown; + err = test_global_map_resize__attach(skel); + if (!ASSERT_OK(err, "test_global_map_resize__attach")) + goto teardown; + + /* run the bpf program which will sum the contents of the array. + * since the array was filled with ones,verify the sum equals array_len + */ + run_prog_data_array_sum(); + if (!ASSERT_EQ(skel->bss->sum, array_len, "sum")) + goto teardown; + +teardown: + test_global_map_resize__destroy(skel); +} + +static void global_map_resize_invalid_subtest(void) +{ + int err; + struct test_global_map_resize *skel; + struct bpf_map *map; + __u32 element_sz, desired_sz; + + skel = test_global_map_resize__open(); + if (!ASSERT_OK_PTR(skel, "test_global_map_resize__open")) + return; + + /* attempt to resize a global datasec map to size + * which does NOT align with array + */ + map = skel->maps.data_custom; + if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, ".data.custom initial btf")) + goto teardown; + /* set desired size a fraction of element size beyond an aligned size */ + element_sz = sizeof(skel->data_custom->my_array[0]); + desired_sz = element_sz + element_sz / 2; + /* confirm desired size does NOT align with array */ + if (!ASSERT_NEQ(desired_sz % element_sz, 0, "my_array alignment")) + goto teardown; + err = bpf_map__set_value_size(map, desired_sz); + /* confirm resize is OK but BTF info is cleared */ + if (!ASSERT_OK(err, ".data.custom bpf_map__set_value_size") || + !ASSERT_EQ(bpf_map__btf_key_type_id(map), 0, ".data.custom clear btf key") || + !ASSERT_EQ(bpf_map__btf_value_type_id(map), 0, ".data.custom clear btf val")) + goto teardown; + + /* attempt to resize a global datasec map whose only var is NOT an array */ + map = skel->maps.data_non_array; + if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, ".data.non_array initial btf")) + goto teardown; + /* set desired size to arbitrary value */ + desired_sz = 1024; + err = bpf_map__set_value_size(map, desired_sz); + /* confirm resize is OK but BTF info is cleared */ + if (!ASSERT_OK(err, ".data.non_array bpf_map__set_value_size") || + !ASSERT_EQ(bpf_map__btf_key_type_id(map), 0, ".data.non_array clear btf key") || + !ASSERT_EQ(bpf_map__btf_value_type_id(map), 0, ".data.non_array clear btf val")) + goto teardown; + + /* attempt to resize a global datasec map + * whose last var is NOT an array + */ + map = skel->maps.data_array_not_last; + if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, ".data.array_not_last initial btf")) + goto teardown; + /* set desired size to a multiple of element size */ + element_sz = sizeof(skel->data_array_not_last->my_array_first[0]); + desired_sz = element_sz * 8; + /* confirm desired size aligns with array */ + if (!ASSERT_EQ(desired_sz % element_sz, 0, "my_array_first alignment")) + goto teardown; + err = bpf_map__set_value_size(map, desired_sz); + /* confirm resize is OK but BTF info is cleared */ + if (!ASSERT_OK(err, ".data.array_not_last bpf_map__set_value_size") || + !ASSERT_EQ(bpf_map__btf_key_type_id(map), 0, ".data.array_not_last clear btf key") || + !ASSERT_EQ(bpf_map__btf_value_type_id(map), 0, ".data.array_not_last clear btf val")) + goto teardown; + +teardown: + test_global_map_resize__destroy(skel); +} + +void test_global_map_resize(void) +{ + if (test__start_subtest("global_map_resize_bss")) + global_map_resize_bss_subtest(); + + if (test__start_subtest("global_map_resize_data")) + global_map_resize_data_subtest(); + + if (test__start_subtest("global_map_resize_invalid")) + global_map_resize_invalid_subtest(); +} diff --git a/tools/testing/selftests/bpf/prog_tests/inner_array_lookup.c b/tools/testing/selftests/bpf/prog_tests/inner_array_lookup.c new file mode 100644 index 000000000000..9ab4cd195108 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/inner_array_lookup.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <test_progs.h> + +#include "inner_array_lookup.skel.h" + +void test_inner_array_lookup(void) +{ + int map1_fd, err; + int key = 3; + int val = 1; + struct inner_array_lookup *skel; + + skel = inner_array_lookup__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_load_skeleton")) + return; + + err = inner_array_lookup__attach(skel); + if (!ASSERT_OK(err, "skeleton_attach")) + goto cleanup; + + map1_fd = bpf_map__fd(skel->maps.inner_map1); + bpf_map_update_elem(map1_fd, &key, &val, 0); + + /* Probe should have set the element at index 3 to 2 */ + bpf_map_lookup_elem(map1_fd, &key, &val); + ASSERT_EQ(val, 2, "value_is_2"); + +cleanup: + inner_array_lookup__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/prog_tests/module_attach.c b/tools/testing/selftests/bpf/prog_tests/module_attach.c index 7fc01ff490db..f53d658ed080 100644 --- a/tools/testing/selftests/bpf/prog_tests/module_attach.c +++ b/tools/testing/selftests/bpf/prog_tests/module_attach.c @@ -4,6 +4,7 @@ #include <test_progs.h> #include <stdbool.h> #include "test_module_attach.skel.h" +#include "testing_helpers.h" static int duration; @@ -32,11 +33,6 @@ static int trigger_module_test_writable(int *val) return 0; } -static int delete_module(const char *name, int flags) -{ - return syscall(__NR_delete_module, name, flags); -} - void test_module_attach(void) { const int READ_SZ = 456; @@ -93,21 +89,21 @@ void test_module_attach(void) if (!ASSERT_OK_PTR(link, "attach_fentry")) goto cleanup; - ASSERT_ERR(delete_module("bpf_testmod", 0), "delete_module"); + ASSERT_ERR(unload_bpf_testmod(false), "unload_bpf_testmod"); bpf_link__destroy(link); link = bpf_program__attach(skel->progs.handle_fexit); if (!ASSERT_OK_PTR(link, "attach_fexit")) goto cleanup; - ASSERT_ERR(delete_module("bpf_testmod", 0), "delete_module"); + ASSERT_ERR(unload_bpf_testmod(false), "unload_bpf_testmod"); bpf_link__destroy(link); link = bpf_program__attach(skel->progs.kprobe_multi); if (!ASSERT_OK_PTR(link, "attach_kprobe_multi")) goto cleanup; - ASSERT_ERR(delete_module("bpf_testmod", 0), "delete_module"); + ASSERT_ERR(unload_bpf_testmod(false), "unload_bpf_testmod"); bpf_link__destroy(link); cleanup: diff --git a/tools/testing/selftests/bpf/prog_tests/netcnt.c b/tools/testing/selftests/bpf/prog_tests/netcnt.c index d3915c58d0e1..c3333edd029f 100644 --- a/tools/testing/selftests/bpf/prog_tests/netcnt.c +++ b/tools/testing/selftests/bpf/prog_tests/netcnt.c @@ -67,12 +67,12 @@ void serial_test_netcnt(void) } /* No packets should be lost */ - ASSERT_EQ(packets, 10000, "packets"); + ASSERT_GE(packets, 10000, "packets"); /* Let's check that bytes counter matches the number of packets * multiplied by the size of ipv6 ICMP packet. */ - ASSERT_EQ(bytes, packets * 104, "bytes"); + ASSERT_GE(bytes, packets * 104, "bytes"); err: if (cg_fd != -1) diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c new file mode 100644 index 000000000000..b0583309a94e --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <test_progs.h> +#include <bpf/bpf_endian.h> + +#include "sock_destroy_prog.skel.h" +#include "sock_destroy_prog_fail.skel.h" +#include "network_helpers.h" + +#define TEST_NS "sock_destroy_netns" + +static void start_iter_sockets(struct bpf_program *prog) +{ + struct bpf_link *link; + char buf[50] = {}; + int iter_fd, len; + + link = bpf_program__attach_iter(prog, NULL); + if (!ASSERT_OK_PTR(link, "attach_iter")) + return; + + iter_fd = bpf_iter_create(bpf_link__fd(link)); + if (!ASSERT_GE(iter_fd, 0, "create_iter")) + goto free_link; + + while ((len = read(iter_fd, buf, sizeof(buf))) > 0) + ; + ASSERT_GE(len, 0, "read"); + + close(iter_fd); + +free_link: + bpf_link__destroy(link); +} + +static void test_tcp_client(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_GE(accept_serv, 0, "serv accept")) + goto cleanup; + + n = send(clien, "t", 1, 0); + if (!ASSERT_EQ(n, 1, "client send")) + goto cleanup; + + /* Run iterator program that destroys connected client sockets. */ + start_iter_sockets(skel->progs.iter_tcp6_client); + + n = send(clien, "t", 1, 0); + if (!ASSERT_LT(n, 0, "client_send on destroyed socket")) + goto cleanup; + ASSERT_EQ(errno, ECONNABORTED, "error code on destroyed socket"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + +static void test_tcp_server(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n, serv_port; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + serv_port = get_socket_local_port(serv); + if (!ASSERT_GE(serv_port, 0, "get_sock_local_port")) + goto cleanup; + skel->bss->serv_port = (__be16) serv_port; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_GE(accept_serv, 0, "serv accept")) + goto cleanup; + + n = send(clien, "t", 1, 0); + if (!ASSERT_EQ(n, 1, "client send")) + goto cleanup; + + /* Run iterator program that destroys server sockets. */ + start_iter_sockets(skel->progs.iter_tcp6_server); + + n = send(clien, "t", 1, 0); + if (!ASSERT_LT(n, 0, "client_send on destroyed socket")) + goto cleanup; + ASSERT_EQ(errno, ECONNRESET, "error code on destroyed socket"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + +static void test_udp_client(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, n = 0; + + serv = start_server(AF_INET6, SOCK_DGRAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + n = send(clien, "t", 1, 0); + if (!ASSERT_EQ(n, 1, "client send")) + goto cleanup; + + /* Run iterator program that destroys sockets. */ + start_iter_sockets(skel->progs.iter_udp6_client); + + n = send(clien, "t", 1, 0); + if (!ASSERT_LT(n, 0, "client_send on destroyed socket")) + goto cleanup; + /* UDP sockets have an overriding error code after they are disconnected, + * so we don't check for ECONNABORTED error code. + */ + +cleanup: + if (clien != -1) + close(clien); + if (serv != -1) + close(serv); +} + +static void test_udp_server(struct sock_destroy_prog *skel) +{ + int *listen_fds = NULL, n, i, serv_port; + unsigned int num_listens = 5; + char buf[1]; + + /* Start reuseport servers. */ + listen_fds = start_reuseport_server(AF_INET6, SOCK_DGRAM, + "::1", 0, 0, num_listens); + if (!ASSERT_OK_PTR(listen_fds, "start_reuseport_server")) + goto cleanup; + serv_port = get_socket_local_port(listen_fds[0]); + if (!ASSERT_GE(serv_port, 0, "get_sock_local_port")) + goto cleanup; + skel->bss->serv_port = (__be16) serv_port; + + /* Run iterator program that destroys server sockets. */ + start_iter_sockets(skel->progs.iter_udp6_server); + + for (i = 0; i < num_listens; ++i) { + n = read(listen_fds[i], buf, sizeof(buf)); + if (!ASSERT_EQ(n, -1, "read") || + !ASSERT_EQ(errno, ECONNABORTED, "error code on destroyed socket")) + break; + } + ASSERT_EQ(i, num_listens, "server socket"); + +cleanup: + free_fds(listen_fds, num_listens); +} + +void test_sock_destroy(void) +{ + struct sock_destroy_prog *skel; + struct nstoken *nstoken = NULL; + int cgroup_fd; + + skel = sock_destroy_prog__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open")) + return; + + cgroup_fd = test__join_cgroup("/sock_destroy"); + if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup")) + goto cleanup; + + skel->links.sock_connect = bpf_program__attach_cgroup( + skel->progs.sock_connect, cgroup_fd); + if (!ASSERT_OK_PTR(skel->links.sock_connect, "prog_attach")) + goto cleanup; + + SYS(cleanup, "ip netns add %s", TEST_NS); + SYS(cleanup, "ip -net %s link set dev lo up", TEST_NS); + + nstoken = open_netns(TEST_NS); + if (!ASSERT_OK_PTR(nstoken, "open_netns")) + goto cleanup; + + if (test__start_subtest("tcp_client")) + test_tcp_client(skel); + if (test__start_subtest("tcp_server")) + test_tcp_server(skel); + if (test__start_subtest("udp_client")) + test_udp_client(skel); + if (test__start_subtest("udp_server")) + test_udp_server(skel); + + RUN_TESTS(sock_destroy_prog_fail); + +cleanup: + if (nstoken) + close_netns(nstoken); + SYS_NOFAIL("ip netns del " TEST_NS " &> /dev/null"); + if (cgroup_fd >= 0) + close(cgroup_fd); + sock_destroy_prog__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/prog_tests/sockopt.c b/tools/testing/selftests/bpf/prog_tests/sockopt.c index aa4debf62fc6..9e6a5e3ed4de 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockopt.c +++ b/tools/testing/selftests/bpf/prog_tests/sockopt.c @@ -5,10 +5,15 @@ static char bpf_log_buf[4096]; static bool verbose; +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + enum sockopt_test_error { OK = 0, DENY_LOAD, DENY_ATTACH, + EOPNOTSUPP_GETSOCKOPT, EPERM_GETSOCKOPT, EFAULT_GETSOCKOPT, EPERM_SETSOCKOPT, @@ -273,10 +278,31 @@ static struct sockopt_test { .error = EFAULT_GETSOCKOPT, }, { - .descr = "getsockopt: deny arbitrary ctx->retval", + .descr = "getsockopt: ignore >PAGE_SIZE optlen", .insns = { - /* ctx->retval = 123 */ - BPF_MOV64_IMM(BPF_REG_0, 123), + /* write 0xFF to the first optval byte */ + + /* r6 = ctx->optval */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, + offsetof(struct bpf_sockopt, optval)), + /* r2 = ctx->optval */ + BPF_MOV64_REG(BPF_REG_2, BPF_REG_6), + /* r6 = ctx->optval + 1 */ + BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1), + + /* r7 = ctx->optval_end */ + BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_1, + offsetof(struct bpf_sockopt, optval_end)), + + /* if (ctx->optval + 1 <= ctx->optval_end) { */ + BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1), + /* ctx->optval[0] = 0xF0 */ + BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0xFF), + /* } */ + + /* retval changes are ignored */ + /* ctx->retval = 5 */ + BPF_MOV64_IMM(BPF_REG_0, 5), BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0, offsetof(struct bpf_sockopt, retval)), @@ -287,9 +313,11 @@ static struct sockopt_test { .attach_type = BPF_CGROUP_GETSOCKOPT, .expected_attach_type = BPF_CGROUP_GETSOCKOPT, - .get_optlen = 64, - - .error = EFAULT_GETSOCKOPT, + .get_level = 1234, + .get_optname = 5678, + .get_optval = {}, /* the changes are ignored */ + .get_optlen = PAGE_SIZE + 1, + .error = EOPNOTSUPP_GETSOCKOPT, }, { .descr = "getsockopt: support smaller ctx->optlen", @@ -649,6 +677,45 @@ static struct sockopt_test { .error = EFAULT_SETSOCKOPT, }, { + .descr = "setsockopt: ignore >PAGE_SIZE optlen", + .insns = { + /* write 0xFF to the first optval byte */ + + /* r6 = ctx->optval */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, + offsetof(struct bpf_sockopt, optval)), + /* r2 = ctx->optval */ + BPF_MOV64_REG(BPF_REG_2, BPF_REG_6), + /* r6 = ctx->optval + 1 */ + BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1), + + /* r7 = ctx->optval_end */ + BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_1, + offsetof(struct bpf_sockopt, optval_end)), + + /* if (ctx->optval + 1 <= ctx->optval_end) { */ + BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1), + /* ctx->optval[0] = 0xF0 */ + BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0xF0), + /* } */ + + BPF_MOV64_IMM(BPF_REG_0, 1), + BPF_EXIT_INSN(), + }, + .attach_type = BPF_CGROUP_SETSOCKOPT, + .expected_attach_type = BPF_CGROUP_SETSOCKOPT, + + .set_level = SOL_IP, + .set_optname = IP_TOS, + .set_optval = {}, + .set_optlen = PAGE_SIZE + 1, + + .get_level = SOL_IP, + .get_optname = IP_TOS, + .get_optval = {}, /* the changes are ignored */ + .get_optlen = 4, + }, + { .descr = "setsockopt: allow changing ctx->optlen within bounds", .insns = { /* r6 = ctx->optval */ @@ -906,6 +973,13 @@ static int run_test(int cgroup_fd, struct sockopt_test *test) } if (test->set_optlen) { + if (test->set_optlen >= PAGE_SIZE) { + int num_pages = test->set_optlen / PAGE_SIZE; + int remainder = test->set_optlen % PAGE_SIZE; + + test->set_optlen = num_pages * sysconf(_SC_PAGESIZE) + remainder; + } + err = setsockopt(sock_fd, test->set_level, test->set_optname, test->set_optval, test->set_optlen); if (err) { @@ -921,7 +995,15 @@ static int run_test(int cgroup_fd, struct sockopt_test *test) } if (test->get_optlen) { + if (test->get_optlen >= PAGE_SIZE) { + int num_pages = test->get_optlen / PAGE_SIZE; + int remainder = test->get_optlen % PAGE_SIZE; + + test->get_optlen = num_pages * sysconf(_SC_PAGESIZE) + remainder; + } + optval = malloc(test->get_optlen); + memset(optval, 0, test->get_optlen); socklen_t optlen = test->get_optlen; socklen_t expected_get_optlen = test->get_optlen_ret ?: test->get_optlen; @@ -929,6 +1011,8 @@ static int run_test(int cgroup_fd, struct sockopt_test *test) err = getsockopt(sock_fd, test->get_level, test->get_optname, optval, &optlen); if (err) { + if (errno == EOPNOTSUPP && test->error == EOPNOTSUPP_GETSOCKOPT) + goto free_optval; if (errno == EPERM && test->error == EPERM_GETSOCKOPT) goto free_optval; if (errno == EFAULT && test->error == EFAULT_GETSOCKOPT) @@ -976,7 +1060,9 @@ void test_sockopt(void) return; for (i = 0; i < ARRAY_SIZE(tests); i++) { - test__start_subtest(tests[i].descr); + if (!test__start_subtest(tests[i].descr)) + continue; + ASSERT_OK(run_test(cgroup_fd, &tests[i]), tests[i].descr); } diff --git a/tools/testing/selftests/bpf/prog_tests/sockopt_inherit.c b/tools/testing/selftests/bpf/prog_tests/sockopt_inherit.c index 60c17a8e2789..917f486db826 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockopt_inherit.c +++ b/tools/testing/selftests/bpf/prog_tests/sockopt_inherit.c @@ -2,6 +2,8 @@ #include <test_progs.h> #include "cgroup_helpers.h" +#include "sockopt_inherit.skel.h" + #define SOL_CUSTOM 0xdeadbeef #define CUSTOM_INHERIT1 0 #define CUSTOM_INHERIT2 1 @@ -132,58 +134,30 @@ static int start_server(void) return fd; } -static int prog_attach(struct bpf_object *obj, int cgroup_fd, const char *title, - const char *prog_name) -{ - enum bpf_attach_type attach_type; - enum bpf_prog_type prog_type; - struct bpf_program *prog; - int err; - - err = libbpf_prog_type_by_name(title, &prog_type, &attach_type); - if (err) { - log_err("Failed to deduct types for %s BPF program", prog_name); - return -1; - } - - prog = bpf_object__find_program_by_name(obj, prog_name); - if (!prog) { - log_err("Failed to find %s BPF program", prog_name); - return -1; - } - - err = bpf_prog_attach(bpf_program__fd(prog), cgroup_fd, - attach_type, 0); - if (err) { - log_err("Failed to attach %s BPF program", prog_name); - return -1; - } - - return 0; -} - static void run_test(int cgroup_fd) { + struct bpf_link *link_getsockopt = NULL; + struct bpf_link *link_setsockopt = NULL; int server_fd = -1, client_fd; - struct bpf_object *obj; + struct sockopt_inherit *obj; void *server_err; pthread_t tid; int err; - obj = bpf_object__open_file("sockopt_inherit.bpf.o", NULL); - if (!ASSERT_OK_PTR(obj, "obj_open")) + obj = sockopt_inherit__open_and_load(); + if (!ASSERT_OK_PTR(obj, "skel-load")) return; - err = bpf_object__load(obj); - if (!ASSERT_OK(err, "obj_load")) - goto close_bpf_object; + obj->bss->page_size = sysconf(_SC_PAGESIZE); - err = prog_attach(obj, cgroup_fd, "cgroup/getsockopt", "_getsockopt"); - if (!ASSERT_OK(err, "prog_attach _getsockopt")) + link_getsockopt = bpf_program__attach_cgroup(obj->progs._getsockopt, + cgroup_fd); + if (!ASSERT_OK_PTR(link_getsockopt, "cg-attach-getsockopt")) goto close_bpf_object; - err = prog_attach(obj, cgroup_fd, "cgroup/setsockopt", "_setsockopt"); - if (!ASSERT_OK(err, "prog_attach _setsockopt")) + link_setsockopt = bpf_program__attach_cgroup(obj->progs._setsockopt, + cgroup_fd); + if (!ASSERT_OK_PTR(link_setsockopt, "cg-attach-setsockopt")) goto close_bpf_object; server_fd = start_server(); @@ -217,7 +191,10 @@ static void run_test(int cgroup_fd) close_server_fd: close(server_fd); close_bpf_object: - bpf_object__close(obj); + bpf_link__destroy(link_getsockopt); + bpf_link__destroy(link_setsockopt); + + sockopt_inherit__destroy(obj); } void test_sockopt_inherit(void) diff --git a/tools/testing/selftests/bpf/prog_tests/sockopt_multi.c b/tools/testing/selftests/bpf/prog_tests/sockopt_multi.c index 7f5659349011..759bbb6f8c5f 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockopt_multi.c +++ b/tools/testing/selftests/bpf/prog_tests/sockopt_multi.c @@ -2,61 +2,13 @@ #include <test_progs.h> #include "cgroup_helpers.h" -static int prog_attach(struct bpf_object *obj, int cgroup_fd, const char *title, const char *name) -{ - enum bpf_attach_type attach_type; - enum bpf_prog_type prog_type; - struct bpf_program *prog; - int err; - - err = libbpf_prog_type_by_name(title, &prog_type, &attach_type); - if (err) { - log_err("Failed to deduct types for %s BPF program", title); - return -1; - } - - prog = bpf_object__find_program_by_name(obj, name); - if (!prog) { - log_err("Failed to find %s BPF program", name); - return -1; - } - - err = bpf_prog_attach(bpf_program__fd(prog), cgroup_fd, - attach_type, BPF_F_ALLOW_MULTI); - if (err) { - log_err("Failed to attach %s BPF program", name); - return -1; - } - - return 0; -} +#include "sockopt_multi.skel.h" -static int prog_detach(struct bpf_object *obj, int cgroup_fd, const char *title, const char *name) -{ - enum bpf_attach_type attach_type; - enum bpf_prog_type prog_type; - struct bpf_program *prog; - int err; - - err = libbpf_prog_type_by_name(title, &prog_type, &attach_type); - if (err) - return -1; - - prog = bpf_object__find_program_by_name(obj, name); - if (!prog) - return -1; - - err = bpf_prog_detach2(bpf_program__fd(prog), cgroup_fd, - attach_type); - if (err) - return -1; - - return 0; -} - -static int run_getsockopt_test(struct bpf_object *obj, int cg_parent, +static int run_getsockopt_test(struct sockopt_multi *obj, int cg_parent, int cg_child, int sock_fd) { + struct bpf_link *link_parent = NULL; + struct bpf_link *link_child = NULL; socklen_t optlen; __u8 buf; int err; @@ -89,8 +41,9 @@ static int run_getsockopt_test(struct bpf_object *obj, int cg_parent, * - child: 0x80 -> 0x90 */ - err = prog_attach(obj, cg_child, "cgroup/getsockopt", "_getsockopt_child"); - if (err) + link_child = bpf_program__attach_cgroup(obj->progs._getsockopt_child, + cg_child); + if (!ASSERT_OK_PTR(link_child, "cg-attach-getsockopt_child")) goto detach; buf = 0x00; @@ -113,8 +66,9 @@ static int run_getsockopt_test(struct bpf_object *obj, int cg_parent, * - parent: 0x90 -> 0xA0 */ - err = prog_attach(obj, cg_parent, "cgroup/getsockopt", "_getsockopt_parent"); - if (err) + link_parent = bpf_program__attach_cgroup(obj->progs._getsockopt_parent, + cg_parent); + if (!ASSERT_OK_PTR(link_parent, "cg-attach-getsockopt_parent")) goto detach; buf = 0x00; @@ -157,11 +111,8 @@ static int run_getsockopt_test(struct bpf_object *obj, int cg_parent, * - parent: unexpected 0x40, EPERM */ - err = prog_detach(obj, cg_child, "cgroup/getsockopt", "_getsockopt_child"); - if (err) { - log_err("Failed to detach child program"); - goto detach; - } + bpf_link__destroy(link_child); + link_child = NULL; buf = 0x00; optlen = 1; @@ -198,15 +149,17 @@ static int run_getsockopt_test(struct bpf_object *obj, int cg_parent, } detach: - prog_detach(obj, cg_child, "cgroup/getsockopt", "_getsockopt_child"); - prog_detach(obj, cg_parent, "cgroup/getsockopt", "_getsockopt_parent"); + bpf_link__destroy(link_child); + bpf_link__destroy(link_parent); return err; } -static int run_setsockopt_test(struct bpf_object *obj, int cg_parent, +static int run_setsockopt_test(struct sockopt_multi *obj, int cg_parent, int cg_child, int sock_fd) { + struct bpf_link *link_parent = NULL; + struct bpf_link *link_child = NULL; socklen_t optlen; __u8 buf; int err; @@ -236,8 +189,9 @@ static int run_setsockopt_test(struct bpf_object *obj, int cg_parent, /* Attach child program and make sure it adds 0x10. */ - err = prog_attach(obj, cg_child, "cgroup/setsockopt", "_setsockopt"); - if (err) + link_child = bpf_program__attach_cgroup(obj->progs._setsockopt, + cg_child); + if (!ASSERT_OK_PTR(link_child, "cg-attach-setsockopt_child")) goto detach; buf = 0x80; @@ -263,8 +217,9 @@ static int run_setsockopt_test(struct bpf_object *obj, int cg_parent, /* Attach parent program and make sure it adds another 0x10. */ - err = prog_attach(obj, cg_parent, "cgroup/setsockopt", "_setsockopt"); - if (err) + link_parent = bpf_program__attach_cgroup(obj->progs._setsockopt, + cg_parent); + if (!ASSERT_OK_PTR(link_parent, "cg-attach-setsockopt_parent")) goto detach; buf = 0x80; @@ -289,8 +244,8 @@ static int run_setsockopt_test(struct bpf_object *obj, int cg_parent, } detach: - prog_detach(obj, cg_child, "cgroup/setsockopt", "_setsockopt"); - prog_detach(obj, cg_parent, "cgroup/setsockopt", "_setsockopt"); + bpf_link__destroy(link_child); + bpf_link__destroy(link_parent); return err; } @@ -298,9 +253,8 @@ detach: void test_sockopt_multi(void) { int cg_parent = -1, cg_child = -1; - struct bpf_object *obj = NULL; + struct sockopt_multi *obj = NULL; int sock_fd = -1; - int err = -1; cg_parent = test__join_cgroup("/parent"); if (!ASSERT_GE(cg_parent, 0, "join_cgroup /parent")) @@ -310,13 +264,11 @@ void test_sockopt_multi(void) if (!ASSERT_GE(cg_child, 0, "join_cgroup /parent/child")) goto out; - obj = bpf_object__open_file("sockopt_multi.bpf.o", NULL); - if (!ASSERT_OK_PTR(obj, "obj_load")) + obj = sockopt_multi__open_and_load(); + if (!ASSERT_OK_PTR(obj, "skel-load")) goto out; - err = bpf_object__load(obj); - if (!ASSERT_OK(err, "obj_load")) - goto out; + obj->bss->page_size = sysconf(_SC_PAGESIZE); sock_fd = socket(AF_INET, SOCK_STREAM, 0); if (!ASSERT_GE(sock_fd, 0, "socket")) @@ -327,7 +279,7 @@ void test_sockopt_multi(void) out: close(sock_fd); - bpf_object__close(obj); + sockopt_multi__destroy(obj); close(cg_child); close(cg_parent); } diff --git a/tools/testing/selftests/bpf/prog_tests/sockopt_qos_to_cc.c b/tools/testing/selftests/bpf/prog_tests/sockopt_qos_to_cc.c index 6b53b3cb8dad..6b2d300e9fd4 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockopt_qos_to_cc.c +++ b/tools/testing/selftests/bpf/prog_tests/sockopt_qos_to_cc.c @@ -42,6 +42,8 @@ void test_sockopt_qos_to_cc(void) if (!ASSERT_OK_PTR(skel, "skel")) goto done; + skel->bss->page_size = sysconf(_SC_PAGESIZE); + sock_fd = socket(AF_INET6, SOCK_STREAM, 0); if (!ASSERT_GE(sock_fd, 0, "v6 socket open")) goto done; diff --git a/tools/testing/selftests/bpf/prog_tests/sockopt_sk.c b/tools/testing/selftests/bpf/prog_tests/sockopt_sk.c index 4512dd808c33..05d0e07da394 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockopt_sk.c +++ b/tools/testing/selftests/bpf/prog_tests/sockopt_sk.c @@ -209,7 +209,7 @@ static int getsetsockopt(void) err, errno); goto err; } - ASSERT_EQ(optlen, 4, "Unexpected NETLINK_LIST_MEMBERSHIPS value"); + ASSERT_EQ(optlen, 8, "Unexpected NETLINK_LIST_MEMBERSHIPS value"); free(big_buf); close(fd); diff --git a/tools/testing/selftests/bpf/prog_tests/subprogs_extable.c b/tools/testing/selftests/bpf/prog_tests/subprogs_extable.c new file mode 100644 index 000000000000..3afd9f775f68 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/subprogs_extable.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <test_progs.h> +#include "test_subprogs_extable.skel.h" + +void test_subprogs_extable(void) +{ + const int read_sz = 456; + struct test_subprogs_extable *skel; + int err; + + skel = test_subprogs_extable__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + return; + + err = test_subprogs_extable__attach(skel); + if (!ASSERT_OK(err, "skel_attach")) + goto cleanup; + + /* trigger tracepoint */ + ASSERT_OK(trigger_module_test_read(read_sz), "trigger_read"); + + ASSERT_NEQ(skel->bss->triggered, 0, "verify at least one program ran"); + + test_subprogs_extable__detach(skel); + +cleanup: + test_subprogs_extable__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/prog_tests/task_under_cgroup.c b/tools/testing/selftests/bpf/prog_tests/task_under_cgroup.c new file mode 100644 index 000000000000..4224727fb364 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/task_under_cgroup.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Bytedance */ + +#include <sys/syscall.h> +#include <test_progs.h> +#include <cgroup_helpers.h> +#include "test_task_under_cgroup.skel.h" + +#define FOO "/foo" + +void test_task_under_cgroup(void) +{ + struct test_task_under_cgroup *skel; + int ret, foo; + pid_t pid; + + foo = test__join_cgroup(FOO); + if (!ASSERT_OK(foo < 0, "cgroup_join_foo")) + return; + + skel = test_task_under_cgroup__open(); + if (!ASSERT_OK_PTR(skel, "test_task_under_cgroup__open")) + goto cleanup; + + skel->rodata->local_pid = getpid(); + skel->bss->remote_pid = getpid(); + skel->rodata->cgid = get_cgroup_id(FOO); + + ret = test_task_under_cgroup__load(skel); + if (!ASSERT_OK(ret, "test_task_under_cgroup__load")) + goto cleanup; + + ret = test_task_under_cgroup__attach(skel); + if (!ASSERT_OK(ret, "test_task_under_cgroup__attach")) + goto cleanup; + + pid = fork(); + if (pid == 0) + exit(0); + + ret = (pid == -1); + if (ASSERT_OK(ret, "fork process")) + wait(NULL); + + test_task_under_cgroup__detach(skel); + + ASSERT_NEQ(skel->bss->remote_pid, skel->rodata->local_pid, + "test task_under_cgroup"); + +cleanup: + test_task_under_cgroup__destroy(skel); + close(foo); +} diff --git a/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c b/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c index 8383a99f610f..0adf8d9475cb 100644 --- a/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c +++ b/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c @@ -171,7 +171,11 @@ static void test_unpriv_bpf_disabled_negative(struct test_unpriv_bpf_disabled *s prog_insns, prog_insn_cnt, &load_opts), -EPERM, "prog_load_fails"); - for (i = BPF_MAP_TYPE_HASH; i <= BPF_MAP_TYPE_BLOOM_FILTER; i++) + /* some map types require particular correct parameters which could be + * sanity-checked before enforcing -EPERM, so only validate that + * the simple ARRAY and HASH maps are failing with -EPERM + */ + for (i = BPF_MAP_TYPE_HASH; i <= BPF_MAP_TYPE_ARRAY; i++) ASSERT_EQ(bpf_map_create(i, NULL, sizeof(int), sizeof(int), 1, NULL), -EPERM, "map_create_fails"); diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 2497716ee379..070a13833c3f 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -50,11 +50,13 @@ #include "verifier_regalloc.skel.h" #include "verifier_ringbuf.skel.h" #include "verifier_runtime_jit.skel.h" +#include "verifier_scalar_ids.skel.h" #include "verifier_search_pruning.skel.h" #include "verifier_sock.skel.h" #include "verifier_spill_fill.skel.h" #include "verifier_spin_lock.skel.h" #include "verifier_stack_ptr.skel.h" +#include "verifier_subprog_precision.skel.h" #include "verifier_subreg.skel.h" #include "verifier_uninit.skel.h" #include "verifier_unpriv.skel.h" @@ -149,11 +151,13 @@ void test_verifier_ref_tracking(void) { RUN(verifier_ref_tracking); } void test_verifier_regalloc(void) { RUN(verifier_regalloc); } void test_verifier_ringbuf(void) { RUN(verifier_ringbuf); } void test_verifier_runtime_jit(void) { RUN(verifier_runtime_jit); } +void test_verifier_scalar_ids(void) { RUN(verifier_scalar_ids); } void test_verifier_search_pruning(void) { RUN(verifier_search_pruning); } void test_verifier_sock(void) { RUN(verifier_sock); } void test_verifier_spill_fill(void) { RUN(verifier_spill_fill); } void test_verifier_spin_lock(void) { RUN(verifier_spin_lock); } void test_verifier_stack_ptr(void) { RUN(verifier_stack_ptr); } +void test_verifier_subprog_precision(void) { RUN(verifier_subprog_precision); } void test_verifier_subreg(void) { RUN(verifier_subreg); } void test_verifier_uninit(void) { RUN(verifier_uninit); } void test_verifier_unpriv(void) { RUN(verifier_unpriv); } diff --git a/tools/testing/selftests/bpf/prog_tests/vrf_socket_lookup.c b/tools/testing/selftests/bpf/prog_tests/vrf_socket_lookup.c new file mode 100644 index 000000000000..2a5e207edad6 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/vrf_socket_lookup.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +/* + * Topology: + * --------- + * NS0 namespace | NS1 namespace + * | + * +--------------+ | +--------------+ + * | veth01 |----------| veth10 | + * | 172.16.1.100 | | | 172.16.1.200 | + * | bpf | | +--------------+ + * +--------------+ | + * server(UDP/TCP) | + * +-------------------+ | + * | vrf1 | | + * | +--------------+ | | +--------------+ + * | | veth02 |----------| veth20 | + * | | 172.16.2.100 | | | | 172.16.2.200 | + * | | bpf | | | +--------------+ + * | +--------------+ | | + * | server(UDP/TCP) | | + * +-------------------+ | + * + * Test flow + * ----------- + * The tests verifies that socket lookup via TC is VRF aware: + * 1) Creates two veth pairs between NS0 and NS1: + * a) veth01 <-> veth10 outside the VRF + * b) veth02 <-> veth20 in the VRF + * 2) Attaches to veth01 and veth02 a program that calls: + * a) bpf_skc_lookup_tcp() with TCP and tcp_skc is true + * b) bpf_sk_lookup_tcp() with TCP and tcp_skc is false + * c) bpf_sk_lookup_udp() with UDP + * The program stores the lookup result in bss->lookup_status. + * 3) Creates a socket TCP/UDP server in/outside the VRF. + * 4) The test expects lookup_status to be: + * a) 0 from device in VRF to server outside VRF + * b) 0 from device outside VRF to server in VRF + * c) 1 from device in VRF to server in VRF + * d) 1 from device outside VRF to server outside VRF + */ + +#include <net/if.h> + +#include "test_progs.h" +#include "network_helpers.h" +#include "vrf_socket_lookup.skel.h" + +#define NS0 "vrf_socket_lookup_0" +#define NS1 "vrf_socket_lookup_1" + +#define IP4_ADDR_VETH01 "172.16.1.100" +#define IP4_ADDR_VETH10 "172.16.1.200" +#define IP4_ADDR_VETH02 "172.16.2.100" +#define IP4_ADDR_VETH20 "172.16.2.200" + +#define NON_VRF_PORT 5000 +#define IN_VRF_PORT 5001 + +#define TIMEOUT_MS 3000 + +static int make_socket(int sotype, const char *ip, int port, + struct sockaddr_storage *addr) +{ + int err, fd; + + err = make_sockaddr(AF_INET, ip, port, addr, NULL); + if (!ASSERT_OK(err, "make_address")) + return -1; + + fd = socket(AF_INET, sotype, 0); + if (!ASSERT_GE(fd, 0, "socket")) + return -1; + + if (!ASSERT_OK(settimeo(fd, TIMEOUT_MS), "settimeo")) + goto fail; + + return fd; +fail: + close(fd); + return -1; +} + +static int make_server(int sotype, const char *ip, int port, const char *ifname) +{ + int err, fd = -1; + + fd = start_server(AF_INET, sotype, ip, port, TIMEOUT_MS); + if (!ASSERT_GE(fd, 0, "start_server")) + return -1; + + if (ifname) { + err = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + ifname, strlen(ifname) + 1); + if (!ASSERT_OK(err, "setsockopt(SO_BINDTODEVICE)")) + goto fail; + } + + return fd; +fail: + close(fd); + return -1; +} + +static int attach_progs(char *ifname, int tc_prog_fd, int xdp_prog_fd) +{ + LIBBPF_OPTS(bpf_tc_hook, hook, .attach_point = BPF_TC_INGRESS); + LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1, + .prog_fd = tc_prog_fd); + int ret, ifindex; + + ifindex = if_nametoindex(ifname); + if (!ASSERT_NEQ(ifindex, 0, "if_nametoindex")) + return -1; + hook.ifindex = ifindex; + + ret = bpf_tc_hook_create(&hook); + if (!ASSERT_OK(ret, "bpf_tc_hook_create")) + return ret; + + ret = bpf_tc_attach(&hook, &opts); + if (!ASSERT_OK(ret, "bpf_tc_attach")) { + bpf_tc_hook_destroy(&hook); + return ret; + } + ret = bpf_xdp_attach(ifindex, xdp_prog_fd, 0, NULL); + if (!ASSERT_OK(ret, "bpf_xdp_attach")) { + bpf_tc_hook_destroy(&hook); + return ret; + } + + return 0; +} + +static void cleanup(void) +{ + SYS_NOFAIL("test -f /var/run/netns/" NS0 " && ip netns delete " + NS0); + SYS_NOFAIL("test -f /var/run/netns/" NS1 " && ip netns delete " + NS1); +} + +static int setup(struct vrf_socket_lookup *skel) +{ + int tc_prog_fd, xdp_prog_fd, ret = 0; + struct nstoken *nstoken = NULL; + + SYS(fail, "ip netns add " NS0); + SYS(fail, "ip netns add " NS1); + + /* NS0 <-> NS1 [veth01 <-> veth10] */ + SYS(fail, "ip link add veth01 netns " NS0 " type veth peer name veth10" + " netns " NS1); + SYS(fail, "ip -net " NS0 " addr add " IP4_ADDR_VETH01 "/24 dev veth01"); + SYS(fail, "ip -net " NS0 " link set dev veth01 up"); + SYS(fail, "ip -net " NS1 " addr add " IP4_ADDR_VETH10 "/24 dev veth10"); + SYS(fail, "ip -net " NS1 " link set dev veth10 up"); + + /* NS0 <-> NS1 [veth02 <-> veth20] */ + SYS(fail, "ip link add veth02 netns " NS0 " type veth peer name veth20" + " netns " NS1); + SYS(fail, "ip -net " NS0 " addr add " IP4_ADDR_VETH02 "/24 dev veth02"); + SYS(fail, "ip -net " NS0 " link set dev veth02 up"); + SYS(fail, "ip -net " NS1 " addr add " IP4_ADDR_VETH20 "/24 dev veth20"); + SYS(fail, "ip -net " NS1 " link set dev veth20 up"); + + /* veth02 -> vrf1 */ + SYS(fail, "ip -net " NS0 " link add vrf1 type vrf table 11"); + SYS(fail, "ip -net " NS0 " route add vrf vrf1 unreachable default" + " metric 4278198272"); + SYS(fail, "ip -net " NS0 " link set vrf1 alias vrf"); + SYS(fail, "ip -net " NS0 " link set vrf1 up"); + SYS(fail, "ip -net " NS0 " link set veth02 master vrf1"); + + /* Attach TC and XDP progs to veth devices in NS0 */ + nstoken = open_netns(NS0); + if (!ASSERT_OK_PTR(nstoken, "setns " NS0)) + goto fail; + tc_prog_fd = bpf_program__fd(skel->progs.tc_socket_lookup); + if (!ASSERT_GE(tc_prog_fd, 0, "bpf_program__tc_fd")) + goto fail; + xdp_prog_fd = bpf_program__fd(skel->progs.xdp_socket_lookup); + if (!ASSERT_GE(xdp_prog_fd, 0, "bpf_program__xdp_fd")) + goto fail; + + if (attach_progs("veth01", tc_prog_fd, xdp_prog_fd)) + goto fail; + + if (attach_progs("veth02", tc_prog_fd, xdp_prog_fd)) + goto fail; + + goto close; +fail: + ret = -1; +close: + if (nstoken) + close_netns(nstoken); + return ret; +} + +static int test_lookup(struct vrf_socket_lookup *skel, int sotype, + const char *ip, int port, bool test_xdp, bool tcp_skc, + int lookup_status_exp) +{ + static const char msg[] = "Hello Server"; + struct sockaddr_storage addr = {}; + int fd, ret = 0; + + fd = make_socket(sotype, ip, port, &addr); + if (fd < 0) + return -1; + + skel->bss->test_xdp = test_xdp; + skel->bss->tcp_skc = tcp_skc; + skel->bss->lookup_status = -1; + + if (sotype == SOCK_STREAM) + connect(fd, (void *)&addr, sizeof(struct sockaddr_in)); + else + sendto(fd, msg, sizeof(msg), 0, (void *)&addr, + sizeof(struct sockaddr_in)); + + if (!ASSERT_EQ(skel->bss->lookup_status, lookup_status_exp, + "lookup_status")) + goto fail; + + goto close; + +fail: + ret = -1; +close: + close(fd); + return ret; +} + +static void _test_vrf_socket_lookup(struct vrf_socket_lookup *skel, int sotype, + bool test_xdp, bool tcp_skc) +{ + int in_vrf_server = -1, non_vrf_server = -1; + struct nstoken *nstoken = NULL; + + nstoken = open_netns(NS0); + if (!ASSERT_OK_PTR(nstoken, "setns " NS0)) + goto done; + + /* Open sockets in and outside VRF */ + non_vrf_server = make_server(sotype, "0.0.0.0", NON_VRF_PORT, NULL); + if (!ASSERT_GE(non_vrf_server, 0, "make_server__outside_vrf_fd")) + goto done; + + in_vrf_server = make_server(sotype, "0.0.0.0", IN_VRF_PORT, "veth02"); + if (!ASSERT_GE(in_vrf_server, 0, "make_server__in_vrf_fd")) + goto done; + + /* Perform test from NS1 */ + close_netns(nstoken); + nstoken = open_netns(NS1); + if (!ASSERT_OK_PTR(nstoken, "setns " NS1)) + goto done; + + if (!ASSERT_OK(test_lookup(skel, sotype, IP4_ADDR_VETH02, NON_VRF_PORT, + test_xdp, tcp_skc, 0), "in_to_out")) + goto done; + if (!ASSERT_OK(test_lookup(skel, sotype, IP4_ADDR_VETH02, IN_VRF_PORT, + test_xdp, tcp_skc, 1), "in_to_in")) + goto done; + if (!ASSERT_OK(test_lookup(skel, sotype, IP4_ADDR_VETH01, NON_VRF_PORT, + test_xdp, tcp_skc, 1), "out_to_out")) + goto done; + if (!ASSERT_OK(test_lookup(skel, sotype, IP4_ADDR_VETH01, IN_VRF_PORT, + test_xdp, tcp_skc, 0), "out_to_in")) + goto done; + +done: + if (non_vrf_server >= 0) + close(non_vrf_server); + if (in_vrf_server >= 0) + close(in_vrf_server); + if (nstoken) + close_netns(nstoken); +} + +void test_vrf_socket_lookup(void) +{ + struct vrf_socket_lookup *skel; + + cleanup(); + + skel = vrf_socket_lookup__open_and_load(); + if (!ASSERT_OK_PTR(skel, "vrf_socket_lookup__open_and_load")) + return; + + if (!ASSERT_OK(setup(skel), "setup")) + goto done; + + if (test__start_subtest("tc_socket_lookup_tcp")) + _test_vrf_socket_lookup(skel, SOCK_STREAM, false, false); + if (test__start_subtest("tc_socket_lookup_tcp_skc")) + _test_vrf_socket_lookup(skel, SOCK_STREAM, false, false); + if (test__start_subtest("tc_socket_lookup_udp")) + _test_vrf_socket_lookup(skel, SOCK_STREAM, false, false); + if (test__start_subtest("xdp_socket_lookup_tcp")) + _test_vrf_socket_lookup(skel, SOCK_STREAM, true, false); + if (test__start_subtest("xdp_socket_lookup_tcp_skc")) + _test_vrf_socket_lookup(skel, SOCK_STREAM, true, false); + if (test__start_subtest("xdp_socket_lookup_udp")) + _test_vrf_socket_lookup(skel, SOCK_STREAM, true, false); + +done: + vrf_socket_lookup__destroy(skel); + cleanup(); +} diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_bonding.c b/tools/testing/selftests/bpf/prog_tests/xdp_bonding.c index d19f79048ff6..c3b45745cbcc 100644 --- a/tools/testing/selftests/bpf/prog_tests/xdp_bonding.c +++ b/tools/testing/selftests/bpf/prog_tests/xdp_bonding.c @@ -18,6 +18,7 @@ #include <linux/if_bonding.h> #include <linux/limits.h> #include <linux/udp.h> +#include <uapi/linux/netdev.h> #include "xdp_dummy.skel.h" #include "xdp_redirect_multi_kern.skel.h" @@ -492,6 +493,123 @@ out: system("ip link del bond_nest2"); } +static void test_xdp_bonding_features(struct skeletons *skeletons) +{ + LIBBPF_OPTS(bpf_xdp_query_opts, query_opts); + int bond_idx, veth1_idx, err; + struct bpf_link *link = NULL; + + if (!ASSERT_OK(system("ip link add bond type bond"), "add bond")) + goto out; + + bond_idx = if_nametoindex("bond"); + if (!ASSERT_GE(bond_idx, 0, "if_nametoindex bond")) + goto out; + + /* query default xdp-feature for bond device */ + err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); + if (!ASSERT_OK(err, "bond bpf_xdp_query")) + goto out; + + if (!ASSERT_EQ(query_opts.feature_flags, NETDEV_XDP_ACT_MASK, + "bond query_opts.feature_flags")) + goto out; + + if (!ASSERT_OK(system("ip link add veth0 type veth peer name veth1"), + "add veth{0,1} pair")) + goto out; + + if (!ASSERT_OK(system("ip link add veth2 type veth peer name veth3"), + "add veth{2,3} pair")) + goto out; + + if (!ASSERT_OK(system("ip link set veth0 master bond"), + "add veth0 to master bond")) + goto out; + + /* xdp-feature for bond device should be obtained from the single slave + * device (veth0) + */ + err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); + if (!ASSERT_OK(err, "bond bpf_xdp_query")) + goto out; + + if (!ASSERT_EQ(query_opts.feature_flags, + NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | + NETDEV_XDP_ACT_RX_SG, + "bond query_opts.feature_flags")) + goto out; + + veth1_idx = if_nametoindex("veth1"); + if (!ASSERT_GE(veth1_idx, 0, "if_nametoindex veth1")) + goto out; + + link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, + veth1_idx); + if (!ASSERT_OK_PTR(link, "attach program to veth1")) + goto out; + + /* xdp-feature for veth0 are changed */ + err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); + if (!ASSERT_OK(err, "bond bpf_xdp_query")) + goto out; + + if (!ASSERT_EQ(query_opts.feature_flags, + NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | + NETDEV_XDP_ACT_RX_SG | NETDEV_XDP_ACT_NDO_XMIT | + NETDEV_XDP_ACT_NDO_XMIT_SG, + "bond query_opts.feature_flags")) + goto out; + + if (!ASSERT_OK(system("ip link set veth2 master bond"), + "add veth2 to master bond")) + goto out; + + err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); + if (!ASSERT_OK(err, "bond bpf_xdp_query")) + goto out; + + /* xdp-feature for bond device should be set to the most restrict + * value obtained from attached slave devices (veth0 and veth2) + */ + if (!ASSERT_EQ(query_opts.feature_flags, + NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | + NETDEV_XDP_ACT_RX_SG, + "bond query_opts.feature_flags")) + goto out; + + if (!ASSERT_OK(system("ip link set veth2 nomaster"), + "del veth2 to master bond")) + goto out; + + err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); + if (!ASSERT_OK(err, "bond bpf_xdp_query")) + goto out; + + if (!ASSERT_EQ(query_opts.feature_flags, + NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | + NETDEV_XDP_ACT_RX_SG | NETDEV_XDP_ACT_NDO_XMIT | + NETDEV_XDP_ACT_NDO_XMIT_SG, + "bond query_opts.feature_flags")) + goto out; + + if (!ASSERT_OK(system("ip link set veth0 nomaster"), + "del veth0 to master bond")) + goto out; + + err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); + if (!ASSERT_OK(err, "bond bpf_xdp_query")) + goto out; + + ASSERT_EQ(query_opts.feature_flags, NETDEV_XDP_ACT_MASK, + "bond query_opts.feature_flags"); +out: + bpf_link__destroy(link); + system("ip link del veth0"); + system("ip link del veth2"); + system("ip link del bond"); +} + static int libbpf_debug_print(enum libbpf_print_level level, const char *format, va_list args) { @@ -546,6 +664,9 @@ void serial_test_xdp_bonding(void) if (test__start_subtest("xdp_bonding_nested")) test_xdp_bonding_nested(&skeletons); + if (test__start_subtest("xdp_bonding_features")) + test_xdp_bonding_features(&skeletons); + for (i = 0; i < ARRAY_SIZE(bond_test_cases); i++) { struct bond_test_case *test_case = &bond_test_cases[i]; diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_ksym.c b/tools/testing/selftests/bpf/progs/bpf_iter_ksym.c index 5ddcc46fd886..521267818f4d 100644 --- a/tools/testing/selftests/bpf/progs/bpf_iter_ksym.c +++ b/tools/testing/selftests/bpf/progs/bpf_iter_ksym.c @@ -59,9 +59,7 @@ int dump_ksym(struct bpf_iter__ksym *ctx) } else { BPF_SEQ_PRINTF(seq, "0x%llx %c %s ", value, type, iter->name); } - if (!iter->pos_arch_end || iter->pos_arch_end > iter->pos) - BPF_SEQ_PRINTF(seq, "CORE "); - else if (!iter->pos_mod_end || iter->pos_mod_end > iter->pos) + if (!iter->pos_mod_end || iter->pos_mod_end > iter->pos) BPF_SEQ_PRINTF(seq, "MOD "); else if (!iter->pos_ftrace_mod_end || iter->pos_ftrace_mod_end > iter->pos) BPF_SEQ_PRINTF(seq, "FTRACE_MOD "); diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h index d3c1217ba79a..38a57a2e70db 100644 --- a/tools/testing/selftests/bpf/progs/bpf_misc.h +++ b/tools/testing/selftests/bpf/progs/bpf_misc.h @@ -86,6 +86,10 @@ #define POINTER_VALUE 0xcafe4all #define TEST_DATA_LEN 64 +#ifndef __used +#define __used __attribute__((used)) +#endif + #if defined(__TARGET_ARCH_x86) #define SYSCALL_WRAPPER 1 #define SYS_PREFIX "__x64_" diff --git a/tools/testing/selftests/bpf/progs/cb_refs.c b/tools/testing/selftests/bpf/progs/cb_refs.c index 50f95ec61165..76d661b20e87 100644 --- a/tools/testing/selftests/bpf/progs/cb_refs.c +++ b/tools/testing/selftests/bpf/progs/cb_refs.c @@ -2,6 +2,7 @@ #include <vmlinux.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_helpers.h> +#include "../bpf_testmod/bpf_testmod_kfunc.h" struct map_value { struct prog_test_ref_kfunc __kptr *ptr; @@ -14,9 +15,6 @@ struct { __uint(max_entries, 16); } array_map SEC(".maps"); -extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; - static __noinline int cb1(void *map, void *key, void *value, void *ctx) { void *p = *(void **)ctx; diff --git a/tools/testing/selftests/bpf/progs/cgroup_getset_retval_getsockopt.c b/tools/testing/selftests/bpf/progs/cgroup_getset_retval_getsockopt.c index b2a409e6382a..932b8ecd4ae3 100644 --- a/tools/testing/selftests/bpf/progs/cgroup_getset_retval_getsockopt.c +++ b/tools/testing/selftests/bpf/progs/cgroup_getset_retval_getsockopt.c @@ -12,6 +12,7 @@ __u32 invocations = 0; __u32 assertion_error = 0; __u32 retval_value = 0; __u32 ctx_retval_value = 0; +__u32 page_size = 0; SEC("cgroup/getsockopt") int get_retval(struct bpf_sockopt *ctx) @@ -20,6 +21,10 @@ int get_retval(struct bpf_sockopt *ctx) ctx_retval_value = ctx->retval; __sync_fetch_and_add(&invocations, 1); + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } @@ -31,6 +36,10 @@ int set_eisconn(struct bpf_sockopt *ctx) if (bpf_set_retval(-EISCONN)) assertion_error = 1; + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } @@ -41,5 +50,9 @@ int clear_retval(struct bpf_sockopt *ctx) ctx->retval = 0; + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } diff --git a/tools/testing/selftests/bpf/progs/cgroup_getset_retval_setsockopt.c b/tools/testing/selftests/bpf/progs/cgroup_getset_retval_setsockopt.c index d6e5903e06ba..b7fa8804e19d 100644 --- a/tools/testing/selftests/bpf/progs/cgroup_getset_retval_setsockopt.c +++ b/tools/testing/selftests/bpf/progs/cgroup_getset_retval_setsockopt.c @@ -11,6 +11,7 @@ __u32 invocations = 0; __u32 assertion_error = 0; __u32 retval_value = 0; +__u32 page_size = 0; SEC("cgroup/setsockopt") int get_retval(struct bpf_sockopt *ctx) @@ -18,6 +19,10 @@ int get_retval(struct bpf_sockopt *ctx) retval_value = bpf_get_retval(); __sync_fetch_and_add(&invocations, 1); + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } @@ -29,6 +34,10 @@ int set_eunatch(struct bpf_sockopt *ctx) if (bpf_set_retval(-EUNATCH)) assertion_error = 1; + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 0; } @@ -40,6 +49,10 @@ int set_eisconn(struct bpf_sockopt *ctx) if (bpf_set_retval(-EISCONN)) assertion_error = 1; + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 0; } @@ -48,5 +61,9 @@ int legacy_eperm(struct bpf_sockopt *ctx) { __sync_fetch_and_add(&invocations, 1); + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 0; } diff --git a/tools/testing/selftests/bpf/progs/cpumask_common.h b/tools/testing/selftests/bpf/progs/cpumask_common.h index 0c5b785a93e4..b15c588ace15 100644 --- a/tools/testing/selftests/bpf/progs/cpumask_common.h +++ b/tools/testing/selftests/bpf/progs/cpumask_common.h @@ -28,6 +28,8 @@ void bpf_cpumask_release(struct bpf_cpumask *cpumask) __ksym; struct bpf_cpumask *bpf_cpumask_acquire(struct bpf_cpumask *cpumask) __ksym; u32 bpf_cpumask_first(const struct cpumask *cpumask) __ksym; u32 bpf_cpumask_first_zero(const struct cpumask *cpumask) __ksym; +u32 bpf_cpumask_first_and(const struct cpumask *src1, + const struct cpumask *src2) __ksym; void bpf_cpumask_set_cpu(u32 cpu, struct bpf_cpumask *cpumask) __ksym; void bpf_cpumask_clear_cpu(u32 cpu, struct bpf_cpumask *cpumask) __ksym; bool bpf_cpumask_test_cpu(u32 cpu, const struct cpumask *cpumask) __ksym; @@ -50,8 +52,8 @@ bool bpf_cpumask_subset(const struct cpumask *src1, const struct cpumask *src2) bool bpf_cpumask_empty(const struct cpumask *cpumask) __ksym; bool bpf_cpumask_full(const struct cpumask *cpumask) __ksym; void bpf_cpumask_copy(struct bpf_cpumask *dst, const struct cpumask *src) __ksym; -u32 bpf_cpumask_any(const struct cpumask *src) __ksym; -u32 bpf_cpumask_any_and(const struct cpumask *src1, const struct cpumask *src2) __ksym; +u32 bpf_cpumask_any_distribute(const struct cpumask *src) __ksym; +u32 bpf_cpumask_any_and_distribute(const struct cpumask *src1, const struct cpumask *src2) __ksym; void bpf_rcu_read_lock(void) __ksym; void bpf_rcu_read_unlock(void) __ksym; diff --git a/tools/testing/selftests/bpf/progs/cpumask_success.c b/tools/testing/selftests/bpf/progs/cpumask_success.c index 2fcdd7f68ac7..674a63424dee 100644 --- a/tools/testing/selftests/bpf/progs/cpumask_success.c +++ b/tools/testing/selftests/bpf/progs/cpumask_success.c @@ -5,6 +5,7 @@ #include <bpf/bpf_tracing.h> #include <bpf/bpf_helpers.h> +#include "bpf_misc.h" #include "cpumask_common.h" char _license[] SEC("license") = "GPL"; @@ -175,6 +176,38 @@ release_exit: } SEC("tp_btf/task_newtask") +int BPF_PROG(test_firstand_nocpu, struct task_struct *task, u64 clone_flags) +{ + struct bpf_cpumask *mask1, *mask2; + u32 first; + + if (!is_test_task()) + return 0; + + mask1 = create_cpumask(); + if (!mask1) + return 0; + + mask2 = create_cpumask(); + if (!mask2) + goto release_exit; + + bpf_cpumask_set_cpu(0, mask1); + bpf_cpumask_set_cpu(1, mask2); + + first = bpf_cpumask_first_and(cast(mask1), cast(mask2)); + if (first <= 1) + err = 3; + +release_exit: + if (mask1) + bpf_cpumask_release(mask1); + if (mask2) + bpf_cpumask_release(mask2); + return 0; +} + +SEC("tp_btf/task_newtask") int BPF_PROG(test_test_and_set_clear, struct task_struct *task, u64 clone_flags) { struct bpf_cpumask *cpumask; @@ -311,13 +344,13 @@ int BPF_PROG(test_copy_any_anyand, struct task_struct *task, u64 clone_flags) bpf_cpumask_set_cpu(1, mask2); bpf_cpumask_or(dst1, cast(mask1), cast(mask2)); - cpu = bpf_cpumask_any(cast(mask1)); + cpu = bpf_cpumask_any_distribute(cast(mask1)); if (cpu != 0) { err = 6; goto release_exit; } - cpu = bpf_cpumask_any(cast(dst2)); + cpu = bpf_cpumask_any_distribute(cast(dst2)); if (cpu < nr_cpus) { err = 7; goto release_exit; @@ -329,13 +362,13 @@ int BPF_PROG(test_copy_any_anyand, struct task_struct *task, u64 clone_flags) goto release_exit; } - cpu = bpf_cpumask_any(cast(dst2)); + cpu = bpf_cpumask_any_distribute(cast(dst2)); if (cpu > 1) { err = 9; goto release_exit; } - cpu = bpf_cpumask_any_and(cast(mask1), cast(mask2)); + cpu = bpf_cpumask_any_and_distribute(cast(mask1), cast(mask2)); if (cpu < nr_cpus) { err = 10; goto release_exit; @@ -426,3 +459,26 @@ int BPF_PROG(test_global_mask_rcu, struct task_struct *task, u64 clone_flags) return 0; } + +SEC("tp_btf/task_newtask") +__success +int BPF_PROG(test_refcount_null_tracking, struct task_struct *task, u64 clone_flags) +{ + struct bpf_cpumask *mask1, *mask2; + + mask1 = bpf_cpumask_create(); + mask2 = bpf_cpumask_create(); + + if (!mask1 || !mask2) + goto free_masks_return; + + bpf_cpumask_test_cpu(0, (const struct cpumask *)mask1); + bpf_cpumask_test_cpu(0, (const struct cpumask *)mask2); + +free_masks_return: + if (mask1) + bpf_cpumask_release(mask1); + if (mask2) + bpf_cpumask_release(mask2); + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index 759eb5c245cd..7ce7e827d5f0 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -3,6 +3,7 @@ #include <errno.h> #include <string.h> +#include <stdbool.h> #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> @@ -1378,3 +1379,310 @@ int invalid_slice_rdwr_rdonly(struct __sk_buff *skb) return 0; } + +/* bpf_dynptr_adjust can only be called on initialized dynptrs */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #1") +int dynptr_adjust_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_adjust(&ptr, 1, 2); + + return 0; +} + +/* bpf_dynptr_is_null can only be called on initialized dynptrs */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #1") +int dynptr_is_null_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_is_null(&ptr); + + return 0; +} + +/* bpf_dynptr_is_rdonly can only be called on initialized dynptrs */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #1") +int dynptr_is_rdonly_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_is_rdonly(&ptr); + + return 0; +} + +/* bpf_dynptr_size can only be called on initialized dynptrs */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #1") +int dynptr_size_invalid(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_size(&ptr); + + return 0; +} + +/* Only initialized dynptrs can be cloned */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #1") +int clone_invalid1(void *ctx) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + + /* this should fail */ + bpf_dynptr_clone(&ptr1, &ptr2); + + return 0; +} + +/* Can't overwrite an existing dynptr when cloning */ +SEC("?xdp") +__failure __msg("cannot overwrite referenced dynptr") +int clone_invalid2(struct xdp_md *xdp) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr clone; + + bpf_dynptr_from_xdp(xdp, 0, &ptr1); + + bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &clone); + + /* this should fail */ + bpf_dynptr_clone(&ptr1, &clone); + + bpf_ringbuf_submit_dynptr(&clone, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate its clones */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #3") +int clone_invalidate1(void *ctx) +{ + struct bpf_dynptr clone; + struct bpf_dynptr ptr; + char read_data[64]; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone); + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &clone, 0, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate its parent */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #3") +int clone_invalidate2(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + char read_data[64]; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone); + + bpf_ringbuf_submit_dynptr(&clone, 0); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate its siblings */ +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as arg #3") +int clone_invalidate3(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone1; + struct bpf_dynptr clone2; + char read_data[64]; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone1); + + bpf_dynptr_clone(&ptr, &clone2); + + bpf_ringbuf_submit_dynptr(&clone2, 0); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &clone1, 0, 0); + + return 0; +} + +/* Invalidating a dynptr should invalidate any data slices + * of its clones + */ +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int clone_invalidate4(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + int *data; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone); + data = bpf_dynptr_data(&clone, 0, sizeof(val)); + if (!data) + return 0; + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + /* this should fail */ + *data = 123; + + return 0; +} + +/* Invalidating a dynptr should invalidate any data slices + * of its parent + */ +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int clone_invalidate5(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone; + int *data; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + data = bpf_dynptr_data(&ptr, 0, sizeof(val)); + if (!data) + return 0; + + bpf_dynptr_clone(&ptr, &clone); + + bpf_ringbuf_submit_dynptr(&clone, 0); + + /* this should fail */ + *data = 123; + + return 0; +} + +/* Invalidating a dynptr should invalidate any data slices + * of its sibling + */ +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int clone_invalidate6(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr clone1; + struct bpf_dynptr clone2; + int *data; + + bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone1); + + bpf_dynptr_clone(&ptr, &clone2); + + data = bpf_dynptr_data(&clone1, 0, sizeof(val)); + if (!data) + return 0; + + bpf_ringbuf_submit_dynptr(&clone2, 0); + + /* this should fail */ + *data = 123; + + return 0; +} + +/* A skb clone's data slices should be invalid anytime packet data changes */ +SEC("?tc") +__failure __msg("invalid mem access 'scalar'") +int clone_skb_packet_data(struct __sk_buff *skb) +{ + char buffer[sizeof(__u32)] = {}; + struct bpf_dynptr clone; + struct bpf_dynptr ptr; + __u32 *data; + + bpf_dynptr_from_skb(skb, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone); + data = bpf_dynptr_slice_rdwr(&clone, 0, buffer, sizeof(buffer)); + if (!data) + return XDP_DROP; + + if (bpf_skb_pull_data(skb, skb->len)) + return SK_DROP; + + /* this should fail */ + *data = 123; + + return 0; +} + +/* A xdp clone's data slices should be invalid anytime packet data changes */ +SEC("?xdp") +__failure __msg("invalid mem access 'scalar'") +int clone_xdp_packet_data(struct xdp_md *xdp) +{ + char buffer[sizeof(__u32)] = {}; + struct bpf_dynptr clone; + struct bpf_dynptr ptr; + struct ethhdr *hdr; + __u32 *data; + + bpf_dynptr_from_xdp(xdp, 0, &ptr); + + bpf_dynptr_clone(&ptr, &clone); + data = bpf_dynptr_slice_rdwr(&clone, 0, buffer, sizeof(buffer)); + if (!data) + return XDP_DROP; + + if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(*hdr))) + return XDP_DROP; + + /* this should fail */ + *data = 123; + + return 0; +} + +/* Buffers that are provided must be sufficiently long */ +SEC("?cgroup_skb/egress") +__failure __msg("memory, len pair leads to invalid memory access") +int test_dynptr_skb_small_buff(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + char buffer[8] = {}; + __u64 *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* This may return NULL. SKB may require a buffer */ + data = bpf_dynptr_slice(&ptr, 0, buffer, 9); + + return !!data; +} diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index b2fa6c47ecc0..5985920d162e 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -2,6 +2,7 @@ /* Copyright (c) 2022 Facebook */ #include <string.h> +#include <stdbool.h> #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include "bpf_misc.h" @@ -207,3 +208,339 @@ int test_dynptr_skb_data(struct __sk_buff *skb) return 1; } + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_adjust(void *ctx) +{ + struct bpf_dynptr ptr; + __u32 bytes = 64; + __u32 off = 10; + __u32 trim = 15; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + err = bpf_ringbuf_reserve_dynptr(&ringbuf, bytes, 0, &ptr); + if (err) { + err = 1; + goto done; + } + + if (bpf_dynptr_size(&ptr) != bytes) { + err = 2; + goto done; + } + + /* Advance the dynptr by off */ + err = bpf_dynptr_adjust(&ptr, off, bpf_dynptr_size(&ptr)); + if (err) { + err = 3; + goto done; + } + + if (bpf_dynptr_size(&ptr) != bytes - off) { + err = 4; + goto done; + } + + /* Trim the dynptr */ + err = bpf_dynptr_adjust(&ptr, off, 15); + if (err) { + err = 5; + goto done; + } + + /* Check that the size was adjusted correctly */ + if (bpf_dynptr_size(&ptr) != trim - off) { + err = 6; + goto done; + } + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_adjust_err(void *ctx) +{ + char write_data[45] = "hello there, world!!"; + struct bpf_dynptr ptr; + __u32 size = 64; + __u32 off = 20; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &ptr)) { + err = 1; + goto done; + } + + /* Check that start can't be greater than end */ + if (bpf_dynptr_adjust(&ptr, 5, 1) != -EINVAL) { + err = 2; + goto done; + } + + /* Check that start can't be greater than size */ + if (bpf_dynptr_adjust(&ptr, size + 1, size + 1) != -ERANGE) { + err = 3; + goto done; + } + + /* Check that end can't be greater than size */ + if (bpf_dynptr_adjust(&ptr, 0, size + 1) != -ERANGE) { + err = 4; + goto done; + } + + if (bpf_dynptr_adjust(&ptr, off, size)) { + err = 5; + goto done; + } + + /* Check that you can't write more bytes than available into the dynptr + * after you've adjusted it + */ + if (bpf_dynptr_write(&ptr, 0, &write_data, sizeof(write_data), 0) != -E2BIG) { + err = 6; + goto done; + } + + /* Check that even after adjusting, submitting/discarding + * a ringbuf dynptr works + */ + bpf_ringbuf_submit_dynptr(&ptr, 0); + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_zero_size_dynptr(void *ctx) +{ + char write_data = 'x', read_data; + struct bpf_dynptr ptr; + __u32 size = 64; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &ptr)) { + err = 1; + goto done; + } + + /* After this, the dynptr has a size of 0 */ + if (bpf_dynptr_adjust(&ptr, size, size)) { + err = 2; + goto done; + } + + /* Test that reading + writing non-zero bytes is not ok */ + if (bpf_dynptr_read(&read_data, sizeof(read_data), &ptr, 0, 0) != -E2BIG) { + err = 3; + goto done; + } + + if (bpf_dynptr_write(&ptr, 0, &write_data, sizeof(write_data), 0) != -E2BIG) { + err = 4; + goto done; + } + + /* Test that reading + writing 0 bytes from a 0-size dynptr is ok */ + if (bpf_dynptr_read(&read_data, 0, &ptr, 0, 0)) { + err = 5; + goto done; + } + + if (bpf_dynptr_write(&ptr, 0, &write_data, 0, 0)) { + err = 6; + goto done; + } + + err = 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_dynptr_is_null(void *ctx) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + __u64 size = 4; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + /* Pass in invalid flags, get back an invalid dynptr */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 123, &ptr1) != -EINVAL) { + err = 1; + goto exit_early; + } + + /* Test that the invalid dynptr is null */ + if (!bpf_dynptr_is_null(&ptr1)) { + err = 2; + goto exit_early; + } + + /* Get a valid dynptr */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &ptr2)) { + err = 3; + goto exit; + } + + /* Test that the valid dynptr is not null */ + if (bpf_dynptr_is_null(&ptr2)) { + err = 4; + goto exit; + } + +exit: + bpf_ringbuf_discard_dynptr(&ptr2, 0); +exit_early: + bpf_ringbuf_discard_dynptr(&ptr1, 0); + return 0; +} + +SEC("cgroup_skb/egress") +int test_dynptr_is_rdonly(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + struct bpf_dynptr ptr3; + + /* Pass in invalid flags, get back an invalid dynptr */ + if (bpf_dynptr_from_skb(skb, 123, &ptr1) != -EINVAL) { + err = 1; + return 0; + } + + /* Test that an invalid dynptr is_rdonly returns false */ + if (bpf_dynptr_is_rdonly(&ptr1)) { + err = 2; + return 0; + } + + /* Get a read-only dynptr */ + if (bpf_dynptr_from_skb(skb, 0, &ptr2)) { + err = 3; + return 0; + } + + /* Test that the dynptr is read-only */ + if (!bpf_dynptr_is_rdonly(&ptr2)) { + err = 4; + return 0; + } + + /* Get a read-writeable dynptr */ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr3)) { + err = 5; + goto done; + } + + /* Test that the dynptr is read-only */ + if (bpf_dynptr_is_rdonly(&ptr3)) { + err = 6; + goto done; + } + +done: + bpf_ringbuf_discard_dynptr(&ptr3, 0); + return 0; +} + +SEC("cgroup_skb/egress") +int test_dynptr_clone(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr1; + struct bpf_dynptr ptr2; + __u32 off = 2, size; + + /* Get a dynptr */ + if (bpf_dynptr_from_skb(skb, 0, &ptr1)) { + err = 1; + return 0; + } + + if (bpf_dynptr_adjust(&ptr1, off, bpf_dynptr_size(&ptr1))) { + err = 2; + return 0; + } + + /* Clone the dynptr */ + if (bpf_dynptr_clone(&ptr1, &ptr2)) { + err = 3; + return 0; + } + + size = bpf_dynptr_size(&ptr1); + + /* Check that the clone has the same size and rd-only */ + if (bpf_dynptr_size(&ptr2) != size) { + err = 4; + return 0; + } + + if (bpf_dynptr_is_rdonly(&ptr2) != bpf_dynptr_is_rdonly(&ptr1)) { + err = 5; + return 0; + } + + /* Advance and trim the original dynptr */ + bpf_dynptr_adjust(&ptr1, 5, 5); + + /* Check that only original dynptr was affected, and the clone wasn't */ + if (bpf_dynptr_size(&ptr2) != size) { + err = 6; + return 0; + } + + return 0; +} + +SEC("?cgroup_skb/egress") +int test_dynptr_skb_no_buff(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + __u64 *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* This may return NULL. SKB may require a buffer */ + data = bpf_dynptr_slice(&ptr, 0, NULL, 1); + + return !!data; +} + +SEC("?cgroup_skb/egress") +int test_dynptr_skb_strcmp(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + char *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* This may return NULL. SKB may require a buffer */ + data = bpf_dynptr_slice(&ptr, 0, NULL, 10); + if (data) { + bpf_strncmp(data, 10, "foo"); + return 1; + } + + return 1; +} diff --git a/tools/testing/selftests/bpf/progs/inner_array_lookup.c b/tools/testing/selftests/bpf/progs/inner_array_lookup.c new file mode 100644 index 000000000000..c2c8f2fa451d --- /dev/null +++ b/tools/testing/selftests/bpf/progs/inner_array_lookup.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> + +struct inner_map { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 5); + __type(key, int); + __type(value, int); +} inner_map1 SEC(".maps"); + +struct outer_map { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __uint(max_entries, 3); + __type(key, int); + __array(values, struct inner_map); +} outer_map1 SEC(".maps") = { + .values = { + [2] = &inner_map1, + }, +}; + +SEC("raw_tp/sys_enter") +int handle__sys_enter(void *ctx) +{ + int outer_key = 2, inner_key = 3; + int *val; + void *map; + + map = bpf_map_lookup_elem(&outer_map1, &outer_key); + if (!map) + return 1; + + val = bpf_map_lookup_elem(map, &inner_key); + if (!val) + return 1; + + if (*val == 1) + *val = 2; + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c index be16143ae292..6b9b3c56f009 100644 --- a/tools/testing/selftests/bpf/progs/iters.c +++ b/tools/testing/selftests/bpf/progs/iters.c @@ -651,29 +651,25 @@ int iter_stack_array_loop(const void *ctx) return sum; } -#define ARR_SZ 16 - -static __noinline void fill(struct bpf_iter_num *it, int *arr, int mul) +static __noinline void fill(struct bpf_iter_num *it, int *arr, __u32 n, int mul) { - int *t; - __u64 i; + int *t, i; while ((t = bpf_iter_num_next(it))) { i = *t; - if (i >= ARR_SZ) + if (i >= n) break; arr[i] = i * mul; } } -static __noinline int sum(struct bpf_iter_num *it, int *arr) +static __noinline int sum(struct bpf_iter_num *it, int *arr, __u32 n) { - int *t, sum = 0;; - __u64 i; + int *t, i, sum = 0;; while ((t = bpf_iter_num_next(it))) { i = *t; - if (i >= ARR_SZ) + if (i >= n) break; sum += arr[i]; } @@ -685,7 +681,7 @@ SEC("raw_tp") __success int iter_pass_iter_ptr_to_subprog(const void *ctx) { - int arr1[ARR_SZ], arr2[ARR_SZ]; + int arr1[16], arr2[32]; struct bpf_iter_num it; int n, sum1, sum2; @@ -694,25 +690,25 @@ int iter_pass_iter_ptr_to_subprog(const void *ctx) /* fill arr1 */ n = ARRAY_SIZE(arr1); bpf_iter_num_new(&it, 0, n); - fill(&it, arr1, 2); + fill(&it, arr1, n, 2); bpf_iter_num_destroy(&it); /* fill arr2 */ n = ARRAY_SIZE(arr2); bpf_iter_num_new(&it, 0, n); - fill(&it, arr2, 10); + fill(&it, arr2, n, 10); bpf_iter_num_destroy(&it); /* sum arr1 */ n = ARRAY_SIZE(arr1); bpf_iter_num_new(&it, 0, n); - sum1 = sum(&it, arr1); + sum1 = sum(&it, arr1, n); bpf_iter_num_destroy(&it); /* sum arr2 */ n = ARRAY_SIZE(arr2); bpf_iter_num_new(&it, 0, n); - sum2 = sum(&it, arr2); + sum2 = sum(&it, arr2, n); bpf_iter_num_destroy(&it); bpf_printk("sum1=%d, sum2=%d", sum1, sum2); diff --git a/tools/testing/selftests/bpf/progs/jit_probe_mem.c b/tools/testing/selftests/bpf/progs/jit_probe_mem.c index 13f00ca2ed0a..f9789e668297 100644 --- a/tools/testing/selftests/bpf/progs/jit_probe_mem.c +++ b/tools/testing/selftests/bpf/progs/jit_probe_mem.c @@ -3,13 +3,11 @@ #include <vmlinux.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_helpers.h> +#include "../bpf_testmod/bpf_testmod_kfunc.h" static struct prog_test_ref_kfunc __kptr *v; long total_sum = -1; -extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; - SEC("tc") int test_jit_probe_mem(struct __sk_buff *ctx) { diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_destructive.c b/tools/testing/selftests/bpf/progs/kfunc_call_destructive.c index 767472bc5a97..7632d9ecb253 100644 --- a/tools/testing/selftests/bpf/progs/kfunc_call_destructive.c +++ b/tools/testing/selftests/bpf/progs/kfunc_call_destructive.c @@ -1,8 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <vmlinux.h> #include <bpf/bpf_helpers.h> - -extern void bpf_kfunc_call_test_destructive(void) __ksym; +#include "../bpf_testmod/bpf_testmod_kfunc.h" SEC("tc") int kfunc_destructive_test(void) diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_fail.c b/tools/testing/selftests/bpf/progs/kfunc_call_fail.c index b98313d391c6..4b0b7b79cdfb 100644 --- a/tools/testing/selftests/bpf/progs/kfunc_call_fail.c +++ b/tools/testing/selftests/bpf/progs/kfunc_call_fail.c @@ -2,14 +2,7 @@ /* Copyright (c) 2021 Facebook */ #include <vmlinux.h> #include <bpf/bpf_helpers.h> - -extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; -extern void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym; -extern int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym; -extern int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym; -extern int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym; -extern void bpf_kfunc_call_int_mem_release(int *p) __ksym; +#include "../bpf_testmod/bpf_testmod_kfunc.h" struct syscall_test_args { __u8 data[16]; diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_race.c b/tools/testing/selftests/bpf/progs/kfunc_call_race.c index 4e8fed75a4e0..d532af07decf 100644 --- a/tools/testing/selftests/bpf/progs/kfunc_call_race.c +++ b/tools/testing/selftests/bpf/progs/kfunc_call_race.c @@ -1,8 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <vmlinux.h> #include <bpf/bpf_helpers.h> - -extern void bpf_testmod_test_mod_kfunc(int i) __ksym; +#include "../bpf_testmod/bpf_testmod_kfunc.h" SEC("tc") int kfunc_call_fail(struct __sk_buff *ctx) diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_test.c b/tools/testing/selftests/bpf/progs/kfunc_call_test.c index 7daa8f5720b9..cf68d1e48a0f 100644 --- a/tools/testing/selftests/bpf/progs/kfunc_call_test.c +++ b/tools/testing/selftests/bpf/progs/kfunc_call_test.c @@ -2,22 +2,7 @@ /* Copyright (c) 2021 Facebook */ #include <vmlinux.h> #include <bpf/bpf_helpers.h> - -extern long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym; -extern int bpf_kfunc_call_test2(struct sock *sk, __u32 a, __u32 b) __ksym; -extern __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b, - __u32 c, __u64 d) __ksym; - -extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; -extern void bpf_kfunc_call_test_pass_ctx(struct __sk_buff *skb) __ksym; -extern void bpf_kfunc_call_test_pass1(struct prog_test_pass1 *p) __ksym; -extern void bpf_kfunc_call_test_pass2(struct prog_test_pass2 *p) __ksym; -extern void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym; -extern void bpf_kfunc_call_test_mem_len_fail2(__u64 *mem, int len) __ksym; -extern int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym; -extern int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym; -extern u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym; +#include "../bpf_testmod/bpf_testmod_kfunc.h" SEC("tc") int kfunc_call_test4(struct __sk_buff *skb) diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_test_subprog.c b/tools/testing/selftests/bpf/progs/kfunc_call_test_subprog.c index c1fdecabeabf..2380c75e74ce 100644 --- a/tools/testing/selftests/bpf/progs/kfunc_call_test_subprog.c +++ b/tools/testing/selftests/bpf/progs/kfunc_call_test_subprog.c @@ -1,13 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2021 Facebook */ -#include <linux/bpf.h> -#include <bpf/bpf_helpers.h> -#include "bpf_tcp_helpers.h" +#include "../bpf_testmod/bpf_testmod_kfunc.h" extern const int bpf_prog_active __ksym; -extern __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b, - __u32 c, __u64 d) __ksym; -extern struct sock *bpf_kfunc_call_test3(struct sock *sk) __ksym; int active_res = -1; int sk_state_res = -1; @@ -28,7 +23,7 @@ int __noinline f1(struct __sk_buff *skb) if (active) active_res = *active; - sk_state_res = bpf_kfunc_call_test3((struct sock *)sk)->sk_state; + sk_state_res = bpf_kfunc_call_test3((struct sock *)sk)->__sk_common.skc_state; return (__u32)bpf_kfunc_call_test1((struct sock *)sk, 1, 2, 3, 4); } diff --git a/tools/testing/selftests/bpf/progs/local_kptr_stash.c b/tools/testing/selftests/bpf/progs/local_kptr_stash.c index 0ef286da092b..06838083079c 100644 --- a/tools/testing/selftests/bpf/progs/local_kptr_stash.c +++ b/tools/testing/selftests/bpf/progs/local_kptr_stash.c @@ -5,7 +5,8 @@ #include <bpf/bpf_tracing.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.h> -#include "bpf_experimental.h" +#include "../bpf_experimental.h" +#include "../bpf_testmod/bpf_testmod_kfunc.h" struct node_data { long key; @@ -32,8 +33,6 @@ struct map_value { */ struct node_data *just_here_because_btf_bug; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; - struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, int); diff --git a/tools/testing/selftests/bpf/progs/map_kptr.c b/tools/testing/selftests/bpf/progs/map_kptr.c index d7150041e5d1..da30f0d59364 100644 --- a/tools/testing/selftests/bpf/progs/map_kptr.c +++ b/tools/testing/selftests/bpf/progs/map_kptr.c @@ -2,6 +2,7 @@ #include <vmlinux.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_helpers.h> +#include "../bpf_testmod/bpf_testmod_kfunc.h" struct map_value { struct prog_test_ref_kfunc __kptr_untrusted *unref_ptr; @@ -114,10 +115,6 @@ DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, hash_map, hash_of_hash_maps); DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, hash_malloc_map, hash_of_hash_malloc_maps); DEFINE_MAP_OF_MAP(BPF_MAP_TYPE_HASH_OF_MAPS, lru_hash_map, hash_of_lru_hash_maps); -extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; -void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym; - #define WRITE_ONCE(x, val) ((*(volatile typeof(x) *) &(x)) = (val)) static void test_kptr_unref(struct map_value *v) diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c index da8c724f839b..450bb373b179 100644 --- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c @@ -4,6 +4,7 @@ #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.h> #include "bpf_misc.h" +#include "../bpf_testmod/bpf_testmod_kfunc.h" struct map_value { char buf[8]; @@ -19,9 +20,6 @@ struct array_map { __uint(max_entries, 1); } array_map SEC(".maps"); -extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; -extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym; - SEC("?tc") __failure __msg("kptr access size must be BPF_DW") int size_not_bpf_dw(struct __sk_buff *ctx) diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c index 1d348a225140..a3da610b1e6b 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c @@ -375,6 +375,8 @@ long rbtree_refcounted_node_ref_escapes(void *ctx) bpf_rbtree_add(&aroot, &n->node, less_a); m = bpf_refcount_acquire(n); bpf_spin_unlock(&alock); + if (!m) + return 2; m->key = 2; bpf_obj_drop(m); diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c index efcb308f80ad..0b09e5c915b1 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c @@ -29,7 +29,7 @@ static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b) } SEC("?tc") -__failure __msg("Unreleased reference id=3 alloc_insn=21") +__failure __msg("Unreleased reference id=4 alloc_insn=21") long rbtree_refcounted_node_ref_escapes(void *ctx) { struct node_acquire *n, *m; @@ -43,6 +43,8 @@ long rbtree_refcounted_node_ref_escapes(void *ctx) /* m becomes an owning ref but is never drop'd or added to a tree */ m = bpf_refcount_acquire(n); bpf_spin_unlock(&glock); + if (!m) + return 2; m->key = 2; return 0; diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c new file mode 100644 index 000000000000..9e0bf7a54cec --- /dev/null +++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> + +#include "bpf_tracing_net.h" + +__be16 serv_port = 0; + +int bpf_sock_destroy(struct sock_common *sk) __ksym; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u64); +} tcp_conn_sockets SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u64); +} udp_conn_sockets SEC(".maps"); + +SEC("cgroup/connect6") +int sock_connect(struct bpf_sock_addr *ctx) +{ + __u64 sock_cookie = 0; + int key = 0; + __u32 keyc = 0; + + if (ctx->family != AF_INET6 || ctx->user_family != AF_INET6) + return 1; + + sock_cookie = bpf_get_socket_cookie(ctx); + if (ctx->protocol == IPPROTO_TCP) + bpf_map_update_elem(&tcp_conn_sockets, &key, &sock_cookie, 0); + else if (ctx->protocol == IPPROTO_UDP) + bpf_map_update_elem(&udp_conn_sockets, &keyc, &sock_cookie, 0); + else + return 1; + + return 1; +} + +SEC("iter/tcp") +int iter_tcp6_client(struct bpf_iter__tcp *ctx) +{ + struct sock_common *sk_common = ctx->sk_common; + __u64 sock_cookie = 0; + __u64 *val; + int key = 0; + + if (!sk_common) + return 0; + + if (sk_common->skc_family != AF_INET6) + return 0; + + sock_cookie = bpf_get_socket_cookie(sk_common); + val = bpf_map_lookup_elem(&tcp_conn_sockets, &key); + if (!val) + return 0; + /* Destroy connected client sockets. */ + if (sock_cookie == *val) + bpf_sock_destroy(sk_common); + + return 0; +} + +SEC("iter/tcp") +int iter_tcp6_server(struct bpf_iter__tcp *ctx) +{ + struct sock_common *sk_common = ctx->sk_common; + const struct inet_connection_sock *icsk; + const struct inet_sock *inet; + struct tcp6_sock *tcp_sk; + __be16 srcp; + + if (!sk_common) + return 0; + + if (sk_common->skc_family != AF_INET6) + return 0; + + tcp_sk = bpf_skc_to_tcp6_sock(sk_common); + if (!tcp_sk) + return 0; + + icsk = &tcp_sk->tcp.inet_conn; + inet = &icsk->icsk_inet; + srcp = inet->inet_sport; + + /* Destroy server sockets. */ + if (srcp == serv_port) + bpf_sock_destroy(sk_common); + + return 0; +} + + +SEC("iter/udp") +int iter_udp6_client(struct bpf_iter__udp *ctx) +{ + struct udp_sock *udp_sk = ctx->udp_sk; + struct sock *sk = (struct sock *) udp_sk; + __u64 sock_cookie = 0, *val; + int key = 0; + + if (!sk) + return 0; + + sock_cookie = bpf_get_socket_cookie(sk); + val = bpf_map_lookup_elem(&udp_conn_sockets, &key); + if (!val) + return 0; + /* Destroy connected client sockets. */ + if (sock_cookie == *val) + bpf_sock_destroy((struct sock_common *)sk); + + return 0; +} + +SEC("iter/udp") +int iter_udp6_server(struct bpf_iter__udp *ctx) +{ + struct udp_sock *udp_sk = ctx->udp_sk; + struct sock *sk = (struct sock *) udp_sk; + struct inet_sock *inet; + __be16 srcp; + + if (!sk) + return 0; + + inet = &udp_sk->inet; + srcp = inet->inet_sport; + if (srcp == serv_port) + bpf_sock_destroy((struct sock_common *)sk); + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_prog_fail.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog_fail.c new file mode 100644 index 000000000000..dd6850b58e25 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog_fail.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "vmlinux.h" +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> + +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +int bpf_sock_destroy(struct sock_common *sk) __ksym; + +SEC("tp_btf/tcp_destroy_sock") +__failure __msg("calling kernel function bpf_sock_destroy is not allowed") +int BPF_PROG(trace_tcp_destroy_sock, struct sock *sk) +{ + /* should not load */ + bpf_sock_destroy((struct sock_common *)sk); + + return 0; +} + diff --git a/tools/testing/selftests/bpf/progs/sockopt_inherit.c b/tools/testing/selftests/bpf/progs/sockopt_inherit.c index 9fb241b97291..c8f59caa4639 100644 --- a/tools/testing/selftests/bpf/progs/sockopt_inherit.c +++ b/tools/testing/selftests/bpf/progs/sockopt_inherit.c @@ -9,6 +9,8 @@ char _license[] SEC("license") = "GPL"; #define CUSTOM_INHERIT2 1 #define CUSTOM_LISTENER 2 +__u32 page_size = 0; + struct sockopt_inherit { __u8 val; }; @@ -55,7 +57,7 @@ int _getsockopt(struct bpf_sockopt *ctx) __u8 *optval = ctx->optval; if (ctx->level != SOL_CUSTOM) - return 1; /* only interested in SOL_CUSTOM */ + goto out; /* only interested in SOL_CUSTOM */ if (optval + 1 > optval_end) return 0; /* EPERM, bounds check */ @@ -70,6 +72,12 @@ int _getsockopt(struct bpf_sockopt *ctx) ctx->optlen = 1; return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } SEC("cgroup/setsockopt") @@ -80,7 +88,7 @@ int _setsockopt(struct bpf_sockopt *ctx) __u8 *optval = ctx->optval; if (ctx->level != SOL_CUSTOM) - return 1; /* only interested in SOL_CUSTOM */ + goto out; /* only interested in SOL_CUSTOM */ if (optval + 1 > optval_end) return 0; /* EPERM, bounds check */ @@ -93,4 +101,10 @@ int _setsockopt(struct bpf_sockopt *ctx) ctx->optlen = -1; return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } diff --git a/tools/testing/selftests/bpf/progs/sockopt_multi.c b/tools/testing/selftests/bpf/progs/sockopt_multi.c index 177a59069dae..96f29fce050b 100644 --- a/tools/testing/selftests/bpf/progs/sockopt_multi.c +++ b/tools/testing/selftests/bpf/progs/sockopt_multi.c @@ -5,6 +5,8 @@ char _license[] SEC("license") = "GPL"; +__u32 page_size = 0; + SEC("cgroup/getsockopt") int _getsockopt_child(struct bpf_sockopt *ctx) { @@ -12,7 +14,7 @@ int _getsockopt_child(struct bpf_sockopt *ctx) __u8 *optval = ctx->optval; if (ctx->level != SOL_IP || ctx->optname != IP_TOS) - return 1; + goto out; if (optval + 1 > optval_end) return 0; /* EPERM, bounds check */ @@ -26,6 +28,12 @@ int _getsockopt_child(struct bpf_sockopt *ctx) ctx->optlen = 1; return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } SEC("cgroup/getsockopt") @@ -35,7 +43,7 @@ int _getsockopt_parent(struct bpf_sockopt *ctx) __u8 *optval = ctx->optval; if (ctx->level != SOL_IP || ctx->optname != IP_TOS) - return 1; + goto out; if (optval + 1 > optval_end) return 0; /* EPERM, bounds check */ @@ -49,6 +57,12 @@ int _getsockopt_parent(struct bpf_sockopt *ctx) ctx->optlen = 1; return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } SEC("cgroup/setsockopt") @@ -58,7 +72,7 @@ int _setsockopt(struct bpf_sockopt *ctx) __u8 *optval = ctx->optval; if (ctx->level != SOL_IP || ctx->optname != IP_TOS) - return 1; + goto out; if (optval + 1 > optval_end) return 0; /* EPERM, bounds check */ @@ -67,4 +81,10 @@ int _setsockopt(struct bpf_sockopt *ctx) ctx->optlen = 1; return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } diff --git a/tools/testing/selftests/bpf/progs/sockopt_qos_to_cc.c b/tools/testing/selftests/bpf/progs/sockopt_qos_to_cc.c index 1bce83b6e3a7..dbe235ede7f3 100644 --- a/tools/testing/selftests/bpf/progs/sockopt_qos_to_cc.c +++ b/tools/testing/selftests/bpf/progs/sockopt_qos_to_cc.c @@ -9,6 +9,8 @@ char _license[] SEC("license") = "GPL"; +__u32 page_size = 0; + SEC("cgroup/setsockopt") int sockopt_qos_to_cc(struct bpf_sockopt *ctx) { @@ -19,7 +21,7 @@ int sockopt_qos_to_cc(struct bpf_sockopt *ctx) char cc_cubic[TCP_CA_NAME_MAX] = "cubic"; if (ctx->level != SOL_IPV6 || ctx->optname != IPV6_TCLASS) - return 1; + goto out; if (optval + 1 > optval_end) return 0; /* EPERM, bounds check */ @@ -36,4 +38,10 @@ int sockopt_qos_to_cc(struct bpf_sockopt *ctx) return 0; } return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } diff --git a/tools/testing/selftests/bpf/progs/sockopt_sk.c b/tools/testing/selftests/bpf/progs/sockopt_sk.c index fe1df4cd206e..cb990a7d3d45 100644 --- a/tools/testing/selftests/bpf/progs/sockopt_sk.c +++ b/tools/testing/selftests/bpf/progs/sockopt_sk.c @@ -37,7 +37,7 @@ int _getsockopt(struct bpf_sockopt *ctx) /* Bypass AF_NETLINK. */ sk = ctx->sk; if (sk && sk->family == AF_NETLINK) - return 1; + goto out; /* Make sure bpf_get_netns_cookie is callable. */ @@ -52,8 +52,7 @@ int _getsockopt(struct bpf_sockopt *ctx) * let next BPF program in the cgroup chain or kernel * handle it. */ - ctx->optlen = 0; /* bypass optval>PAGE_SIZE */ - return 1; + goto out; } if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) { @@ -61,7 +60,7 @@ int _getsockopt(struct bpf_sockopt *ctx) * let next BPF program in the cgroup chain or kernel * handle it. */ - return 1; + goto out; } if (ctx->level == SOL_TCP && ctx->optname == TCP_CONGESTION) { @@ -69,7 +68,7 @@ int _getsockopt(struct bpf_sockopt *ctx) * let next BPF program in the cgroup chain or kernel * handle it. */ - return 1; + goto out; } if (ctx->level == SOL_TCP && ctx->optname == TCP_ZEROCOPY_RECEIVE) { @@ -85,7 +84,7 @@ int _getsockopt(struct bpf_sockopt *ctx) if (((struct tcp_zerocopy_receive *)optval)->address != 0) return 0; /* unexpected data */ - return 1; + goto out; } if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) { @@ -129,6 +128,12 @@ int _getsockopt(struct bpf_sockopt *ctx) ctx->optlen = 1; return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } SEC("cgroup/setsockopt") @@ -142,7 +147,7 @@ int _setsockopt(struct bpf_sockopt *ctx) /* Bypass AF_NETLINK. */ sk = ctx->sk; if (sk && sk->family == AF_NETLINK) - return 1; + goto out; /* Make sure bpf_get_netns_cookie is callable. */ @@ -224,4 +229,10 @@ int _setsockopt(struct bpf_sockopt *ctx) */ return 1; + +out: + /* optval larger than PAGE_SIZE use kernel's buffer. */ + if (ctx->optlen > page_size) + ctx->optlen = 0; + return 1; } diff --git a/tools/testing/selftests/bpf/progs/test_global_func1.c b/tools/testing/selftests/bpf/progs/test_global_func1.c index b85fc8c423ba..17a9f59bf5f3 100644 --- a/tools/testing/selftests/bpf/progs/test_global_func1.c +++ b/tools/testing/selftests/bpf/progs/test_global_func1.c @@ -10,6 +10,8 @@ static __attribute__ ((noinline)) int f0(int var, struct __sk_buff *skb) { + asm volatile (""); + return skb->len; } diff --git a/tools/testing/selftests/bpf/progs/test_global_map_resize.c b/tools/testing/selftests/bpf/progs/test_global_map_resize.c new file mode 100644 index 000000000000..2588f2384246 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_global_map_resize.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> + +char _license[] SEC("license") = "GPL"; + +/* rodata section */ +const volatile pid_t pid; +const volatile size_t bss_array_len; +const volatile size_t data_array_len; + +/* bss section */ +int sum = 0; +int array[1]; + +/* custom data secton */ +int my_array[1] SEC(".data.custom"); + +/* custom data section which should NOT be resizable, + * since it contains a single var which is not an array + */ +int my_int SEC(".data.non_array"); + +/* custom data section which should NOT be resizable, + * since its last var is not an array + */ +int my_array_first[1] SEC(".data.array_not_last"); +int my_int_last SEC(".data.array_not_last"); + +SEC("tp/syscalls/sys_enter_getpid") +int bss_array_sum(void *ctx) +{ + if (pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + + sum = 0; + + for (size_t i = 0; i < bss_array_len; ++i) + sum += array[i]; + + return 0; +} + +SEC("tp/syscalls/sys_enter_getuid") +int data_array_sum(void *ctx) +{ + if (pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + + sum = 0; + + for (size_t i = 0; i < data_array_len; ++i) + sum += my_array[i]; + + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/test_sock_fields.c b/tools/testing/selftests/bpf/progs/test_sock_fields.c index bbad3c2d9aa5..f75e531bf36f 100644 --- a/tools/testing/selftests/bpf/progs/test_sock_fields.c +++ b/tools/testing/selftests/bpf/progs/test_sock_fields.c @@ -265,7 +265,10 @@ static __noinline bool sk_dst_port__load_word(struct bpf_sock *sk) static __noinline bool sk_dst_port__load_half(struct bpf_sock *sk) { - __u16 *half = (__u16 *)&sk->dst_port; + __u16 *half; + + asm volatile (""); + half = (__u16 *)&sk->dst_port; return half[0] == bpf_htons(0xcafe); } diff --git a/tools/testing/selftests/bpf/progs/test_subprogs_extable.c b/tools/testing/selftests/bpf/progs/test_subprogs_extable.c new file mode 100644 index 000000000000..e2a21fbd4e44 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_subprogs_extable.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 8); + __type(key, __u32); + __type(value, __u64); +} test_array SEC(".maps"); + +unsigned int triggered; + +static __u64 test_cb(struct bpf_map *map, __u32 *key, __u64 *val, void *data) +{ + return 1; +} + +SEC("fexit/bpf_testmod_return_ptr") +int BPF_PROG(handle_fexit_ret_subprogs, int arg, struct file *ret) +{ + *(volatile long *)ret; + *(volatile int *)&ret->f_mode; + bpf_for_each_map_elem(&test_array, test_cb, NULL, 0); + triggered++; + return 0; +} + +SEC("fexit/bpf_testmod_return_ptr") +int BPF_PROG(handle_fexit_ret_subprogs2, int arg, struct file *ret) +{ + *(volatile long *)ret; + *(volatile int *)&ret->f_mode; + bpf_for_each_map_elem(&test_array, test_cb, NULL, 0); + triggered++; + return 0; +} + +SEC("fexit/bpf_testmod_return_ptr") +int BPF_PROG(handle_fexit_ret_subprogs3, int arg, struct file *ret) +{ + *(volatile long *)ret; + *(volatile int *)&ret->f_mode; + bpf_for_each_map_elem(&test_array, test_cb, NULL, 0); + triggered++; + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_task_under_cgroup.c b/tools/testing/selftests/bpf/progs/test_task_under_cgroup.c new file mode 100644 index 000000000000..56cdc0a553f0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_task_under_cgroup.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Bytedance */ + +#include <vmlinux.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> + +#include "bpf_misc.h" + +struct cgroup *bpf_cgroup_from_id(u64 cgid) __ksym; +long bpf_task_under_cgroup(struct task_struct *task, struct cgroup *ancestor) __ksym; +void bpf_cgroup_release(struct cgroup *p) __ksym; +struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym; +void bpf_task_release(struct task_struct *p) __ksym; + +const volatile int local_pid; +const volatile __u64 cgid; +int remote_pid; + +SEC("tp_btf/task_newtask") +int BPF_PROG(handle__task_newtask, struct task_struct *task, u64 clone_flags) +{ + struct cgroup *cgrp = NULL; + struct task_struct *acquired; + + if (local_pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + + acquired = bpf_task_acquire(task); + if (!acquired) + return 0; + + if (local_pid == acquired->tgid) + goto out; + + cgrp = bpf_cgroup_from_id(cgid); + if (!cgrp) + goto out; + + if (bpf_task_under_cgroup(acquired, cgrp)) + remote_pid = acquired->tgid; + +out: + if (cgrp) + bpf_cgroup_release(cgrp); + bpf_task_release(acquired); + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_xdp_dynptr.c b/tools/testing/selftests/bpf/progs/test_xdp_dynptr.c index 25ee4a22e48d..78c368e71797 100644 --- a/tools/testing/selftests/bpf/progs/test_xdp_dynptr.c +++ b/tools/testing/selftests/bpf/progs/test_xdp_dynptr.c @@ -2,6 +2,7 @@ /* Copyright (c) 2022 Meta */ #include <stddef.h> #include <string.h> +#include <stdbool.h> #include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/if_packet.h> diff --git a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c new file mode 100644 index 000000000000..13b29a7faa71 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> +#include "bpf_misc.h" + +/* Check that precision marks propagate through scalar IDs. + * Registers r{0,1,2} have the same scalar ID at the moment when r0 is + * marked to be precise, this mark is immediately propagated to r{1,2}. + */ +SEC("socket") +__success __log_level(2) +__msg("frame0: regs=r0,r1,r2 stack= before 4: (bf) r3 = r10") +__msg("frame0: regs=r0,r1,r2 stack= before 3: (bf) r2 = r0") +__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0") +__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255") +__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_same_state(void) +{ + asm volatile ( + /* r0 = random number up to 0xff */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* tie r0.id == r1.id == r2.id */ + "r1 = r0;" + "r2 = r0;" + /* force r0 to be precise, this immediately marks r1 and r2 as + * precise as well because of shared IDs + */ + "r3 = r10;" + "r3 += r0;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Same as precision_same_state, but mark propagates through state / + * parent state boundary. + */ +SEC("socket") +__success __log_level(2) +__msg("frame0: last_idx 6 first_idx 5 subseq_idx -1") +__msg("frame0: regs=r0,r1,r2 stack= before 5: (bf) r3 = r10") +__msg("frame0: parent state regs=r0,r1,r2 stack=:") +__msg("frame0: regs=r0,r1,r2 stack= before 4: (05) goto pc+0") +__msg("frame0: regs=r0,r1,r2 stack= before 3: (bf) r2 = r0") +__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0") +__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255") +__msg("frame0: parent state regs=r0 stack=:") +__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_cross_state(void) +{ + asm volatile ( + /* r0 = random number up to 0xff */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* tie r0.id == r1.id == r2.id */ + "r1 = r0;" + "r2 = r0;" + /* force checkpoint */ + "goto +0;" + /* force r0 to be precise, this immediately marks r1 and r2 as + * precise as well because of shared IDs + */ + "r3 = r10;" + "r3 += r0;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Same as precision_same_state, but break one of the + * links, note that r1 is absent from regs=... in __msg below. + */ +SEC("socket") +__success __log_level(2) +__msg("frame0: regs=r0,r2 stack= before 5: (bf) r3 = r10") +__msg("frame0: regs=r0,r2 stack= before 4: (b7) r1 = 0") +__msg("frame0: regs=r0,r2 stack= before 3: (bf) r2 = r0") +__msg("frame0: regs=r0 stack= before 2: (bf) r1 = r0") +__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255") +__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_same_state_broken_link(void) +{ + asm volatile ( + /* r0 = random number up to 0xff */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* tie r0.id == r1.id == r2.id */ + "r1 = r0;" + "r2 = r0;" + /* break link for r1, this is the only line that differs + * compared to the previous test + */ + "r1 = 0;" + /* force r0 to be precise, this immediately marks r1 and r2 as + * precise as well because of shared IDs + */ + "r3 = r10;" + "r3 += r0;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Same as precision_same_state_broken_link, but with state / + * parent state boundary. + */ +SEC("socket") +__success __log_level(2) +__msg("frame0: regs=r0,r2 stack= before 6: (bf) r3 = r10") +__msg("frame0: regs=r0,r2 stack= before 5: (b7) r1 = 0") +__msg("frame0: parent state regs=r0,r2 stack=:") +__msg("frame0: regs=r0,r1,r2 stack= before 4: (05) goto pc+0") +__msg("frame0: regs=r0,r1,r2 stack= before 3: (bf) r2 = r0") +__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0") +__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255") +__msg("frame0: parent state regs=r0 stack=:") +__msg("frame0: regs=r0 stack= before 0: (85) call bpf_ktime_get_ns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_cross_state_broken_link(void) +{ + asm volatile ( + /* r0 = random number up to 0xff */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* tie r0.id == r1.id == r2.id */ + "r1 = r0;" + "r2 = r0;" + /* force checkpoint, although link between r1 and r{0,2} is + * broken by the next statement current precision tracking + * algorithm can't react to it and propagates mark for r1 to + * the parent state. + */ + "goto +0;" + /* break link for r1, this is the only line that differs + * compared to precision_cross_state() + */ + "r1 = 0;" + /* force r0 to be precise, this immediately marks r1 and r2 as + * precise as well because of shared IDs + */ + "r3 = r10;" + "r3 += r0;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Check that precision marks propagate through scalar IDs. + * Use the same scalar ID in multiple stack frames, check that + * precision information is propagated up the call stack. + */ +SEC("socket") +__success __log_level(2) +__msg("11: (0f) r2 += r1") +/* Current state */ +__msg("frame2: last_idx 11 first_idx 10 subseq_idx -1") +__msg("frame2: regs=r1 stack= before 10: (bf) r2 = r10") +__msg("frame2: parent state regs=r1 stack=") +/* frame1.r{6,7} are marked because mark_precise_scalar_ids() + * looks for all registers with frame2.r1.id in the current state + */ +__msg("frame1: parent state regs=r6,r7 stack=") +__msg("frame0: parent state regs=r6 stack=") +/* Parent state */ +__msg("frame2: last_idx 8 first_idx 8 subseq_idx 10") +__msg("frame2: regs=r1 stack= before 8: (85) call pc+1") +/* frame1.r1 is marked because of backtracking of call instruction */ +__msg("frame1: parent state regs=r1,r6,r7 stack=") +__msg("frame0: parent state regs=r6 stack=") +/* Parent state */ +__msg("frame1: last_idx 7 first_idx 6 subseq_idx 8") +__msg("frame1: regs=r1,r6,r7 stack= before 7: (bf) r7 = r1") +__msg("frame1: regs=r1,r6 stack= before 6: (bf) r6 = r1") +__msg("frame1: parent state regs=r1 stack=") +__msg("frame0: parent state regs=r6 stack=") +/* Parent state */ +__msg("frame1: last_idx 4 first_idx 4 subseq_idx 6") +__msg("frame1: regs=r1 stack= before 4: (85) call pc+1") +__msg("frame0: parent state regs=r1,r6 stack=") +/* Parent state */ +__msg("frame0: last_idx 3 first_idx 1 subseq_idx 4") +__msg("frame0: regs=r0,r1,r6 stack= before 3: (bf) r6 = r0") +__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0") +__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_many_frames(void) +{ + asm volatile ( + /* r0 = random number up to 0xff */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* tie r0.id == r1.id == r6.id */ + "r1 = r0;" + "r6 = r0;" + "call precision_many_frames__foo;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +static __naked __noinline __used +void precision_many_frames__foo(void) +{ + asm volatile ( + /* conflate one of the register numbers (r6) with outer frame, + * to verify that those are tracked independently + */ + "r6 = r1;" + "r7 = r1;" + "call precision_many_frames__bar;" + "exit" + ::: __clobber_all); +} + +static __naked __noinline __used +void precision_many_frames__bar(void) +{ + asm volatile ( + /* force r1 to be precise, this immediately marks: + * - bar frame r1 + * - foo frame r{1,6,7} + * - main frame r{1,6} + */ + "r2 = r10;" + "r2 += r1;" + "r0 = 0;" + "exit;" + ::: __clobber_all); +} + +/* Check that scalars with the same IDs are marked precise on stack as + * well as in registers. + */ +SEC("socket") +__success __log_level(2) +/* foo frame */ +__msg("frame1: regs=r1 stack=-8,-16 before 9: (bf) r2 = r10") +__msg("frame1: regs=r1 stack=-8,-16 before 8: (7b) *(u64 *)(r10 -16) = r1") +__msg("frame1: regs=r1 stack=-8 before 7: (7b) *(u64 *)(r10 -8) = r1") +__msg("frame1: regs=r1 stack= before 4: (85) call pc+2") +/* main frame */ +__msg("frame0: regs=r0,r1 stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r1") +__msg("frame0: regs=r0,r1 stack= before 2: (bf) r1 = r0") +__msg("frame0: regs=r0 stack= before 1: (57) r0 &= 255") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_stack(void) +{ + asm volatile ( + /* r0 = random number up to 0xff */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* tie r0.id == r1.id == fp[-8].id */ + "r1 = r0;" + "*(u64*)(r10 - 8) = r1;" + "call precision_stack__foo;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +static __naked __noinline __used +void precision_stack__foo(void) +{ + asm volatile ( + /* conflate one of the register numbers (r6) with outer frame, + * to verify that those are tracked independently + */ + "*(u64*)(r10 - 8) = r1;" + "*(u64*)(r10 - 16) = r1;" + /* force r1 to be precise, this immediately marks: + * - foo frame r1,fp{-8,-16} + * - main frame r1,fp{-8} + */ + "r2 = r10;" + "r2 += r1;" + "exit" + ::: __clobber_all); +} + +/* Use two separate scalar IDs to check that these are propagated + * independently. + */ +SEC("socket") +__success __log_level(2) +/* r{6,7} */ +__msg("11: (0f) r3 += r7") +__msg("frame0: regs=r6,r7 stack= before 10: (bf) r3 = r10") +/* ... skip some insns ... */ +__msg("frame0: regs=r6,r7 stack= before 3: (bf) r7 = r0") +__msg("frame0: regs=r0,r6 stack= before 2: (bf) r6 = r0") +/* r{8,9} */ +__msg("12: (0f) r3 += r9") +__msg("frame0: regs=r8,r9 stack= before 11: (0f) r3 += r7") +/* ... skip some insns ... */ +__msg("frame0: regs=r8,r9 stack= before 7: (bf) r9 = r0") +__msg("frame0: regs=r0,r8 stack= before 6: (bf) r8 = r0") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void precision_two_ids(void) +{ + asm volatile ( + /* r6 = random number up to 0xff + * r6.id == r7.id + */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + "r6 = r0;" + "r7 = r0;" + /* same, but for r{8,9} */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + "r8 = r0;" + "r9 = r0;" + /* clear r0 id */ + "r0 = 0;" + /* force checkpoint */ + "goto +0;" + "r3 = r10;" + /* force r7 to be precise, this also marks r6 */ + "r3 += r7;" + /* force r9 to be precise, this also marks r8 */ + "r3 += r9;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Verify that check_ids() is used by regsafe() for scalars. + * + * r9 = ... some pointer with range X ... + * r6 = ... unbound scalar ID=a ... + * r7 = ... unbound scalar ID=b ... + * if (r6 > r7) goto +1 + * r7 = r6 + * if (r7 > X) goto exit + * r9 += r6 + * ... access memory using r9 ... + * + * The memory access is safe only if r7 is bounded, + * which is true for one branch and not true for another. + */ +SEC("socket") +__failure __msg("register with unbounded min value") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void check_ids_in_regsafe(void) +{ + asm volatile ( + /* Bump allocated stack */ + "r1 = 0;" + "*(u64*)(r10 - 8) = r1;" + /* r9 = pointer to stack */ + "r9 = r10;" + "r9 += -8;" + /* r7 = ktime_get_ns() */ + "call %[bpf_ktime_get_ns];" + "r7 = r0;" + /* r6 = ktime_get_ns() */ + "call %[bpf_ktime_get_ns];" + "r6 = r0;" + /* if r6 > r7 is an unpredictable jump */ + "if r6 > r7 goto l1_%=;" + "r7 = r6;" +"l1_%=:" + /* if r7 > 4 ...; transfers range to r6 on one execution path + * but does not transfer on another + */ + "if r7 > 4 goto l2_%=;" + /* Access memory at r9[r6], r6 is not always bounded */ + "r9 += r6;" + "r0 = *(u8*)(r9 + 0);" +"l2_%=:" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Similar to check_ids_in_regsafe. + * The l0 could be reached in two states: + * + * (1) r6{.id=A}, r7{.id=A}, r8{.id=B} + * (2) r6{.id=B}, r7{.id=A}, r8{.id=B} + * + * Where (2) is not safe, as "r7 > 4" check won't propagate range for it. + * This example would be considered safe without changes to + * mark_chain_precision() to track scalar values with equal IDs. + */ +SEC("socket") +__failure __msg("register with unbounded min value") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void check_ids_in_regsafe_2(void) +{ + asm volatile ( + /* Bump allocated stack */ + "r1 = 0;" + "*(u64*)(r10 - 8) = r1;" + /* r9 = pointer to stack */ + "r9 = r10;" + "r9 += -8;" + /* r8 = ktime_get_ns() */ + "call %[bpf_ktime_get_ns];" + "r8 = r0;" + /* r7 = ktime_get_ns() */ + "call %[bpf_ktime_get_ns];" + "r7 = r0;" + /* r6 = ktime_get_ns() */ + "call %[bpf_ktime_get_ns];" + "r6 = r0;" + /* scratch .id from r0 */ + "r0 = 0;" + /* if r6 > r7 is an unpredictable jump */ + "if r6 > r7 goto l1_%=;" + /* tie r6 and r7 .id */ + "r6 = r7;" +"l0_%=:" + /* if r7 > 4 exit(0) */ + "if r7 > 4 goto l2_%=;" + /* Access memory at r9[r6] */ + "r9 += r6;" + "r0 = *(u8*)(r9 + 0);" +"l2_%=:" + "r0 = 0;" + "exit;" +"l1_%=:" + /* tie r6 and r8 .id */ + "r6 = r8;" + "goto l0_%=;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Check that scalar IDs *are not* generated on register to register + * assignments if source register is a constant. + * + * If such IDs *are* generated the 'l1' below would be reached in + * two states: + * + * (1) r1{.id=A}, r2{.id=A} + * (2) r1{.id=C}, r2{.id=C} + * + * Thus forcing 'if r1 == r2' verification twice. + */ +SEC("socket") +__success __log_level(2) +__msg("11: (1d) if r3 == r4 goto pc+0") +__msg("frame 0: propagating r3,r4") +__msg("11: safe") +__msg("processed 15 insns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void no_scalar_id_for_const(void) +{ + asm volatile ( + "call %[bpf_ktime_get_ns];" + /* unpredictable jump */ + "if r0 > 7 goto l0_%=;" + /* possibly generate same scalar ids for r3 and r4 */ + "r1 = 0;" + "r1 = r1;" + "r3 = r1;" + "r4 = r1;" + "goto l1_%=;" +"l0_%=:" + /* possibly generate different scalar ids for r3 and r4 */ + "r1 = 0;" + "r2 = 0;" + "r3 = r1;" + "r4 = r2;" +"l1_%=:" + /* predictable jump, marks r3 and r4 precise */ + "if r3 == r4 goto +0;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Same as no_scalar_id_for_const() but for 32-bit values */ +SEC("socket") +__success __log_level(2) +__msg("11: (1e) if w3 == w4 goto pc+0") +__msg("frame 0: propagating r3,r4") +__msg("11: safe") +__msg("processed 15 insns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void no_scalar_id_for_const32(void) +{ + asm volatile ( + "call %[bpf_ktime_get_ns];" + /* unpredictable jump */ + "if r0 > 7 goto l0_%=;" + /* possibly generate same scalar ids for r3 and r4 */ + "w1 = 0;" + "w1 = w1;" + "w3 = w1;" + "w4 = w1;" + "goto l1_%=;" +"l0_%=:" + /* possibly generate different scalar ids for r3 and r4 */ + "w1 = 0;" + "w2 = 0;" + "w3 = w1;" + "w4 = w2;" +"l1_%=:" + /* predictable jump, marks r1 and r2 precise */ + "if w3 == w4 goto +0;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Check that unique scalar IDs are ignored when new verifier state is + * compared to cached verifier state. For this test: + * - cached state has no id on r1 + * - new state has a unique id on r1 + */ +SEC("socket") +__success __log_level(2) +__msg("6: (25) if r6 > 0x7 goto pc+1") +__msg("7: (57) r1 &= 255") +__msg("8: (bf) r2 = r10") +__msg("from 6 to 8: safe") +__msg("processed 12 insns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void ignore_unique_scalar_ids_cur(void) +{ + asm volatile ( + "call %[bpf_ktime_get_ns];" + "r6 = r0;" + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* r1.id == r0.id */ + "r1 = r0;" + /* make r1.id unique */ + "r0 = 0;" + "if r6 > 7 goto l0_%=;" + /* clear r1 id, but keep the range compatible */ + "r1 &= 0xff;" +"l0_%=:" + /* get here in two states: + * - first: r1 has no id (cached state) + * - second: r1 has a unique id (should be considered equivalent) + */ + "r2 = r10;" + "r2 += r1;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Check that unique scalar IDs are ignored when new verifier state is + * compared to cached verifier state. For this test: + * - cached state has a unique id on r1 + * - new state has no id on r1 + */ +SEC("socket") +__success __log_level(2) +__msg("6: (25) if r6 > 0x7 goto pc+1") +__msg("7: (05) goto pc+1") +__msg("9: (bf) r2 = r10") +__msg("9: safe") +__msg("processed 13 insns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void ignore_unique_scalar_ids_old(void) +{ + asm volatile ( + "call %[bpf_ktime_get_ns];" + "r6 = r0;" + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + /* r1.id == r0.id */ + "r1 = r0;" + /* make r1.id unique */ + "r0 = 0;" + "if r6 > 7 goto l1_%=;" + "goto l0_%=;" +"l1_%=:" + /* clear r1 id, but keep the range compatible */ + "r1 &= 0xff;" +"l0_%=:" + /* get here in two states: + * - first: r1 has a unique id (cached state) + * - second: r1 has no id (should be considered equivalent) + */ + "r2 = r10;" + "r2 += r1;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +/* Check that two different scalar IDs in a verified state can't be + * mapped to the same scalar ID in current state. + */ +SEC("socket") +__success __log_level(2) +/* The exit instruction should be reachable from two states, + * use two matches and "processed .. insns" to ensure this. + */ +__msg("13: (95) exit") +__msg("13: (95) exit") +__msg("processed 18 insns") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void two_old_ids_one_cur_id(void) +{ + asm volatile ( + /* Give unique scalar IDs to r{6,7} */ + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + "r6 = r0;" + "call %[bpf_ktime_get_ns];" + "r0 &= 0xff;" + "r7 = r0;" + "r0 = 0;" + /* Maybe make r{6,7} IDs identical */ + "if r6 > r7 goto l0_%=;" + "goto l1_%=;" +"l0_%=:" + "r6 = r7;" +"l1_%=:" + /* Mark r{6,7} precise. + * Get here in two states: + * - first: r6{.id=A}, r7{.id=B} (cached state) + * - second: r6{.id=A}, r7{.id=A} + * Currently we don't want to consider such states equivalent. + * Thus "exit;" would be verified twice. + */ + "r2 = r10;" + "r2 += r6;" + "r2 += r7;" + "exit;" + : + : __imm(bpf_ktime_get_ns) + : __clobber_all); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c index 136e5530b72c..6115520154e3 100644 --- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c +++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c @@ -371,4 +371,83 @@ __naked void and_then_at_fp_8(void) " ::: __clobber_all); } +SEC("xdp") +__description("32-bit spill of 64-bit reg should clear ID") +__failure __msg("math between ctx pointer and 4294967295 is not allowed") +__naked void spill_32bit_of_64bit_fail(void) +{ + asm volatile (" \ + r6 = r1; \ + /* Roll one bit to force the verifier to track both branches. */\ + call %[bpf_get_prandom_u32]; \ + r0 &= 0x8; \ + /* Put a large number into r1. */ \ + r1 = 0xffffffff; \ + r1 <<= 32; \ + r1 += r0; \ + /* Assign an ID to r1. */ \ + r2 = r1; \ + /* 32-bit spill r1 to stack - should clear the ID! */\ + *(u32*)(r10 - 8) = r1; \ + /* 32-bit fill r2 from stack. */ \ + r2 = *(u32*)(r10 - 8); \ + /* Compare r2 with another register to trigger find_equal_scalars.\ + * Having one random bit is important here, otherwise the verifier cuts\ + * the corners. If the ID was mistakenly preserved on spill, this would\ + * cause the verifier to think that r1 is also equal to zero in one of\ + * the branches, and equal to eight on the other branch.\ + */ \ + r3 = 0; \ + if r2 != r3 goto l0_%=; \ +l0_%=: r1 >>= 32; \ + /* At this point, if the verifier thinks that r1 is 0, an out-of-bounds\ + * read will happen, because it actually contains 0xffffffff.\ + */ \ + r6 += r1; \ + r0 = *(u32*)(r6 + 0); \ + exit; \ +" : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +SEC("xdp") +__description("16-bit spill of 32-bit reg should clear ID") +__failure __msg("dereference of modified ctx ptr R6 off=65535 disallowed") +__naked void spill_16bit_of_32bit_fail(void) +{ + asm volatile (" \ + r6 = r1; \ + /* Roll one bit to force the verifier to track both branches. */\ + call %[bpf_get_prandom_u32]; \ + r0 &= 0x8; \ + /* Put a large number into r1. */ \ + w1 = 0xffff0000; \ + r1 += r0; \ + /* Assign an ID to r1. */ \ + r2 = r1; \ + /* 16-bit spill r1 to stack - should clear the ID! */\ + *(u16*)(r10 - 8) = r1; \ + /* 16-bit fill r2 from stack. */ \ + r2 = *(u16*)(r10 - 8); \ + /* Compare r2 with another register to trigger find_equal_scalars.\ + * Having one random bit is important here, otherwise the verifier cuts\ + * the corners. If the ID was mistakenly preserved on spill, this would\ + * cause the verifier to think that r1 is also equal to zero in one of\ + * the branches, and equal to eight on the other branch.\ + */ \ + r3 = 0; \ + if r2 != r3 goto l0_%=; \ +l0_%=: r1 >>= 16; \ + /* At this point, if the verifier thinks that r1 is 0, an out-of-bounds\ + * read will happen, because it actually contains 0xffff.\ + */ \ + r6 += r1; \ + r0 = *(u32*)(r6 + 0); \ + exit; \ +" : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c new file mode 100644 index 000000000000..db6b3143338b --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ + +#include <errno.h> +#include <string.h> +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> +#include "bpf_misc.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +int vals[] SEC(".data.vals") = {1, 2, 3, 4}; + +__naked __noinline __used +static unsigned long identity_subprog() +{ + /* the simplest *static* 64-bit identity function */ + asm volatile ( + "r0 = r1;" + "exit;" + ); +} + +__noinline __used +unsigned long global_identity_subprog(__u64 x) +{ + /* the simplest *global* 64-bit identity function */ + return x; +} + +__naked __noinline __used +static unsigned long callback_subprog() +{ + /* the simplest callback function */ + asm volatile ( + "r0 = 0;" + "exit;" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("7: (0f) r1 += r0") +__msg("mark_precise: frame0: regs=r0 stack= before 6: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r0 stack= before 5: (27) r0 *= 4") +__msg("mark_precise: frame0: regs=r0 stack= before 11: (95) exit") +__msg("mark_precise: frame1: regs=r0 stack= before 10: (bf) r0 = r1") +__msg("mark_precise: frame1: regs=r1 stack= before 4: (85) call pc+5") +__msg("mark_precise: frame0: regs=r1 stack= before 3: (bf) r1 = r6") +__msg("mark_precise: frame0: regs=r6 stack= before 2: (b7) r6 = 3") +__naked int subprog_result_precise(void) +{ + asm volatile ( + "r6 = 3;" + /* pass r6 through r1 into subprog to get it back as r0; + * this whole chain will have to be marked as precise later + */ + "r1 = r6;" + "call identity_subprog;" + /* now use subprog's returned value (which is a + * r6 -> r1 -> r0 chain), as index into vals array, forcing + * all of that to be known precisely + */ + "r0 *= 4;" + "r1 = %[vals];" + /* here r0->r1->r6 chain is forced to be precise and has to be + * propagated back to the beginning, including through the + * subprog call + */ + "r1 += r0;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("9: (0f) r1 += r0") +__msg("mark_precise: frame0: last_idx 9 first_idx 0") +__msg("mark_precise: frame0: regs=r0 stack= before 8: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r0 stack= before 7: (27) r0 *= 4") +__msg("mark_precise: frame0: regs=r0 stack= before 5: (a5) if r0 < 0x4 goto pc+1") +__msg("mark_precise: frame0: regs=r0 stack= before 4: (85) call pc+7") +__naked int global_subprog_result_precise(void) +{ + asm volatile ( + "r6 = 3;" + /* pass r6 through r1 into subprog to get it back as r0; + * given global_identity_subprog is global, precision won't + * propagate all the way back to r6 + */ + "r1 = r6;" + "call global_identity_subprog;" + /* now use subprog's returned value (which is unknown now, so + * we need to clamp it), as index into vals array, forcing r0 + * to be marked precise (with no effect on r6, though) + */ + "if r0 < %[vals_arr_sz] goto 1f;" + "r0 = %[vals_arr_sz] - 1;" + "1:" + "r0 *= 4;" + "r1 = %[vals];" + /* here r0 is forced to be precise and has to be + * propagated back to the global subprog call, but it + * shouldn't go all the way to mark r6 as precise + */ + "r1 += r0;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals), + __imm_const(vals_arr_sz, ARRAY_SIZE(vals)) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("14: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 14 first_idx 10") +__msg("mark_precise: frame0: regs=r6 stack= before 13: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 12: (27) r6 *= 4") +__msg("mark_precise: frame0: regs=r6 stack= before 11: (25) if r6 > 0x3 goto pc+4") +__msg("mark_precise: frame0: regs=r6 stack= before 10: (bf) r6 = r0") +__msg("mark_precise: frame0: parent state regs=r0 stack=:") +__msg("mark_precise: frame0: last_idx 18 first_idx 0") +__msg("mark_precise: frame0: regs=r0 stack= before 18: (95) exit") +__naked int callback_result_precise(void) +{ + asm volatile ( + "r6 = 3;" + + /* call subprog and use result; r0 shouldn't propagate back to + * callback_subprog + */ + "r1 = r6;" /* nr_loops */ + "r2 = %[callback_subprog];" /* callback_fn */ + "r3 = 0;" /* callback_ctx */ + "r4 = 0;" /* flags */ + "call %[bpf_loop];" + + "r6 = r0;" + "if r6 > 3 goto 1f;" + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the bpf_loop() call, but not beyond + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "1:" + "exit;" + : + : __imm_ptr(vals), + __imm_ptr(callback_subprog), + __imm(bpf_loop) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("7: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 7 first_idx 0") +__msg("mark_precise: frame0: regs=r6 stack= before 6: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 5: (27) r6 *= 4") +__msg("mark_precise: frame0: regs=r6 stack= before 11: (95) exit") +__msg("mark_precise: frame1: regs= stack= before 10: (bf) r0 = r1") +__msg("mark_precise: frame1: regs= stack= before 4: (85) call pc+5") +__msg("mark_precise: frame0: regs=r6 stack= before 3: (b7) r1 = 0") +__msg("mark_precise: frame0: regs=r6 stack= before 2: (b7) r6 = 3") +__naked int parent_callee_saved_reg_precise(void) +{ + asm volatile ( + "r6 = 3;" + + /* call subprog and ignore result; we need this call only to + * complicate jump history + */ + "r1 = 0;" + "call identity_subprog;" + + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the beginning, handling (and ignoring) subprog call + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("7: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 7 first_idx 0") +__msg("mark_precise: frame0: regs=r6 stack= before 6: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 5: (27) r6 *= 4") +__msg("mark_precise: frame0: regs=r6 stack= before 4: (85) call pc+5") +__msg("mark_precise: frame0: regs=r6 stack= before 3: (b7) r1 = 0") +__msg("mark_precise: frame0: regs=r6 stack= before 2: (b7) r6 = 3") +__naked int parent_callee_saved_reg_precise_global(void) +{ + asm volatile ( + "r6 = 3;" + + /* call subprog and ignore result; we need this call only to + * complicate jump history + */ + "r1 = 0;" + "call global_identity_subprog;" + + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the beginning, handling (and ignoring) subprog call + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("12: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 12 first_idx 10") +__msg("mark_precise: frame0: regs=r6 stack= before 11: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 10: (27) r6 *= 4") +__msg("mark_precise: frame0: parent state regs=r6 stack=:") +__msg("mark_precise: frame0: last_idx 16 first_idx 0") +__msg("mark_precise: frame0: regs=r6 stack= before 16: (95) exit") +__msg("mark_precise: frame1: regs= stack= before 15: (b7) r0 = 0") +__msg("mark_precise: frame1: regs= stack= before 9: (85) call bpf_loop#181") +__msg("mark_precise: frame0: regs=r6 stack= before 8: (b7) r4 = 0") +__msg("mark_precise: frame0: regs=r6 stack= before 7: (b7) r3 = 0") +__msg("mark_precise: frame0: regs=r6 stack= before 6: (bf) r2 = r8") +__msg("mark_precise: frame0: regs=r6 stack= before 5: (b7) r1 = 1") +__msg("mark_precise: frame0: regs=r6 stack= before 4: (b7) r6 = 3") +__naked int parent_callee_saved_reg_precise_with_callback(void) +{ + asm volatile ( + "r6 = 3;" + + /* call subprog and ignore result; we need this call only to + * complicate jump history + */ + "r1 = 1;" /* nr_loops */ + "r2 = %[callback_subprog];" /* callback_fn */ + "r3 = 0;" /* callback_ctx */ + "r4 = 0;" /* flags */ + "call %[bpf_loop];" + + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the beginning, handling (and ignoring) callback call + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals), + __imm_ptr(callback_subprog), + __imm(bpf_loop) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("9: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 9 first_idx 6") +__msg("mark_precise: frame0: regs=r6 stack= before 8: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 7: (27) r6 *= 4") +__msg("mark_precise: frame0: regs=r6 stack= before 6: (79) r6 = *(u64 *)(r10 -8)") +__msg("mark_precise: frame0: parent state regs= stack=-8:") +__msg("mark_precise: frame0: last_idx 13 first_idx 0") +__msg("mark_precise: frame0: regs= stack=-8 before 13: (95) exit") +__msg("mark_precise: frame1: regs= stack= before 12: (bf) r0 = r1") +__msg("mark_precise: frame1: regs= stack= before 5: (85) call pc+6") +__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r1 = 0") +__msg("mark_precise: frame0: regs= stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r6") +__msg("mark_precise: frame0: regs=r6 stack= before 2: (b7) r6 = 3") +__naked int parent_stack_slot_precise(void) +{ + asm volatile ( + /* spill reg */ + "r6 = 3;" + "*(u64 *)(r10 - 8) = r6;" + + /* call subprog and ignore result; we need this call only to + * complicate jump history + */ + "r1 = 0;" + "call identity_subprog;" + + /* restore reg from stack; in this case we'll be carrying + * stack mask when going back into subprog through jump + * history + */ + "r6 = *(u64 *)(r10 - 8);" + + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the beginning, handling (and ignoring) subprog call + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("9: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 9 first_idx 6") +__msg("mark_precise: frame0: regs=r6 stack= before 8: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 7: (27) r6 *= 4") +__msg("mark_precise: frame0: regs=r6 stack= before 6: (79) r6 = *(u64 *)(r10 -8)") +__msg("mark_precise: frame0: parent state regs= stack=-8:") +__msg("mark_precise: frame0: last_idx 5 first_idx 0") +__msg("mark_precise: frame0: regs= stack=-8 before 5: (85) call pc+6") +__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r1 = 0") +__msg("mark_precise: frame0: regs= stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r6") +__msg("mark_precise: frame0: regs=r6 stack= before 2: (b7) r6 = 3") +__naked int parent_stack_slot_precise_global(void) +{ + asm volatile ( + /* spill reg */ + "r6 = 3;" + "*(u64 *)(r10 - 8) = r6;" + + /* call subprog and ignore result; we need this call only to + * complicate jump history + */ + "r1 = 0;" + "call global_identity_subprog;" + + /* restore reg from stack; in this case we'll be carrying + * stack mask when going back into subprog through jump + * history + */ + "r6 = *(u64 *)(r10 - 8);" + + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the beginning, handling (and ignoring) subprog call + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals) + : __clobber_common, "r6" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("14: (0f) r1 += r6") +__msg("mark_precise: frame0: last_idx 14 first_idx 11") +__msg("mark_precise: frame0: regs=r6 stack= before 13: (bf) r1 = r7") +__msg("mark_precise: frame0: regs=r6 stack= before 12: (27) r6 *= 4") +__msg("mark_precise: frame0: regs=r6 stack= before 11: (79) r6 = *(u64 *)(r10 -8)") +__msg("mark_precise: frame0: parent state regs= stack=-8:") +__msg("mark_precise: frame0: last_idx 18 first_idx 0") +__msg("mark_precise: frame0: regs= stack=-8 before 18: (95) exit") +__msg("mark_precise: frame1: regs= stack= before 17: (b7) r0 = 0") +__msg("mark_precise: frame1: regs= stack= before 10: (85) call bpf_loop#181") +__msg("mark_precise: frame0: regs= stack=-8 before 9: (b7) r4 = 0") +__msg("mark_precise: frame0: regs= stack=-8 before 8: (b7) r3 = 0") +__msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r2 = r8") +__msg("mark_precise: frame0: regs= stack=-8 before 6: (bf) r1 = r6") +__msg("mark_precise: frame0: regs= stack=-8 before 5: (7b) *(u64 *)(r10 -8) = r6") +__msg("mark_precise: frame0: regs=r6 stack= before 4: (b7) r6 = 3") +__naked int parent_stack_slot_precise_with_callback(void) +{ + asm volatile ( + /* spill reg */ + "r6 = 3;" + "*(u64 *)(r10 - 8) = r6;" + + /* ensure we have callback frame in jump history */ + "r1 = r6;" /* nr_loops */ + "r2 = %[callback_subprog];" /* callback_fn */ + "r3 = 0;" /* callback_ctx */ + "r4 = 0;" /* flags */ + "call %[bpf_loop];" + + /* restore reg from stack; in this case we'll be carrying + * stack mask when going back into subprog through jump + * history + */ + "r6 = *(u64 *)(r10 - 8);" + + "r6 *= 4;" + "r1 = %[vals];" + /* here r6 is forced to be precise and has to be propagated + * back to the beginning, handling (and ignoring) subprog call + */ + "r1 += r6;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals), + __imm_ptr(callback_subprog), + __imm(bpf_loop) + : __clobber_common, "r6" + ); +} + +__noinline __used +static __u64 subprog_with_precise_arg(__u64 x) +{ + return vals[x]; /* x is forced to be precise */ +} + +SEC("?raw_tp") +__success __log_level(2) +__msg("8: (0f) r2 += r1") +__msg("mark_precise: frame1: last_idx 8 first_idx 0") +__msg("mark_precise: frame1: regs=r1 stack= before 6: (18) r2 = ") +__msg("mark_precise: frame1: regs=r1 stack= before 5: (67) r1 <<= 2") +__msg("mark_precise: frame1: regs=r1 stack= before 2: (85) call pc+2") +__msg("mark_precise: frame0: regs=r1 stack= before 1: (bf) r1 = r6") +__msg("mark_precise: frame0: regs=r6 stack= before 0: (b7) r6 = 3") +__naked int subprog_arg_precise(void) +{ + asm volatile ( + "r6 = 3;" + "r1 = r6;" + /* subprog_with_precise_arg expects its argument to be + * precise, so r1->r6 will be marked precise from inside the + * subprog + */ + "call subprog_with_precise_arg;" + "r0 += r6;" + "exit;" + : + : + : __clobber_common, "r6" + ); +} + +/* r1 is pointer to stack slot; + * r2 is a register to spill into that slot + * subprog also spills r2 into its own stack slot + */ +__naked __noinline __used +static __u64 subprog_spill_reg_precise(void) +{ + asm volatile ( + /* spill to parent stack */ + "*(u64 *)(r1 + 0) = r2;" + /* spill to subprog stack (we use -16 offset to avoid + * accidental confusion with parent's -8 stack slot in + * verifier log output) + */ + "*(u64 *)(r10 - 16) = r2;" + /* use both spills as return result to propagete precision everywhere */ + "r0 = *(u64 *)(r10 - 16);" + "r2 = *(u64 *)(r1 + 0);" + "r0 += r2;" + "exit;" + ); +} + +SEC("?raw_tp") +__success __log_level(2) +/* precision backtracking can't currently handle stack access not through r10, + * so we won't be able to mark stack slot fp-8 as precise, and so will + * fallback to forcing all as precise + */ +__msg("mark_precise: frame0: falling back to forcing all scalars precise") +__naked int subprog_spill_into_parent_stack_slot_precise(void) +{ + asm volatile ( + "r6 = 1;" + + /* pass pointer to stack slot and r6 to subprog; + * r6 will be marked precise and spilled into fp-8 slot, which + * also should be marked precise + */ + "r1 = r10;" + "r1 += -8;" + "r2 = r6;" + "call subprog_spill_reg_precise;" + + /* restore reg from stack; in this case we'll be carrying + * stack mask when going back into subprog through jump + * history + */ + "r7 = *(u64 *)(r10 - 8);" + + "r7 *= 4;" + "r1 = %[vals];" + /* here r7 is forced to be precise and has to be propagated + * back to the beginning, handling subprog call and logic + */ + "r1 += r7;" + "r0 = *(u32 *)(r1 + 0);" + "exit;" + : + : __imm_ptr(vals) + : __clobber_common, "r6", "r7" + ); +} + +__naked __noinline __used +static __u64 subprog_with_checkpoint(void) +{ + asm volatile ( + "r0 = 0;" + /* guaranteed checkpoint if BPF_F_TEST_STATE_FREQ is used */ + "goto +0;" + "exit;" + ); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/vrf_socket_lookup.c b/tools/testing/selftests/bpf/progs/vrf_socket_lookup.c new file mode 100644 index 000000000000..bcfb6feb38c0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/vrf_socket_lookup.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> + +#include <linux/ip.h> +#include <linux/in.h> +#include <linux/if_ether.h> +#include <linux/pkt_cls.h> +#include <stdbool.h> + +int lookup_status; +bool test_xdp; +bool tcp_skc; + +#define CUR_NS BPF_F_CURRENT_NETNS + +static void socket_lookup(void *ctx, void *data_end, void *data) +{ + struct ethhdr *eth = data; + struct bpf_sock_tuple *tp; + struct bpf_sock *sk; + struct iphdr *iph; + int tplen; + + if (eth + 1 > data_end) + return; + + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return; + + iph = (struct iphdr *)(eth + 1); + if (iph + 1 > data_end) + return; + + tp = (struct bpf_sock_tuple *)&iph->saddr; + tplen = sizeof(tp->ipv4); + if ((void *)tp + tplen > data_end) + return; + + switch (iph->protocol) { + case IPPROTO_TCP: + if (tcp_skc) + sk = bpf_skc_lookup_tcp(ctx, tp, tplen, CUR_NS, 0); + else + sk = bpf_sk_lookup_tcp(ctx, tp, tplen, CUR_NS, 0); + break; + case IPPROTO_UDP: + sk = bpf_sk_lookup_udp(ctx, tp, tplen, CUR_NS, 0); + break; + default: + return; + } + + lookup_status = 0; + + if (sk) { + bpf_sk_release(sk); + lookup_status = 1; + } +} + +SEC("tc") +int tc_socket_lookup(struct __sk_buff *skb) +{ + void *data_end = (void *)(long)skb->data_end; + void *data = (void *)(long)skb->data; + + if (test_xdp) + return TC_ACT_UNSPEC; + + socket_lookup(skb, data_end, data); + return TC_ACT_UNSPEC; +} + +SEC("xdp") +int xdp_socket_lookup(struct xdp_md *xdp) +{ + void *data_end = (void *)(long)xdp->data_end; + void *data = (void *)(long)xdp->data; + + if (!test_xdp) + return XDP_PASS; + + socket_lookup(xdp, data_end, data); + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c b/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c index e1c787815e44..b2dfd7066c6e 100644 --- a/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c +++ b/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c @@ -77,7 +77,9 @@ int rx(struct xdp_md *ctx) } err = bpf_xdp_metadata_rx_timestamp(ctx, &meta->rx_timestamp); - if (err) + if (!err) + meta->xdp_timestamp = bpf_ktime_get_tai_ns(); + else meta->rx_timestamp = 0; /* Used by AF_XDP as not avail signal */ err = bpf_xdp_metadata_rx_hash(ctx, &meta->rx_hash, &meta->rx_hash_type); diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c index ea82921110da..4d582cac2c09 100644 --- a/tools/testing/selftests/bpf/test_progs.c +++ b/tools/testing/selftests/bpf/test_progs.c @@ -11,7 +11,6 @@ #include <signal.h> #include <string.h> #include <execinfo.h> /* backtrace */ -#include <linux/membarrier.h> #include <sys/sysinfo.h> /* get_nprocs */ #include <netinet/in.h> #include <sys/select.h> @@ -629,68 +628,6 @@ out: return err; } -static int finit_module(int fd, const char *param_values, int flags) -{ - return syscall(__NR_finit_module, fd, param_values, flags); -} - -static int delete_module(const char *name, int flags) -{ - return syscall(__NR_delete_module, name, flags); -} - -/* - * Trigger synchronize_rcu() in kernel. - */ -int kern_sync_rcu(void) -{ - return syscall(__NR_membarrier, MEMBARRIER_CMD_SHARED, 0, 0); -} - -static void unload_bpf_testmod(void) -{ - if (kern_sync_rcu()) - fprintf(env.stderr, "Failed to trigger kernel-side RCU sync!\n"); - if (delete_module("bpf_testmod", 0)) { - if (errno == ENOENT) { - if (verbose()) - fprintf(stdout, "bpf_testmod.ko is already unloaded.\n"); - return; - } - fprintf(env.stderr, "Failed to unload bpf_testmod.ko from kernel: %d\n", -errno); - return; - } - if (verbose()) - fprintf(stdout, "Successfully unloaded bpf_testmod.ko.\n"); -} - -static int load_bpf_testmod(void) -{ - int fd; - - /* ensure previous instance of the module is unloaded */ - unload_bpf_testmod(); - - if (verbose()) - fprintf(stdout, "Loading bpf_testmod.ko...\n"); - - fd = open("bpf_testmod.ko", O_RDONLY); - if (fd < 0) { - fprintf(env.stderr, "Can't find bpf_testmod.ko kernel module: %d\n", -errno); - return -ENOENT; - } - if (finit_module(fd, "", 0)) { - fprintf(env.stderr, "Failed to load bpf_testmod.ko into the kernel: %d\n", -errno); - close(fd); - return -EINVAL; - } - close(fd); - - if (verbose()) - fprintf(stdout, "Successfully loaded bpf_testmod.ko.\n"); - return 0; -} - /* extern declarations for test funcs */ #define DEFINE_TEST(name) \ extern void test_##name(void) __weak; \ @@ -714,7 +651,13 @@ static struct test_state test_states[ARRAY_SIZE(prog_test_defs)]; const char *argp_program_version = "test_progs 0.1"; const char *argp_program_bug_address = "<bpf@vger.kernel.org>"; -static const char argp_program_doc[] = "BPF selftests test runner"; +static const char argp_program_doc[] = +"BPF selftests test runner\v" +"Options accepting the NAMES parameter take either a comma-separated list\n" +"of test names, or a filename prefixed with @. The file contains one name\n" +"(or wildcard pattern) per line, and comments beginning with # are ignored.\n" +"\n" +"These options can be passed repeatedly to read multiple files.\n"; enum ARG_KEYS { ARG_TEST_NUM = 'n', @@ -797,6 +740,7 @@ extern int extra_prog_load_log_flags; static error_t parse_arg(int key, char *arg, struct argp_state *state) { struct test_env *env = state->input; + int err = 0; switch (key) { case ARG_TEST_NUM: { @@ -821,18 +765,28 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) } case ARG_TEST_NAME_GLOB_ALLOWLIST: case ARG_TEST_NAME: { - if (parse_test_list(arg, - &env->test_selector.whitelist, - key == ARG_TEST_NAME_GLOB_ALLOWLIST)) - return -ENOMEM; + if (arg[0] == '@') + err = parse_test_list_file(arg + 1, + &env->test_selector.whitelist, + key == ARG_TEST_NAME_GLOB_ALLOWLIST); + else + err = parse_test_list(arg, + &env->test_selector.whitelist, + key == ARG_TEST_NAME_GLOB_ALLOWLIST); + break; } case ARG_TEST_NAME_GLOB_DENYLIST: case ARG_TEST_NAME_BLACKLIST: { - if (parse_test_list(arg, - &env->test_selector.blacklist, - key == ARG_TEST_NAME_GLOB_DENYLIST)) - return -ENOMEM; + if (arg[0] == '@') + err = parse_test_list_file(arg + 1, + &env->test_selector.blacklist, + key == ARG_TEST_NAME_GLOB_DENYLIST); + else + err = parse_test_list(arg, + &env->test_selector.blacklist, + key == ARG_TEST_NAME_GLOB_DENYLIST); + break; } case ARG_VERIFIER_STATS: @@ -900,7 +854,7 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) default: return ARGP_ERR_UNKNOWN; } - return 0; + return err; } /* @@ -1703,9 +1657,14 @@ int main(int argc, char **argv) env.stderr = stderr; env.has_testmod = true; - if (!env.list_test_names && load_bpf_testmod()) { - fprintf(env.stderr, "WARNING! Selftests relying on bpf_testmod.ko will be skipped.\n"); - env.has_testmod = false; + if (!env.list_test_names) { + /* ensure previous instance of the module is unloaded */ + unload_bpf_testmod(verbose()); + + if (load_bpf_testmod(verbose())) { + fprintf(env.stderr, "WARNING! Selftests relying on bpf_testmod.ko will be skipped.\n"); + env.has_testmod = false; + } } /* initializing tests */ @@ -1802,7 +1761,7 @@ int main(int argc, char **argv) close(env.saved_netns_fd); out: if (!env.list_test_names && env.has_testmod) - unload_bpf_testmod(); + unload_bpf_testmod(verbose()); free_test_selector(&env.test_selector); free_test_selector(&env.subtest_selector); diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h index 0ed3134333d4..77bd492c6024 100644 --- a/tools/testing/selftests/bpf/test_progs.h +++ b/tools/testing/selftests/bpf/test_progs.h @@ -405,7 +405,6 @@ static inline void *u64_to_ptr(__u64 ptr) int bpf_find_map(const char *test, struct bpf_object *obj, const char *name); int compare_map_keys(int map1_fd, int map2_fd); int compare_stack_ips(int smap_fd, int amap_fd, int stack_trace_len); -int kern_sync_rcu(void); int trigger_module_test_read(int read_sz); int trigger_module_test_write(int write_sz); int write_sysctl(const char *sysctl, const char *value); diff --git a/tools/testing/selftests/bpf/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c index e4657c5bc3f1..31f1c935cd07 100644 --- a/tools/testing/selftests/bpf/test_verifier.c +++ b/tools/testing/selftests/bpf/test_verifier.c @@ -40,6 +40,7 @@ #include "bpf_util.h" #include "test_btf.h" #include "../../../include/linux/filter.h" +#include "testing_helpers.h" #ifndef ENOTSUPP #define ENOTSUPP 524 @@ -873,8 +874,140 @@ static int create_map_kptr(void) return fd; } +static void set_root(bool set) +{ + __u64 caps; + + if (set) { + if (cap_enable_effective(1ULL << CAP_SYS_ADMIN, &caps)) + perror("cap_disable_effective(CAP_SYS_ADMIN)"); + } else { + if (cap_disable_effective(1ULL << CAP_SYS_ADMIN, &caps)) + perror("cap_disable_effective(CAP_SYS_ADMIN)"); + } +} + +static __u64 ptr_to_u64(const void *ptr) +{ + return (uintptr_t) ptr; +} + +static struct btf *btf__load_testmod_btf(struct btf *vmlinux) +{ + struct bpf_btf_info info; + __u32 len = sizeof(info); + struct btf *btf = NULL; + char name[64]; + __u32 id = 0; + int err, fd; + + /* Iterate all loaded BTF objects and find bpf_testmod, + * we need SYS_ADMIN cap for that. + */ + set_root(true); + + while (true) { + err = bpf_btf_get_next_id(id, &id); + if (err) { + if (errno == ENOENT) + break; + perror("bpf_btf_get_next_id failed"); + break; + } + + fd = bpf_btf_get_fd_by_id(id); + if (fd < 0) { + if (errno == ENOENT) + continue; + perror("bpf_btf_get_fd_by_id failed"); + break; + } + + memset(&info, 0, sizeof(info)); + info.name_len = sizeof(name); + info.name = ptr_to_u64(name); + len = sizeof(info); + + err = bpf_obj_get_info_by_fd(fd, &info, &len); + if (err) { + close(fd); + perror("bpf_obj_get_info_by_fd failed"); + break; + } + + if (strcmp("bpf_testmod", name)) { + close(fd); + continue; + } + + btf = btf__load_from_kernel_by_id_split(id, vmlinux); + if (!btf) { + close(fd); + break; + } + + /* We need the fd to stay open so it can be used in fd_array. + * The final cleanup call to btf__free will free btf object + * and close the file descriptor. + */ + btf__set_fd(btf, fd); + break; + } + + set_root(false); + return btf; +} + +static struct btf *testmod_btf; +static struct btf *vmlinux_btf; + +static void kfuncs_cleanup(void) +{ + btf__free(testmod_btf); + btf__free(vmlinux_btf); +} + +static void fixup_prog_kfuncs(struct bpf_insn *prog, int *fd_array, + struct kfunc_btf_id_pair *fixup_kfunc_btf_id) +{ + /* Patch in kfunc BTF IDs */ + while (fixup_kfunc_btf_id->kfunc) { + int btf_id = 0; + + /* try to find kfunc in kernel BTF */ + vmlinux_btf = vmlinux_btf ?: btf__load_vmlinux_btf(); + if (vmlinux_btf) { + btf_id = btf__find_by_name_kind(vmlinux_btf, + fixup_kfunc_btf_id->kfunc, + BTF_KIND_FUNC); + btf_id = btf_id < 0 ? 0 : btf_id; + } + + /* kfunc not found in kernel BTF, try bpf_testmod BTF */ + if (!btf_id) { + testmod_btf = testmod_btf ?: btf__load_testmod_btf(vmlinux_btf); + if (testmod_btf) { + btf_id = btf__find_by_name_kind(testmod_btf, + fixup_kfunc_btf_id->kfunc, + BTF_KIND_FUNC); + btf_id = btf_id < 0 ? 0 : btf_id; + if (btf_id) { + /* We put bpf_testmod module fd into fd_array + * and its index 1 into instruction 'off'. + */ + *fd_array = btf__fd(testmod_btf); + prog[fixup_kfunc_btf_id->insn_idx].off = 1; + } + } + } + + prog[fixup_kfunc_btf_id->insn_idx].imm = btf_id; + fixup_kfunc_btf_id++; + } +} + static void do_test_fixup(struct bpf_test *test, enum bpf_prog_type prog_type, - struct bpf_insn *prog, int *map_fds) + struct bpf_insn *prog, int *map_fds, int *fd_array) { int *fixup_map_hash_8b = test->fixup_map_hash_8b; int *fixup_map_hash_48b = test->fixup_map_hash_48b; @@ -899,7 +1032,6 @@ static void do_test_fixup(struct bpf_test *test, enum bpf_prog_type prog_type, int *fixup_map_ringbuf = test->fixup_map_ringbuf; int *fixup_map_timer = test->fixup_map_timer; int *fixup_map_kptr = test->fixup_map_kptr; - struct kfunc_btf_id_pair *fixup_kfunc_btf_id = test->fixup_kfunc_btf_id; if (test->fill_helper) { test->fill_insns = calloc(MAX_TEST_INSNS, sizeof(struct bpf_insn)); @@ -1100,25 +1232,7 @@ static void do_test_fixup(struct bpf_test *test, enum bpf_prog_type prog_type, } while (*fixup_map_kptr); } - /* Patch in kfunc BTF IDs */ - if (fixup_kfunc_btf_id->kfunc) { - struct btf *btf; - int btf_id; - - do { - btf_id = 0; - btf = btf__load_vmlinux_btf(); - if (btf) { - btf_id = btf__find_by_name_kind(btf, - fixup_kfunc_btf_id->kfunc, - BTF_KIND_FUNC); - btf_id = btf_id < 0 ? 0 : btf_id; - } - btf__free(btf); - prog[fixup_kfunc_btf_id->insn_idx].imm = btf_id; - fixup_kfunc_btf_id++; - } while (fixup_kfunc_btf_id->kfunc); - } + fixup_prog_kfuncs(prog, fd_array, test->fixup_kfunc_btf_id); } struct libcap { @@ -1227,45 +1341,46 @@ static bool cmp_str_seq(const char *log, const char *exp) return true; } -static int get_xlated_program(int fd_prog, struct bpf_insn **buf, int *cnt) +static struct bpf_insn *get_xlated_program(int fd_prog, int *cnt) { + __u32 buf_element_size = sizeof(struct bpf_insn); struct bpf_prog_info info = {}; __u32 info_len = sizeof(info); __u32 xlated_prog_len; - __u32 buf_element_size = sizeof(struct bpf_insn); + struct bpf_insn *buf; if (bpf_prog_get_info_by_fd(fd_prog, &info, &info_len)) { perror("bpf_prog_get_info_by_fd failed"); - return -1; + return NULL; } xlated_prog_len = info.xlated_prog_len; if (xlated_prog_len % buf_element_size) { printf("Program length %d is not multiple of %d\n", xlated_prog_len, buf_element_size); - return -1; + return NULL; } *cnt = xlated_prog_len / buf_element_size; - *buf = calloc(*cnt, buf_element_size); + buf = calloc(*cnt, buf_element_size); if (!buf) { perror("can't allocate xlated program buffer"); - return -ENOMEM; + return NULL; } bzero(&info, sizeof(info)); info.xlated_prog_len = xlated_prog_len; - info.xlated_prog_insns = (__u64)(unsigned long)*buf; + info.xlated_prog_insns = (__u64)(unsigned long)buf; if (bpf_prog_get_info_by_fd(fd_prog, &info, &info_len)) { perror("second bpf_prog_get_info_by_fd failed"); goto out_free_buf; } - return 0; + return buf; out_free_buf: - free(*buf); - return -1; + free(buf); + return NULL; } static bool is_null_insn(struct bpf_insn *insn) @@ -1398,7 +1513,8 @@ static bool check_xlated_program(struct bpf_test *test, int fd_prog) if (!check_expected && !check_unexpected) goto out; - if (get_xlated_program(fd_prog, &buf, &cnt)) { + buf = get_xlated_program(fd_prog, &cnt); + if (!buf) { printf("FAIL: can't get xlated program\n"); result = false; goto out; @@ -1445,6 +1561,7 @@ static void do_test_single(struct bpf_test *test, bool unpriv, int run_errs, run_successes; int map_fds[MAX_NR_MAPS]; const char *expected_err; + int fd_array[2] = { -1, -1 }; int saved_errno; int fixup_skips; __u32 pflags; @@ -1458,7 +1575,7 @@ static void do_test_single(struct bpf_test *test, bool unpriv, if (!prog_type) prog_type = BPF_PROG_TYPE_SOCKET_FILTER; fixup_skips = skips; - do_test_fixup(test, prog_type, prog, map_fds); + do_test_fixup(test, prog_type, prog, map_fds, &fd_array[1]); if (test->fill_insns) { prog = test->fill_insns; prog_len = test->prog_len; @@ -1492,6 +1609,8 @@ static void do_test_single(struct bpf_test *test, bool unpriv, else opts.log_level = DEFAULT_LIBBPF_LOG_LEVEL; opts.prog_flags = pflags; + if (fd_array[1] != -1) + opts.fd_array = &fd_array[0]; if ((prog_type == BPF_PROG_TYPE_TRACING || prog_type == BPF_PROG_TYPE_LSM) && test->kfunc) { @@ -1684,6 +1803,12 @@ static int do_test(bool unpriv, unsigned int from, unsigned int to) { int i, passes = 0, errors = 0; + /* ensure previous instance of the module is unloaded */ + unload_bpf_testmod(verbose); + + if (load_bpf_testmod(verbose)) + return EXIT_FAILURE; + for (i = from; i < to; i++) { struct bpf_test *test = &tests[i]; @@ -1711,6 +1836,9 @@ static int do_test(bool unpriv, unsigned int from, unsigned int to) } } + unload_bpf_testmod(verbose); + kfuncs_cleanup(); + printf("Summary: %d PASSED, %d SKIPPED, %d FAILED\n", passes, skips, errors); return errors ? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/tools/testing/selftests/bpf/test_xsk.sh b/tools/testing/selftests/bpf/test_xsk.sh index 377fb157a57c..c2ad50f26b63 100755 --- a/tools/testing/selftests/bpf/test_xsk.sh +++ b/tools/testing/selftests/bpf/test_xsk.sh @@ -68,9 +68,6 @@ # Run with verbose output: # sudo ./test_xsk.sh -v # -# Run and dump packet contents: -# sudo ./test_xsk.sh -D -# # Set up veth interfaces and leave them up so xskxceiver can be launched in a debugger: # sudo ./test_xsk.sh -d # @@ -81,11 +78,10 @@ ETH="" -while getopts "vDi:d" flag +while getopts "vi:d" flag do case "${flag}" in v) verbose=1;; - D) dump_pkts=1;; d) debug=1;; i) ETH=${OPTARG};; esac @@ -157,10 +153,6 @@ if [[ $verbose -eq 1 ]]; then ARGS+="-v " fi -if [[ $dump_pkts -eq 1 ]]; then - ARGS="-D " -fi - retval=$? test_status $retval "${TEST_NAME}" diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c index 0b5e0829e5be..8d994884c7b4 100644 --- a/tools/testing/selftests/bpf/testing_helpers.c +++ b/tools/testing/selftests/bpf/testing_helpers.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) /* Copyright (C) 2019 Netronome Systems, Inc. */ /* Copyright (C) 2020 Facebook, Inc. */ +#include <ctype.h> #include <stdlib.h> #include <string.h> #include <errno.h> @@ -8,6 +9,7 @@ #include <bpf/libbpf.h> #include "test_progs.h" #include "testing_helpers.h" +#include <linux/membarrier.h> int parse_num_list(const char *s, bool **num_set, int *num_set_len) { @@ -70,92 +72,168 @@ int parse_num_list(const char *s, bool **num_set, int *num_set_len) return 0; } -int parse_test_list(const char *s, - struct test_filter_set *set, - bool is_glob_pattern) +static int do_insert_test(struct test_filter_set *set, + char *test_str, + char *subtest_str) { - char *input, *state = NULL, *next; - struct test_filter *tmp, *tests = NULL; - int i, j, cnt = 0; + struct test_filter *tmp, *test; + char **ctmp; + int i; - input = strdup(s); - if (!input) + for (i = 0; i < set->cnt; i++) { + test = &set->tests[i]; + + if (strcmp(test_str, test->name) == 0) { + free(test_str); + goto subtest; + } + } + + tmp = realloc(set->tests, sizeof(*test) * (set->cnt + 1)); + if (!tmp) return -ENOMEM; - while ((next = strtok_r(state ? NULL : input, ",", &state))) { - char *subtest_str = strchr(next, '/'); - char *pattern = NULL; - int glob_chars = 0; + set->tests = tmp; + test = &set->tests[set->cnt]; - tmp = realloc(tests, sizeof(*tests) * (cnt + 1)); - if (!tmp) - goto err; - tests = tmp; + test->name = test_str; + test->subtests = NULL; + test->subtest_cnt = 0; - tests[cnt].subtest_cnt = 0; - tests[cnt].subtests = NULL; + set->cnt++; - if (is_glob_pattern) { - pattern = "%s"; - } else { - pattern = "*%s*"; - glob_chars = 2; - } +subtest: + if (!subtest_str) + return 0; - if (subtest_str) { - char **tmp_subtests = NULL; - int subtest_cnt = tests[cnt].subtest_cnt; - - *subtest_str = '\0'; - subtest_str += 1; - tmp_subtests = realloc(tests[cnt].subtests, - sizeof(*tmp_subtests) * - (subtest_cnt + 1)); - if (!tmp_subtests) - goto err; - tests[cnt].subtests = tmp_subtests; - - tests[cnt].subtests[subtest_cnt] = - malloc(strlen(subtest_str) + glob_chars + 1); - if (!tests[cnt].subtests[subtest_cnt]) - goto err; - sprintf(tests[cnt].subtests[subtest_cnt], - pattern, - subtest_str); - - tests[cnt].subtest_cnt++; + for (i = 0; i < test->subtest_cnt; i++) { + if (strcmp(subtest_str, test->subtests[i]) == 0) { + free(subtest_str); + return 0; } + } - tests[cnt].name = malloc(strlen(next) + glob_chars + 1); - if (!tests[cnt].name) - goto err; - sprintf(tests[cnt].name, pattern, next); + ctmp = realloc(test->subtests, + sizeof(*test->subtests) * (test->subtest_cnt + 1)); + if (!ctmp) + return -ENOMEM; - cnt++; + test->subtests = ctmp; + test->subtests[test->subtest_cnt] = subtest_str; + + test->subtest_cnt++; + + return 0; +} + +static int insert_test(struct test_filter_set *set, + char *test_spec, + bool is_glob_pattern) +{ + char *pattern, *subtest_str, *ext_test_str, *ext_subtest_str = NULL; + int glob_chars = 0; + + if (is_glob_pattern) { + pattern = "%s"; + } else { + pattern = "*%s*"; + glob_chars = 2; } - tmp = realloc(set->tests, sizeof(*tests) * (cnt + set->cnt)); - if (!tmp) + subtest_str = strchr(test_spec, '/'); + if (subtest_str) { + *subtest_str = '\0'; + subtest_str += 1; + } + + ext_test_str = malloc(strlen(test_spec) + glob_chars + 1); + if (!ext_test_str) goto err; - memcpy(tmp + set->cnt, tests, sizeof(*tests) * cnt); - set->tests = tmp; - set->cnt += cnt; + sprintf(ext_test_str, pattern, test_spec); - free(tests); - free(input); - return 0; + if (subtest_str) { + ext_subtest_str = malloc(strlen(subtest_str) + glob_chars + 1); + if (!ext_subtest_str) + goto err; + + sprintf(ext_subtest_str, pattern, subtest_str); + } + + return do_insert_test(set, ext_test_str, ext_subtest_str); err: - for (i = 0; i < cnt; i++) { - for (j = 0; j < tests[i].subtest_cnt; j++) - free(tests[i].subtests[j]); + free(ext_test_str); + free(ext_subtest_str); + + return -ENOMEM; +} + +int parse_test_list_file(const char *path, + struct test_filter_set *set, + bool is_glob_pattern) +{ + char *buf = NULL, *capture_start, *capture_end, *scan_end; + size_t buflen = 0; + int err = 0; + FILE *f; + + f = fopen(path, "r"); + if (!f) { + err = -errno; + fprintf(stderr, "Failed to open '%s': %d\n", path, err); + return err; + } + + while (getline(&buf, &buflen, f) != -1) { + capture_start = buf; + + while (isspace(*capture_start)) + ++capture_start; + + capture_end = capture_start; + scan_end = capture_start; + + while (*scan_end && *scan_end != '#') { + if (!isspace(*scan_end)) + capture_end = scan_end; + + ++scan_end; + } + + if (capture_end == capture_start) + continue; + + *(++capture_end) = '\0'; + + err = insert_test(set, capture_start, is_glob_pattern); + if (err) + break; + } + + fclose(f); + return err; +} + +int parse_test_list(const char *s, + struct test_filter_set *set, + bool is_glob_pattern) +{ + char *input, *state = NULL, *test_spec; + int err = 0; + + input = strdup(s); + if (!input) + return -ENOMEM; - free(tests[i].name); + while ((test_spec = strtok_r(state ? NULL : input, ",", &state))) { + err = insert_test(set, test_spec, is_glob_pattern); + if (err) + break; } - free(tests); + free(input); - return -ENOMEM; + return err; } __u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info) @@ -249,3 +327,63 @@ __u64 read_perf_max_sample_freq(void) fclose(f); return sample_freq; } + +static int finit_module(int fd, const char *param_values, int flags) +{ + return syscall(__NR_finit_module, fd, param_values, flags); +} + +static int delete_module(const char *name, int flags) +{ + return syscall(__NR_delete_module, name, flags); +} + +int unload_bpf_testmod(bool verbose) +{ + if (kern_sync_rcu()) + fprintf(stdout, "Failed to trigger kernel-side RCU sync!\n"); + if (delete_module("bpf_testmod", 0)) { + if (errno == ENOENT) { + if (verbose) + fprintf(stdout, "bpf_testmod.ko is already unloaded.\n"); + return -1; + } + fprintf(stdout, "Failed to unload bpf_testmod.ko from kernel: %d\n", -errno); + return -1; + } + if (verbose) + fprintf(stdout, "Successfully unloaded bpf_testmod.ko.\n"); + return 0; +} + +int load_bpf_testmod(bool verbose) +{ + int fd; + + if (verbose) + fprintf(stdout, "Loading bpf_testmod.ko...\n"); + + fd = open("bpf_testmod.ko", O_RDONLY); + if (fd < 0) { + fprintf(stdout, "Can't find bpf_testmod.ko kernel module: %d\n", -errno); + return -ENOENT; + } + if (finit_module(fd, "", 0)) { + fprintf(stdout, "Failed to load bpf_testmod.ko into the kernel: %d\n", -errno); + close(fd); + return -EINVAL; + } + close(fd); + + if (verbose) + fprintf(stdout, "Successfully loaded bpf_testmod.ko.\n"); + return 0; +} + +/* + * Trigger synchronize_rcu() in kernel. + */ +int kern_sync_rcu(void) +{ + return syscall(__NR_membarrier, MEMBARRIER_CMD_SHARED, 0, 0); +} diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h index eb8790f928e4..5312323881b6 100644 --- a/tools/testing/selftests/bpf/testing_helpers.h +++ b/tools/testing/selftests/bpf/testing_helpers.h @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ /* Copyright (C) 2020 Facebook, Inc. */ + +#ifndef __TESTING_HELPERS_H +#define __TESTING_HELPERS_H + #include <stdbool.h> #include <bpf/bpf.h> #include <bpf/libbpf.h> @@ -20,5 +24,13 @@ struct test_filter_set; int parse_test_list(const char *s, struct test_filter_set *test_set, bool is_glob_pattern); +int parse_test_list_file(const char *path, + struct test_filter_set *test_set, + bool is_glob_pattern); __u64 read_perf_max_sample_freq(void); +int load_bpf_testmod(bool verbose); +int unload_bpf_testmod(bool verbose); +int kern_sync_rcu(void); + +#endif /* __TESTING_HELPERS_H */ diff --git a/tools/testing/selftests/bpf/verifier/precise.c b/tools/testing/selftests/bpf/verifier/precise.c index 6c03a7d805f9..99272bb890da 100644 --- a/tools/testing/selftests/bpf/verifier/precise.c +++ b/tools/testing/selftests/bpf/verifier/precise.c @@ -38,25 +38,24 @@ .fixup_map_array_48b = { 1 }, .result = VERBOSE_ACCEPT, .errstr = - "26: (85) call bpf_probe_read_kernel#113\ - last_idx 26 first_idx 20\ - regs=4 stack=0 before 25\ - regs=4 stack=0 before 24\ - regs=4 stack=0 before 23\ - regs=4 stack=0 before 22\ - regs=4 stack=0 before 20\ - parent didn't have regs=4 stack=0 marks\ - last_idx 19 first_idx 10\ - regs=4 stack=0 before 19\ - regs=200 stack=0 before 18\ - regs=300 stack=0 before 17\ - regs=201 stack=0 before 15\ - regs=201 stack=0 before 14\ - regs=200 stack=0 before 13\ - regs=200 stack=0 before 12\ - regs=200 stack=0 before 11\ - regs=200 stack=0 before 10\ - parent already had regs=0 stack=0 marks", + "mark_precise: frame0: last_idx 26 first_idx 20\ + mark_precise: frame0: regs=r2 stack= before 25\ + mark_precise: frame0: regs=r2 stack= before 24\ + mark_precise: frame0: regs=r2 stack= before 23\ + mark_precise: frame0: regs=r2 stack= before 22\ + mark_precise: frame0: regs=r2 stack= before 20\ + mark_precise: frame0: parent state regs=r2 stack=:\ + mark_precise: frame0: last_idx 19 first_idx 10\ + mark_precise: frame0: regs=r2,r9 stack= before 19\ + mark_precise: frame0: regs=r9 stack= before 18\ + mark_precise: frame0: regs=r8,r9 stack= before 17\ + mark_precise: frame0: regs=r0,r9 stack= before 15\ + mark_precise: frame0: regs=r0,r9 stack= before 14\ + mark_precise: frame0: regs=r9 stack= before 13\ + mark_precise: frame0: regs=r9 stack= before 12\ + mark_precise: frame0: regs=r9 stack= before 11\ + mark_precise: frame0: regs=r9 stack= before 10\ + mark_precise: frame0: parent state regs= stack=:", }, { "precise: test 2", @@ -100,20 +99,20 @@ .flags = BPF_F_TEST_STATE_FREQ, .errstr = "26: (85) call bpf_probe_read_kernel#113\ - last_idx 26 first_idx 22\ - regs=4 stack=0 before 25\ - regs=4 stack=0 before 24\ - regs=4 stack=0 before 23\ - regs=4 stack=0 before 22\ - parent didn't have regs=4 stack=0 marks\ - last_idx 20 first_idx 20\ - regs=4 stack=0 before 20\ - parent didn't have regs=4 stack=0 marks\ - last_idx 19 first_idx 17\ - regs=4 stack=0 before 19\ - regs=200 stack=0 before 18\ - regs=300 stack=0 before 17\ - parent already had regs=0 stack=0 marks", + mark_precise: frame0: last_idx 26 first_idx 22\ + mark_precise: frame0: regs=r2 stack= before 25\ + mark_precise: frame0: regs=r2 stack= before 24\ + mark_precise: frame0: regs=r2 stack= before 23\ + mark_precise: frame0: regs=r2 stack= before 22\ + mark_precise: frame0: parent state regs=r2 stack=:\ + mark_precise: frame0: last_idx 20 first_idx 20\ + mark_precise: frame0: regs=r2,r9 stack= before 20\ + mark_precise: frame0: parent state regs=r2,r9 stack=:\ + mark_precise: frame0: last_idx 19 first_idx 17\ + mark_precise: frame0: regs=r2,r9 stack= before 19\ + mark_precise: frame0: regs=r9 stack= before 18\ + mark_precise: frame0: regs=r8,r9 stack= before 17\ + mark_precise: frame0: parent state regs= stack=:", }, { "precise: cross frame pruning", @@ -153,15 +152,16 @@ }, .prog_type = BPF_PROG_TYPE_XDP, .flags = BPF_F_TEST_STATE_FREQ, - .errstr = "5: (2d) if r4 > r0 goto pc+0\ - last_idx 5 first_idx 5\ - parent didn't have regs=10 stack=0 marks\ - last_idx 4 first_idx 2\ - regs=10 stack=0 before 4\ - regs=10 stack=0 before 3\ - regs=0 stack=1 before 2\ - last_idx 5 first_idx 5\ - parent didn't have regs=1 stack=0 marks", + .errstr = "mark_precise: frame0: last_idx 5 first_idx 5\ + mark_precise: frame0: parent state regs=r4 stack=:\ + mark_precise: frame0: last_idx 4 first_idx 2\ + mark_precise: frame0: regs=r4 stack= before 4\ + mark_precise: frame0: regs=r4 stack= before 3\ + mark_precise: frame0: regs= stack=-8 before 2\ + mark_precise: frame0: falling back to forcing all scalars precise\ + force_precise: frame0: forcing r0 to be precise\ + mark_precise: frame0: last_idx 5 first_idx 5\ + mark_precise: frame0: parent state regs= stack=:", .result = VERBOSE_ACCEPT, .retval = -1, }, @@ -179,16 +179,19 @@ }, .prog_type = BPF_PROG_TYPE_XDP, .flags = BPF_F_TEST_STATE_FREQ, - .errstr = "last_idx 6 first_idx 6\ - parent didn't have regs=10 stack=0 marks\ - last_idx 5 first_idx 3\ - regs=10 stack=0 before 5\ - regs=10 stack=0 before 4\ - regs=0 stack=1 before 3\ - last_idx 6 first_idx 6\ - parent didn't have regs=1 stack=0 marks\ - last_idx 5 first_idx 3\ - regs=1 stack=0 before 5", + .errstr = "mark_precise: frame0: last_idx 6 first_idx 6\ + mark_precise: frame0: parent state regs=r4 stack=:\ + mark_precise: frame0: last_idx 5 first_idx 3\ + mark_precise: frame0: regs=r4 stack= before 5\ + mark_precise: frame0: regs=r4 stack= before 4\ + mark_precise: frame0: regs= stack=-8 before 3\ + mark_precise: frame0: falling back to forcing all scalars precise\ + force_precise: frame0: forcing r0 to be precise\ + force_precise: frame0: forcing r0 to be precise\ + force_precise: frame0: forcing r0 to be precise\ + force_precise: frame0: forcing r0 to be precise\ + mark_precise: frame0: last_idx 6 first_idx 6\ + mark_precise: frame0: parent state regs= stack=:", .result = VERBOSE_ACCEPT, .retval = -1, }, @@ -217,3 +220,39 @@ .errstr = "invalid access to memory, mem_size=1 off=42 size=8", .result = REJECT, }, +{ + "precise: program doesn't prematurely prune branches", + .insns = { + BPF_ALU64_IMM(BPF_MOV, BPF_REG_6, 0x400), + BPF_ALU64_IMM(BPF_MOV, BPF_REG_7, 0), + BPF_ALU64_IMM(BPF_MOV, BPF_REG_8, 0), + BPF_ALU64_IMM(BPF_MOV, BPF_REG_9, 0x80000000), + BPF_ALU64_IMM(BPF_MOD, BPF_REG_6, 0x401), + BPF_JMP_IMM(BPF_JA, 0, 0, 0), + BPF_JMP_REG(BPF_JLE, BPF_REG_6, BPF_REG_9, 2), + BPF_ALU64_IMM(BPF_MOD, BPF_REG_6, 1), + BPF_ALU64_IMM(BPF_MOV, BPF_REG_9, 0), + BPF_JMP_REG(BPF_JLE, BPF_REG_6, BPF_REG_9, 1), + BPF_ALU64_IMM(BPF_MOV, BPF_REG_6, 0), + BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 0), + BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), + BPF_LD_MAP_FD(BPF_REG_4, 0), + BPF_ALU64_REG(BPF_MOV, BPF_REG_1, BPF_REG_4), + BPF_ALU64_REG(BPF_MOV, BPF_REG_2, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), + BPF_EXIT_INSN(), + BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 10), + BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 8192), + BPF_ALU64_REG(BPF_MOV, BPF_REG_1, BPF_REG_0), + BPF_ALU64_REG(BPF_ADD, BPF_REG_0, BPF_REG_6), + BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_3, 0), + BPF_EXIT_INSN(), + }, + .fixup_map_array_48b = { 13 }, + .prog_type = BPF_PROG_TYPE_XDP, + .result = REJECT, + .errstr = "register with unbounded min value is not allowed", +}, diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c index 1db7185181da..655095810d4a 100644 --- a/tools/testing/selftests/bpf/veristat.c +++ b/tools/testing/selftests/bpf/veristat.c @@ -141,6 +141,7 @@ static struct env { bool verbose; bool debug; bool quiet; + bool force_checkpoints; enum resfmt out_fmt; bool show_version; bool comparison_mode; @@ -209,6 +210,8 @@ static const struct argp_option opts[] = { { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode)" }, { "log-fixed", OPT_LOG_FIXED, NULL, 0, "Disable verifier log rotation" }, { "log-size", OPT_LOG_SIZE, "BYTES", 0, "Customize verifier log size (default to 16MB)" }, + { "test-states", 't', NULL, 0, + "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, { "quiet", 'q', NULL, 0, "Quiet mode" }, { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" }, { "sort", 's', "SPEC", 0, "Specify sort order" }, @@ -284,6 +287,9 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) argp_usage(state); } break; + case 't': + env.force_checkpoints = true; + break; case 'C': env.comparison_mode = true; break; @@ -989,6 +995,9 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf /* increase chances of successful BPF object loading */ fixup_obj(obj, prog, base_filename); + if (env.force_checkpoints) + bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_STATE_FREQ); + err = bpf_object__load(obj); env.progs_processed++; diff --git a/tools/testing/selftests/bpf/xdp_hw_metadata.c b/tools/testing/selftests/bpf/xdp_hw_metadata.c index 987cf0db5ebc..613321eb84c1 100644 --- a/tools/testing/selftests/bpf/xdp_hw_metadata.c +++ b/tools/testing/selftests/bpf/xdp_hw_metadata.c @@ -27,6 +27,7 @@ #include <sys/mman.h> #include <net/if.h> #include <poll.h> +#include <time.h> #include "xdp_metadata.h" @@ -134,18 +135,52 @@ static void refill_rx(struct xsk *xsk, __u64 addr) } } -static void verify_xdp_metadata(void *data) +#define NANOSEC_PER_SEC 1000000000 /* 10^9 */ +static __u64 gettime(clockid_t clock_id) +{ + struct timespec t; + int res; + + /* See man clock_gettime(2) for type of clock_id's */ + res = clock_gettime(clock_id, &t); + + if (res < 0) + error(res, errno, "Error with clock_gettime()"); + + return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec; +} + +static void verify_xdp_metadata(void *data, clockid_t clock_id) { struct xdp_meta *meta; meta = data - sizeof(*meta); - printf("rx_timestamp: %llu\n", meta->rx_timestamp); if (meta->rx_hash_err < 0) printf("No rx_hash err=%d\n", meta->rx_hash_err); else printf("rx_hash: 0x%X with RSS type:0x%X\n", meta->rx_hash, meta->rx_hash_type); + + printf("rx_timestamp: %llu (sec:%0.4f)\n", meta->rx_timestamp, + (double)meta->rx_timestamp / NANOSEC_PER_SEC); + if (meta->rx_timestamp) { + __u64 usr_clock = gettime(clock_id); + __u64 xdp_clock = meta->xdp_timestamp; + __s64 delta_X = xdp_clock - meta->rx_timestamp; + __s64 delta_X2U = usr_clock - xdp_clock; + + printf("XDP RX-time: %llu (sec:%0.4f) delta sec:%0.4f (%0.3f usec)\n", + xdp_clock, (double)xdp_clock / NANOSEC_PER_SEC, + (double)delta_X / NANOSEC_PER_SEC, + (double)delta_X / 1000); + + printf("AF_XDP time: %llu (sec:%0.4f) delta sec:%0.4f (%0.3f usec)\n", + usr_clock, (double)usr_clock / NANOSEC_PER_SEC, + (double)delta_X2U / NANOSEC_PER_SEC, + (double)delta_X2U / 1000); + } + } static void verify_skb_metadata(int fd) @@ -193,7 +228,7 @@ static void verify_skb_metadata(int fd) printf("skb hwtstamp is not found!\n"); } -static int verify_metadata(struct xsk *rx_xsk, int rxq, int server_fd) +static int verify_metadata(struct xsk *rx_xsk, int rxq, int server_fd, clockid_t clock_id) { const struct xdp_desc *rx_desc; struct pollfd fds[rxq + 1]; @@ -243,7 +278,8 @@ static int verify_metadata(struct xsk *rx_xsk, int rxq, int server_fd) addr = xsk_umem__add_offset_to_addr(rx_desc->addr); printf("%p: rx_desc[%u]->addr=%llx addr=%llx comp_addr=%llx\n", xsk, idx, rx_desc->addr, addr, comp_addr); - verify_xdp_metadata(xsk_umem__get_data(xsk->umem_area, addr)); + verify_xdp_metadata(xsk_umem__get_data(xsk->umem_area, addr), + clock_id); xsk_ring_cons__release(&xsk->rx, 1); refill_rx(xsk, comp_addr); } @@ -370,6 +406,7 @@ static void timestamping_enable(int fd, int val) int main(int argc, char *argv[]) { + clockid_t clock_id = CLOCK_TAI; int server_fd = -1; int ret; int i; @@ -443,7 +480,7 @@ int main(int argc, char *argv[]) error(1, -ret, "bpf_xdp_attach"); signal(SIGINT, handle_signal); - ret = verify_metadata(rx_xsk, rxq, server_fd); + ret = verify_metadata(rx_xsk, rxq, server_fd, clock_id); close(server_fd); cleanup(); if (ret) diff --git a/tools/testing/selftests/bpf/xdp_metadata.h b/tools/testing/selftests/bpf/xdp_metadata.h index 0c4624dc6f2f..938a729bd307 100644 --- a/tools/testing/selftests/bpf/xdp_metadata.h +++ b/tools/testing/selftests/bpf/xdp_metadata.h @@ -11,6 +11,7 @@ struct xdp_meta { __u64 rx_timestamp; + __u64 xdp_timestamp; __u32 rx_hash; union { __u32 rx_hash_type; diff --git a/tools/testing/selftests/bpf/xsk.h b/tools/testing/selftests/bpf/xsk.h index 04ed8b544712..8da8d557768b 100644 --- a/tools/testing/selftests/bpf/xsk.h +++ b/tools/testing/selftests/bpf/xsk.h @@ -134,6 +134,11 @@ static inline void xsk_ring_prod__submit(struct xsk_ring_prod *prod, __u32 nb) __atomic_store_n(prod->producer, *prod->producer + nb, __ATOMIC_RELEASE); } +static inline void xsk_ring_prod__cancel(struct xsk_ring_prod *prod, __u32 nb) +{ + prod->cached_prod -= nb; +} + static inline __u32 xsk_ring_cons__peek(struct xsk_ring_cons *cons, __u32 nb, __u32 *idx) { __u32 entries = xsk_cons_nb_avail(cons, nb); diff --git a/tools/testing/selftests/bpf/xskxceiver.c b/tools/testing/selftests/bpf/xskxceiver.c index f144d0604ddf..218d7f694e5c 100644 --- a/tools/testing/selftests/bpf/xskxceiver.c +++ b/tools/testing/selftests/bpf/xskxceiver.c @@ -76,16 +76,13 @@ #include <asm/barrier.h> #include <linux/if_link.h> #include <linux/if_ether.h> -#include <linux/ip.h> #include <linux/mman.h> -#include <linux/udp.h> #include <arpa/inet.h> #include <net/if.h> #include <locale.h> #include <poll.h> #include <pthread.h> #include <signal.h> -#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -94,10 +91,8 @@ #include <sys/socket.h> #include <sys/time.h> #include <sys/types.h> -#include <sys/queue.h> #include <time.h> #include <unistd.h> -#include <stdatomic.h> #include "xsk_xdp_progs.skel.h" #include "xsk.h" @@ -109,10 +104,6 @@ static const char *MAC1 = "\x00\x0A\x56\x9E\xEE\x62"; static const char *MAC2 = "\x00\x0A\x56\x9E\xEE\x61"; -static const char *IP1 = "192.168.100.162"; -static const char *IP2 = "192.168.100.161"; -static const u16 UDP_PORT1 = 2020; -static const u16 UDP_PORT2 = 2121; static void __exit_with_error(int error, const char *file, const char *func, int line) { @@ -147,112 +138,25 @@ static void report_failure(struct test_spec *test) test->fail = true; } -static void memset32_htonl(void *dest, u32 val, u32 size) -{ - u32 *ptr = (u32 *)dest; - int i; - - val = htonl(val); - - for (i = 0; i < (size & (~0x3)); i += 4) - ptr[i >> 2] = val; -} - -/* - * Fold a partial checksum - * This function code has been taken from - * Linux kernel include/asm-generic/checksum.h - */ -static __u16 csum_fold(__u32 csum) -{ - u32 sum = (__force u32)csum; - - sum = (sum & 0xffff) + (sum >> 16); - sum = (sum & 0xffff) + (sum >> 16); - return (__force __u16)~sum; -} - -/* - * This function code has been taken from - * Linux kernel lib/checksum.c - */ -static u32 from64to32(u64 x) -{ - /* add up 32-bit and 32-bit for 32+c bit */ - x = (x & 0xffffffff) + (x >> 32); - /* add up carry.. */ - x = (x & 0xffffffff) + (x >> 32); - return (u32)x; -} - -/* - * This function code has been taken from - * Linux kernel lib/checksum.c - */ -static __u32 csum_tcpudp_nofold(__be32 saddr, __be32 daddr, __u32 len, __u8 proto, __u32 sum) -{ - unsigned long long s = (__force u32)sum; - - s += (__force u32)saddr; - s += (__force u32)daddr; -#ifdef __BIG_ENDIAN__ - s += proto + len; -#else - s += (proto + len) << 8; -#endif - return (__force __u32)from64to32(s); -} - -/* - * This function has been taken from - * Linux kernel include/asm-generic/checksum.h +/* The payload is a word consisting of a packet sequence number in the upper + * 16-bits and a intra packet data sequence number in the lower 16 bits. So the 3rd packet's + * 5th word of data will contain the number (2<<16) | 4 as they are numbered from 0. */ -static __u16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, __u32 len, __u8 proto, __u32 sum) -{ - return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum)); -} - -static u16 udp_csum(u32 saddr, u32 daddr, u32 len, u8 proto, u16 *udp_pkt) +static void write_payload(void *dest, u32 pkt_nb, u32 start, u32 size) { - u32 csum = 0; - u32 cnt = 0; - - /* udp hdr and data */ - for (; cnt < len; cnt += 2) - csum += udp_pkt[cnt >> 1]; + u32 *ptr = (u32 *)dest, i; - return csum_tcpudp_magic(saddr, daddr, len, proto, csum); + start /= sizeof(*ptr); + size /= sizeof(*ptr); + for (i = 0; i < size; i++) + ptr[i] = htonl(pkt_nb << 16 | (i + start)); } static void gen_eth_hdr(struct ifobject *ifobject, struct ethhdr *eth_hdr) { memcpy(eth_hdr->h_dest, ifobject->dst_mac, ETH_ALEN); memcpy(eth_hdr->h_source, ifobject->src_mac, ETH_ALEN); - eth_hdr->h_proto = htons(ETH_P_IP); -} - -static void gen_ip_hdr(struct ifobject *ifobject, struct iphdr *ip_hdr) -{ - ip_hdr->version = IP_PKT_VER; - ip_hdr->ihl = 0x5; - ip_hdr->tos = IP_PKT_TOS; - ip_hdr->tot_len = htons(IP_PKT_SIZE); - ip_hdr->id = 0; - ip_hdr->frag_off = 0; - ip_hdr->ttl = IPDEFTTL; - ip_hdr->protocol = IPPROTO_UDP; - ip_hdr->saddr = ifobject->src_ip; - ip_hdr->daddr = ifobject->dst_ip; - ip_hdr->check = 0; -} - -static void gen_udp_hdr(u32 payload, void *pkt, struct ifobject *ifobject, - struct udphdr *udp_hdr) -{ - udp_hdr->source = htons(ifobject->src_port); - udp_hdr->dest = htons(ifobject->dst_port); - udp_hdr->len = htons(UDP_PKT_SIZE); - memset32_htonl(pkt + PKT_HDR_SIZE, payload, UDP_PKT_DATA_SIZE); + eth_hdr->h_proto = htons(ETH_P_LOOPBACK); } static bool is_umem_valid(struct ifobject *ifobj) @@ -260,19 +164,18 @@ static bool is_umem_valid(struct ifobject *ifobj) return !!ifobj->umem->umem; } -static void gen_udp_csum(struct udphdr *udp_hdr, struct iphdr *ip_hdr) +static u32 mode_to_xdp_flags(enum test_mode mode) { - udp_hdr->check = 0; - udp_hdr->check = - udp_csum(ip_hdr->saddr, ip_hdr->daddr, UDP_PKT_SIZE, IPPROTO_UDP, (u16 *)udp_hdr); + return (mode == TEST_MODE_SKB) ? XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE; } -static u32 mode_to_xdp_flags(enum test_mode mode) +static u64 umem_size(struct xsk_umem_info *umem) { - return (mode == TEST_MODE_SKB) ? XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE; + return umem->num_frames * umem->frame_size; } -static int xsk_configure_umem(struct xsk_umem_info *umem, void *buffer, u64 size) +static int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, + u64 size) { struct xsk_umem_config cfg = { .fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS, @@ -292,9 +195,31 @@ static int xsk_configure_umem(struct xsk_umem_info *umem, void *buffer, u64 size return ret; umem->buffer = buffer; + if (ifobj->shared_umem && ifobj->rx_on) { + umem->base_addr = umem_size(umem); + umem->next_buffer = umem_size(umem); + } + return 0; } +static u64 umem_alloc_buffer(struct xsk_umem_info *umem) +{ + u64 addr; + + addr = umem->next_buffer; + umem->next_buffer += umem->frame_size; + if (umem->next_buffer >= umem->base_addr + umem_size(umem)) + umem->next_buffer = umem->base_addr; + + return addr; +} + +static void umem_reset_alloc(struct xsk_umem_info *umem) +{ + umem->next_buffer = 0; +} + static void enable_busy_poll(struct xsk_socket_info *xsk) { int sock_opt; @@ -354,7 +279,7 @@ static bool ifobj_zc_avail(struct ifobject *ifobject) exit_with_error(ENOMEM); } umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; - ret = xsk_configure_umem(umem, bufs, umem_sz); + ret = xsk_configure_umem(ifobject, umem, bufs, umem_sz); if (ret) exit_with_error(-ret); @@ -380,7 +305,6 @@ out: static struct option long_options[] = { {"interface", required_argument, 0, 'i'}, {"busy-poll", no_argument, 0, 'b'}, - {"dump-pkts", no_argument, 0, 'D'}, {"verbose", no_argument, 0, 'v'}, {0, 0, 0, 0} }; @@ -391,7 +315,6 @@ static void usage(const char *prog) " Usage: %s [OPTIONS]\n" " Options:\n" " -i, --interface Use interface\n" - " -D, --dump-pkts Dump packets L2 - L5\n" " -v, --verbose Verbose output\n" " -b, --busy-poll Enable busy poll\n"; @@ -415,7 +338,7 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj opterr = 0; for (;;) { - c = getopt_long(argc, argv, "i:Dvb", long_options, &option_index); + c = getopt_long(argc, argv, "i:vb", long_options, &option_index); if (c == -1) break; @@ -437,9 +360,6 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj interface_nb++; break; - case 'D': - opt_pkt_dump = true; - break; case 'v': opt_verbose = true; break; @@ -482,9 +402,6 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx, memset(ifobj->umem, 0, sizeof(*ifobj->umem)); ifobj->umem->num_frames = DEFAULT_UMEM_BUFFERS; ifobj->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; - if (ifobj->shared_umem && ifobj->rx_on) - ifobj->umem->base_addr = DEFAULT_UMEM_BUFFERS * - XSK_UMEM__DEFAULT_FRAME_SIZE; for (j = 0; j < MAX_SOCKETS; j++) { memset(&ifobj->xsk_arr[j], 0, sizeof(ifobj->xsk_arr[j])); @@ -554,24 +471,24 @@ static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *x static void pkt_stream_reset(struct pkt_stream *pkt_stream) { if (pkt_stream) - pkt_stream->rx_pkt_nb = 0; + pkt_stream->current_pkt_nb = 0; } -static struct pkt *pkt_stream_get_pkt(struct pkt_stream *pkt_stream, u32 pkt_nb) +static struct pkt *pkt_stream_get_next_tx_pkt(struct pkt_stream *pkt_stream) { - if (pkt_nb >= pkt_stream->nb_pkts) + if (pkt_stream->current_pkt_nb >= pkt_stream->nb_pkts) return NULL; - return &pkt_stream->pkts[pkt_nb]; + return &pkt_stream->pkts[pkt_stream->current_pkt_nb++]; } static struct pkt *pkt_stream_get_next_rx_pkt(struct pkt_stream *pkt_stream, u32 *pkts_sent) { - while (pkt_stream->rx_pkt_nb < pkt_stream->nb_pkts) { + while (pkt_stream->current_pkt_nb < pkt_stream->nb_pkts) { (*pkts_sent)++; - if (pkt_stream->pkts[pkt_stream->rx_pkt_nb].valid) - return &pkt_stream->pkts[pkt_stream->rx_pkt_nb++]; - pkt_stream->rx_pkt_nb++; + if (pkt_stream->pkts[pkt_stream->current_pkt_nb].valid) + return &pkt_stream->pkts[pkt_stream->current_pkt_nb++]; + pkt_stream->current_pkt_nb++; } return NULL; } @@ -616,9 +533,21 @@ static struct pkt_stream *__pkt_stream_alloc(u32 nb_pkts) return pkt_stream; } -static void pkt_set(struct xsk_umem_info *umem, struct pkt *pkt, u64 addr, u32 len) +static u32 ceil_u32(u32 a, u32 b) +{ + return (a + b - 1) / b; +} + +static u32 pkt_nb_frags(u32 frame_size, struct pkt *pkt) +{ + if (!pkt || !pkt->valid) + return 1; + return ceil_u32(pkt->len, frame_size); +} + +static void pkt_set(struct xsk_umem_info *umem, struct pkt *pkt, int offset, u32 len) { - pkt->addr = addr + umem->base_addr; + pkt->offset = offset; pkt->len = len; if (len > umem->frame_size - XDP_PACKET_HEADROOM - MIN_PKT_SIZE * 2 - umem->frame_headroom) pkt->valid = false; @@ -626,6 +555,11 @@ static void pkt_set(struct xsk_umem_info *umem, struct pkt *pkt, u64 addr, u32 l pkt->valid = true; } +static u32 pkt_get_buffer_len(struct xsk_umem_info *umem, u32 len) +{ + return ceil_u32(len, umem->frame_size) * umem->frame_size; +} + static struct pkt_stream *pkt_stream_generate(struct xsk_umem_info *umem, u32 nb_pkts, u32 pkt_len) { struct pkt_stream *pkt_stream; @@ -635,10 +569,13 @@ static struct pkt_stream *pkt_stream_generate(struct xsk_umem_info *umem, u32 nb if (!pkt_stream) exit_with_error(ENOMEM); + pkt_stream->nb_pkts = nb_pkts; + pkt_stream->max_pkt_len = pkt_len; for (i = 0; i < nb_pkts; i++) { - pkt_set(umem, &pkt_stream->pkts[i], (i % umem->num_frames) * umem->frame_size, - pkt_len); - pkt_stream->pkts[i].payload = i; + struct pkt *pkt = &pkt_stream->pkts[i]; + + pkt_set(umem, pkt, 0, pkt_len); + pkt->pkt_nb = i; } return pkt_stream; @@ -669,8 +606,7 @@ static void __pkt_stream_replace_half(struct ifobject *ifobj, u32 pkt_len, pkt_stream = pkt_stream_clone(umem, ifobj->pkt_stream); for (i = 1; i < ifobj->pkt_stream->nb_pkts; i += 2) - pkt_set(umem, &pkt_stream->pkts[i], - (i % umem->num_frames) * umem->frame_size + offset, pkt_len); + pkt_set(umem, &pkt_stream->pkts[i], offset, pkt_len); ifobj->pkt_stream = pkt_stream; } @@ -694,30 +630,31 @@ static void pkt_stream_receive_half(struct test_spec *test) pkt_stream->pkts[i].valid = false; } -static struct pkt *pkt_generate(struct ifobject *ifobject, u32 pkt_nb) +static u64 pkt_get_addr(struct pkt *pkt, struct xsk_umem_info *umem) { - struct pkt *pkt = pkt_stream_get_pkt(ifobject->pkt_stream, pkt_nb); - struct udphdr *udp_hdr; - struct ethhdr *eth_hdr; - struct iphdr *ip_hdr; - void *data; + if (!pkt->valid) + return pkt->offset; + return pkt->offset + umem_alloc_buffer(umem); +} - if (!pkt) - return NULL; - if (!pkt->valid || pkt->len < MIN_PKT_SIZE) - return pkt; +static void pkt_generate(struct ifobject *ifobject, u64 addr, u32 len, u32 pkt_nb, + u32 bytes_written) +{ + void *data = xsk_umem__get_data(ifobject->umem->buffer, addr); - data = xsk_umem__get_data(ifobject->umem->buffer, pkt->addr); - udp_hdr = (struct udphdr *)(data + sizeof(struct ethhdr) + sizeof(struct iphdr)); - ip_hdr = (struct iphdr *)(data + sizeof(struct ethhdr)); - eth_hdr = (struct ethhdr *)data; + if (len < MIN_PKT_SIZE) + return; - gen_udp_hdr(pkt_nb, data, ifobject, udp_hdr); - gen_ip_hdr(ifobject, ip_hdr); - gen_udp_csum(udp_hdr, ip_hdr); - gen_eth_hdr(ifobject, eth_hdr); + if (!bytes_written) { + gen_eth_hdr(ifobject, data); - return pkt; + len -= PKT_HDR_SIZE; + data += PKT_HDR_SIZE; + } else { + bytes_written -= PKT_HDR_SIZE; + } + + write_payload(data, pkt_nb, bytes_written, len); } static void __pkt_stream_generate_custom(struct ifobject *ifobj, @@ -731,10 +668,14 @@ static void __pkt_stream_generate_custom(struct ifobject *ifobj, exit_with_error(ENOMEM); for (i = 0; i < nb_pkts; i++) { - pkt_stream->pkts[i].addr = pkts[i].addr + ifobj->umem->base_addr; - pkt_stream->pkts[i].len = pkts[i].len; - pkt_stream->pkts[i].payload = i; - pkt_stream->pkts[i].valid = pkts[i].valid; + struct pkt *pkt = &pkt_stream->pkts[i]; + + pkt->offset = pkts[i].offset; + pkt->len = pkts[i].len; + pkt->pkt_nb = i; + pkt->valid = pkts[i].valid; + if (pkt->len > pkt_stream->max_pkt_len) + pkt_stream->max_pkt_len = pkt->len; } ifobj->pkt_stream = pkt_stream; @@ -746,53 +687,62 @@ static void pkt_stream_generate_custom(struct test_spec *test, struct pkt *pkts, __pkt_stream_generate_custom(test->ifobj_rx, pkts, nb_pkts); } -static void pkt_dump(void *pkt, u32 len) -{ - char s[INET_ADDRSTRLEN]; - struct ethhdr *ethhdr; - struct udphdr *udphdr; - struct iphdr *iphdr; - u32 payload, i; - - ethhdr = pkt; - iphdr = pkt + sizeof(*ethhdr); - udphdr = pkt + sizeof(*ethhdr) + sizeof(*iphdr); - - /*extract L2 frame */ - fprintf(stdout, "DEBUG>> L2: dst mac: "); - for (i = 0; i < ETH_ALEN; i++) - fprintf(stdout, "%02X", ethhdr->h_dest[i]); - - fprintf(stdout, "\nDEBUG>> L2: src mac: "); - for (i = 0; i < ETH_ALEN; i++) - fprintf(stdout, "%02X", ethhdr->h_source[i]); - - /*extract L3 frame */ - fprintf(stdout, "\nDEBUG>> L3: ip_hdr->ihl: %02X\n", iphdr->ihl); - fprintf(stdout, "DEBUG>> L3: ip_hdr->saddr: %s\n", - inet_ntop(AF_INET, &iphdr->saddr, s, sizeof(s))); - fprintf(stdout, "DEBUG>> L3: ip_hdr->daddr: %s\n", - inet_ntop(AF_INET, &iphdr->daddr, s, sizeof(s))); - /*extract L4 frame */ - fprintf(stdout, "DEBUG>> L4: udp_hdr->src: %d\n", ntohs(udphdr->source)); - fprintf(stdout, "DEBUG>> L4: udp_hdr->dst: %d\n", ntohs(udphdr->dest)); - /*extract L5 frame */ - payload = ntohl(*((u32 *)(pkt + PKT_HDR_SIZE))); +static void pkt_print_data(u32 *data, u32 cnt) +{ + u32 i; + + for (i = 0; i < cnt; i++) { + u32 seqnum, pkt_nb; - fprintf(stdout, "DEBUG>> L5: payload: %d\n", payload); - fprintf(stdout, "---------------------------------------\n"); + seqnum = ntohl(*data) & 0xffff; + pkt_nb = ntohl(*data) >> 16; + fprintf(stdout, "%u:%u ", pkt_nb, seqnum); + data++; + } } -static bool is_offset_correct(struct xsk_umem_info *umem, struct pkt_stream *pkt_stream, u64 addr, - u64 pkt_stream_addr) +static void pkt_dump(void *pkt, u32 len, bool eth_header) +{ + struct ethhdr *ethhdr = pkt; + u32 i, *data; + + if (eth_header) { + /*extract L2 frame */ + fprintf(stdout, "DEBUG>> L2: dst mac: "); + for (i = 0; i < ETH_ALEN; i++) + fprintf(stdout, "%02X", ethhdr->h_dest[i]); + + fprintf(stdout, "\nDEBUG>> L2: src mac: "); + for (i = 0; i < ETH_ALEN; i++) + fprintf(stdout, "%02X", ethhdr->h_source[i]); + + data = pkt + PKT_HDR_SIZE; + } else { + data = pkt; + } + + /*extract L5 frame */ + fprintf(stdout, "\nDEBUG>> L5: seqnum: "); + pkt_print_data(data, PKT_DUMP_NB_TO_PRINT); + fprintf(stdout, "...."); + if (len > PKT_DUMP_NB_TO_PRINT * sizeof(u32)) { + fprintf(stdout, "\n.... "); + pkt_print_data(data + len / sizeof(u32) - PKT_DUMP_NB_TO_PRINT, + PKT_DUMP_NB_TO_PRINT); + } + fprintf(stdout, "\n---------------------------------------\n"); +} + +static bool is_offset_correct(struct xsk_umem_info *umem, struct pkt *pkt, u64 addr) { u32 headroom = umem->unaligned_mode ? 0 : umem->frame_headroom; - u32 offset = addr % umem->frame_size, expected_offset = 0; + u32 offset = addr % umem->frame_size, expected_offset; + int pkt_offset = pkt->valid ? pkt->offset : 0; - if (!pkt_stream->use_addr_for_fill) - pkt_stream_addr = 0; + if (!umem->unaligned_mode) + pkt_offset = 0; - expected_offset += (pkt_stream_addr + headroom + XDP_PACKET_HEADROOM) % umem->frame_size; + expected_offset = (pkt_offset + headroom + XDP_PACKET_HEADROOM) % umem->frame_size; if (offset == expected_offset) return true; @@ -806,9 +756,9 @@ static bool is_metadata_correct(struct pkt *pkt, void *buffer, u64 addr) void *data = xsk_umem__get_data(buffer, addr); struct xdp_info *meta = data - sizeof(struct xdp_info); - if (meta->count != pkt->payload) { + if (meta->count != pkt->pkt_nb) { ksft_print_msg("[%s] expected meta_count [%d], got meta_count [%d]\n", - __func__, pkt->payload, meta->count); + __func__, pkt->pkt_nb, meta->count); return false; } @@ -818,11 +768,11 @@ static bool is_metadata_correct(struct pkt *pkt, void *buffer, u64 addr) static bool is_pkt_valid(struct pkt *pkt, void *buffer, u64 addr, u32 len) { void *data = xsk_umem__get_data(buffer, addr); - struct iphdr *iphdr = (struct iphdr *)(data + sizeof(struct ethhdr)); + u32 seqnum, pkt_data; if (!pkt) { ksft_print_msg("[%s] too many packets received\n", __func__); - return false; + goto error; } if (len < MIN_PKT_SIZE || pkt->len < MIN_PKT_SIZE) { @@ -833,28 +783,23 @@ static bool is_pkt_valid(struct pkt *pkt, void *buffer, u64 addr, u32 len) if (pkt->len != len) { ksft_print_msg("[%s] expected length [%d], got length [%d]\n", __func__, pkt->len, len); - return false; + goto error; } - if (iphdr->version == IP_PKT_VER && iphdr->tos == IP_PKT_TOS) { - u32 seqnum = ntohl(*((u32 *)(data + PKT_HDR_SIZE))); - - if (opt_pkt_dump) - pkt_dump(data, PKT_SIZE); + pkt_data = ntohl(*((u32 *)(data + PKT_HDR_SIZE))); + seqnum = pkt_data >> 16; - if (pkt->payload != seqnum) { - ksft_print_msg("[%s] expected seqnum [%d], got seqnum [%d]\n", - __func__, pkt->payload, seqnum); - return false; - } - } else { - ksft_print_msg("Invalid frame received: "); - ksft_print_msg("[IP_PKT_VER: %02X], [IP_PKT_TOS: %02X]\n", iphdr->version, - iphdr->tos); - return false; + if (pkt->pkt_nb != seqnum) { + ksft_print_msg("[%s] expected seqnum [%d], got seqnum [%d]\n", + __func__, pkt->pkt_nb, seqnum); + goto error; } return true; + +error: + pkt_dump(data, len, true); + return false; } static void kick_tx(struct xsk_socket_info *xsk) @@ -976,7 +921,7 @@ static int receive_pkts(struct test_spec *test, struct pollfd *fds) addr = xsk_umem__add_offset_to_addr(addr); if (!is_pkt_valid(pkt, umem->buffer, addr, desc->len) || - !is_offset_correct(umem, pkt_stream, addr, pkt->addr) || + !is_offset_correct(umem, pkt, addr) || (ifobj->use_metadata && !is_metadata_correct(pkt, umem->buffer, addr))) return TEST_FAILURE; @@ -992,8 +937,6 @@ static int receive_pkts(struct test_spec *test, struct pollfd *fds) pthread_mutex_lock(&pacing_mutex); pkts_in_flight -= pkts_sent; - if (pkts_in_flight < umem->num_frames) - pthread_cond_signal(&pacing_cond); pthread_mutex_unlock(&pacing_mutex); pkts_sent = 0; } @@ -1001,14 +944,21 @@ static int receive_pkts(struct test_spec *test, struct pollfd *fds) return TEST_PASS; } -static int __send_pkts(struct ifobject *ifobject, u32 *pkt_nb, struct pollfd *fds, - bool timeout) +static int __send_pkts(struct ifobject *ifobject, struct pollfd *fds, bool timeout) { struct xsk_socket_info *xsk = ifobject->xsk; + struct xsk_umem_info *umem = ifobject->umem; + u32 i, idx = 0, valid_pkts = 0, buffer_len; bool use_poll = ifobject->use_poll; - u32 i, idx = 0, valid_pkts = 0; int ret; + buffer_len = pkt_get_buffer_len(umem, ifobject->pkt_stream->max_pkt_len); + /* pkts_in_flight might be negative if many invalid packets are sent */ + if (pkts_in_flight >= (int)((umem_size(umem) - BATCH_SIZE * buffer_len) / buffer_len)) { + kick_tx(xsk); + return TEST_CONTINUE; + } + while (xsk_ring_prod__reserve(&xsk->tx, BATCH_SIZE, &idx) < BATCH_SIZE) { if (use_poll) { ret = poll(fds, 1, POLL_TMOUT); @@ -1034,25 +984,21 @@ static int __send_pkts(struct ifobject *ifobject, u32 *pkt_nb, struct pollfd *fd for (i = 0; i < BATCH_SIZE; i++) { struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i); - struct pkt *pkt = pkt_generate(ifobject, *pkt_nb); + struct pkt *pkt = pkt_stream_get_next_tx_pkt(ifobject->pkt_stream); if (!pkt) break; - tx_desc->addr = pkt->addr; + tx_desc->addr = pkt_get_addr(pkt, umem); tx_desc->len = pkt->len; - (*pkt_nb)++; - if (pkt->valid) + if (pkt->valid) { valid_pkts++; + pkt_generate(ifobject, tx_desc->addr, tx_desc->len, pkt->pkt_nb, 0); + } } pthread_mutex_lock(&pacing_mutex); pkts_in_flight += valid_pkts; - /* pkts_in_flight might be negative if many invalid packets are sent */ - if (pkts_in_flight >= (int)(ifobject->umem->num_frames - BATCH_SIZE)) { - kick_tx(xsk); - pthread_cond_wait(&pacing_cond, &pacing_mutex); - } pthread_mutex_unlock(&pacing_mutex); xsk_ring_prod__submit(&xsk->tx, i); @@ -1088,18 +1034,21 @@ static void wait_for_tx_completion(struct xsk_socket_info *xsk) static int send_pkts(struct test_spec *test, struct ifobject *ifobject) { + struct pkt_stream *pkt_stream = ifobject->pkt_stream; bool timeout = !is_umem_valid(test->ifobj_rx); struct pollfd fds = { }; - u32 pkt_cnt = 0, ret; + u32 ret; fds.fd = xsk_socket__fd(ifobject->xsk->xsk); fds.events = POLLOUT; - while (pkt_cnt < ifobject->pkt_stream->nb_pkts) { - ret = __send_pkts(ifobject, &pkt_cnt, &fds, timeout); + while (pkt_stream->current_pkt_nb < pkt_stream->nb_pkts) { + ret = __send_pkts(ifobject, &fds, timeout); + if (ret == TEST_CONTINUE && !test->fail) + continue; if ((ret || test->fail) && !timeout) return TEST_FAILURE; - else if (ret == TEST_PASS && timeout) + if (ret == TEST_PASS && timeout) return ret; } @@ -1249,11 +1198,14 @@ static void thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobje ifobject->xsk = &ifobject->xsk_arr[0]; ifobject->xskmap = test->ifobj_rx->xskmap; memcpy(ifobject->umem, test->ifobj_rx->umem, sizeof(struct xsk_umem_info)); + ifobject->umem->base_addr = 0; } -static void xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream *pkt_stream) +static void xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream *pkt_stream, + bool fill_up) { - u32 idx = 0, i, buffers_to_fill; + u32 rx_frame_size = umem->frame_size - XDP_PACKET_HEADROOM; + u32 idx = 0, filled = 0, buffers_to_fill, nb_pkts; int ret; if (umem->num_frames < XSK_RING_PROD__DEFAULT_NUM_DESCS) @@ -1264,22 +1216,33 @@ static void xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream ret = xsk_ring_prod__reserve(&umem->fq, buffers_to_fill, &idx); if (ret != buffers_to_fill) exit_with_error(ENOSPC); - for (i = 0; i < buffers_to_fill; i++) { - u64 addr; - if (pkt_stream->use_addr_for_fill) { - struct pkt *pkt = pkt_stream_get_pkt(pkt_stream, i); + while (filled < buffers_to_fill) { + struct pkt *pkt = pkt_stream_get_next_rx_pkt(pkt_stream, &nb_pkts); + u64 addr; + u32 i; + + for (i = 0; i < pkt_nb_frags(rx_frame_size, pkt); i++) { + if (!pkt) { + if (!fill_up) + break; + addr = filled * umem->frame_size + umem->base_addr; + } else if (pkt->offset >= 0) { + addr = pkt->offset % umem->frame_size + umem_alloc_buffer(umem); + } else { + addr = pkt->offset + umem_alloc_buffer(umem); + } - if (!pkt) + *xsk_ring_prod__fill_addr(&umem->fq, idx++) = addr; + if (++filled >= buffers_to_fill) break; - addr = pkt->addr; - } else { - addr = i * umem->frame_size; } - - *xsk_ring_prod__fill_addr(&umem->fq, idx++) = addr; } - xsk_ring_prod__submit(&umem->fq, i); + xsk_ring_prod__submit(&umem->fq, filled); + xsk_ring_prod__cancel(&umem->fq, buffers_to_fill - filled); + + pkt_stream_reset(pkt_stream); + umem_reset_alloc(umem); } static void thread_common_ops(struct test_spec *test, struct ifobject *ifobject) @@ -1300,12 +1263,10 @@ static void thread_common_ops(struct test_spec *test, struct ifobject *ifobject) if (bufs == MAP_FAILED) exit_with_error(errno); - ret = xsk_configure_umem(ifobject->umem, bufs, umem_sz); + ret = xsk_configure_umem(ifobject, ifobject->umem, bufs, umem_sz); if (ret) exit_with_error(-ret); - xsk_populate_fill_ring(ifobject->umem, ifobject->pkt_stream); - xsk_configure_socket(test, ifobject, ifobject->umem, false); ifobject->xsk = &ifobject->xsk_arr[0]; @@ -1313,6 +1274,8 @@ static void thread_common_ops(struct test_spec *test, struct ifobject *ifobject) if (!ifobject->rx_on) return; + xsk_populate_fill_ring(ifobject->umem, ifobject->pkt_stream, ifobject->use_fill_ring); + ret = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk->xsk); if (ret) exit_with_error(errno); @@ -1370,12 +1333,8 @@ static void *worker_testapp_validate_rx(void *arg) if (!err && ifobject->validation_func) err = ifobject->validation_func(ifobject); - if (err) { + if (err) report_failure(test); - pthread_mutex_lock(&pacing_mutex); - pthread_cond_signal(&pacing_cond); - pthread_mutex_unlock(&pacing_mutex); - } pthread_exit(NULL); } @@ -1402,11 +1361,20 @@ static void handler(int signum) pthread_exit(NULL); } -static bool xdp_prog_changed(struct test_spec *test, struct ifobject *ifobj) +static bool xdp_prog_changed_rx(struct test_spec *test) { + struct ifobject *ifobj = test->ifobj_rx; + return ifobj->xdp_prog != test->xdp_prog_rx || ifobj->mode != test->mode; } +static bool xdp_prog_changed_tx(struct test_spec *test) +{ + struct ifobject *ifobj = test->ifobj_tx; + + return ifobj->xdp_prog != test->xdp_prog_tx || ifobj->mode != test->mode; +} + static void xsk_reattach_xdp(struct ifobject *ifobj, struct bpf_program *xdp_prog, struct bpf_map *xskmap, enum test_mode mode) { @@ -1433,13 +1401,13 @@ static void xsk_reattach_xdp(struct ifobject *ifobj, struct bpf_program *xdp_pro static void xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_rx, struct ifobject *ifobj_tx) { - if (xdp_prog_changed(test, ifobj_rx)) + if (xdp_prog_changed_rx(test)) xsk_reattach_xdp(ifobj_rx, test->xdp_prog_rx, test->xskmap_rx, test->mode); if (!ifobj_tx || ifobj_tx->shared_umem) return; - if (xdp_prog_changed(test, ifobj_tx)) + if (xdp_prog_changed_tx(test)) xsk_reattach_xdp(ifobj_tx, test->xdp_prog_tx, test->xskmap_tx, test->mode); } @@ -1448,9 +1416,11 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i { pthread_t t0, t1; - if (ifobj2) + if (ifobj2) { if (pthread_barrier_init(&barr, NULL, 2)) exit_with_error(errno); + pkt_stream_reset(ifobj2->pkt_stream); + } test->current_step++; pkt_stream_reset(ifobj1->pkt_stream); @@ -1493,6 +1463,12 @@ static int testapp_validate_traffic(struct test_spec *test) struct ifobject *ifobj_rx = test->ifobj_rx; struct ifobject *ifobj_tx = test->ifobj_tx; + if ((ifobj_rx->umem->unaligned_mode && !ifobj_rx->unaligned_supp) || + (ifobj_tx->umem->unaligned_mode && !ifobj_tx->unaligned_supp)) { + ksft_test_result_skip("No huge pages present.\n"); + return TEST_SKIP; + } + xsk_attach_xdp_progs(test, ifobj_rx, ifobj_tx); return __testapp_validate_traffic(test, ifobj_rx, ifobj_tx); } @@ -1502,16 +1478,18 @@ static int testapp_validate_traffic_single_thread(struct test_spec *test, struct return __testapp_validate_traffic(test, ifobj, NULL); } -static void testapp_teardown(struct test_spec *test) +static int testapp_teardown(struct test_spec *test) { int i; test_spec_set_name(test, "TEARDOWN"); for (i = 0; i < MAX_TEARDOWN_ITER; i++) { if (testapp_validate_traffic(test)) - return; + return TEST_FAILURE; test_spec_reset(test); } + + return TEST_PASS; } static void swap_directions(struct ifobject **ifobj1, struct ifobject **ifobj2) @@ -1526,20 +1504,23 @@ static void swap_directions(struct ifobject **ifobj1, struct ifobject **ifobj2) *ifobj2 = tmp_ifobj; } -static void testapp_bidi(struct test_spec *test) +static int testapp_bidi(struct test_spec *test) { + int res; + test_spec_set_name(test, "BIDIRECTIONAL"); test->ifobj_tx->rx_on = true; test->ifobj_rx->tx_on = true; test->total_steps = 2; if (testapp_validate_traffic(test)) - return; + return TEST_FAILURE; print_verbose("Switching Tx/Rx vectors\n"); swap_directions(&test->ifobj_rx, &test->ifobj_tx); - __testapp_validate_traffic(test, test->ifobj_rx, test->ifobj_tx); + res = __testapp_validate_traffic(test, test->ifobj_rx, test->ifobj_tx); swap_directions(&test->ifobj_rx, &test->ifobj_tx); + return res; } static void swap_xsk_resources(struct ifobject *ifobj_tx, struct ifobject *ifobj_rx) @@ -1556,160 +1537,139 @@ static void swap_xsk_resources(struct ifobject *ifobj_tx, struct ifobject *ifobj exit_with_error(errno); } -static void testapp_bpf_res(struct test_spec *test) +static int testapp_bpf_res(struct test_spec *test) { test_spec_set_name(test, "BPF_RES"); test->total_steps = 2; test->nb_sockets = 2; if (testapp_validate_traffic(test)) - return; + return TEST_FAILURE; swap_xsk_resources(test->ifobj_tx, test->ifobj_rx); - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_headroom(struct test_spec *test) +static int testapp_headroom(struct test_spec *test) { test_spec_set_name(test, "UMEM_HEADROOM"); test->ifobj_rx->umem->frame_headroom = UMEM_HEADROOM_TEST_SIZE; - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_stats_rx_dropped(struct test_spec *test) +static int testapp_stats_rx_dropped(struct test_spec *test) { test_spec_set_name(test, "STAT_RX_DROPPED"); + if (test->mode == TEST_MODE_ZC) { + ksft_test_result_skip("Can not run RX_DROPPED test for ZC mode\n"); + return TEST_SKIP; + } + pkt_stream_replace_half(test, MIN_PKT_SIZE * 4, 0); test->ifobj_rx->umem->frame_headroom = test->ifobj_rx->umem->frame_size - XDP_PACKET_HEADROOM - MIN_PKT_SIZE * 3; pkt_stream_receive_half(test); test->ifobj_rx->validation_func = validate_rx_dropped; - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_stats_tx_invalid_descs(struct test_spec *test) +static int testapp_stats_tx_invalid_descs(struct test_spec *test) { test_spec_set_name(test, "STAT_TX_INVALID"); pkt_stream_replace_half(test, XSK_UMEM__INVALID_FRAME_SIZE, 0); test->ifobj_tx->validation_func = validate_tx_invalid_descs; - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_stats_rx_full(struct test_spec *test) +static int testapp_stats_rx_full(struct test_spec *test) { test_spec_set_name(test, "STAT_RX_FULL"); - pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, PKT_SIZE); + pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, MIN_PKT_SIZE); test->ifobj_rx->pkt_stream = pkt_stream_generate(test->ifobj_rx->umem, - DEFAULT_UMEM_BUFFERS, PKT_SIZE); - if (!test->ifobj_rx->pkt_stream) - exit_with_error(ENOMEM); + DEFAULT_UMEM_BUFFERS, MIN_PKT_SIZE); test->ifobj_rx->xsk->rxqsize = DEFAULT_UMEM_BUFFERS; test->ifobj_rx->release_rx = false; test->ifobj_rx->validation_func = validate_rx_full; - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_stats_fill_empty(struct test_spec *test) +static int testapp_stats_fill_empty(struct test_spec *test) { test_spec_set_name(test, "STAT_RX_FILL_EMPTY"); - pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, PKT_SIZE); + pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, MIN_PKT_SIZE); test->ifobj_rx->pkt_stream = pkt_stream_generate(test->ifobj_rx->umem, - DEFAULT_UMEM_BUFFERS, PKT_SIZE); - if (!test->ifobj_rx->pkt_stream) - exit_with_error(ENOMEM); + DEFAULT_UMEM_BUFFERS, MIN_PKT_SIZE); test->ifobj_rx->use_fill_ring = false; test->ifobj_rx->validation_func = validate_fill_empty; - testapp_validate_traffic(test); -} - -/* Simple test */ -static bool hugepages_present(struct ifobject *ifobject) -{ - size_t mmap_sz = 2 * ifobject->umem->num_frames * ifobject->umem->frame_size; - void *bufs; - - bufs = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_2MB, -1, 0); - if (bufs == MAP_FAILED) - return false; - - mmap_sz = ceil_u64(mmap_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE; - munmap(bufs, mmap_sz); - return true; + return testapp_validate_traffic(test); } -static bool testapp_unaligned(struct test_spec *test) +static int testapp_unaligned(struct test_spec *test) { - if (!hugepages_present(test->ifobj_tx)) { - ksft_test_result_skip("No 2M huge pages present.\n"); - return false; - } - test_spec_set_name(test, "UNALIGNED_MODE"); test->ifobj_tx->umem->unaligned_mode = true; test->ifobj_rx->umem->unaligned_mode = true; - /* Let half of the packets straddle a buffer boundrary */ - pkt_stream_replace_half(test, PKT_SIZE, -PKT_SIZE / 2); - test->ifobj_rx->pkt_stream->use_addr_for_fill = true; - testapp_validate_traffic(test); + /* Let half of the packets straddle a 4K buffer boundary */ + pkt_stream_replace_half(test, MIN_PKT_SIZE, -MIN_PKT_SIZE / 2); - return true; + return testapp_validate_traffic(test); } -static void testapp_single_pkt(struct test_spec *test) +static int testapp_single_pkt(struct test_spec *test) { - struct pkt pkts[] = {{0x1000, PKT_SIZE, 0, true}}; + struct pkt pkts[] = {{0, MIN_PKT_SIZE, 0, true}}; pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_invalid_desc(struct test_spec *test) +static int testapp_invalid_desc(struct test_spec *test) { - u64 umem_size = test->ifobj_tx->umem->num_frames * test->ifobj_tx->umem->frame_size; + struct xsk_umem_info *umem = test->ifobj_tx->umem; + u64 umem_size = umem->num_frames * umem->frame_size; struct pkt pkts[] = { /* Zero packet address allowed */ - {0, PKT_SIZE, 0, true}, + {0, MIN_PKT_SIZE, 0, true}, /* Allowed packet */ - {0x1000, PKT_SIZE, 0, true}, + {0, MIN_PKT_SIZE, 0, true}, /* Straddling the start of umem */ - {-2, PKT_SIZE, 0, false}, + {-2, MIN_PKT_SIZE, 0, false}, /* Packet too large */ - {0x2000, XSK_UMEM__INVALID_FRAME_SIZE, 0, false}, + {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false}, /* Up to end of umem allowed */ - {umem_size - PKT_SIZE, PKT_SIZE, 0, true}, + {umem_size - MIN_PKT_SIZE - 2 * umem->frame_size, MIN_PKT_SIZE, 0, true}, /* After umem ends */ - {umem_size, PKT_SIZE, 0, false}, + {umem_size, MIN_PKT_SIZE, 0, false}, /* Straddle the end of umem */ - {umem_size - PKT_SIZE / 2, PKT_SIZE, 0, false}, - /* Straddle a page boundrary */ - {0x3000 - PKT_SIZE / 2, PKT_SIZE, 0, false}, - /* Straddle a 2K boundrary */ - {0x3800 - PKT_SIZE / 2, PKT_SIZE, 0, true}, + {umem_size - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false}, + /* Straddle a 4K boundary */ + {0x1000 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false}, + /* Straddle a 2K boundary */ + {0x800 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, true}, /* Valid packet for synch so that something is received */ - {0x4000, PKT_SIZE, 0, true}}; + {0, MIN_PKT_SIZE, 0, true}}; - if (test->ifobj_tx->umem->unaligned_mode) { - /* Crossing a page boundrary allowed */ + if (umem->unaligned_mode) { + /* Crossing a page boundary allowed */ pkts[7].valid = true; } - if (test->ifobj_tx->umem->frame_size == XSK_UMEM__DEFAULT_FRAME_SIZE / 2) { - /* Crossing a 2K frame size boundrary not allowed */ + if (umem->frame_size == XSK_UMEM__DEFAULT_FRAME_SIZE / 2) { + /* Crossing a 2K frame size boundary not allowed */ pkts[8].valid = false; } if (test->ifobj_tx->shared_umem) { - pkts[4].addr += umem_size; - pkts[5].addr += umem_size; - pkts[6].addr += umem_size; + pkts[4].offset += umem_size; + pkts[5].offset += umem_size; + pkts[6].offset += umem_size; } pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_xdp_drop(struct test_spec *test) +static int testapp_xdp_drop(struct test_spec *test) { struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; @@ -1719,10 +1679,10 @@ static void testapp_xdp_drop(struct test_spec *test) skel_rx->maps.xsk, skel_tx->maps.xsk); pkt_stream_receive_half(test); - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_xdp_metadata_count(struct test_spec *test) +static int testapp_xdp_metadata_count(struct test_spec *test) { struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; @@ -1743,10 +1703,10 @@ static void testapp_xdp_metadata_count(struct test_spec *test) if (bpf_map_update_elem(bpf_map__fd(data_map), &key, &count, BPF_ANY)) exit_with_error(errno); - testapp_validate_traffic(test); + return testapp_validate_traffic(test); } -static void testapp_poll_txq_tmout(struct test_spec *test) +static int testapp_poll_txq_tmout(struct test_spec *test) { test_spec_set_name(test, "POLL_TXQ_FULL"); @@ -1754,14 +1714,14 @@ static void testapp_poll_txq_tmout(struct test_spec *test) /* create invalid frame by set umem frame_size and pkt length equal to 2048 */ test->ifobj_tx->umem->frame_size = 2048; pkt_stream_replace(test, 2 * DEFAULT_PKT_CNT, 2048); - testapp_validate_traffic_single_thread(test, test->ifobj_tx); + return testapp_validate_traffic_single_thread(test, test->ifobj_tx); } -static void testapp_poll_rxq_tmout(struct test_spec *test) +static int testapp_poll_rxq_tmout(struct test_spec *test) { test_spec_set_name(test, "POLL_RXQ_EMPTY"); test->ifobj_rx->use_poll = true; - testapp_validate_traffic_single_thread(test, test->ifobj_rx); + return testapp_validate_traffic_single_thread(test, test->ifobj_rx); } static int xsk_load_xdp_programs(struct ifobject *ifobj) @@ -1778,25 +1738,30 @@ static void xsk_unload_xdp_programs(struct ifobject *ifobj) xsk_xdp_progs__destroy(ifobj->xdp_progs); } +/* Simple test */ +static bool hugepages_present(void) +{ + size_t mmap_sz = 2 * DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE; + void *bufs; + + bufs = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, MAP_HUGE_2MB); + if (bufs == MAP_FAILED) + return false; + + mmap_sz = ceil_u64(mmap_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE; + munmap(bufs, mmap_sz); + return true; +} + static void init_iface(struct ifobject *ifobj, const char *dst_mac, const char *src_mac, - const char *dst_ip, const char *src_ip, const u16 dst_port, - const u16 src_port, thread_func_t func_ptr) + thread_func_t func_ptr) { - struct in_addr ip; int err; memcpy(ifobj->dst_mac, dst_mac, ETH_ALEN); memcpy(ifobj->src_mac, src_mac, ETH_ALEN); - inet_aton(dst_ip, &ip); - ifobj->dst_ip = ip.s_addr; - - inet_aton(src_ip, &ip); - ifobj->src_ip = ip.s_addr; - - ifobj->dst_port = dst_port; - ifobj->src_port = src_port; - ifobj->func_ptr = func_ptr; err = xsk_load_xdp_programs(ifobj); @@ -1804,94 +1769,87 @@ static void init_iface(struct ifobject *ifobj, const char *dst_mac, const char * printf("Error loading XDP program\n"); exit_with_error(err); } + + if (hugepages_present()) + ifobj->unaligned_supp = true; } static void run_pkt_test(struct test_spec *test, enum test_mode mode, enum test_type type) { + int ret = TEST_SKIP; + switch (type) { case TEST_TYPE_STATS_RX_DROPPED: - if (mode == TEST_MODE_ZC) { - ksft_test_result_skip("Can not run RX_DROPPED test for ZC mode\n"); - return; - } - testapp_stats_rx_dropped(test); + ret = testapp_stats_rx_dropped(test); break; case TEST_TYPE_STATS_TX_INVALID_DESCS: - testapp_stats_tx_invalid_descs(test); + ret = testapp_stats_tx_invalid_descs(test); break; case TEST_TYPE_STATS_RX_FULL: - testapp_stats_rx_full(test); + ret = testapp_stats_rx_full(test); break; case TEST_TYPE_STATS_FILL_EMPTY: - testapp_stats_fill_empty(test); + ret = testapp_stats_fill_empty(test); break; case TEST_TYPE_TEARDOWN: - testapp_teardown(test); + ret = testapp_teardown(test); break; case TEST_TYPE_BIDI: - testapp_bidi(test); + ret = testapp_bidi(test); break; case TEST_TYPE_BPF_RES: - testapp_bpf_res(test); + ret = testapp_bpf_res(test); break; case TEST_TYPE_RUN_TO_COMPLETION: test_spec_set_name(test, "RUN_TO_COMPLETION"); - testapp_validate_traffic(test); + ret = testapp_validate_traffic(test); break; case TEST_TYPE_RUN_TO_COMPLETION_SINGLE_PKT: test_spec_set_name(test, "RUN_TO_COMPLETION_SINGLE_PKT"); - testapp_single_pkt(test); + ret = testapp_single_pkt(test); break; case TEST_TYPE_RUN_TO_COMPLETION_2K_FRAME: test_spec_set_name(test, "RUN_TO_COMPLETION_2K_FRAME_SIZE"); test->ifobj_tx->umem->frame_size = 2048; test->ifobj_rx->umem->frame_size = 2048; - pkt_stream_replace(test, DEFAULT_PKT_CNT, PKT_SIZE); - testapp_validate_traffic(test); + pkt_stream_replace(test, DEFAULT_PKT_CNT, MIN_PKT_SIZE); + ret = testapp_validate_traffic(test); break; case TEST_TYPE_RX_POLL: test->ifobj_rx->use_poll = true; test_spec_set_name(test, "POLL_RX"); - testapp_validate_traffic(test); + ret = testapp_validate_traffic(test); break; case TEST_TYPE_TX_POLL: test->ifobj_tx->use_poll = true; test_spec_set_name(test, "POLL_TX"); - testapp_validate_traffic(test); + ret = testapp_validate_traffic(test); break; case TEST_TYPE_POLL_TXQ_TMOUT: - testapp_poll_txq_tmout(test); + ret = testapp_poll_txq_tmout(test); break; case TEST_TYPE_POLL_RXQ_TMOUT: - testapp_poll_rxq_tmout(test); + ret = testapp_poll_rxq_tmout(test); break; case TEST_TYPE_ALIGNED_INV_DESC: test_spec_set_name(test, "ALIGNED_INV_DESC"); - testapp_invalid_desc(test); + ret = testapp_invalid_desc(test); break; case TEST_TYPE_ALIGNED_INV_DESC_2K_FRAME: test_spec_set_name(test, "ALIGNED_INV_DESC_2K_FRAME_SIZE"); test->ifobj_tx->umem->frame_size = 2048; test->ifobj_rx->umem->frame_size = 2048; - testapp_invalid_desc(test); + ret = testapp_invalid_desc(test); break; case TEST_TYPE_UNALIGNED_INV_DESC: - if (!hugepages_present(test->ifobj_tx)) { - ksft_test_result_skip("No 2M huge pages present.\n"); - return; - } test_spec_set_name(test, "UNALIGNED_INV_DESC"); test->ifobj_tx->umem->unaligned_mode = true; test->ifobj_rx->umem->unaligned_mode = true; - testapp_invalid_desc(test); + ret = testapp_invalid_desc(test); break; case TEST_TYPE_UNALIGNED_INV_DESC_4K1_FRAME: { u64 page_size, umem_size; - if (!hugepages_present(test->ifobj_tx)) { - ksft_test_result_skip("No 2M huge pages present.\n"); - return; - } test_spec_set_name(test, "UNALIGNED_INV_DESC_4K1_FRAME_SIZE"); /* Odd frame size so the UMEM doesn't end near a page boundary. */ test->ifobj_tx->umem->frame_size = 4001; @@ -1903,29 +1861,28 @@ static void run_pkt_test(struct test_spec *test, enum test_mode mode, enum test_ */ page_size = sysconf(_SC_PAGESIZE); umem_size = test->ifobj_tx->umem->num_frames * test->ifobj_tx->umem->frame_size; - assert(umem_size % page_size > PKT_SIZE); - assert(umem_size % page_size < page_size - PKT_SIZE); - testapp_invalid_desc(test); + assert(umem_size % page_size > MIN_PKT_SIZE); + assert(umem_size % page_size < page_size - MIN_PKT_SIZE); + ret = testapp_invalid_desc(test); break; } case TEST_TYPE_UNALIGNED: - if (!testapp_unaligned(test)) - return; + ret = testapp_unaligned(test); break; case TEST_TYPE_HEADROOM: - testapp_headroom(test); + ret = testapp_headroom(test); break; case TEST_TYPE_XDP_DROP_HALF: - testapp_xdp_drop(test); + ret = testapp_xdp_drop(test); break; case TEST_TYPE_XDP_METADATA_COUNT: - testapp_xdp_metadata_count(test); + ret = testapp_xdp_metadata_count(test); break; default: break; } - if (!test->fail) + if (ret == TEST_PASS) ksft_test_result_pass("PASS: %s %s%s\n", mode_string(test), busy_poll_string(test), test->name); pkt_stream_restore_default(test); @@ -2030,14 +1987,12 @@ int main(int argc, char **argv) modes++; } - init_iface(ifobj_rx, MAC1, MAC2, IP1, IP2, UDP_PORT1, UDP_PORT2, - worker_testapp_validate_rx); - init_iface(ifobj_tx, MAC2, MAC1, IP2, IP1, UDP_PORT2, UDP_PORT1, - worker_testapp_validate_tx); + init_iface(ifobj_rx, MAC1, MAC2, worker_testapp_validate_rx); + init_iface(ifobj_tx, MAC2, MAC1, worker_testapp_validate_tx); test_spec_init(&test, ifobj_tx, ifobj_rx, 0); - tx_pkt_stream_default = pkt_stream_generate(ifobj_tx->umem, DEFAULT_PKT_CNT, PKT_SIZE); - rx_pkt_stream_default = pkt_stream_generate(ifobj_rx->umem, DEFAULT_PKT_CNT, PKT_SIZE); + tx_pkt_stream_default = pkt_stream_generate(ifobj_tx->umem, DEFAULT_PKT_CNT, MIN_PKT_SIZE); + rx_pkt_stream_default = pkt_stream_generate(ifobj_rx->umem, DEFAULT_PKT_CNT, MIN_PKT_SIZE); if (!tx_pkt_stream_default || !rx_pkt_stream_default) exit_with_error(ENOMEM); test.tx_pkt_stream_default = tx_pkt_stream_default; diff --git a/tools/testing/selftests/bpf/xskxceiver.h b/tools/testing/selftests/bpf/xskxceiver.h index c535aeab2ca3..aaf27e067640 100644 --- a/tools/testing/selftests/bpf/xskxceiver.h +++ b/tools/testing/selftests/bpf/xskxceiver.h @@ -30,22 +30,14 @@ #define TEST_PASS 0 #define TEST_FAILURE -1 #define TEST_CONTINUE 1 +#define TEST_SKIP 2 #define MAX_INTERFACES 2 #define MAX_INTERFACE_NAME_CHARS 16 #define MAX_SOCKETS 2 #define MAX_TEST_NAME_SIZE 32 #define MAX_TEARDOWN_ITER 10 -#define PKT_HDR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \ - sizeof(struct udphdr)) -#define MIN_ETH_PKT_SIZE 64 -#define ETH_FCS_SIZE 4 -#define MIN_PKT_SIZE (MIN_ETH_PKT_SIZE - ETH_FCS_SIZE) -#define PKT_SIZE (MIN_PKT_SIZE) -#define IP_PKT_SIZE (PKT_SIZE - sizeof(struct ethhdr)) -#define IP_PKT_VER 0x4 -#define IP_PKT_TOS 0x9 -#define UDP_PKT_SIZE (IP_PKT_SIZE - sizeof(struct iphdr)) -#define UDP_PKT_DATA_SIZE (UDP_PKT_SIZE - sizeof(struct udphdr)) +#define PKT_HDR_SIZE (sizeof(struct ethhdr) + 2) /* Just to align the data in the packet */ +#define MIN_PKT_SIZE 64 #define USLEEP_MAX 10000 #define SOCK_RECONF_CTR 10 #define BATCH_SIZE 64 @@ -57,6 +49,7 @@ #define UMEM_HEADROOM_TEST_SIZE 128 #define XSK_UMEM__INVALID_FRAME_SIZE (XSK_UMEM__DEFAULT_FRAME_SIZE + 1) #define HUGEPAGE_SIZE (2 * 1024 * 1024) +#define PKT_DUMP_NB_TO_PRINT 16 #define print_verbose(x...) do { if (opt_verbose) ksft_print_msg(x); } while (0) @@ -93,13 +86,13 @@ enum test_type { TEST_TYPE_MAX }; -static bool opt_pkt_dump; static bool opt_verbose; struct xsk_umem_info { struct xsk_ring_prod fq; struct xsk_ring_cons cq; struct xsk_umem *umem; + u64 next_buffer; u32 num_frames; u32 frame_headroom; void *buffer; @@ -118,17 +111,17 @@ struct xsk_socket_info { }; struct pkt { - u64 addr; + int offset; u32 len; - u32 payload; + u32 pkt_nb; bool valid; }; struct pkt_stream { u32 nb_pkts; - u32 rx_pkt_nb; + u32 current_pkt_nb; struct pkt *pkts; - bool use_addr_for_fill; + u32 max_pkt_len; }; struct ifobject; @@ -148,11 +141,7 @@ struct ifobject { struct bpf_program *xdp_prog; enum test_mode mode; int ifindex; - u32 dst_ip; - u32 src_ip; u32 bind_flags; - u16 src_port; - u16 dst_port; bool tx_on; bool rx_on; bool use_poll; @@ -161,6 +150,7 @@ struct ifobject { bool release_rx; bool shared_umem; bool use_metadata; + bool unaligned_supp; u8 dst_mac[ETH_ALEN]; u8 src_mac[ETH_ALEN]; }; @@ -184,7 +174,6 @@ struct test_spec { pthread_barrier_t barr; pthread_mutex_t pacing_mutex = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t pacing_cond = PTHREAD_COND_INITIALIZER; int pkts_in_flight; diff --git a/tools/testing/selftests/cachestat/.gitignore b/tools/testing/selftests/cachestat/.gitignore new file mode 100644 index 000000000000..d6c30b43a4bb --- /dev/null +++ b/tools/testing/selftests/cachestat/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +test_cachestat diff --git a/tools/testing/selftests/cachestat/Makefile b/tools/testing/selftests/cachestat/Makefile new file mode 100644 index 000000000000..fca73aaa7d14 --- /dev/null +++ b/tools/testing/selftests/cachestat/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := test_cachestat + +CFLAGS += $(KHDR_INCLUDES) +CFLAGS += -Wall +CFLAGS += -lrt + +include ../lib.mk diff --git a/tools/testing/selftests/cachestat/test_cachestat.c b/tools/testing/selftests/cachestat/test_cachestat.c new file mode 100644 index 000000000000..54d09b820ed4 --- /dev/null +++ b/tools/testing/selftests/cachestat/test_cachestat.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdbool.h> +#include <linux/kernel.h> +#include <linux/mman.h> +#include <sys/mman.h> +#include <sys/shm.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> + +#include "../kselftest.h" + +static const char * const dev_files[] = { + "/dev/zero", "/dev/null", "/dev/urandom", + "/proc/version", "/proc" +}; +static const int cachestat_nr = 451; + +void print_cachestat(struct cachestat *cs) +{ + ksft_print_msg( + "Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\n", + cs->nr_cache, cs->nr_dirty, cs->nr_writeback, + cs->nr_evicted, cs->nr_recently_evicted); +} + +bool write_exactly(int fd, size_t filesize) +{ + int random_fd = open("/dev/urandom", O_RDONLY); + char *cursor, *data; + int remained; + bool ret; + + if (random_fd < 0) { + ksft_print_msg("Unable to access urandom.\n"); + ret = false; + goto out; + } + + data = malloc(filesize); + if (!data) { + ksft_print_msg("Unable to allocate data.\n"); + ret = false; + goto close_random_fd; + } + + remained = filesize; + cursor = data; + + while (remained) { + ssize_t read_len = read(random_fd, cursor, remained); + + if (read_len <= 0) { + ksft_print_msg("Unable to read from urandom.\n"); + ret = false; + goto out_free_data; + } + + remained -= read_len; + cursor += read_len; + } + + /* write random data to fd */ + remained = filesize; + cursor = data; + while (remained) { + ssize_t write_len = write(fd, cursor, remained); + + if (write_len <= 0) { + ksft_print_msg("Unable write random data to file.\n"); + ret = false; + goto out_free_data; + } + + remained -= write_len; + cursor += write_len; + } + + ret = true; +out_free_data: + free(data); +close_random_fd: + close(random_fd); +out: + return ret; +} + +/* + * Open/create the file at filename, (optionally) write random data to it + * (exactly num_pages), then test the cachestat syscall on this file. + * + * If test_fsync == true, fsync the file, then check the number of dirty + * pages. + */ +bool test_cachestat(const char *filename, bool write_random, bool create, + bool test_fsync, unsigned long num_pages, int open_flags, + mode_t open_mode) +{ + size_t PS = sysconf(_SC_PAGESIZE); + int filesize = num_pages * PS; + bool ret = true; + long syscall_ret; + struct cachestat cs; + struct cachestat_range cs_range = { 0, filesize }; + + int fd = open(filename, open_flags, open_mode); + + if (fd == -1) { + ksft_print_msg("Unable to create/open file.\n"); + ret = false; + goto out; + } else { + ksft_print_msg("Create/open %s\n", filename); + } + + if (write_random) { + if (!write_exactly(fd, filesize)) { + ksft_print_msg("Unable to access urandom.\n"); + ret = false; + goto out1; + } + } + + syscall_ret = syscall(cachestat_nr, fd, &cs_range, &cs, 0); + + ksft_print_msg("Cachestat call returned %ld\n", syscall_ret); + + if (syscall_ret) { + ksft_print_msg("Cachestat returned non-zero.\n"); + ret = false; + goto out1; + + } else { + print_cachestat(&cs); + + if (write_random) { + if (cs.nr_cache + cs.nr_evicted != num_pages) { + ksft_print_msg( + "Total number of cached and evicted pages is off.\n"); + ret = false; + } + } + } + + if (test_fsync) { + if (fsync(fd)) { + ksft_print_msg("fsync fails.\n"); + ret = false; + } else { + syscall_ret = syscall(cachestat_nr, fd, &cs_range, &cs, 0); + + ksft_print_msg("Cachestat call (after fsync) returned %ld\n", + syscall_ret); + + if (!syscall_ret) { + print_cachestat(&cs); + + if (cs.nr_dirty) { + ret = false; + ksft_print_msg( + "Number of dirty should be zero after fsync.\n"); + } + } else { + ksft_print_msg("Cachestat (after fsync) returned non-zero.\n"); + ret = false; + goto out1; + } + } + } + +out1: + close(fd); + + if (create) + remove(filename); +out: + return ret; +} + +bool test_cachestat_shmem(void) +{ + size_t PS = sysconf(_SC_PAGESIZE); + size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */ + int syscall_ret; + size_t compute_len = PS * 512; + struct cachestat_range cs_range = { PS, compute_len }; + char *filename = "tmpshmcstat"; + struct cachestat cs; + bool ret = true; + unsigned long num_pages = compute_len / PS; + int fd = shm_open(filename, O_CREAT | O_RDWR, 0600); + + if (fd < 0) { + ksft_print_msg("Unable to create shmem file.\n"); + ret = false; + goto out; + } + + if (ftruncate(fd, filesize)) { + ksft_print_msg("Unable to truncate shmem file.\n"); + ret = false; + goto close_fd; + } + + if (!write_exactly(fd, filesize)) { + ksft_print_msg("Unable to write to shmem file.\n"); + ret = false; + goto close_fd; + } + + syscall_ret = syscall(cachestat_nr, fd, &cs_range, &cs, 0); + + if (syscall_ret) { + ksft_print_msg("Cachestat returned non-zero.\n"); + ret = false; + goto close_fd; + } else { + print_cachestat(&cs); + if (cs.nr_cache + cs.nr_evicted != num_pages) { + ksft_print_msg( + "Total number of cached and evicted pages is off.\n"); + ret = false; + } + } + +close_fd: + shm_unlink(filename); +out: + return ret; +} + +int main(void) +{ + int ret = 0; + + for (int i = 0; i < 5; i++) { + const char *dev_filename = dev_files[i]; + + if (test_cachestat(dev_filename, false, false, false, + 4, O_RDONLY, 0400)) + ksft_test_result_pass("cachestat works with %s\n", dev_filename); + else { + ksft_test_result_fail("cachestat fails with %s\n", dev_filename); + ret = 1; + } + } + + if (test_cachestat("tmpfilecachestat", true, true, + true, 4, O_CREAT | O_RDWR, 0400 | 0600)) + ksft_test_result_pass("cachestat works with a normal file\n"); + else { + ksft_test_result_fail("cachestat fails with normal file\n"); + ret = 1; + } + + if (test_cachestat_shmem()) + ksft_test_result_pass("cachestat works with a shmem file\n"); + else { + ksft_test_result_fail("cachestat fails with a shmem file\n"); + ret = 1; + } + + return ret; +} diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index f4f7c0aef702..c7c9572003a8 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -292,6 +292,7 @@ static int test_memcg_protection(const char *root, bool min) char *children[4] = {NULL}; const char *attribute = min ? "memory.min" : "memory.low"; long c[4]; + long current; int i, attempts; int fd; @@ -400,7 +401,8 @@ static int test_memcg_protection(const char *root, bool min) goto cleanup; } - if (!values_close(cg_read_long(parent[1], "memory.current"), MB(50), 3)) + current = min ? MB(50) : MB(30); + if (!values_close(cg_read_long(parent[1], "memory.current"), current, 3)) goto cleanup; if (!reclaim_until(children[0], MB(10))) @@ -987,7 +989,9 @@ static int tcp_client(const char *cgroup, unsigned short port) char servport[6]; int retries = 0x10; /* nice round number */ int sk, ret; + long allocated; + allocated = cg_read_long(cgroup, "memory.current"); snprintf(servport, sizeof(servport), "%hd", port); ret = getaddrinfo(server, servport, NULL, &ai); if (ret) @@ -1015,7 +1019,8 @@ static int tcp_client(const char *cgroup, unsigned short port) if (current < 0 || sock < 0) goto close_sk; - if (values_close(current, sock, 10)) { + /* exclude the memory not related to socket connection */ + if (values_close(current - allocated, sock, 10)) { ret = KSFT_PASS; break; } diff --git a/tools/testing/selftests/clone3/clone3.c b/tools/testing/selftests/clone3/clone3.c index e495f895a2cd..e60cf4da8fb0 100644 --- a/tools/testing/selftests/clone3/clone3.c +++ b/tools/testing/selftests/clone3/clone3.c @@ -129,7 +129,7 @@ int main(int argc, char *argv[]) uid_t uid = getuid(); ksft_print_header(); - ksft_set_plan(18); + ksft_set_plan(19); test_clone3_supported(); /* Just a simple clone3() should return 0.*/ @@ -198,5 +198,8 @@ int main(int argc, char *argv[]) /* Do a clone3() in a new time namespace */ test_clone3(CLONE_NEWTIME, 0, 0, CLONE3_ARGS_NO_TEST); + /* Do a clone3() with exit signal (SIGCHLD) in flags */ + test_clone3(SIGCHLD, 0, -EINVAL, CLONE3_ARGS_NO_TEST); + ksft_finished(); } diff --git a/tools/testing/selftests/cpufreq/config b/tools/testing/selftests/cpufreq/config index 75e900793e8a..ce5068f5a6a2 100644 --- a/tools/testing/selftests/cpufreq/config +++ b/tools/testing/selftests/cpufreq/config @@ -5,11 +5,3 @@ CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_ONDEMAND=y CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y -CONFIG_DEBUG_RT_MUTEXES=y -CONFIG_DEBUG_PLIST=y -CONFIG_DEBUG_SPINLOCK=y -CONFIG_DEBUG_MUTEXES=y -CONFIG_DEBUG_LOCK_ALLOC=y -CONFIG_PROVE_LOCKING=y -CONFIG_LOCKDEP=y -CONFIG_DEBUG_ATOMIC_SLEEP=y diff --git a/tools/testing/selftests/damon/config b/tools/testing/selftests/damon/config new file mode 100644 index 000000000000..0daf38974eb0 --- /dev/null +++ b/tools/testing/selftests/damon/config @@ -0,0 +1,7 @@ +CONFIG_DAMON=y +CONFIG_DAMON_SYSFS=y +CONFIG_DAMON_DBGFS=y +CONFIG_DAMON_PADDR=y +CONFIG_DAMON_VADDR=y +CONFIG_DAMON_RECLAIM=y +CONFIG_DAMON_LRU_SORT=y diff --git a/tools/testing/selftests/drivers/net/bonding/bond-eth-type-change.sh b/tools/testing/selftests/drivers/net/bonding/bond-eth-type-change.sh index 5cdd22048ba7..862e947e17c7 100755 --- a/tools/testing/selftests/drivers/net/bonding/bond-eth-type-change.sh +++ b/tools/testing/selftests/drivers/net/bonding/bond-eth-type-change.sh @@ -53,7 +53,6 @@ bond_test_enslave_type_change() # restore ARPHRD_ETHER type by enslaving such device ip link set dev "$devbond2" master "$devbond0" check_err $? "could not enslave $devbond2 to $devbond0" - ip link set dev "$devbond1" nomaster bond_check_flags "$devbond0" diff --git a/tools/testing/selftests/drivers/net/mlxsw/egress_vid_classification.sh b/tools/testing/selftests/drivers/net/mlxsw/egress_vid_classification.sh index 0cf9e47e3209..a5c2aec52898 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/egress_vid_classification.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/egress_vid_classification.sh @@ -16,10 +16,9 @@ # +----------------|--+ +--|-----------------+ # | | # +----------------|-------------------------|-----------------+ -# | SW | | | +# | SW $swp1 + + $swp2 | +# | | | | # | +--------------|-------------------------|---------------+ | -# | | $swp1 + + $swp2 | | -# | | | | | | # | | $swp1.10 + + $swp2.10 | | # | | | | # | | br0 | | diff --git a/tools/testing/selftests/drivers/net/mlxsw/extack.sh b/tools/testing/selftests/drivers/net/mlxsw/extack.sh index 7a0a99c1d22f..6fd422d38fe8 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/extack.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/extack.sh @@ -35,7 +35,9 @@ netdev_pre_up_test() { RET=0 - ip link add name br1 up type bridge vlan_filtering 0 mcast_snooping 0 + ip link add name br1 type bridge vlan_filtering 0 mcast_snooping 0 + ip link set dev br1 addrgenmode none + ip link set dev br1 up ip link add name vx1 up type vxlan id 1000 \ local 192.0.2.17 remote 192.0.2.18 \ dstport 4789 nolearning noudpcsum tos inherit ttl 100 @@ -46,7 +48,9 @@ netdev_pre_up_test() ip link set dev $swp1 master br1 check_err $? - ip link add name br2 up type bridge vlan_filtering 0 mcast_snooping 0 + ip link add name br2 type bridge vlan_filtering 0 mcast_snooping 0 + ip link set dev br2 addrgenmode none + ip link set dev br2 up ip link add name vx2 up type vxlan id 2000 \ local 192.0.2.17 remote 192.0.2.18 \ dstport 4789 nolearning noudpcsum tos inherit ttl 100 @@ -81,7 +85,9 @@ vxlan_vlan_add_test() { RET=0 - ip link add name br1 up type bridge vlan_filtering 1 mcast_snooping 0 + ip link add name br1 type bridge vlan_filtering 1 mcast_snooping 0 + ip link set dev br1 addrgenmode none + ip link set dev br1 up # Unsupported configuration: mlxsw demands VXLAN with "noudpcsum". ip link add name vx1 up type vxlan id 1000 \ @@ -117,7 +123,9 @@ vxlan_bridge_create_test() dstport 4789 tos inherit ttl 100 # Test with VLAN-aware bridge. - ip link add name br1 up type bridge vlan_filtering 1 mcast_snooping 0 + ip link add name br1 type bridge vlan_filtering 1 mcast_snooping 0 + ip link set dev br1 addrgenmode none + ip link set dev br1 up ip link set dev vx1 master br1 @@ -142,8 +150,12 @@ bridge_create_test() { RET=0 - ip link add name br1 up type bridge vlan_filtering 1 - ip link add name br2 up type bridge vlan_filtering 1 + ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none + ip link set dev br1 up + ip link add name br2 type bridge vlan_filtering 1 + ip link set dev br2 addrgenmode none + ip link set dev br2 up ip link set dev $swp1 master br1 check_err $? diff --git a/tools/testing/selftests/drivers/net/mlxsw/ingress_rif_conf_1d.sh b/tools/testing/selftests/drivers/net/mlxsw/ingress_rif_conf_1d.sh index df2b09966886..7d7f862c809c 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/ingress_rif_conf_1d.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/ingress_rif_conf_1d.sh @@ -15,10 +15,9 @@ # +----------------|--+ +--|-----------------+ # | | # +----------------|-------------------------|-----------------+ -# | SW | | | +# | SW $swp1 + + $swp2 | +# | | | | # | +--------------|-------------------------|---------------+ | -# | | $swp1 + + $swp2 | | -# | | | | | | # | | $swp1.10 + + $swp2.10 | | # | | | | # | | br0 | | diff --git a/tools/testing/selftests/drivers/net/mlxsw/mirror_gre_scale.sh b/tools/testing/selftests/drivers/net/mlxsw/mirror_gre_scale.sh index e00435753008..e5589e2fca85 100644 --- a/tools/testing/selftests/drivers/net/mlxsw/mirror_gre_scale.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/mirror_gre_scale.sh @@ -165,6 +165,7 @@ mirror_gre_setup_prepare() simple_if_init $h3 ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none ip link set dev br1 up ip link set dev $swp1 master br1 diff --git a/tools/testing/selftests/drivers/net/mlxsw/one_armed_router.sh b/tools/testing/selftests/drivers/net/mlxsw/one_armed_router.sh index f02d83e94576..fca0e1e642c6 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/one_armed_router.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/one_armed_router.sh @@ -83,7 +83,8 @@ h2_destroy() switch_create() { - ip link add name br0 type bridge mcast_snooping 0 + ip link add name br0 address $(mac_get $swp1) \ + type bridge mcast_snooping 0 ip link set dev br0 up ip link set dev $swp1 master br0 diff --git a/tools/testing/selftests/drivers/net/mlxsw/q_in_q_veto.sh b/tools/testing/selftests/drivers/net/mlxsw/q_in_q_veto.sh index 7edaed8eb86a..00d55b0e98c1 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/q_in_q_veto.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/q_in_q_veto.sh @@ -48,6 +48,7 @@ create_vlan_upper_on_top_of_bridge() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol $bridge_proto vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link set dev $swp1 master br0 @@ -88,6 +89,7 @@ create_8021ad_vlan_upper_on_top_bridge_port() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev $swp1 master br0 ip link set dev br0 up @@ -155,6 +157,7 @@ create_vlan_upper_on_top_front_panel_enslaved_to_8021ad_bridge() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link set dev $swp1 master br0 @@ -177,6 +180,7 @@ create_vlan_upper_on_top_lag_enslaved_to_8021ad_bridge() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link add name bond1 type bond mode 802.3ad @@ -203,6 +207,7 @@ enslave_front_panel_with_vlan_upper_to_8021ad_bridge() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link add name $swp1.100 link $swp1 type vlan id 100 @@ -225,6 +230,7 @@ enslave_lag_with_vlan_upper_to_8021ad_bridge() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link add name bond1 type bond mode 802.3ad @@ -252,6 +258,7 @@ add_ip_address_to_8021ad_bridge() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link set dev $swp1 master br0 @@ -273,6 +280,7 @@ switch_bridge_protocol_from_8021q_to_8021ad() ip link add dev br0 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link set dev $swp1 master br0 diff --git a/tools/testing/selftests/drivers/net/mlxsw/qos_dscp_bridge.sh b/tools/testing/selftests/drivers/net/mlxsw/qos_dscp_bridge.sh index 87c41f5727c9..914c63d6318a 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/qos_dscp_bridge.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/qos_dscp_bridge.sh @@ -65,6 +65,7 @@ h2_destroy() switch_create() { ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none ip link set dev br1 up ip link set dev $swp1 master br1 ip link set dev $swp1 up diff --git a/tools/testing/selftests/drivers/net/mlxsw/qos_ets_strict.sh b/tools/testing/selftests/drivers/net/mlxsw/qos_ets_strict.sh index 690d8daa71b4..fee74f215cec 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/qos_ets_strict.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/qos_ets_strict.sh @@ -138,11 +138,15 @@ switch_create() vlan_create $swp3 111 vlan_create $swp3 222 - ip link add name br111 up type bridge vlan_filtering 0 + ip link add name br111 type bridge vlan_filtering 0 + ip link set dev br111 addrgenmode none + ip link set dev br111 up ip link set dev $swp1.111 master br111 ip link set dev $swp3.111 master br111 - ip link add name br222 up type bridge vlan_filtering 0 + ip link add name br222 type bridge vlan_filtering 0 + ip link set dev br222 addrgenmode none + ip link set dev br222 up ip link set dev $swp2.222 master br222 ip link set dev $swp3.222 master br222 diff --git a/tools/testing/selftests/drivers/net/mlxsw/qos_mc_aware.sh b/tools/testing/selftests/drivers/net/mlxsw/qos_mc_aware.sh index c8e55fa91660..6d892de43fa8 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/qos_mc_aware.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/qos_mc_aware.sh @@ -135,11 +135,13 @@ switch_create() prio bands 8 priomap 7 7 7 7 7 7 7 7 ip link add name br1 type bridge vlan_filtering 0 + ip link set dev br1 addrgenmode none ip link set dev br1 up ip link set dev $swp1 master br1 ip link set dev $swp3 master br1 ip link add name br111 type bridge vlan_filtering 0 + ip link set dev br111 addrgenmode none ip link set dev br111 up ip link set dev $swp2.111 master br111 ip link set dev $swp3.111 master br111 diff --git a/tools/testing/selftests/drivers/net/mlxsw/spectrum/q_in_vni_veto.sh b/tools/testing/selftests/drivers/net/mlxsw/spectrum/q_in_vni_veto.sh index f0443b1b05b9..60753d46a2d4 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/spectrum/q_in_vni_veto.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/spectrum/q_in_vni_veto.sh @@ -34,6 +34,7 @@ create_vxlan_on_top_of_8021ad_bridge() ip link add dev br0 type bridge vlan_filtering 1 vlan_protocol 802.1ad \ vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br0 addrgenmode none ip link set dev br0 up ip link add name vx100 type vxlan id 1000 local 192.0.2.17 dstport \ diff --git a/tools/testing/selftests/drivers/net/mlxsw/vxlan.sh b/tools/testing/selftests/drivers/net/mlxsw/vxlan.sh index 99a332b712f0..4687b0a7dffb 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/vxlan.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/vxlan.sh @@ -444,8 +444,12 @@ offload_indication_setup_create() { # Create a simple setup with two bridges, each with a VxLAN device # and one local port - ip link add name br0 up type bridge mcast_snooping 0 - ip link add name br1 up type bridge mcast_snooping 0 + ip link add name br0 type bridge mcast_snooping 0 + ip link set dev br0 addrgenmode none + ip link set dev br0 up + ip link add name br1 type bridge mcast_snooping 0 + ip link set dev br1 addrgenmode none + ip link set dev br1 up ip link set dev $swp1 master br0 ip link set dev $swp2 master br1 @@ -646,8 +650,12 @@ offload_indication_decap_route_test() RET=0 - ip link add name br0 up type bridge mcast_snooping 0 - ip link add name br1 up type bridge mcast_snooping 0 + ip link add name br0 type bridge mcast_snooping 0 + ip link set dev br0 addrgenmode none + ip link set dev br0 up + ip link add name br1 type bridge mcast_snooping 0 + ip link set dev br1 addrgenmode none + ip link set dev br1 up ip link set dev $swp1 master br0 ip link set dev $swp2 master br1 ip link set dev vxlan0 master br0 @@ -780,7 +788,9 @@ __offload_indication_join_vxlan_first() offload_indication_join_vxlan_first() { - ip link add dev br0 up type bridge mcast_snooping 0 + ip link add dev br0 type bridge mcast_snooping 0 + ip link set dev br0 addrgenmode none + ip link set dev br0 up ip link add name vxlan0 up type vxlan id 10 nolearning $UDPCSUM_FLAFS \ ttl 20 tos inherit local $LOCAL_IP_1 dstport 4789 @@ -815,7 +825,9 @@ __offload_indication_join_vxlan_last() offload_indication_join_vxlan_last() { - ip link add dev br0 up type bridge mcast_snooping 0 + ip link add dev br0 type bridge mcast_snooping 0 + ip link set dev br0 addrgenmode none + ip link set dev br0 up ip link add name vxlan0 up type vxlan id 10 nolearning $UDPCSUM_FLAFS \ ttl 20 tos inherit local $LOCAL_IP_1 dstport 4789 @@ -842,6 +854,7 @@ sanitization_vlan_aware_test() RET=0 ip link add dev br0 type bridge mcast_snooping 0 vlan_filtering 1 + ip link set dev br0 addrgenmode none ip link add name vxlan10 up master br0 type vxlan id 10 nolearning \ $UDPCSUM_FLAFS ttl 20 tos inherit local $LOCAL_IP_1 dstport 4789 @@ -915,8 +928,10 @@ offload_indication_vlan_aware_setup_create() { # Create a simple setup with two VxLAN devices and a single VLAN-aware # bridge - ip link add name br0 up type bridge mcast_snooping 0 vlan_filtering 1 \ + ip link add name br0 type bridge mcast_snooping 0 vlan_filtering 1 \ vlan_default_pvid 0 + ip link set dev br0 addrgenmode none + ip link set dev br0 up ip link set dev $swp1 master br0 @@ -1060,8 +1075,10 @@ offload_indication_vlan_aware_decap_route_test() offload_indication_vlan_aware_join_vxlan_first() { - ip link add dev br0 up type bridge mcast_snooping 0 \ + ip link add dev br0 type bridge mcast_snooping 0 \ vlan_filtering 1 vlan_default_pvid 1 + ip link set dev br0 addrgenmode none + ip link set dev br0 up ip link add name vxlan0 up type vxlan id 10 nolearning $UDPCSUM_FLAFS \ ttl 20 tos inherit local $LOCAL_IP_1 dstport 4789 @@ -1073,8 +1090,10 @@ offload_indication_vlan_aware_join_vxlan_first() offload_indication_vlan_aware_join_vxlan_last() { - ip link add dev br0 up type bridge mcast_snooping 0 \ + ip link add dev br0 type bridge mcast_snooping 0 \ vlan_filtering 1 vlan_default_pvid 1 + ip link set dev br0 addrgenmode none + ip link set dev br0 up ip link add name vxlan0 up type vxlan id 10 nolearning $UDPCSUM_FLAFS \ ttl 20 tos inherit local $LOCAL_IP_1 dstport 4789 @@ -1091,8 +1110,10 @@ offload_indication_vlan_aware_l3vni_test() RET=0 sysctl_set net.ipv6.conf.default.disable_ipv6 1 - ip link add dev br0 up type bridge mcast_snooping 0 \ + ip link add dev br0 type bridge mcast_snooping 0 \ vlan_filtering 1 vlan_default_pvid 0 + ip link set dev br0 addrgenmode none + ip link set dev br0 up ip link add name vxlan0 up type vxlan id 10 nolearning $UDPCSUM_FLAFS \ ttl 20 tos inherit local $LOCAL_IP_1 dstport 4789 diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest index 2506621e75df..cb5f18c06593 100755 --- a/tools/testing/selftests/ftrace/ftracetest +++ b/tools/testing/selftests/ftrace/ftracetest @@ -301,7 +301,7 @@ ktaptest() { # result comment comment="# $comment" fi - echo $CASENO $result $INSTANCE$CASENAME $comment + echo $result $CASENO $INSTANCE$CASENAME $comment } eval_result() { # sigval diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_btfarg.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_btfarg.tc new file mode 100644 index 000000000000..b89de1771655 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_btfarg.tc @@ -0,0 +1,58 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Generic dynamic event - add/remove probes with BTF arguments +# requires: dynamic_events "<argname>":README + +KPROBES= +FPROBES= + +if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then + KPROBES=yes +fi +if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then + FPROBES=yes +fi + +if [ -z "$KPROBES" -a "$FPROBES" ] ; then + exit_unsupported +fi + +echo 0 > events/enable +echo > dynamic_events + +TP=kfree + +if [ "$FPROBES" ] ; then +echo "f:fpevent $TP object" >> dynamic_events +echo "t:tpevent $TP ptr" >> dynamic_events + +grep -q "fpevent.*object=object" dynamic_events +grep -q "tpevent.*ptr=ptr" dynamic_events + +echo > dynamic_events + +echo "f:fpevent $TP "'$arg1' >> dynamic_events +grep -q "fpevent.*object=object" dynamic_events +echo > dynamic_events + +echo "f:fpevent $TP "'$arg*' >> dynamic_events +echo "t:tpevent $TP "'$arg*' >> dynamic_events + +grep -q "fpevent.*object=object" dynamic_events +grep -q "tpevent.*ptr=ptr" dynamic_events +! grep -q "tpevent.*_data" dynamic_events +fi + +echo > dynamic_events + +if [ "$KPROBES" ] ; then +echo "p:kpevent $TP object" >> dynamic_events +grep -q "kpevent.*object=object" dynamic_events + +echo > dynamic_events + +echo "p:kpevent $TP "'$arg*' >> dynamic_events +grep -q "kpevent.*object=object" dynamic_events +fi + +clear_trace diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe.tc new file mode 100644 index 000000000000..dc25bcf4f9e2 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe.tc @@ -0,0 +1,26 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Generic dynamic event - add/remove fprobe events +# requires: dynamic_events "f[:[<group>/][<event>]] <func-name>[%return] [<args>]":README + +echo 0 > events/enable +echo > dynamic_events + +PLACE=$FUNCTION_FORK + +echo "f:myevent1 $PLACE" >> dynamic_events +echo "f:myevent2 $PLACE%return" >> dynamic_events + +grep -q myevent1 dynamic_events +grep -q myevent2 dynamic_events +test -d events/fprobes/myevent1 +test -d events/fprobes/myevent2 + +echo "-:myevent2" >> dynamic_events + +grep -q myevent1 dynamic_events +! grep -q myevent2 dynamic_events + +echo > dynamic_events + +clear_trace diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_tprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_tprobe.tc new file mode 100644 index 000000000000..155792eaeee5 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_tprobe.tc @@ -0,0 +1,27 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Generic dynamic event - add/remove tracepoint probe events +# requires: dynamic_events "t[:[<group>/][<event>]] <tracepoint> [<args>]":README + +echo 0 > events/enable +echo > dynamic_events + +TRACEPOINT1=kmem_cache_alloc +TRACEPOINT2=kmem_cache_free + +echo "t:myevent1 $TRACEPOINT1" >> dynamic_events +echo "t:myevent2 $TRACEPOINT2" >> dynamic_events + +grep -q myevent1 dynamic_events +grep -q myevent2 dynamic_events +test -d events/tracepoints/myevent1 +test -d events/tracepoints/myevent2 + +echo "-:myevent2" >> dynamic_events + +grep -q myevent1 dynamic_events +! grep -q myevent2 dynamic_events + +echo > dynamic_events + +clear_trace diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc new file mode 100644 index 000000000000..812f5b3f6055 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc @@ -0,0 +1,111 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Fprobe event parser error log check +# requires: dynamic_events "f[:[<group>/][<event>]] <func-name>[%return] [<args>]":README + +check_error() { # command-with-error-pos-by-^ + ftrace_errlog_check 'trace_fprobe' "$1" 'dynamic_events' +} + +case `uname -m` in +x86_64|i[3456]86) + REG=%ax ;; +aarch64) + REG=%x0 ;; +*) + REG=%r0 ;; +esac + +check_error 'f^100 vfs_read' # MAXACT_NO_KPROBE +check_error 'f^1a111 vfs_read' # BAD_MAXACT +check_error 'f^100000 vfs_read' # MAXACT_TOO_BIG + +check_error 'f ^non_exist_func' # BAD_PROBE_ADDR (enoent) +check_error 'f ^vfs_read+10' # BAD_PROBE_ADDR +check_error 'f:^/bar vfs_read' # NO_GROUP_NAME +check_error 'f:^12345678901234567890123456789012345678901234567890123456789012345/bar vfs_read' # GROUP_TOO_LONG + +check_error 'f:^foo.1/bar vfs_read' # BAD_GROUP_NAME +check_error 'f:^ vfs_read' # NO_EVENT_NAME +check_error 'f:foo/^12345678901234567890123456789012345678901234567890123456789012345 vfs_read' # EVENT_TOO_LONG +check_error 'f:foo/^bar.1 vfs_read' # BAD_EVENT_NAME + +check_error 'f vfs_read ^$retval' # RETVAL_ON_PROBE +check_error 'f vfs_read ^$stack10000' # BAD_STACK_NUM + +check_error 'f vfs_read ^$arg10000' # BAD_ARG_NUM + +check_error 'f vfs_read ^$none_var' # BAD_VAR +check_error 'f vfs_read ^'$REG # BAD_VAR + +check_error 'f vfs_read ^@12345678abcde' # BAD_MEM_ADDR +check_error 'f vfs_read ^@+10' # FILE_ON_KPROBE + +grep -q "imm-value" README && \ +check_error 'f vfs_read arg1=\^x' # BAD_IMM +grep -q "imm-string" README && \ +check_error 'f vfs_read arg1=\"abcd^' # IMMSTR_NO_CLOSE + +check_error 'f vfs_read ^+0@0)' # DEREF_NEED_BRACE +check_error 'f vfs_read ^+0ab1(@0)' # BAD_DEREF_OFFS +check_error 'f vfs_read +0(+0(@0^)' # DEREF_OPEN_BRACE + +if grep -A1 "fetcharg:" README | grep -q '\$comm' ; then +check_error 'f vfs_read +0(^$comm)' # COMM_CANT_DEREF +fi + +check_error 'f vfs_read ^&1' # BAD_FETCH_ARG + + +# We've introduced this limitation with array support +if grep -q ' <type>\\\[<array-size>\\\]' README; then +check_error 'f vfs_read +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS? +check_error 'f vfs_read +0(@11):u8[10^' # ARRAY_NO_CLOSE +check_error 'f vfs_read +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX +check_error 'f vfs_read +0(@11):u8[^10a]' # BAD_ARRAY_NUM +check_error 'f vfs_read +0(@11):u8[^256]' # ARRAY_TOO_BIG +fi + +check_error 'f vfs_read @11:^unknown_type' # BAD_TYPE +check_error 'f vfs_read $stack0:^string' # BAD_STRING +check_error 'f vfs_read @11:^b10@a/16' # BAD_BITFIELD + +check_error 'f vfs_read ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_LOG +check_error 'f vfs_read ^=@11' # NO_ARG_NAME +check_error 'f vfs_read ^var.1=@11' # BAD_ARG_NAME +check_error 'f vfs_read var1=@11 ^var1=@12' # USED_ARG_NAME +check_error 'f vfs_read ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG +check_error 'f vfs_read arg1=^' # NO_ARG_BODY + + +# multiprobe errors +if grep -q "Create/append/" README && grep -q "imm-value" README; then +echo "f:fprobes/testevent $FUNCTION_FORK" > dynamic_events +check_error '^f:fprobes/testevent do_exit%return' # DIFF_PROBE_TYPE + +# Explicitly use printf "%s" to not interpret \1 +printf "%s" "f:fprobes/testevent $FUNCTION_FORK abcd=\\1" > dynamic_events +check_error "f:fprobes/testevent $FUNCTION_FORK ^bcd=\\1" # DIFF_ARG_TYPE +check_error "f:fprobes/testevent $FUNCTION_FORK ^abcd=\\1:u8" # DIFF_ARG_TYPE +check_error "f:fprobes/testevent $FUNCTION_FORK ^abcd=\\\"foo\"" # DIFF_ARG_TYPE +check_error "^f:fprobes/testevent $FUNCTION_FORK abcd=\\1" # SAME_PROBE +fi + +# %return suffix errors +check_error 'f vfs_read^%hoge' # BAD_ADDR_SUFFIX + +# BTF arguments errors +if grep -q "<argname>" README; then +check_error 'f vfs_read args=^$arg*' # BAD_VAR_ARGS +check_error 'f vfs_read +0(^$arg*)' # BAD_VAR_ARGS +check_error 'f vfs_read $arg* ^$arg*' # DOUBLE_ARGS +check_error 'f vfs_read%return ^$arg*' # NOFENTRY_ARGS +check_error 'f vfs_read ^hoge' # NO_BTFARG +check_error 'f kfree ^$arg10' # NO_BTFARG (exceed the number of parameters) +check_error 'f kfree%return ^$retval' # NO_RETVAL +else +check_error 'f vfs_read ^$arg*' # NOSUP_BTFARG +check_error 't kfree ^$arg*' # NOSUP_BTFARG +fi + +exit 0 diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc new file mode 100644 index 000000000000..da117b8f1d12 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc @@ -0,0 +1,82 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Tracepoint probe event parser error log check +# requires: dynamic_events "t[:[<group>/][<event>]] <tracepoint> [<args>]":README + +check_error() { # command-with-error-pos-by-^ + ftrace_errlog_check 'trace_fprobe' "$1" 'dynamic_events' +} + +check_error 't^100 kfree' # BAD_MAXACT_TYPE + +check_error 't ^non_exist_tracepoint' # NO_TRACEPOINT +check_error 't:^/bar kfree' # NO_GROUP_NAME +check_error 't:^12345678901234567890123456789012345678901234567890123456789012345/bar kfree' # GROUP_TOO_LONG + +check_error 't:^foo.1/bar kfree' # BAD_GROUP_NAME +check_error 't:^ kfree' # NO_EVENT_NAME +check_error 't:foo/^12345678901234567890123456789012345678901234567890123456789012345 kfree' # EVENT_TOO_LONG +check_error 't:foo/^bar.1 kfree' # BAD_EVENT_NAME + +check_error 't kfree ^$retval' # RETVAL_ON_PROBE +check_error 't kfree ^$stack10000' # BAD_STACK_NUM + +check_error 't kfree ^$arg10000' # BAD_ARG_NUM + +check_error 't kfree ^$none_var' # BAD_VAR +check_error 't kfree ^%rax' # BAD_VAR + +check_error 't kfree ^@12345678abcde' # BAD_MEM_ADDR +check_error 't kfree ^@+10' # FILE_ON_KPROBE + +grep -q "imm-value" README && \ +check_error 't kfree arg1=\^x' # BAD_IMM +grep -q "imm-string" README && \ +check_error 't kfree arg1=\"abcd^' # IMMSTR_NO_CLOSE + +check_error 't kfree ^+0@0)' # DEREF_NEED_BRACE +check_error 't kfree ^+0ab1(@0)' # BAD_DEREF_OFFS +check_error 't kfree +0(+0(@0^)' # DEREF_OPEN_BRACE + +if grep -A1 "fetcharg:" README | grep -q '\$comm' ; then +check_error 't kfree +0(^$comm)' # COMM_CANT_DEREF +fi + +check_error 't kfree ^&1' # BAD_FETCH_ARG + + +# We've introduced this limitation with array support +if grep -q ' <type>\\\[<array-size>\\\]' README; then +check_error 't kfree +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS? +check_error 't kfree +0(@11):u8[10^' # ARRAY_NO_CLOSE +check_error 't kfree +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX +check_error 't kfree +0(@11):u8[^10a]' # BAD_ARRAY_NUM +check_error 't kfree +0(@11):u8[^256]' # ARRAY_TOO_BIG +fi + +check_error 't kfree @11:^unknown_type' # BAD_TYPE +check_error 't kfree $stack0:^string' # BAD_STRING +check_error 't kfree @11:^b10@a/16' # BAD_BITFIELD + +check_error 't kfree ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_LOG +check_error 't kfree ^=@11' # NO_ARG_NAME +check_error 't kfree ^var.1=@11' # BAD_ARG_NAME +check_error 't kfree var1=@11 ^var1=@12' # USED_ARG_NAME +check_error 't kfree ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG +check_error 't kfree arg1=^' # NO_ARG_BODY + + +# multiprobe errors +if grep -q "Create/append/" README && grep -q "imm-value" README; then +echo "t:tracepoint/testevent kfree" > dynamic_events +check_error '^f:tracepoint/testevent kfree' # DIFF_PROBE_TYPE + +# Explicitly use printf "%s" to not interpret \1 +printf "%s" "t:tracepoints/testevent kfree abcd=\\1" > dynamic_events +check_error "t:tracepoints/testevent kfree ^bcd=\\1" # DIFF_ARG_TYPE +check_error "t:tracepoints/testevent kfree ^abcd=\\1:u8" # DIFF_ARG_TYPE +check_error "t:tracepoints/testevent kfree ^abcd=\\\"foo\"" # DIFF_ARG_TYPE +check_error "^t:tracepoints/testevent kfree abcd=\\1" # SAME_PROBE +fi + +exit 0 diff --git a/tools/testing/selftests/ftrace/test.d/filter/event-filter-function.tc b/tools/testing/selftests/ftrace/test.d/filter/event-filter-function.tc index e2ff3bf4df80..2de7c61d1ae3 100644 --- a/tools/testing/selftests/ftrace/test.d/filter/event-filter-function.tc +++ b/tools/testing/selftests/ftrace/test.d/filter/event-filter-function.tc @@ -9,18 +9,33 @@ fail() { #msg exit_fail } -echo "Test event filter function name" +sample_events() { + echo > trace + echo 1 > events/kmem/kmem_cache_free/enable + echo 1 > tracing_on + ls > /dev/null + echo 0 > tracing_on + echo 0 > events/kmem/kmem_cache_free/enable +} + echo 0 > tracing_on echo 0 > events/enable + +echo "Get the most frequently calling function" +sample_events + +target_func=`cut -d: -f3 trace | sed 's/call_site=\([^+]*\)+0x.*/\1/' | sort | uniq -c | sort | tail -n 1 | sed 's/^[ 0-9]*//'` +if [ -z "$target_func" ]; then + exit_fail +fi echo > trace -echo 'call_site.function == exit_mmap' > events/kmem/kmem_cache_free/filter -echo 1 > events/kmem/kmem_cache_free/enable -echo 1 > tracing_on -ls > /dev/null -echo 0 > events/kmem/kmem_cache_free/enable -hitcnt=`grep kmem_cache_free trace| grep exit_mmap | wc -l` -misscnt=`grep kmem_cache_free trace| grep -v exit_mmap | wc -l` +echo "Test event filter function name" +echo "call_site.function == $target_func" > events/kmem/kmem_cache_free/filter +sample_events + +hitcnt=`grep kmem_cache_free trace| grep $target_func | wc -l` +misscnt=`grep kmem_cache_free trace| grep -v $target_func | wc -l` if [ $hitcnt -eq 0 ]; then exit_fail @@ -30,20 +45,14 @@ if [ $misscnt -gt 0 ]; then exit_fail fi -address=`grep ' exit_mmap$' /proc/kallsyms | cut -d' ' -f1` +address=`grep " ${target_func}\$" /proc/kallsyms | cut -d' ' -f1` echo "Test event filter function address" -echo 0 > tracing_on -echo 0 > events/enable -echo > trace echo "call_site.function == 0x$address" > events/kmem/kmem_cache_free/filter -echo 1 > events/kmem/kmem_cache_free/enable -echo 1 > tracing_on -sleep 1 -echo 0 > events/kmem/kmem_cache_free/enable +sample_events -hitcnt=`grep kmem_cache_free trace| grep exit_mmap | wc -l` -misscnt=`grep kmem_cache_free trace| grep -v exit_mmap | wc -l` +hitcnt=`grep kmem_cache_free trace| grep $target_func | wc -l` +misscnt=`grep kmem_cache_free trace| grep -v $target_func | wc -l` if [ $hitcnt -eq 0 ]; then exit_fail diff --git a/tools/testing/selftests/ftrace/test.d/ftrace/fgraph-retval.tc b/tools/testing/selftests/ftrace/test.d/ftrace/fgraph-retval.tc new file mode 100644 index 000000000000..e34c0bdef3ed --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/ftrace/fgraph-retval.tc @@ -0,0 +1,44 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: ftrace - function graph print function return value +# requires: options/funcgraph-retval options/funcgraph-retval-hex function_graph:tracer + +# Make sure that funcgraph-retval works + +fail() { # msg + echo $1 + exit_fail +} + +disable_tracing +clear_trace + +# get self PID, can not use $$, because it is PPID +read PID _ < /proc/self/stat + +[ -f set_ftrace_filter ] && echo proc_reg_write > set_ftrace_filter +[ -f set_ftrace_pid ] && echo ${PID} > set_ftrace_pid +echo function_graph > current_tracer +echo 1 > options/funcgraph-retval + +set +e +enable_tracing +echo > /proc/interrupts +disable_tracing +set -e + +: "Test printing the error code in signed decimal format" +echo 0 > options/funcgraph-retval-hex +count=`cat trace | grep 'proc_reg_write' | grep '= -5' | wc -l` +if [ $count -eq 0 ]; then + fail "Return value can not be printed in signed decimal format" +fi + +: "Test printing the error code in hexadecimal format" +echo 1 > options/funcgraph-retval-hex +count=`cat trace | grep 'proc_reg_write' | grep 'fffffffb' | wc -l` +if [ $count -eq 0 ]; then + fail "Return value can not be printed in hexadecimal format" +fi + +exit 0 diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_insn_boundary.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_insn_boundary.tc new file mode 100644 index 000000000000..4f7cc318f331 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_insn_boundary.tc @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2023 Akanksha J N, IBM corporation +# description: Register multiple kprobe events in a function +# requires: kprobe_events + +for i in `seq 0 255`; do + echo p $FUNCTION_FORK+${i} >> kprobe_events || continue +done + +cat kprobe_events >> $testlog + +echo 1 > events/kprobes/enable +( echo "forked" ) +echo 0 > events/kprobes/enable +echo > kprobe_events +echo "Waiting for unoptimizing & freeing" +sleep 5 +echo "Done" diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_opt_types.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_opt_types.tc new file mode 100644 index 000000000000..9f5d99328086 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_opt_types.tc @@ -0,0 +1,34 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2023 Akanksha J N, IBM corporation +# description: Register/unregister optimized probe +# requires: kprobe_events + +case `uname -m` in +x86_64) +;; +arm*) +;; +ppc*) +;; +*) + echo "Please implement other architecture here" + exit_unsupported +esac + +DEFAULT=$(cat /proc/sys/debug/kprobes-optimization) +echo 1 > /proc/sys/debug/kprobes-optimization +for i in `seq 0 255`; do + echo "p:testprobe $FUNCTION_FORK+${i}" > kprobe_events || continue + echo 1 > events/kprobes/enable || continue + (echo "forked") + PROBE=$(grep $FUNCTION_FORK /sys/kernel/debug/kprobes/list) + echo 0 > events/kprobes/enable + echo > kprobe_events + if echo $PROBE | grep -q OPTIMIZED; then + echo "$DEFAULT" > /proc/sys/debug/kprobes-optimization + exit_pass + fi +done +echo "$DEFAULT" > /proc/sys/debug/kprobes-optimization +exit_unresolved diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc index 9e85d3019ff0..65fbb26fd58c 100644 --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc @@ -8,7 +8,7 @@ check_error() { # command-with-error-pos-by-^ } if grep -q 'r\[maxactive\]' README; then -check_error 'p^100 vfs_read' # MAXACT_NO_KPROBE +check_error 'p^100 vfs_read' # BAD_MAXACT_TYPE check_error 'r^1a111 vfs_read' # BAD_MAXACT check_error 'r^100000 vfs_read' # MAXACT_TOO_BIG fi @@ -103,4 +103,18 @@ check_error 'p vfs_read^%hoge' # BAD_ADDR_SUFFIX check_error 'p ^vfs_read+10%return' # BAD_RETPROBE fi +# BTF arguments errors +if grep -q "<argname>" README; then +check_error 'p vfs_read args=^$arg*' # BAD_VAR_ARGS +check_error 'p vfs_read +0(^$arg*)' # BAD_VAR_ARGS +check_error 'p vfs_read $arg* ^$arg*' # DOUBLE_ARGS +check_error 'r vfs_read ^$arg*' # NOFENTRY_ARGS +check_error 'p vfs_read+8 ^$arg*' # NOFENTRY_ARGS +check_error 'p vfs_read ^hoge' # NO_BTFARG +check_error 'p kfree ^$arg10' # NO_BTFARG (exceed the number of parameters) +check_error 'r kfree ^$retval' # NO_RETVAL +else +check_error 'p vfs_read ^$arg*' # NOSUP_BTFARG +fi + exit 0 diff --git a/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack-legacy.tc b/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack-legacy.tc new file mode 100644 index 000000000000..d0cd91a93069 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack-legacy.tc @@ -0,0 +1,24 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event trigger - test inter-event histogram trigger trace action with dynamic string param (legacy stack) +# requires: set_event synthetic_events events/sched/sched_process_exec/hist "long[] stack' >> synthetic_events":README + +fail() { #msg + echo $1 + exit_fail +} + +echo "Test create synthetic event with stack" + +# Test the old stacktrace keyword (for backward compatibility) +echo 's:wake_lat pid_t pid; u64 delta; unsigned long[] stack;' > dynamic_events +echo 'hist:keys=next_pid:ts=common_timestamp.usecs,st=stacktrace if prev_state == 1||prev_state == 2' >> events/sched/sched_switch/trigger +echo 'hist:keys=prev_pid:delta=common_timestamp.usecs-$ts,s=$st:onmax($delta).trace(wake_lat,prev_pid,$delta,$s)' >> events/sched/sched_switch/trigger +echo 1 > events/synthetic/wake_lat/enable +sleep 1 + +if ! grep -q "=>.*sched" trace; then + fail "Failed to create synthetic event with stack" +fi + +exit 0 diff --git a/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack.tc b/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack.tc index 755dbe94ccf4..8f1cc9a86a06 100644 --- a/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack.tc +++ b/tools/testing/selftests/ftrace/test.d/trigger/inter-event/trigger-synthetic-event-stack.tc @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # description: event trigger - test inter-event histogram trigger trace action with dynamic string param -# requires: set_event synthetic_events events/sched/sched_process_exec/hist "long[]' >> synthetic_events":README +# requires: set_event synthetic_events events/sched/sched_process_exec/hist "can be any field, or the special string 'common_stacktrace'":README fail() { #msg echo $1 @@ -10,9 +10,8 @@ fail() { #msg echo "Test create synthetic event with stack" - echo 's:wake_lat pid_t pid; u64 delta; unsigned long[] stack;' > dynamic_events -echo 'hist:keys=next_pid:ts=common_timestamp.usecs,st=stacktrace if prev_state == 1||prev_state == 2' >> events/sched/sched_switch/trigger +echo 'hist:keys=next_pid:ts=common_timestamp.usecs,st=common_stacktrace if prev_state == 1||prev_state == 2' >> events/sched/sched_switch/trigger echo 'hist:keys=prev_pid:delta=common_timestamp.usecs-$ts,s=$st:onmax($delta).trace(wake_lat,prev_pid,$delta,$s)' >> events/sched/sched_switch/trigger echo 1 > events/synthetic/wake_lat/enable sleep 1 diff --git a/tools/testing/selftests/gpio/gpio-sim.sh b/tools/testing/selftests/gpio/gpio-sim.sh index 9f539d454ee4..6fb66a687f17 100755 --- a/tools/testing/selftests/gpio/gpio-sim.sh +++ b/tools/testing/selftests/gpio/gpio-sim.sh @@ -152,9 +152,9 @@ sysfs_set_pull() { local PULL=$4 local DEVNAME=`configfs_dev_name $DEV` local CHIPNAME=`configfs_chip_name $DEV $BANK` - local SYSFSPATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull" + local SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull" - echo $PULL > $SYSFSPATH || fail "Unable to set line pull in sysfs" + echo $PULL > $SYSFS_PATH || fail "Unable to set line pull in sysfs" } # Load the gpio-sim module. This will pull in configfs if needed too. @@ -389,6 +389,9 @@ create_chip chip create_bank chip bank set_num_lines chip bank 8 enable_chip chip +DEVNAME=`configfs_dev_name chip` +CHIPNAME=`configfs_chip_name chip bank` +SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value" $BASE_DIR/gpio-mockup-cdev -b pull-up /dev/`configfs_chip_name chip bank` 0 test `cat $SYSFS_PATH` = "1" || fail "bias setting does not work" remove_chip chip diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py index b1eb2bc787fc..f92fe8e02c1b 100644 --- a/tools/testing/selftests/hid/tests/test_wacom_generic.py +++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py @@ -31,6 +31,7 @@ from enum import Enum from hidtools.hut import HUT from hidtools.hid import HidUnit from . import base +from . import test_multitouch import libevdev import pytest @@ -517,7 +518,7 @@ class BaseTest: for usage in get_report_usages(report): yield usage - def assertName(self, uhdev): + def assertName(self, uhdev, type): """ Assert that the name is as we expect. @@ -526,7 +527,7 @@ class BaseTest: this assertion from the base class to work properly. """ evdev = uhdev.get_evdev() - expected_name = uhdev.name + " Pen" + expected_name = uhdev.name + type if "wacom" not in expected_name.lower(): expected_name = "Wacom " + expected_name assert evdev.name == expected_name @@ -549,6 +550,12 @@ class BaseTest: usage_id("Generic Desktop", "Y"): PhysRange( PhysRange.CENTIMETER, 5, 150 ), + usage_id("Digitizers", "Width"): PhysRange( + PhysRange.CENTIMETER, 5, 150 + ), + usage_id("Digitizers", "Height"): PhysRange( + PhysRange.CENTIMETER, 5, 150 + ), usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360), @@ -603,7 +610,17 @@ class BaseTest: pass -class TestOpaqueTablet(BaseTest.TestTablet): +class PenTabletTest(BaseTest.TestTablet): + def assertName(self, uhdev): + super().assertName(uhdev, " Pen") + + +class TouchTabletTest(BaseTest.TestTablet): + def assertName(self, uhdev): + super().assertName(uhdev, " Finger") + + +class TestOpaqueTablet(PenTabletTest): def create_device(self): return OpaqueTablet() @@ -842,3 +859,64 @@ class TestPTHX60_Pen(TestOpaqueCTLTablet): libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0), ], ) + + +class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest): + def create_device(self): + return test_multitouch.Digitizer( + "DTH 2452", + rdesc="05 0d 09 04 a1 01 85 0c 95 01 75 08 15 00 26 ff 00 81 03 09 54 81 02 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 75 08 95 0e 81 03 09 55 26 ff 00 75 08 b1 02 85 0a 06 00 ff 09 c5 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 13 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0", + input_info=(0x3, 0x056A, 0x0383), + ) + + def test_contact_id_0(self): + """ + Bring a finger in contact with the tablet, then hold it down and remove it. + + Ensure that even with contact ID = 0 which is usually given as an invalid + touch event by most tablets with the exception of a few, that given the + confidence bit is set to 1 it should process it as a valid touch to cover + the few tablets using contact ID = 0 as a valid touch value. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = test_multitouch.Touch(0, 50, 100) + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + slot = self.get_slot(uhdev, t0, 0) + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + + t0.tipswitch = False + if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: + t0.inrange = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + def test_confidence_false(self): + """ + Bring a finger in contact with the tablet with confidence set to false. + + Ensure that the confidence bit being set to false should not result in a touch event. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = test_multitouch.Touch(1, 50, 100) + t0.confidence = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + slot = self.get_slot(uhdev, t0, 0) + + assert not events
\ No newline at end of file diff --git a/tools/testing/selftests/kselftest/runner.sh b/tools/testing/selftests/kselftest/runner.sh index 294619ade49f..1c952d1401d4 100644 --- a/tools/testing/selftests/kselftest/runner.sh +++ b/tools/testing/selftests/kselftest/runner.sh @@ -8,7 +8,8 @@ export logfile=/dev/stdout export per_test_logging= # Defaults for "settings" file fields: -# "timeout" how many seconds to let each test run before failing. +# "timeout" how many seconds to let each test run before running +# over our soft timeout limit. export kselftest_default_timeout=45 # There isn't a shell-agnostic way to find the path of a sourced file, @@ -90,6 +91,14 @@ run_one() done < "$settings" fi + # Command line timeout overrides the settings file + if [ -n "$kselftest_override_timeout" ]; then + kselftest_timeout="$kselftest_override_timeout" + echo "# overriding timeout to $kselftest_timeout" >> "$logfile" + else + echo "# timeout set to $kselftest_timeout" >> "$logfile" + fi + TEST_HDR_MSG="selftests: $DIR: $BASENAME_TEST" echo "# $TEST_HDR_MSG" if [ ! -e "$TEST" ]; then diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index d8bff2005dfc..5fd49ad0c696 100644 --- a/tools/testing/selftests/kselftest_harness.h +++ b/tools/testing/selftests/kselftest_harness.h @@ -249,7 +249,7 @@ /** * FIXTURE_SETUP() - Prepares the setup function for the fixture. - * *_metadata* is included so that EXPECT_* and ASSERT_* work correctly. + * *_metadata* is included so that EXPECT_*, ASSERT_* etc. work correctly. * * @fixture_name: fixture name * @@ -275,7 +275,7 @@ /** * FIXTURE_TEARDOWN() - * *_metadata* is included so that EXPECT_* and ASSERT_* work correctly. + * *_metadata* is included so that EXPECT_*, ASSERT_* etc. work correctly. * * @fixture_name: fixture name * @@ -388,7 +388,7 @@ if (setjmp(_metadata->env) == 0) { \ fixture_name##_setup(_metadata, &self, variant->data); \ /* Let setup failure terminate early. */ \ - if (!_metadata->passed) \ + if (!_metadata->passed || _metadata->skip) \ return; \ _metadata->setup_completed = true; \ fixture_name##_##test_name(_metadata, &self, variant->data); \ diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index 7a5ff646e7e7..c692cc86e7da 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -61,6 +61,7 @@ TEST_PROGS_x86_64 += x86_64/nx_huge_pages_test.sh # Compiled test targets TEST_GEN_PROGS_x86_64 = x86_64/cpuid_test TEST_GEN_PROGS_x86_64 += x86_64/cr4_cpuid_sync_test +TEST_GEN_PROGS_x86_64 += x86_64/dirty_log_page_splitting_test TEST_GEN_PROGS_x86_64 += x86_64/get_msr_index_features TEST_GEN_PROGS_x86_64 += x86_64/exit_on_emulation_failure_test TEST_GEN_PROGS_x86_64 += x86_64/fix_hypercall_test @@ -116,6 +117,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/sev_migrate_tests TEST_GEN_PROGS_x86_64 += x86_64/amx_test TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test TEST_GEN_PROGS_x86_64 += x86_64/triple_fault_event_test +TEST_GEN_PROGS_x86_64 += x86_64/recalc_apic_map_test TEST_GEN_PROGS_x86_64 += access_tracking_perf_test TEST_GEN_PROGS_x86_64 += demand_paging_test TEST_GEN_PROGS_x86_64 += dirty_log_test @@ -163,6 +165,7 @@ TEST_GEN_PROGS_s390x = s390x/memop TEST_GEN_PROGS_s390x += s390x/resets TEST_GEN_PROGS_s390x += s390x/sync_regs_test TEST_GEN_PROGS_s390x += s390x/tprot +TEST_GEN_PROGS_s390x += s390x/cmma_test TEST_GEN_PROGS_s390x += demand_paging_test TEST_GEN_PROGS_s390x += dirty_log_test TEST_GEN_PROGS_s390x += kvm_create_max_vcpus @@ -183,6 +186,8 @@ TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH_DIR)) TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH_DIR)) LIBKVM += $(LIBKVM_$(ARCH_DIR)) +OVERRIDE_TARGETS = 1 + # lib.mak defines $(OUTPUT), prepends $(OUTPUT)/ to $(TEST_GEN_PROGS), and most # importantly defines, i.e. overwrites, $(CC) (unless `make -e` or `make CC=`, # which causes the environment variable to override the makefile). @@ -197,7 +202,7 @@ else LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include endif CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -O2 -g -std=gnu99 \ - -Wno-gnu-variable-sized-type-not-at-end \ + -Wno-gnu-variable-sized-type-not-at-end -MD\ -fno-builtin-memcmp -fno-builtin-memcpy -fno-builtin-memset \ -fno-stack-protector -fno-PIE -I$(LINUX_TOOL_INCLUDE) \ -I$(LINUX_TOOL_ARCH_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude \ @@ -224,7 +229,18 @@ LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S)) LIBKVM_STRING_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_STRING)) LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ) -EXTRA_CLEAN += $(LIBKVM_OBJS) cscope.* +TEST_GEN_OBJ = $(patsubst %, %.o, $(TEST_GEN_PROGS)) +TEST_GEN_OBJ += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED)) +TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_OBJ)) +TEST_DEP_FILES += $(patsubst %.o, %.d, $(LIBKVM_OBJS)) +-include $(TEST_DEP_FILES) + +$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): %: %.o + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIBKVM_OBJS) $(LDLIBS) -o $@ +$(TEST_GEN_OBJ): $(OUTPUT)/%.o: %.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +EXTRA_CLEAN += $(LIBKVM_OBJS) $(TEST_DEP_FILES) $(TEST_GEN_OBJ) cscope.* x := $(shell mkdir -p $(sort $(dir $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ)))) $(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c diff --git a/tools/testing/selftests/kvm/aarch64/get-reg-list.c b/tools/testing/selftests/kvm/aarch64/get-reg-list.c index d4e1f4af29d6..4f10055af2aa 100644 --- a/tools/testing/selftests/kvm/aarch64/get-reg-list.c +++ b/tools/testing/selftests/kvm/aarch64/get-reg-list.c @@ -48,6 +48,34 @@ struct reg_sublist { __u64 rejects_set_n; }; +struct feature_id_reg { + __u64 reg; + __u64 id_reg; + __u64 feat_shift; + __u64 feat_min; +}; + +static struct feature_id_reg feat_id_regs[] = { + { + ARM64_SYS_REG(3, 0, 2, 0, 3), /* TCR2_EL1 */ + ARM64_SYS_REG(3, 0, 0, 7, 3), /* ID_AA64MMFR3_EL1 */ + 0, + 1 + }, + { + ARM64_SYS_REG(3, 0, 10, 2, 2), /* PIRE0_EL1 */ + ARM64_SYS_REG(3, 0, 0, 7, 3), /* ID_AA64MMFR3_EL1 */ + 4, + 1 + }, + { + ARM64_SYS_REG(3, 0, 10, 2, 3), /* PIR_EL1 */ + ARM64_SYS_REG(3, 0, 0, 7, 3), /* ID_AA64MMFR3_EL1 */ + 4, + 1 + } +}; + struct vcpu_config { char *name; struct reg_sublist sublists[]; @@ -68,7 +96,8 @@ static int vcpu_configs_n; #define for_each_missing_reg(i) \ for ((i) = 0; (i) < blessed_n; ++(i)) \ - if (!find_reg(reg_list->reg, reg_list->n, blessed_reg[i])) + if (!find_reg(reg_list->reg, reg_list->n, blessed_reg[i])) \ + if (check_supported_feat_reg(vcpu, blessed_reg[i])) #define for_each_new_reg(i) \ for_each_reg_filtered(i) \ @@ -132,6 +161,25 @@ static bool find_reg(__u64 regs[], __u64 nr_regs, __u64 reg) return false; } +static bool check_supported_feat_reg(struct kvm_vcpu *vcpu, __u64 reg) +{ + int i, ret; + __u64 data, feat_val; + + for (i = 0; i < ARRAY_SIZE(feat_id_regs); i++) { + if (feat_id_regs[i].reg == reg) { + ret = __vcpu_get_reg(vcpu, feat_id_regs[i].id_reg, &data); + if (ret < 0) + return false; + + feat_val = ((data >> feat_id_regs[i].feat_shift) & 0xf); + return feat_val >= feat_id_regs[i].feat_min; + } + } + + return true; +} + static const char *str_with_index(const char *template, __u64 index) { char *str, *p; @@ -843,12 +891,15 @@ static __u64 base_regs[] = { ARM64_SYS_REG(3, 0, 2, 0, 0), /* TTBR0_EL1 */ ARM64_SYS_REG(3, 0, 2, 0, 1), /* TTBR1_EL1 */ ARM64_SYS_REG(3, 0, 2, 0, 2), /* TCR_EL1 */ + ARM64_SYS_REG(3, 0, 2, 0, 3), /* TCR2_EL1 */ ARM64_SYS_REG(3, 0, 5, 1, 0), /* AFSR0_EL1 */ ARM64_SYS_REG(3, 0, 5, 1, 1), /* AFSR1_EL1 */ ARM64_SYS_REG(3, 0, 5, 2, 0), /* ESR_EL1 */ ARM64_SYS_REG(3, 0, 6, 0, 0), /* FAR_EL1 */ ARM64_SYS_REG(3, 0, 7, 4, 0), /* PAR_EL1 */ ARM64_SYS_REG(3, 0, 10, 2, 0), /* MAIR_EL1 */ + ARM64_SYS_REG(3, 0, 10, 2, 2), /* PIRE0_EL1 */ + ARM64_SYS_REG(3, 0, 10, 2, 3), /* PIR_EL1 */ ARM64_SYS_REG(3, 0, 10, 3, 0), /* AMAIR_EL1 */ ARM64_SYS_REG(3, 0, 12, 0, 0), /* VBAR_EL1 */ ARM64_SYS_REG(3, 0, 12, 1, 1), /* DISR_EL1 */ diff --git a/tools/testing/selftests/kvm/demand_paging_test.c b/tools/testing/selftests/kvm/demand_paging_test.c index 2439c4043fed..09c116a82a84 100644 --- a/tools/testing/selftests/kvm/demand_paging_test.c +++ b/tools/testing/selftests/kvm/demand_paging_test.c @@ -128,6 +128,7 @@ static void prefault_mem(void *alias, uint64_t len) static void run_test(enum vm_guest_mode mode, void *arg) { + struct memstress_vcpu_args *vcpu_args; struct test_params *p = arg; struct uffd_desc **uffd_descs = NULL; struct timespec start; @@ -145,24 +146,24 @@ static void run_test(enum vm_guest_mode mode, void *arg) "Failed to allocate buffer for guest data pattern"); memset(guest_data_prototype, 0xAB, demand_paging_size); + if (p->uffd_mode == UFFDIO_REGISTER_MODE_MINOR) { + for (i = 0; i < nr_vcpus; i++) { + vcpu_args = &memstress_args.vcpu_args[i]; + prefault_mem(addr_gpa2alias(vm, vcpu_args->gpa), + vcpu_args->pages * memstress_args.guest_page_size); + } + } + if (p->uffd_mode) { uffd_descs = malloc(nr_vcpus * sizeof(struct uffd_desc *)); TEST_ASSERT(uffd_descs, "Memory allocation failed"); - for (i = 0; i < nr_vcpus; i++) { - struct memstress_vcpu_args *vcpu_args; void *vcpu_hva; - void *vcpu_alias; vcpu_args = &memstress_args.vcpu_args[i]; /* Cache the host addresses of the region */ vcpu_hva = addr_gpa2hva(vm, vcpu_args->gpa); - vcpu_alias = addr_gpa2alias(vm, vcpu_args->gpa); - - prefault_mem(vcpu_alias, - vcpu_args->pages * memstress_args.guest_page_size); - /* * Set up user fault fd to handle demand paging * requests. @@ -207,10 +208,11 @@ static void help(char *name) { puts(""); printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-d uffd_delay_usec]\n" - " [-b memory] [-s type] [-v vcpus] [-o]\n", name); + " [-b memory] [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name); guest_modes_help(); printf(" -u: use userfaultfd to handle vCPU page faults. Mode is a\n" " UFFD registration mode: 'MISSING' or 'MINOR'.\n"); + kvm_print_vcpu_pinning_help(); printf(" -d: add a delay in usec to the User Fault\n" " FD handler to simulate demand paging\n" " overheads. Ignored without -u.\n"); @@ -228,6 +230,7 @@ static void help(char *name) int main(int argc, char *argv[]) { int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS); + const char *cpulist = NULL; struct test_params p = { .src_type = DEFAULT_VM_MEM_SRC, .partition_vcpu_memory_access = true, @@ -236,7 +239,7 @@ int main(int argc, char *argv[]) guest_modes_append_default(); - while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:o")) != -1) { + while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:c:o")) != -1) { switch (opt) { case 'm': guest_modes_cmdline(optarg); @@ -263,6 +266,9 @@ int main(int argc, char *argv[]) TEST_ASSERT(nr_vcpus <= max_vcpus, "Invalid number of vcpus, must be between 1 and %d", max_vcpus); break; + case 'c': + cpulist = optarg; + break; case 'o': p.partition_vcpu_memory_access = false; break; @@ -278,6 +284,12 @@ int main(int argc, char *argv[]) TEST_FAIL("userfaultfd MINOR mode requires shared memory; pick a different -s"); } + if (cpulist) { + kvm_parse_vcpu_pinning(cpulist, memstress_args.vcpu_to_pcpu, + nr_vcpus); + memstress_args.pin_vcpus = true; + } + for_each_guest_mode(run_test, &p); return 0; diff --git a/tools/testing/selftests/kvm/dirty_log_perf_test.c b/tools/testing/selftests/kvm/dirty_log_perf_test.c index e9d6d1aecf89..d374dbcf9a53 100644 --- a/tools/testing/selftests/kvm/dirty_log_perf_test.c +++ b/tools/testing/selftests/kvm/dirty_log_perf_test.c @@ -136,77 +136,6 @@ struct test_params { bool random_access; }; -static void toggle_dirty_logging(struct kvm_vm *vm, int slots, bool enable) -{ - int i; - - for (i = 0; i < slots; i++) { - int slot = MEMSTRESS_MEM_SLOT_INDEX + i; - int flags = enable ? KVM_MEM_LOG_DIRTY_PAGES : 0; - - vm_mem_region_set_flags(vm, slot, flags); - } -} - -static inline void enable_dirty_logging(struct kvm_vm *vm, int slots) -{ - toggle_dirty_logging(vm, slots, true); -} - -static inline void disable_dirty_logging(struct kvm_vm *vm, int slots) -{ - toggle_dirty_logging(vm, slots, false); -} - -static void get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots) -{ - int i; - - for (i = 0; i < slots; i++) { - int slot = MEMSTRESS_MEM_SLOT_INDEX + i; - - kvm_vm_get_dirty_log(vm, slot, bitmaps[i]); - } -} - -static void clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], - int slots, uint64_t pages_per_slot) -{ - int i; - - for (i = 0; i < slots; i++) { - int slot = MEMSTRESS_MEM_SLOT_INDEX + i; - - kvm_vm_clear_dirty_log(vm, slot, bitmaps[i], 0, pages_per_slot); - } -} - -static unsigned long **alloc_bitmaps(int slots, uint64_t pages_per_slot) -{ - unsigned long **bitmaps; - int i; - - bitmaps = malloc(slots * sizeof(bitmaps[0])); - TEST_ASSERT(bitmaps, "Failed to allocate bitmaps array."); - - for (i = 0; i < slots; i++) { - bitmaps[i] = bitmap_zalloc(pages_per_slot); - TEST_ASSERT(bitmaps[i], "Failed to allocate slot bitmap."); - } - - return bitmaps; -} - -static void free_bitmaps(unsigned long *bitmaps[], int slots) -{ - int i; - - for (i = 0; i < slots; i++) - free(bitmaps[i]); - - free(bitmaps); -} - static void run_test(enum vm_guest_mode mode, void *arg) { struct test_params *p = arg; @@ -236,7 +165,7 @@ static void run_test(enum vm_guest_mode mode, void *arg) host_num_pages = vm_num_host_pages(mode, guest_num_pages); pages_per_slot = host_num_pages / p->slots; - bitmaps = alloc_bitmaps(p->slots, pages_per_slot); + bitmaps = memstress_alloc_bitmaps(p->slots, pages_per_slot); if (dirty_log_manual_caps) vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2, @@ -277,7 +206,7 @@ static void run_test(enum vm_guest_mode mode, void *arg) /* Enable dirty logging */ clock_gettime(CLOCK_MONOTONIC, &start); - enable_dirty_logging(vm, p->slots); + memstress_enable_dirty_logging(vm, p->slots); ts_diff = timespec_elapsed(start); pr_info("Enabling dirty logging time: %ld.%.9lds\n\n", ts_diff.tv_sec, ts_diff.tv_nsec); @@ -306,7 +235,7 @@ static void run_test(enum vm_guest_mode mode, void *arg) iteration, ts_diff.tv_sec, ts_diff.tv_nsec); clock_gettime(CLOCK_MONOTONIC, &start); - get_dirty_log(vm, bitmaps, p->slots); + memstress_get_dirty_log(vm, bitmaps, p->slots); ts_diff = timespec_elapsed(start); get_dirty_log_total = timespec_add(get_dirty_log_total, ts_diff); @@ -315,7 +244,8 @@ static void run_test(enum vm_guest_mode mode, void *arg) if (dirty_log_manual_caps) { clock_gettime(CLOCK_MONOTONIC, &start); - clear_dirty_log(vm, bitmaps, p->slots, pages_per_slot); + memstress_clear_dirty_log(vm, bitmaps, p->slots, + pages_per_slot); ts_diff = timespec_elapsed(start); clear_dirty_log_total = timespec_add(clear_dirty_log_total, ts_diff); @@ -334,7 +264,7 @@ static void run_test(enum vm_guest_mode mode, void *arg) /* Disable dirty logging */ clock_gettime(CLOCK_MONOTONIC, &start); - disable_dirty_logging(vm, p->slots); + memstress_disable_dirty_logging(vm, p->slots); ts_diff = timespec_elapsed(start); pr_info("Disabling dirty logging time: %ld.%.9lds\n", ts_diff.tv_sec, ts_diff.tv_nsec); @@ -359,7 +289,7 @@ static void run_test(enum vm_guest_mode mode, void *arg) clear_dirty_log_total.tv_nsec, avg.tv_sec, avg.tv_nsec); } - free_bitmaps(bitmaps, p->slots); + memstress_free_bitmaps(bitmaps, p->slots); arch_cleanup_vm(vm); memstress_destroy_vm(vm); } @@ -402,17 +332,7 @@ static void help(char *name) " so -w X means each page has an X%% chance of writing\n" " and a (100-X)%% chance of reading.\n" " (default: 100 i.e. all pages are written to.)\n"); - printf(" -c: Pin tasks to physical CPUs. Takes a list of comma separated\n" - " values (target pCPU), one for each vCPU, plus an optional\n" - " entry for the main application task (specified via entry\n" - " <nr_vcpus + 1>). If used, entries must be provided for all\n" - " vCPUs, i.e. pinning vCPUs is all or nothing.\n\n" - " E.g. to create 3 vCPUs, pin vCPU0=>pCPU22, vCPU1=>pCPU23,\n" - " vCPU2=>pCPU24, and pin the application task to pCPU50:\n\n" - " ./dirty_log_perf_test -v 3 -c 22,23,24,50\n\n" - " To leave the application task unpinned, drop the final entry:\n\n" - " ./dirty_log_perf_test -v 3 -c 22,23,24\n\n" - " (default: no pinning)\n"); + kvm_print_vcpu_pinning_help(); puts(""); exit(0); } diff --git a/tools/testing/selftests/kvm/include/kvm_util_base.h b/tools/testing/selftests/kvm/include/kvm_util_base.h index a089c356f354..07732a157ccd 100644 --- a/tools/testing/selftests/kvm/include/kvm_util_base.h +++ b/tools/testing/selftests/kvm/include/kvm_util_base.h @@ -733,6 +733,7 @@ static inline struct kvm_vm *vm_create_with_one_vcpu(struct kvm_vcpu **vcpu, struct kvm_vcpu *vm_recreate_with_one_vcpu(struct kvm_vm *vm); void kvm_pin_this_task_to_pcpu(uint32_t pcpu); +void kvm_print_vcpu_pinning_help(void); void kvm_parse_vcpu_pinning(const char *pcpus_string, uint32_t vcpu_to_pcpu[], int nr_vcpus); diff --git a/tools/testing/selftests/kvm/include/memstress.h b/tools/testing/selftests/kvm/include/memstress.h index 72e3e358ef7b..ce4e603050ea 100644 --- a/tools/testing/selftests/kvm/include/memstress.h +++ b/tools/testing/selftests/kvm/include/memstress.h @@ -72,4 +72,12 @@ void memstress_guest_code(uint32_t vcpu_id); uint64_t memstress_nested_pages(int nr_vcpus); void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vcpus[]); +void memstress_enable_dirty_logging(struct kvm_vm *vm, int slots); +void memstress_disable_dirty_logging(struct kvm_vm *vm, int slots); +void memstress_get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots); +void memstress_clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], + int slots, uint64_t pages_per_slot); +unsigned long **memstress_alloc_bitmaps(int slots, uint64_t pages_per_slot); +void memstress_free_bitmaps(unsigned long *bitmaps[], int slots); + #endif /* SELFTEST_KVM_MEMSTRESS_H */ diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index 298c4372fb1a..9741a7ff6380 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -494,6 +494,23 @@ static uint32_t parse_pcpu(const char *cpu_str, const cpu_set_t *allowed_mask) return pcpu; } +void kvm_print_vcpu_pinning_help(void) +{ + const char *name = program_invocation_name; + + printf(" -c: Pin tasks to physical CPUs. Takes a list of comma separated\n" + " values (target pCPU), one for each vCPU, plus an optional\n" + " entry for the main application task (specified via entry\n" + " <nr_vcpus + 1>). If used, entries must be provided for all\n" + " vCPUs, i.e. pinning vCPUs is all or nothing.\n\n" + " E.g. to create 3 vCPUs, pin vCPU0=>pCPU22, vCPU1=>pCPU23,\n" + " vCPU2=>pCPU24, and pin the application task to pCPU50:\n\n" + " %s -v 3 -c 22,23,24,50\n\n" + " To leave the application task unpinned, drop the final entry:\n\n" + " %s -v 3 -c 22,23,24\n\n" + " (default: no pinning)\n", name, name); +} + void kvm_parse_vcpu_pinning(const char *pcpus_string, uint32_t vcpu_to_pcpu[], int nr_vcpus) { diff --git a/tools/testing/selftests/kvm/lib/memstress.c b/tools/testing/selftests/kvm/lib/memstress.c index 5f1d3173c238..df457452d146 100644 --- a/tools/testing/selftests/kvm/lib/memstress.c +++ b/tools/testing/selftests/kvm/lib/memstress.c @@ -5,6 +5,7 @@ #define _GNU_SOURCE #include <inttypes.h> +#include <linux/bitmap.h> #include "kvm_util.h" #include "memstress.h" @@ -64,6 +65,9 @@ void memstress_guest_code(uint32_t vcpu_idx) GUEST_ASSERT(vcpu_args->vcpu_idx == vcpu_idx); while (true) { + for (i = 0; i < sizeof(memstress_args); i += args->guest_page_size) + (void) *((volatile char *)args + i); + for (i = 0; i < pages; i++) { if (args->random_access) page = guest_random_u32(&rand_state) % pages; @@ -320,3 +324,74 @@ void memstress_join_vcpu_threads(int nr_vcpus) for (i = 0; i < nr_vcpus; i++) pthread_join(vcpu_threads[i].thread, NULL); } + +static void toggle_dirty_logging(struct kvm_vm *vm, int slots, bool enable) +{ + int i; + + for (i = 0; i < slots; i++) { + int slot = MEMSTRESS_MEM_SLOT_INDEX + i; + int flags = enable ? KVM_MEM_LOG_DIRTY_PAGES : 0; + + vm_mem_region_set_flags(vm, slot, flags); + } +} + +void memstress_enable_dirty_logging(struct kvm_vm *vm, int slots) +{ + toggle_dirty_logging(vm, slots, true); +} + +void memstress_disable_dirty_logging(struct kvm_vm *vm, int slots) +{ + toggle_dirty_logging(vm, slots, false); +} + +void memstress_get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots) +{ + int i; + + for (i = 0; i < slots; i++) { + int slot = MEMSTRESS_MEM_SLOT_INDEX + i; + + kvm_vm_get_dirty_log(vm, slot, bitmaps[i]); + } +} + +void memstress_clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], + int slots, uint64_t pages_per_slot) +{ + int i; + + for (i = 0; i < slots; i++) { + int slot = MEMSTRESS_MEM_SLOT_INDEX + i; + + kvm_vm_clear_dirty_log(vm, slot, bitmaps[i], 0, pages_per_slot); + } +} + +unsigned long **memstress_alloc_bitmaps(int slots, uint64_t pages_per_slot) +{ + unsigned long **bitmaps; + int i; + + bitmaps = malloc(slots * sizeof(bitmaps[0])); + TEST_ASSERT(bitmaps, "Failed to allocate bitmaps array."); + + for (i = 0; i < slots; i++) { + bitmaps[i] = bitmap_zalloc(pages_per_slot); + TEST_ASSERT(bitmaps[i], "Failed to allocate slot bitmap."); + } + + return bitmaps; +} + +void memstress_free_bitmaps(unsigned long *bitmaps[], int slots) +{ + int i; + + for (i = 0; i < slots; i++) + free(bitmaps[i]); + + free(bitmaps); +} diff --git a/tools/testing/selftests/kvm/lib/userfaultfd_util.c b/tools/testing/selftests/kvm/lib/userfaultfd_util.c index 92cef20902f1..271f63891581 100644 --- a/tools/testing/selftests/kvm/lib/userfaultfd_util.c +++ b/tools/testing/selftests/kvm/lib/userfaultfd_util.c @@ -70,7 +70,7 @@ static void *uffd_handler_thread_fn(void *arg) r = read(pollfd[1].fd, &tmp_chr, 1); TEST_ASSERT(r == 1, "Error reading pipefd in UFFD thread\n"); - return NULL; + break; } if (!(pollfd[0].revents & POLLIN)) @@ -103,7 +103,7 @@ static void *uffd_handler_thread_fn(void *arg) ts_diff = timespec_elapsed(start); PER_VCPU_DEBUG("userfaulted %ld pages over %ld.%.9lds. (%f/sec)\n", pages, ts_diff.tv_sec, ts_diff.tv_nsec, - pages / ((double)ts_diff.tv_sec + (double)ts_diff.tv_nsec / 100000000.0)); + pages / ((double)ts_diff.tv_sec + (double)ts_diff.tv_nsec / NSEC_PER_SEC)); return NULL; } diff --git a/tools/testing/selftests/kvm/s390x/cmma_test.c b/tools/testing/selftests/kvm/s390x/cmma_test.c new file mode 100644 index 000000000000..1d73e78e8fa7 --- /dev/null +++ b/tools/testing/selftests/kvm/s390x/cmma_test.c @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test for s390x CMMA migration + * + * Copyright IBM Corp. 2023 + * + * Authors: + * Nico Boehr <nrb@linux.ibm.com> + */ + +#define _GNU_SOURCE /* for program_invocation_short_name */ +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "test_util.h" +#include "kvm_util.h" +#include "kselftest.h" + +#define MAIN_PAGE_COUNT 512 + +#define TEST_DATA_PAGE_COUNT 512 +#define TEST_DATA_MEMSLOT 1 +#define TEST_DATA_START_GFN 4096 + +#define TEST_DATA_TWO_PAGE_COUNT 256 +#define TEST_DATA_TWO_MEMSLOT 2 +#define TEST_DATA_TWO_START_GFN 8192 + +static char cmma_value_buf[MAIN_PAGE_COUNT + TEST_DATA_PAGE_COUNT]; + +/** + * Dirty CMMA attributes of exactly one page in the TEST_DATA memslot, + * so use_cmma goes on and the CMMA related ioctls do something. + */ +static void guest_do_one_essa(void) +{ + asm volatile( + /* load TEST_DATA_START_GFN into r1 */ + " llilf 1,%[start_gfn]\n" + /* calculate the address from the gfn */ + " sllg 1,1,12(0)\n" + /* set the first page in TEST_DATA memslot to STABLE */ + " .insn rrf,0xb9ab0000,2,1,1,0\n" + /* hypercall */ + " diag 0,0,0x501\n" + "0: j 0b" + : + : [start_gfn] "L"(TEST_DATA_START_GFN) + : "r1", "r2", "memory", "cc" + ); +} + +/** + * Touch CMMA attributes of all pages in TEST_DATA memslot. Set them to stable + * state. + */ +static void guest_dirty_test_data(void) +{ + asm volatile( + /* r1 = TEST_DATA_START_GFN */ + " xgr 1,1\n" + " llilf 1,%[start_gfn]\n" + /* r5 = TEST_DATA_PAGE_COUNT */ + " lghi 5,%[page_count]\n" + /* r5 += r1 */ + "2: agfr 5,1\n" + /* r2 = r1 << 12 */ + "1: sllg 2,1,12(0)\n" + /* essa(r4, r2, SET_STABLE) */ + " .insn rrf,0xb9ab0000,4,2,1,0\n" + /* i++ */ + " agfi 1,1\n" + /* if r1 < r5 goto 1 */ + " cgrjl 1,5,1b\n" + /* hypercall */ + " diag 0,0,0x501\n" + "0: j 0b" + : + : [start_gfn] "L"(TEST_DATA_START_GFN), + [page_count] "L"(TEST_DATA_PAGE_COUNT) + : + /* the counter in our loop over the pages */ + "r1", + /* the calculated page physical address */ + "r2", + /* ESSA output register */ + "r4", + /* last page */ + "r5", + "cc", "memory" + ); +} + +static struct kvm_vm *create_vm(void) +{ + return ____vm_create(VM_MODE_DEFAULT); +} + +static void create_main_memslot(struct kvm_vm *vm) +{ + int i; + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, MAIN_PAGE_COUNT, 0); + /* set the array of memslots to zero like __vm_create does */ + for (i = 0; i < NR_MEM_REGIONS; i++) + vm->memslots[i] = 0; +} + +static void create_test_memslot(struct kvm_vm *vm) +{ + vm_userspace_mem_region_add(vm, + VM_MEM_SRC_ANONYMOUS, + TEST_DATA_START_GFN << vm->page_shift, + TEST_DATA_MEMSLOT, + TEST_DATA_PAGE_COUNT, + 0 + ); + vm->memslots[MEM_REGION_TEST_DATA] = TEST_DATA_MEMSLOT; +} + +static void create_memslots(struct kvm_vm *vm) +{ + /* + * Our VM has the following memory layout: + * +------+---------------------------+ + * | GFN | Memslot | + * +------+---------------------------+ + * | 0 | | + * | ... | MAIN (Code, Stack, ...) | + * | 511 | | + * +------+---------------------------+ + * | 4096 | | + * | ... | TEST_DATA | + * | 4607 | | + * +------+---------------------------+ + */ + create_main_memslot(vm); + create_test_memslot(vm); +} + +static void finish_vm_setup(struct kvm_vm *vm) +{ + struct userspace_mem_region *slot0; + + kvm_vm_elf_load(vm, program_invocation_name); + + slot0 = memslot2region(vm, 0); + ucall_init(vm, slot0->region.guest_phys_addr + slot0->region.memory_size); + + kvm_arch_vm_post_create(vm); +} + +static struct kvm_vm *create_vm_two_memslots(void) +{ + struct kvm_vm *vm; + + vm = create_vm(); + + create_memslots(vm); + + finish_vm_setup(vm); + + return vm; +} + +static void enable_cmma(struct kvm_vm *vm) +{ + int r; + + r = __kvm_device_attr_set(vm->fd, KVM_S390_VM_MEM_CTRL, KVM_S390_VM_MEM_ENABLE_CMMA, NULL); + TEST_ASSERT(!r, "enabling cmma failed r=%d errno=%d", r, errno); +} + +static void enable_dirty_tracking(struct kvm_vm *vm) +{ + vm_mem_region_set_flags(vm, 0, KVM_MEM_LOG_DIRTY_PAGES); + vm_mem_region_set_flags(vm, TEST_DATA_MEMSLOT, KVM_MEM_LOG_DIRTY_PAGES); +} + +static int __enable_migration_mode(struct kvm_vm *vm) +{ + return __kvm_device_attr_set(vm->fd, + KVM_S390_VM_MIGRATION, + KVM_S390_VM_MIGRATION_START, + NULL + ); +} + +static void enable_migration_mode(struct kvm_vm *vm) +{ + int r = __enable_migration_mode(vm); + + TEST_ASSERT(!r, "enabling migration mode failed r=%d errno=%d", r, errno); +} + +static bool is_migration_mode_on(struct kvm_vm *vm) +{ + u64 out; + int r; + + r = __kvm_device_attr_get(vm->fd, + KVM_S390_VM_MIGRATION, + KVM_S390_VM_MIGRATION_STATUS, + &out + ); + TEST_ASSERT(!r, "getting migration mode status failed r=%d errno=%d", r, errno); + return out; +} + +static int vm_get_cmma_bits(struct kvm_vm *vm, u64 flags, int *errno_out) +{ + struct kvm_s390_cmma_log args; + int rc; + + errno = 0; + + args = (struct kvm_s390_cmma_log){ + .start_gfn = 0, + .count = sizeof(cmma_value_buf), + .flags = flags, + .values = (__u64)&cmma_value_buf[0] + }; + rc = __vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); + + *errno_out = errno; + return rc; +} + +static void test_get_cmma_basic(void) +{ + struct kvm_vm *vm = create_vm_two_memslots(); + struct kvm_vcpu *vcpu; + int rc, errno_out; + + /* GET_CMMA_BITS without CMMA enabled should fail */ + rc = vm_get_cmma_bits(vm, 0, &errno_out); + ASSERT_EQ(rc, -1); + ASSERT_EQ(errno_out, ENXIO); + + enable_cmma(vm); + vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa); + + vcpu_run(vcpu); + + /* GET_CMMA_BITS without migration mode and without peeking should fail */ + rc = vm_get_cmma_bits(vm, 0, &errno_out); + ASSERT_EQ(rc, -1); + ASSERT_EQ(errno_out, EINVAL); + + /* GET_CMMA_BITS without migration mode and with peeking should work */ + rc = vm_get_cmma_bits(vm, KVM_S390_CMMA_PEEK, &errno_out); + ASSERT_EQ(rc, 0); + ASSERT_EQ(errno_out, 0); + + enable_dirty_tracking(vm); + enable_migration_mode(vm); + + /* GET_CMMA_BITS with invalid flags */ + rc = vm_get_cmma_bits(vm, 0xfeedc0fe, &errno_out); + ASSERT_EQ(rc, -1); + ASSERT_EQ(errno_out, EINVAL); + + kvm_vm_free(vm); +} + +static void assert_exit_was_hypercall(struct kvm_vcpu *vcpu) +{ + ASSERT_EQ(vcpu->run->exit_reason, 13); + ASSERT_EQ(vcpu->run->s390_sieic.icptcode, 4); + ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x8300); + ASSERT_EQ(vcpu->run->s390_sieic.ipb, 0x5010000); +} + +static void test_migration_mode(void) +{ + struct kvm_vm *vm = create_vm(); + struct kvm_vcpu *vcpu; + u64 orig_psw; + int rc; + + /* enabling migration mode on a VM without memory should fail */ + rc = __enable_migration_mode(vm); + ASSERT_EQ(rc, -1); + ASSERT_EQ(errno, EINVAL); + TEST_ASSERT(!is_migration_mode_on(vm), "migration mode should still be off"); + errno = 0; + + create_memslots(vm); + finish_vm_setup(vm); + + enable_cmma(vm); + vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa); + orig_psw = vcpu->run->psw_addr; + + /* + * Execute one essa instruction in the guest. Otherwise the guest will + * not have use_cmm enabled and GET_CMMA_BITS will return no pages. + */ + vcpu_run(vcpu); + assert_exit_was_hypercall(vcpu); + + /* migration mode when memslots have dirty tracking off should fail */ + rc = __enable_migration_mode(vm); + ASSERT_EQ(rc, -1); + ASSERT_EQ(errno, EINVAL); + TEST_ASSERT(!is_migration_mode_on(vm), "migration mode should still be off"); + errno = 0; + + /* enable dirty tracking */ + enable_dirty_tracking(vm); + + /* enabling migration mode should work now */ + rc = __enable_migration_mode(vm); + ASSERT_EQ(rc, 0); + TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); + errno = 0; + + /* execute another ESSA instruction to see this goes fine */ + vcpu->run->psw_addr = orig_psw; + vcpu_run(vcpu); + assert_exit_was_hypercall(vcpu); + + /* + * With migration mode on, create a new memslot with dirty tracking off. + * This should turn off migration mode. + */ + TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); + vm_userspace_mem_region_add(vm, + VM_MEM_SRC_ANONYMOUS, + TEST_DATA_TWO_START_GFN << vm->page_shift, + TEST_DATA_TWO_MEMSLOT, + TEST_DATA_TWO_PAGE_COUNT, + 0 + ); + TEST_ASSERT(!is_migration_mode_on(vm), + "creating memslot without dirty tracking turns off migration mode" + ); + + /* ESSA instructions should still execute fine */ + vcpu->run->psw_addr = orig_psw; + vcpu_run(vcpu); + assert_exit_was_hypercall(vcpu); + + /* + * Turn on dirty tracking on the new memslot. + * It should be possible to turn migration mode back on again. + */ + vm_mem_region_set_flags(vm, TEST_DATA_TWO_MEMSLOT, KVM_MEM_LOG_DIRTY_PAGES); + rc = __enable_migration_mode(vm); + ASSERT_EQ(rc, 0); + TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); + errno = 0; + + /* + * Turn off dirty tracking again, this time with just a flag change. + * Again, migration mode should turn off. + */ + TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on"); + vm_mem_region_set_flags(vm, TEST_DATA_TWO_MEMSLOT, 0); + TEST_ASSERT(!is_migration_mode_on(vm), + "disabling dirty tracking should turn off migration mode" + ); + + /* ESSA instructions should still execute fine */ + vcpu->run->psw_addr = orig_psw; + vcpu_run(vcpu); + assert_exit_was_hypercall(vcpu); + + kvm_vm_free(vm); +} + +/** + * Given a VM with the MAIN and TEST_DATA memslot, assert that both slots have + * CMMA attributes of all pages in both memslots and nothing more dirty. + * This has the useful side effect of ensuring nothing is CMMA dirty after this + * function. + */ +static void assert_all_slots_cmma_dirty(struct kvm_vm *vm) +{ + struct kvm_s390_cmma_log args; + + /* + * First iteration - everything should be dirty. + * Start at the main memslot... + */ + args = (struct kvm_s390_cmma_log){ + .start_gfn = 0, + .count = sizeof(cmma_value_buf), + .flags = 0, + .values = (__u64)&cmma_value_buf[0] + }; + memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); + vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); + ASSERT_EQ(args.count, MAIN_PAGE_COUNT); + ASSERT_EQ(args.remaining, TEST_DATA_PAGE_COUNT); + ASSERT_EQ(args.start_gfn, 0); + + /* ...and then - after a hole - the TEST_DATA memslot should follow */ + args = (struct kvm_s390_cmma_log){ + .start_gfn = MAIN_PAGE_COUNT, + .count = sizeof(cmma_value_buf), + .flags = 0, + .values = (__u64)&cmma_value_buf[0] + }; + memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); + vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); + ASSERT_EQ(args.count, TEST_DATA_PAGE_COUNT); + ASSERT_EQ(args.start_gfn, TEST_DATA_START_GFN); + ASSERT_EQ(args.remaining, 0); + + /* ...and nothing else should be there */ + args = (struct kvm_s390_cmma_log){ + .start_gfn = TEST_DATA_START_GFN + TEST_DATA_PAGE_COUNT, + .count = sizeof(cmma_value_buf), + .flags = 0, + .values = (__u64)&cmma_value_buf[0] + }; + memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); + vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); + ASSERT_EQ(args.count, 0); + ASSERT_EQ(args.start_gfn, 0); + ASSERT_EQ(args.remaining, 0); +} + +/** + * Given a VM, assert no pages are CMMA dirty. + */ +static void assert_no_pages_cmma_dirty(struct kvm_vm *vm) +{ + struct kvm_s390_cmma_log args; + + /* If we start from GFN 0 again, nothing should be dirty. */ + args = (struct kvm_s390_cmma_log){ + .start_gfn = 0, + .count = sizeof(cmma_value_buf), + .flags = 0, + .values = (__u64)&cmma_value_buf[0] + }; + memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); + vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args); + if (args.count || args.remaining || args.start_gfn) + TEST_FAIL("pages are still dirty start_gfn=0x%llx count=%u remaining=%llu", + args.start_gfn, + args.count, + args.remaining + ); +} + +static void test_get_inital_dirty(void) +{ + struct kvm_vm *vm = create_vm_two_memslots(); + struct kvm_vcpu *vcpu; + + enable_cmma(vm); + vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa); + + /* + * Execute one essa instruction in the guest. Otherwise the guest will + * not have use_cmm enabled and GET_CMMA_BITS will return no pages. + */ + vcpu_run(vcpu); + assert_exit_was_hypercall(vcpu); + + enable_dirty_tracking(vm); + enable_migration_mode(vm); + + assert_all_slots_cmma_dirty(vm); + + /* Start from the beginning again and make sure nothing else is dirty */ + assert_no_pages_cmma_dirty(vm); + + kvm_vm_free(vm); +} + +static void query_cmma_range(struct kvm_vm *vm, + u64 start_gfn, u64 gfn_count, + struct kvm_s390_cmma_log *res_out) +{ + *res_out = (struct kvm_s390_cmma_log){ + .start_gfn = start_gfn, + .count = gfn_count, + .flags = 0, + .values = (__u64)&cmma_value_buf[0] + }; + memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf)); + vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, res_out); +} + +/** + * Assert the given cmma_log struct that was executed by query_cmma_range() + * indicates the first dirty gfn is at first_dirty_gfn and contains exactly + * dirty_gfn_count CMMA values. + */ +static void assert_cmma_dirty(u64 first_dirty_gfn, + u64 dirty_gfn_count, + const struct kvm_s390_cmma_log *res) +{ + ASSERT_EQ(res->start_gfn, first_dirty_gfn); + ASSERT_EQ(res->count, dirty_gfn_count); + for (size_t i = 0; i < dirty_gfn_count; i++) + ASSERT_EQ(cmma_value_buf[0], 0x0); /* stable state */ + ASSERT_EQ(cmma_value_buf[dirty_gfn_count], 0xff); /* not touched */ +} + +static void test_get_skip_holes(void) +{ + size_t gfn_offset; + struct kvm_vm *vm = create_vm_two_memslots(); + struct kvm_s390_cmma_log log; + struct kvm_vcpu *vcpu; + u64 orig_psw; + + enable_cmma(vm); + vcpu = vm_vcpu_add(vm, 1, guest_dirty_test_data); + + orig_psw = vcpu->run->psw_addr; + + /* + * Execute some essa instructions in the guest. Otherwise the guest will + * not have use_cmm enabled and GET_CMMA_BITS will return no pages. + */ + vcpu_run(vcpu); + assert_exit_was_hypercall(vcpu); + + enable_dirty_tracking(vm); + enable_migration_mode(vm); + + /* un-dirty all pages */ + assert_all_slots_cmma_dirty(vm); + + /* Then, dirty just the TEST_DATA memslot */ + vcpu->run->psw_addr = orig_psw; + vcpu_run(vcpu); + + gfn_offset = TEST_DATA_START_GFN; + /** + * Query CMMA attributes of one page, starting at page 0. Since the + * main memslot was not touched by the VM, this should yield the first + * page of the TEST_DATA memslot. + * The dirty bitmap should now look like this: + * 0: not dirty + * [0x1, 0x200): dirty + */ + query_cmma_range(vm, 0, 1, &log); + assert_cmma_dirty(gfn_offset, 1, &log); + gfn_offset++; + + /** + * Query CMMA attributes of 32 (0x20) pages past the end of the TEST_DATA + * memslot. This should wrap back to the beginning of the TEST_DATA + * memslot, page 1. + * The dirty bitmap should now look like this: + * [0, 0x21): not dirty + * [0x21, 0x200): dirty + */ + query_cmma_range(vm, TEST_DATA_START_GFN + TEST_DATA_PAGE_COUNT, 0x20, &log); + assert_cmma_dirty(gfn_offset, 0x20, &log); + gfn_offset += 0x20; + + /* Skip 32 pages */ + gfn_offset += 0x20; + + /** + * After skipping 32 pages, query the next 32 (0x20) pages. + * The dirty bitmap should now look like this: + * [0, 0x21): not dirty + * [0x21, 0x41): dirty + * [0x41, 0x61): not dirty + * [0x61, 0x200): dirty + */ + query_cmma_range(vm, gfn_offset, 0x20, &log); + assert_cmma_dirty(gfn_offset, 0x20, &log); + gfn_offset += 0x20; + + /** + * Query 1 page from the beginning of the TEST_DATA memslot. This should + * yield page 0x21. + * The dirty bitmap should now look like this: + * [0, 0x22): not dirty + * [0x22, 0x41): dirty + * [0x41, 0x61): not dirty + * [0x61, 0x200): dirty + */ + query_cmma_range(vm, TEST_DATA_START_GFN, 1, &log); + assert_cmma_dirty(TEST_DATA_START_GFN + 0x21, 1, &log); + gfn_offset++; + + /** + * Query 15 (0xF) pages from page 0x23 in TEST_DATA memslot. + * This should yield pages [0x23, 0x33). + * The dirty bitmap should now look like this: + * [0, 0x22): not dirty + * 0x22: dirty + * [0x23, 0x33): not dirty + * [0x33, 0x41): dirty + * [0x41, 0x61): not dirty + * [0x61, 0x200): dirty + */ + gfn_offset = TEST_DATA_START_GFN + 0x23; + query_cmma_range(vm, gfn_offset, 15, &log); + assert_cmma_dirty(gfn_offset, 15, &log); + + /** + * Query 17 (0x11) pages from page 0x22 in TEST_DATA memslot. + * This should yield page [0x22, 0x33) + * The dirty bitmap should now look like this: + * [0, 0x33): not dirty + * [0x33, 0x41): dirty + * [0x41, 0x61): not dirty + * [0x61, 0x200): dirty + */ + gfn_offset = TEST_DATA_START_GFN + 0x22; + query_cmma_range(vm, gfn_offset, 17, &log); + assert_cmma_dirty(gfn_offset, 17, &log); + + /** + * Query 25 (0x19) pages from page 0x40 in TEST_DATA memslot. + * This should yield page 0x40 and nothing more, since there are more + * than 16 non-dirty pages after page 0x40. + * The dirty bitmap should now look like this: + * [0, 0x33): not dirty + * [0x33, 0x40): dirty + * [0x40, 0x61): not dirty + * [0x61, 0x200): dirty + */ + gfn_offset = TEST_DATA_START_GFN + 0x40; + query_cmma_range(vm, gfn_offset, 25, &log); + assert_cmma_dirty(gfn_offset, 1, &log); + + /** + * Query pages [0x33, 0x40). + * The dirty bitmap should now look like this: + * [0, 0x61): not dirty + * [0x61, 0x200): dirty + */ + gfn_offset = TEST_DATA_START_GFN + 0x33; + query_cmma_range(vm, gfn_offset, 0x40 - 0x33, &log); + assert_cmma_dirty(gfn_offset, 0x40 - 0x33, &log); + + /** + * Query the remaining pages [0x61, 0x200). + */ + gfn_offset = TEST_DATA_START_GFN; + query_cmma_range(vm, gfn_offset, TEST_DATA_PAGE_COUNT - 0x61, &log); + assert_cmma_dirty(TEST_DATA_START_GFN + 0x61, TEST_DATA_PAGE_COUNT - 0x61, &log); + + assert_no_pages_cmma_dirty(vm); +} + +struct testdef { + const char *name; + void (*test)(void); +} testlist[] = { + { "migration mode and dirty tracking", test_migration_mode }, + { "GET_CMMA_BITS: basic calls", test_get_cmma_basic }, + { "GET_CMMA_BITS: all pages are dirty initally", test_get_inital_dirty }, + { "GET_CMMA_BITS: holes are skipped", test_get_skip_holes }, +}; + +/** + * The kernel may support CMMA, but the machine may not (i.e. if running as + * guest-3). + * + * In this case, the CMMA capabilities are all there, but the CMMA-related + * ioctls fail. To find out whether the machine supports CMMA, create a + * temporary VM and then query the CMMA feature of the VM. + */ +static int machine_has_cmma(void) +{ + struct kvm_vm *vm = create_vm(); + int r; + + r = !__kvm_has_device_attr(vm->fd, KVM_S390_VM_MEM_CTRL, KVM_S390_VM_MEM_ENABLE_CMMA); + kvm_vm_free(vm); + + return r; +} + +int main(int argc, char *argv[]) +{ + int idx; + + TEST_REQUIRE(kvm_has_cap(KVM_CAP_SYNC_REGS)); + TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_CMMA_MIGRATION)); + TEST_REQUIRE(machine_has_cmma()); + + ksft_print_header(); + + ksft_set_plan(ARRAY_SIZE(testlist)); + + for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) { + testlist[idx].test(); + ksft_test_result_pass("%s\n", testlist[idx].name); + } + + ksft_finished(); /* Print results and exit() accordingly */ +} diff --git a/tools/testing/selftests/kvm/x86_64/cpuid_test.c b/tools/testing/selftests/kvm/x86_64/cpuid_test.c index 2fc3ad9c887e..d3c3aa93f090 100644 --- a/tools/testing/selftests/kvm/x86_64/cpuid_test.c +++ b/tools/testing/selftests/kvm/x86_64/cpuid_test.c @@ -163,6 +163,25 @@ static void set_cpuid_after_run(struct kvm_vcpu *vcpu) ent->eax = eax; } +static void test_get_cpuid2(struct kvm_vcpu *vcpu) +{ + struct kvm_cpuid2 *cpuid = allocate_kvm_cpuid2(vcpu->cpuid->nent + 1); + int i, r; + + vcpu_ioctl(vcpu, KVM_GET_CPUID2, cpuid); + TEST_ASSERT(cpuid->nent == vcpu->cpuid->nent, + "KVM didn't update nent on success, wanted %u, got %u\n", + vcpu->cpuid->nent, cpuid->nent); + + for (i = 0; i < vcpu->cpuid->nent; i++) { + cpuid->nent = i; + r = __vcpu_ioctl(vcpu, KVM_GET_CPUID2, cpuid); + TEST_ASSERT(r && errno == E2BIG, KVM_IOCTL_ERROR(KVM_GET_CPUID2, r)); + TEST_ASSERT(cpuid->nent == i, "KVM modified nent on failure"); + } + free(cpuid); +} + int main(void) { struct kvm_vcpu *vcpu; @@ -183,5 +202,7 @@ int main(void) set_cpuid_after_run(vcpu); + test_get_cpuid2(vcpu); + kvm_vm_free(vm); } diff --git a/tools/testing/selftests/kvm/x86_64/dirty_log_page_splitting_test.c b/tools/testing/selftests/kvm/x86_64/dirty_log_page_splitting_test.c new file mode 100644 index 000000000000..beb7e2c10211 --- /dev/null +++ b/tools/testing/selftests/kvm/x86_64/dirty_log_page_splitting_test.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KVM dirty logging page splitting test + * + * Based on dirty_log_perf.c + * + * Copyright (C) 2018, Red Hat, Inc. + * Copyright (C) 2023, Google, Inc. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <linux/bitmap.h> + +#include "kvm_util.h" +#include "test_util.h" +#include "memstress.h" +#include "guest_modes.h" + +#define VCPUS 2 +#define SLOTS 2 +#define ITERATIONS 2 + +static uint64_t guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE; + +static enum vm_mem_backing_src_type backing_src = VM_MEM_SRC_ANONYMOUS_HUGETLB; + +static u64 dirty_log_manual_caps; +static bool host_quit; +static int iteration; +static int vcpu_last_completed_iteration[KVM_MAX_VCPUS]; + +struct kvm_page_stats { + uint64_t pages_4k; + uint64_t pages_2m; + uint64_t pages_1g; + uint64_t hugepages; +}; + +static void get_page_stats(struct kvm_vm *vm, struct kvm_page_stats *stats, const char *stage) +{ + stats->pages_4k = vm_get_stat(vm, "pages_4k"); + stats->pages_2m = vm_get_stat(vm, "pages_2m"); + stats->pages_1g = vm_get_stat(vm, "pages_1g"); + stats->hugepages = stats->pages_2m + stats->pages_1g; + + pr_debug("\nPage stats after %s: 4K: %ld 2M: %ld 1G: %ld huge: %ld\n", + stage, stats->pages_4k, stats->pages_2m, stats->pages_1g, + stats->hugepages); +} + +static void run_vcpu_iteration(struct kvm_vm *vm) +{ + int i; + + iteration++; + for (i = 0; i < VCPUS; i++) { + while (READ_ONCE(vcpu_last_completed_iteration[i]) != + iteration) + ; + } +} + +static void vcpu_worker(struct memstress_vcpu_args *vcpu_args) +{ + struct kvm_vcpu *vcpu = vcpu_args->vcpu; + int vcpu_idx = vcpu_args->vcpu_idx; + + while (!READ_ONCE(host_quit)) { + int current_iteration = READ_ONCE(iteration); + + vcpu_run(vcpu); + + ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_SYNC); + + vcpu_last_completed_iteration[vcpu_idx] = current_iteration; + + /* Wait for the start of the next iteration to be signaled. */ + while (current_iteration == READ_ONCE(iteration) && + READ_ONCE(iteration) >= 0 && + !READ_ONCE(host_quit)) + ; + } +} + +static void run_test(enum vm_guest_mode mode, void *unused) +{ + struct kvm_vm *vm; + unsigned long **bitmaps; + uint64_t guest_num_pages; + uint64_t host_num_pages; + uint64_t pages_per_slot; + int i; + uint64_t total_4k_pages; + struct kvm_page_stats stats_populated; + struct kvm_page_stats stats_dirty_logging_enabled; + struct kvm_page_stats stats_dirty_pass[ITERATIONS]; + struct kvm_page_stats stats_clear_pass[ITERATIONS]; + struct kvm_page_stats stats_dirty_logging_disabled; + struct kvm_page_stats stats_repopulated; + + vm = memstress_create_vm(mode, VCPUS, guest_percpu_mem_size, + SLOTS, backing_src, false); + + guest_num_pages = (VCPUS * guest_percpu_mem_size) >> vm->page_shift; + guest_num_pages = vm_adjust_num_guest_pages(mode, guest_num_pages); + host_num_pages = vm_num_host_pages(mode, guest_num_pages); + pages_per_slot = host_num_pages / SLOTS; + + bitmaps = memstress_alloc_bitmaps(SLOTS, pages_per_slot); + + if (dirty_log_manual_caps) + vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2, + dirty_log_manual_caps); + + /* Start the iterations */ + iteration = -1; + host_quit = false; + + for (i = 0; i < VCPUS; i++) + vcpu_last_completed_iteration[i] = -1; + + memstress_start_vcpu_threads(VCPUS, vcpu_worker); + + run_vcpu_iteration(vm); + get_page_stats(vm, &stats_populated, "populating memory"); + + /* Enable dirty logging */ + memstress_enable_dirty_logging(vm, SLOTS); + + get_page_stats(vm, &stats_dirty_logging_enabled, "enabling dirty logging"); + + while (iteration < ITERATIONS) { + run_vcpu_iteration(vm); + get_page_stats(vm, &stats_dirty_pass[iteration - 1], + "dirtying memory"); + + memstress_get_dirty_log(vm, bitmaps, SLOTS); + + if (dirty_log_manual_caps) { + memstress_clear_dirty_log(vm, bitmaps, SLOTS, pages_per_slot); + + get_page_stats(vm, &stats_clear_pass[iteration - 1], "clearing dirty log"); + } + } + + /* Disable dirty logging */ + memstress_disable_dirty_logging(vm, SLOTS); + + get_page_stats(vm, &stats_dirty_logging_disabled, "disabling dirty logging"); + + /* Run vCPUs again to fault pages back in. */ + run_vcpu_iteration(vm); + get_page_stats(vm, &stats_repopulated, "repopulating memory"); + + /* + * Tell the vCPU threads to quit. No need to manually check that vCPUs + * have stopped running after disabling dirty logging, the join will + * wait for them to exit. + */ + host_quit = true; + memstress_join_vcpu_threads(VCPUS); + + memstress_free_bitmaps(bitmaps, SLOTS); + memstress_destroy_vm(vm); + + /* Make assertions about the page counts. */ + total_4k_pages = stats_populated.pages_4k; + total_4k_pages += stats_populated.pages_2m * 512; + total_4k_pages += stats_populated.pages_1g * 512 * 512; + + /* + * Check that all huge pages were split. Since large pages can only + * exist in the data slot, and the vCPUs should have dirtied all pages + * in the data slot, there should be no huge pages left after splitting. + * Splitting happens at dirty log enable time without + * KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 and after the first clear pass + * with that capability. + */ + if (dirty_log_manual_caps) { + ASSERT_EQ(stats_clear_pass[0].hugepages, 0); + ASSERT_EQ(stats_clear_pass[0].pages_4k, total_4k_pages); + ASSERT_EQ(stats_dirty_logging_enabled.hugepages, stats_populated.hugepages); + } else { + ASSERT_EQ(stats_dirty_logging_enabled.hugepages, 0); + ASSERT_EQ(stats_dirty_logging_enabled.pages_4k, total_4k_pages); + } + + /* + * Once dirty logging is disabled and the vCPUs have touched all their + * memory again, the page counts should be the same as they were + * right after initial population of memory. + */ + ASSERT_EQ(stats_populated.pages_4k, stats_repopulated.pages_4k); + ASSERT_EQ(stats_populated.pages_2m, stats_repopulated.pages_2m); + ASSERT_EQ(stats_populated.pages_1g, stats_repopulated.pages_1g); +} + +static void help(char *name) +{ + puts(""); + printf("usage: %s [-h] [-b vcpu bytes] [-s mem type]\n", + name); + puts(""); + printf(" -b: specify the size of the memory region which should be\n" + " dirtied by each vCPU. e.g. 10M or 3G.\n" + " (default: 1G)\n"); + backing_src_help("-s"); + puts(""); +} + +int main(int argc, char *argv[]) +{ + int opt; + + TEST_REQUIRE(get_kvm_param_bool("eager_page_split")); + TEST_REQUIRE(get_kvm_param_bool("tdp_mmu")); + + while ((opt = getopt(argc, argv, "b:hs:")) != -1) { + switch (opt) { + case 'b': + guest_percpu_mem_size = parse_size(optarg); + break; + case 'h': + help(argv[0]); + exit(0); + case 's': + backing_src = parse_backing_src_type(optarg); + break; + default: + help(argv[0]); + exit(1); + } + } + + if (!is_backing_src_hugetlb(backing_src)) { + pr_info("This test will only work reliably with HugeTLB memory. " + "It can work with THP, but that is best effort.\n"); + } + + guest_modes_append_default(); + + dirty_log_manual_caps = 0; + for_each_guest_mode(run_test, NULL); + + dirty_log_manual_caps = + kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2); + + if (dirty_log_manual_caps) { + dirty_log_manual_caps &= (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | + KVM_DIRTY_LOG_INITIALLY_SET); + for_each_guest_mode(run_test, NULL); + } else { + pr_info("Skipping testing with MANUAL_PROTECT as it is not supported"); + } + + return 0; +} diff --git a/tools/testing/selftests/kvm/x86_64/nx_huge_pages_test.c b/tools/testing/selftests/kvm/x86_64/nx_huge_pages_test.c index 251794f83719..7f36c32fa760 100644 --- a/tools/testing/selftests/kvm/x86_64/nx_huge_pages_test.c +++ b/tools/testing/selftests/kvm/x86_64/nx_huge_pages_test.c @@ -226,7 +226,7 @@ static void help(char *name) puts(""); printf("usage: %s [-h] [-p period_ms] [-t token]\n", name); puts(""); - printf(" -p: The NX reclaim period in miliseconds.\n"); + printf(" -p: The NX reclaim period in milliseconds.\n"); printf(" -t: The magic token to indicate environment setup is done.\n"); printf(" -r: The test has reboot permissions and can disable NX huge pages.\n"); puts(""); diff --git a/tools/testing/selftests/kvm/x86_64/recalc_apic_map_test.c b/tools/testing/selftests/kvm/x86_64/recalc_apic_map_test.c new file mode 100644 index 000000000000..4c416ebe7d66 --- /dev/null +++ b/tools/testing/selftests/kvm/x86_64/recalc_apic_map_test.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test edge cases and race conditions in kvm_recalculate_apic_map(). + */ + +#include <sys/ioctl.h> +#include <pthread.h> +#include <time.h> + +#include "processor.h" +#include "test_util.h" +#include "kvm_util.h" +#include "apic.h" + +#define TIMEOUT 5 /* seconds */ + +#define LAPIC_DISABLED 0 +#define LAPIC_X2APIC (MSR_IA32_APICBASE_ENABLE | X2APIC_ENABLE) +#define MAX_XAPIC_ID 0xff + +static void *race(void *arg) +{ + struct kvm_lapic_state lapic = {}; + struct kvm_vcpu *vcpu = arg; + + while (1) { + /* Trigger kvm_recalculate_apic_map(). */ + vcpu_ioctl(vcpu, KVM_SET_LAPIC, &lapic); + pthread_testcancel(); + } + + return NULL; +} + +int main(void) +{ + struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; + struct kvm_vcpu *vcpuN; + struct kvm_vm *vm; + pthread_t thread; + time_t t; + int i; + + kvm_static_assert(KVM_MAX_VCPUS > MAX_XAPIC_ID); + + /* + * Create the max number of vCPUs supported by selftests so that KVM + * has decent amount of work to do when recalculating the map, i.e. to + * make the problematic window large enough to hit. + */ + vm = vm_create_with_vcpus(KVM_MAX_VCPUS, NULL, vcpus); + + /* + * Enable x2APIC on all vCPUs so that KVM doesn't bail from the recalc + * due to vCPUs having aliased xAPIC IDs (truncated to 8 bits). + */ + for (i = 0; i < KVM_MAX_VCPUS; i++) + vcpu_set_msr(vcpus[i], MSR_IA32_APICBASE, LAPIC_X2APIC); + + ASSERT_EQ(pthread_create(&thread, NULL, race, vcpus[0]), 0); + + vcpuN = vcpus[KVM_MAX_VCPUS - 1]; + for (t = time(NULL) + TIMEOUT; time(NULL) < t;) { + vcpu_set_msr(vcpuN, MSR_IA32_APICBASE, LAPIC_X2APIC); + vcpu_set_msr(vcpuN, MSR_IA32_APICBASE, LAPIC_DISABLED); + } + + ASSERT_EQ(pthread_cancel(thread), 0); + ASSERT_EQ(pthread_join(thread, NULL), 0); + + kvm_vm_free(vm); + + return 0; +} diff --git a/tools/testing/selftests/kvm/x86_64/vmx_nested_tsc_scaling_test.c b/tools/testing/selftests/kvm/x86_64/vmx_nested_tsc_scaling_test.c index fa03c8d1ce4e..e710b6e7fb38 100644 --- a/tools/testing/selftests/kvm/x86_64/vmx_nested_tsc_scaling_test.c +++ b/tools/testing/selftests/kvm/x86_64/vmx_nested_tsc_scaling_test.c @@ -116,29 +116,21 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) GUEST_DONE(); } -static void stable_tsc_check_supported(void) +static bool system_has_stable_tsc(void) { + bool tsc_is_stable; FILE *fp; char buf[4]; fp = fopen("/sys/devices/system/clocksource/clocksource0/current_clocksource", "r"); if (fp == NULL) - goto skip_test; + return false; - if (fgets(buf, sizeof(buf), fp) == NULL) - goto close_fp; + tsc_is_stable = fgets(buf, sizeof(buf), fp) && + !strncmp(buf, "tsc", sizeof(buf)); - if (strncmp(buf, "tsc", sizeof(buf))) - goto close_fp; - - fclose(fp); - return; - -close_fp: fclose(fp); -skip_test: - print_skip("Kernel does not use TSC clocksource - assuming that host TSC is not stable"); - exit(KSFT_SKIP); + return tsc_is_stable; } int main(int argc, char *argv[]) @@ -156,7 +148,7 @@ int main(int argc, char *argv[]) TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL)); - stable_tsc_check_supported(); + TEST_REQUIRE(system_has_stable_tsc()); /* * We set L1's scale factor to be a random number from 2 to 10. diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config index 0f0a65287bac..3dc9e438eab1 100644 --- a/tools/testing/selftests/landlock/config +++ b/tools/testing/selftests/landlock/config @@ -1,7 +1,10 @@ +CONFIG_CGROUPS=y +CONFIG_CGROUP_SCHED=y CONFIG_OVERLAY_FS=y -CONFIG_SECURITY_LANDLOCK=y -CONFIG_SECURITY_PATH=y +CONFIG_PROC_FS=y CONFIG_SECURITY=y +CONFIG_SECURITY_LANDLOCK=y CONFIG_SHMEM=y -CONFIG_TMPFS_XATTR=y +CONFIG_SYSFS=y CONFIG_TMPFS=y +CONFIG_TMPFS_XATTR=y diff --git a/tools/testing/selftests/landlock/config.um b/tools/testing/selftests/landlock/config.um new file mode 100644 index 000000000000..40937c0395d6 --- /dev/null +++ b/tools/testing/selftests/landlock/config.um @@ -0,0 +1 @@ +CONFIG_HOSTFS=y diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index b6c4be3faf7a..83d565569512 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -10,6 +10,7 @@ #define _GNU_SOURCE #include <fcntl.h> #include <linux/landlock.h> +#include <linux/magic.h> #include <sched.h> #include <stdio.h> #include <string.h> @@ -19,6 +20,7 @@ #include <sys/sendfile.h> #include <sys/stat.h> #include <sys/sysmacros.h> +#include <sys/vfs.h> #include <unistd.h> #include "common.h" @@ -107,8 +109,10 @@ static bool fgrep(FILE *const inf, const char *const str) return false; } -static bool supports_overlayfs(void) +static bool supports_filesystem(const char *const filesystem) { + char str[32]; + int len; bool res; FILE *const inf = fopen("/proc/filesystems", "r"); @@ -119,11 +123,33 @@ static bool supports_overlayfs(void) if (!inf) return true; - res = fgrep(inf, "nodev\toverlay\n"); + /* filesystem can be null for bind mounts. */ + if (!filesystem) + return true; + + len = snprintf(str, sizeof(str), "nodev\t%s\n", filesystem); + if (len >= sizeof(str)) + /* Ignores too-long filesystem names. */ + return true; + + res = fgrep(inf, str); fclose(inf); return res; } +static bool cwd_matches_fs(unsigned int fs_magic) +{ + struct statfs statfs_buf; + + if (!fs_magic) + return true; + + if (statfs(".", &statfs_buf)) + return true; + + return statfs_buf.f_type == fs_magic; +} + static void mkdir_parents(struct __test_metadata *const _metadata, const char *const path) { @@ -206,7 +232,26 @@ out: return err; } -static void prepare_layout(struct __test_metadata *const _metadata) +struct mnt_opt { + const char *const source; + const char *const type; + const unsigned long flags; + const char *const data; +}; + +const struct mnt_opt mnt_tmp = { + .type = "tmpfs", + .data = "size=4m,mode=700", +}; + +static int mount_opt(const struct mnt_opt *const mnt, const char *const target) +{ + return mount(mnt->source ?: mnt->type, target, mnt->type, mnt->flags, + mnt->data); +} + +static void prepare_layout_opt(struct __test_metadata *const _metadata, + const struct mnt_opt *const mnt) { disable_caps(_metadata); umask(0077); @@ -217,12 +262,28 @@ static void prepare_layout(struct __test_metadata *const _metadata) * for tests relying on pivot_root(2) and move_mount(2). */ set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, unshare(CLONE_NEWNS)); - ASSERT_EQ(0, mount("tmp", TMP_DIR, "tmpfs", 0, "size=4m,mode=700")); + ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWCGROUP)); + ASSERT_EQ(0, mount_opt(mnt, TMP_DIR)) + { + TH_LOG("Failed to mount the %s filesystem: %s", mnt->type, + strerror(errno)); + /* + * FIXTURE_TEARDOWN() is not called when FIXTURE_SETUP() + * failed, so we need to explicitly do a minimal cleanup to + * avoid cascading errors with other tests that don't depend on + * the same filesystem. + */ + remove_path(TMP_DIR); + } ASSERT_EQ(0, mount(NULL, TMP_DIR, NULL, MS_PRIVATE | MS_REC, NULL)); clear_cap(_metadata, CAP_SYS_ADMIN); } +static void prepare_layout(struct __test_metadata *const _metadata) +{ + prepare_layout_opt(_metadata, &mnt_tmp); +} + static void cleanup_layout(struct __test_metadata *const _metadata) { set_cap(_metadata, CAP_SYS_ADMIN); @@ -231,6 +292,20 @@ static void cleanup_layout(struct __test_metadata *const _metadata) EXPECT_EQ(0, remove_path(TMP_DIR)); } +/* clang-format off */ +FIXTURE(layout0) {}; +/* clang-format on */ + +FIXTURE_SETUP(layout0) +{ + prepare_layout(_metadata); +} + +FIXTURE_TEARDOWN(layout0) +{ + cleanup_layout(_metadata); +} + static void create_layout1(struct __test_metadata *const _metadata) { create_file(_metadata, file1_s1d1); @@ -248,7 +323,7 @@ static void create_layout1(struct __test_metadata *const _metadata) create_file(_metadata, file1_s3d1); create_directory(_metadata, dir_s3d2); set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700")); + ASSERT_EQ(0, mount_opt(&mnt_tmp, dir_s3d2)); clear_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, mkdir(dir_s3d3, 0700)); @@ -262,11 +337,13 @@ static void remove_layout1(struct __test_metadata *const _metadata) EXPECT_EQ(0, remove_path(file1_s1d3)); EXPECT_EQ(0, remove_path(file1_s1d2)); EXPECT_EQ(0, remove_path(file1_s1d1)); + EXPECT_EQ(0, remove_path(dir_s1d3)); EXPECT_EQ(0, remove_path(file2_s2d3)); EXPECT_EQ(0, remove_path(file1_s2d3)); EXPECT_EQ(0, remove_path(file1_s2d2)); EXPECT_EQ(0, remove_path(file1_s2d1)); + EXPECT_EQ(0, remove_path(dir_s2d2)); EXPECT_EQ(0, remove_path(file1_s3d1)); EXPECT_EQ(0, remove_path(dir_s3d3)); @@ -510,7 +587,7 @@ TEST_F_FORK(layout1, file_and_dir_access_rights) ASSERT_EQ(0, close(ruleset_fd)); } -TEST_F_FORK(layout1, unknown_access_rights) +TEST_F_FORK(layout0, unknown_access_rights) { __u64 access_mask; @@ -608,7 +685,7 @@ static void enforce_ruleset(struct __test_metadata *const _metadata, } } -TEST_F_FORK(layout1, proc_nsfs) +TEST_F_FORK(layout0, proc_nsfs) { const struct rule rules[] = { { @@ -657,11 +734,11 @@ TEST_F_FORK(layout1, proc_nsfs) ASSERT_EQ(0, close(path_beneath.parent_fd)); } -TEST_F_FORK(layout1, unpriv) +TEST_F_FORK(layout0, unpriv) { const struct rule rules[] = { { - .path = dir_s1d2, + .path = TMP_DIR, .access = ACCESS_RO, }, {}, @@ -1301,12 +1378,12 @@ TEST_F_FORK(layout1, inherit_superset) ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); } -TEST_F_FORK(layout1, max_layers) +TEST_F_FORK(layout0, max_layers) { int i, err; const struct rule rules[] = { { - .path = dir_s1d2, + .path = TMP_DIR, .access = ACCESS_RO, }, {}, @@ -4030,21 +4107,24 @@ static const char (*merge_sub_files[])[] = { * └── work */ -/* clang-format off */ -FIXTURE(layout2_overlay) {}; -/* clang-format on */ +FIXTURE(layout2_overlay) +{ + bool skip_test; +}; FIXTURE_SETUP(layout2_overlay) { - if (!supports_overlayfs()) - SKIP(return, "overlayfs is not supported"); + if (!supports_filesystem("overlay")) { + self->skip_test = true; + SKIP(return, "overlayfs is not supported (setup)"); + } prepare_layout(_metadata); create_directory(_metadata, LOWER_BASE); set_cap(_metadata, CAP_SYS_ADMIN); /* Creates tmpfs mount points to get deterministic overlayfs. */ - ASSERT_EQ(0, mount("tmp", LOWER_BASE, "tmpfs", 0, "size=4m,mode=700")); + ASSERT_EQ(0, mount_opt(&mnt_tmp, LOWER_BASE)); clear_cap(_metadata, CAP_SYS_ADMIN); create_file(_metadata, lower_fl1); create_file(_metadata, lower_dl1_fl2); @@ -4054,7 +4134,7 @@ FIXTURE_SETUP(layout2_overlay) create_directory(_metadata, UPPER_BASE); set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, mount("tmp", UPPER_BASE, "tmpfs", 0, "size=4m,mode=700")); + ASSERT_EQ(0, mount_opt(&mnt_tmp, UPPER_BASE)); clear_cap(_metadata, CAP_SYS_ADMIN); create_file(_metadata, upper_fu1); create_file(_metadata, upper_du1_fu2); @@ -4075,8 +4155,8 @@ FIXTURE_SETUP(layout2_overlay) FIXTURE_TEARDOWN(layout2_overlay) { - if (!supports_overlayfs()) - SKIP(return, "overlayfs is not supported"); + if (self->skip_test) + SKIP(return, "overlayfs is not supported (teardown)"); EXPECT_EQ(0, remove_path(lower_do1_fl3)); EXPECT_EQ(0, remove_path(lower_dl1_fl2)); @@ -4109,8 +4189,8 @@ FIXTURE_TEARDOWN(layout2_overlay) TEST_F_FORK(layout2_overlay, no_restriction) { - if (!supports_overlayfs()) - SKIP(return, "overlayfs is not supported"); + if (self->skip_test) + SKIP(return, "overlayfs is not supported (test)"); ASSERT_EQ(0, test_open(lower_fl1, O_RDONLY)); ASSERT_EQ(0, test_open(lower_dl1, O_RDONLY)); @@ -4275,8 +4355,8 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) size_t i; const char *path_entry; - if (!supports_overlayfs()) - SKIP(return, "overlayfs is not supported"); + if (self->skip_test) + SKIP(return, "overlayfs is not supported (test)"); /* Sets rules on base directories (i.e. outside overlay scope). */ ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); @@ -4423,4 +4503,261 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) } } +FIXTURE(layout3_fs) +{ + bool has_created_dir; + bool has_created_file; + char *dir_path; + bool skip_test; +}; + +FIXTURE_VARIANT(layout3_fs) +{ + const struct mnt_opt mnt; + const char *const file_path; + unsigned int cwd_fs_magic; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout3_fs, tmpfs) { + /* clang-format on */ + .mnt = mnt_tmp, + .file_path = file1_s1d1, +}; + +FIXTURE_VARIANT_ADD(layout3_fs, ramfs) { + .mnt = { + .type = "ramfs", + .data = "mode=700", + }, + .file_path = TMP_DIR "/dir/file", +}; + +FIXTURE_VARIANT_ADD(layout3_fs, cgroup2) { + .mnt = { + .type = "cgroup2", + }, + .file_path = TMP_DIR "/test/cgroup.procs", +}; + +FIXTURE_VARIANT_ADD(layout3_fs, proc) { + .mnt = { + .type = "proc", + }, + .file_path = TMP_DIR "/self/status", +}; + +FIXTURE_VARIANT_ADD(layout3_fs, sysfs) { + .mnt = { + .type = "sysfs", + }, + .file_path = TMP_DIR "/kernel/notes", +}; + +FIXTURE_VARIANT_ADD(layout3_fs, hostfs) { + .mnt = { + .source = TMP_DIR, + .flags = MS_BIND, + }, + .file_path = TMP_DIR "/dir/file", + .cwd_fs_magic = HOSTFS_SUPER_MAGIC, +}; + +FIXTURE_SETUP(layout3_fs) +{ + struct stat statbuf; + const char *slash; + size_t dir_len; + + if (!supports_filesystem(variant->mnt.type) || + !cwd_matches_fs(variant->cwd_fs_magic)) { + self->skip_test = true; + SKIP(return, "this filesystem is not supported (setup)"); + } + + slash = strrchr(variant->file_path, '/'); + ASSERT_NE(slash, NULL); + dir_len = (size_t)slash - (size_t)variant->file_path; + ASSERT_LT(0, dir_len); + self->dir_path = malloc(dir_len + 1); + self->dir_path[dir_len] = '\0'; + strncpy(self->dir_path, variant->file_path, dir_len); + + prepare_layout_opt(_metadata, &variant->mnt); + + /* Creates directory when required. */ + if (stat(self->dir_path, &statbuf)) { + set_cap(_metadata, CAP_DAC_OVERRIDE); + EXPECT_EQ(0, mkdir(self->dir_path, 0700)) + { + TH_LOG("Failed to create directory \"%s\": %s", + self->dir_path, strerror(errno)); + free(self->dir_path); + self->dir_path = NULL; + } + self->has_created_dir = true; + clear_cap(_metadata, CAP_DAC_OVERRIDE); + } + + /* Creates file when required. */ + if (stat(variant->file_path, &statbuf)) { + int fd; + + set_cap(_metadata, CAP_DAC_OVERRIDE); + fd = creat(variant->file_path, 0600); + EXPECT_LE(0, fd) + { + TH_LOG("Failed to create file \"%s\": %s", + variant->file_path, strerror(errno)); + } + EXPECT_EQ(0, close(fd)); + self->has_created_file = true; + clear_cap(_metadata, CAP_DAC_OVERRIDE); + } +} + +FIXTURE_TEARDOWN(layout3_fs) +{ + if (self->skip_test) + SKIP(return, "this filesystem is not supported (teardown)"); + + if (self->has_created_file) { + set_cap(_metadata, CAP_DAC_OVERRIDE); + /* + * Don't check for error because the file might already + * have been removed (cf. release_inode test). + */ + unlink(variant->file_path); + clear_cap(_metadata, CAP_DAC_OVERRIDE); + } + + if (self->has_created_dir) { + set_cap(_metadata, CAP_DAC_OVERRIDE); + /* + * Don't check for error because the directory might already + * have been removed (cf. release_inode test). + */ + rmdir(self->dir_path); + clear_cap(_metadata, CAP_DAC_OVERRIDE); + } + free(self->dir_path); + self->dir_path = NULL; + + cleanup_layout(_metadata); +} + +static void layer3_fs_tag_inode(struct __test_metadata *const _metadata, + FIXTURE_DATA(layout3_fs) * self, + const FIXTURE_VARIANT(layout3_fs) * variant, + const char *const rule_path) +{ + const struct rule layer1_allow_read_file[] = { + { + .path = rule_path, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + {}, + }; + const struct landlock_ruleset_attr layer2_deny_everything_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, + }; + const char *const dev_null_path = "/dev/null"; + int ruleset_fd; + + if (self->skip_test) + SKIP(return, "this filesystem is not supported (test)"); + + /* Checks without Landlock. */ + EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); + EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); + + ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, + layer1_allow_read_file); + EXPECT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); + EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); + + /* Forbids directory reading. */ + ruleset_fd = + landlock_create_ruleset(&layer2_deny_everything_attr, + sizeof(layer2_deny_everything_attr), 0); + EXPECT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Checks with Landlock and forbidden access. */ + EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); + EXPECT_EQ(EACCES, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); +} + +/* Matrix of tests to check file hierarchy evaluation. */ + +TEST_F_FORK(layout3_fs, tag_inode_dir_parent) +{ + /* The current directory must not be the root for this test. */ + layer3_fs_tag_inode(_metadata, self, variant, "."); +} + +TEST_F_FORK(layout3_fs, tag_inode_dir_mnt) +{ + layer3_fs_tag_inode(_metadata, self, variant, TMP_DIR); +} + +TEST_F_FORK(layout3_fs, tag_inode_dir_child) +{ + layer3_fs_tag_inode(_metadata, self, variant, self->dir_path); +} + +TEST_F_FORK(layout3_fs, tag_inode_file) +{ + layer3_fs_tag_inode(_metadata, self, variant, variant->file_path); +} + +/* Light version of layout1.release_inodes */ +TEST_F_FORK(layout3_fs, release_inodes) +{ + const struct rule layer1[] = { + { + .path = TMP_DIR, + .access = LANDLOCK_ACCESS_FS_READ_DIR, + }, + {}, + }; + int ruleset_fd; + + if (self->skip_test) + SKIP(return, "this filesystem is not supported (test)"); + + /* Clean up for the teardown to not fail. */ + if (self->has_created_file) + EXPECT_EQ(0, remove_path(variant->file_path)); + + if (self->has_created_dir) + /* Don't check for error because of cgroup specificities. */ + remove_path(self->dir_path); + + ruleset_fd = + create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1); + ASSERT_LE(0, ruleset_fd); + + /* Unmount the filesystem while it is being used by a ruleset. */ + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, umount(TMP_DIR)); + clear_cap(_metadata, CAP_SYS_ADMIN); + + /* Replaces with a new mount point to simplify FIXTURE_TEARDOWN. */ + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, mount_opt(&mnt_tmp, TMP_DIR)); + clear_cap(_metadata, CAP_SYS_ADMIN); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks that access to the new mount point is denied. */ + ASSERT_EQ(EACCES, test_open(TMP_DIR, O_RDONLY)); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk index 05400462c779..d17854285f2b 100644 --- a/tools/testing/selftests/lib.mk +++ b/tools/testing/selftests/lib.mk @@ -44,10 +44,26 @@ endif selfdir = $(realpath $(dir $(filter %/lib.mk,$(MAKEFILE_LIST)))) top_srcdir = $(selfdir)/../../.. -ifeq ($(KHDR_INCLUDES),) -KHDR_INCLUDES := -isystem $(top_srcdir)/usr/include +ifeq ("$(origin O)", "command line") + KBUILD_OUTPUT := $(O) endif +ifneq ($(KBUILD_OUTPUT),) + # Make's built-in functions such as $(abspath ...), $(realpath ...) cannot + # expand a shell special character '~'. We use a somewhat tedious way here. + abs_objtree := $(shell cd $(top_srcdir) && mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) && pwd) + $(if $(abs_objtree),, \ + $(error failed to create output directory "$(KBUILD_OUTPUT)")) + # $(realpath ...) resolves symlinks + abs_objtree := $(realpath $(abs_objtree)) + KHDR_DIR := ${abs_objtree}/usr/include +else + abs_srctree := $(shell cd $(top_srcdir) && pwd) + KHDR_DIR := ${abs_srctree}/usr/include +endif + +KHDR_INCLUDES := -isystem $(KHDR_DIR) + # The following are built by lib.mk common compile rules. # TEST_CUSTOM_PROGS should be used by tests that require # custom build rule and prevent common build rule use. @@ -58,7 +74,25 @@ TEST_GEN_PROGS := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS)) TEST_GEN_PROGS_EXTENDED := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_PROGS_EXTENDED)) TEST_GEN_FILES := $(patsubst %,$(OUTPUT)/%,$(TEST_GEN_FILES)) -all: $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) +all: kernel_header_files $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) \ + $(TEST_GEN_FILES) + +kernel_header_files: + @ls $(KHDR_DIR)/linux/*.h >/dev/null 2>/dev/null; \ + if [ $$? -ne 0 ]; then \ + RED='\033[1;31m'; \ + NOCOLOR='\033[0m'; \ + echo; \ + echo -e "$${RED}error$${NOCOLOR}: missing kernel header files."; \ + echo "Please run this and try again:"; \ + echo; \ + echo " cd $(top_srcdir)"; \ + echo " make headers"; \ + echo; \ + exit 1; \ + fi + +.PHONY: kernel_header_files define RUN_TESTS BASE_DIR="$(selfdir)"; \ diff --git a/tools/testing/selftests/media_tests/video_device_test.c b/tools/testing/selftests/media_tests/video_device_test.c index 0f6aef2e2593..2c44e115f2f0 100644 --- a/tools/testing/selftests/media_tests/video_device_test.c +++ b/tools/testing/selftests/media_tests/video_device_test.c @@ -37,45 +37,58 @@ #include <time.h> #include <linux/videodev2.h> -int main(int argc, char **argv) +#define PRIORITY_MAX 4 + +int priority_test(int fd) { - int opt; - char video_dev[256]; - int count; - struct v4l2_tuner vtuner; - struct v4l2_capability vcap; + /* This test will try to update the priority associated with a file descriptor */ + + enum v4l2_priority old_priority, new_priority, priority_to_compare; int ret; - int fd; + int result = 0; - if (argc < 2) { - printf("Usage: %s [-d </dev/videoX>]\n", argv[0]); - exit(-1); + ret = ioctl(fd, VIDIOC_G_PRIORITY, &old_priority); + if (ret < 0) { + printf("Failed to get priority: %s\n", strerror(errno)); + return -1; + } + new_priority = (old_priority + 1) % PRIORITY_MAX; + ret = ioctl(fd, VIDIOC_S_PRIORITY, &new_priority); + if (ret < 0) { + printf("Failed to set priority: %s\n", strerror(errno)); + return -1; + } + ret = ioctl(fd, VIDIOC_G_PRIORITY, &priority_to_compare); + if (ret < 0) { + printf("Failed to get new priority: %s\n", strerror(errno)); + result = -1; + goto cleanup; + } + if (priority_to_compare != new_priority) { + printf("Priority wasn't set - test failed\n"); + result = -1; } - /* Process arguments */ - while ((opt = getopt(argc, argv, "d:")) != -1) { - switch (opt) { - case 'd': - strncpy(video_dev, optarg, sizeof(video_dev) - 1); - video_dev[sizeof(video_dev)-1] = '\0'; - break; - default: - printf("Usage: %s [-d </dev/videoX>]\n", argv[0]); - exit(-1); - } +cleanup: + ret = ioctl(fd, VIDIOC_S_PRIORITY, &old_priority); + if (ret < 0) { + printf("Failed to restore priority: %s\n", strerror(errno)); + return -1; } + return result; +} + +int loop_test(int fd) +{ + int count; + struct v4l2_tuner vtuner; + struct v4l2_capability vcap; + int ret; /* Generate random number of interations */ srand((unsigned int) time(NULL)); count = rand(); - /* Open Video device and keep it open */ - fd = open(video_dev, O_RDWR); - if (fd == -1) { - printf("Video Device open errno %s\n", strerror(errno)); - exit(-1); - } - printf("\nNote:\n" "While test is running, remove the device or unbind\n" "driver and ensure there are no use after free errors\n" @@ -98,4 +111,46 @@ int main(int argc, char **argv) sleep(10); count--; } + return 0; +} + +int main(int argc, char **argv) +{ + int opt; + char video_dev[256]; + int fd; + int test_result; + + if (argc < 2) { + printf("Usage: %s [-d </dev/videoX>]\n", argv[0]); + exit(-1); + } + + /* Process arguments */ + while ((opt = getopt(argc, argv, "d:")) != -1) { + switch (opt) { + case 'd': + strncpy(video_dev, optarg, sizeof(video_dev) - 1); + video_dev[sizeof(video_dev)-1] = '\0'; + break; + default: + printf("Usage: %s [-d </dev/videoX>]\n", argv[0]); + exit(-1); + } + } + + /* Open Video device and keep it open */ + fd = open(video_dev, O_RDWR); + if (fd == -1) { + printf("Video Device open errno %s\n", strerror(errno)); + exit(-1); + } + + test_result = priority_test(fd); + if (!test_result) + printf("Priority test - PASSED\n"); + else + printf("Priority test - FAILED\n"); + + loop_test(fd); } diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore index 8917455f4f51..7e2a982383c0 100644 --- a/tools/testing/selftests/mm/.gitignore +++ b/tools/testing/selftests/mm/.gitignore @@ -39,3 +39,6 @@ local_config.h local_config.mk ksm_functional_tests mdwe_test +gup_longterm +mkdirty +va_high_addr_switch diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index 23af4633f0f4..66d7c07dc177 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -5,12 +5,15 @@ LOCAL_HDRS += $(selfdir)/mm/local_config.h $(top_srcdir)/mm/gup_test.h include local_config.mk +ifeq ($(ARCH),) + ifeq ($(CROSS_COMPILE),) uname_M := $(shell uname -m 2>/dev/null || echo not) else uname_M := $(shell echo $(CROSS_COMPILE) | grep -o '^[a-z0-9]\+') endif -MACHINE ?= $(shell echo $(uname_M) | sed -e 's/aarch64.*/arm64/' -e 's/ppc64.*/ppc64/') +ARCH ?= $(shell echo $(uname_M) | sed -e 's/aarch64.*/arm64/' -e 's/ppc64.*/ppc64/') +endif # Without this, failed build products remain, with up-to-date timestamps, # thus tricking Make (and you!) into believing that All Is Well, in subsequent @@ -29,11 +32,12 @@ MACHINE ?= $(shell echo $(uname_M) | sed -e 's/aarch64.*/arm64/' -e 's/ppc64.*/p # LDLIBS. MAKEFLAGS += --no-builtin-rules -CFLAGS = -Wall -I $(top_srcdir) -I $(top_srcdir)/tools/include/uapi $(EXTRA_CFLAGS) $(KHDR_INCLUDES) +CFLAGS = -Wall -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) LDLIBS = -lrt -lpthread TEST_GEN_PROGS = cow TEST_GEN_PROGS += compaction_test +TEST_GEN_PROGS += gup_longterm TEST_GEN_PROGS += gup_test TEST_GEN_PROGS += hmm-tests TEST_GEN_PROGS += hugetlb-madvise @@ -65,7 +69,7 @@ TEST_GEN_PROGS += ksm_tests TEST_GEN_PROGS += ksm_functional_tests TEST_GEN_PROGS += mdwe_test -ifeq ($(MACHINE),x86_64) +ifeq ($(ARCH),x86_64) CAN_BUILD_I386 := $(shell ./../x86/check_cc.sh "$(CC)" ../x86/trivial_32bit_program.c -m32) CAN_BUILD_X86_64 := $(shell ./../x86/check_cc.sh "$(CC)" ../x86/trivial_64bit_program.c) CAN_BUILD_WITH_NOPIE := $(shell ./../x86/check_cc.sh "$(CC)" ../x86/trivial_program.c -no-pie) @@ -87,13 +91,13 @@ TEST_GEN_PROGS += $(BINARIES_64) endif else -ifneq (,$(findstring $(MACHINE),ppc64)) +ifneq (,$(findstring $(ARCH),ppc64)) TEST_GEN_PROGS += protection_keys endif endif -ifneq (,$(filter $(MACHINE),arm64 ia64 mips64 parisc64 ppc64 riscv64 s390x sparc64 x86_64)) +ifneq (,$(filter $(ARCH),arm64 ia64 mips64 parisc64 ppc64 riscv64 s390x sparc64 x86_64)) TEST_GEN_PROGS += va_high_addr_switch TEST_GEN_PROGS += virtual_address_range TEST_GEN_PROGS += write_to_hugetlbfs @@ -112,7 +116,7 @@ $(TEST_GEN_PROGS): vm_util.c $(OUTPUT)/uffd-stress: uffd-common.c $(OUTPUT)/uffd-unit-tests: uffd-common.c -ifeq ($(MACHINE),x86_64) +ifeq ($(ARCH),x86_64) BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32)) BINARIES_64 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_64)) @@ -164,6 +168,8 @@ endif # IOURING_EXTRA_LIBS may get set in local_config.mk, or it may be left empty. $(OUTPUT)/cow: LDLIBS += $(IOURING_EXTRA_LIBS) +$(OUTPUT)/gup_longterm: LDLIBS += $(IOURING_EXTRA_LIBS) + $(OUTPUT)/mlock-random-test $(OUTPUT)/memfd_secret: LDLIBS += -lcap $(OUTPUT)/ksm_tests: LDLIBS += -lnuma diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c index dc9d6fe86028..7324ce5363c0 100644 --- a/tools/testing/selftests/mm/cow.c +++ b/tools/testing/selftests/mm/cow.c @@ -14,8 +14,8 @@ #include <unistd.h> #include <errno.h> #include <fcntl.h> -#include <dirent.h> #include <assert.h> +#include <linux/mman.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/wait.h> @@ -30,13 +30,6 @@ #include "../kselftest.h" #include "vm_util.h" -#ifndef MADV_PAGEOUT -#define MADV_PAGEOUT 21 -#endif -#ifndef MADV_COLLAPSE -#define MADV_COLLAPSE 25 -#endif - static size_t pagesize; static int pagemap_fd; static size_t thpsize; @@ -70,31 +63,6 @@ static void detect_huge_zeropage(void) close(fd); } -static void detect_hugetlbsizes(void) -{ - DIR *dir = opendir("/sys/kernel/mm/hugepages/"); - - if (!dir) - return; - - while (nr_hugetlbsizes < ARRAY_SIZE(hugetlbsizes)) { - struct dirent *entry = readdir(dir); - size_t kb; - - if (!entry) - break; - if (entry->d_type != DT_DIR) - continue; - if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1) - continue; - hugetlbsizes[nr_hugetlbsizes] = kb * 1024; - nr_hugetlbsizes++; - ksft_print_msg("[INFO] detected hugetlb size: %zu KiB\n", - kb); - } - closedir(dir); -} - static bool range_is_swapped(void *addr, size_t size) { for (; size; addr += pagesize, size -= pagesize) @@ -1717,7 +1685,8 @@ int main(int argc, char **argv) if (thpsize) ksft_print_msg("[INFO] detected THP size: %zu KiB\n", thpsize / 1024); - detect_hugetlbsizes(); + nr_hugetlbsizes = detect_hugetlb_page_sizes(hugetlbsizes, + ARRAY_SIZE(hugetlbsizes)); detect_huge_zeropage(); ksft_print_header(); diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c new file mode 100644 index 000000000000..d33d3e68ffab --- /dev/null +++ b/tools/testing/selftests/mm/gup_longterm.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * GUP long-term page pinning tests. + * + * Copyright 2023, Red Hat, Inc. + * + * Author(s): David Hildenbrand <david@redhat.com> + */ +#define _GNU_SOURCE +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <assert.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/vfs.h> +#include <linux/magic.h> +#include <linux/memfd.h> + +#include "local_config.h" +#ifdef LOCAL_CONFIG_HAVE_LIBURING +#include <liburing.h> +#endif /* LOCAL_CONFIG_HAVE_LIBURING */ + +#include "../../../../mm/gup_test.h" +#include "../kselftest.h" +#include "vm_util.h" + +static size_t pagesize; +static int nr_hugetlbsizes; +static size_t hugetlbsizes[10]; +static int gup_fd; + +static __fsword_t get_fs_type(int fd) +{ + struct statfs fs; + int ret; + + do { + ret = fstatfs(fd, &fs); + } while (ret && errno == EINTR); + + return ret ? 0 : fs.f_type; +} + +static bool fs_is_unknown(__fsword_t fs_type) +{ + /* + * We only support some filesystems in our tests when dealing with + * R/W long-term pinning. For these filesystems, we can be fairly sure + * whether they support it or not. + */ + switch (fs_type) { + case TMPFS_MAGIC: + case HUGETLBFS_MAGIC: + case BTRFS_SUPER_MAGIC: + case EXT4_SUPER_MAGIC: + case XFS_SUPER_MAGIC: + return false; + default: + return true; + } +} + +static bool fs_supports_writable_longterm_pinning(__fsword_t fs_type) +{ + assert(!fs_is_unknown(fs_type)); + switch (fs_type) { + case TMPFS_MAGIC: + case HUGETLBFS_MAGIC: + return true; + default: + return false; + } +} + +enum test_type { + TEST_TYPE_RO, + TEST_TYPE_RO_FAST, + TEST_TYPE_RW, + TEST_TYPE_RW_FAST, +#ifdef LOCAL_CONFIG_HAVE_LIBURING + TEST_TYPE_IOURING, +#endif /* LOCAL_CONFIG_HAVE_LIBURING */ +}; + +static void do_test(int fd, size_t size, enum test_type type, bool shared) +{ + __fsword_t fs_type = get_fs_type(fd); + bool should_work; + char *mem; + int ret; + + if (ftruncate(fd, size)) { + ksft_test_result_fail("ftruncate() failed\n"); + return; + } + + if (fallocate(fd, 0, 0, size)) { + if (size == pagesize) + ksft_test_result_fail("fallocate() failed\n"); + else + ksft_test_result_skip("need more free huge pages\n"); + return; + } + + mem = mmap(NULL, size, PROT_READ | PROT_WRITE, + shared ? MAP_SHARED : MAP_PRIVATE, fd, 0); + if (mem == MAP_FAILED) { + if (size == pagesize || shared) + ksft_test_result_fail("mmap() failed\n"); + else + ksft_test_result_skip("need more free huge pages\n"); + return; + } + + /* + * Fault in the page writable such that GUP-fast can eventually pin + * it immediately. + */ + memset(mem, 0, size); + + switch (type) { + case TEST_TYPE_RO: + case TEST_TYPE_RO_FAST: + case TEST_TYPE_RW: + case TEST_TYPE_RW_FAST: { + struct pin_longterm_test args; + const bool fast = type == TEST_TYPE_RO_FAST || + type == TEST_TYPE_RW_FAST; + const bool rw = type == TEST_TYPE_RW || + type == TEST_TYPE_RW_FAST; + + if (gup_fd < 0) { + ksft_test_result_skip("gup_test not available\n"); + break; + } + + if (rw && shared && fs_is_unknown(fs_type)) { + ksft_test_result_skip("Unknown filesystem\n"); + return; + } + /* + * R/O pinning or pinning in a private mapping is always + * expected to work. Otherwise, we expect long-term R/W pinning + * to only succeed for special fielesystems. + */ + should_work = !shared || !rw || + fs_supports_writable_longterm_pinning(fs_type); + + args.addr = (__u64)(uintptr_t)mem; + args.size = size; + args.flags = fast ? PIN_LONGTERM_TEST_FLAG_USE_FAST : 0; + args.flags |= rw ? PIN_LONGTERM_TEST_FLAG_USE_WRITE : 0; + ret = ioctl(gup_fd, PIN_LONGTERM_TEST_START, &args); + if (ret && errno == EINVAL) { + ksft_test_result_skip("PIN_LONGTERM_TEST_START failed\n"); + break; + } else if (ret && errno == EFAULT) { + ksft_test_result(!should_work, "Should have failed\n"); + break; + } else if (ret) { + ksft_test_result_fail("PIN_LONGTERM_TEST_START failed\n"); + break; + } + + if (ioctl(gup_fd, PIN_LONGTERM_TEST_STOP)) + ksft_print_msg("[INFO] PIN_LONGTERM_TEST_STOP failed\n"); + + /* + * TODO: if the kernel ever supports long-term R/W pinning on + * some previously unsupported filesystems, we might want to + * perform some additional tests for possible data corruptions. + */ + ksft_test_result(should_work, "Should have worked\n"); + break; + } +#ifdef LOCAL_CONFIG_HAVE_LIBURING + case TEST_TYPE_IOURING: { + struct io_uring ring; + struct iovec iov; + + /* io_uring always pins pages writable. */ + if (shared && fs_is_unknown(fs_type)) { + ksft_test_result_skip("Unknown filesystem\n"); + return; + } + should_work = !shared || + fs_supports_writable_longterm_pinning(fs_type); + + /* Skip on errors, as we might just lack kernel support. */ + ret = io_uring_queue_init(1, &ring, 0); + if (ret < 0) { + ksft_test_result_skip("io_uring_queue_init() failed\n"); + break; + } + /* + * Register the range as a fixed buffer. This will FOLL_WRITE | + * FOLL_PIN | FOLL_LONGTERM the range. + */ + iov.iov_base = mem; + iov.iov_len = size; + ret = io_uring_register_buffers(&ring, &iov, 1); + /* Only new kernels return EFAULT. */ + if (ret && (errno == ENOSPC || errno == EOPNOTSUPP || + errno == EFAULT)) { + ksft_test_result(!should_work, "Should have failed\n"); + } else if (ret) { + /* + * We might just lack support or have insufficient + * MEMLOCK limits. + */ + ksft_test_result_skip("io_uring_register_buffers() failed\n"); + } else { + ksft_test_result(should_work, "Should have worked\n"); + io_uring_unregister_buffers(&ring); + } + + io_uring_queue_exit(&ring); + break; + } +#endif /* LOCAL_CONFIG_HAVE_LIBURING */ + default: + assert(false); + } + + munmap(mem, size); +} + +typedef void (*test_fn)(int fd, size_t size); + +static void run_with_memfd(test_fn fn, const char *desc) +{ + int fd; + + ksft_print_msg("[RUN] %s ... with memfd\n", desc); + + fd = memfd_create("test", 0); + if (fd < 0) { + ksft_test_result_fail("memfd_create() failed\n"); + return; + } + + fn(fd, pagesize); + close(fd); +} + +static void run_with_tmpfile(test_fn fn, const char *desc) +{ + FILE *file; + int fd; + + ksft_print_msg("[RUN] %s ... with tmpfile\n", desc); + + file = tmpfile(); + if (!file) { + ksft_test_result_fail("tmpfile() failed\n"); + return; + } + + fd = fileno(file); + if (fd < 0) { + ksft_test_result_fail("fileno() failed\n"); + return; + } + + fn(fd, pagesize); + fclose(file); +} + +static void run_with_local_tmpfile(test_fn fn, const char *desc) +{ + char filename[] = __FILE__"_tmpfile_XXXXXX"; + int fd; + + ksft_print_msg("[RUN] %s ... with local tmpfile\n", desc); + + fd = mkstemp(filename); + if (fd < 0) { + ksft_test_result_fail("mkstemp() failed\n"); + return; + } + + if (unlink(filename)) { + ksft_test_result_fail("unlink() failed\n"); + goto close; + } + + fn(fd, pagesize); +close: + close(fd); +} + +static void run_with_memfd_hugetlb(test_fn fn, const char *desc, + size_t hugetlbsize) +{ + int flags = MFD_HUGETLB; + int fd; + + ksft_print_msg("[RUN] %s ... with memfd hugetlb (%zu kB)\n", desc, + hugetlbsize / 1024); + + flags |= __builtin_ctzll(hugetlbsize) << MFD_HUGE_SHIFT; + + fd = memfd_create("test", flags); + if (fd < 0) { + ksft_test_result_skip("memfd_create() failed\n"); + return; + } + + fn(fd, hugetlbsize); + close(fd); +} + +struct test_case { + const char *desc; + test_fn fn; +}; + +static void test_shared_rw_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RW, true); +} + +static void test_shared_rw_fast_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RW_FAST, true); +} + +static void test_shared_ro_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RO, true); +} + +static void test_shared_ro_fast_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RO_FAST, true); +} + +static void test_private_rw_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RW, false); +} + +static void test_private_rw_fast_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RW_FAST, false); +} + +static void test_private_ro_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RO, false); +} + +static void test_private_ro_fast_pin(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_RO_FAST, false); +} + +#ifdef LOCAL_CONFIG_HAVE_LIBURING +static void test_shared_iouring(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_IOURING, true); +} + +static void test_private_iouring(int fd, size_t size) +{ + do_test(fd, size, TEST_TYPE_IOURING, false); +} +#endif /* LOCAL_CONFIG_HAVE_LIBURING */ + +static const struct test_case test_cases[] = { + { + "R/W longterm GUP pin in MAP_SHARED file mapping", + test_shared_rw_pin, + }, + { + "R/W longterm GUP-fast pin in MAP_SHARED file mapping", + test_shared_rw_fast_pin, + }, + { + "R/O longterm GUP pin in MAP_SHARED file mapping", + test_shared_ro_pin, + }, + { + "R/O longterm GUP-fast pin in MAP_SHARED file mapping", + test_shared_ro_fast_pin, + }, + { + "R/W longterm GUP pin in MAP_PRIVATE file mapping", + test_private_rw_pin, + }, + { + "R/W longterm GUP-fast pin in MAP_PRIVATE file mapping", + test_private_rw_fast_pin, + }, + { + "R/O longterm GUP pin in MAP_PRIVATE file mapping", + test_private_ro_pin, + }, + { + "R/O longterm GUP-fast pin in MAP_PRIVATE file mapping", + test_private_ro_fast_pin, + }, +#ifdef LOCAL_CONFIG_HAVE_LIBURING + { + "io_uring fixed buffer with MAP_SHARED file mapping", + test_shared_iouring, + }, + { + "io_uring fixed buffer with MAP_PRIVATE file mapping", + test_private_iouring, + }, +#endif /* LOCAL_CONFIG_HAVE_LIBURING */ +}; + +static void run_test_case(struct test_case const *test_case) +{ + int i; + + run_with_memfd(test_case->fn, test_case->desc); + run_with_tmpfile(test_case->fn, test_case->desc); + run_with_local_tmpfile(test_case->fn, test_case->desc); + for (i = 0; i < nr_hugetlbsizes; i++) + run_with_memfd_hugetlb(test_case->fn, test_case->desc, + hugetlbsizes[i]); +} + +static int tests_per_test_case(void) +{ + return 3 + nr_hugetlbsizes; +} + +int main(int argc, char **argv) +{ + int i, err; + + pagesize = getpagesize(); + nr_hugetlbsizes = detect_hugetlb_page_sizes(hugetlbsizes, + ARRAY_SIZE(hugetlbsizes)); + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(test_cases) * tests_per_test_case()); + + gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR); + + for (i = 0; i < ARRAY_SIZE(test_cases); i++) + run_test_case(&test_cases[i]); + + err = ksft_get_fail_cnt(); + if (err) + ksft_exit_fail_msg("%d out of %d tests failed\n", + err, ksft_test_num()); + return ksft_exit_pass(); +} diff --git a/tools/testing/selftests/mm/hugepage-shm.c b/tools/testing/selftests/mm/hugepage-shm.c index e2527f32005b..478bb1e989e9 100644 --- a/tools/testing/selftests/mm/hugepage-shm.c +++ b/tools/testing/selftests/mm/hugepage-shm.c @@ -35,10 +35,6 @@ #include <sys/shm.h> #include <sys/mman.h> -#ifndef SHM_HUGETLB -#define SHM_HUGETLB 04000 -#endif - #define LENGTH (256UL*1024*1024) #define dprintf(x) printf(x) diff --git a/tools/testing/selftests/mm/hugepage-vmemmap.c b/tools/testing/selftests/mm/hugepage-vmemmap.c index 557bdbd4f87e..5b354c209e93 100644 --- a/tools/testing/selftests/mm/hugepage-vmemmap.c +++ b/tools/testing/selftests/mm/hugepage-vmemmap.c @@ -13,10 +13,6 @@ #define MAP_LENGTH (2UL * 1024 * 1024) -#ifndef MAP_HUGETLB -#define MAP_HUGETLB 0x40000 /* arch specific */ -#endif - #define PAGE_SIZE 4096 #define PAGE_COMPOUND_HEAD (1UL << 15) diff --git a/tools/testing/selftests/mm/hugetlb-madvise.c b/tools/testing/selftests/mm/hugetlb-madvise.c index 28426e30d9bc..d55322df4b73 100644 --- a/tools/testing/selftests/mm/hugetlb-madvise.c +++ b/tools/testing/selftests/mm/hugetlb-madvise.c @@ -65,11 +65,15 @@ void write_fault_pages(void *addr, unsigned long nr_pages) void read_fault_pages(void *addr, unsigned long nr_pages) { - unsigned long dummy = 0; + volatile unsigned long dummy = 0; unsigned long i; - for (i = 0; i < nr_pages; i++) + for (i = 0; i < nr_pages; i++) { dummy += *((unsigned long *)(addr + (i * huge_page_size))); + + /* Prevent the compiler from optimizing out the entire loop: */ + asm volatile("" : "+r" (dummy)); + } } int main(int argc, char **argv) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index 97adc0f34f9c..030667cb5533 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -11,6 +11,7 @@ #include <string.h> #include <unistd.h> +#include <linux/mman.h> #include <sys/mman.h> #include <sys/wait.h> #include <sys/types.h> @@ -22,16 +23,6 @@ #include "vm_util.h" -#ifndef MADV_PAGEOUT -#define MADV_PAGEOUT 21 -#endif -#ifndef MADV_POPULATE_READ -#define MADV_POPULATE_READ 22 -#endif -#ifndef MADV_COLLAPSE -#define MADV_COLLAPSE 25 -#endif - #define BASE_ADDR ((void *)(1UL << 30)) static unsigned long hpage_pmd_size; static unsigned long page_size; diff --git a/tools/testing/selftests/mm/madv_populate.c b/tools/testing/selftests/mm/madv_populate.c index 262eae6b58f2..60547245e479 100644 --- a/tools/testing/selftests/mm/madv_populate.c +++ b/tools/testing/selftests/mm/madv_populate.c @@ -20,13 +20,6 @@ #include "../kselftest.h" #include "vm_util.h" -#ifndef MADV_POPULATE_READ -#define MADV_POPULATE_READ 22 -#endif /* MADV_POPULATE_READ */ -#ifndef MADV_POPULATE_WRITE -#define MADV_POPULATE_WRITE 23 -#endif /* MADV_POPULATE_WRITE */ - /* * For now, we're using 2 MiB of private anonymous memory for all tests. */ diff --git a/tools/testing/selftests/mm/map_fixed_noreplace.c b/tools/testing/selftests/mm/map_fixed_noreplace.c index eed44322d1a6..598159f3df1f 100644 --- a/tools/testing/selftests/mm/map_fixed_noreplace.c +++ b/tools/testing/selftests/mm/map_fixed_noreplace.c @@ -13,10 +13,6 @@ #include <stdlib.h> #include <unistd.h> -#ifndef MAP_FIXED_NOREPLACE -#define MAP_FIXED_NOREPLACE 0x100000 -#endif - static void dump_maps(void) { char cmd[32]; diff --git a/tools/testing/selftests/mm/map_hugetlb.c b/tools/testing/selftests/mm/map_hugetlb.c index 312889edb84a..193281560b61 100644 --- a/tools/testing/selftests/mm/map_hugetlb.c +++ b/tools/testing/selftests/mm/map_hugetlb.c @@ -19,18 +19,6 @@ #define LENGTH (256UL*1024*1024) #define PROTECTION (PROT_READ | PROT_WRITE) -#ifndef MAP_HUGETLB -#define MAP_HUGETLB 0x40000 /* arch specific */ -#endif - -#ifndef MAP_HUGE_SHIFT -#define MAP_HUGE_SHIFT 26 -#endif - -#ifndef MAP_HUGE_MASK -#define MAP_HUGE_MASK 0x3f -#endif - /* Only ia64 requires this */ #ifdef __ia64__ #define ADDR (void *)(0x8000000000000000UL) diff --git a/tools/testing/selftests/mm/map_populate.c b/tools/testing/selftests/mm/map_populate.c index 6b8aeaa0bf7a..240f2d9dae7a 100644 --- a/tools/testing/selftests/mm/map_populate.c +++ b/tools/testing/selftests/mm/map_populate.c @@ -17,9 +17,7 @@ #include <string.h> #include <unistd.h> -#ifndef MMAP_SZ #define MMAP_SZ 4096 -#endif #define BUG_ON(condition, description) \ do { \ diff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c index 1cec8425e3ca..379581567f27 100644 --- a/tools/testing/selftests/mm/migration.c +++ b/tools/testing/selftests/mm/migration.c @@ -95,12 +95,15 @@ int migrate(uint64_t *ptr, int n1, int n2) void *access_mem(void *ptr) { - uint64_t y = 0; + volatile uint64_t y = 0; volatile uint64_t *x = ptr; while (1) { pthread_testcancel(); y += *x; + + /* Prevent the compiler from optimizing out the writes to y: */ + asm volatile("" : "+r" (y)); } return NULL; diff --git a/tools/testing/selftests/mm/mlock-random-test.c b/tools/testing/selftests/mm/mlock-random-test.c index 782ea94dee2f..1fba77df7f62 100644 --- a/tools/testing/selftests/mm/mlock-random-test.c +++ b/tools/testing/selftests/mm/mlock-random-test.c @@ -7,6 +7,7 @@ #include <sys/resource.h> #include <sys/capability.h> #include <sys/mman.h> +#include <linux/mman.h> #include <fcntl.h> #include <string.h> #include <sys/ipc.h> diff --git a/tools/testing/selftests/mm/mlock2-tests.c b/tools/testing/selftests/mm/mlock2-tests.c index 11b2301f3aa3..80cddc0de206 100644 --- a/tools/testing/selftests/mm/mlock2-tests.c +++ b/tools/testing/selftests/mm/mlock2-tests.c @@ -50,7 +50,6 @@ static int get_vm_area(unsigned long addr, struct vm_boundaries *area) printf("cannot parse /proc/self/maps\n"); goto out; } - stop = '\0'; sscanf(line, "%lx", &start); sscanf(end_addr, "%lx", &end); diff --git a/tools/testing/selftests/mm/mlock2.h b/tools/testing/selftests/mm/mlock2.h index 2a6e76c226bc..8e02991b313c 100644 --- a/tools/testing/selftests/mm/mlock2.h +++ b/tools/testing/selftests/mm/mlock2.h @@ -4,14 +4,6 @@ #include <stdio.h> #include <stdlib.h> -#ifndef MLOCK_ONFAULT -#define MLOCK_ONFAULT 1 -#endif - -#ifndef MCL_ONFAULT -#define MCL_ONFAULT (MCL_FUTURE << 1) -#endif - static int mlock2_(void *start, size_t len, int flags) { #ifdef __NR_mlock2 diff --git a/tools/testing/selftests/mm/mrelease_test.c b/tools/testing/selftests/mm/mrelease_test.c index 37b6d33b9e84..dca21042b679 100644 --- a/tools/testing/selftests/mm/mrelease_test.c +++ b/tools/testing/selftests/mm/mrelease_test.c @@ -9,18 +9,10 @@ #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> +#include <asm-generic/unistd.h> #include "vm_util.h" - #include "../kselftest.h" -#ifndef __NR_pidfd_open -#define __NR_pidfd_open -1 -#endif - -#ifndef __NR_process_mrelease -#define __NR_process_mrelease -1 -#endif - #define MB(x) (x << 20) #define MAX_SIZE_MB 1024 diff --git a/tools/testing/selftests/mm/mremap_dontunmap.c b/tools/testing/selftests/mm/mremap_dontunmap.c index f01dc4a85b0b..ca2359835e75 100644 --- a/tools/testing/selftests/mm/mremap_dontunmap.c +++ b/tools/testing/selftests/mm/mremap_dontunmap.c @@ -15,10 +15,6 @@ #include "../kselftest.h" -#ifndef MREMAP_DONTUNMAP -#define MREMAP_DONTUNMAP 4 -#endif - unsigned long page_size; char *page_buffer; diff --git a/tools/testing/selftests/mm/on-fault-limit.c b/tools/testing/selftests/mm/on-fault-limit.c index 634d87dfb2a4..b5888d613f34 100644 --- a/tools/testing/selftests/mm/on-fault-limit.c +++ b/tools/testing/selftests/mm/on-fault-limit.c @@ -6,10 +6,6 @@ #include <sys/time.h> #include <sys/resource.h> -#ifndef MCL_ONFAULT -#define MCL_ONFAULT (MCL_FUTURE << 1) -#endif - static int test_limit(void) { int ret = 1; diff --git a/tools/testing/selftests/mm/pkey-powerpc.h b/tools/testing/selftests/mm/pkey-powerpc.h index 1ebb586b2fbc..ae5df26104e5 100644 --- a/tools/testing/selftests/mm/pkey-powerpc.h +++ b/tools/testing/selftests/mm/pkey-powerpc.h @@ -3,9 +3,6 @@ #ifndef _PKEYS_POWERPC_H #define _PKEYS_POWERPC_H -#ifndef SYS_mprotect_key -# define SYS_mprotect_key 386 -#endif #ifndef SYS_pkey_alloc # define SYS_pkey_alloc 384 # define SYS_pkey_free 385 diff --git a/tools/testing/selftests/mm/pkey-x86.h b/tools/testing/selftests/mm/pkey-x86.h index 72c14cd3ddc7..814758e109c0 100644 --- a/tools/testing/selftests/mm/pkey-x86.h +++ b/tools/testing/selftests/mm/pkey-x86.h @@ -5,29 +5,11 @@ #ifdef __i386__ -#ifndef SYS_mprotect_key -# define SYS_mprotect_key 380 -#endif - -#ifndef SYS_pkey_alloc -# define SYS_pkey_alloc 381 -# define SYS_pkey_free 382 -#endif - #define REG_IP_IDX REG_EIP #define si_pkey_offset 0x14 #else -#ifndef SYS_mprotect_key -# define SYS_mprotect_key 329 -#endif - -#ifndef SYS_pkey_alloc -# define SYS_pkey_alloc 330 -# define SYS_pkey_free 331 -#endif - #define REG_IP_IDX REG_RIP #define si_pkey_offset 0x20 @@ -132,7 +114,7 @@ int pkey_reg_xstate_offset(void) unsigned int ecx; unsigned int edx; int xstate_offset; - int xstate_size; + int xstate_size = 0; unsigned long XSTATE_CPUID = 0xd; int leaf; diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c index 0381c34fdd56..48dc151f8fca 100644 --- a/tools/testing/selftests/mm/protection_keys.c +++ b/tools/testing/selftests/mm/protection_keys.c @@ -294,15 +294,6 @@ void pkey_access_deny(int pkey) pkey_disable_set(pkey, PKEY_DISABLE_ACCESS); } -/* Failed address bound checks: */ -#ifndef SEGV_BNDERR -# define SEGV_BNDERR 3 -#endif - -#ifndef SEGV_PKUERR -# define SEGV_PKUERR 4 -#endif - static char *si_code_str(int si_code) { if (si_code == SEGV_MAPERR) @@ -476,7 +467,7 @@ int sys_mprotect_pkey(void *ptr, size_t size, unsigned long orig_prot, ptr, size, orig_prot, pkey); errno = 0; - sret = syscall(SYS_mprotect_key, ptr, size, orig_prot, pkey); + sret = syscall(__NR_pkey_mprotect, ptr, size, orig_prot, pkey); if (errno) { dprintf2("SYS_mprotect_key sret: %d\n", sret); dprintf2("SYS_mprotect_key prot: 0x%lx\n", orig_prot); @@ -1684,7 +1675,7 @@ void test_mprotect_pkey_on_unsupported_cpu(int *ptr, u16 pkey) return; } - sret = syscall(SYS_mprotect_key, ptr, size, PROT_READ, pkey); + sret = syscall(__NR_pkey_mprotect, ptr, size, PROT_READ, pkey); pkey_assert(sret < 0); } diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh index 4893eb60d96d..3f26f6e15b2a 100644 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -24,7 +24,7 @@ separated by spaces: - mmap tests for mmap(2) - gup_test - tests for gup using gup_test interface + tests for gup - userfaultfd tests for userfaultfd(2) - compaction @@ -196,6 +196,8 @@ CATEGORY="gup_test" run_test ./gup_test -a # Dump pages 0, 19, and 4096, using pin_user_pages: CATEGORY="gup_test" run_test ./gup_test -ct -F 0x1 0 19 0x1000 +CATEGORY="gup_test" run_test ./gup_longterm + CATEGORY="userfaultfd" run_test ./uffd-unit-tests uffd_stress_bin=./uffd-stress CATEGORY="userfaultfd" run_test ${uffd_stress_bin} anon 20 16 @@ -242,18 +244,18 @@ if [ $VADDR64 -ne 0 ]; then if [ "$ARCH" == "$ARCH_ARM64" ]; then echo 6 > /proc/sys/vm/nr_hugepages fi - CATEGORY="hugevm" run_test ./va_high_addr_switch.sh + CATEGORY="hugevm" run_test bash ./va_high_addr_switch.sh if [ "$ARCH" == "$ARCH_ARM64" ]; then echo $prev_nr_hugepages > /proc/sys/vm/nr_hugepages fi fi # VADDR64 # vmalloc stability smoke test -CATEGORY="vmalloc" run_test ./test_vmalloc.sh smoke +CATEGORY="vmalloc" run_test bash ./test_vmalloc.sh smoke CATEGORY="mremap" run_test ./mremap_dontunmap -CATEGORY="hmm" run_test ./test_hmm.sh smoke +CATEGORY="hmm" run_test bash ./test_hmm.sh smoke # MADV_POPULATE_READ and MADV_POPULATE_WRITE tests CATEGORY="madv_populate" run_test ./madv_populate diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c index 61c6250adf93..ba20d7504022 100644 --- a/tools/testing/selftests/mm/uffd-common.c +++ b/tools/testing/selftests/mm/uffd-common.c @@ -616,3 +616,62 @@ int copy_page(int ufd, unsigned long offset, bool wp) { return __copy_page(ufd, offset, false, wp); } + +int uffd_open_dev(unsigned int flags) +{ + int fd, uffd; + + fd = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC); + if (fd < 0) + return fd; + uffd = ioctl(fd, USERFAULTFD_IOC_NEW, flags); + close(fd); + + return uffd; +} + +int uffd_open_sys(unsigned int flags) +{ +#ifdef __NR_userfaultfd + return syscall(__NR_userfaultfd, flags); +#else + return -1; +#endif +} + +int uffd_open(unsigned int flags) +{ + int uffd = uffd_open_sys(flags); + + if (uffd < 0) + uffd = uffd_open_dev(flags); + + return uffd; +} + +int uffd_get_features(uint64_t *features) +{ + struct uffdio_api uffdio_api = { .api = UFFD_API, .features = 0 }; + /* + * This should by default work in most kernels; the feature list + * will be the same no matter what we pass in here. + */ + int fd = uffd_open(UFFD_USER_MODE_ONLY); + + if (fd < 0) + /* Maybe the kernel is older than user-only mode? */ + fd = uffd_open(0); + + if (fd < 0) + return fd; + + if (ioctl(fd, UFFDIO_API, &uffdio_api)) { + close(fd); + return -errno; + } + + *features = uffdio_api.features; + close(fd); + + return 0; +} diff --git a/tools/testing/selftests/mm/uffd-common.h b/tools/testing/selftests/mm/uffd-common.h index 6068f2346b86..197f5262fe0d 100644 --- a/tools/testing/selftests/mm/uffd-common.h +++ b/tools/testing/selftests/mm/uffd-common.h @@ -110,6 +110,11 @@ int __copy_page(int ufd, unsigned long offset, bool retry, bool wp); int copy_page(int ufd, unsigned long offset, bool wp); void *uffd_poll_thread(void *arg); +int uffd_open_dev(unsigned int flags); +int uffd_open_sys(unsigned int flags); +int uffd_open(unsigned int flags); +int uffd_get_features(uint64_t *features); + #define TEST_ANON 1 #define TEST_HUGETLB 2 #define TEST_SHMEM 3 diff --git a/tools/testing/selftests/mm/uffd-stress.c b/tools/testing/selftests/mm/uffd-stress.c index f1ad9eef1c3a..995ff13e74c7 100644 --- a/tools/testing/selftests/mm/uffd-stress.c +++ b/tools/testing/selftests/mm/uffd-stress.c @@ -88,16 +88,6 @@ static void uffd_stats_reset(struct uffd_args *args, unsigned long n_cpus) } } -static inline uint64_t uffd_minor_feature(void) -{ - if (test_type == TEST_HUGETLB && map_shared) - return UFFD_FEATURE_MINOR_HUGETLBFS; - else if (test_type == TEST_SHMEM) - return UFFD_FEATURE_MINOR_SHMEM; - else - return 0; -} - static void *locking_thread(void *arg) { unsigned long cpu = (unsigned long) arg; diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c index 269c86768a02..04d91f144d1c 100644 --- a/tools/testing/selftests/mm/uffd-unit-tests.c +++ b/tools/testing/selftests/mm/uffd-unit-tests.c @@ -109,12 +109,11 @@ static void uffd_test_pass(void) ksft_inc_fail_cnt(); \ } while (0) -#define uffd_test_skip(...) do { \ - printf("skipped [reason: "); \ - printf(__VA_ARGS__); \ - printf("]\n"); \ - ksft_inc_xskip_cnt(); \ - } while (0) +static void uffd_test_skip(const char *message) +{ + printf("skipped [reason: %s]\n", message); + ksft_inc_xskip_cnt(); +} /* * Returns 1 if specific userfaultfd supported, 0 otherwise. Note, we'll @@ -1149,7 +1148,6 @@ int main(int argc, char *argv[]) uffd_test_case_t *test; mem_type_t *mem_type; uffd_test_args_t args; - char test_name[128]; const char *errmsg; int has_uffd, opt; int i, j; @@ -1192,10 +1190,8 @@ int main(int argc, char *argv[]) mem_type = &mem_types[j]; if (!(test->mem_targets & mem_type->mem_flag)) continue; - snprintf(test_name, sizeof(test_name), - "%s on %s", test->name, mem_type->name); - uffd_test_start(test_name); + uffd_test_start("%s on %s", test->name, mem_type->name); if (!uffd_feature_supported(test)) { uffd_test_skip("feature missing"); continue; diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index 9b06a5034808..558c9cd8901c 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <string.h> #include <fcntl.h> +#include <dirent.h> #include <sys/ioctl.h> #include <linux/userfaultfd.h> #include <sys/syscall.h> @@ -198,6 +199,32 @@ unsigned long default_huge_page_size(void) return hps; } +int detect_hugetlb_page_sizes(size_t sizes[], int max) +{ + DIR *dir = opendir("/sys/kernel/mm/hugepages/"); + int count = 0; + + if (!dir) + return 0; + + while (count < max) { + struct dirent *entry = readdir(dir); + size_t kb; + + if (!entry) + break; + if (entry->d_type != DT_DIR) + continue; + if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1) + continue; + sizes[count++] = kb * 1024; + ksft_print_msg("[INFO] detected hugetlb page size: %zu KiB\n", + kb); + } + closedir(dir); + return count; +} + /* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */ int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len, bool miss, bool wp, bool minor, uint64_t *ioctls) @@ -242,62 +269,3 @@ int uffd_unregister(int uffd, void *addr, uint64_t len) return ret; } - -int uffd_open_dev(unsigned int flags) -{ - int fd, uffd; - - fd = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC); - if (fd < 0) - return fd; - uffd = ioctl(fd, USERFAULTFD_IOC_NEW, flags); - close(fd); - - return uffd; -} - -int uffd_open_sys(unsigned int flags) -{ -#ifdef __NR_userfaultfd - return syscall(__NR_userfaultfd, flags); -#else - return -1; -#endif -} - -int uffd_open(unsigned int flags) -{ - int uffd = uffd_open_sys(flags); - - if (uffd < 0) - uffd = uffd_open_dev(flags); - - return uffd; -} - -int uffd_get_features(uint64_t *features) -{ - struct uffdio_api uffdio_api = { .api = UFFD_API, .features = 0 }; - /* - * This should by default work in most kernels; the feature list - * will be the same no matter what we pass in here. - */ - int fd = uffd_open(UFFD_USER_MODE_ONLY); - - if (fd < 0) - /* Maybe the kernel is older than user-only mode? */ - fd = uffd_open(0); - - if (fd < 0) - return fd; - - if (ioctl(fd, UFFDIO_API, &uffdio_api)) { - close(fd); - return -errno; - } - - *features = uffdio_api.features; - close(fd); - - return 0; -} diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h index b950bd16083a..c7fa61f0dff8 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -44,14 +44,11 @@ bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size); bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size); int64_t allocate_transhuge(void *ptr, int pagemap_fd); unsigned long default_huge_page_size(void); +int detect_hugetlb_page_sizes(size_t sizes[], int max); int uffd_register(int uffd, void *addr, uint64_t len, bool miss, bool wp, bool minor); int uffd_unregister(int uffd, void *addr, uint64_t len); -int uffd_open_dev(unsigned int flags); -int uffd_open_sys(unsigned int flags); -int uffd_open(unsigned int flags); -int uffd_get_features(uint64_t *features); int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len, bool miss, bool wp, bool minor, uint64_t *ioctls); diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index 80f06aa62034..501854a89cc0 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -8,8 +8,10 @@ diag_uid fin_ack_lat gro hwtstamp_config +io_uring_zerocopy_tx ioam6_parser ip_defrag +ip_local_port_range ipsec ipv6_flowlabel ipv6_flowlabel_mgr @@ -26,6 +28,8 @@ reuseport_bpf_cpu reuseport_bpf_numa reuseport_dualstack rxtimestamp +sctp_hello +scm_pidfd sk_bind_sendto_listen sk_connect_zero_addr socket diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index c12df57d5539..7f3ab2a93ed6 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -84,6 +84,7 @@ TEST_GEN_FILES += ip_local_port_range TEST_GEN_FILES += bind_wildcard TEST_PROGS += test_vxlan_mdb.sh TEST_PROGS += test_bridge_neigh_suppress.sh +TEST_PROGS += test_vxlan_nolocalbypass.sh TEST_FILES := settings diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index 1e4b397cece6..221c387a7d7f 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -1,3 +1,4 @@ -TEST_GEN_PROGS := diag_uid test_unix_oob unix_connect +CFLAGS += $(KHDR_INCLUDES) +TEST_GEN_PROGS := diag_uid test_unix_oob unix_connect scm_pidfd include ../../lib.mk diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c new file mode 100644 index 000000000000..a86222143d79 --- /dev/null +++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +#define _GNU_SOURCE +#include <error.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <linux/socket.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/un.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "../../kselftest_harness.h" + +#define clean_errno() (errno == 0 ? "None" : strerror(errno)) +#define log_err(MSG, ...) \ + fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", __FILE__, __LINE__, \ + clean_errno(), ##__VA_ARGS__) + +#ifndef SCM_PIDFD +#define SCM_PIDFD 0x04 +#endif + +static void child_die() +{ + exit(1); +} + +static int safe_int(const char *numstr, int *converted) +{ + char *err = NULL; + long sli; + + errno = 0; + sli = strtol(numstr, &err, 0); + if (errno == ERANGE && (sli == LONG_MAX || sli == LONG_MIN)) + return -ERANGE; + + if (errno != 0 && sli == 0) + return -EINVAL; + + if (err == numstr || *err != '\0') + return -EINVAL; + + if (sli > INT_MAX || sli < INT_MIN) + return -ERANGE; + + *converted = (int)sli; + return 0; +} + +static int char_left_gc(const char *buffer, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (buffer[i] == ' ' || buffer[i] == '\t') + continue; + + return i; + } + + return 0; +} + +static int char_right_gc(const char *buffer, size_t len) +{ + int i; + + for (i = len - 1; i >= 0; i--) { + if (buffer[i] == ' ' || buffer[i] == '\t' || + buffer[i] == '\n' || buffer[i] == '\0') + continue; + + return i + 1; + } + + return 0; +} + +static char *trim_whitespace_in_place(char *buffer) +{ + buffer += char_left_gc(buffer, strlen(buffer)); + buffer[char_right_gc(buffer, strlen(buffer))] = '\0'; + return buffer; +} + +/* borrowed (with all helpers) from pidfd/pidfd_open_test.c */ +static pid_t get_pid_from_fdinfo_file(int pidfd, const char *key, size_t keylen) +{ + int ret; + char path[512]; + FILE *f; + size_t n = 0; + pid_t result = -1; + char *line = NULL; + + snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd); + + f = fopen(path, "re"); + if (!f) + return -1; + + while (getline(&line, &n, f) != -1) { + char *numstr; + + if (strncmp(line, key, keylen)) + continue; + + numstr = trim_whitespace_in_place(line + 4); + ret = safe_int(numstr, &result); + if (ret < 0) + goto out; + + break; + } + +out: + free(line); + fclose(f); + return result; +} + +static int cmsg_check(int fd) +{ + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + struct iovec iov; + struct ucred *ucred = NULL; + int data = 0; + char control[CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(sizeof(int))] = { 0 }; + int *pidfd = NULL; + pid_t parent_pid; + int err; + + iov.iov_base = &data; + iov.iov_len = sizeof(data); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + err = recvmsg(fd, &msg, 0); + if (err < 0) { + log_err("recvmsg"); + return 1; + } + + if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) { + log_err("recvmsg: truncated"); + return 1; + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_PIDFD) { + if (cmsg->cmsg_len < sizeof(*pidfd)) { + log_err("CMSG parse: SCM_PIDFD wrong len"); + return 1; + } + + pidfd = (void *)CMSG_DATA(cmsg); + } + + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS) { + if (cmsg->cmsg_len < sizeof(*ucred)) { + log_err("CMSG parse: SCM_CREDENTIALS wrong len"); + return 1; + } + + ucred = (void *)CMSG_DATA(cmsg); + } + } + + /* send(pfd, "x", sizeof(char), 0) */ + if (data != 'x') { + log_err("recvmsg: data corruption"); + return 1; + } + + if (!pidfd) { + log_err("CMSG parse: SCM_PIDFD not found"); + return 1; + } + + if (!ucred) { + log_err("CMSG parse: SCM_CREDENTIALS not found"); + return 1; + } + + /* pidfd from SCM_PIDFD should point to the parent process PID */ + parent_pid = + get_pid_from_fdinfo_file(*pidfd, "Pid:", sizeof("Pid:") - 1); + if (parent_pid != getppid()) { + log_err("wrong SCM_PIDFD %d != %d", parent_pid, getppid()); + return 1; + } + + return 0; +} + +struct sock_addr { + char sock_name[32]; + struct sockaddr_un listen_addr; + socklen_t addrlen; +}; + +FIXTURE(scm_pidfd) +{ + int server; + pid_t client_pid; + int startup_pipe[2]; + struct sock_addr server_addr; + struct sock_addr *client_addr; +}; + +FIXTURE_VARIANT(scm_pidfd) +{ + int type; + bool abstract; +}; + +FIXTURE_VARIANT_ADD(scm_pidfd, stream_pathname) +{ + .type = SOCK_STREAM, + .abstract = 0, +}; + +FIXTURE_VARIANT_ADD(scm_pidfd, stream_abstract) +{ + .type = SOCK_STREAM, + .abstract = 1, +}; + +FIXTURE_VARIANT_ADD(scm_pidfd, dgram_pathname) +{ + .type = SOCK_DGRAM, + .abstract = 0, +}; + +FIXTURE_VARIANT_ADD(scm_pidfd, dgram_abstract) +{ + .type = SOCK_DGRAM, + .abstract = 1, +}; + +FIXTURE_SETUP(scm_pidfd) +{ + self->client_addr = mmap(NULL, sizeof(*self->client_addr), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, self->client_addr); +} + +FIXTURE_TEARDOWN(scm_pidfd) +{ + close(self->server); + + kill(self->client_pid, SIGKILL); + waitpid(self->client_pid, NULL, 0); + + if (!variant->abstract) { + unlink(self->server_addr.sock_name); + unlink(self->client_addr->sock_name); + } +} + +static void fill_sockaddr(struct sock_addr *addr, bool abstract) +{ + char *sun_path_buf = (char *)&addr->listen_addr.sun_path; + + addr->listen_addr.sun_family = AF_UNIX; + addr->addrlen = offsetof(struct sockaddr_un, sun_path); + snprintf(addr->sock_name, sizeof(addr->sock_name), "scm_pidfd_%d", getpid()); + addr->addrlen += strlen(addr->sock_name); + if (abstract) { + *sun_path_buf = '\0'; + addr->addrlen++; + sun_path_buf++; + } else { + unlink(addr->sock_name); + } + memcpy(sun_path_buf, addr->sock_name, strlen(addr->sock_name)); +} + +static void client(FIXTURE_DATA(scm_pidfd) *self, + const FIXTURE_VARIANT(scm_pidfd) *variant) +{ + int err; + int cfd; + socklen_t len; + struct ucred peer_cred; + int peer_pidfd; + pid_t peer_pid; + int on = 0; + + cfd = socket(AF_UNIX, variant->type, 0); + if (cfd < 0) { + log_err("socket"); + child_die(); + } + + if (variant->type == SOCK_DGRAM) { + fill_sockaddr(self->client_addr, variant->abstract); + + if (bind(cfd, (struct sockaddr *)&self->client_addr->listen_addr, self->client_addr->addrlen)) { + log_err("bind"); + child_die(); + } + } + + if (connect(cfd, (struct sockaddr *)&self->server_addr.listen_addr, + self->server_addr.addrlen) != 0) { + log_err("connect"); + child_die(); + } + + on = 1; + if (setsockopt(cfd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) { + log_err("Failed to set SO_PASSCRED"); + child_die(); + } + + if (setsockopt(cfd, SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) { + log_err("Failed to set SO_PASSPIDFD"); + child_die(); + } + + close(self->startup_pipe[1]); + + if (cmsg_check(cfd)) { + log_err("cmsg_check failed"); + child_die(); + } + + /* skip further for SOCK_DGRAM as it's not applicable */ + if (variant->type == SOCK_DGRAM) + return; + + len = sizeof(peer_cred); + if (getsockopt(cfd, SOL_SOCKET, SO_PEERCRED, &peer_cred, &len)) { + log_err("Failed to get SO_PEERCRED"); + child_die(); + } + + len = sizeof(peer_pidfd); + if (getsockopt(cfd, SOL_SOCKET, SO_PEERPIDFD, &peer_pidfd, &len)) { + log_err("Failed to get SO_PEERPIDFD"); + child_die(); + } + + /* pid from SO_PEERCRED should point to the parent process PID */ + if (peer_cred.pid != getppid()) { + log_err("peer_cred.pid != getppid(): %d != %d", peer_cred.pid, getppid()); + child_die(); + } + + peer_pid = get_pid_from_fdinfo_file(peer_pidfd, + "Pid:", sizeof("Pid:") - 1); + if (peer_pid != peer_cred.pid) { + log_err("peer_pid != peer_cred.pid: %d != %d", peer_pid, peer_cred.pid); + child_die(); + } +} + +TEST_F(scm_pidfd, test) +{ + int err; + int pfd; + int child_status = 0; + + self->server = socket(AF_UNIX, variant->type, 0); + ASSERT_NE(-1, self->server); + + fill_sockaddr(&self->server_addr, variant->abstract); + + err = bind(self->server, (struct sockaddr *)&self->server_addr.listen_addr, self->server_addr.addrlen); + ASSERT_EQ(0, err); + + if (variant->type == SOCK_STREAM) { + err = listen(self->server, 1); + ASSERT_EQ(0, err); + } + + err = pipe(self->startup_pipe); + ASSERT_NE(-1, err); + + self->client_pid = fork(); + ASSERT_NE(-1, self->client_pid); + if (self->client_pid == 0) { + close(self->server); + close(self->startup_pipe[0]); + client(self, variant); + exit(0); + } + close(self->startup_pipe[1]); + + if (variant->type == SOCK_STREAM) { + pfd = accept(self->server, NULL, NULL); + ASSERT_NE(-1, pfd); + } else { + pfd = self->server; + } + + /* wait until the child arrives at checkpoint */ + read(self->startup_pipe[0], &err, sizeof(int)); + close(self->startup_pipe[0]); + + if (variant->type == SOCK_DGRAM) { + err = sendto(pfd, "x", sizeof(char), 0, (struct sockaddr *)&self->client_addr->listen_addr, self->client_addr->addrlen); + ASSERT_NE(-1, err); + } else { + err = send(pfd, "x", sizeof(char), 0); + ASSERT_NE(-1, err); + } + + close(pfd); + waitpid(self->client_pid, &child_status, 0); + ASSERT_EQ(0, WIFEXITED(child_status) ? WEXITSTATUS(child_status) : 1); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index d1d421ec10a3..cd3cc52c59b4 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -50,3 +50,4 @@ CONFIG_CRYPTO_SM4_GENERIC=y CONFIG_AMT=m CONFIG_VXLAN=m CONFIG_IP_SCTP=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m diff --git a/tools/testing/selftests/net/fcnal-test.sh b/tools/testing/selftests/net/fcnal-test.sh index 21ca91473c09..d32a14ba069a 100755 --- a/tools/testing/selftests/net/fcnal-test.sh +++ b/tools/testing/selftests/net/fcnal-test.sh @@ -92,6 +92,13 @@ NSC_CMD="ip netns exec ${NSC}" which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping) +# Check if FIPS mode is enabled +if [ -f /proc/sys/crypto/fips_enabled ]; then + fips_enabled=`cat /proc/sys/crypto/fips_enabled` +else + fips_enabled=0 +fi + ################################################################################ # utilities @@ -585,6 +592,20 @@ ipv4_ping_novrf() done # + # out, but don't use gateway if peer is not on link + # + a=${NSB_IP} + log_start + run_cmd ping -c 1 -w 1 -r ${a} + log_test_addr ${a} $? 0 "ping out (don't route), peer on link" + + a=${NSB_LO_IP} + log_start + show_hint "Fails since peer is not on link" + run_cmd ping -c 1 -w 1 -r ${a} + log_test_addr ${a} $? 1 "ping out (don't route), peer not on link" + + # # in # for a in ${NSA_IP} ${NSA_LO_IP} @@ -1098,6 +1119,59 @@ test_ipv4_md5_vrf__global_server__bind_ifindex0() set_sysctl net.ipv4.tcp_l3mdev_accept="$old_tcp_l3mdev_accept" } +ipv4_tcp_dontroute() +{ + local syncookies=$1 + local nsa_syncookies + local nsb_syncookies + local a + + # + # Link local connection tests (SO_DONTROUTE). + # Connections should succeed only when the remote IP address is + # on link (doesn't need to be routed through a gateway). + # + + nsa_syncookies=$(ip netns exec "${NSA}" sysctl -n net.ipv4.tcp_syncookies) + nsb_syncookies=$(ip netns exec "${NSB}" sysctl -n net.ipv4.tcp_syncookies) + ip netns exec "${NSA}" sysctl -wq net.ipv4.tcp_syncookies=${syncookies} + ip netns exec "${NSB}" sysctl -wq net.ipv4.tcp_syncookies=${syncookies} + + # Test with eth1 address (on link). + + a=${NSB_IP} + log_start + do_run_cmd nettest -B -N "${NSA}" -O "${NSB}" -r ${a} --client-dontroute + log_test_addr ${a} $? 0 "SO_DONTROUTE client, syncookies=${syncookies}" + + a=${NSB_IP} + log_start + do_run_cmd nettest -B -N "${NSA}" -O "${NSB}" -r ${a} --server-dontroute + log_test_addr ${a} $? 0 "SO_DONTROUTE server, syncookies=${syncookies}" + + # Test with loopback address (routed). + # + # The client would use the eth1 address as source IP by default. + # Therefore, we need to use the -c option here, to force the use of the + # routed (loopback) address as source IP (so that the server will try + # to respond to a routed address and not a link local one). + + a=${NSB_LO_IP} + log_start + show_hint "Should fail 'Network is unreachable' since server is not on link" + do_run_cmd nettest -B -N "${NSA}" -O "${NSB}" -c "${NSA_LO_IP}" -r ${a} --client-dontroute + log_test_addr ${a} $? 1 "SO_DONTROUTE client, syncookies=${syncookies}" + + a=${NSB_LO_IP} + log_start + show_hint "Should timeout since server cannot respond (client is not on link)" + do_run_cmd nettest -B -N "${NSA}" -O "${NSB}" -c "${NSA_LO_IP}" -r ${a} --server-dontroute + log_test_addr ${a} $? 2 "SO_DONTROUTE server, syncookies=${syncookies}" + + ip netns exec "${NSB}" sysctl -wq net.ipv4.tcp_syncookies=${nsb_syncookies} + ip netns exec "${NSA}" sysctl -wq net.ipv4.tcp_syncookies=${nsa_syncookies} +} + ipv4_tcp_novrf() { local a @@ -1216,7 +1290,10 @@ ipv4_tcp_novrf() run_cmd nettest -d ${NSA_DEV} -r ${a} log_test_addr ${a} $? 1 "No server, device client, local conn" - ipv4_tcp_md5_novrf + [ "$fips_enabled" = "1" ] || ipv4_tcp_md5_novrf + + ipv4_tcp_dontroute 0 + ipv4_tcp_dontroute 2 } ipv4_tcp_vrf() @@ -1270,9 +1347,11 @@ ipv4_tcp_vrf() log_test_addr ${a} $? 1 "Global server, local connection" # run MD5 tests - setup_vrf_dup - ipv4_tcp_md5 - cleanup_vrf_dup + if [ "$fips_enabled" = "0" ]; then + setup_vrf_dup + ipv4_tcp_md5 + cleanup_vrf_dup + fi # # enable VRF global server @@ -1585,6 +1664,23 @@ ipv4_udp_novrf() log_start run_cmd nettest -D -d ${NSA_DEV} -r ${a} log_test_addr ${a} $? 2 "No server, device client, local conn" + + # + # Link local connection tests (SO_DONTROUTE). + # Connections should succeed only when the remote IP address is + # on link (doesn't need to be routed through a gateway). + # + + a=${NSB_IP} + log_start + do_run_cmd nettest -B -D -N "${NSA}" -O "${NSB}" -r ${a} --client-dontroute + log_test_addr ${a} $? 0 "SO_DONTROUTE client" + + a=${NSB_LO_IP} + log_start + show_hint "Should fail 'Network is unreachable' since server is not on link" + do_run_cmd nettest -B -D -N "${NSA}" -O "${NSB}" -r ${a} --client-dontroute + log_test_addr ${a} $? 1 "SO_DONTROUTE client" } ipv4_udp_vrf() @@ -2772,7 +2868,7 @@ ipv6_tcp_novrf() log_test_addr ${a} $? 1 "No server, device client, local conn" done - ipv6_tcp_md5_novrf + [ "$fips_enabled" = "1" ] || ipv6_tcp_md5_novrf } ipv6_tcp_vrf() @@ -2842,9 +2938,11 @@ ipv6_tcp_vrf() log_test_addr ${a} $? 1 "Global server, local connection" # run MD5 tests - setup_vrf_dup - ipv6_tcp_md5 - cleanup_vrf_dup + if [ "$fips_enabled" = "0" ]; then + setup_vrf_dup + ipv6_tcp_md5 + cleanup_vrf_dup + fi # # enable VRF global server diff --git a/tools/testing/selftests/net/forwarding/Makefile b/tools/testing/selftests/net/forwarding/Makefile index a474c60fe348..770efbe24f0d 100644 --- a/tools/testing/selftests/net/forwarding/Makefile +++ b/tools/testing/selftests/net/forwarding/Makefile @@ -83,6 +83,8 @@ TEST_PROGS = bridge_igmp.sh \ tc_chains.sh \ tc_flower_router.sh \ tc_flower.sh \ + tc_flower_l2_miss.sh \ + tc_flower_cfm.sh \ tc_mpls_l2vpn.sh \ tc_police.sh \ tc_shblocks.sh \ diff --git a/tools/testing/selftests/net/forwarding/dual_vxlan_bridge.sh b/tools/testing/selftests/net/forwarding/dual_vxlan_bridge.sh index 5148d97a5df8..68ee92df3e07 100755 --- a/tools/testing/selftests/net/forwarding/dual_vxlan_bridge.sh +++ b/tools/testing/selftests/net/forwarding/dual_vxlan_bridge.sh @@ -132,6 +132,7 @@ switch_create() #### BR1 #### ip link add name br1 type bridge vlan_filtering 1 \ vlan_protocol 802.1ad vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br1 addrgenmode none # Make sure the bridge uses the MAC address of the local port and not # that of the VxLAN's device. ip link set dev br1 address $(mac_get $swp1) diff --git a/tools/testing/selftests/net/forwarding/hw_stats_l3.sh b/tools/testing/selftests/net/forwarding/hw_stats_l3.sh index 432fe8469851..48584a51388f 100755 --- a/tools/testing/selftests/net/forwarding/hw_stats_l3.sh +++ b/tools/testing/selftests/net/forwarding/hw_stats_l3.sh @@ -84,8 +84,9 @@ h2_destroy() router_rp1_200_create() { - ip link add name $rp1.200 up \ - link $rp1 addrgenmode eui64 type vlan id 200 + ip link add name $rp1.200 link $rp1 type vlan id 200 + ip link set dev $rp1.200 addrgenmode eui64 + ip link set dev $rp1.200 up ip address add dev $rp1.200 192.0.2.2/28 ip address add dev $rp1.200 2001:db8:1::2/64 ip stats set dev $rp1.200 l3_stats on @@ -256,9 +257,11 @@ reapply_config() router_rp1_200_destroy - ip link add name $rp1.200 link $rp1 addrgenmode none type vlan id 200 + ip link add name $rp1.200 link $rp1 type vlan id 200 + ip link set dev $rp1.200 addrgenmode none ip stats set dev $rp1.200 l3_stats on - ip link set dev $rp1.200 up addrgenmode eui64 + ip link set dev $rp1.200 addrgenmode eui64 + ip link set dev $rp1.200 up ip address add dev $rp1.200 192.0.2.2/28 ip address add dev $rp1.200 2001:db8:1::2/64 } diff --git a/tools/testing/selftests/net/forwarding/mirror_gre_bound.sh b/tools/testing/selftests/net/forwarding/mirror_gre_bound.sh index 360ca133bead..6c257ec03756 100755 --- a/tools/testing/selftests/net/forwarding/mirror_gre_bound.sh +++ b/tools/testing/selftests/net/forwarding/mirror_gre_bound.sh @@ -98,6 +98,7 @@ switch_create() # Bridge between H1 and H2. ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none ip link set dev br1 up ip link set dev $swp1 master br1 diff --git a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d.sh b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d.sh index c5095da7f6bf..04fd14b0a9b7 100755 --- a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d.sh +++ b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d.sh @@ -65,7 +65,8 @@ setup_prepare() vrf_prepare mirror_gre_topo_create - ip link add name br2 type bridge vlan_filtering 0 + ip link add name br2 address $(mac_get $swp3) \ + type bridge vlan_filtering 0 ip link set dev br2 up ip link set dev $swp3 master br2 @@ -93,12 +94,16 @@ cleanup() test_gretap() { + ip neigh replace 192.0.2.130 lladdr $(mac_get $h3) \ + nud permanent dev br2 full_test_span_gre_dir gt4 ingress 8 0 "mirror to gretap" full_test_span_gre_dir gt4 egress 0 8 "mirror to gretap" } test_ip6gretap() { + ip neigh replace 2001:db8:2::2 lladdr $(mac_get $h3) \ + nud permanent dev br2 full_test_span_gre_dir gt6 ingress 8 0 "mirror to ip6gretap" full_test_span_gre_dir gt6 egress 0 8 "mirror to ip6gretap" } diff --git a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d_vlan.sh b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d_vlan.sh index 1b27f2b0f196..f35313c76fac 100755 --- a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d_vlan.sh +++ b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1d_vlan.sh @@ -35,7 +35,8 @@ setup_prepare() vrf_prepare mirror_gre_topo_create - ip link add name br2 type bridge vlan_filtering 0 + ip link add name br2 address $(mac_get $swp3) \ + type bridge vlan_filtering 0 ip link set dev br2 up vlan_create $swp3 555 diff --git a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q.sh b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q.sh index 9ff22f28032d..0cf4c47a46f9 100755 --- a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q.sh +++ b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q.sh @@ -90,12 +90,16 @@ cleanup() test_gretap() { + ip neigh replace 192.0.2.130 lladdr $(mac_get $h3) \ + nud permanent dev br1 full_test_span_gre_dir gt4 ingress 8 0 "mirror to gretap" full_test_span_gre_dir gt4 egress 0 8 "mirror to gretap" } test_ip6gretap() { + ip neigh replace 2001:db8:2::2 lladdr $(mac_get $h3) \ + nud permanent dev br1 full_test_span_gre_dir gt6 ingress 8 0 "mirror to ip6gretap" full_test_span_gre_dir gt6 egress 0 8 "mirror to ip6gretap" } diff --git a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q_lag.sh b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q_lag.sh index 91e431cd919e..c53148b1dc63 100755 --- a/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q_lag.sh +++ b/tools/testing/selftests/net/forwarding/mirror_gre_bridge_1q_lag.sh @@ -140,7 +140,8 @@ switch_create() ip link set dev $swp3 up ip link set dev $swp4 up - ip link add name br1 type bridge vlan_filtering 1 + ip link add name br1 address $(mac_get $swp3) \ + type bridge vlan_filtering 1 team_create lag loadbalance $swp3 $swp4 ip link set dev lag master br1 diff --git a/tools/testing/selftests/net/forwarding/mirror_topo_lib.sh b/tools/testing/selftests/net/forwarding/mirror_topo_lib.sh index 04979e5962e7..bb1adbb7b98a 100644 --- a/tools/testing/selftests/net/forwarding/mirror_topo_lib.sh +++ b/tools/testing/selftests/net/forwarding/mirror_topo_lib.sh @@ -60,6 +60,7 @@ mirror_topo_switch_create() ip link set dev $swp3 up ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none ip link set dev br1 up ip link set dev $swp1 master br1 diff --git a/tools/testing/selftests/net/forwarding/pedit_dsfield.sh b/tools/testing/selftests/net/forwarding/pedit_dsfield.sh index 64fbd211d907..af008fbf2725 100755 --- a/tools/testing/selftests/net/forwarding/pedit_dsfield.sh +++ b/tools/testing/selftests/net/forwarding/pedit_dsfield.sh @@ -60,7 +60,9 @@ h2_destroy() switch_create() { - ip link add name br1 up type bridge vlan_filtering 1 + ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none + ip link set dev br1 up ip link set dev $swp1 master br1 ip link set dev $swp1 up ip link set dev $swp2 master br1 diff --git a/tools/testing/selftests/net/forwarding/q_in_vni.sh b/tools/testing/selftests/net/forwarding/q_in_vni.sh index 4c50c0234bce..798b13525c02 100755 --- a/tools/testing/selftests/net/forwarding/q_in_vni.sh +++ b/tools/testing/selftests/net/forwarding/q_in_vni.sh @@ -137,6 +137,7 @@ switch_create() { ip link add name br1 type bridge vlan_filtering 1 vlan_protocol 802.1ad \ vlan_default_pvid 0 mcast_snooping 0 + ip link set dev br1 addrgenmode none # Make sure the bridge uses the MAC address of the local port and not # that of the VxLAN's device. ip link set dev br1 address $(mac_get $swp1) diff --git a/tools/testing/selftests/net/forwarding/router_bridge.sh b/tools/testing/selftests/net/forwarding/router_bridge.sh index ebc596a272f7..8ce0aed54ece 100755 --- a/tools/testing/selftests/net/forwarding/router_bridge.sh +++ b/tools/testing/selftests/net/forwarding/router_bridge.sh @@ -38,7 +38,8 @@ h2_destroy() router_create() { - ip link add name br1 type bridge vlan_filtering 1 + ip link add name br1 address $(mac_get $swp1) \ + type bridge vlan_filtering 1 ip link set dev br1 up ip link set dev $swp1 master br1 diff --git a/tools/testing/selftests/net/forwarding/router_bridge_vlan.sh b/tools/testing/selftests/net/forwarding/router_bridge_vlan.sh index fa6a88c50750..de2b2d5480dd 100755 --- a/tools/testing/selftests/net/forwarding/router_bridge_vlan.sh +++ b/tools/testing/selftests/net/forwarding/router_bridge_vlan.sh @@ -1,6 +1,28 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +# +------------------------+ +----------------------+ +# | H1 (vrf) | | H2 (vrf) | +# | + $h1.555 | | + $h2 | +# | | 192.0.2.1/28 | | | 192.0.2.130/28 | +# | | 2001:db8:1::1/64 | | | 2001:db8:2::2/64 | +# | | | | | | +# | + $h1 | | | | +# +----|-------------------+ +--|-------------------+ +# | | +# +----|--------------------------------------------------|-------------------+ +# | SW | | | +# | +--|-------------------------------+ + $swp2 | +# | | + $swp1 | 192.0.2.129/28 | +# | | vid 555 | 2001:db8:2::1/64 | +# | | | | +# | | + BR1 (802.1q) | | +# | | vid 555 pvid untagged | | +# | | 192.0.2.2/28 | | +# | | 2001:db8:1::2/64 | | +# | +----------------------------------+ | +# +---------------------------------------------------------------------------+ + ALL_TESTS=" ping_ipv4 ping_ipv6 @@ -41,7 +63,7 @@ h2_destroy() router_create() { - ip link add name br1 type bridge vlan_filtering 1 + ip link add name br1 type bridge vlan_filtering 1 vlan_default_pvid 0 ip link set dev br1 up ip link set dev $swp1 master br1 diff --git a/tools/testing/selftests/net/forwarding/skbedit_priority.sh b/tools/testing/selftests/net/forwarding/skbedit_priority.sh index bde11dc27873..3dd5fcbd3eaa 100755 --- a/tools/testing/selftests/net/forwarding/skbedit_priority.sh +++ b/tools/testing/selftests/net/forwarding/skbedit_priority.sh @@ -54,7 +54,9 @@ h2_destroy() switch_create() { - ip link add name br1 up type bridge vlan_filtering 1 + ip link add name br1 type bridge vlan_filtering 1 + ip link set dev br1 addrgenmode none + ip link set dev br1 up ip link set dev $swp1 master br1 ip link set dev $swp1 up ip link set dev $swp2 master br1 diff --git a/tools/testing/selftests/net/forwarding/tc_flower_cfm.sh b/tools/testing/selftests/net/forwarding/tc_flower_cfm.sh new file mode 100755 index 000000000000..3ca20df952eb --- /dev/null +++ b/tools/testing/selftests/net/forwarding/tc_flower_cfm.sh @@ -0,0 +1,206 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +ALL_TESTS="match_cfm_opcode match_cfm_level match_cfm_level_and_opcode" +NUM_NETIFS=2 +source tc_common.sh +source lib.sh + +h1_create() +{ + simple_if_init $h1 +} + +h1_destroy() +{ + simple_if_fini $h1 +} + +h2_create() +{ + simple_if_init $h2 + tc qdisc add dev $h2 clsact +} + +h2_destroy() +{ + tc qdisc del dev $h2 clsact + simple_if_fini $h2 +} + +u8_to_hex() +{ + local u8=$1; shift + + printf "%02x" $u8 +} + +generate_cfm_hdr() +{ + local mdl=$1; shift + local op=$1; shift + local flags=$1; shift + local tlv_offset=$1; shift + + local cfm_hdr=$(: + )"$(u8_to_hex $((mdl << 5))):"$( : MD level and Version + )"$(u8_to_hex $op):"$( : OpCode + )"$(u8_to_hex $flags):"$( : Flags + )"$(u8_to_hex $tlv_offset)"$( : TLV offset + ) + + echo $cfm_hdr +} + +match_cfm_opcode() +{ + local ethtype="89 02"; readonly ethtype + RET=0 + + tc filter add dev $h2 ingress protocol cfm pref 1 handle 101 \ + flower cfm op 47 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 102 \ + flower cfm op 43 action drop + + pkt="$ethtype $(generate_cfm_hdr 7 47 0 32)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 6 5 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Did not match on correct opcode" + + tc_check_packets "dev $h2 ingress" 102 0 + check_err $? "Matched on the wrong opcode" + + pkt="$ethtype $(generate_cfm_hdr 0 43 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Matched on the wrong opcode" + + tc_check_packets "dev $h2 ingress" 102 1 + check_err $? "Did not match on correct opcode" + + tc filter del dev $h2 ingress protocol cfm pref 1 handle 101 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 102 flower + + log_test "CFM opcode match test" +} + +match_cfm_level() +{ + local ethtype="89 02"; readonly ethtype + RET=0 + + tc filter add dev $h2 ingress protocol cfm pref 1 handle 101 \ + flower cfm mdl 5 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 102 \ + flower cfm mdl 3 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 103 \ + flower cfm mdl 0 action drop + + pkt="$ethtype $(generate_cfm_hdr 5 42 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 6 1 0 70)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 0 1 0 70)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Did not match on correct level" + + tc_check_packets "dev $h2 ingress" 102 0 + check_err $? "Matched on the wrong level" + + tc_check_packets "dev $h2 ingress" 103 1 + check_err $? "Did not match on correct level" + + pkt="$ethtype $(generate_cfm_hdr 3 0 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Matched on the wrong level" + + tc_check_packets "dev $h2 ingress" 102 1 + check_err $? "Did not match on correct level" + + tc_check_packets "dev $h2 ingress" 103 1 + check_err $? "Matched on the wrong level" + + tc filter del dev $h2 ingress protocol cfm pref 1 handle 101 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 102 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 103 flower + + log_test "CFM level match test" +} + +match_cfm_level_and_opcode() +{ + local ethtype="89 02"; readonly ethtype + RET=0 + + tc filter add dev $h2 ingress protocol cfm pref 1 handle 101 \ + flower cfm mdl 5 op 41 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 102 \ + flower cfm mdl 7 op 42 action drop + + pkt="$ethtype $(generate_cfm_hdr 5 41 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 7 3 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 3 42 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Did not match on correct level and opcode" + + tc_check_packets "dev $h2 ingress" 102 0 + check_err $? "Matched on the wrong level and opcode" + + pkt="$ethtype $(generate_cfm_hdr 7 42 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Matched on the wrong level and opcode" + + tc_check_packets "dev $h2 ingress" 102 1 + check_err $? "Did not match on correct level and opcode" + + tc filter del dev $h2 ingress protocol cfm pref 1 handle 101 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 102 flower + + log_test "CFM opcode and level match test" +} + +setup_prepare() +{ + h1=${NETIFS[p1]} + h2=${NETIFS[p2]} + h1mac=$(mac_get $h1) + h2mac=$(mac_get $h2) + + vrf_prepare + + h1_create + h2_create +} + +cleanup() +{ + pre_cleanup + + h2_destroy + h1_destroy + + vrf_cleanup +} + +trap cleanup EXIT + +setup_prepare +setup_wait + +tests_run + +exit $EXIT_STATUS diff --git a/tools/testing/selftests/net/forwarding/tc_flower_l2_miss.sh b/tools/testing/selftests/net/forwarding/tc_flower_l2_miss.sh new file mode 100755 index 000000000000..e22c2d28b6eb --- /dev/null +++ b/tools/testing/selftests/net/forwarding/tc_flower_l2_miss.sh @@ -0,0 +1,350 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# +-----------------------+ +----------------------+ +# | H1 (vrf) | | H2 (vrf) | +# | + $h1 | | $h2 + | +# | | 192.0.2.1/28 | | 192.0.2.2/28 | | +# | | 2001:db8:1::1/64 | | 2001:db8:1::2/64 | | +# +----|------------------+ +------------------|---+ +# | | +# +----|-------------------------------------------------------------------|---+ +# | SW | | | +# | +-|-------------------------------------------------------------------|-+ | +# | | + $swp1 BR $swp2 + | | +# | +-----------------------------------------------------------------------+ | +# +----------------------------------------------------------------------------+ + +ALL_TESTS=" + test_l2_miss_unicast + test_l2_miss_multicast + test_l2_miss_ll_multicast + test_l2_miss_broadcast +" + +NUM_NETIFS=4 +source lib.sh +source tc_common.sh + +h1_create() +{ + simple_if_init $h1 192.0.2.1/28 2001:db8:1::1/64 +} + +h1_destroy() +{ + simple_if_fini $h1 192.0.2.1/28 2001:db8:1::1/64 +} + +h2_create() +{ + simple_if_init $h2 192.0.2.2/28 2001:db8:1::2/64 +} + +h2_destroy() +{ + simple_if_fini $h2 192.0.2.2/28 2001:db8:1::2/64 +} + +switch_create() +{ + ip link add name br1 up type bridge + ip link set dev $swp1 master br1 + ip link set dev $swp1 up + ip link set dev $swp2 master br1 + ip link set dev $swp2 up + + tc qdisc add dev $swp2 clsact +} + +switch_destroy() +{ + tc qdisc del dev $swp2 clsact + + ip link set dev $swp2 down + ip link set dev $swp2 nomaster + ip link set dev $swp1 down + ip link set dev $swp1 nomaster + ip link del dev br1 +} + +test_l2_miss_unicast() +{ + local dmac=00:01:02:03:04:05 + local dip=192.0.2.2 + local sip=192.0.2.1 + + RET=0 + + # Unknown unicast. + tc filter add dev $swp2 egress protocol ipv4 handle 101 pref 1 \ + flower indev $swp1 l2_miss 1 dst_mac $dmac src_ip $sip \ + dst_ip $dip action pass + # Known unicast. + tc filter add dev $swp2 egress protocol ipv4 handle 102 pref 1 \ + flower indev $swp1 l2_miss 0 dst_mac $dmac src_ip $sip \ + dst_ip $dip action pass + + # Before adding FDB entry. + $MZ $h1 -a own -b $dmac -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 1 + check_err $? "Unknown unicast filter was not hit before adding FDB entry" + + tc_check_packets "dev $swp2 egress" 102 0 + check_err $? "Known unicast filter was hit before adding FDB entry" + + # Adding FDB entry. + bridge fdb replace $dmac dev $swp2 master static + + $MZ $h1 -a own -b $dmac -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 1 + check_err $? "Unknown unicast filter was hit after adding FDB entry" + + tc_check_packets "dev $swp2 egress" 102 1 + check_err $? "Known unicast filter was not hit after adding FDB entry" + + # Deleting FDB entry. + bridge fdb del $dmac dev $swp2 master static + + $MZ $h1 -a own -b $dmac -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 2 + check_err $? "Unknown unicast filter was not hit after deleting FDB entry" + + tc_check_packets "dev $swp2 egress" 102 1 + check_err $? "Known unicast filter was hit after deleting FDB entry" + + tc filter del dev $swp2 egress protocol ipv4 pref 1 handle 102 flower + tc filter del dev $swp2 egress protocol ipv4 pref 1 handle 101 flower + + log_test "L2 miss - Unicast" +} + +test_l2_miss_multicast_common() +{ + local proto=$1; shift + local sip=$1; shift + local dip=$1; shift + local mode=$1; shift + local name=$1; shift + + RET=0 + + # Unregistered multicast. + tc filter add dev $swp2 egress protocol $proto handle 101 pref 1 \ + flower indev $swp1 l2_miss 1 src_ip $sip dst_ip $dip \ + action pass + # Registered multicast. + tc filter add dev $swp2 egress protocol $proto handle 102 pref 1 \ + flower indev $swp1 l2_miss 0 src_ip $sip dst_ip $dip \ + action pass + + # Before adding MDB entry. + $MZ $mode $h1 -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 1 + check_err $? "Unregistered multicast filter was not hit before adding MDB entry" + + tc_check_packets "dev $swp2 egress" 102 0 + check_err $? "Registered multicast filter was hit before adding MDB entry" + + # Adding MDB entry. + bridge mdb replace dev br1 port $swp2 grp $dip permanent + + $MZ $mode $h1 -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 1 + check_err $? "Unregistered multicast filter was hit after adding MDB entry" + + tc_check_packets "dev $swp2 egress" 102 1 + check_err $? "Registered multicast filter was not hit after adding MDB entry" + + # Deleting MDB entry. + bridge mdb del dev br1 port $swp2 grp $dip + + $MZ $mode $h1 -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 2 + check_err $? "Unregistered multicast filter was not hit after deleting MDB entry" + + tc_check_packets "dev $swp2 egress" 102 1 + check_err $? "Registered multicast filter was hit after deleting MDB entry" + + tc filter del dev $swp2 egress protocol $proto pref 1 handle 102 flower + tc filter del dev $swp2 egress protocol $proto pref 1 handle 101 flower + + log_test "L2 miss - Multicast ($name)" +} + +test_l2_miss_multicast_ipv4() +{ + local proto="ipv4" + local sip=192.0.2.1 + local dip=239.1.1.1 + local mode="-4" + local name="IPv4" + + test_l2_miss_multicast_common $proto $sip $dip $mode $name +} + +test_l2_miss_multicast_ipv6() +{ + local proto="ipv6" + local sip=2001:db8:1::1 + local dip=ff0e::1 + local mode="-6" + local name="IPv6" + + test_l2_miss_multicast_common $proto $sip $dip $mode $name +} + +test_l2_miss_multicast() +{ + # Configure $swp2 as a multicast router port so that it will forward + # both registered and unregistered multicast traffic. + bridge link set dev $swp2 mcast_router 2 + + # Forwarding according to MDB entries only takes place when the bridge + # detects that there is a valid querier in the network. Set the bridge + # as the querier and assign it a valid IPv6 link-local address to be + # used as the source address for MLD queries. + ip link set dev br1 type bridge mcast_querier 1 + ip -6 address add fe80::1/64 nodad dev br1 + # Wait the default Query Response Interval (10 seconds) for the bridge + # to determine that there are no other queriers in the network. + sleep 10 + + test_l2_miss_multicast_ipv4 + test_l2_miss_multicast_ipv6 + + ip -6 address del fe80::1/64 dev br1 + ip link set dev br1 type bridge mcast_querier 0 + bridge link set dev $swp2 mcast_router 1 +} + +test_l2_miss_multicast_common2() +{ + local name=$1; shift + local dmac=$1; shift + local dip=224.0.0.1 + local sip=192.0.2.1 + +} + +test_l2_miss_ll_multicast_common() +{ + local proto=$1; shift + local dmac=$1; shift + local sip=$1; shift + local dip=$1; shift + local mode=$1; shift + local name=$1; shift + + RET=0 + + tc filter add dev $swp2 egress protocol $proto handle 101 pref 1 \ + flower indev $swp1 l2_miss 1 dst_mac $dmac src_ip $sip \ + dst_ip $dip action pass + + $MZ $mode $h1 -a own -b $dmac -t ip -A $sip -B $dip -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 1 + check_err $? "Filter was not hit" + + tc filter del dev $swp2 egress protocol $proto pref 1 handle 101 flower + + log_test "L2 miss - Link-local multicast ($name)" +} + +test_l2_miss_ll_multicast_ipv4() +{ + local proto=ipv4 + local dmac=01:00:5e:00:00:01 + local sip=192.0.2.1 + local dip=224.0.0.1 + local mode="-4" + local name="IPv4" + + test_l2_miss_ll_multicast_common $proto $dmac $sip $dip $mode $name +} + +test_l2_miss_ll_multicast_ipv6() +{ + local proto=ipv6 + local dmac=33:33:00:00:00:01 + local sip=2001:db8:1::1 + local dip=ff02::1 + local mode="-6" + local name="IPv6" + + test_l2_miss_ll_multicast_common $proto $dmac $sip $dip $mode $name +} + +test_l2_miss_ll_multicast() +{ + test_l2_miss_ll_multicast_ipv4 + test_l2_miss_ll_multicast_ipv6 +} + +test_l2_miss_broadcast() +{ + local dmac=ff:ff:ff:ff:ff:ff + local smac=00:01:02:03:04:05 + + RET=0 + + tc filter add dev $swp2 egress protocol all handle 101 pref 1 \ + flower l2_miss 1 dst_mac $dmac src_mac $smac \ + action pass + tc filter add dev $swp2 egress protocol all handle 102 pref 1 \ + flower l2_miss 0 dst_mac $dmac src_mac $smac \ + action pass + + $MZ $h1 -a $smac -b $dmac -c 1 -p 100 -q + + tc_check_packets "dev $swp2 egress" 101 0 + check_err $? "L2 miss filter was hit when should not" + + tc_check_packets "dev $swp2 egress" 102 1 + check_err $? "L2 no miss filter was not hit when should" + + tc filter del dev $swp2 egress protocol all pref 1 handle 102 flower + tc filter del dev $swp2 egress protocol all pref 1 handle 101 flower + + log_test "L2 miss - Broadcast" +} + +setup_prepare() +{ + h1=${NETIFS[p1]} + swp1=${NETIFS[p2]} + + swp2=${NETIFS[p3]} + h2=${NETIFS[p4]} + + vrf_prepare + h1_create + h2_create + switch_create +} + +cleanup() +{ + pre_cleanup + + switch_destroy + h2_destroy + h1_destroy + vrf_cleanup +} + +trap cleanup EXIT + +setup_prepare +setup_wait + +tests_run + +exit $EXIT_STATUS diff --git a/tools/testing/selftests/net/mptcp/Makefile b/tools/testing/selftests/net/mptcp/Makefile index 43a723626126..7b936a926859 100644 --- a/tools/testing/selftests/net/mptcp/Makefile +++ b/tools/testing/selftests/net/mptcp/Makefile @@ -9,7 +9,7 @@ TEST_PROGS := mptcp_connect.sh pm_netlink.sh mptcp_join.sh diag.sh \ TEST_GEN_FILES = mptcp_connect pm_nl_ctl mptcp_sockopt mptcp_inq -TEST_FILES := settings +TEST_FILES := mptcp_lib.sh settings EXTRA_CLEAN := *.pcap diff --git a/tools/testing/selftests/net/mptcp/config b/tools/testing/selftests/net/mptcp/config index 38021a0dd527..e317c2e44dae 100644 --- a/tools/testing/selftests/net/mptcp/config +++ b/tools/testing/selftests/net/mptcp/config @@ -1,3 +1,4 @@ +CONFIG_KALLSYMS=y CONFIG_MPTCP=y CONFIG_IPV6=y CONFIG_MPTCP_IPV6=y @@ -5,6 +6,7 @@ CONFIG_INET_DIAG=m CONFIG_INET_MPTCP_DIAG=m CONFIG_VETH=y CONFIG_NET_SCH_NETEM=m +CONFIG_SYN_COOKIES=y CONFIG_NETFILTER=y CONFIG_NETFILTER_ADVANCED=y CONFIG_NETFILTER_NETLINK=m diff --git a/tools/testing/selftests/net/mptcp/diag.sh b/tools/testing/selftests/net/mptcp/diag.sh index ef628b16fe9b..fa9e09ad97d9 100755 --- a/tools/testing/selftests/net/mptcp/diag.sh +++ b/tools/testing/selftests/net/mptcp/diag.sh @@ -1,6 +1,8 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +. "$(dirname "${0}")/mptcp_lib.sh" + sec=$(date +%s) rndh=$(printf %x $sec)-$(mktemp -u XXXXXX) ns="ns1-$rndh" @@ -31,6 +33,8 @@ cleanup() ip netns del $ns } +mptcp_lib_check_mptcp + ip -Version > /dev/null 2>&1 if [ $? -ne 0 ];then echo "SKIP: Could not run test without ip tool" @@ -51,16 +55,20 @@ __chk_nr() { local command="$1" local expected=$2 - local msg nr + local msg="$3" + local skip="${4:-SKIP}" + local nr - shift 2 - msg=$* nr=$(eval $command) printf "%-50s" "$msg" if [ $nr != $expected ]; then - echo "[ fail ] expected $expected found $nr" - ret=$test_cnt + if [ $nr = "$skip" ] && ! mptcp_lib_expect_all_features; then + echo "[ skip ] Feature probably not supported" + else + echo "[ fail ] expected $expected found $nr" + ret=$test_cnt + fi else echo "[ ok ]" fi @@ -72,12 +80,12 @@ __chk_msk_nr() local condition=$1 shift 1 - __chk_nr "ss -inmHMN $ns | $condition" $* + __chk_nr "ss -inmHMN $ns | $condition" "$@" } chk_msk_nr() { - __chk_msk_nr "grep -c token:" $* + __chk_msk_nr "grep -c token:" "$@" } wait_msk_nr() @@ -115,37 +123,26 @@ wait_msk_nr() chk_msk_fallback_nr() { - __chk_msk_nr "grep -c fallback" $* + __chk_msk_nr "grep -c fallback" "$@" } chk_msk_remote_key_nr() { - __chk_msk_nr "grep -c remote_key" $* + __chk_msk_nr "grep -c remote_key" "$@" } __chk_listen() { local filter="$1" local expected=$2 + local msg="$3" - shift 2 - msg=$* - - nr=$(ss -N $ns -Ml "$filter" | grep -c LISTEN) - printf "%-50s" "$msg" - - if [ $nr != $expected ]; then - echo "[ fail ] expected $expected found $nr" - ret=$test_cnt - else - echo "[ ok ]" - fi + __chk_nr "ss -N $ns -Ml '$filter' | grep -c LISTEN" "$expected" "$msg" 0 } chk_msk_listen() { lport=$1 - local msg="check for listen socket" # destination port search should always return empty list __chk_listen "dport $lport" 0 "listen match for dport $lport" @@ -163,10 +160,9 @@ chk_msk_listen() chk_msk_inuse() { local expected=$1 + local msg="$2" local listen_nr - shift 1 - listen_nr=$(ss -N "${ns}" -Ml | grep -c LISTEN) expected=$((expected + listen_nr)) @@ -177,7 +173,7 @@ chk_msk_inuse() sleep 0.1 done - __chk_nr get_msk_inuse $expected $* + __chk_nr get_msk_inuse $expected "$msg" 0 } # $1: ns, $2: port diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.sh b/tools/testing/selftests/net/mptcp/mptcp_connect.sh index a43d3e2f59bb..bbae40882bfa 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.sh @@ -1,6 +1,8 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +. "$(dirname "${0}")/mptcp_lib.sh" + time_start=$(date +%s) optstring="S:R:d:e:l:r:h4cm:f:tC" @@ -141,6 +143,9 @@ cleanup() done } +mptcp_lib_check_mptcp +mptcp_lib_check_kallsyms + ip -Version > /dev/null 2>&1 if [ $? -ne 0 ];then echo "SKIP: Could not run test without ip tool" @@ -691,6 +696,15 @@ run_test_transparent() return 0 fi + # IP(V6)_TRANSPARENT has been added after TOS support which came with + # the required infrastructure in MPTCP sockopt code. To support TOS, the + # following function has been exported (T). Not great but better than + # checking for a specific kernel version. + if ! mptcp_lib_kallsyms_has "T __ip_sock_set_tos$"; then + echo "INFO: ${msg} not supported by the kernel: SKIP" + return + fi + ip netns exec "$listener_ns" nft -f /dev/stdin <<"EOF" flush ruleset table inet mangle { @@ -704,6 +718,7 @@ table inet mangle { EOF if [ $? -ne 0 ]; then echo "SKIP: $msg, could not load nft ruleset" + mptcp_lib_fail_if_expected_feature "nft rules" return fi @@ -719,6 +734,7 @@ EOF if [ $? -ne 0 ]; then ip netns exec "$listener_ns" nft flush ruleset echo "SKIP: $msg, ip $r6flag rule failed" + mptcp_lib_fail_if_expected_feature "ip rule" return fi @@ -727,6 +743,7 @@ EOF ip netns exec "$listener_ns" nft flush ruleset ip -net "$listener_ns" $r6flag rule del fwmark 1 lookup 100 echo "SKIP: $msg, ip route add local $local_addr failed" + mptcp_lib_fail_if_expected_feature "ip route" return fi @@ -763,6 +780,11 @@ run_tests_peekmode() run_tests_mptfo() { + if ! mptcp_lib_kallsyms_has "mptcp_fastopen_"; then + echo "INFO: TFO not supported by the kernel: SKIP" + return + fi + echo "INFO: with MPTFO start" ip netns exec "$ns1" sysctl -q net.ipv4.tcp_fastopen=2 ip netns exec "$ns2" sysctl -q net.ipv4.tcp_fastopen=1 @@ -783,9 +805,14 @@ run_tests_disconnect() local old_cin=$cin local old_sin=$sin + if ! mptcp_lib_kallsyms_has "mptcp_pm_data_reset$"; then + echo "INFO: Full disconnect not supported: SKIP" + return + fi + cat $cin $cin $cin > "$cin".disconnect - # force do_transfer to cope with the multiple tranmissions + # force do_transfer to cope with the multiple transmissions sin="$cin.disconnect" cin="$cin.disconnect" cin_disconnect="$old_cin" diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index 26310c17b4c6..e6c9d5451c5b 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -10,6 +10,8 @@ # because it's invoked by variable name, see how the "tests" array is used #shellcheck disable=SC2317 +. "$(dirname "${0}")/mptcp_lib.sh" + ret=0 sin="" sinfail="" @@ -17,11 +19,14 @@ sout="" cin="" cinfail="" cinsent="" +tmpfile="" cout="" capout="" ns1="" ns2="" ksft_skip=4 +iptables="iptables" +ip6tables="ip6tables" timeout_poll=30 timeout_test=$((timeout_poll * 2 + 1)) capture=0 @@ -34,6 +39,7 @@ evts_ns1="" evts_ns2="" evts_ns1_pid=0 evts_ns2_pid=0 +stats_dumped=0 declare -A all_tests declare -a only_tests_ids @@ -44,6 +50,10 @@ TEST_NAME="" nr_blank=40 export FAILING_LINKS="" +export test_linkfail=0 +export addr_nr_ns1=0 +export addr_nr_ns2=0 +export sflags="" # generated using "nfbpf_compile '(ip && (ip[54] & 0xf0) == 0x30) || # (ip6 && (ip6[74] & 0xf0) == 0x30)'" @@ -79,7 +89,7 @@ init_partial() ip netns add $netns || exit $ksft_skip ip -net $netns link set lo up ip netns exec $netns sysctl -q net.mptcp.enabled=1 - ip netns exec $netns sysctl -q net.mptcp.pm_type=0 + ip netns exec $netns sysctl -q net.mptcp.pm_type=0 2>/dev/null || true ip netns exec $netns sysctl -q net.ipv4.conf.all.rp_filter=0 ip netns exec $netns sysctl -q net.ipv4.conf.default.rp_filter=0 if [ $checksum -eq 1 ]; then @@ -87,6 +97,7 @@ init_partial() fi done + stats_dumped=0 check_invert=0 validate_checksum=$checksum FAILING_LINKS="" @@ -136,12 +147,19 @@ cleanup_partial() check_tools() { + mptcp_lib_check_mptcp + mptcp_lib_check_kallsyms + if ! ip -Version &> /dev/null; then echo "SKIP: Could not run test without ip tool" exit $ksft_skip fi - if ! iptables -V &> /dev/null; then + # Use the legacy version if available to support old kernel versions + if iptables-legacy -V &> /dev/null; then + iptables="iptables-legacy" + ip6tables="ip6tables-legacy" + elif ! iptables -V &> /dev/null; then echo "SKIP: Could not run all tests without iptables tool" exit $ksft_skip fi @@ -175,10 +193,37 @@ cleanup() { rm -f "$cin" "$cout" "$sinfail" rm -f "$sin" "$sout" "$cinsent" "$cinfail" + rm -f "$tmpfile" rm -rf $evts_ns1 $evts_ns2 cleanup_partial } +# $1: msg +print_title() +{ + printf "%03u %-36s %s" "${TEST_COUNT}" "${TEST_NAME}" "${1}" +} + +# [ $1: fail msg ] +mark_as_skipped() +{ + local msg="${1:-"Feature not supported"}" + + mptcp_lib_fail_if_expected_feature "${msg}" + + print_title "[ skip ] ${msg}" + printf "\n" +} + +# $@: condition +continue_if() +{ + if ! "${@}"; then + mark_as_skipped + return 1 + fi +} + skip_test() { if [ "${#only_tests_ids[@]}" -eq 0 ] && [ "${#only_tests_names[@]}" -eq 0 ]; then @@ -222,6 +267,19 @@ reset() return 0 } +# $1: test name ; $2: counter to check +reset_check_counter() +{ + reset "${1}" || return 1 + + local counter="${2}" + + if ! nstat -asz "${counter}" | grep -wq "${counter}"; then + mark_as_skipped "counter '${counter}' is not available" + return 1 + fi +} + # $1: test name reset_with_cookies() { @@ -241,17 +299,21 @@ reset_with_add_addr_timeout() reset "${1}" || return 1 - tables="iptables" + tables="${iptables}" if [ $ip -eq 6 ]; then - tables="ip6tables" + tables="${ip6tables}" fi ip netns exec $ns1 sysctl -q net.mptcp.add_addr_timeout=1 - ip netns exec $ns2 $tables -A OUTPUT -p tcp \ - -m tcp --tcp-option 30 \ - -m bpf --bytecode \ - "$CBPF_MPTCP_SUBOPTION_ADD_ADDR" \ - -j DROP + + if ! ip netns exec $ns2 $tables -A OUTPUT -p tcp \ + -m tcp --tcp-option 30 \ + -m bpf --bytecode \ + "$CBPF_MPTCP_SUBOPTION_ADD_ADDR" \ + -j DROP; then + mark_as_skipped "unable to set the 'add addr' rule" + return 1 + fi } # $1: test name @@ -295,22 +357,17 @@ reset_with_allow_join_id0() # tc action pedit offset 162 out of bounds # # Netfilter is used to mark packets with enough data. -reset_with_fail() +setup_fail_rules() { - reset "${1}" || return 1 - - ip netns exec $ns1 sysctl -q net.mptcp.checksum_enabled=1 - ip netns exec $ns2 sysctl -q net.mptcp.checksum_enabled=1 - check_invert=1 validate_checksum=1 - local i="$2" - local ip="${3:-4}" + local i="$1" + local ip="${2:-4}" local tables - tables="iptables" + tables="${iptables}" if [ $ip -eq 6 ]; then - tables="ip6tables" + tables="${ip6tables}" fi ip netns exec $ns2 $tables \ @@ -320,15 +377,32 @@ reset_with_fail() -p tcp \ -m length --length 150:9999 \ -m statistic --mode nth --packet 1 --every 99999 \ - -j MARK --set-mark 42 || exit 1 + -j MARK --set-mark 42 || return ${ksft_skip} - tc -n $ns2 qdisc add dev ns2eth$i clsact || exit 1 + tc -n $ns2 qdisc add dev ns2eth$i clsact || return ${ksft_skip} tc -n $ns2 filter add dev ns2eth$i egress \ protocol ip prio 1000 \ handle 42 fw \ action pedit munge offset 148 u8 invert \ pipe csum tcp \ - index 100 || exit 1 + index 100 || return ${ksft_skip} +} + +reset_with_fail() +{ + reset_check_counter "${1}" "MPTcpExtInfiniteMapTx" || return 1 + shift + + ip netns exec $ns1 sysctl -q net.mptcp.checksum_enabled=1 + ip netns exec $ns2 sysctl -q net.mptcp.checksum_enabled=1 + + local rc=0 + setup_fail_rules "${@}" || rc=$? + + if [ ${rc} -eq ${ksft_skip} ]; then + mark_as_skipped "unable to set the 'fail' rules" + return 1 + fi } reset_with_events() @@ -343,10 +417,32 @@ reset_with_events() evts_ns2_pid=$! } +reset_with_tcp_filter() +{ + reset "${1}" || return 1 + shift + + local ns="${!1}" + local src="${2}" + local target="${3}" + + if ! ip netns exec "${ns}" ${iptables} \ + -A INPUT \ + -s "${src}" \ + -p tcp \ + -j "${target}"; then + mark_as_skipped "unable to set the filter rules" + return 1 + fi +} + fail_test() { ret=1 failed_tests[${TEST_COUNT}]="${TEST_NAME}" + + [ "${stats_dumped}" = 0 ] && dump_stats + stats_dumped=1 } get_failed_tests_ids() @@ -383,9 +479,16 @@ check_transfer() fail_test return 1 fi - bytes="--bytes=${bytes}" + + # note: BusyBox's "cmp" command doesn't support --bytes + tmpfile=$(mktemp) + head --bytes="$bytes" "$in" > "$tmpfile" + mv "$tmpfile" "$in" + head --bytes="$bytes" "$out" > "$tmpfile" + mv "$tmpfile" "$out" + tmpfile="" fi - cmp -l "$in" "$out" ${bytes} | while read -r i a b; do + cmp -l "$in" "$out" | while read -r i a b; do local sum=$((0${a} + 0${b})) if [ $check_invert -eq 0 ] || [ $sum -ne $((0xff)) ]; then echo "[ FAIL ] $what does not match (in, out):" @@ -454,11 +557,25 @@ wait_local_port_listen() done } -rm_addr_count() +# $1: ns ; $2: counter +get_counter() { - local ns=${1} + local ns="${1}" + local counter="${2}" + local count - ip netns exec ${ns} nstat -as | grep MPTcpExtRmAddr | awk '{print $2}' + count=$(ip netns exec ${ns} nstat -asz "${counter}" | awk 'NR==1 {next} {print $2}') + if [ -z "${count}" ]; then + mptcp_lib_fail_if_expected_feature "${counter} counter" + return 1 + fi + + echo "${count}" +} + +rm_addr_count() +{ + get_counter "${1}" "MPTcpExtRmAddr" } # $1: ns, $2: old rm_addr counter in $ns @@ -476,16 +593,36 @@ wait_rm_addr() done } +rm_sf_count() +{ + get_counter "${1}" "MPTcpExtRmSubflow" +} + +# $1: ns, $2: old rm_sf counter in $ns +wait_rm_sf() +{ + local ns="${1}" + local old_cnt="${2}" + local cnt + + local i + for i in $(seq 10); do + cnt=$(rm_sf_count ${ns}) + [ "$cnt" = "${old_cnt}" ] || break + sleep 0.1 + done +} + wait_mpj() { local ns="${1}" local cnt old_cnt - old_cnt=$(ip netns exec ${ns} nstat -as | grep MPJoinAckRx | awk '{print $2}') + old_cnt=$(get_counter ${ns} "MPTcpExtMPJoinAckRx") local i for i in $(seq 10); do - cnt=$(ip netns exec ${ns} nstat -as | grep MPJoinAckRx | awk '{print $2}') + cnt=$(get_counter ${ns} "MPTcpExtMPJoinAckRx") [ "$cnt" = "${old_cnt}" ] || break sleep 0.1 done @@ -685,144 +822,11 @@ pm_nl_check_endpoint() fi } -filter_tcp_from() -{ - local ns="${1}" - local src="${2}" - local target="${3}" - - ip netns exec "${ns}" iptables -A INPUT -s "${src}" -p tcp -j "${target}" -} - -do_transfer() +pm_nl_set_endpoint() { local listener_ns="$1" local connector_ns="$2" - local cl_proto="$3" - local srv_proto="$4" - local connect_addr="$5" - local test_link_fail="$6" - local addr_nr_ns1="$7" - local addr_nr_ns2="$8" - local speed="$9" - local sflags="${10}" - - local port=$((10000 + TEST_COUNT - 1)) - local cappid - local userspace_pm=0 - - :> "$cout" - :> "$sout" - :> "$capout" - - if [ $capture -eq 1 ]; then - local capuser - if [ -z $SUDO_USER ] ; then - capuser="" - else - capuser="-Z $SUDO_USER" - fi - - capfile=$(printf "mp_join-%02u-%s.pcap" "$TEST_COUNT" "${listener_ns}") - - echo "Capturing traffic for test $TEST_COUNT into $capfile" - ip netns exec ${listener_ns} tcpdump -i any -s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 & - cappid=$! - - sleep 1 - fi - - NSTAT_HISTORY=/tmp/${listener_ns}.nstat ip netns exec ${listener_ns} \ - nstat -n - NSTAT_HISTORY=/tmp/${connector_ns}.nstat ip netns exec ${connector_ns} \ - nstat -n - - local extra_args - if [ $speed = "fast" ]; then - extra_args="-j" - elif [ $speed = "slow" ]; then - extra_args="-r 50" - elif [[ $speed = "speed_"* ]]; then - extra_args="-r ${speed:6}" - fi - - if [[ "${addr_nr_ns1}" = "userspace_"* ]]; then - userspace_pm=1 - addr_nr_ns1=${addr_nr_ns1:10} - fi - - local flags="subflow" - local extra_cl_args="" - local extra_srv_args="" - local trunc_size="" - if [[ "${addr_nr_ns2}" = "fastclose_"* ]]; then - if [ ${test_link_fail} -le 1 ]; then - echo "fastclose tests need test_link_fail argument" - fail_test - return 1 - fi - - # disconnect - trunc_size=${test_link_fail} - local side=${addr_nr_ns2:10} - - if [ ${side} = "client" ]; then - extra_cl_args="-f ${test_link_fail}" - extra_srv_args="-f -1" - elif [ ${side} = "server" ]; then - extra_srv_args="-f ${test_link_fail}" - extra_cl_args="-f -1" - else - echo "wrong/unknown fastclose spec ${side}" - fail_test - return 1 - fi - addr_nr_ns2=0 - elif [[ "${addr_nr_ns2}" = "userspace_"* ]]; then - userspace_pm=1 - addr_nr_ns2=${addr_nr_ns2:10} - elif [[ "${addr_nr_ns2}" = "fullmesh_"* ]]; then - flags="${flags},fullmesh" - addr_nr_ns2=${addr_nr_ns2:9} - fi - - extra_srv_args="$extra_args $extra_srv_args" - if [ "$test_link_fail" -gt 1 ];then - timeout ${timeout_test} \ - ip netns exec ${listener_ns} \ - ./mptcp_connect -t ${timeout_poll} -l -p $port -s ${srv_proto} \ - $extra_srv_args "::" < "$sinfail" > "$sout" & - else - timeout ${timeout_test} \ - ip netns exec ${listener_ns} \ - ./mptcp_connect -t ${timeout_poll} -l -p $port -s ${srv_proto} \ - $extra_srv_args "::" < "$sin" > "$sout" & - fi - local spid=$! - - wait_local_port_listen "${listener_ns}" "${port}" - - extra_cl_args="$extra_args $extra_cl_args" - if [ "$test_link_fail" -eq 0 ];then - timeout ${timeout_test} \ - ip netns exec ${connector_ns} \ - ./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \ - $extra_cl_args $connect_addr < "$cin" > "$cout" & - elif [ "$test_link_fail" -eq 1 ] || [ "$test_link_fail" -eq 2 ];then - ( cat "$cinfail" ; sleep 2; link_failure $listener_ns ; cat "$cinfail" ) | \ - tee "$cinsent" | \ - timeout ${timeout_test} \ - ip netns exec ${connector_ns} \ - ./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \ - $extra_cl_args $connect_addr > "$cout" & - else - tee "$cinsent" < "$cinfail" | \ - timeout ${timeout_test} \ - ip netns exec ${connector_ns} \ - ./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \ - $extra_cl_args $connect_addr > "$cout" & - fi - local cpid=$! + local connect_addr="$3" # let the mptcp subflow be established in background before # do endpoint manipulation @@ -834,7 +838,6 @@ do_transfer() local counter=2 local add_nr_ns1=${addr_nr_ns1} local id=10 - local tk while [ $add_nr_ns1 -gt 0 ]; do local addr if is_v6 "${connect_addr}"; then @@ -842,16 +845,7 @@ do_transfer() else addr="10.0.$counter.1" fi - if [ $userspace_pm -eq 0 ]; then - pm_nl_add_endpoint $ns1 $addr flags signal - else - tk=$(grep "type:1," "$evts_ns1" | - sed -n 's/.*\(token:\)\([[:digit:]]*\).*$/\2/p;q') - ip netns exec ${listener_ns} ./pm_nl_ctl ann $addr token $tk id $id - sleep 1 - ip netns exec ${listener_ns} ./pm_nl_ctl rem token $tk id $id - fi - + pm_nl_add_endpoint $ns1 $addr flags signal counter=$((counter + 1)) add_nr_ns1=$((add_nr_ns1 - 1)) id=$((id + 1)) @@ -896,7 +890,6 @@ do_transfer() local add_nr_ns2=${addr_nr_ns2} local counter=3 local id=20 - local tk da dp sp while [ $add_nr_ns2 -gt 0 ]; do local addr if is_v6 "${connect_addr}"; then @@ -904,20 +897,7 @@ do_transfer() else addr="10.0.$counter.2" fi - if [ $userspace_pm -eq 0 ]; then - pm_nl_add_endpoint $ns2 $addr flags $flags - else - tk=$(sed -n 's/.*\(token:\)\([[:digit:]]*\).*$/\2/p;q' "$evts_ns2") - da=$(sed -n 's/.*\(daddr4:\)\([0-9.]*\).*$/\2/p;q' "$evts_ns2") - dp=$(sed -n 's/.*\(dport:\)\([[:digit:]]*\).*$/\2/p;q' "$evts_ns2") - ip netns exec ${connector_ns} ./pm_nl_ctl csf lip $addr lid $id \ - rip $da rport $dp token $tk - sleep 1 - sp=$(grep "type:10" "$evts_ns2" | - sed -n 's/.*\(sport:\)\([[:digit:]]*\).*$/\2/p;q') - ip netns exec ${connector_ns} ./pm_nl_ctl dsf lip $addr lport $sp \ - rip $da rport $dp token $tk - fi + pm_nl_add_endpoint $ns2 $addr flags $flags counter=$((counter + 1)) add_nr_ns2=$((add_nr_ns2 - 1)) id=$((id + 1)) @@ -986,6 +966,126 @@ do_transfer() done done fi +} + +do_transfer() +{ + local listener_ns="$1" + local connector_ns="$2" + local cl_proto="$3" + local srv_proto="$4" + local connect_addr="$5" + local speed="$6" + + local port=$((10000 + TEST_COUNT - 1)) + local cappid + + :> "$cout" + :> "$sout" + :> "$capout" + + if [ $capture -eq 1 ]; then + local capuser + if [ -z $SUDO_USER ] ; then + capuser="" + else + capuser="-Z $SUDO_USER" + fi + + capfile=$(printf "mp_join-%02u-%s.pcap" "$TEST_COUNT" "${listener_ns}") + + echo "Capturing traffic for test $TEST_COUNT into $capfile" + ip netns exec ${listener_ns} tcpdump -i any -s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 & + cappid=$! + + sleep 1 + fi + + NSTAT_HISTORY=/tmp/${listener_ns}.nstat ip netns exec ${listener_ns} \ + nstat -n + NSTAT_HISTORY=/tmp/${connector_ns}.nstat ip netns exec ${connector_ns} \ + nstat -n + + local extra_args + if [ $speed = "fast" ]; then + extra_args="-j" + elif [ $speed = "slow" ]; then + extra_args="-r 50" + elif [[ $speed = "speed_"* ]]; then + extra_args="-r ${speed:6}" + fi + + local flags="subflow" + local extra_cl_args="" + local extra_srv_args="" + local trunc_size="" + if [[ "${addr_nr_ns2}" = "fastclose_"* ]]; then + if [ ${test_linkfail} -le 1 ]; then + echo "fastclose tests need test_linkfail argument" + fail_test + return 1 + fi + + # disconnect + trunc_size=${test_linkfail} + local side=${addr_nr_ns2:10} + + if [ ${side} = "client" ]; then + extra_cl_args="-f ${test_linkfail}" + extra_srv_args="-f -1" + elif [ ${side} = "server" ]; then + extra_srv_args="-f ${test_linkfail}" + extra_cl_args="-f -1" + else + echo "wrong/unknown fastclose spec ${side}" + fail_test + return 1 + fi + addr_nr_ns2=0 + elif [[ "${addr_nr_ns2}" = "fullmesh_"* ]]; then + flags="${flags},fullmesh" + addr_nr_ns2=${addr_nr_ns2:9} + fi + + extra_srv_args="$extra_args $extra_srv_args" + if [ "$test_linkfail" -gt 1 ];then + timeout ${timeout_test} \ + ip netns exec ${listener_ns} \ + ./mptcp_connect -t ${timeout_poll} -l -p $port -s ${srv_proto} \ + $extra_srv_args "::" < "$sinfail" > "$sout" & + else + timeout ${timeout_test} \ + ip netns exec ${listener_ns} \ + ./mptcp_connect -t ${timeout_poll} -l -p $port -s ${srv_proto} \ + $extra_srv_args "::" < "$sin" > "$sout" & + fi + local spid=$! + + wait_local_port_listen "${listener_ns}" "${port}" + + extra_cl_args="$extra_args $extra_cl_args" + if [ "$test_linkfail" -eq 0 ];then + timeout ${timeout_test} \ + ip netns exec ${connector_ns} \ + ./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \ + $extra_cl_args $connect_addr < "$cin" > "$cout" & + elif [ "$test_linkfail" -eq 1 ] || [ "$test_linkfail" -eq 2 ];then + ( cat "$cinfail" ; sleep 2; link_failure $listener_ns ; cat "$cinfail" ) | \ + tee "$cinsent" | \ + timeout ${timeout_test} \ + ip netns exec ${connector_ns} \ + ./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \ + $extra_cl_args $connect_addr > "$cout" & + else + tee "$cinsent" < "$cinfail" | \ + timeout ${timeout_test} \ + ip netns exec ${connector_ns} \ + ./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \ + $extra_cl_args $connect_addr > "$cout" & + fi + local cpid=$! + + pm_nl_set_endpoint $listener_ns $connector_ns $connect_addr wait $cpid local retc=$? @@ -1016,13 +1116,13 @@ do_transfer() return 1 fi - if [ "$test_link_fail" -gt 1 ];then + if [ "$test_linkfail" -gt 1 ];then check_transfer $sinfail $cout "file received by client" $trunc_size else check_transfer $sin $cout "file received by client" $trunc_size fi retc=$? - if [ "$test_link_fail" -eq 0 ];then + if [ "$test_linkfail" -eq 0 ];then check_transfer $cin $sout "file received by server" $trunc_size else check_transfer $cinsent $sout "file received by server" $trunc_size @@ -1055,11 +1155,7 @@ run_tests() local listener_ns="$1" local connector_ns="$2" local connect_addr="$3" - local test_linkfail="${4:-0}" - local addr_nr_ns1="${5:-0}" - local addr_nr_ns2="${6:-0}" - local speed="${7:-fast}" - local sflags="${8:-""}" + local speed="${4:-fast}" local size @@ -1103,8 +1199,7 @@ run_tests() make_file "$sinfail" "server" $size fi - do_transfer ${listener_ns} ${connector_ns} MPTCP MPTCP ${connect_addr} \ - ${test_linkfail} ${addr_nr_ns1} ${addr_nr_ns2} ${speed} ${sflags} + do_transfer ${listener_ns} ${connector_ns} MPTCP MPTCP ${connect_addr} ${speed} } dump_stats() @@ -1120,7 +1215,6 @@ chk_csum_nr() local csum_ns1=${1:-0} local csum_ns2=${2:-0} local count - local dump_stats local extra_msg="" local allow_multi_errors_ns1=0 local allow_multi_errors_ns2=0 @@ -1135,34 +1229,33 @@ chk_csum_nr() fi printf "%-${nr_blank}s %s" " " "sum" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtDataCsumErr | awk '{print $2}') - [ -z "$count" ] && count=0 + count=$(get_counter ${ns1} "MPTcpExtDataCsumErr") if [ "$count" != "$csum_ns1" ]; then extra_msg="$extra_msg ns1=$count" fi - if { [ "$count" != $csum_ns1 ] && [ $allow_multi_errors_ns1 -eq 0 ]; } || + if [ -z "$count" ]; then + echo -n "[skip]" + elif { [ "$count" != $csum_ns1 ] && [ $allow_multi_errors_ns1 -eq 0 ]; } || { [ "$count" -lt $csum_ns1 ] && [ $allow_multi_errors_ns1 -eq 1 ]; }; then echo "[fail] got $count data checksum error[s] expected $csum_ns1" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - csum " - count=$(ip netns exec $ns2 nstat -as | grep MPTcpExtDataCsumErr | awk '{print $2}') - [ -z "$count" ] && count=0 + count=$(get_counter ${ns2} "MPTcpExtDataCsumErr") if [ "$count" != "$csum_ns2" ]; then extra_msg="$extra_msg ns2=$count" fi - if { [ "$count" != $csum_ns2 ] && [ $allow_multi_errors_ns2 -eq 0 ]; } || + if [ -z "$count" ]; then + echo -n "[skip]" + elif { [ "$count" != $csum_ns2 ] && [ $allow_multi_errors_ns2 -eq 0 ]; } || { [ "$count" -lt $csum_ns2 ] && [ $allow_multi_errors_ns2 -eq 1 ]; }; then echo "[fail] got $count data checksum error[s] expected $csum_ns2" fail_test - dump_stats=1 else echo -n "[ ok ]" fi - [ "${dump_stats}" = 1 ] && dump_stats echo "$extra_msg" } @@ -1173,7 +1266,6 @@ chk_fail_nr() local fail_rx=$2 local ns_invert=${3:-""} local count - local dump_stats local ns_tx=$ns1 local ns_rx=$ns2 local extra_msg="" @@ -1196,37 +1288,35 @@ chk_fail_nr() fi printf "%-${nr_blank}s %s" " " "ftx" - count=$(ip netns exec $ns_tx nstat -as | grep MPTcpExtMPFailTx | awk '{print $2}') - [ -z "$count" ] && count=0 + count=$(get_counter ${ns_tx} "MPTcpExtMPFailTx") if [ "$count" != "$fail_tx" ]; then extra_msg="$extra_msg,tx=$count" fi - if { [ "$count" != "$fail_tx" ] && [ $allow_tx_lost -eq 0 ]; } || + if [ -z "$count" ]; then + echo -n "[skip]" + elif { [ "$count" != "$fail_tx" ] && [ $allow_tx_lost -eq 0 ]; } || { [ "$count" -gt "$fail_tx" ] && [ $allow_tx_lost -eq 1 ]; }; then echo "[fail] got $count MP_FAIL[s] TX expected $fail_tx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - failrx" - count=$(ip netns exec $ns_rx nstat -as | grep MPTcpExtMPFailRx | awk '{print $2}') - [ -z "$count" ] && count=0 + count=$(get_counter ${ns_rx} "MPTcpExtMPFailRx") if [ "$count" != "$fail_rx" ]; then extra_msg="$extra_msg,rx=$count" fi - if { [ "$count" != "$fail_rx" ] && [ $allow_rx_lost -eq 0 ]; } || + if [ -z "$count" ]; then + echo -n "[skip]" + elif { [ "$count" != "$fail_rx" ] && [ $allow_rx_lost -eq 0 ]; } || { [ "$count" -gt "$fail_rx" ] && [ $allow_rx_lost -eq 1 ]; }; then echo "[fail] got $count MP_FAIL[s] RX expected $fail_rx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi - [ "${dump_stats}" = 1 ] && dump_stats - echo "$extra_msg" } @@ -1236,7 +1326,6 @@ chk_fclose_nr() local fclose_rx=$2 local ns_invert=$3 local count - local dump_stats local ns_tx=$ns2 local ns_rx=$ns1 local extra_msg=" " @@ -1248,31 +1337,29 @@ chk_fclose_nr() fi printf "%-${nr_blank}s %s" " " "ctx" - count=$(ip netns exec $ns_tx nstat -as | grep MPTcpExtMPFastcloseTx | awk '{print $2}') - [ -z "$count" ] && count=0 - [ "$count" != "$fclose_tx" ] && extra_msg="$extra_msg,tx=$count" - if [ "$count" != "$fclose_tx" ]; then + count=$(get_counter ${ns_tx} "MPTcpExtMPFastcloseTx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$fclose_tx" ]; then + extra_msg="$extra_msg,tx=$count" echo "[fail] got $count MP_FASTCLOSE[s] TX expected $fclose_tx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - fclzrx" - count=$(ip netns exec $ns_rx nstat -as | grep MPTcpExtMPFastcloseRx | awk '{print $2}') - [ -z "$count" ] && count=0 - [ "$count" != "$fclose_rx" ] && extra_msg="$extra_msg,rx=$count" - if [ "$count" != "$fclose_rx" ]; then + count=$(get_counter ${ns_rx} "MPTcpExtMPFastcloseRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$fclose_rx" ]; then + extra_msg="$extra_msg,rx=$count" echo "[fail] got $count MP_FASTCLOSE[s] RX expected $fclose_rx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi - [ "${dump_stats}" = 1 ] && dump_stats - echo "$extra_msg" } @@ -1282,7 +1369,6 @@ chk_rst_nr() local rst_rx=$2 local ns_invert=${3:-""} local count - local dump_stats local ns_tx=$ns1 local ns_rx=$ns2 local extra_msg="" @@ -1294,29 +1380,27 @@ chk_rst_nr() fi printf "%-${nr_blank}s %s" " " "rtx" - count=$(ip netns exec $ns_tx nstat -as | grep MPTcpExtMPRstTx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ $count -lt $rst_tx ]; then + count=$(get_counter ${ns_tx} "MPTcpExtMPRstTx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ $count -lt $rst_tx ]; then echo "[fail] got $count MP_RST[s] TX expected $rst_tx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - rstrx " - count=$(ip netns exec $ns_rx nstat -as | grep MPTcpExtMPRstRx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" -lt "$rst_rx" ]; then + count=$(get_counter ${ns_rx} "MPTcpExtMPRstRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" -lt "$rst_rx" ]; then echo "[fail] got $count MP_RST[s] RX expected $rst_rx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi - [ "${dump_stats}" = 1 ] && dump_stats - echo "$extra_msg" } @@ -1325,31 +1409,28 @@ chk_infi_nr() local infi_tx=$1 local infi_rx=$2 local count - local dump_stats printf "%-${nr_blank}s %s" " " "itx" - count=$(ip netns exec $ns2 nstat -as | grep InfiniteMapTx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$infi_tx" ]; then + count=$(get_counter ${ns2} "MPTcpExtInfiniteMapTx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$infi_tx" ]; then echo "[fail] got $count infinite map[s] TX expected $infi_tx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - infirx" - count=$(ip netns exec $ns1 nstat -as | grep InfiniteMapRx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$infi_rx" ]; then + count=$(get_counter ${ns1} "MPTcpExtInfiniteMapRx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$infi_rx" ]; then echo "[fail] got $count infinite map[s] RX expected $infi_rx" fail_test - dump_stats=1 else echo "[ ok ]" fi - - [ "${dump_stats}" = 1 ] && dump_stats } chk_join_nr() @@ -1364,7 +1445,6 @@ chk_join_nr() local infi_nr=${8:-0} local corrupted_pkts=${9:-0} local count - local dump_stats local with_cookie local title="${TEST_NAME}" @@ -1373,21 +1453,22 @@ chk_join_nr() fi printf "%03u %-36s %s" "${TEST_COUNT}" "${title}" "syn" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMPJoinSynRx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$syn_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtMPJoinSynRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$syn_nr" ]; then echo "[fail] got $count JOIN[s] syn expected $syn_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - synack" with_cookie=$(ip netns exec $ns2 sysctl -n net.ipv4.tcp_syncookies) - count=$(ip netns exec $ns2 nstat -as | grep MPTcpExtMPJoinSynAckRx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$syn_ack_nr" ]; then + count=$(get_counter ${ns2} "MPTcpExtMPJoinSynAckRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$syn_ack_nr" ]; then # simult connections exceeding the limit with cookie enabled could go up to # synack validation as the conn limit can be enforced reliably only after # the subflow creation @@ -1396,23 +1477,21 @@ chk_join_nr() else echo "[fail] got $count JOIN[s] synack expected $syn_ack_nr" fail_test - dump_stats=1 fi else echo -n "[ ok ]" fi echo -n " - ack" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMPJoinAckRx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$ack_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtMPJoinAckRx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$ack_nr" ]; then echo "[fail] got $count JOIN[s] ack expected $ack_nr" fail_test - dump_stats=1 else echo "[ ok ]" fi - [ "${dump_stats}" = 1 ] && dump_stats if [ $validate_checksum -eq 1 ]; then chk_csum_nr $csum_ns1 $csum_ns2 chk_fail_nr $fail_nr $fail_nr @@ -1437,12 +1516,12 @@ chk_stale_nr() local recover_nr printf "%-${nr_blank}s %-18s" " " "stale" - stale_nr=$(ip netns exec $ns nstat -as | grep MPTcpExtSubflowStale | awk '{print $2}') - [ -z "$stale_nr" ] && stale_nr=0 - recover_nr=$(ip netns exec $ns nstat -as | grep MPTcpExtSubflowRecover | awk '{print $2}') - [ -z "$recover_nr" ] && recover_nr=0 - if [ $stale_nr -lt $stale_min ] || + stale_nr=$(get_counter ${ns} "MPTcpExtSubflowStale") + recover_nr=$(get_counter ${ns} "MPTcpExtSubflowRecover") + if [ -z "$stale_nr" ] || [ -z "$recover_nr" ]; then + echo "[skip]" + elif [ $stale_nr -lt $stale_min ] || { [ $stale_max -gt 0 ] && [ $stale_nr -gt $stale_max ]; } || [ $((stale_nr - recover_nr)) -ne $stale_delta ]; then echo "[fail] got $stale_nr stale[s] $recover_nr recover[s], " \ @@ -1472,117 +1551,142 @@ chk_add_nr() local mis_syn_nr=${7:-0} local mis_ack_nr=${8:-0} local count - local dump_stats local timeout timeout=$(ip netns exec $ns1 sysctl -n net.mptcp.add_addr_timeout) printf "%-${nr_blank}s %s" " " "add" - count=$(ip netns exec $ns2 nstat -as MPTcpExtAddAddr | grep MPTcpExtAddAddr | awk '{print $2}') - [ -z "$count" ] && count=0 - + count=$(get_counter ${ns2} "MPTcpExtAddAddr") + if [ -z "$count" ]; then + echo -n "[skip]" # if the test configured a short timeout tolerate greater then expected # add addrs options, due to retransmissions - if [ "$count" != "$add_nr" ] && { [ "$timeout" -gt 1 ] || [ "$count" -lt "$add_nr" ]; }; then + elif [ "$count" != "$add_nr" ] && { [ "$timeout" -gt 1 ] || [ "$count" -lt "$add_nr" ]; }; then echo "[fail] got $count ADD_ADDR[s] expected $add_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - echo " - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtEchoAdd | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$echo_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtEchoAdd") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$echo_nr" ]; then echo "[fail] got $count ADD_ADDR echo[s] expected $echo_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi if [ $port_nr -gt 0 ]; then echo -n " - pt " - count=$(ip netns exec $ns2 nstat -as | grep MPTcpExtPortAdd | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$port_nr" ]; then + count=$(get_counter ${ns2} "MPTcpExtPortAdd") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$port_nr" ]; then echo "[fail] got $count ADD_ADDR[s] with a port-number expected $port_nr" fail_test - dump_stats=1 else echo "[ ok ]" fi printf "%-${nr_blank}s %s" " " "syn" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMPJoinPortSynRx | - awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$syn_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtMPJoinPortSynRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$syn_nr" ]; then echo "[fail] got $count JOIN[s] syn with a different \ port-number expected $syn_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - synack" - count=$(ip netns exec $ns2 nstat -as | grep MPTcpExtMPJoinPortSynAckRx | - awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$syn_ack_nr" ]; then + count=$(get_counter ${ns2} "MPTcpExtMPJoinPortSynAckRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$syn_ack_nr" ]; then echo "[fail] got $count JOIN[s] synack with a different \ port-number expected $syn_ack_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - ack" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMPJoinPortAckRx | - awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$ack_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtMPJoinPortAckRx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$ack_nr" ]; then echo "[fail] got $count JOIN[s] ack with a different \ port-number expected $ack_nr" fail_test - dump_stats=1 else echo "[ ok ]" fi printf "%-${nr_blank}s %s" " " "syn" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMismatchPortSynRx | - awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$mis_syn_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtMismatchPortSynRx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$mis_syn_nr" ]; then echo "[fail] got $count JOIN[s] syn with a mismatched \ port-number expected $mis_syn_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - ack " - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMismatchPortAckRx | - awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$mis_ack_nr" ]; then + count=$(get_counter ${ns1} "MPTcpExtMismatchPortAckRx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$mis_ack_nr" ]; then echo "[fail] got $count JOIN[s] ack with a mismatched \ port-number expected $mis_ack_nr" fail_test - dump_stats=1 else echo "[ ok ]" fi else echo "" fi +} + +chk_add_tx_nr() +{ + local add_tx_nr=$1 + local echo_tx_nr=$2 + local timeout + local count + + timeout=$(ip netns exec $ns1 sysctl -n net.mptcp.add_addr_timeout) + + printf "%-${nr_blank}s %s" " " "add TX" + count=$(get_counter ${ns1} "MPTcpExtAddAddrTx") + if [ -z "$count" ]; then + echo -n "[skip]" + # if the test configured a short timeout tolerate greater then expected + # add addrs options, due to retransmissions + elif [ "$count" != "$add_tx_nr" ] && { [ "$timeout" -gt 1 ] || [ "$count" -lt "$add_tx_nr" ]; }; then + echo "[fail] got $count ADD_ADDR[s] TX, expected $add_tx_nr" + fail_test + else + echo -n "[ ok ]" + fi - [ "${dump_stats}" = 1 ] && dump_stats + echo -n " - echo TX " + count=$(get_counter ${ns2} "MPTcpExtEchoAddTx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$echo_tx_nr" ]; then + echo "[fail] got $count ADD_ADDR echo[s] TX, expected $echo_tx_nr" + fail_test + else + echo "[ ok ]" + fi } chk_rm_nr() @@ -1592,7 +1696,6 @@ chk_rm_nr() local invert local simult local count - local dump_stats local addr_ns=$ns1 local subflow_ns=$ns2 local extra_msg="" @@ -1614,82 +1717,89 @@ chk_rm_nr() fi printf "%-${nr_blank}s %s" " " "rm " - count=$(ip netns exec $addr_ns nstat -as | grep MPTcpExtRmAddr | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$rm_addr_nr" ]; then + count=$(get_counter ${addr_ns} "MPTcpExtRmAddr") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$rm_addr_nr" ]; then echo "[fail] got $count RM_ADDR[s] expected $rm_addr_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - rmsf " - count=$(ip netns exec $subflow_ns nstat -as | grep MPTcpExtRmSubflow | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ -n "$simult" ]; then + count=$(get_counter ${subflow_ns} "MPTcpExtRmSubflow") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ -n "$simult" ]; then local cnt suffix - cnt=$(ip netns exec $addr_ns nstat -as | grep MPTcpExtRmSubflow | awk '{print $2}') + cnt=$(get_counter ${addr_ns} "MPTcpExtRmSubflow") # in case of simult flush, the subflow removal count on each side is # unreliable - [ -z "$cnt" ] && cnt=0 count=$((count + cnt)) [ "$count" != "$rm_subflow_nr" ] && suffix="$count in [$rm_subflow_nr:$((rm_subflow_nr*2))]" if [ $count -ge "$rm_subflow_nr" ] && \ [ "$count" -le "$((rm_subflow_nr *2 ))" ]; then - echo "[ ok ] $suffix" + echo -n "[ ok ] $suffix" else echo "[fail] got $count RM_SUBFLOW[s] expected in range [$rm_subflow_nr:$((rm_subflow_nr*2))]" fail_test - dump_stats=1 fi - return - fi - if [ "$count" != "$rm_subflow_nr" ]; then + elif [ "$count" != "$rm_subflow_nr" ]; then echo "[fail] got $count RM_SUBFLOW[s] expected $rm_subflow_nr" fail_test - dump_stats=1 else echo -n "[ ok ]" fi - [ "${dump_stats}" = 1 ] && dump_stats - echo "$extra_msg" } +chk_rm_tx_nr() +{ + local rm_addr_tx_nr=$1 + + printf "%-${nr_blank}s %s" " " "rm TX " + count=$(get_counter ${ns2} "MPTcpExtRmAddrTx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$rm_addr_tx_nr" ]; then + echo "[fail] got $count RM_ADDR[s] expected $rm_addr_tx_nr" + fail_test + else + echo "[ ok ]" + fi +} + chk_prio_nr() { local mp_prio_nr_tx=$1 local mp_prio_nr_rx=$2 local count - local dump_stats printf "%-${nr_blank}s %s" " " "ptx" - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMPPrioTx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$mp_prio_nr_tx" ]; then + count=$(get_counter ${ns1} "MPTcpExtMPPrioTx") + if [ -z "$count" ]; then + echo -n "[skip]" + elif [ "$count" != "$mp_prio_nr_tx" ]; then echo "[fail] got $count MP_PRIO[s] TX expected $mp_prio_nr_tx" fail_test - dump_stats=1 else echo -n "[ ok ]" fi echo -n " - prx " - count=$(ip netns exec $ns1 nstat -as | grep MPTcpExtMPPrioRx | awk '{print $2}') - [ -z "$count" ] && count=0 - if [ "$count" != "$mp_prio_nr_rx" ]; then + count=$(get_counter ${ns1} "MPTcpExtMPPrioRx") + if [ -z "$count" ]; then + echo "[skip]" + elif [ "$count" != "$mp_prio_nr_rx" ]; then echo "[fail] got $count MP_PRIO[s] RX expected $mp_prio_nr_rx" fail_test - dump_stats=1 else echo "[ ok ]" fi - - [ "${dump_stats}" = 1 ] && dump_stats } chk_subflow_nr() @@ -1721,37 +1831,31 @@ chk_subflow_nr() ss -N $ns1 -tOni ss -N $ns1 -tOni | grep token ip -n $ns1 mptcp endpoint - dump_stats fi } chk_mptcp_info() { - local nr_info=$1 - local info + local info1=$1 + local exp1=$2 + local info2=$3 + local exp2=$4 local cnt1 local cnt2 local dump_stats - if [[ $nr_info = "subflows_"* ]]; then - info="subflows" - nr_info=${nr_info:9} - else - echo "[fail] unsupported argument: $nr_info" - fail_test - return 1 - fi - - printf "%-${nr_blank}s %-30s" " " "mptcp_info $info=$nr_info" + printf "%-${nr_blank}s %-30s" " " "mptcp_info $info1:$info2=$exp1:$exp2" - cnt1=$(ss -N $ns1 -inmHM | grep "$info:" | - sed -n 's/.*\('"$info"':\)\([[:digit:]]*\).*$/\2/p;q') + cnt1=$(ss -N $ns1 -inmHM | grep "$info1:" | + sed -n 's/.*\('"$info1"':\)\([[:digit:]]*\).*$/\2/p;q') + cnt2=$(ss -N $ns2 -inmHM | grep "$info2:" | + sed -n 's/.*\('"$info2"':\)\([[:digit:]]*\).*$/\2/p;q') + # 'ss' only display active connections and counters that are not 0. [ -z "$cnt1" ] && cnt1=0 - cnt2=$(ss -N $ns2 -inmHM | grep "$info:" | - sed -n 's/.*\('"$info"':\)\([[:digit:]]*\).*$/\2/p;q') [ -z "$cnt2" ] && cnt2=0 - if [ "$cnt1" != "$nr_info" ] || [ "$cnt2" != "$nr_info" ]; then - echo "[fail] got $cnt1:$cnt2 $info expected $nr_info" + + if [ "$cnt1" != "$exp1" ] || [ "$cnt2" != "$exp2" ]; then + echo "[fail] got $cnt1:$cnt2 $info1:$info2 expected $exp1:$exp2" fail_test dump_stats=1 else @@ -1761,7 +1865,6 @@ chk_mptcp_info() if [ "$dump_stats" = 1 ]; then ss -N $ns1 -inmHM ss -N $ns2 -inmHM - dump_stats fi } @@ -1797,7 +1900,7 @@ wait_attempt_fail() while [ $time -lt $timeout_ms ]; do local cnt - cnt=$(ip netns exec $ns nstat -as TcpAttemptFails | grep TcpAttemptFails | awk '{print $2}') + cnt=$(get_counter ${ns} "TcpAttemptFails") [ "$cnt" = 1 ] && return 1 time=$((time + 100)) @@ -1885,41 +1988,41 @@ subflows_error_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.1.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 fi # multiple subflows, with subflow creation error - if reset "multi subflows, with failing subflow"; then + if reset_with_tcp_filter "multi subflows, with failing subflow" ns1 10.0.3.2 REJECT && + continue_if mptcp_lib_kallsyms_has "mptcp_pm_subflow_check_next$"; then pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 0 2 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow pm_nl_add_endpoint $ns2 10.0.2.2 flags subflow - filter_tcp_from $ns1 10.0.3.2 REJECT - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 fi # multiple subflows, with subflow timeout on MPJ - if reset "multi subflows, with subflow timeout"; then + if reset_with_tcp_filter "multi subflows, with subflow timeout" ns1 10.0.3.2 DROP && + continue_if mptcp_lib_kallsyms_has "mptcp_pm_subflow_check_next$"; then pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 0 2 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow pm_nl_add_endpoint $ns2 10.0.2.2 flags subflow - filter_tcp_from $ns1 10.0.3.2 DROP - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 fi # multiple subflows, check that the endpoint corresponding to # closed subflow (due to reset) is not reused if additional # subflows are added later - if reset "multi subflows, fair usage on close"; then + if reset_with_tcp_filter "multi subflows, fair usage on close" ns1 10.0.3.2 REJECT && + continue_if mptcp_lib_kallsyms_has "mptcp_pm_subflow_check_next$"; then pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - filter_tcp_from $ns1 10.0.3.2 REJECT - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow & + run_tests $ns1 $ns2 10.0.1.1 slow & # mpj subflow will be in TW after the reset wait_attempt_fail $ns2 @@ -1939,6 +2042,7 @@ signal_address_tests() pm_nl_add_endpoint $ns1 10.0.2.1 flags signal run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 0 0 0 + chk_add_tx_nr 1 1 chk_add_nr 1 1 fi @@ -2017,12 +2121,19 @@ signal_address_tests() # the peer could possibly miss some addr notification, allow retransmission ip netns exec $ns1 sysctl -q net.mptcp.add_addr_timeout=1 - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow - chk_join_nr 3 3 3 + run_tests $ns1 $ns2 10.0.1.1 slow - # the server will not signal the address terminating - # the MPC subflow - chk_add_nr 3 3 + # It is not directly linked to the commit introducing this + # symbol but for the parent one which is linked anyway. + if ! mptcp_lib_kallsyms_has "mptcp_pm_subflow_check_next$"; then + chk_join_nr 3 3 2 + chk_add_nr 4 4 + else + chk_join_nr 3 3 3 + # the server will not signal the address terminating + # the MPC subflow + chk_add_nr 3 3 + fi fi } @@ -2042,7 +2153,8 @@ link_failure_tests() pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns2 10.0.3.2 dev ns2eth3 flags subflow pm_nl_add_endpoint $ns2 10.0.4.2 dev ns2eth4 flags subflow - run_tests $ns1 $ns2 10.0.1.1 1 + test_linkfail=1 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 3 3 3 chk_add_nr 1 1 chk_stale_nr $ns2 1 5 1 @@ -2057,7 +2169,8 @@ link_failure_tests() pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns2 10.0.3.2 dev ns2eth3 flags subflow pm_nl_add_endpoint $ns2 10.0.4.2 dev ns2eth4 flags subflow - run_tests $ns1 $ns2 10.0.1.1 2 + test_linkfail=2 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 3 3 3 chk_add_nr 1 1 chk_stale_nr $ns2 1 -1 1 @@ -2070,9 +2183,9 @@ link_failure_tests() pm_nl_set_limits $ns1 0 2 pm_nl_add_endpoint $ns1 10.0.2.1 dev ns1eth2 flags signal pm_nl_set_limits $ns2 1 2 - FAILING_LINKS="1" pm_nl_add_endpoint $ns2 10.0.3.2 dev ns2eth3 flags subflow,backup - run_tests $ns1 $ns2 10.0.1.1 1 + FAILING_LINKS="1" test_linkfail=1 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 2 2 2 chk_add_nr 1 1 chk_link_usage $ns2 ns2eth3 $cinsent 0 @@ -2086,8 +2199,8 @@ link_failure_tests() pm_nl_add_endpoint $ns1 10.0.2.1 dev ns1eth2 flags signal pm_nl_set_limits $ns2 1 2 pm_nl_add_endpoint $ns2 10.0.3.2 dev ns2eth3 flags subflow,backup - FAILING_LINKS="1 2" - run_tests $ns1 $ns2 10.0.1.1 1 + FAILING_LINKS="1 2" test_linkfail=1 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 2 2 2 chk_add_nr 1 1 chk_stale_nr $ns2 2 4 2 @@ -2102,8 +2215,8 @@ link_failure_tests() pm_nl_add_endpoint $ns1 10.0.2.1 dev ns1eth2 flags signal pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns2 10.0.3.2 dev ns2eth3 flags subflow,backup - FAILING_LINKS="1 2" - run_tests $ns1 $ns2 10.0.1.1 2 + FAILING_LINKS="1 2" test_linkfail=2 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 2 2 2 chk_add_nr 1 1 chk_stale_nr $ns2 1 -1 2 @@ -2118,8 +2231,9 @@ add_addr_timeout_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 1 1 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 + chk_add_tx_nr 4 4 chk_add_nr 4 0 fi @@ -2128,7 +2242,7 @@ add_addr_timeout_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 1 1 pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal - run_tests $ns1 $ns2 dead:beef:1::1 0 0 0 slow + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 1 1 1 chk_add_nr 4 0 fi @@ -2139,7 +2253,7 @@ add_addr_timeout_tests() pm_nl_add_endpoint $ns1 10.0.2.1 flags signal pm_nl_add_endpoint $ns1 10.0.3.1 flags signal pm_nl_set_limits $ns2 2 2 - run_tests $ns1 $ns2 10.0.1.1 0 0 0 speed_10 + run_tests $ns1 $ns2 10.0.1.1 speed_10 chk_join_nr 2 2 2 chk_add_nr 8 0 fi @@ -2150,7 +2264,7 @@ add_addr_timeout_tests() pm_nl_add_endpoint $ns1 10.0.12.1 flags signal pm_nl_add_endpoint $ns1 10.0.3.1 flags signal pm_nl_set_limits $ns2 2 2 - run_tests $ns1 $ns2 10.0.1.1 0 0 0 speed_10 + run_tests $ns1 $ns2 10.0.1.1 speed_10 chk_join_nr 1 1 1 chk_add_nr 8 0 fi @@ -2163,8 +2277,10 @@ remove_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 -1 slow + addr_nr_ns2=-1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 + chk_rm_tx_nr 1 chk_rm_nr 1 1 fi @@ -2174,7 +2290,8 @@ remove_tests() pm_nl_set_limits $ns2 0 2 pm_nl_add_endpoint $ns2 10.0.2.2 flags subflow pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 -2 slow + addr_nr_ns2=-2 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_rm_nr 2 2 fi @@ -2184,7 +2301,8 @@ remove_tests() pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 -1 0 slow + addr_nr_ns1=-1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 chk_rm_nr 1 1 invert @@ -2196,7 +2314,8 @@ remove_tests() pm_nl_add_endpoint $ns1 10.0.2.1 flags signal pm_nl_set_limits $ns2 1 2 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 -1 -1 slow + addr_nr_ns1=-1 addr_nr_ns2=-1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_add_nr 1 1 chk_rm_nr 1 1 @@ -2209,7 +2328,8 @@ remove_tests() pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow pm_nl_add_endpoint $ns2 10.0.4.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 -1 -2 speed_10 + addr_nr_ns1=-1 addr_nr_ns2=-2 \ + run_tests $ns1 $ns2 10.0.1.1 speed_10 chk_join_nr 3 3 3 chk_add_nr 1 1 chk_rm_nr 2 2 @@ -2222,7 +2342,8 @@ remove_tests() pm_nl_add_endpoint $ns1 10.0.3.1 flags signal pm_nl_add_endpoint $ns1 10.0.4.1 flags signal pm_nl_set_limits $ns2 3 3 - run_tests $ns1 $ns2 10.0.1.1 0 -3 0 speed_10 + addr_nr_ns1=-3 \ + run_tests $ns1 $ns2 10.0.1.1 speed_10 chk_join_nr 3 3 3 chk_add_nr 3 3 chk_rm_nr 3 3 invert @@ -2235,7 +2356,8 @@ remove_tests() pm_nl_add_endpoint $ns1 10.0.3.1 flags signal pm_nl_add_endpoint $ns1 10.0.14.1 flags signal pm_nl_set_limits $ns2 3 3 - run_tests $ns1 $ns2 10.0.1.1 0 -3 0 speed_10 + addr_nr_ns1=-3 \ + run_tests $ns1 $ns2 10.0.1.1 speed_10 chk_join_nr 1 1 1 chk_add_nr 3 3 chk_rm_nr 3 1 invert @@ -2248,7 +2370,8 @@ remove_tests() pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow pm_nl_add_endpoint $ns2 10.0.4.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 -8 -8 slow + addr_nr_ns1=-8 addr_nr_ns2=-8 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 3 3 3 chk_add_nr 1 1 chk_rm_nr 1 3 invert simult @@ -2261,9 +2384,16 @@ remove_tests() pm_nl_add_endpoint $ns2 10.0.2.2 flags subflow id 150 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow pm_nl_add_endpoint $ns2 10.0.4.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 -8 -8 slow + addr_nr_ns1=-8 addr_nr_ns2=-8 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 3 3 3 - chk_rm_nr 0 3 simult + + if mptcp_lib_kversion_ge 5.18; then + chk_rm_tx_nr 0 + chk_rm_nr 0 3 simult + else + chk_rm_nr 3 3 + fi fi # addresses flush @@ -2273,7 +2403,8 @@ remove_tests() pm_nl_add_endpoint $ns1 10.0.3.1 flags signal pm_nl_add_endpoint $ns1 10.0.4.1 flags signal pm_nl_set_limits $ns2 3 3 - run_tests $ns1 $ns2 10.0.1.1 0 -8 -8 slow + addr_nr_ns1=-8 addr_nr_ns2=-8 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 3 3 3 chk_add_nr 3 3 chk_rm_nr 3 3 invert simult @@ -2286,7 +2417,8 @@ remove_tests() pm_nl_add_endpoint $ns1 10.0.3.1 flags signal pm_nl_add_endpoint $ns1 10.0.14.1 flags signal pm_nl_set_limits $ns2 3 3 - run_tests $ns1 $ns2 10.0.1.1 0 -8 0 slow + addr_nr_ns1=-8 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 3 3 chk_rm_nr 3 1 invert @@ -2297,7 +2429,8 @@ remove_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 -9 slow + addr_nr_ns2=-9 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_rm_nr 1 1 fi @@ -2307,7 +2440,8 @@ remove_tests() pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 -9 0 slow + addr_nr_ns1=-9 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 chk_rm_nr 1 1 invert @@ -2320,7 +2454,8 @@ add_tests() if reset "add single subflow"; then pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 - run_tests $ns1 $ns2 10.0.1.1 0 0 1 slow + addr_nr_ns2=1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 fi @@ -2328,7 +2463,8 @@ add_tests() if reset "add signal address"; then pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 1 0 slow + addr_nr_ns1=1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 fi @@ -2337,7 +2473,8 @@ add_tests() if reset "add multiple subflows"; then pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 0 2 - run_tests $ns1 $ns2 10.0.1.1 0 0 2 slow + addr_nr_ns2=2 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 fi @@ -2345,7 +2482,8 @@ add_tests() if reset "add multiple subflows IPv6"; then pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 0 2 - run_tests $ns1 $ns2 dead:beef:1::1 0 0 2 slow + addr_nr_ns2=2 \ + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 2 2 2 fi @@ -2353,7 +2491,8 @@ add_tests() if reset "add multiple addresses IPv6"; then pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 2 2 - run_tests $ns1 $ns2 dead:beef:1::1 0 2 0 slow + addr_nr_ns1=2 \ + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 2 2 2 chk_add_nr 2 2 fi @@ -2366,14 +2505,14 @@ ipv6_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 dead:beef:3::2 dev ns2eth3 flags subflow - run_tests $ns1 $ns2 dead:beef:1::1 0 0 0 slow + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 1 1 1 fi # add_address, unused IPv6 if reset "unused signal address IPv6"; then pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal - run_tests $ns1 $ns2 dead:beef:1::1 0 0 0 slow + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 0 0 0 chk_add_nr 1 1 fi @@ -2383,7 +2522,7 @@ ipv6_tests() pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 dead:beef:1::1 0 0 0 slow + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 fi @@ -2393,7 +2532,8 @@ ipv6_tests() pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 dead:beef:1::1 0 -1 0 slow + addr_nr_ns1=-1 \ + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 chk_rm_nr 1 1 invert @@ -2405,7 +2545,8 @@ ipv6_tests() pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal pm_nl_set_limits $ns2 1 2 pm_nl_add_endpoint $ns2 dead:beef:3::2 dev ns2eth3 flags subflow - run_tests $ns1 $ns2 dead:beef:1::1 0 -1 -1 slow + addr_nr_ns1=-1 addr_nr_ns2=-1 \ + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 2 2 2 chk_add_nr 1 1 chk_rm_nr 1 1 @@ -2501,41 +2642,46 @@ v4mapped_tests() mixed_tests() { - if reset "IPv4 sockets do not use IPv6 addresses"; then + if reset "IPv4 sockets do not use IPv6 addresses" && + continue_if mptcp_lib_kversion_ge 6.3; then pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 1 1 pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 fi # Need an IPv6 mptcp socket to allow subflows of both families - if reset "simult IPv4 and IPv6 subflows"; then + if reset "simult IPv4 and IPv6 subflows" && + continue_if mptcp_lib_kversion_ge 6.3; then pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 1 1 pm_nl_add_endpoint $ns1 10.0.1.1 flags signal - run_tests $ns1 $ns2 dead:beef:2::1 0 0 0 slow + run_tests $ns1 $ns2 dead:beef:2::1 slow chk_join_nr 1 1 1 fi # cross families subflows will not be created even in fullmesh mode - if reset "simult IPv4 and IPv6 subflows, fullmesh 1x1"; then + if reset "simult IPv4 and IPv6 subflows, fullmesh 1x1" && + continue_if mptcp_lib_kversion_ge 6.3; then pm_nl_set_limits $ns1 0 4 pm_nl_set_limits $ns2 1 4 pm_nl_add_endpoint $ns2 dead:beef:2::2 flags subflow,fullmesh pm_nl_add_endpoint $ns1 10.0.1.1 flags signal - run_tests $ns1 $ns2 dead:beef:2::1 0 0 0 slow + run_tests $ns1 $ns2 dead:beef:2::1 slow chk_join_nr 1 1 1 fi # fullmesh still tries to create all the possibly subflows with # matching family - if reset "simult IPv4 and IPv6 subflows, fullmesh 2x2"; then + if reset "simult IPv4 and IPv6 subflows, fullmesh 2x2" && + continue_if mptcp_lib_kversion_ge 6.3; then pm_nl_set_limits $ns1 0 4 pm_nl_set_limits $ns2 2 4 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal - run_tests $ns1 $ns2 dead:beef:1::1 0 0 fullmesh_1 slow + addr_nr_ns2=fullmesh_1 \ + run_tests $ns1 $ns2 dead:beef:1::1 slow chk_join_nr 4 4 4 fi } @@ -2543,63 +2689,75 @@ mixed_tests() backup_tests() { # single subflow, backup - if reset "single subflow, backup"; then + if reset "single subflow, backup" && + continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow,backup - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow nobackup + sflags=nobackup \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_prio_nr 0 1 fi # single address, backup - if reset "single address, backup"; then + if reset "single address, backup" && + continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow backup + sflags=backup \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 chk_prio_nr 1 1 fi # single address with port, backup - if reset "single address with port, backup"; then + if reset "single address with port, backup" && + continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal port 10100 pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow backup + sflags=backup \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 chk_prio_nr 1 1 fi - if reset "mpc backup"; then + if reset "mpc backup" && + continue_if mptcp_lib_kallsyms_doesnt_have "mptcp_subflow_send_ack$"; then pm_nl_add_endpoint $ns2 10.0.1.2 flags subflow,backup - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 chk_prio_nr 0 1 fi - if reset "mpc backup both sides"; then + if reset "mpc backup both sides" && + continue_if mptcp_lib_kallsyms_doesnt_have "mptcp_subflow_send_ack$"; then pm_nl_add_endpoint $ns1 10.0.1.1 flags subflow,backup pm_nl_add_endpoint $ns2 10.0.1.2 flags subflow,backup - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 chk_prio_nr 1 1 fi - if reset "mpc switch to backup"; then + if reset "mpc switch to backup" && + continue_if mptcp_lib_kallsyms_doesnt_have "mptcp_subflow_send_ack$"; then pm_nl_add_endpoint $ns2 10.0.1.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow backup + sflags=backup \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 chk_prio_nr 0 1 fi - if reset "mpc switch to backup both sides"; then + if reset "mpc switch to backup both sides" && + continue_if mptcp_lib_kallsyms_doesnt_have "mptcp_subflow_send_ack$"; then pm_nl_add_endpoint $ns1 10.0.1.1 flags subflow pm_nl_add_endpoint $ns2 10.0.1.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow backup + sflags=backup \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 chk_prio_nr 1 1 fi @@ -2622,38 +2780,41 @@ verify_listener_events() local family local saddr local sport + local name if [ $e_type = $LISTENER_CREATED ]; then - stdbuf -o0 -e0 printf "\t\t\t\t\t CREATE_LISTENER %s:%s"\ - $e_saddr $e_sport + name="LISTENER_CREATED" elif [ $e_type = $LISTENER_CLOSED ]; then - stdbuf -o0 -e0 printf "\t\t\t\t\t CLOSE_LISTENER %s:%s "\ - $e_saddr $e_sport + name="LISTENER_CLOSED" + else + name="$e_type" fi - type=$(grep "type:$e_type," $evt | - sed --unbuffered -n 's/.*\(type:\)\([[:digit:]]*\).*$/\2/p;q') - family=$(grep "type:$e_type," $evt | - sed --unbuffered -n 's/.*\(family:\)\([[:digit:]]*\).*$/\2/p;q') - sport=$(grep "type:$e_type," $evt | - sed --unbuffered -n 's/.*\(sport:\)\([[:digit:]]*\).*$/\2/p;q') + printf "%-${nr_blank}s %s %s:%s " " " "$name" "$e_saddr" "$e_sport" + + if ! mptcp_lib_kallsyms_has "mptcp_event_pm_listener$"; then + printf "[skip]: event not supported\n" + return + fi + + type=$(grep "type:$e_type," $evt | sed -n 's/.*\(type:\)\([[:digit:]]*\).*$/\2/p;q') + family=$(grep "type:$e_type," $evt | sed -n 's/.*\(family:\)\([[:digit:]]*\).*$/\2/p;q') + sport=$(grep "type:$e_type," $evt | sed -n 's/.*\(sport:\)\([[:digit:]]*\).*$/\2/p;q') if [ $family ] && [ $family = $AF_INET6 ]; then - saddr=$(grep "type:$e_type," $evt | - sed --unbuffered -n 's/.*\(saddr6:\)\([0-9a-f:.]*\).*$/\2/p;q') + saddr=$(grep "type:$e_type," $evt | sed -n 's/.*\(saddr6:\)\([0-9a-f:.]*\).*$/\2/p;q') else - saddr=$(grep "type:$e_type," $evt | - sed --unbuffered -n 's/.*\(saddr4:\)\([0-9.]*\).*$/\2/p;q') + saddr=$(grep "type:$e_type," $evt | sed -n 's/.*\(saddr4:\)\([0-9.]*\).*$/\2/p;q') fi if [ $type ] && [ $type = $e_type ] && [ $family ] && [ $family = $e_family ] && [ $saddr ] && [ $saddr = $e_saddr ] && [ $sport ] && [ $sport = $e_sport ]; then - stdbuf -o0 -e0 printf "[ ok ]\n" + echo "[ ok ]" return 0 fi fail_test - stdbuf -o0 -e0 printf "[fail]\n" + echo "[fail]" } add_addr_ports_tests() @@ -2685,7 +2846,8 @@ add_addr_ports_tests() pm_nl_set_limits $ns1 0 1 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal port 10100 pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 -1 0 slow + addr_nr_ns1=-1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 1 chk_add_nr 1 1 1 chk_rm_nr 1 1 invert @@ -2701,7 +2863,8 @@ add_addr_ports_tests() pm_nl_add_endpoint $ns1 10.0.2.1 flags signal port 10100 pm_nl_set_limits $ns2 1 2 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 -1 -1 slow + addr_nr_ns1=-1 addr_nr_ns2=-1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_add_nr 1 1 1 chk_rm_nr 1 1 @@ -2714,7 +2877,8 @@ add_addr_ports_tests() pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow pm_nl_add_endpoint $ns2 10.0.4.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 -8 -2 slow + addr_nr_ns1=-8 addr_nr_ns2=-2 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 3 3 3 chk_add_nr 1 1 chk_rm_nr 1 3 invert simult @@ -2916,7 +3080,8 @@ fullmesh_tests() pm_nl_set_limits $ns2 1 4 pm_nl_add_endpoint $ns2 10.0.2.2 flags subflow,fullmesh pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow,fullmesh - run_tests $ns1 $ns2 10.0.1.1 0 1 0 slow + addr_nr_ns1=1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 4 4 4 chk_add_nr 1 1 fi @@ -2928,7 +3093,8 @@ fullmesh_tests() pm_nl_set_limits $ns1 1 3 pm_nl_set_limits $ns2 1 3 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal - run_tests $ns1 $ns2 10.0.1.1 0 0 fullmesh_1 slow + addr_nr_ns2=fullmesh_1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 3 3 3 chk_add_nr 1 1 fi @@ -2940,7 +3106,8 @@ fullmesh_tests() pm_nl_set_limits $ns1 2 5 pm_nl_set_limits $ns2 1 5 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal - run_tests $ns1 $ns2 10.0.1.1 0 0 fullmesh_2 slow + addr_nr_ns2=fullmesh_2 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 5 5 5 chk_add_nr 1 1 fi @@ -2953,48 +3120,57 @@ fullmesh_tests() pm_nl_set_limits $ns1 2 4 pm_nl_set_limits $ns2 1 4 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal - run_tests $ns1 $ns2 10.0.1.1 0 0 fullmesh_2 slow + addr_nr_ns2=fullmesh_2 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 4 4 4 chk_add_nr 1 1 fi # set fullmesh flag - if reset "set fullmesh flag test"; then + if reset "set fullmesh flag test" && + continue_if mptcp_lib_kversion_ge 5.18; then pm_nl_set_limits $ns1 4 4 pm_nl_add_endpoint $ns1 10.0.2.1 flags subflow pm_nl_set_limits $ns2 4 4 - run_tests $ns1 $ns2 10.0.1.1 0 0 1 slow fullmesh + addr_nr_ns2=1 sflags=fullmesh \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_rm_nr 0 1 fi # set nofullmesh flag - if reset "set nofullmesh flag test"; then + if reset "set nofullmesh flag test" && + continue_if mptcp_lib_kversion_ge 5.18; then pm_nl_set_limits $ns1 4 4 pm_nl_add_endpoint $ns1 10.0.2.1 flags subflow,fullmesh pm_nl_set_limits $ns2 4 4 - run_tests $ns1 $ns2 10.0.1.1 0 0 fullmesh_1 slow nofullmesh + addr_nr_ns2=fullmesh_1 sflags=nofullmesh \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_rm_nr 0 1 fi # set backup,fullmesh flags - if reset "set backup,fullmesh flags test"; then + if reset "set backup,fullmesh flags test" && + continue_if mptcp_lib_kversion_ge 5.18; then pm_nl_set_limits $ns1 4 4 pm_nl_add_endpoint $ns1 10.0.2.1 flags subflow pm_nl_set_limits $ns2 4 4 - run_tests $ns1 $ns2 10.0.1.1 0 0 1 slow backup,fullmesh + addr_nr_ns2=1 sflags=backup,fullmesh \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_prio_nr 0 1 chk_rm_nr 0 1 fi # set nobackup,nofullmesh flags - if reset "set nobackup,nofullmesh flags test"; then + if reset "set nobackup,nofullmesh flags test" && + continue_if mptcp_lib_kversion_ge 5.18; then pm_nl_set_limits $ns1 4 4 pm_nl_set_limits $ns2 4 4 pm_nl_add_endpoint $ns2 10.0.2.2 flags subflow,backup,fullmesh - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow nobackup,nofullmesh + sflags=nobackup,nofullmesh \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 2 2 2 chk_prio_nr 0 1 chk_rm_nr 0 1 @@ -3003,15 +3179,17 @@ fullmesh_tests() fastclose_tests() { - if reset "fastclose test"; then - run_tests $ns1 $ns2 10.0.1.1 1024 0 fastclose_client + if reset_check_counter "fastclose test" "MPTcpExtMPFastcloseTx"; then + test_linkfail=1024 addr_nr_ns2=fastclose_client \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 0 0 0 chk_fclose_nr 1 1 chk_rst_nr 1 1 invert fi - if reset "fastclose server test"; then - run_tests $ns1 $ns2 10.0.1.1 1024 0 fastclose_server + if reset_check_counter "fastclose server test" "MPTcpExtMPFastcloseRx"; then + test_linkfail=1024 addr_nr_ns2=fastclose_server \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 0 0 0 chk_fclose_nr 1 1 invert chk_rst_nr 1 1 @@ -3029,7 +3207,8 @@ fail_tests() { # single subflow if reset_with_fail "Infinite map" 1; then - run_tests $ns1 $ns2 10.0.1.1 128 + test_linkfail=128 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 0 0 0 +1 +0 1 0 1 "$(pedit_action_pkts)" chk_fail_nr 1 -1 invert fi @@ -3040,15 +3219,82 @@ fail_tests() pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.2.2 dev ns2eth2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 1024 + test_linkfail=1024 \ + run_tests $ns1 $ns2 10.0.1.1 chk_join_nr 1 1 1 1 0 1 1 0 "$(pedit_action_pkts)" fi } +userspace_pm_add_addr() +{ + local addr=$1 + local id=$2 + local tk + + tk=$(grep "type:1," "$evts_ns1" | + sed -n 's/.*\(token:\)\([[:digit:]]*\).*$/\2/p;q') + ip netns exec $ns1 ./pm_nl_ctl ann $addr token $tk id $id + sleep 1 +} + +userspace_pm_rm_sf_addr_ns1() +{ + local addr=$1 + local id=$2 + local tk sp da dp + + tk=$(grep "type:1," "$evts_ns1" | + sed -n 's/.*\(token:\)\([[:digit:]]*\).*$/\2/p;q') + sp=$(grep "type:10" "$evts_ns1" | + sed -n 's/.*\(sport:\)\([[:digit:]]*\).*$/\2/p;q') + da=$(grep "type:10" "$evts_ns1" | + sed -n 's/.*\(daddr6:\)\([0-9a-f:.]*\).*$/\2/p;q') + dp=$(grep "type:10" "$evts_ns1" | + sed -n 's/.*\(dport:\)\([[:digit:]]*\).*$/\2/p;q') + ip netns exec $ns1 ./pm_nl_ctl rem token $tk id $id + ip netns exec $ns1 ./pm_nl_ctl dsf lip "::ffff:$addr" \ + lport $sp rip $da rport $dp token $tk + wait_rm_addr $ns1 1 + wait_rm_sf $ns1 1 +} + +userspace_pm_add_sf() +{ + local addr=$1 + local id=$2 + local tk da dp + + tk=$(sed -n 's/.*\(token:\)\([[:digit:]]*\).*$/\2/p;q' "$evts_ns2") + da=$(sed -n 's/.*\(daddr4:\)\([0-9.]*\).*$/\2/p;q' "$evts_ns2") + dp=$(sed -n 's/.*\(dport:\)\([[:digit:]]*\).*$/\2/p;q' "$evts_ns2") + ip netns exec $ns2 ./pm_nl_ctl csf lip $addr lid $id \ + rip $da rport $dp token $tk + sleep 1 +} + +userspace_pm_rm_sf_addr_ns2() +{ + local addr=$1 + local id=$2 + local tk da dp sp + + tk=$(sed -n 's/.*\(token:\)\([[:digit:]]*\).*$/\2/p;q' "$evts_ns2") + da=$(sed -n 's/.*\(daddr4:\)\([0-9.]*\).*$/\2/p;q' "$evts_ns2") + dp=$(sed -n 's/.*\(dport:\)\([[:digit:]]*\).*$/\2/p;q' "$evts_ns2") + sp=$(grep "type:10" "$evts_ns2" | + sed -n 's/.*\(sport:\)\([[:digit:]]*\).*$/\2/p;q') + ip netns exec $ns2 ./pm_nl_ctl rem token $tk id $id + ip netns exec $ns2 ./pm_nl_ctl dsf lip $addr lport $sp \ + rip $da rport $dp token $tk + wait_rm_addr $ns2 1 + wait_rm_sf $ns2 1 +} + userspace_tests() { # userspace pm type prevents add_addr - if reset "userspace pm type prevents add_addr"; then + if reset "userspace pm type prevents add_addr" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns1 pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 0 2 @@ -3059,7 +3305,8 @@ userspace_tests() fi # userspace pm type does not echo add_addr without daemon - if reset "userspace pm no echo w/o daemon"; then + if reset "userspace pm no echo w/o daemon" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns2 pm_nl_set_limits $ns1 0 2 pm_nl_set_limits $ns2 0 2 @@ -3070,7 +3317,8 @@ userspace_tests() fi # userspace pm type rejects join - if reset "userspace pm type rejects join"; then + if reset "userspace pm type rejects join" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns1 pm_nl_set_limits $ns1 1 1 pm_nl_set_limits $ns2 1 1 @@ -3080,7 +3328,8 @@ userspace_tests() fi # userspace pm type does not send join - if reset "userspace pm type does not send join"; then + if reset "userspace pm type does not send join" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns2 pm_nl_set_limits $ns1 1 1 pm_nl_set_limits $ns2 1 1 @@ -3090,62 +3339,87 @@ userspace_tests() fi # userspace pm type prevents mp_prio - if reset "userspace pm type prevents mp_prio"; then + if reset "userspace pm type prevents mp_prio" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns1 pm_nl_set_limits $ns1 1 1 pm_nl_set_limits $ns2 1 1 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow backup + sflags=backup \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 1 1 0 chk_prio_nr 0 0 fi # userspace pm type prevents rm_addr - if reset "userspace pm type prevents rm_addr"; then + if reset "userspace pm type prevents rm_addr" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns1 set_userspace_pm $ns2 pm_nl_set_limits $ns1 0 1 pm_nl_set_limits $ns2 0 1 pm_nl_add_endpoint $ns2 10.0.3.2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 0 0 -1 slow + addr_nr_ns2=-1 \ + run_tests $ns1 $ns2 10.0.1.1 slow chk_join_nr 0 0 0 chk_rm_nr 0 0 fi # userspace pm add & remove address - if reset_with_events "userspace pm add & remove address"; then + if reset_with_events "userspace pm add & remove address" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns1 pm_nl_set_limits $ns2 1 1 - run_tests $ns1 $ns2 10.0.1.1 0 userspace_1 0 slow + run_tests $ns1 $ns2 10.0.1.1 speed_10 & + local tests_pid=$! + wait_mpj $ns1 + userspace_pm_add_addr 10.0.2.1 10 chk_join_nr 1 1 1 chk_add_nr 1 1 + chk_mptcp_info subflows 1 subflows 1 + chk_mptcp_info add_addr_signal 1 add_addr_accepted 1 + userspace_pm_rm_sf_addr_ns1 10.0.2.1 10 chk_rm_nr 1 1 invert + chk_mptcp_info subflows 0 subflows 0 kill_events_pids + wait $tests_pid fi # userspace pm create destroy subflow - if reset_with_events "userspace pm create destroy subflow"; then + if reset_with_events "userspace pm create destroy subflow" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then set_userspace_pm $ns2 pm_nl_set_limits $ns1 0 1 - run_tests $ns1 $ns2 10.0.1.1 0 0 userspace_1 slow + run_tests $ns1 $ns2 10.0.1.1 speed_10 & + local tests_pid=$! + wait_mpj $ns2 + userspace_pm_add_sf 10.0.3.2 20 chk_join_nr 1 1 1 - chk_rm_nr 0 1 + chk_mptcp_info subflows 1 subflows 1 + userspace_pm_rm_sf_addr_ns2 10.0.3.2 20 + chk_rm_nr 1 1 + chk_mptcp_info subflows 0 subflows 0 kill_events_pids + wait $tests_pid fi } endpoint_tests() { + # subflow_rebuild_header is needed to support the implicit flag # userspace pm type prevents add_addr - if reset "implicit EP"; then + if reset "implicit EP" && + mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then pm_nl_set_limits $ns1 2 2 pm_nl_set_limits $ns2 2 2 pm_nl_add_endpoint $ns1 10.0.2.1 flags signal - run_tests $ns1 $ns2 10.0.1.1 0 0 0 slow 2>/dev/null & + run_tests $ns1 $ns2 10.0.1.1 slow 2>/dev/null & wait_mpj $ns1 pm_nl_check_endpoint 1 "creation" \ $ns2 10.0.2.2 id 1 flags implicit + chk_mptcp_info subflows 1 subflows 1 + chk_mptcp_info add_addr_signal 1 add_addr_accepted 1 pm_nl_add_endpoint $ns2 10.0.2.2 id 33 pm_nl_check_endpoint 0 "ID change is prevented" \ @@ -3157,25 +3431,27 @@ endpoint_tests() kill_tests_wait fi - if reset "delete and re-add"; then + if reset "delete and re-add" && + mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then pm_nl_set_limits $ns1 1 1 pm_nl_set_limits $ns2 1 1 pm_nl_add_endpoint $ns2 10.0.2.2 id 2 dev ns2eth2 flags subflow - run_tests $ns1 $ns2 10.0.1.1 4 0 0 speed_20 2>/dev/null & + test_linkfail=4 \ + run_tests $ns1 $ns2 10.0.1.1 speed_20 2>/dev/null & wait_mpj $ns2 chk_subflow_nr needtitle "before delete" 2 - chk_mptcp_info subflows_1 + chk_mptcp_info subflows 1 subflows 1 pm_nl_del_endpoint $ns2 2 10.0.2.2 sleep 0.5 chk_subflow_nr "" "after delete" 1 - chk_mptcp_info subflows_0 + chk_mptcp_info subflows 0 subflows 0 pm_nl_add_endpoint $ns2 10.0.2.2 dev ns2eth2 flags subflow wait_mpj $ns2 chk_subflow_nr "" "after re-add" 2 - chk_mptcp_info subflows_1 + chk_mptcp_info subflows 1 subflows 1 kill_tests_wait fi } diff --git a/tools/testing/selftests/net/mptcp/mptcp_lib.sh b/tools/testing/selftests/net/mptcp/mptcp_lib.sh new file mode 100644 index 000000000000..f32045b23b89 --- /dev/null +++ b/tools/testing/selftests/net/mptcp/mptcp_lib.sh @@ -0,0 +1,104 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 + +readonly KSFT_FAIL=1 +readonly KSFT_SKIP=4 + +# SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES env var can be set when validating all +# features using the last version of the kernel and the selftests to make sure +# a test is not being skipped by mistake. +mptcp_lib_expect_all_features() { + [ "${SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES:-}" = "1" ] +} + +# $1: msg +mptcp_lib_fail_if_expected_feature() { + if mptcp_lib_expect_all_features; then + echo "ERROR: missing feature: ${*}" + exit ${KSFT_FAIL} + fi + + return 1 +} + +# $1: file +mptcp_lib_has_file() { + local f="${1}" + + if [ -f "${f}" ]; then + return 0 + fi + + mptcp_lib_fail_if_expected_feature "${f} file not found" +} + +mptcp_lib_check_mptcp() { + if ! mptcp_lib_has_file "/proc/sys/net/mptcp/enabled"; then + echo "SKIP: MPTCP support is not available" + exit ${KSFT_SKIP} + fi +} + +mptcp_lib_check_kallsyms() { + if ! mptcp_lib_has_file "/proc/kallsyms"; then + echo "SKIP: CONFIG_KALLSYMS is missing" + exit ${KSFT_SKIP} + fi +} + +# Internal: use mptcp_lib_kallsyms_has() instead +__mptcp_lib_kallsyms_has() { + local sym="${1}" + + mptcp_lib_check_kallsyms + + grep -q " ${sym}" /proc/kallsyms +} + +# $1: part of a symbol to look at, add '$' at the end for full name +mptcp_lib_kallsyms_has() { + local sym="${1}" + + if __mptcp_lib_kallsyms_has "${sym}"; then + return 0 + fi + + mptcp_lib_fail_if_expected_feature "${sym} symbol not found" +} + +# $1: part of a symbol to look at, add '$' at the end for full name +mptcp_lib_kallsyms_doesnt_have() { + local sym="${1}" + + if ! __mptcp_lib_kallsyms_has "${sym}"; then + return 0 + fi + + mptcp_lib_fail_if_expected_feature "${sym} symbol has been found" +} + +# !!!AVOID USING THIS!!! +# Features might not land in the expected version and features can be backported +# +# $1: kernel version, e.g. 6.3 +mptcp_lib_kversion_ge() { + local exp_maj="${1%.*}" + local exp_min="${1#*.}" + local v maj min + + # If the kernel has backported features, set this env var to 1: + if [ "${SELFTESTS_MPTCP_LIB_NO_KVERSION_CHECK:-}" = "1" ]; then + return 0 + fi + + v=$(uname -r | cut -d'.' -f1,2) + maj=${v%.*} + min=${v#*.} + + if [ "${maj}" -gt "${exp_maj}" ] || + { [ "${maj}" -eq "${exp_maj}" ] && [ "${min}" -ge "${exp_min}" ]; }; then + return 0 + fi + + mptcp_lib_fail_if_expected_feature "kernel version ${1} lower than ${v}" +} diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c index ae61f39556ca..926b0be87c99 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c +++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c @@ -51,6 +51,11 @@ struct mptcp_info { __u8 mptcpi_local_addr_used; __u8 mptcpi_local_addr_max; __u8 mptcpi_csum_enabled; + __u32 mptcpi_retransmits; + __u64 mptcpi_bytes_retrans; + __u64 mptcpi_bytes_sent; + __u64 mptcpi_bytes_received; + __u64 mptcpi_bytes_acked; }; struct mptcp_subflow_data { @@ -81,12 +86,47 @@ struct mptcp_subflow_addrs { #define MPTCP_SUBFLOW_ADDRS 3 #endif +#ifndef MPTCP_FULL_INFO +struct mptcp_subflow_info { + __u32 id; + struct mptcp_subflow_addrs addrs; +}; + +struct mptcp_full_info { + __u32 size_tcpinfo_kernel; /* must be 0, set by kernel */ + __u32 size_tcpinfo_user; + __u32 size_sfinfo_kernel; /* must be 0, set by kernel */ + __u32 size_sfinfo_user; + __u32 num_subflows; /* must be 0, set by kernel (real subflow count) */ + __u32 size_arrays_user; /* max subflows that userspace is interested in; + * the buffers at subflow_info/tcp_info + * are respectively at least: + * size_arrays * size_sfinfo_user + * size_arrays * size_tcpinfo_user + * bytes wide + */ + __aligned_u64 subflow_info; + __aligned_u64 tcp_info; + struct mptcp_info mptcp_info; +}; + +#define MPTCP_FULL_INFO 4 +#endif + struct so_state { struct mptcp_info mi; + struct mptcp_info last_sample; + struct tcp_info tcp_info; + struct mptcp_subflow_addrs addrs; uint64_t mptcpi_rcv_delta; uint64_t tcpi_rcv_delta; + bool pkt_stats_avail; }; +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + static void die_perror(const char *msg) { perror(msg); @@ -318,8 +358,9 @@ static void do_getsockopt_mptcp_info(struct so_state *s, int fd, size_t w) if (ret < 0) die_perror("getsockopt MPTCP_INFO"); - assert(olen == sizeof(i)); + s->pkt_stats_avail = olen >= sizeof(i); + s->last_sample = i; if (s->mi.mptcpi_write_seq == 0) s->mi = i; @@ -349,13 +390,16 @@ static void do_getsockopt_tcp_info(struct so_state *s, int fd, size_t r, size_t xerror("getsockopt MPTCP_TCPINFO (tries %d, %m)"); assert(olen <= sizeof(ti)); - assert(ti.d.size_user == ti.d.size_kernel); - assert(ti.d.size_user == sizeof(struct tcp_info)); + assert(ti.d.size_kernel > 0); + assert(ti.d.size_user == + MIN(ti.d.size_kernel, sizeof(struct tcp_info))); assert(ti.d.num_subflows == 1); assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data)); olen -= sizeof(struct mptcp_subflow_data); - assert(olen == sizeof(struct tcp_info)); + assert(olen == ti.d.size_user); + + s->tcp_info = ti.ti[0]; if (ti.ti[0].tcpi_bytes_sent == w && ti.ti[0].tcpi_bytes_received == r) @@ -378,7 +422,7 @@ done: do_getsockopt_bogus_sf_data(fd, MPTCP_TCPINFO); } -static void do_getsockopt_subflow_addrs(int fd) +static void do_getsockopt_subflow_addrs(struct so_state *s, int fd) { struct sockaddr_storage remote, local; socklen_t olen, rlen, llen; @@ -401,13 +445,14 @@ static void do_getsockopt_subflow_addrs(int fd) die_perror("getsockopt MPTCP_SUBFLOW_ADDRS"); assert(olen <= sizeof(addrs)); - assert(addrs.d.size_user == addrs.d.size_kernel); - assert(addrs.d.size_user == sizeof(struct mptcp_subflow_addrs)); + assert(addrs.d.size_kernel > 0); + assert(addrs.d.size_user == + MIN(addrs.d.size_kernel, sizeof(struct mptcp_subflow_addrs))); assert(addrs.d.num_subflows == 1); assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data)); olen -= sizeof(struct mptcp_subflow_data); - assert(olen == sizeof(struct mptcp_subflow_addrs)); + assert(olen == addrs.d.size_user); llen = sizeof(local); ret = getsockname(fd, (struct sockaddr *)&local, &llen); @@ -425,6 +470,7 @@ static void do_getsockopt_subflow_addrs(int fd) assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) == 0); assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) == 0); + s->addrs = addrs.addr[0]; memset(&addrs, 0, sizeof(addrs)); @@ -445,13 +491,70 @@ static void do_getsockopt_subflow_addrs(int fd) do_getsockopt_bogus_sf_data(fd, MPTCP_SUBFLOW_ADDRS); } +static void do_getsockopt_mptcp_full_info(struct so_state *s, int fd) +{ + size_t data_size = sizeof(struct mptcp_full_info); + struct mptcp_subflow_info sfinfo[2]; + struct tcp_info tcp_info[2]; + struct mptcp_full_info mfi; + socklen_t olen; + int ret; + + memset(&mfi, 0, data_size); + memset(tcp_info, 0, sizeof(tcp_info)); + memset(sfinfo, 0, sizeof(sfinfo)); + + mfi.size_tcpinfo_user = sizeof(struct tcp_info); + mfi.size_sfinfo_user = sizeof(struct mptcp_subflow_info); + mfi.size_arrays_user = 2; + mfi.subflow_info = (unsigned long)&sfinfo[0]; + mfi.tcp_info = (unsigned long)&tcp_info[0]; + olen = data_size; + + ret = getsockopt(fd, SOL_MPTCP, MPTCP_FULL_INFO, &mfi, &olen); + if (ret < 0) { + if (errno == EOPNOTSUPP) { + perror("MPTCP_FULL_INFO test skipped"); + return; + } + xerror("getsockopt MPTCP_FULL_INFO"); + } + + assert(olen <= data_size); + assert(mfi.size_tcpinfo_kernel > 0); + assert(mfi.size_tcpinfo_user == + MIN(mfi.size_tcpinfo_kernel, sizeof(struct tcp_info))); + assert(mfi.size_sfinfo_kernel > 0); + assert(mfi.size_sfinfo_user == + MIN(mfi.size_sfinfo_kernel, sizeof(struct mptcp_subflow_info))); + assert(mfi.num_subflows == 1); + + /* Tolerate future extension to mptcp_info struct and running newer + * test on top of older kernel. + * Anyway any kernel supporting MPTCP_FULL_INFO must at least include + * the following in mptcp_info. + */ + assert(olen > (socklen_t)__builtin_offsetof(struct mptcp_full_info, tcp_info)); + assert(mfi.mptcp_info.mptcpi_subflows == 0); + assert(mfi.mptcp_info.mptcpi_bytes_sent == s->last_sample.mptcpi_bytes_sent); + assert(mfi.mptcp_info.mptcpi_bytes_received == s->last_sample.mptcpi_bytes_received); + + assert(sfinfo[0].id == 1); + assert(tcp_info[0].tcpi_bytes_sent == s->tcp_info.tcpi_bytes_sent); + assert(tcp_info[0].tcpi_bytes_received == s->tcp_info.tcpi_bytes_received); + assert(!memcmp(&sfinfo->addrs, &s->addrs, sizeof(struct mptcp_subflow_addrs))); +} + static void do_getsockopts(struct so_state *s, int fd, size_t r, size_t w) { do_getsockopt_mptcp_info(s, fd, w); do_getsockopt_tcp_info(s, fd, r, w); - do_getsockopt_subflow_addrs(fd); + do_getsockopt_subflow_addrs(s, fd); + + if (r) + do_getsockopt_mptcp_full_info(s, fd); } static void connect_one_server(int fd, int pipefd) @@ -556,6 +659,23 @@ static void process_one_client(int fd, int pipefd) do_getsockopts(&s, fd, ret, ret2); if (s.mptcpi_rcv_delta != (uint64_t)ret + 1) xerror("mptcpi_rcv_delta %" PRIu64 ", expect %" PRIu64, s.mptcpi_rcv_delta, ret + 1, s.mptcpi_rcv_delta - ret); + + /* be nice when running on top of older kernel */ + if (s.pkt_stats_avail) { + if (s.last_sample.mptcpi_bytes_sent != ret2) + xerror("mptcpi_bytes_sent %" PRIu64 ", expect %" PRIu64, + s.last_sample.mptcpi_bytes_sent, ret2, + s.last_sample.mptcpi_bytes_sent - ret2); + if (s.last_sample.mptcpi_bytes_received != ret) + xerror("mptcpi_bytes_received %" PRIu64 ", expect %" PRIu64, + s.last_sample.mptcpi_bytes_received, ret, + s.last_sample.mptcpi_bytes_received - ret); + if (s.last_sample.mptcpi_bytes_acked != ret) + xerror("mptcpi_bytes_acked %" PRIu64 ", expect %" PRIu64, + s.last_sample.mptcpi_bytes_acked, ret2, + s.last_sample.mptcpi_bytes_acked - ret2); + } + close(fd); } diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh b/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh index 1b70c0a304ce..dc8d473fc82c 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh @@ -1,6 +1,8 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +. "$(dirname "${0}")/mptcp_lib.sh" + ret=0 sin="" sout="" @@ -10,6 +12,8 @@ ksft_skip=4 timeout_poll=30 timeout_test=$((timeout_poll * 2 + 1)) mptcp_connect="" +iptables="iptables" +ip6tables="ip6tables" sec=$(date +%s) rndh=$(printf %x $sec)-$(mktemp -u XXXXXX) @@ -23,7 +27,7 @@ add_mark_rules() local m=$2 local t - for t in iptables ip6tables; do + for t in ${iptables} ${ip6tables}; do # just to debug: check we have multiple subflows connection requests ip netns exec $ns $t -A OUTPUT -p tcp --syn -m mark --mark $m -j ACCEPT @@ -84,20 +88,23 @@ cleanup() rm -f "$sin" "$sout" } +mptcp_lib_check_mptcp +mptcp_lib_check_kallsyms + ip -Version > /dev/null 2>&1 if [ $? -ne 0 ];then echo "SKIP: Could not run test without ip tool" exit $ksft_skip fi -iptables -V > /dev/null 2>&1 -if [ $? -ne 0 ];then +# Use the legacy version if available to support old kernel versions +if iptables-legacy -V &> /dev/null; then + iptables="iptables-legacy" + ip6tables="ip6tables-legacy" +elif ! iptables -V &> /dev/null; then echo "SKIP: Could not run all tests without iptables tool" exit $ksft_skip -fi - -ip6tables -V > /dev/null 2>&1 -if [ $? -ne 0 ];then +elif ! ip6tables -V &> /dev/null; then echo "SKIP: Could not run all tests without ip6tables tool" exit $ksft_skip fi @@ -107,10 +114,10 @@ check_mark() local ns=$1 local af=$2 - local tables=iptables + local tables=${iptables} if [ $af -eq 6 ];then - tables=ip6tables + tables=${ip6tables} fi local counters values @@ -121,6 +128,7 @@ check_mark() for v in $values; do if [ $v -ne 0 ]; then echo "FAIL: got $tables $values in ns $ns , not 0 - not all expected packets marked" 1>&2 + ret=1 return 1 fi done @@ -182,9 +190,14 @@ do_transfer() local_addr="0.0.0.0" fi + cmsg="TIMESTAMPNS" + if mptcp_lib_kallsyms_has "mptcp_ioctl$"; then + cmsg+=",TCPINQ" + fi + timeout ${timeout_test} \ ip netns exec ${listener_ns} \ - $mptcp_connect -t ${timeout_poll} -l -M 1 -p $port -s ${srv_proto} -c TIMESTAMPNS,TCPINQ \ + $mptcp_connect -t ${timeout_poll} -l -M 1 -p $port -s ${srv_proto} -c "${cmsg}" \ ${local_addr} < "$sin" > "$sout" & local spid=$! @@ -192,7 +205,7 @@ do_transfer() timeout ${timeout_test} \ ip netns exec ${connector_ns} \ - $mptcp_connect -t ${timeout_poll} -M 2 -p $port -s ${cl_proto} -c TIMESTAMPNS,TCPINQ \ + $mptcp_connect -t ${timeout_poll} -M 2 -p $port -s ${cl_proto} -c "${cmsg}" \ $connect_addr < "$cin" > "$cout" & local cpid=$! @@ -215,11 +228,11 @@ do_transfer() fi if [ $local_addr = "::" ];then - check_mark $listener_ns 6 - check_mark $connector_ns 6 + check_mark $listener_ns 6 || retc=1 + check_mark $connector_ns 6 || retc=1 else - check_mark $listener_ns 4 - check_mark $connector_ns 4 + check_mark $listener_ns 4 || retc=1 + check_mark $connector_ns 4 || retc=1 fi check_transfer $cin $sout "file received by server" @@ -249,6 +262,11 @@ do_mptcp_sockopt_tests() { local lret=0 + if ! mptcp_lib_kallsyms_has "mptcp_diag_fill_info$"; then + echo "INFO: MPTCP sockopt not supported: SKIP" + return + fi + ip netns exec "$ns_sbox" ./mptcp_sockopt lret=$? @@ -303,6 +321,11 @@ do_tcpinq_tests() { local lret=0 + if ! mptcp_lib_kallsyms_has "mptcp_ioctl$"; then + echo "INFO: TCP_INQ not supported: SKIP" + return + fi + local args for args in "-t tcp" "-r tcp"; do do_tcpinq_test $args diff --git a/tools/testing/selftests/net/mptcp/pm_netlink.sh b/tools/testing/selftests/net/mptcp/pm_netlink.sh index 89839d1ff9d8..d02e0d63a8f9 100755 --- a/tools/testing/selftests/net/mptcp/pm_netlink.sh +++ b/tools/testing/selftests/net/mptcp/pm_netlink.sh @@ -1,6 +1,8 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +. "$(dirname "${0}")/mptcp_lib.sh" + ksft_skip=4 ret=0 @@ -34,6 +36,8 @@ cleanup() ip netns del $ns1 } +mptcp_lib_check_mptcp + ip -Version > /dev/null 2>&1 if [ $? -ne 0 ];then echo "SKIP: Could not run test without ip tool" @@ -69,8 +73,12 @@ check() } check "ip netns exec $ns1 ./pm_nl_ctl dump" "" "defaults addr list" -check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0 + +default_limits="$(ip netns exec $ns1 ./pm_nl_ctl limits)" +if mptcp_lib_expect_all_features; then + check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0 subflows 2" "defaults limits" +fi ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.1 ip netns exec $ns1 ./pm_nl_ctl add 10.0.1.2 flags subflow dev lo @@ -117,12 +125,10 @@ ip netns exec $ns1 ./pm_nl_ctl flush check "ip netns exec $ns1 ./pm_nl_ctl dump" "" "flush addrs" ip netns exec $ns1 ./pm_nl_ctl limits 9 1 -check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0 -subflows 2" "rcv addrs above hard limit" +check "ip netns exec $ns1 ./pm_nl_ctl limits" "$default_limits" "rcv addrs above hard limit" ip netns exec $ns1 ./pm_nl_ctl limits 1 9 -check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 0 -subflows 2" "subflows above hard limit" +check "ip netns exec $ns1 ./pm_nl_ctl limits" "$default_limits" "subflows above hard limit" ip netns exec $ns1 ./pm_nl_ctl limits 8 8 check "ip netns exec $ns1 ./pm_nl_ctl limits" "accept 8 @@ -172,14 +178,19 @@ subflow,backup 10.0.1.1" "set flags (backup)" ip netns exec $ns1 ./pm_nl_ctl set 10.0.1.1 flags nobackup check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ subflow 10.0.1.1" " (nobackup)" + +# fullmesh support has been added later ip netns exec $ns1 ./pm_nl_ctl set id 1 flags fullmesh -check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ +if ip netns exec $ns1 ./pm_nl_ctl dump | grep -q "fullmesh" || + mptcp_lib_expect_all_features; then + check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ subflow,fullmesh 10.0.1.1" " (fullmesh)" -ip netns exec $ns1 ./pm_nl_ctl set id 1 flags nofullmesh -check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ + ip netns exec $ns1 ./pm_nl_ctl set id 1 flags nofullmesh + check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ subflow 10.0.1.1" " (nofullmesh)" -ip netns exec $ns1 ./pm_nl_ctl set id 1 flags backup,fullmesh -check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ + ip netns exec $ns1 ./pm_nl_ctl set id 1 flags backup,fullmesh + check "ip netns exec $ns1 ./pm_nl_ctl dump" "id 1 flags \ subflow,backup,fullmesh 10.0.1.1" " (backup,fullmesh)" +fi exit $ret diff --git a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c index abddf4c63e79..1887bd61bd9a 100644 --- a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c +++ b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c @@ -425,7 +425,7 @@ int dsf(int fd, int pm_family, int argc, char *argv[]) } /* token */ - token = atoi(params[4]); + token = strtoul(params[4], NULL, 10); rta = (void *)(data + off); rta->rta_type = MPTCP_PM_ATTR_TOKEN; rta->rta_len = RTA_LENGTH(4); @@ -551,7 +551,7 @@ int csf(int fd, int pm_family, int argc, char *argv[]) } /* token */ - token = atoi(params[4]); + token = strtoul(params[4], NULL, 10); rta = (void *)(data + off); rta->rta_type = MPTCP_PM_ATTR_TOKEN; rta->rta_len = RTA_LENGTH(4); @@ -598,7 +598,7 @@ int remove_addr(int fd, int pm_family, int argc, char *argv[]) if (++arg >= argc) error(1, 0, " missing token value"); - token = atoi(argv[arg]); + token = strtoul(argv[arg], NULL, 10); rta = (void *)(data + off); rta->rta_type = MPTCP_PM_ATTR_TOKEN; rta->rta_len = RTA_LENGTH(4); @@ -710,7 +710,7 @@ int announce_addr(int fd, int pm_family, int argc, char *argv[]) if (++arg >= argc) error(1, 0, " missing token value"); - token = atoi(argv[arg]); + token = strtoul(argv[arg], NULL, 10); } else error(1, 0, "unknown keyword %s", argv[arg]); } @@ -1347,7 +1347,7 @@ int set_flags(int fd, int pm_family, int argc, char *argv[]) error(1, 0, " missing token value"); /* token */ - token = atoi(argv[arg]); + token = strtoul(argv[arg], NULL, 10); } else if (!strcmp(argv[arg], "flags")) { char *tok, *str; diff --git a/tools/testing/selftests/net/mptcp/simult_flows.sh b/tools/testing/selftests/net/mptcp/simult_flows.sh index 9f22f7e5027d..36a3c9d92e20 100755 --- a/tools/testing/selftests/net/mptcp/simult_flows.sh +++ b/tools/testing/selftests/net/mptcp/simult_flows.sh @@ -1,6 +1,8 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +. "$(dirname "${0}")/mptcp_lib.sh" + sec=$(date +%s) rndh=$(printf %x $sec)-$(mktemp -u XXXXXX) ns1="ns1-$rndh" @@ -34,6 +36,8 @@ cleanup() done } +mptcp_lib_check_mptcp + ip -Version > /dev/null 2>&1 if [ $? -ne 0 ];then echo "SKIP: Could not run test without ip tool" diff --git a/tools/testing/selftests/net/mptcp/userspace_pm.sh b/tools/testing/selftests/net/mptcp/userspace_pm.sh index b1eb7bce599d..b180133a30af 100755 --- a/tools/testing/selftests/net/mptcp/userspace_pm.sh +++ b/tools/testing/selftests/net/mptcp/userspace_pm.sh @@ -1,10 +1,20 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +. "$(dirname "${0}")/mptcp_lib.sh" + +mptcp_lib_check_mptcp +mptcp_lib_check_kallsyms + +if ! mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then + echo "userspace pm tests are not supported by the kernel: SKIP" + exit ${KSFT_SKIP} +fi + ip -Version > /dev/null 2>&1 if [ $? -ne 0 ];then echo "SKIP: Cannot not run test without ip tool" - exit 1 + exit ${KSFT_SKIP} fi ANNOUNCED=6 # MPTCP_EVENT_ANNOUNCED @@ -413,6 +423,7 @@ test_remove() stdbuf -o0 -e0 printf "[OK]\n" else stdbuf -o0 -e0 printf "[FAIL]\n" + exit 1 fi # RM_ADDR using an invalid addr id should result in no action @@ -427,6 +438,7 @@ test_remove() stdbuf -o0 -e0 printf "[OK]\n" else stdbuf -o0 -e0 printf "[FAIL]\n" + exit 1 fi # RM_ADDR from the client to server machine @@ -838,7 +850,7 @@ test_prio() local count # Send MP_PRIO signal from client to server machine - ip netns exec "$ns2" ./pm_nl_ctl set 10.0.1.2 port "$client4_port" flags backup token "$client4_token" rip 10.0.1.1 rport "$server4_port" + ip netns exec "$ns2" ./pm_nl_ctl set 10.0.1.2 port "$client4_port" flags backup token "$client4_token" rip 10.0.1.1 rport "$app4_port" sleep 0.5 # Check TX @@ -905,6 +917,11 @@ test_listener() { print_title "Listener tests" + if ! mptcp_lib_kallsyms_has "mptcp_event_pm_listener$"; then + stdbuf -o0 -e0 printf "LISTENER events \t[SKIP] Not supported\n" + return + fi + # Capture events on the network namespace running the client :>$client_evts diff --git a/tools/testing/selftests/net/nettest.c b/tools/testing/selftests/net/nettest.c index ee9a72982705..39a0e01f8554 100644 --- a/tools/testing/selftests/net/nettest.c +++ b/tools/testing/selftests/net/nettest.c @@ -76,7 +76,9 @@ struct sock_args { has_grp:1, has_expected_laddr:1, has_expected_raddr:1, - bind_test_only:1; + bind_test_only:1, + client_dontroute:1, + server_dontroute:1; unsigned short port; @@ -611,6 +613,18 @@ static int set_dsfield(int sd, int version, int dsfield) return 0; } +static int set_dontroute(int sd) +{ + unsigned int one = 1; + + if (setsockopt(sd, SOL_SOCKET, SO_DONTROUTE, &one, sizeof(one)) < 0) { + log_err_errno("setsockopt(SO_DONTROUTE)"); + return -1; + } + + return 0; +} + static int str_to_uint(const char *str, int min, int max, unsigned int *value) { int number; @@ -1351,6 +1365,14 @@ static int msock_init(struct sock_args *args, int server) if (set_dsfield(sd, AF_INET, args->dsfield) != 0) goto out_err; + if (server) { + if (args->server_dontroute && set_dontroute(sd) != 0) + goto out_err; + } else { + if (args->client_dontroute && set_dontroute(sd) != 0) + goto out_err; + } + if (args->dev && bind_to_device(sd, args->dev) != 0) goto out_err; else if (args->use_setsockopt && @@ -1482,6 +1504,9 @@ static int lsock_init(struct sock_args *args) if (set_dsfield(sd, args->version, args->dsfield) != 0) goto err; + if (args->server_dontroute && set_dontroute(sd) != 0) + goto err; + if (args->dev && bind_to_device(sd, args->dev) != 0) goto err; else if (args->use_setsockopt && @@ -1698,6 +1723,9 @@ static int connectsock(void *addr, socklen_t alen, struct sock_args *args) if (set_dsfield(sd, args->version, args->dsfield) != 0) goto err; + if (args->client_dontroute && set_dontroute(sd) != 0) + goto err; + if (args->dev && bind_to_device(sd, args->dev) != 0) goto err; else if (args->use_setsockopt && @@ -1905,10 +1933,14 @@ static int ipc_parent(int cpid, int fd, struct sock_args *args) #define GETOPT_STR "sr:l:c:Q:p:t:g:P:DRn:M:X:m:d:I:BN:O:SUCi6xL:0:1:2:3:Fbqf" #define OPT_FORCE_BIND_KEY_IFINDEX 1001 #define OPT_NO_BIND_KEY_IFINDEX 1002 +#define OPT_CLIENT_DONTROUTE 1003 +#define OPT_SERVER_DONTROUTE 1004 static struct option long_opts[] = { {"force-bind-key-ifindex", 0, 0, OPT_FORCE_BIND_KEY_IFINDEX}, {"no-bind-key-ifindex", 0, 0, OPT_NO_BIND_KEY_IFINDEX}, + {"client-dontroute", 0, 0, OPT_CLIENT_DONTROUTE}, + {"server-dontroute", 0, 0, OPT_SERVER_DONTROUTE}, {0, 0, 0, 0} }; @@ -1954,6 +1986,12 @@ static void print_usage(char *prog) " --no-bind-key-ifindex: Force TCP_MD5SIG_FLAG_IFINDEX off\n" " --force-bind-key-ifindex: Force TCP_MD5SIG_FLAG_IFINDEX on\n" " (default: only if -I is passed)\n" + " --client-dontroute: don't use gateways for client socket: send\n" + " packets only if destination is on link (see\n" + " SO_DONTROUTE in socket(7))\n" + " --server-dontroute: don't use gateways for server socket: send\n" + " packets only if destination is on link (see\n" + " SO_DONTROUTE in socket(7))\n" "\n" " -g grp multicast group (e.g., 239.1.1.1)\n" " -i interactive mode (default is echo and terminate)\n" @@ -2076,6 +2114,12 @@ int main(int argc, char *argv[]) case OPT_NO_BIND_KEY_IFINDEX: args.bind_key_ifindex = -1; break; + case OPT_CLIENT_DONTROUTE: + args.client_dontroute = 1; + break; + case OPT_SERVER_DONTROUTE: + args.server_dontroute = 1; + break; case 'X': args.client_pw = optarg; break; diff --git a/tools/testing/selftests/net/rtnetlink.sh b/tools/testing/selftests/net/rtnetlink.sh index 383ac6fc037d..ba286d680fd9 100755 --- a/tools/testing/selftests/net/rtnetlink.sh +++ b/tools/testing/selftests/net/rtnetlink.sh @@ -860,6 +860,7 @@ EOF fi # clean up any leftovers + echo 0 > /sys/bus/netdevsim/del_device $probed && rmmod netdevsim if [ $ret -ne 0 ]; then diff --git a/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh b/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh new file mode 100755 index 000000000000..f75212bf142c --- /dev/null +++ b/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh @@ -0,0 +1,240 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# This test is for checking the [no]localbypass VXLAN device option. The test +# configures two VXLAN devices in the same network namespace and a tc filter on +# the loopback device that drops encapsulated packets. The test sends packets +# from the first VXLAN device and verifies that by default these packets are +# received by the second VXLAN device. The test then enables the nolocalbypass +# option and verifies that packets are no longer received by the second VXLAN +# device. + +ret=0 +# Kselftest framework requirement - SKIP code is 4. +ksft_skip=4 + +TESTS=" + nolocalbypass +" +VERBOSE=0 +PAUSE_ON_FAIL=no +PAUSE=no + +################################################################################ +# Utilities + +log_test() +{ + local rc=$1 + local expected=$2 + local msg="$3" + + if [ ${rc} -eq ${expected} ]; then + printf "TEST: %-60s [ OK ]\n" "${msg}" + nsuccess=$((nsuccess+1)) + else + ret=1 + nfail=$((nfail+1)) + printf "TEST: %-60s [FAIL]\n" "${msg}" + if [ "$VERBOSE" = "1" ]; then + echo " rc=$rc, expected $expected" + fi + + if [ "${PAUSE_ON_FAIL}" = "yes" ]; then + echo + echo "hit enter to continue, 'q' to quit" + read a + [ "$a" = "q" ] && exit 1 + fi + fi + + if [ "${PAUSE}" = "yes" ]; then + echo + echo "hit enter to continue, 'q' to quit" + read a + [ "$a" = "q" ] && exit 1 + fi + + [ "$VERBOSE" = "1" ] && echo +} + +run_cmd() +{ + local cmd="$1" + local out + local stderr="2>/dev/null" + + if [ "$VERBOSE" = "1" ]; then + printf "COMMAND: $cmd\n" + stderr= + fi + + out=$(eval $cmd $stderr) + rc=$? + if [ "$VERBOSE" = "1" -a -n "$out" ]; then + echo " $out" + fi + + return $rc +} + +tc_check_packets() +{ + local ns=$1; shift + local id=$1; shift + local handle=$1; shift + local count=$1; shift + local pkts + + sleep 0.1 + pkts=$(tc -n $ns -j -s filter show $id \ + | jq ".[] | select(.options.handle == $handle) | \ + .options.actions[0].stats.packets") + [[ $pkts == $count ]] +} + +################################################################################ +# Setup + +setup() +{ + ip netns add ns1 + + ip -n ns1 link set dev lo up + ip -n ns1 address add 192.0.2.1/32 dev lo + ip -n ns1 address add 198.51.100.1/32 dev lo + + ip -n ns1 link add name vx0 up type vxlan id 100 local 198.51.100.1 \ + dstport 4789 nolearning + ip -n ns1 link add name vx1 up type vxlan id 100 dstport 4790 +} + +cleanup() +{ + ip netns del ns1 &> /dev/null +} + +################################################################################ +# Tests + +nolocalbypass() +{ + local smac=00:01:02:03:04:05 + local dmac=00:0a:0b:0c:0d:0e + + run_cmd "bridge -n ns1 fdb add $dmac dev vx0 self static dst 192.0.2.1 port 4790" + + run_cmd "tc -n ns1 qdisc add dev vx1 clsact" + run_cmd "tc -n ns1 filter add dev vx1 ingress pref 1 handle 101 proto all flower src_mac $smac dst_mac $dmac action pass" + + run_cmd "tc -n ns1 qdisc add dev lo clsact" + run_cmd "tc -n ns1 filter add dev lo ingress pref 1 handle 101 proto ip flower ip_proto udp dst_port 4790 action drop" + + run_cmd "ip -n ns1 -d -j link show dev vx0 | jq -e '.[][\"linkinfo\"][\"info_data\"][\"localbypass\"] == true'" + log_test $? 0 "localbypass enabled" + + run_cmd "ip netns exec ns1 mausezahn vx0 -a $smac -b $dmac -c 1 -p 100 -q" + + tc_check_packets "ns1" "dev vx1 ingress" 101 1 + log_test $? 0 "Packet received by local VXLAN device - localbypass" + + run_cmd "ip -n ns1 link set dev vx0 type vxlan nolocalbypass" + + run_cmd "ip -n ns1 -d -j link show dev vx0 | jq -e '.[][\"linkinfo\"][\"info_data\"][\"localbypass\"] == false'" + log_test $? 0 "localbypass disabled" + + run_cmd "ip netns exec ns1 mausezahn vx0 -a $smac -b $dmac -c 1 -p 100 -q" + + tc_check_packets "ns1" "dev vx1 ingress" 101 1 + log_test $? 0 "Packet not received by local VXLAN device - nolocalbypass" + + run_cmd "ip -n ns1 link set dev vx0 type vxlan localbypass" + + run_cmd "ip -n ns1 -d -j link show dev vx0 | jq -e '.[][\"linkinfo\"][\"info_data\"][\"localbypass\"] == true'" + log_test $? 0 "localbypass enabled" + + run_cmd "ip netns exec ns1 mausezahn vx0 -a $smac -b $dmac -c 1 -p 100 -q" + + tc_check_packets "ns1" "dev vx1 ingress" 101 2 + log_test $? 0 "Packet received by local VXLAN device - localbypass" +} + +################################################################################ +# Usage + +usage() +{ + cat <<EOF +usage: ${0##*/} OPTS + + -t <test> Test(s) to run (default: all) + (options: $TESTS) + -p Pause on fail + -P Pause after each test before cleanup + -v Verbose mode (show commands and output) +EOF +} + +################################################################################ +# Main + +trap cleanup EXIT + +while getopts ":t:pPvh" opt; do + case $opt in + t) TESTS=$OPTARG ;; + p) PAUSE_ON_FAIL=yes;; + P) PAUSE=yes;; + v) VERBOSE=$(($VERBOSE + 1));; + h) usage; exit 0;; + *) usage; exit 1;; + esac +done + +# Make sure we don't pause twice. +[ "${PAUSE}" = "yes" ] && PAUSE_ON_FAIL=no + +if [ "$(id -u)" -ne 0 ];then + echo "SKIP: Need root privileges" + exit $ksft_skip; +fi + +if [ ! -x "$(command -v ip)" ]; then + echo "SKIP: Could not run test without ip tool" + exit $ksft_skip +fi + +if [ ! -x "$(command -v bridge)" ]; then + echo "SKIP: Could not run test without bridge tool" + exit $ksft_skip +fi + +if [ ! -x "$(command -v mausezahn)" ]; then + echo "SKIP: Could not run test without mausezahn tool" + exit $ksft_skip +fi + +if [ ! -x "$(command -v jq)" ]; then + echo "SKIP: Could not run test without jq tool" + exit $ksft_skip +fi + +ip link help vxlan 2>&1 | grep -q "localbypass" +if [ $? -ne 0 ]; then + echo "SKIP: iproute2 ip too old, missing VXLAN nolocalbypass support" + exit $ksft_skip +fi + +cleanup + +for t in $TESTS +do + setup; $t; cleanup; +done + +if [ "$TESTS" != "none" ]; then + printf "\nTests passed: %3d\n" ${nsuccess} + printf "Tests failed: %3d\n" ${nfail} +fi + +exit $ret diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c index e699548d4247..a3c57004344c 100644 --- a/tools/testing/selftests/net/tls.c +++ b/tools/testing/selftests/net/tls.c @@ -15,6 +15,7 @@ #include <linux/tcp.h> #include <linux/socket.h> +#include <sys/epoll.h> #include <sys/types.h> #include <sys/sendfile.h> #include <sys/socket.h> @@ -25,6 +26,8 @@ #define TLS_PAYLOAD_MAX_LEN 16384 #define SOL_TLS 282 +static int fips_enabled; + struct tls_crypto_info_keys { union { struct tls12_crypto_info_aes_gcm_128 aes128; @@ -235,7 +238,7 @@ FIXTURE_VARIANT(tls) { uint16_t tls_version; uint16_t cipher_type; - bool nopad; + bool nopad, fips_non_compliant; }; FIXTURE_VARIANT_ADD(tls, 12_aes_gcm) @@ -254,24 +257,28 @@ FIXTURE_VARIANT_ADD(tls, 12_chacha) { .tls_version = TLS_1_2_VERSION, .cipher_type = TLS_CIPHER_CHACHA20_POLY1305, + .fips_non_compliant = true, }; FIXTURE_VARIANT_ADD(tls, 13_chacha) { .tls_version = TLS_1_3_VERSION, .cipher_type = TLS_CIPHER_CHACHA20_POLY1305, + .fips_non_compliant = true, }; FIXTURE_VARIANT_ADD(tls, 13_sm4_gcm) { .tls_version = TLS_1_3_VERSION, .cipher_type = TLS_CIPHER_SM4_GCM, + .fips_non_compliant = true, }; FIXTURE_VARIANT_ADD(tls, 13_sm4_ccm) { .tls_version = TLS_1_3_VERSION, .cipher_type = TLS_CIPHER_SM4_CCM, + .fips_non_compliant = true, }; FIXTURE_VARIANT_ADD(tls, 12_aes_ccm) @@ -311,6 +318,9 @@ FIXTURE_SETUP(tls) int one = 1; int ret; + if (fips_enabled && variant->fips_non_compliant) + SKIP(return, "Unsupported cipher in FIPS mode"); + tls_crypto_info_init(variant->tls_version, variant->cipher_type, &tls12); @@ -1637,6 +1647,136 @@ TEST_F(tls_err, timeo) } } +TEST_F(tls_err, poll_partial_rec) +{ + struct pollfd pfd = { }; + ssize_t rec_len; + char rec[256]; + char buf[128]; + + if (self->notls) + SKIP(return, "no TLS support"); + + pfd.fd = self->cfd2; + pfd.events = POLLIN; + EXPECT_EQ(poll(&pfd, 1, 1), 0); + + memrnd(buf, sizeof(buf)); + EXPECT_EQ(send(self->fd, buf, sizeof(buf), 0), sizeof(buf)); + rec_len = recv(self->cfd, rec, sizeof(rec), 0); + EXPECT_GT(rec_len, sizeof(buf)); + + /* Write 100B, not the full record ... */ + EXPECT_EQ(send(self->fd2, rec, 100, 0), 100); + /* ... no full record should mean no POLLIN */ + pfd.fd = self->cfd2; + pfd.events = POLLIN; + EXPECT_EQ(poll(&pfd, 1, 1), 0); + /* Now write the rest, and it should all pop out of the other end. */ + EXPECT_EQ(send(self->fd2, rec + 100, rec_len - 100, 0), rec_len - 100); + pfd.fd = self->cfd2; + pfd.events = POLLIN; + EXPECT_EQ(poll(&pfd, 1, 1), 1); + EXPECT_EQ(recv(self->cfd2, rec, sizeof(rec), 0), sizeof(buf)); + EXPECT_EQ(memcmp(buf, rec, sizeof(buf)), 0); +} + +TEST_F(tls_err, epoll_partial_rec) +{ + struct epoll_event ev, events[10]; + ssize_t rec_len; + char rec[256]; + char buf[128]; + int epollfd; + + if (self->notls) + SKIP(return, "no TLS support"); + + epollfd = epoll_create1(0); + ASSERT_GE(epollfd, 0); + + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN; + ev.data.fd = self->cfd2; + ASSERT_GE(epoll_ctl(epollfd, EPOLL_CTL_ADD, self->cfd2, &ev), 0); + + EXPECT_EQ(epoll_wait(epollfd, events, 10, 0), 0); + + memrnd(buf, sizeof(buf)); + EXPECT_EQ(send(self->fd, buf, sizeof(buf), 0), sizeof(buf)); + rec_len = recv(self->cfd, rec, sizeof(rec), 0); + EXPECT_GT(rec_len, sizeof(buf)); + + /* Write 100B, not the full record ... */ + EXPECT_EQ(send(self->fd2, rec, 100, 0), 100); + /* ... no full record should mean no POLLIN */ + EXPECT_EQ(epoll_wait(epollfd, events, 10, 0), 0); + /* Now write the rest, and it should all pop out of the other end. */ + EXPECT_EQ(send(self->fd2, rec + 100, rec_len - 100, 0), rec_len - 100); + EXPECT_EQ(epoll_wait(epollfd, events, 10, 0), 1); + EXPECT_EQ(recv(self->cfd2, rec, sizeof(rec), 0), sizeof(buf)); + EXPECT_EQ(memcmp(buf, rec, sizeof(buf)), 0); + + close(epollfd); +} + +TEST_F(tls_err, poll_partial_rec_async) +{ + struct pollfd pfd = { }; + ssize_t rec_len; + char rec[256]; + char buf[128]; + char token; + int p[2]; + int ret; + + if (self->notls) + SKIP(return, "no TLS support"); + + ASSERT_GE(pipe(p), 0); + + memrnd(buf, sizeof(buf)); + EXPECT_EQ(send(self->fd, buf, sizeof(buf), 0), sizeof(buf)); + rec_len = recv(self->cfd, rec, sizeof(rec), 0); + EXPECT_GT(rec_len, sizeof(buf)); + + ret = fork(); + ASSERT_GE(ret, 0); + + if (ret) { + int status, pid2; + + close(p[1]); + usleep(1000); /* Give child a head start */ + + EXPECT_EQ(send(self->fd2, rec, 100, 0), 100); + + EXPECT_EQ(read(p[0], &token, 1), 1); /* Barrier #1 */ + + EXPECT_EQ(send(self->fd2, rec + 100, rec_len - 100, 0), + rec_len - 100); + + pid2 = wait(&status); + EXPECT_EQ(pid2, ret); + EXPECT_EQ(status, 0); + } else { + close(p[0]); + + /* Child should sleep in poll(), never get a wake */ + pfd.fd = self->cfd2; + pfd.events = POLLIN; + EXPECT_EQ(poll(&pfd, 1, 5), 0); + + EXPECT_EQ(write(p[1], &token, 1), 1); /* Barrier #1 */ + + pfd.fd = self->cfd2; + pfd.events = POLLIN; + EXPECT_EQ(poll(&pfd, 1, 5), 1); + + exit(!_metadata->passed); + } +} + TEST(non_established) { struct tls12_crypto_info_aes_gcm_256 tls12; struct sockaddr_in addr; @@ -1865,4 +2005,17 @@ TEST(prequeue) { close(cfd); } +static void __attribute__((constructor)) fips_check(void) { + int res; + FILE *f; + + f = fopen("/proc/sys/crypto/fips_enabled", "r"); + if (f) { + res = fscanf(f, "%d", &fips_enabled); + if (res != 1) + ksft_print_msg("ERROR: Couldn't read /proc/sys/crypto/fips_enabled\n"); + fclose(f); + } +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/vrf-xfrm-tests.sh b/tools/testing/selftests/net/vrf-xfrm-tests.sh index 184da81f554f..452638ae8aed 100755 --- a/tools/testing/selftests/net/vrf-xfrm-tests.sh +++ b/tools/testing/selftests/net/vrf-xfrm-tests.sh @@ -264,60 +264,60 @@ setup_xfrm() ip -netns host1 xfrm state add src ${HOST1_4} dst ${HOST2_4} \ proto esp spi ${SPI_1} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_1} 96 \ - enc 'cbc(des3_ede)' ${ENC_1} \ + auth-trunc 'hmac(sha1)' ${AUTH_1} 96 \ + enc 'cbc(aes)' ${ENC_1} \ sel src ${h1_4} dst ${h2_4} ${devarg} ip -netns host2 xfrm state add src ${HOST1_4} dst ${HOST2_4} \ proto esp spi ${SPI_1} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_1} 96 \ - enc 'cbc(des3_ede)' ${ENC_1} \ + auth-trunc 'hmac(sha1)' ${AUTH_1} 96 \ + enc 'cbc(aes)' ${ENC_1} \ sel src ${h1_4} dst ${h2_4} ip -netns host1 xfrm state add src ${HOST2_4} dst ${HOST1_4} \ proto esp spi ${SPI_2} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_2} 96 \ - enc 'cbc(des3_ede)' ${ENC_2} \ + auth-trunc 'hmac(sha1)' ${AUTH_2} 96 \ + enc 'cbc(aes)' ${ENC_2} \ sel src ${h2_4} dst ${h1_4} ${devarg} ip -netns host2 xfrm state add src ${HOST2_4} dst ${HOST1_4} \ proto esp spi ${SPI_2} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_2} 96 \ - enc 'cbc(des3_ede)' ${ENC_2} \ + auth-trunc 'hmac(sha1)' ${AUTH_2} 96 \ + enc 'cbc(aes)' ${ENC_2} \ sel src ${h2_4} dst ${h1_4} ip -6 -netns host1 xfrm state add src ${HOST1_6} dst ${HOST2_6} \ proto esp spi ${SPI_1} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_1} 96 \ - enc 'cbc(des3_ede)' ${ENC_1} \ + auth-trunc 'hmac(sha1)' ${AUTH_1} 96 \ + enc 'cbc(aes)' ${ENC_1} \ sel src ${h1_6} dst ${h2_6} ${devarg} ip -6 -netns host2 xfrm state add src ${HOST1_6} dst ${HOST2_6} \ proto esp spi ${SPI_1} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_1} 96 \ - enc 'cbc(des3_ede)' ${ENC_1} \ + auth-trunc 'hmac(sha1)' ${AUTH_1} 96 \ + enc 'cbc(aes)' ${ENC_1} \ sel src ${h1_6} dst ${h2_6} ip -6 -netns host1 xfrm state add src ${HOST2_6} dst ${HOST1_6} \ proto esp spi ${SPI_2} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_2} 96 \ - enc 'cbc(des3_ede)' ${ENC_2} \ + auth-trunc 'hmac(sha1)' ${AUTH_2} 96 \ + enc 'cbc(aes)' ${ENC_2} \ sel src ${h2_6} dst ${h1_6} ${devarg} ip -6 -netns host2 xfrm state add src ${HOST2_6} dst ${HOST1_6} \ proto esp spi ${SPI_2} reqid 0 mode tunnel \ replay-window 4 replay-oseq 0x4 \ - auth-trunc 'hmac(md5)' ${AUTH_2} 96 \ - enc 'cbc(des3_ede)' ${ENC_2} \ + auth-trunc 'hmac(sha1)' ${AUTH_2} 96 \ + enc 'cbc(aes)' ${ENC_2} \ sel src ${h2_6} dst ${h1_6} } diff --git a/tools/testing/selftests/nolibc/.gitignore b/tools/testing/selftests/nolibc/.gitignore index 4696df589d68..52f613cdad54 100644 --- a/tools/testing/selftests/nolibc/.gitignore +++ b/tools/testing/selftests/nolibc/.gitignore @@ -1,4 +1,5 @@ /initramfs/ +/libc-test /nolibc-test /run.out /sysroot/ diff --git a/tools/testing/selftests/nolibc/Makefile b/tools/testing/selftests/nolibc/Makefile index bbce57420465..1b7b3c82f8ad 100644 --- a/tools/testing/selftests/nolibc/Makefile +++ b/tools/testing/selftests/nolibc/Makefile @@ -64,7 +64,7 @@ QEMU_ARGS_mips = -M malta -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_riscv = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_s390 = -M s390-ccw-virtio -m 1G -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_loongarch = -M virt -append "console=ttyS0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS = $(QEMU_ARGS_$(ARCH)) +QEMU_ARGS = $(QEMU_ARGS_$(ARCH)) $(QEMU_ARGS_EXTRA) # OUTPUT is only set when run from the main makefile, otherwise # it defaults to this nolibc directory. @@ -76,16 +76,12 @@ else Q=@ endif -CFLAGS_STACKPROTECTOR = -DNOLIBC_STACKPROTECTOR \ - $(call cc-option,-mstack-protector-guard=global) \ - $(call cc-option,-fstack-protector-all) -CFLAGS_STKP_i386 = $(CFLAGS_STACKPROTECTOR) -CFLAGS_STKP_x86_64 = $(CFLAGS_STACKPROTECTOR) -CFLAGS_STKP_x86 = $(CFLAGS_STACKPROTECTOR) CFLAGS_s390 = -m64 -CFLAGS ?= -Os -fno-ident -fno-asynchronous-unwind-tables \ +CFLAGS_mips = -EL +CFLAGS_STACKPROTECTOR ?= $(call cc-option,-mstack-protector-guard=global $(call cc-option,-fstack-protector-all)) +CFLAGS ?= -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 \ $(call cc-option,-fno-stack-protector) \ - $(CFLAGS_STKP_$(ARCH)) $(CFLAGS_$(ARCH)) + $(CFLAGS_$(ARCH)) $(CFLAGS_STACKPROTECTOR) LDFLAGS := -s help: @@ -94,6 +90,7 @@ help: @echo " help this help" @echo " sysroot create the nolibc sysroot here (uses \$$ARCH)" @echo " nolibc-test build the executable (uses \$$CC and \$$CROSS_COMPILE)" + @echo " libc-test build an executable using the compiler's default libc instead" @echo " run-user runs the executable under QEMU (uses \$$ARCH, \$$TEST)" @echo " initramfs prepare the initramfs with nolibc-test" @echo " defconfig create a fresh new default config (uses \$$ARCH)" @@ -128,10 +125,16 @@ nolibc-test: nolibc-test.c sysroot/$(ARCH)/include $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ \ -nostdlib -static -Isysroot/$(ARCH)/include $< -lgcc +libc-test: nolibc-test.c + $(QUIET_CC)$(CC) -o $@ $< + # qemu user-land test run-user: nolibc-test $(Q)qemu-$(QEMU_ARCH) ./nolibc-test > "$(CURDIR)/run.out" || : - $(Q)grep -w FAIL "$(CURDIR)/run.out" && echo "See all results in $(CURDIR)/run.out" || echo "$$(grep -c ^[0-9].*OK $(CURDIR)/run.out) test(s) passed." + $(Q)awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{f++} /\[SKIPPED\][\r]*$$/{s++} \ + END{ printf("%d test(s) passed, %d skipped, %d failed.", p, s, f); \ + if (s+f > 0) printf(" See all results in %s\n", ARGV[1]); else print; }' \ + $(CURDIR)/run.out initramfs: nolibc-test $(QUIET_MKDIR)mkdir -p initramfs @@ -147,18 +150,26 @@ kernel: initramfs # run the tests after building the kernel run: kernel $(Q)qemu-system-$(QEMU_ARCH) -display none -no-reboot -kernel "$(srctree)/$(IMAGE)" -serial stdio $(QEMU_ARGS) > "$(CURDIR)/run.out" - $(Q)grep -w FAIL "$(CURDIR)/run.out" && echo "See all results in $(CURDIR)/run.out" || echo "$$(grep -c ^[0-9].*OK $(CURDIR)/run.out) test(s) passed." + $(Q)awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{f++} /\[SKIPPED\][\r]*$$/{s++} \ + END{ printf("%d test(s) passed, %d skipped, %d failed.", p, s, f); \ + if (s+f > 0) printf(" See all results in %s\n", ARGV[1]); else print; }' \ + $(CURDIR)/run.out # re-run the tests from an existing kernel rerun: $(Q)qemu-system-$(QEMU_ARCH) -display none -no-reboot -kernel "$(srctree)/$(IMAGE)" -serial stdio $(QEMU_ARGS) > "$(CURDIR)/run.out" - $(Q)grep -w FAIL "$(CURDIR)/run.out" && echo "See all results in $(CURDIR)/run.out" || echo "$$(grep -c ^[0-9].*OK $(CURDIR)/run.out) test(s) passed." + $(Q)awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{f++} /\[SKIPPED\][\r]*$$/{s++} \ + END{ printf("%d test(s) passed, %d skipped, %d failed.", p, s, f); \ + if (s+f > 0) printf(" See all results in %s\n", ARGV[1]); else print; }' \ + $(CURDIR)/run.out clean: $(call QUIET_CLEAN, sysroot) $(Q)rm -rf sysroot $(call QUIET_CLEAN, nolibc-test) $(Q)rm -f nolibc-test + $(call QUIET_CLEAN, libc-test) + $(Q)rm -f libc-test $(call QUIET_CLEAN, initramfs) $(Q)rm -rf initramfs $(call QUIET_CLEAN, run.out) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 21bacc928bf7..486334981e60 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1,10 +1,7 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ #define _GNU_SOURCE -/* platform-specific include files coming from the compiler */ -#include <limits.h> - /* libc-specific include files * The program may be built in 3 ways: * $(CC) -nostdlib -include /path/to/nolibc.h => NOLIBC already defined @@ -20,7 +17,9 @@ #include <linux/reboot.h> #include <sys/io.h> #include <sys/ioctl.h> +#include <sys/mman.h> #include <sys/mount.h> +#include <sys/prctl.h> #include <sys/reboot.h> #include <sys/stat.h> #include <sys/syscall.h> @@ -34,7 +33,10 @@ #include <sched.h> #include <signal.h> #include <stdarg.h> +#include <stddef.h> +#include <stdint.h> #include <unistd.h> +#include <limits.h> #endif #endif @@ -43,8 +45,8 @@ char **environ; /* definition of a series of tests */ struct test { - const char *name; // test name - int (*func)(int min, int max); // handler + const char *name; /* test name */ + int (*func)(int min, int max); /* handler */ }; #ifndef _NOLIBC_STDLIB_H @@ -103,24 +105,32 @@ const char *errorname(int err) CASE_ERR(EDOM); CASE_ERR(ERANGE); CASE_ERR(ENOSYS); + CASE_ERR(EOVERFLOW); default: return itoa(err); } } +static void putcharn(char c, size_t n) +{ + char buf[64]; + + memset(buf, c, n); + buf[n] = '\0'; + fputs(buf, stdout); +} + static int pad_spc(int llen, int cnt, const char *fmt, ...) { va_list args; - int len; int ret; - for (len = 0; len < cnt - llen; len++) - putchar(' '); + putcharn(' ', cnt - llen); va_start(args, fmt); ret = vfprintf(stdout, fmt, args); va_end(args); - return ret < 0 ? ret : ret + len; + return ret < 0 ? ret : ret + cnt - llen; } /* The tests below are intended to be used by the macroes, which evaluate @@ -162,7 +172,7 @@ static int expect_eq(uint64_t expr, int llen, uint64_t val) { int ret = !(expr == val); - llen += printf(" = %lld ", expr); + llen += printf(" = %lld ", (long long)expr); pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } @@ -290,18 +300,24 @@ static int expect_sysne(int expr, int llen, int val) } +#define EXPECT_SYSER2(cond, expr, expret, experr1, experr2) \ + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_syserr2(expr, expret, experr1, experr2, llen); } while (0) + #define EXPECT_SYSER(cond, expr, expret, experr) \ - do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_syserr(expr, expret, experr, llen); } while (0) + EXPECT_SYSER2(cond, expr, expret, experr, 0) -static int expect_syserr(int expr, int expret, int experr, int llen) +static int expect_syserr2(int expr, int expret, int experr1, int experr2, int llen) { int ret = 0; int _errno = errno; llen += printf(" = %d %s ", expr, errorname(_errno)); - if (expr != expret || _errno != experr) { + if (expr != expret || (_errno != experr1 && _errno != experr2)) { ret = 1; - llen += printf(" != (%d %s) ", expret, errorname(experr)); + if (experr2 == 0) + llen += printf(" != (%d %s) ", expret, errorname(experr1)); + else + llen += printf(" != (%d %s %s) ", expret, errorname(experr1), errorname(experr2)); llen += pad_spc(llen, 64, "[FAIL]\n"); } else { llen += pad_spc(llen, 64, " [OK]\n"); @@ -471,11 +487,60 @@ static int test_getpagesize(void) return !c; } +static int test_fork(void) +{ + int status; + pid_t pid; + + /* flush the printf buffer to avoid child flush it */ + fflush(stdout); + fflush(stderr); + + pid = fork(); + + switch (pid) { + case -1: + return 1; + + case 0: + exit(123); + + default: + pid = waitpid(pid, &status, 0); + + return pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 123; + } +} + +static int test_stat_timestamps(void) +{ + struct stat st; + + if (sizeof(st.st_atim.tv_sec) != sizeof(st.st_atime)) + return 1; + + if (stat("/proc/self/", &st)) + return 1; + + if (st.st_atim.tv_sec != st.st_atime || st.st_atim.tv_nsec > 1000000000) + return 1; + + if (st.st_mtim.tv_sec != st.st_mtime || st.st_mtim.tv_nsec > 1000000000) + return 1; + + if (st.st_ctim.tv_sec != st.st_ctime || st.st_ctim.tv_nsec > 1000000000) + return 1; + + return 0; +} + /* Run syscall tests between IDs <min> and <max>. * Return 0 on success, non-zero on failure. */ int run_syscall(int min, int max) { + struct timeval tv; + struct timezone tz; struct stat stat_buf; int euid0; int proc; @@ -491,7 +556,7 @@ int run_syscall(int min, int max) euid0 = geteuid() == 0; for (test = min; test >= 0 && test <= max; test++) { - int llen = 0; // line length + int llen = 0; /* line length */ /* avoid leaving empty lines below, this will insert holes into * test numbers. @@ -527,14 +592,11 @@ int run_syscall(int min, int max) CASE_TEST(dup3_0); tmp = dup3(0, 100, 0); EXPECT_SYSNE(1, tmp, -1); close(tmp); break; CASE_TEST(dup3_m1); tmp = dup3(-1, 100, 0); EXPECT_SYSER(1, tmp, -1, EBADF); if (tmp != -1) close(tmp); break; CASE_TEST(execve_root); EXPECT_SYSER(1, execve("/", (char*[]){ [0] = "/", [1] = NULL }, NULL), -1, EACCES); break; + CASE_TEST(fork); EXPECT_SYSZR(1, test_fork()); break; CASE_TEST(getdents64_root); EXPECT_SYSNE(1, test_getdents64("/"), -1); break; CASE_TEST(getdents64_null); EXPECT_SYSER(1, test_getdents64("/dev/null"), -1, ENOTDIR); break; - CASE_TEST(gettimeofday_null); EXPECT_SYSZR(1, gettimeofday(NULL, NULL)); break; -#ifdef NOLIBC - CASE_TEST(gettimeofday_bad1); EXPECT_SYSER(1, gettimeofday((void *)1, NULL), -1, EFAULT); break; - CASE_TEST(gettimeofday_bad2); EXPECT_SYSER(1, gettimeofday(NULL, (void *)1), -1, EFAULT); break; - CASE_TEST(gettimeofday_bad2); EXPECT_SYSER(1, gettimeofday(NULL, (void *)1), -1, EFAULT); break; -#endif + CASE_TEST(gettimeofday_tv); EXPECT_SYSZR(1, gettimeofday(&tv, NULL)); break; + CASE_TEST(gettimeofday_tv_tz);EXPECT_SYSZR(1, gettimeofday(&tv, &tz)); break; CASE_TEST(getpagesize); EXPECT_SYSZR(1, test_getpagesize()); break; CASE_TEST(ioctl_tiocinq); EXPECT_SYSZR(1, ioctl(0, TIOCINQ, &tmp)); break; CASE_TEST(ioctl_tiocinq); EXPECT_SYSZR(1, ioctl(0, TIOCINQ, &tmp)); break; @@ -550,6 +612,7 @@ int run_syscall(int min, int max) CASE_TEST(poll_null); EXPECT_SYSZR(1, poll(NULL, 0, 0)); break; CASE_TEST(poll_stdout); EXPECT_SYSNE(1, ({ struct pollfd fds = { 1, POLLOUT, 0}; poll(&fds, 1, 0); }), -1); break; CASE_TEST(poll_fault); EXPECT_SYSER(1, poll((void *)1, 1, 0), -1, EFAULT); break; + CASE_TEST(prctl); EXPECT_SYSER(1, prctl(PR_SET_NAME, (unsigned long)NULL, 0, 0, 0), -1, EFAULT); break; CASE_TEST(read_badf); EXPECT_SYSER(1, read(-1, &tmp, 1), -1, EBADF); break; CASE_TEST(sched_yield); EXPECT_SYSZR(1, sched_yield()); break; CASE_TEST(select_null); EXPECT_SYSZR(1, ({ struct timeval tv = { 0 }; select(0, NULL, NULL, NULL, &tv); })); break; @@ -557,6 +620,7 @@ int run_syscall(int min, int max) CASE_TEST(select_fault); EXPECT_SYSER(1, select(1, (void *)1, NULL, NULL, 0), -1, EFAULT); break; CASE_TEST(stat_blah); EXPECT_SYSER(1, stat("/proc/self/blah", &stat_buf), -1, ENOENT); break; CASE_TEST(stat_fault); EXPECT_SYSER(1, stat(NULL, &stat_buf), -1, EFAULT); break; + CASE_TEST(stat_timestamps); EXPECT_SYSZR(1, test_stat_timestamps()); break; CASE_TEST(symlink_root); EXPECT_SYSER(1, symlink("/", "/"), -1, EEXIST); break; CASE_TEST(unlink_root); EXPECT_SYSER(1, unlink("/"), -1, EISDIR); break; CASE_TEST(unlink_blah); EXPECT_SYSER(1, unlink("/proc/self/blah"), -1, ENOENT); break; @@ -565,6 +629,8 @@ int run_syscall(int min, int max) CASE_TEST(waitpid_child); EXPECT_SYSER(1, waitpid(getpid(), &tmp, WNOHANG), -1, ECHILD); break; CASE_TEST(write_badf); EXPECT_SYSER(1, write(-1, &tmp, 1), -1, EBADF); break; CASE_TEST(write_zero); EXPECT_SYSZR(1, write(1, &tmp, 0)); break; + CASE_TEST(syscall_noargs); EXPECT_SYSEQ(1, syscall(__NR_getpid), getpid()); break; + CASE_TEST(syscall_args); EXPECT_SYSER(1, syscall(__NR_statx, 0, NULL, 0, 0, NULL), -1, EFAULT); break; case __LINE__: return ret; /* must be last */ /* note: do not set any defaults so as to permit holes above */ @@ -581,7 +647,7 @@ int run_stdlib(int min, int max) void *p1, *p2; for (test = min; test >= 0 && test <= max; test++) { - int llen = 0; // line length + int llen = 0; /* line length */ /* avoid leaving empty lines below, this will insert holes into * test numbers. @@ -639,9 +705,9 @@ int run_stdlib(int min, int max) CASE_TEST(limit_int_fast32_min); EXPECT_EQ(1, INT_FAST32_MIN, (int_fast32_t) INTPTR_MIN); break; CASE_TEST(limit_int_fast32_max); EXPECT_EQ(1, INT_FAST32_MAX, (int_fast32_t) INTPTR_MAX); break; CASE_TEST(limit_uint_fast32_max); EXPECT_EQ(1, UINT_FAST32_MAX, (uint_fast32_t) UINTPTR_MAX); break; - CASE_TEST(limit_int_fast64_min); EXPECT_EQ(1, INT_FAST64_MIN, (int_fast64_t) INTPTR_MIN); break; - CASE_TEST(limit_int_fast64_max); EXPECT_EQ(1, INT_FAST64_MAX, (int_fast64_t) INTPTR_MAX); break; - CASE_TEST(limit_uint_fast64_max); EXPECT_EQ(1, UINT_FAST64_MAX, (uint_fast64_t) UINTPTR_MAX); break; + CASE_TEST(limit_int_fast64_min); EXPECT_EQ(1, INT_FAST64_MIN, (int_fast64_t) INT64_MIN); break; + CASE_TEST(limit_int_fast64_max); EXPECT_EQ(1, INT_FAST64_MAX, (int_fast64_t) INT64_MAX); break; + CASE_TEST(limit_uint_fast64_max); EXPECT_EQ(1, UINT_FAST64_MAX, (uint_fast64_t) UINT64_MAX); break; #if __SIZEOF_LONG__ == 8 CASE_TEST(limit_intptr_min); EXPECT_EQ(1, INTPTR_MIN, (intptr_t) 0x8000000000000000LL); break; CASE_TEST(limit_intptr_max); EXPECT_EQ(1, INTPTR_MAX, (intptr_t) 0x7fffffffffffffffLL); break; @@ -667,17 +733,98 @@ int run_stdlib(int min, int max) return ret; } -#if defined(__clang__) -__attribute__((optnone)) -#elif defined(__GNUC__) -__attribute__((optimize("O0"))) -#endif +#define EXPECT_VFPRINTF(c, expected, fmt, ...) \ + ret += expect_vfprintf(llen, c, expected, fmt, ##__VA_ARGS__) + +static int expect_vfprintf(int llen, size_t c, const char *expected, const char *fmt, ...) +{ + int ret, fd, w, r; + char buf[100]; + FILE *memfile; + va_list args; + + fd = memfd_create("vfprintf", 0); + if (fd == -1) { + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + } + + memfile = fdopen(fd, "w+"); + if (!memfile) { + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + } + + va_start(args, fmt); + w = vfprintf(memfile, fmt, args); + va_end(args); + + if (w != c) { + llen += printf(" written(%d) != %d", w, (int) c); + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + } + + fflush(memfile); + lseek(fd, 0, SEEK_SET); + + r = read(fd, buf, sizeof(buf) - 1); + buf[r] = '\0'; + + fclose(memfile); + + if (r != w) { + llen += printf(" written(%d) != read(%d)", w, r); + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + } + + llen += printf(" \"%s\" = \"%s\"", expected, buf); + ret = strncmp(expected, buf, c); + + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); + return ret; +} + +static int run_vfprintf(int min, int max) +{ + int test; + int tmp; + int ret = 0; + void *p1, *p2; + + for (test = min; test >= 0 && test <= max; test++) { + int llen = 0; /* line length */ + + /* avoid leaving empty lines below, this will insert holes into + * test numbers. + */ + switch (test + __LINE__ + 1) { + CASE_TEST(empty); EXPECT_VFPRINTF(0, "", ""); break; + CASE_TEST(simple); EXPECT_VFPRINTF(3, "foo", "foo"); break; + CASE_TEST(string); EXPECT_VFPRINTF(3, "foo", "%s", "foo"); break; + CASE_TEST(number); EXPECT_VFPRINTF(4, "1234", "%d", 1234); break; + CASE_TEST(negnumber); EXPECT_VFPRINTF(5, "-1234", "%d", -1234); break; + CASE_TEST(unsigned); EXPECT_VFPRINTF(5, "12345", "%u", 12345); break; + CASE_TEST(char); EXPECT_VFPRINTF(1, "c", "%c", 'c'); break; + CASE_TEST(hex); EXPECT_VFPRINTF(1, "f", "%x", 0xf); break; + CASE_TEST(pointer); EXPECT_VFPRINTF(3, "0x1", "%p", (void *) 0x1); break; + case __LINE__: + return ret; /* must be last */ + /* note: do not set any defaults so as to permit holes above */ + } + } + return ret; +} + static int smash_stack(void) { char buf[100]; + volatile char *ptr = buf; + size_t i; - for (size_t i = 0; i < 200; i++) - buf[i] = 'P'; + for (i = 0; i < 200; i++) + ptr[i] = 'P'; return 1; } @@ -689,12 +836,20 @@ static int run_protection(int min, int max) llen += printf("0 -fstackprotector "); -#if !defined(NOLIBC_STACKPROTECTOR) +#if !defined(_NOLIBC_STACKPROTECTOR) llen += printf("not supported"); pad_spc(llen, 64, "[SKIPPED]\n"); return 0; #endif +#if defined(_NOLIBC_STACKPROTECTOR) + if (!__stack_chk_guard) { + llen += printf("__stack_chk_guard not initialized"); + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + } +#endif + pid = -1; pid = fork(); @@ -708,6 +863,7 @@ static int run_protection(int min, int max) close(STDOUT_FILENO); close(STDERR_FILENO); + prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); smash_stack(); return 1; @@ -778,6 +934,7 @@ static const struct test test_names[] = { /* add new tests here */ { .name = "syscall", .func = run_syscall }, { .name = "stdlib", .func = run_stdlib }, + { .name = "vfprintf", .func = run_vfprintf }, { .name = "protection", .func = run_protection }, { 0 } }; @@ -785,7 +942,7 @@ static const struct test test_names[] = { int main(int argc, char **argv, char **envp) { int min = 0; - int max = __INT_MAX__; + int max = INT_MAX; int ret = 0; int err; int idx; @@ -833,7 +990,7 @@ int main(int argc, char **argv, char **envp) * here, which defaults to the full range. */ do { - min = 0; max = __INT_MAX__; + min = 0; max = INT_MAX; value = colon; if (value && *value) { colon = strchr(value, ':'); @@ -899,7 +1056,7 @@ int main(int argc, char **argv, char **envp) #else else if (ioperm(0x501, 1, 1) == 0) #endif - asm volatile ("outb %%al, %%dx" :: "d"(0x501), "a"(0)); + __asm__ volatile ("outb %%al, %%dx" :: "d"(0x501), "a"(0)); /* if it does nothing, fall back to the regular panic */ #endif } diff --git a/tools/testing/selftests/pidfd/pidfd.h b/tools/testing/selftests/pidfd/pidfd.h index 6922d6417e1c..88d6830ee004 100644 --- a/tools/testing/selftests/pidfd/pidfd.h +++ b/tools/testing/selftests/pidfd/pidfd.h @@ -90,7 +90,6 @@ again: } ret = WEXITSTATUS(status); - ksft_print_msg("waitpid WEXITSTATUS=%d\n", ret); return ret; } diff --git a/tools/testing/selftests/pidfd/pidfd_fdinfo_test.c b/tools/testing/selftests/pidfd/pidfd_fdinfo_test.c index 3fd8e903118f..4e86f927880c 100644 --- a/tools/testing/selftests/pidfd/pidfd_fdinfo_test.c +++ b/tools/testing/selftests/pidfd/pidfd_fdinfo_test.c @@ -143,6 +143,7 @@ static inline int child_join(struct child *child, struct error *err) r = -1; } + ksft_print_msg("waitpid WEXITSTATUS=%d\n", r); return r; } diff --git a/tools/testing/selftests/pidfd/pidfd_test.c b/tools/testing/selftests/pidfd/pidfd_test.c index e2dd4ed84984..00a07e7c571c 100644 --- a/tools/testing/selftests/pidfd/pidfd_test.c +++ b/tools/testing/selftests/pidfd/pidfd_test.c @@ -115,7 +115,8 @@ static int test_pidfd_send_signal_exited_fail(void) pidfd = open(buf, O_DIRECTORY | O_CLOEXEC); - (void)wait_for_pid(pid); + ret = wait_for_pid(pid); + ksft_print_msg("waitpid WEXITSTATUS=%d\n", ret); if (pidfd < 0) ksft_exit_fail_msg( diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile index ae2bfc0d822f..49f2ad1793fd 100644 --- a/tools/testing/selftests/powerpc/Makefile +++ b/tools/testing/selftests/powerpc/Makefile @@ -17,6 +17,7 @@ SUB_DIRS = alignment \ benchmarks \ cache_shape \ copyloops \ + dexcr \ dscr \ mm \ nx-gzip \ diff --git a/tools/testing/selftests/powerpc/dexcr/.gitignore b/tools/testing/selftests/powerpc/dexcr/.gitignore new file mode 100644 index 000000000000..b82f45dd46b9 --- /dev/null +++ b/tools/testing/selftests/powerpc/dexcr/.gitignore @@ -0,0 +1,2 @@ +hashchk_test +lsdexcr diff --git a/tools/testing/selftests/powerpc/dexcr/Makefile b/tools/testing/selftests/powerpc/dexcr/Makefile new file mode 100644 index 000000000000..76210f2bcec3 --- /dev/null +++ b/tools/testing/selftests/powerpc/dexcr/Makefile @@ -0,0 +1,9 @@ +TEST_GEN_PROGS := hashchk_test +TEST_GEN_FILES := lsdexcr + +include ../../lib.mk + +$(OUTPUT)/hashchk_test: CFLAGS += -fno-pie $(call cc-option,-mno-rop-protect) + +$(TEST_GEN_PROGS): ../harness.c ../utils.c ./dexcr.c +$(TEST_GEN_FILES): ../utils.c ./dexcr.c diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr.c b/tools/testing/selftests/powerpc/dexcr/dexcr.c new file mode 100644 index 000000000000..65ec5347de98 --- /dev/null +++ b/tools/testing/selftests/powerpc/dexcr/dexcr.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <setjmp.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "dexcr.h" +#include "reg.h" +#include "utils.h" + +static jmp_buf generic_signal_jump_buf; + +static void generic_signal_handler(int signum, siginfo_t *info, void *context) +{ + longjmp(generic_signal_jump_buf, 0); +} + +bool dexcr_exists(void) +{ + struct sigaction old; + volatile bool exists; + + old = push_signal_handler(SIGILL, generic_signal_handler); + if (setjmp(generic_signal_jump_buf)) + goto out; + + /* + * If the SPR is not recognised by the hardware it triggers + * a hypervisor emulation interrupt. If the kernel does not + * recognise/try to emulate it, we receive a SIGILL signal. + * + * If we do not receive a signal, assume we have the SPR or the + * kernel is trying to emulate it correctly. + */ + exists = false; + mfspr(SPRN_DEXCR_RO); + exists = true; + +out: + pop_signal_handler(SIGILL, old); + return exists; +} + +/* + * Just test if a bad hashchk triggers a signal, without checking + * for support or if the NPHIE aspect is enabled. + */ +bool hashchk_triggers(void) +{ + struct sigaction old; + volatile bool triggers; + + old = push_signal_handler(SIGILL, generic_signal_handler); + if (setjmp(generic_signal_jump_buf)) + goto out; + + triggers = true; + do_bad_hashchk(); + triggers = false; + +out: + pop_signal_handler(SIGILL, old); + return triggers; +} + +unsigned int get_dexcr(enum dexcr_source source) +{ + switch (source) { + case DEXCR: + return mfspr(SPRN_DEXCR_RO); + case HDEXCR: + return mfspr(SPRN_HDEXCR_RO); + case EFFECTIVE: + return mfspr(SPRN_DEXCR_RO) | mfspr(SPRN_HDEXCR_RO); + default: + FAIL_IF_EXIT_MSG(true, "bad enum dexcr_source"); + } +} + +void await_child_success(pid_t pid) +{ + int wstatus; + + FAIL_IF_EXIT_MSG(pid == -1, "fork failed"); + FAIL_IF_EXIT_MSG(waitpid(pid, &wstatus, 0) == -1, "wait failed"); + FAIL_IF_EXIT_MSG(!WIFEXITED(wstatus), "child did not exit cleanly"); + FAIL_IF_EXIT_MSG(WEXITSTATUS(wstatus) != 0, "child exit error"); +} + +/* + * Perform a hashst instruction. The following components determine the result + * + * 1. The LR value (any register technically) + * 2. The SP value (also any register, but it must be a valid address) + * 3. A secret key managed by the kernel + * + * The result is stored to the address held in SP. + */ +void hashst(unsigned long lr, void *sp) +{ + asm volatile ("addi 31, %0, 0;" /* set r31 (pretend LR) to lr */ + "addi 30, %1, 8;" /* set r30 (pretend SP) to sp + 8 */ + PPC_RAW_HASHST(31, -8, 30) /* compute hash into stack location */ + : : "r" (lr), "r" (sp) : "r31", "r30", "memory"); +} + +/* + * Perform a hashchk instruction. A hash is computed as per hashst(), + * however the result is not stored to memory. Instead the existing + * value is read and compared against the computed hash. + * + * If they match, execution continues. + * If they differ, an interrupt triggers. + */ +void hashchk(unsigned long lr, void *sp) +{ + asm volatile ("addi 31, %0, 0;" /* set r31 (pretend LR) to lr */ + "addi 30, %1, 8;" /* set r30 (pretend SP) to sp + 8 */ + PPC_RAW_HASHCHK(31, -8, 30) /* check hash at stack location */ + : : "r" (lr), "r" (sp) : "r31", "r30", "memory"); +} + +void do_bad_hashchk(void) +{ + unsigned long hash = 0; + + hashst(0, &hash); + hash += 1; + hashchk(0, &hash); +} diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr.h b/tools/testing/selftests/powerpc/dexcr/dexcr.h new file mode 100644 index 000000000000..f55cbbc8643b --- /dev/null +++ b/tools/testing/selftests/powerpc/dexcr/dexcr.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * POWER Dynamic Execution Control Facility (DEXCR) + * + * This header file contains helper functions and macros + * required for all the DEXCR related test cases. + */ +#ifndef _SELFTESTS_POWERPC_DEXCR_DEXCR_H +#define _SELFTESTS_POWERPC_DEXCR_DEXCR_H + +#include <stdbool.h> +#include <sys/types.h> + +#include "reg.h" + +#define DEXCR_PR_BIT(aspect) __MASK(63 - (32 + (aspect))) +#define DEXCR_PR_SBHE DEXCR_PR_BIT(0) +#define DEXCR_PR_IBRTPD DEXCR_PR_BIT(3) +#define DEXCR_PR_SRAPD DEXCR_PR_BIT(4) +#define DEXCR_PR_NPHIE DEXCR_PR_BIT(5) + +#define PPC_RAW_HASH_ARGS(b, i, a) \ + ((((i) >> 3) & 0x1F) << 21 | (a) << 16 | (b) << 11 | (((i) >> 8) & 0x1)) +#define PPC_RAW_HASHST(b, i, a) \ + str(.long (0x7C0005A4 | PPC_RAW_HASH_ARGS(b, i, a));) +#define PPC_RAW_HASHCHK(b, i, a) \ + str(.long (0x7C0005E4 | PPC_RAW_HASH_ARGS(b, i, a));) + +bool dexcr_exists(void); + +bool hashchk_triggers(void); + +enum dexcr_source { + DEXCR, /* Userspace DEXCR value */ + HDEXCR, /* Hypervisor enforced DEXCR value */ + EFFECTIVE, /* Bitwise OR of UDEXCR and ENFORCED DEXCR bits */ +}; + +unsigned int get_dexcr(enum dexcr_source source); + +void await_child_success(pid_t pid); + +void hashst(unsigned long lr, void *sp); + +void hashchk(unsigned long lr, void *sp); + +void do_bad_hashchk(void); + +#endif /* _SELFTESTS_POWERPC_DEXCR_DEXCR_H */ diff --git a/tools/testing/selftests/powerpc/dexcr/hashchk_test.c b/tools/testing/selftests/powerpc/dexcr/hashchk_test.c new file mode 100644 index 000000000000..7d5658c9ebe4 --- /dev/null +++ b/tools/testing/selftests/powerpc/dexcr/hashchk_test.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#define _GNU_SOURCE + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <unistd.h> + +#include "dexcr.h" +#include "utils.h" + +static int require_nphie(void) +{ + SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported"); + SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE), + "DEXCR[NPHIE] not enabled"); + + return 0; +} + +static jmp_buf hashchk_detected_buf; +static const char *hashchk_failure_msg; + +static void hashchk_handler(int signum, siginfo_t *info, void *context) +{ + if (signum != SIGILL) + hashchk_failure_msg = "wrong signal received"; + else if (info->si_code != ILL_ILLOPN) + hashchk_failure_msg = "wrong signal code received"; + + longjmp(hashchk_detected_buf, 0); +} + +/* + * Check that hashchk triggers when DEXCR[NPHIE] is enabled + * and is detected as such by the kernel exception handler + */ +static int hashchk_detected_test(void) +{ + struct sigaction old; + int err; + + err = require_nphie(); + if (err) + return err; + + old = push_signal_handler(SIGILL, hashchk_handler); + if (setjmp(hashchk_detected_buf)) + goto out; + + hashchk_failure_msg = NULL; + do_bad_hashchk(); + hashchk_failure_msg = "hashchk failed to trigger"; + +out: + pop_signal_handler(SIGILL, old); + FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg); + return 0; +} + +#define HASH_COUNT 8 + +static unsigned long hash_values[HASH_COUNT + 1]; + +static void fill_hash_values(void) +{ + for (unsigned long i = 0; i < HASH_COUNT; i++) + hashst(i, &hash_values[i]); + + /* Used to ensure the checks uses the same addresses as the hashes */ + hash_values[HASH_COUNT] = (unsigned long)&hash_values; +} + +static unsigned int count_hash_values_matches(void) +{ + unsigned long matches = 0; + + for (unsigned long i = 0; i < HASH_COUNT; i++) { + unsigned long orig_hash = hash_values[i]; + hash_values[i] = 0; + + hashst(i, &hash_values[i]); + + if (hash_values[i] == orig_hash) + matches++; + } + + return matches; +} + +static int hashchk_exec_child(void) +{ + ssize_t count; + + fill_hash_values(); + + count = write(STDOUT_FILENO, hash_values, sizeof(hash_values)); + return count == sizeof(hash_values) ? 0 : EOVERFLOW; +} + +static char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL }; + +/* + * Check that new programs get different keys so a malicious process + * can't recreate a victim's hash values. + */ +static int hashchk_exec_random_key_test(void) +{ + pid_t pid; + int err; + int pipefd[2]; + + err = require_nphie(); + if (err) + return err; + + FAIL_IF_MSG(pipe(pipefd), "failed to create pipe"); + + pid = fork(); + if (pid == 0) { + if (dup2(pipefd[1], STDOUT_FILENO) == -1) + _exit(errno); + + execve("/proc/self/exe", hashchk_exec_child_args, NULL); + _exit(errno); + } + + await_child_success(pid); + FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != sizeof(hash_values), + "missing expected child output"); + + /* Verify the child used the same hash_values address */ + FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values, + "bad address check"); + + /* If all hashes are the same it means (most likely) same key */ + FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key detected"); + + return 0; +} + +/* + * Check that forks share the same key so that existing hash values + * remain valid. + */ +static int hashchk_fork_share_key_test(void) +{ + pid_t pid; + int err; + + err = require_nphie(); + if (err) + return err; + + fill_hash_values(); + + pid = fork(); + if (pid == 0) { + if (count_hash_values_matches() != HASH_COUNT) + _exit(1); + _exit(0); + } + + await_child_success(pid); + return 0; +} + +#define STACK_SIZE (1024 * 1024) + +static int hashchk_clone_child_fn(void *args) +{ + fill_hash_values(); + return 0; +} + +/* + * Check that threads share the same key so that existing hash values + * remain valid. + */ +static int hashchk_clone_share_key_test(void) +{ + void *child_stack; + pid_t pid; + int err; + + err = require_nphie(); + if (err) + return err; + + child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + + FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack"); + + pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE, + CLONE_VM | SIGCHLD, NULL); + + await_child_success(pid); + FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT, + "different key detected"); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int err = 0; + + if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0])) + return hashchk_exec_child(); + + err |= test_harness(hashchk_detected_test, "hashchk_detected"); + err |= test_harness(hashchk_exec_random_key_test, "hashchk_exec_random_key"); + err |= test_harness(hashchk_fork_share_key_test, "hashchk_fork_share_key"); + err |= test_harness(hashchk_clone_share_key_test, "hashchk_clone_share_key"); + + return err; +} diff --git a/tools/testing/selftests/powerpc/dexcr/lsdexcr.c b/tools/testing/selftests/powerpc/dexcr/lsdexcr.c new file mode 100644 index 000000000000..94abbfcc389e --- /dev/null +++ b/tools/testing/selftests/powerpc/dexcr/lsdexcr.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "dexcr.h" +#include "utils.h" + +static unsigned int dexcr; +static unsigned int hdexcr; +static unsigned int effective; + +struct dexcr_aspect { + const char *name; + const char *desc; + unsigned int index; +}; + +static const struct dexcr_aspect aspects[] = { + { + .name = "SBHE", + .desc = "Speculative branch hint enable", + .index = 0, + }, + { + .name = "IBRTPD", + .desc = "Indirect branch recurrent target prediction disable", + .index = 3, + }, + { + .name = "SRAPD", + .desc = "Subroutine return address prediction disable", + .index = 4, + }, + { + .name = "NPHIE", + .desc = "Non-privileged hash instruction enable", + .index = 5, + }, + { + .name = "PHIE", + .desc = "Privileged hash instruction enable", + .index = 6, + }, +}; + +static void print_list(const char *list[], size_t len) +{ + for (size_t i = 0; i < len; i++) { + printf("%s", list[i]); + if (i + 1 < len) + printf(", "); + } +} + +static void print_dexcr(char *name, unsigned int bits) +{ + const char *enabled_aspects[ARRAY_SIZE(aspects) + 1] = {NULL}; + size_t j = 0; + + printf("%s: %08x", name, bits); + + if (bits == 0) { + printf("\n"); + return; + } + + for (size_t i = 0; i < ARRAY_SIZE(aspects); i++) { + unsigned int mask = DEXCR_PR_BIT(aspects[i].index); + + if (bits & mask) { + enabled_aspects[j++] = aspects[i].name; + bits &= ~mask; + } + } + + if (bits) + enabled_aspects[j++] = "unknown"; + + printf(" ("); + print_list(enabled_aspects, j); + printf(")\n"); +} + +static void print_aspect(const struct dexcr_aspect *aspect) +{ + const char *attributes[8] = {NULL}; + size_t j = 0; + unsigned long mask; + + mask = DEXCR_PR_BIT(aspect->index); + if (dexcr & mask) + attributes[j++] = "set"; + if (hdexcr & mask) + attributes[j++] = "set (hypervisor)"; + if (!(effective & mask)) + attributes[j++] = "clear"; + + printf("%12s %c (%d): ", aspect->name, effective & mask ? '*' : ' ', aspect->index); + print_list(attributes, j); + printf(" \t(%s)\n", aspect->desc); +} + +int main(int argc, char *argv[]) +{ + if (!dexcr_exists()) { + printf("DEXCR not detected on this hardware\n"); + return 1; + } + + dexcr = get_dexcr(DEXCR); + hdexcr = get_dexcr(HDEXCR); + effective = dexcr | hdexcr; + + print_dexcr(" DEXCR", dexcr); + print_dexcr(" HDEXCR", hdexcr); + print_dexcr("Effective", effective); + printf("\n"); + + for (size_t i = 0; i < ARRAY_SIZE(aspects); i++) + print_aspect(&aspects[i]); + printf("\n"); + + if (effective & DEXCR_PR_NPHIE) { + printf("DEXCR[NPHIE] enabled: hashst/hashchk "); + if (hashchk_triggers()) + printf("working\n"); + else + printf("failed to trigger\n"); + } else { + printf("DEXCR[NPHIE] disabled: hashst/hashchk "); + if (hashchk_triggers()) + printf("unexpectedly triggered\n"); + else + printf("ignored\n"); + } + + return 0; +} diff --git a/tools/testing/selftests/powerpc/include/reg.h b/tools/testing/selftests/powerpc/include/reg.h index d5a547f72669..fad09c9d3387 100644 --- a/tools/testing/selftests/powerpc/include/reg.h +++ b/tools/testing/selftests/powerpc/include/reg.h @@ -19,6 +19,8 @@ #define mb() asm volatile("sync" : : : "memory"); #define barrier() asm volatile("" : : : "memory"); +#define SPRN_HDEXCR_RO 455 /* Userspace readonly view of SPRN_HDEXCR (471) */ + #define SPRN_MMCR2 769 #define SPRN_MMCRA 770 #define SPRN_MMCR0 779 @@ -47,6 +49,8 @@ #define SPRN_SDAR 781 #define SPRN_SIER 768 +#define SPRN_DEXCR_RO 812 /* Userspace readonly view of SPRN_DEXCR (828) */ + #define SPRN_TEXASR 0x82 /* Transaction Exception and Status Register */ #define SPRN_TFIAR 0x81 /* Transaction Failure Inst Addr */ #define SPRN_TFHAR 0x80 /* Transaction Failure Handler Addr */ diff --git a/tools/testing/selftests/powerpc/include/utils.h b/tools/testing/selftests/powerpc/include/utils.h index 44bfd48b93d6..36c30c611457 100644 --- a/tools/testing/selftests/powerpc/include/utils.h +++ b/tools/testing/selftests/powerpc/include/utils.h @@ -9,11 +9,18 @@ #define __cacheline_aligned __attribute__((aligned(128))) #include <stdint.h> +#include <stdio.h> #include <stdbool.h> +#include <sys/signal.h> #include <linux/auxvec.h> #include <linux/perf_event.h> #include <asm/cputable.h> #include "reg.h" +#include <unistd.h> + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif /* Avoid headaches with PRI?64 - just use %ll? always */ typedef unsigned long long u64; @@ -67,7 +74,6 @@ struct perf_event_read { }; #if !defined(__GLIBC_PREREQ) || !__GLIBC_PREREQ(2, 30) -#include <unistd.h> #include <sys/syscall.h> static inline pid_t gettid(void) @@ -106,6 +112,9 @@ static inline char *auxv_platform(void) bool is_ppc64le(void); int using_hash_mmu(bool *using_hash); +struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, void *)); +struct sigaction pop_signal_handler(int sig, struct sigaction old_handler); + /* Yes, this is evil */ #define FAIL_IF(x) \ do { \ @@ -116,6 +125,16 @@ do { \ } \ } while (0) +#define FAIL_IF_MSG(x, msg) \ +do { \ + if ((x)) { \ + fprintf(stderr, \ + "[FAIL] Test FAILED on line %d: %s\n", \ + __LINE__, msg); \ + return 1; \ + } \ +} while (0) + #define FAIL_IF_EXIT(x) \ do { \ if ((x)) { \ @@ -125,6 +144,16 @@ do { \ } \ } while (0) +#define FAIL_IF_EXIT_MSG(x, msg) \ +do { \ + if ((x)) { \ + fprintf(stderr, \ + "[FAIL] Test FAILED on line %d: %s\n", \ + __LINE__, msg); \ + _exit(1); \ + } \ +} while (0) + /* The test harness uses this, yes it's gross */ #define MAGIC_SKIP_RETURN_VALUE 99 diff --git a/tools/testing/selftests/powerpc/pmu/sampling_tests/misc.h b/tools/testing/selftests/powerpc/pmu/sampling_tests/misc.h index 4181755cf5a0..64e25cce1435 100644 --- a/tools/testing/selftests/powerpc/pmu/sampling_tests/misc.h +++ b/tools/testing/selftests/powerpc/pmu/sampling_tests/misc.h @@ -18,8 +18,6 @@ #define MMCR1_RSQ 0x200000000000ULL /* radix scope qual field */ #define BHRB_DISABLE 0x2000000000ULL /* MMCRA BHRB DISABLE bit */ -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - extern int ev_mask_pmcxsel, ev_shift_pmcxsel; extern int ev_mask_marked, ev_shift_marked; extern int ev_mask_comb, ev_shift_comb; diff --git a/tools/testing/selftests/powerpc/utils.c b/tools/testing/selftests/powerpc/utils.c index 252fb4a95e90..e5f2d8735c64 100644 --- a/tools/testing/selftests/powerpc/utils.c +++ b/tools/testing/selftests/powerpc/utils.c @@ -618,3 +618,27 @@ out: fclose(f); return rc; } + +struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, void *)) +{ + struct sigaction sa; + struct sigaction old_handler; + + sa.sa_sigaction = fn; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + FAIL_IF_EXIT_MSG(sigaction(sig, &sa, &old_handler), + "failed to push signal handler"); + + return old_handler; +} + +struct sigaction pop_signal_handler(int sig, struct sigaction old_handler) +{ + struct sigaction popped; + + FAIL_IF_EXIT_MSG(sigaction(sig, &old_handler, &popped), + "failed to pop signal handler"); + + return popped; +} diff --git a/tools/testing/selftests/prctl/set-anon-vma-name-test.c b/tools/testing/selftests/prctl/set-anon-vma-name-test.c index 26d853c5a0c1..4275cb256dce 100644 --- a/tools/testing/selftests/prctl/set-anon-vma-name-test.c +++ b/tools/testing/selftests/prctl/set-anon-vma-name-test.c @@ -97,7 +97,7 @@ TEST_F(vma, renaming) { TH_LOG("Try to pass invalid name (with non-printable character \\1) to rename the VMA"); EXPECT_EQ(rename_vma((unsigned long)self->ptr_anon, AREA_SIZE, BAD_NAME), -EINVAL); - TH_LOG("Try to rename non-anonynous VMA"); + TH_LOG("Try to rename non-anonymous VMA"); EXPECT_EQ(rename_vma((unsigned long) self->ptr_not_anon, AREA_SIZE, GOOD_NAME), -EINVAL); } diff --git a/tools/testing/selftests/ptp/testptp.c b/tools/testing/selftests/ptp/testptp.c index 198ad5f32187..e9438a1862ad 100644 --- a/tools/testing/selftests/ptp/testptp.c +++ b/tools/testing/selftests/ptp/testptp.c @@ -110,7 +110,7 @@ static long ppb_to_scaled_ppm(int ppb) static int64_t pctns(struct ptp_clock_time *t) { - return t->sec * 1000000000LL + t->nsec; + return t->sec * NSEC_PER_SEC + t->nsec; } static void usage(char *progname) @@ -134,6 +134,7 @@ static void usage(char *progname) " 1 - external time stamp\n" " 2 - periodic output\n" " -n val shift the ptp clock time by 'val' nanoseconds\n" + " -o val phase offset (in nanoseconds) to be provided to the PHC servo\n" " -p val enable output with a period of 'val' nanoseconds\n" " -H val set output phase to 'val' nanoseconds (requires -p)\n" " -w val set output pulse width to 'val' nanoseconds (requires -p)\n" @@ -167,6 +168,7 @@ int main(int argc, char *argv[]) int adjfreq = 0x7fffffff; int adjtime = 0; int adjns = 0; + int adjphase = 0; int capabilities = 0; int extts = 0; int flagtest = 0; @@ -188,7 +190,7 @@ int main(int argc, char *argv[]) progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; - while (EOF != (c = getopt(argc, argv, "cd:e:f:ghH:i:k:lL:n:p:P:sSt:T:w:z"))) { + while (EOF != (c = getopt(argc, argv, "cd:e:f:ghH:i:k:lL:n:o:p:P:sSt:T:w:z"))) { switch (c) { case 'c': capabilities = 1; @@ -228,6 +230,9 @@ int main(int argc, char *argv[]) case 'n': adjns = atoi(optarg); break; + case 'o': + adjphase = atoi(optarg); + break; case 'p': perout = atoll(optarg); break; @@ -287,7 +292,8 @@ int main(int argc, char *argv[]) " %d pulse per second\n" " %d programmable pins\n" " %d cross timestamping\n" - " %d adjust_phase\n", + " %d adjust_phase\n" + " %d maximum phase adjustment (ns)\n", caps.max_adj, caps.n_alarm, caps.n_ext_ts, @@ -295,7 +301,8 @@ int main(int argc, char *argv[]) caps.pps, caps.n_pins, caps.cross_timestamping, - caps.adjust_phase); + caps.adjust_phase, + caps.max_phase_adj); } } @@ -317,7 +324,7 @@ int main(int argc, char *argv[]) tx.time.tv_usec = adjns; while (tx.time.tv_usec < 0) { tx.time.tv_sec -= 1; - tx.time.tv_usec += 1000000000; + tx.time.tv_usec += NSEC_PER_SEC; } if (clock_adjtime(clkid, &tx) < 0) { @@ -327,6 +334,18 @@ int main(int argc, char *argv[]) } } + if (adjphase) { + memset(&tx, 0, sizeof(tx)); + tx.modes = ADJ_OFFSET | ADJ_NANO; + tx.offset = adjphase; + + if (clock_adjtime(clkid, &tx) < 0) { + perror("clock_adjtime"); + } else { + puts("phase adjustment okay"); + } + } + if (gettime) { if (clock_gettime(clkid, &ts)) { perror("clock_gettime"); @@ -502,11 +521,11 @@ int main(int argc, char *argv[]) interval = t2 - t1; offset = (t2 + t1) / 2 - tp; - printf("system time: %lld.%u\n", + printf("system time: %lld.%09u\n", (pct+2*i)->sec, (pct+2*i)->nsec); - printf("phc time: %lld.%u\n", + printf("phc time: %lld.%09u\n", (pct+2*i+1)->sec, (pct+2*i+1)->nsec); - printf("system time: %lld.%u\n", + printf("system time: %lld.%09u\n", (pct+2*i+2)->sec, (pct+2*i+2)->nsec); printf("system/phc clock time offset is %" PRId64 " ns\n" "system clock time delay is %" PRId64 " ns\n", diff --git a/tools/testing/selftests/rcutorture/bin/functions.sh b/tools/testing/selftests/rcutorture/bin/functions.sh index b52d5069563c..48b9147e8c91 100644 --- a/tools/testing/selftests/rcutorture/bin/functions.sh +++ b/tools/testing/selftests/rcutorture/bin/functions.sh @@ -250,7 +250,7 @@ identify_qemu_args () { echo -machine virt,gic-version=host -cpu host ;; qemu-system-ppc64) - echo -enable-kvm -M pseries -nodefaults + echo -M pseries -nodefaults echo -device spapr-vscsi if test -n "$TORTURE_QEMU_INTERACTIVE" -a -n "$TORTURE_QEMU_MAC" then diff --git a/tools/testing/selftests/rcutorture/bin/kvm.sh b/tools/testing/selftests/rcutorture/bin/kvm.sh index 62f3b0f56e4d..d3cdc2d33d4b 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm.sh @@ -655,4 +655,4 @@ fi # Control buffer size: --bootargs trace_buf_size=3k # Get trace-buffer dumps on all oopses: --bootargs ftrace_dump_on_oops # Ditto, but dump only the oopsing CPU: --bootargs ftrace_dump_on_oops=orig_cpu -# Heavy-handed way to also dump on warnings: --bootargs panic_on_warn +# Heavy-handed way to also dump on warnings: --bootargs panic_on_warn=1 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/BUSTED-BOOST.boot b/tools/testing/selftests/rcutorture/configs/rcu/BUSTED-BOOST.boot index f57720c52c0f..84f6bb98ce99 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/BUSTED-BOOST.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/BUSTED-BOOST.boot @@ -5,4 +5,4 @@ rcutree.gp_init_delay=3 rcutree.gp_cleanup_delay=3 rcutree.kthread_prio=2 threadirqs -tree.use_softirq=0 +rcutree.use_softirq=0 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot index 64f864f1f361..8e50bfd4b710 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot @@ -4,4 +4,4 @@ rcutree.gp_init_delay=3 rcutree.gp_cleanup_delay=3 rcutree.kthread_prio=2 threadirqs -tree.use_softirq=0 +rcutree.use_softirq=0 diff --git a/tools/testing/selftests/riscv/Makefile b/tools/testing/selftests/riscv/Makefile index 32a72902d045..9dd629cc86aa 100644 --- a/tools/testing/selftests/riscv/Makefile +++ b/tools/testing/selftests/riscv/Makefile @@ -5,7 +5,7 @@ ARCH ?= $(shell uname -m 2>/dev/null || echo not) ifneq (,$(filter $(ARCH),riscv)) -RISCV_SUBTARGETS ?= hwprobe +RISCV_SUBTARGETS ?= hwprobe vector else RISCV_SUBTARGETS := endif diff --git a/tools/testing/selftests/riscv/hwprobe/.gitignore b/tools/testing/selftests/riscv/hwprobe/.gitignore new file mode 100644 index 000000000000..8113dc3bdd03 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/.gitignore @@ -0,0 +1 @@ +hwprobe diff --git a/tools/testing/selftests/riscv/vector/.gitignore b/tools/testing/selftests/riscv/vector/.gitignore new file mode 100644 index 000000000000..9ae7964491d5 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/.gitignore @@ -0,0 +1,3 @@ +vstate_exec_nolibc +vstate_prctl +v_initval_nolibc diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile new file mode 100644 index 000000000000..bfff0ff4f3be --- /dev/null +++ b/tools/testing/selftests/riscv/vector/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 ARM Limited +# Originally tools/testing/arm64/abi/Makefile + +TEST_GEN_PROGS := vstate_prctl v_initval_nolibc +TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc + +include ../../lib.mk + +$(OUTPUT)/vstate_prctl: vstate_prctl.c ../hwprobe/sys_hwprobe.S + $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/vstate_exec_nolibc: vstate_exec_nolibc.c + $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \ + -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc + +$(OUTPUT)/v_initval_nolibc: v_initval_nolibc.c + $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \ + -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc diff --git a/tools/testing/selftests/riscv/vector/v_initval_nolibc.c b/tools/testing/selftests/riscv/vector/v_initval_nolibc.c new file mode 100644 index 000000000000..66764edb0d52 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/v_initval_nolibc.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "../../kselftest.h" +#define MAX_VSIZE (8192 * 32) + +void dump(char *ptr, int size) +{ + int i = 0; + + for (i = 0; i < size; i++) { + if (i != 0) { + if (i % 16 == 0) + printf("\n"); + else if (i % 8 == 0) + printf(" "); + } + printf("%02x ", ptr[i]); + } + printf("\n"); +} + +int main(void) +{ + int i; + unsigned long vl; + char *datap, *tmp; + + datap = malloc(MAX_VSIZE); + if (!datap) { + ksft_test_result_fail("fail to allocate memory for size = %lu\n", MAX_VSIZE); + exit(-1); + } + + tmp = datap; + asm volatile ( + ".option push\n\t" + ".option arch, +v\n\t" + "vsetvli %0, x0, e8, m8, ta, ma\n\t" + "vse8.v v0, (%2)\n\t" + "add %1, %2, %0\n\t" + "vse8.v v8, (%1)\n\t" + "add %1, %1, %0\n\t" + "vse8.v v16, (%1)\n\t" + "add %1, %1, %0\n\t" + "vse8.v v24, (%1)\n\t" + ".option pop\n\t" + : "=&r" (vl), "=r" (tmp) : "r" (datap) : "memory"); + + ksft_print_msg("vl = %lu\n", vl); + + if (datap[0] != 0x00 && datap[0] != 0xff) { + ksft_test_result_fail("v-regesters are not properly initialized\n"); + dump(datap, vl * 4); + exit(-1); + } + + for (i = 1; i < vl * 4; i++) { + if (datap[i] != datap[0]) { + ksft_test_result_fail("detect stale values on v-regesters\n"); + dump(datap, vl * 4); + exit(-2); + } + } + + free(datap); + ksft_exit_pass(); + return 0; +} diff --git a/tools/testing/selftests/riscv/vector/vstate_exec_nolibc.c b/tools/testing/selftests/riscv/vector/vstate_exec_nolibc.c new file mode 100644 index 000000000000..5cbc392944a6 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_exec_nolibc.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/prctl.h> + +#define THIS_PROGRAM "./vstate_exec_nolibc" + +int main(int argc, char **argv) +{ + int rc, pid, status, test_inherit = 0; + long ctrl, ctrl_c; + char *exec_argv[2], *exec_envp[2]; + + if (argc > 1) + test_inherit = 1; + + ctrl = my_syscall1(__NR_prctl, PR_RISCV_V_GET_CONTROL); + if (ctrl < 0) { + puts("PR_RISCV_V_GET_CONTROL is not supported\n"); + return ctrl; + } + + if (test_inherit) { + pid = fork(); + if (pid == -1) { + puts("fork failed\n"); + exit(-1); + } + + /* child */ + if (!pid) { + exec_argv[0] = THIS_PROGRAM; + exec_argv[1] = NULL; + exec_envp[0] = NULL; + exec_envp[1] = NULL; + /* launch the program again to check inherit */ + rc = execve(THIS_PROGRAM, exec_argv, exec_envp); + if (rc) { + puts("child execve failed\n"); + exit(-1); + } + } + + } else { + pid = fork(); + if (pid == -1) { + puts("fork failed\n"); + exit(-1); + } + + if (!pid) { + rc = my_syscall1(__NR_prctl, PR_RISCV_V_GET_CONTROL); + if (rc != ctrl) { + puts("child's vstate_ctrl not equal to parent's\n"); + exit(-1); + } + asm volatile (".option push\n\t" + ".option arch, +v\n\t" + "vsetvli x0, x0, e32, m8, ta, ma\n\t" + ".option pop\n\t" + ); + exit(ctrl); + } + } + + rc = waitpid(-1, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == -1) { + puts("child exited abnormally\n"); + exit(-1); + } + + if (WIFSIGNALED(status)) { + if (WTERMSIG(status) != SIGILL) { + puts("child was terminated by unexpected signal\n"); + exit(-1); + } + + if ((ctrl & PR_RISCV_V_VSTATE_CTRL_CUR_MASK) != PR_RISCV_V_VSTATE_CTRL_OFF) { + puts("child signaled by illegal V access but vstate_ctrl is not off\n"); + exit(-1); + } + + /* child terminated, and its vstate_ctrl is off */ + exit(ctrl); + } + + ctrl_c = WEXITSTATUS(status); + if (test_inherit) { + if (ctrl & PR_RISCV_V_VSTATE_CTRL_INHERIT) { + if (!(ctrl_c & PR_RISCV_V_VSTATE_CTRL_INHERIT)) { + puts("parent has inherit bit, but child has not\n"); + exit(-1); + } + } + rc = (ctrl & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK) >> 2; + if (rc != PR_RISCV_V_VSTATE_CTRL_DEFAULT) { + if (rc != (ctrl_c & PR_RISCV_V_VSTATE_CTRL_CUR_MASK)) { + puts("parent's next setting does not equal to child's\n"); + exit(-1); + } + + if (!(ctrl & PR_RISCV_V_VSTATE_CTRL_INHERIT)) { + if ((ctrl_c & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK) != + PR_RISCV_V_VSTATE_CTRL_DEFAULT) { + puts("must clear child's next vstate_ctrl if !inherit\n"); + exit(-1); + } + } + } + } + return ctrl; +} diff --git a/tools/testing/selftests/riscv/vector/vstate_prctl.c b/tools/testing/selftests/riscv/vector/vstate_prctl.c new file mode 100644 index 000000000000..b348b475be57 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_prctl.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/prctl.h> +#include <unistd.h> +#include <asm/hwprobe.h> +#include <errno.h> +#include <sys/wait.h> + +#include "../../kselftest.h" + +/* + * Rather than relying on having a new enough libc to define this, just do it + * ourselves. This way we don't need to be coupled to a new-enough libc to + * contain the call. + */ +long riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, + size_t cpu_count, unsigned long *cpus, unsigned int flags); + +#define NEXT_PROGRAM "./vstate_exec_nolibc" +static int launch_test(int test_inherit) +{ + char *exec_argv[3], *exec_envp[1]; + int rc, pid, status; + + pid = fork(); + if (pid < 0) { + ksft_test_result_fail("fork failed %d", pid); + return -1; + } + + if (!pid) { + exec_argv[0] = NEXT_PROGRAM; + exec_argv[1] = test_inherit != 0 ? "x" : NULL; + exec_argv[2] = NULL; + exec_envp[0] = NULL; + /* launch the program again to check inherit */ + rc = execve(NEXT_PROGRAM, exec_argv, exec_envp); + if (rc) { + perror("execve"); + ksft_test_result_fail("child execve failed %d\n", rc); + exit(-1); + } + } + + rc = waitpid(-1, &status, 0); + if (rc < 0) { + ksft_test_result_fail("waitpid failed\n"); + return -3; + } + + if ((WIFEXITED(status) && WEXITSTATUS(status) == -1) || + WIFSIGNALED(status)) { + ksft_test_result_fail("child exited abnormally\n"); + return -4; + } + + return WEXITSTATUS(status); +} + +int test_and_compare_child(long provided, long expected, int inherit) +{ + int rc; + + rc = prctl(PR_RISCV_V_SET_CONTROL, provided); + if (rc != 0) { + ksft_test_result_fail("prctl with provided arg %lx failed with code %d\n", + provided, rc); + return -1; + } + rc = launch_test(inherit); + if (rc != expected) { + ksft_test_result_fail("Test failed, check %d != %d\n", rc, + expected); + return -2; + } + return 0; +} + +#define PR_RISCV_V_VSTATE_CTRL_CUR_SHIFT 0 +#define PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT 2 + +int main(void) +{ + struct riscv_hwprobe pair; + long flag, expected; + long rc; + + pair.key = RISCV_HWPROBE_KEY_IMA_EXT_0; + rc = riscv_hwprobe(&pair, 1, 0, NULL, 0); + if (rc < 0) { + ksft_test_result_fail("hwprobe() failed with %d\n", rc); + return -1; + } + + if (pair.key != RISCV_HWPROBE_KEY_IMA_EXT_0) { + ksft_test_result_fail("hwprobe cannot probe RISCV_HWPROBE_KEY_IMA_EXT_0\n"); + return -2; + } + + if (!(pair.value & RISCV_HWPROBE_IMA_V)) { + rc = prctl(PR_RISCV_V_GET_CONTROL); + if (rc != -1 || errno != EINVAL) { + ksft_test_result_fail("GET_CONTROL should fail on kernel/hw without V\n"); + return -3; + } + + rc = prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_ON); + if (rc != -1 || errno != EINVAL) { + ksft_test_result_fail("GET_CONTROL should fail on kernel/hw without V\n"); + return -4; + } + + ksft_test_result_skip("Vector not supported\n"); + return 0; + } + + flag = PR_RISCV_V_VSTATE_CTRL_ON; + rc = prctl(PR_RISCV_V_SET_CONTROL, flag); + if (rc != 0) { + ksft_test_result_fail("Enabling V for current should always success\n"); + return -5; + } + + flag = PR_RISCV_V_VSTATE_CTRL_OFF; + rc = prctl(PR_RISCV_V_SET_CONTROL, flag); + if (rc != -1 || errno != EPERM) { + ksft_test_result_fail("Disabling current's V alive must fail with EPERM(%d)\n", + errno); + return -5; + } + + /* Turn on next's vector explicitly and test */ + flag = PR_RISCV_V_VSTATE_CTRL_ON << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + if (test_and_compare_child(flag, PR_RISCV_V_VSTATE_CTRL_ON, 0)) + return -6; + + /* Turn off next's vector explicitly and test */ + flag = PR_RISCV_V_VSTATE_CTRL_OFF << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + if (test_and_compare_child(flag, PR_RISCV_V_VSTATE_CTRL_OFF, 0)) + return -7; + + /* Turn on next's vector explicitly and test inherit */ + flag = PR_RISCV_V_VSTATE_CTRL_ON << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + flag |= PR_RISCV_V_VSTATE_CTRL_INHERIT; + expected = flag | PR_RISCV_V_VSTATE_CTRL_ON; + if (test_and_compare_child(flag, expected, 0)) + return -8; + + if (test_and_compare_child(flag, expected, 1)) + return -9; + + /* Turn off next's vector explicitly and test inherit */ + flag = PR_RISCV_V_VSTATE_CTRL_OFF << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + flag |= PR_RISCV_V_VSTATE_CTRL_INHERIT; + expected = flag | PR_RISCV_V_VSTATE_CTRL_OFF; + if (test_and_compare_child(flag, expected, 0)) + return -10; + + if (test_and_compare_child(flag, expected, 1)) + return -11; + + /* arguments should fail with EINVAL */ + rc = prctl(PR_RISCV_V_SET_CONTROL, 0xff0); + if (rc != -1 || errno != EINVAL) { + ksft_test_result_fail("Undefined control argument should return EINVAL\n"); + return -12; + } + + rc = prctl(PR_RISCV_V_SET_CONTROL, 0x3); + if (rc != -1 || errno != EINVAL) { + ksft_test_result_fail("Undefined control argument should return EINVAL\n"); + return -12; + } + + rc = prctl(PR_RISCV_V_SET_CONTROL, 0xc); + if (rc != -1 || errno != EINVAL) { + ksft_test_result_fail("Undefined control argument should return EINVAL\n"); + return -12; + } + + rc = prctl(PR_RISCV_V_SET_CONTROL, 0xc); + if (rc != -1 || errno != EINVAL) { + ksft_test_result_fail("Undefined control argument should return EINVAL\n"); + return -12; + } + + ksft_test_result_pass("tests for riscv_v_vstate_ctrl pass\n"); + ksft_exit_pass(); + return 0; +} diff --git a/tools/testing/selftests/run_kselftest.sh b/tools/testing/selftests/run_kselftest.sh index 97165a83df63..92743980e553 100755 --- a/tools/testing/selftests/run_kselftest.sh +++ b/tools/testing/selftests/run_kselftest.sh @@ -26,6 +26,7 @@ Usage: $0 [OPTIONS] -l | --list List the available collection:test entries -d | --dry-run Don't actually run any tests -h | --help Show this usage info + -o | --override-timeout Number of seconds after which we timeout EOF exit $1 } @@ -33,6 +34,7 @@ EOF COLLECTIONS="" TESTS="" dryrun="" +kselftest_override_timeout="" while true; do case "$1" in -s | --summary) @@ -51,6 +53,9 @@ while true; do -d | --dry-run) dryrun="echo" shift ;; + -o | --override-timeout) + kselftest_override_timeout="$2" + shift 2 ;; -h | --help) usage 0 ;; "") @@ -85,7 +90,7 @@ if [ -n "$TESTS" ]; then available="$(echo "$valid" | sed -e 's/ /\n/g')" fi -collections=$(echo "$available" | cut -d: -f1 | uniq) +collections=$(echo "$available" | cut -d: -f1 | sort | uniq) for collection in $collections ; do [ -w /dev/kmsg ] && echo "kselftest: Running tests in $collection" >> /dev/kmsg tests=$(echo "$available" | grep "^$collection:" | cut -d: -f2) diff --git a/tools/testing/selftests/sysctl/sysctl.sh b/tools/testing/selftests/sysctl/sysctl.sh index bfc54b422f25..444b2befda82 100755 --- a/tools/testing/selftests/sysctl/sysctl.sh +++ b/tools/testing/selftests/sysctl/sysctl.sh @@ -14,23 +14,27 @@ TEST_FILE=$(mktemp) # This represents # -# TEST_ID:TEST_COUNT:ENABLED:TARGET +# TEST_ID:TEST_COUNT:ENABLED:TARGET:SKIP_NO_TARGET # # TEST_ID: is the test id number # TEST_COUNT: number of times we should run the test # ENABLED: 1 if enabled, 0 otherwise # TARGET: test target file required on the test_sysctl module +# SKIP_NO_TARGET: 1 skip if TARGET not there +# 0 run eventhough TARGET not there # # Once these are enabled please leave them as-is. Write your own test, # we have tons of space. -ALL_TESTS="0001:1:1:int_0001" -ALL_TESTS="$ALL_TESTS 0002:1:1:string_0001" -ALL_TESTS="$ALL_TESTS 0003:1:1:int_0002" -ALL_TESTS="$ALL_TESTS 0004:1:1:uint_0001" -ALL_TESTS="$ALL_TESTS 0005:3:1:int_0003" -ALL_TESTS="$ALL_TESTS 0006:50:1:bitmap_0001" -ALL_TESTS="$ALL_TESTS 0007:1:1:boot_int" -ALL_TESTS="$ALL_TESTS 0008:1:1:match_int" +ALL_TESTS="0001:1:1:int_0001:1" +ALL_TESTS="$ALL_TESTS 0002:1:1:string_0001:1" +ALL_TESTS="$ALL_TESTS 0003:1:1:int_0002:1" +ALL_TESTS="$ALL_TESTS 0004:1:1:uint_0001:1" +ALL_TESTS="$ALL_TESTS 0005:3:1:int_0003:1" +ALL_TESTS="$ALL_TESTS 0006:50:1:bitmap_0001:1" +ALL_TESTS="$ALL_TESTS 0007:1:1:boot_int:1" +ALL_TESTS="$ALL_TESTS 0008:1:1:match_int:1" +ALL_TESTS="$ALL_TESTS 0009:1:1:unregister_error:0" +ALL_TESTS="$ALL_TESTS 0010:1:1:mnt/mnt_error:0" function allow_user_defaults() { @@ -613,7 +617,6 @@ target_exists() TEST_ID="$2" if [ ! -f ${TARGET} ] ; then - echo "Target for test $TEST_ID: $TARGET not exist, skipping test ..." return 0 fi return 1 @@ -730,7 +733,7 @@ sysctl_test_0005() sysctl_test_0006() { - TARGET="${SYSCTL}/bitmap_0001" + TARGET="${SYSCTL}/$(get_test_target 0006)" reset_vals ORIG="" run_bitmaptest @@ -738,7 +741,7 @@ sysctl_test_0006() sysctl_test_0007() { - TARGET="${SYSCTL}/boot_int" + TARGET="${SYSCTL}/$(get_test_target 0007)" if [ ! -f $TARGET ]; then echo "Skipping test for $TARGET as it is not present ..." return $ksft_skip @@ -778,7 +781,7 @@ sysctl_test_0007() sysctl_test_0008() { - TARGET="${SYSCTL}/match_int" + TARGET="${SYSCTL}/$(get_test_target 0008)" if [ ! -f $TARGET ]; then echo "Skipping test for $TARGET as it is not present ..." return $ksft_skip @@ -797,6 +800,34 @@ sysctl_test_0008() return 0 } +sysctl_test_0009() +{ + TARGET="${SYSCTL}/$(get_test_target 0009)" + echo -n "Testing if $TARGET unregistered correctly ..." + if [ -d $TARGET ]; then + echo "TEST FAILED" + rc=1 + test_rc + fi + + echo "ok" + return 0 +} + +sysctl_test_0010() +{ + TARGET="${SYSCTL}/$(get_test_target 0010)" + echo -n "Testing that $TARGET was not created ..." + if [ -d $TARGET ]; then + echo "TEST FAILED" + rc=1 + test_rc + fi + + echo "ok" + return 0 +} + list_tests() { echo "Test ID list:" @@ -813,6 +844,8 @@ list_tests() echo "0006 x $(get_test_count 0006) - tests proc_do_large_bitmap()" echo "0007 x $(get_test_count 0007) - tests setting sysctl from kernel boot param" echo "0008 x $(get_test_count 0008) - tests sysctl macro values match" + echo "0009 x $(get_test_count 0009) - tests sysct unregister" + echo "0010 x $(get_test_count 0010) - tests sysct mount point" } usage() @@ -857,38 +890,65 @@ function test_num() usage fi } +function remove_leading_zeros() +{ + echo $1 | sed 's/^0*//' +} function get_test_count() { test_num $1 - TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') + awk_field=$(remove_leading_zeros $1) + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}') echo ${TEST_DATA} | awk -F":" '{print $2}' } function get_test_enabled() { test_num $1 - TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') + awk_field=$(remove_leading_zeros $1) + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}') echo ${TEST_DATA} | awk -F":" '{print $3}' } function get_test_target() { test_num $1 - TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') + awk_field=$(remove_leading_zeros $1) + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}') echo ${TEST_DATA} | awk -F":" '{print $4}' } +function get_test_skip_no_target() +{ + test_num $1 + awk_field=$(remove_leading_zeros $1) + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}') + echo ${TEST_DATA} | awk -F":" '{print $5}' +} + +function skip_test() +{ + TEST_ID=$1 + TEST_TARGET=$2 + if target_exists $TEST_TARGET $TEST_ID; then + TEST_SKIP=$(get_test_skip_no_target $TEST_ID) + if [[ $TEST_SKIP -eq "1" ]]; then + echo "Target for test $TEST_ID: $TEST_TARGET not exist, skipping test ..." + return 0 + fi + fi + return 1 +} + function run_all_tests() { for i in $ALL_TESTS ; do - TEST_ID=${i%:*:*:*} + TEST_ID=${i%:*:*:*:*} ENABLED=$(get_test_enabled $TEST_ID) TEST_COUNT=$(get_test_count $TEST_ID) TEST_TARGET=$(get_test_target $TEST_ID) - if target_exists $TEST_TARGET $TEST_ID; then - continue - fi + if [[ $ENABLED -eq "1" ]]; then test_case $TEST_ID $TEST_COUNT $TEST_TARGET fi @@ -923,18 +983,19 @@ function watch_case() function test_case() { + TEST_ID=$1 NUM_TESTS=$2 + TARGET=$3 - i=0 - - if target_exists $3 $1; then - continue + if skip_test $TEST_ID $TARGET; then + return fi + i=0 while [ $i -lt $NUM_TESTS ]; do - test_num $1 - watch_log $i ${TEST_NAME}_test_$1 noclear - RUN_TEST=${TEST_NAME}_test_$1 + test_num $TEST_ID + watch_log $i ${TEST_NAME}_test_${TEST_ID} noclear + RUN_TEST=${TEST_NAME}_test_${TEST_ID} $RUN_TEST let i=$i+1 done diff --git a/tools/testing/selftests/tc-testing/config b/tools/testing/selftests/tc-testing/config index 4638c63a339f..6e73b09c20c8 100644 --- a/tools/testing/selftests/tc-testing/config +++ b/tools/testing/selftests/tc-testing/config @@ -6,20 +6,18 @@ CONFIG_NF_CONNTRACK_MARK=y CONFIG_NF_CONNTRACK_ZONES=y CONFIG_NF_CONNTRACK_LABELS=y CONFIG_NF_NAT=m +CONFIG_NETFILTER_XT_TARGET_LOG=m CONFIG_NET_SCHED=y # # Queueing/Scheduling # -CONFIG_NET_SCH_ATM=m CONFIG_NET_SCH_CAKE=m -CONFIG_NET_SCH_CBQ=m CONFIG_NET_SCH_CBS=m CONFIG_NET_SCH_CHOKE=m CONFIG_NET_SCH_CODEL=m CONFIG_NET_SCH_DRR=m -CONFIG_NET_SCH_DSMARK=m CONFIG_NET_SCH_ETF=m CONFIG_NET_SCH_FQ=m CONFIG_NET_SCH_FQ_CODEL=m @@ -57,8 +55,6 @@ CONFIG_NET_CLS_FLOW=m CONFIG_NET_CLS_FLOWER=m CONFIG_NET_CLS_MATCHALL=m CONFIG_NET_CLS_ROUTE4=m -CONFIG_NET_CLS_RSVP=m -CONFIG_NET_CLS_TCINDEX=m CONFIG_NET_EMATCH=y CONFIG_NET_EMATCH_STACK=32 CONFIG_NET_EMATCH_CMP=m diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json b/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json new file mode 100644 index 000000000000..c4c778e83da2 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json @@ -0,0 +1,25 @@ +[ + { + "id": "c2b4", + "name": "soft lockup alarm will be not generated after delete the prio 0 filter of the chain", + "category": [ + "filter", + "chain" + ], + "setup": [ + "$IP link add dev $DUMMY type dummy || /bin/true", + "$TC qdisc add dev $DUMMY root handle 1: htb default 1", + "$TC chain add dev $DUMMY", + "$TC filter del dev $DUMMY chain 0 parent 1: prio 0" + ], + "cmdUnderTest": "$TC filter add dev $DUMMY chain 0 parent 1:", + "expExitCode": "2", + "verifyCmd": "$TC chain ls dev $DUMMY", + "matchPattern": "chain parent 1: chain 0", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY root handle 1: htb default 1", + "$IP link del dev $DUMMY type dummy" + ] + } +] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/mq.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/mq.json index 44fbfc6caec7..e3d2de5c184f 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/mq.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/mq.json @@ -155,5 +155,28 @@ "teardown": [ "echo \"1\" > /sys/bus/netdevsim/del_device" ] - } + }, + { + "id": "0531", + "name": "Replace mq with invalid parent ID", + "category": [ + "qdisc", + "mq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 16\" > /sys/bus/netdevsim/new_device", + "$TC qdisc add dev $ETH root handle ffff: mq" + ], + "cmdUnderTest": "$TC qdisc replace dev $ETH parent ffff:fff1 handle ffff: mq", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $ETH", + "matchPattern": "qdisc [a-zA-Z0-9_]+ 0: parent ffff", + "matchCount": "16", + "teardown": [ + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + } ] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/sfb.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/sfb.json index ba2f5e79cdbf..e21c7f22c6d4 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/sfb.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/sfb.json @@ -58,10 +58,10 @@ "setup": [ "$IP link add dev $DUMMY type dummy || /bin/true" ], - "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root sfb db 10", + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root sfb db 100", "expExitCode": "0", "verifyCmd": "$TC qdisc show dev $DUMMY", - "matchPattern": "qdisc sfb 1: root refcnt [0-9]+ rehash 600s db 10ms", + "matchPattern": "qdisc sfb 1: root refcnt [0-9]+ rehash 600s db 100ms", "matchCount": "1", "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root", diff --git a/tools/testing/selftests/tc-testing/tdc.sh b/tools/testing/selftests/tc-testing/tdc.sh index afb0cd86fa3d..eb357bd7923c 100755 --- a/tools/testing/selftests/tc-testing/tdc.sh +++ b/tools/testing/selftests/tc-testing/tdc.sh @@ -2,5 +2,6 @@ # SPDX-License-Identifier: GPL-2.0 modprobe netdevsim +modprobe sch_teql ./tdc.py -c actions --nobuildebpf ./tdc.py -c qdisc diff --git a/tools/testing/selftests/tty/.gitignore b/tools/testing/selftests/tty/.gitignore new file mode 100644 index 000000000000..fe70462a4aad --- /dev/null +++ b/tools/testing/selftests/tty/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +tty_tstamp_update diff --git a/tools/testing/selftests/tty/Makefile b/tools/testing/selftests/tty/Makefile new file mode 100644 index 000000000000..50d7027b2ae3 --- /dev/null +++ b/tools/testing/selftests/tty/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS = -O2 -Wall +TEST_GEN_PROGS := tty_tstamp_update + +include ../lib.mk diff --git a/tools/testing/selftests/tty/tty_tstamp_update.c b/tools/testing/selftests/tty/tty_tstamp_update.c new file mode 100644 index 000000000000..0ee97943dccc --- /dev/null +++ b/tools/testing/selftests/tty/tty_tstamp_update.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <linux/limits.h> + +#include "../kselftest.h" + +#define MIN_TTY_PATH_LEN 8 + +static bool tty_valid(char *tty) +{ + if (strlen(tty) < MIN_TTY_PATH_LEN) + return false; + + if (strncmp(tty, "/dev/tty", MIN_TTY_PATH_LEN) == 0 || + strncmp(tty, "/dev/pts", MIN_TTY_PATH_LEN) == 0) + return true; + + return false; +} + +static int write_dev_tty(void) +{ + FILE *f; + int r = 0; + + f = fopen("/dev/tty", "r+"); + if (!f) + return -errno; + + r = fprintf(f, "hello, world!\n"); + if (r != strlen("hello, world!\n")) + r = -EIO; + + fclose(f); + return r; +} + +int main(int argc, char **argv) +{ + int r; + char tty[PATH_MAX] = {}; + struct stat st1, st2; + + ksft_print_header(); + ksft_set_plan(1); + + r = readlink("/proc/self/fd/0", tty, PATH_MAX); + if (r < 0) + ksft_exit_fail_msg("readlink on /proc/self/fd/0 failed: %m\n"); + + if (!tty_valid(tty)) + ksft_exit_skip("invalid tty path '%s'\n", tty); + + r = stat(tty, &st1); + if (r < 0) + ksft_exit_fail_msg("stat failed on tty path '%s': %m\n", tty); + + /* We need to wait at least 8 seconds in order to observe timestamp change */ + /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fbf47635315ab308c9b58a1ea0906e711a9228de */ + sleep(10); + + r = write_dev_tty(); + if (r < 0) + ksft_exit_fail_msg("failed to write to /dev/tty: %s\n", + strerror(-r)); + + r = stat(tty, &st2); + if (r < 0) + ksft_exit_fail_msg("stat failed on tty path '%s': %m\n", tty); + + /* We wrote to the terminal so timestamps should have been updated */ + if (st1.st_atim.tv_sec == st2.st_atim.tv_sec && + st1.st_mtim.tv_sec == st2.st_mtim.tv_sec) { + ksft_test_result_fail("tty timestamps not updated\n"); + ksft_exit_fail(); + } + + ksft_test_result_pass( + "timestamps of terminal '%s' updated after write to /dev/tty\n", tty); + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/user_events/dyn_test.c b/tools/testing/selftests/user_events/dyn_test.c index 8879a7b04c6a..d6979a48478f 100644 --- a/tools/testing/selftests/user_events/dyn_test.c +++ b/tools/testing/selftests/user_events/dyn_test.c @@ -16,42 +16,140 @@ #include "../kselftest_harness.h" -const char *dyn_file = "/sys/kernel/tracing/dynamic_events"; -const char *clear = "!u:__test_event"; +const char *abi_file = "/sys/kernel/tracing/user_events_data"; +const char *enable_file = "/sys/kernel/tracing/events/user_events/__test_event/enable"; -static int Append(const char *value) +static bool wait_for_delete(void) { - int fd = open(dyn_file, O_RDWR | O_APPEND); - int ret = write(fd, value, strlen(value)); + int i; + + for (i = 0; i < 1000; ++i) { + int fd = open(enable_file, O_RDONLY); + + if (fd == -1) + return true; + + close(fd); + usleep(1000); + } + + return false; +} + +static int reg_event(int fd, int *check, int bit, const char *value) +{ + struct user_reg reg = {0}; + + reg.size = sizeof(reg); + reg.name_args = (__u64)value; + reg.enable_bit = bit; + reg.enable_addr = (__u64)check; + reg.enable_size = sizeof(*check); + + if (ioctl(fd, DIAG_IOCSREG, ®) == -1) + return -1; + + return 0; +} + +static int unreg_event(int fd, int *check, int bit) +{ + struct user_unreg unreg = {0}; + + unreg.size = sizeof(unreg); + unreg.disable_bit = bit; + unreg.disable_addr = (__u64)check; + + return ioctl(fd, DIAG_IOCSUNREG, &unreg); +} + +static int parse(int *check, const char *value) +{ + int fd = open(abi_file, O_RDWR); + int ret; + + if (fd == -1) + return -1; + + /* Until we have persist flags via dynamic events, use the base name */ + if (value[0] != 'u' || value[1] != ':') { + close(fd); + return -1; + } + + ret = reg_event(fd, check, 31, value + 2); + + if (ret != -1) { + if (unreg_event(fd, check, 31) == -1) + printf("WARN: Couldn't unreg event\n"); + } close(fd); + return ret; } -#define CLEAR() \ +static int check_match(int *check, const char *first, const char *second, bool *match) +{ + int fd = open(abi_file, O_RDWR); + int ret = -1; + + if (fd == -1) + return -1; + + if (reg_event(fd, check, 31, first) == -1) + goto cleanup; + + if (reg_event(fd, check, 30, second) == -1) { + if (errno == EADDRINUSE) { + /* Name is in use, with different fields */ + *match = false; + ret = 0; + } + + goto cleanup; + } + + *match = true; + ret = 0; +cleanup: + unreg_event(fd, check, 31); + unreg_event(fd, check, 30); + + close(fd); + + wait_for_delete(); + + return ret; +} + +#define TEST_MATCH(x, y) \ do { \ - int ret = Append(clear); \ - if (ret == -1) \ - ASSERT_EQ(ENOENT, errno); \ + bool match; \ + ASSERT_NE(-1, check_match(&self->check, x, y, &match)); \ + ASSERT_EQ(true, match); \ } while (0) -#define TEST_PARSE(x) \ +#define TEST_NMATCH(x, y) \ do { \ - ASSERT_NE(-1, Append(x)); \ - CLEAR(); \ + bool match; \ + ASSERT_NE(-1, check_match(&self->check, x, y, &match)); \ + ASSERT_EQ(false, match); \ } while (0) -#define TEST_NPARSE(x) ASSERT_EQ(-1, Append(x)) +#define TEST_PARSE(x) ASSERT_NE(-1, parse(&self->check, x)) + +#define TEST_NPARSE(x) ASSERT_EQ(-1, parse(&self->check, x)) FIXTURE(user) { + int check; }; FIXTURE_SETUP(user) { - CLEAR(); } FIXTURE_TEARDOWN(user) { - CLEAR(); + wait_for_delete(); } TEST_F(user, basic_types) { @@ -95,33 +193,30 @@ TEST_F(user, size_types) { TEST_NPARSE("u:__test_event char a 20"); } -TEST_F(user, flags) { - /* Should work */ - TEST_PARSE("u:__test_event:BPF_ITER u32 a"); - /* Forward compat */ - TEST_PARSE("u:__test_event:BPF_ITER,FLAG_FUTURE u32 a"); -} - TEST_F(user, matching) { - /* Register */ - ASSERT_NE(-1, Append("u:__test_event struct custom a 20")); - /* Should not match */ - TEST_NPARSE("!u:__test_event struct custom b"); - /* Should match */ - TEST_PARSE("!u:__test_event struct custom a"); - /* Multi field reg */ - ASSERT_NE(-1, Append("u:__test_event u32 a; u32 b")); - /* Non matching cases */ - TEST_NPARSE("!u:__test_event u32 a"); - TEST_NPARSE("!u:__test_event u32 b"); - TEST_NPARSE("!u:__test_event u32 a; u32 "); - TEST_NPARSE("!u:__test_event u32 a; u32 a"); - /* Matching case */ - TEST_PARSE("!u:__test_event u32 a; u32 b"); - /* Register */ - ASSERT_NE(-1, Append("u:__test_event u32 a; u32 b")); - /* Ensure trailing semi-colon case */ - TEST_PARSE("!u:__test_event u32 a; u32 b;"); + /* Single name matches */ + TEST_MATCH("__test_event u32 a", + "__test_event u32 a"); + + /* Multiple names match */ + TEST_MATCH("__test_event u32 a; u32 b", + "__test_event u32 a; u32 b"); + + /* Multiple names match with dangling ; */ + TEST_MATCH("__test_event u32 a; u32 b", + "__test_event u32 a; u32 b;"); + + /* Single name doesn't match */ + TEST_NMATCH("__test_event u32 a", + "__test_event u32 b"); + + /* Multiple names don't match */ + TEST_NMATCH("__test_event u32 a; u32 b", + "__test_event u32 b; u32 a"); + + /* Types don't match */ + TEST_NMATCH("__test_event u64 a; u64 b", + "__test_event u32 a; u32 b"); } int main(int argc, char **argv) diff --git a/tools/testing/selftests/user_events/ftrace_test.c b/tools/testing/selftests/user_events/ftrace_test.c index 7c99cef94a65..5beb0aef1d81 100644 --- a/tools/testing/selftests/user_events/ftrace_test.c +++ b/tools/testing/selftests/user_events/ftrace_test.c @@ -102,30 +102,56 @@ err: return -1; } +static bool wait_for_delete(void) +{ + int i; + + for (i = 0; i < 1000; ++i) { + int fd = open(enable_file, O_RDONLY); + + if (fd == -1) + return true; + + close(fd); + usleep(1000); + } + + return false; +} + static int clear(int *check) { struct user_unreg unreg = {0}; + int fd; unreg.size = sizeof(unreg); unreg.disable_bit = 31; unreg.disable_addr = (__u64)check; - int fd = open(data_file, O_RDWR); + fd = open(data_file, O_RDWR); if (fd == -1) return -1; if (ioctl(fd, DIAG_IOCSUNREG, &unreg) == -1) if (errno != ENOENT) - return -1; - - if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) - if (errno != ENOENT) - return -1; + goto fail; + + if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) { + if (errno == EBUSY) { + if (!wait_for_delete()) + goto fail; + } else if (errno != ENOENT) + goto fail; + } close(fd); return 0; +fail: + close(fd); + + return -1; } static int check_print_fmt(const char *event, const char *expected, int *check) @@ -155,9 +181,8 @@ static int check_print_fmt(const char *event, const char *expected, int *check) /* Register should work */ ret = ioctl(fd, DIAG_IOCSREG, ®); - close(fd); - if (ret != 0) { + close(fd); printf("Reg failed in fmt\n"); return ret; } @@ -165,6 +190,8 @@ static int check_print_fmt(const char *event, const char *expected, int *check) /* Ensure correct print_fmt */ ret = get_print_fmt(print_fmt, sizeof(print_fmt)); + close(fd); + if (ret != 0) return ret; @@ -228,6 +255,12 @@ TEST_F(user, register_events) { ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); + /* Multiple registers to same name but different args should fail */ + reg.enable_bit = 29; + reg.name_args = (__u64)"__test_event u32 field1;"; + ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(EADDRINUSE, errno); + /* Ensure disabled */ self->enable_fd = open(enable_file, O_RDWR); ASSERT_NE(-1, self->enable_fd); @@ -250,10 +283,10 @@ TEST_F(user, register_events) { unreg.disable_bit = 30; ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSUNREG, &unreg)); - /* Delete should work only after close and unregister */ + /* Delete should have been auto-done after close and unregister */ close(self->data_fd); - self->data_fd = open(data_file, O_RDWR); - ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSDEL, "__test_event")); + + ASSERT_EQ(true, wait_for_delete()); } TEST_F(user, write_events) { @@ -291,6 +324,10 @@ TEST_F(user, write_events) { io[0].iov_base = ®.write_index; io[0].iov_len = sizeof(reg.write_index); + /* Write should return -EBADF when event is not enabled */ + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EBADF, errno); + /* Enable event */ self->enable_fd = open(enable_file, O_RDWR); ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) @@ -310,6 +347,39 @@ TEST_F(user, write_events) { ASSERT_EQ(EINVAL, errno); } +TEST_F(user, write_empty_events) { + struct user_reg reg = {0}; + struct iovec io[1]; + int before = 0, after = 0; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event"; + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); + + io[0].iov_base = ®.write_index; + io[0].iov_len = sizeof(reg.write_index); + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_EQ(0, self->check); + + /* Enable event */ + self->enable_fd = open(enable_file, O_RDWR); + ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) + + /* Event should now be enabled */ + ASSERT_EQ(1 << reg.enable_bit, self->check); + + /* Write should make it out to ftrace buffers */ + before = trace_bytes(); + ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 1)); + after = trace_bytes(); + ASSERT_GT(after, before); +} + TEST_F(user, write_fault) { struct user_reg reg = {0}; struct iovec io[2]; @@ -334,6 +404,10 @@ TEST_F(user, write_fault) { ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); + /* Enable event */ + self->enable_fd = open(enable_file, O_RDWR); + ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) + /* Write should work normally */ ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 2)); diff --git a/tools/testing/selftests/user_events/perf_test.c b/tools/testing/selftests/user_events/perf_test.c index a070258d4449..8b09be566fa2 100644 --- a/tools/testing/selftests/user_events/perf_test.c +++ b/tools/testing/selftests/user_events/perf_test.c @@ -81,6 +81,32 @@ static int get_offset(void) return offset; } +static int clear(int *check) +{ + struct user_unreg unreg = {0}; + + unreg.size = sizeof(unreg); + unreg.disable_bit = 31; + unreg.disable_addr = (__u64)check; + + int fd = open(data_file, O_RDWR); + + if (fd == -1) + return -1; + + if (ioctl(fd, DIAG_IOCSUNREG, &unreg) == -1) + if (errno != ENOENT) + return -1; + + if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) + if (errno != ENOENT) + return -1; + + close(fd); + + return 0; +} + FIXTURE(user) { int data_fd; int check; @@ -93,6 +119,9 @@ FIXTURE_SETUP(user) { FIXTURE_TEARDOWN(user) { close(self->data_fd); + + if (clear(&self->check) != 0) + printf("WARNING: Clear didn't work!\n"); } TEST_F(user, perf_write) { @@ -160,6 +189,59 @@ TEST_F(user, perf_write) { ASSERT_EQ(0, self->check); } +TEST_F(user, perf_empty_events) { + struct perf_event_attr pe = {0}; + struct user_reg reg = {0}; + struct perf_event_mmap_page *perf_page; + int page_size = sysconf(_SC_PAGESIZE); + int id, fd; + __u32 *val; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event"; + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_EQ(0, self->check); + + /* Id should be there */ + id = get_id(); + ASSERT_NE(-1, id); + + pe.type = PERF_TYPE_TRACEPOINT; + pe.size = sizeof(pe); + pe.config = id; + pe.sample_type = PERF_SAMPLE_RAW; + pe.sample_period = 1; + pe.wakeup_events = 1; + + /* Tracepoint attach should work */ + fd = perf_event_open(&pe, 0, -1, -1, 0); + ASSERT_NE(-1, fd); + + perf_page = mmap(NULL, page_size * 2, PROT_READ, MAP_SHARED, fd, 0); + ASSERT_NE(MAP_FAILED, perf_page); + + /* Status should be updated */ + ASSERT_EQ(1 << reg.enable_bit, self->check); + + /* Ensure write shows up at correct offset */ + ASSERT_NE(-1, write(self->data_fd, ®.write_index, + sizeof(reg.write_index))); + val = (void *)(((char *)perf_page) + perf_page->data_offset); + ASSERT_EQ(PERF_RECORD_SAMPLE, *val); + + munmap(perf_page, page_size * 2); + close(fd); + + /* Status should be updated */ + ASSERT_EQ(0, self->check); +} + int main(int argc, char **argv) { return test_harness_run(argc, argv); diff --git a/tools/testing/selftests/vDSO/vdso_test_clock_getres.c b/tools/testing/selftests/vDSO/vdso_test_clock_getres.c index 15dcee16ff72..38d46a8bf7cb 100644 --- a/tools/testing/selftests/vDSO/vdso_test_clock_getres.c +++ b/tools/testing/selftests/vDSO/vdso_test_clock_getres.c @@ -84,12 +84,12 @@ static inline int vdso_test_clock(unsigned int clock_id) int main(int argc, char **argv) { - int ret; + int ret = 0; #if _POSIX_TIMERS > 0 #ifdef CLOCK_REALTIME - ret = vdso_test_clock(CLOCK_REALTIME); + ret += vdso_test_clock(CLOCK_REALTIME); #endif #ifdef CLOCK_BOOTTIME diff --git a/tools/testing/selftests/wireguard/netns.sh b/tools/testing/selftests/wireguard/netns.sh index 69c7796c7ca9..405ff262ca93 100755 --- a/tools/testing/selftests/wireguard/netns.sh +++ b/tools/testing/selftests/wireguard/netns.sh @@ -514,10 +514,32 @@ n2 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/all/rp_filter' n1 ping -W 1 -c 1 192.168.241.2 [[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.3:1" ]] -ip1 link del veth1 -ip1 link del veth3 -ip1 link del wg0 -ip2 link del wg0 +ip1 link del dev veth3 +ip1 link del dev wg0 +ip2 link del dev wg0 + +# Make sure persistent keep alives are sent when an adapter comes up +ip1 link add dev wg0 type wireguard +n1 wg set wg0 private-key <(echo "$key1") peer "$pub2" endpoint 10.0.0.1:1 persistent-keepalive 1 +read _ _ tx_bytes < <(n1 wg show wg0 transfer) +[[ $tx_bytes -eq 0 ]] +ip1 link set dev wg0 up +read _ _ tx_bytes < <(n1 wg show wg0 transfer) +[[ $tx_bytes -gt 0 ]] +ip1 link del dev wg0 +# This should also happen even if the private key is set later +ip1 link add dev wg0 type wireguard +n1 wg set wg0 peer "$pub2" endpoint 10.0.0.1:1 persistent-keepalive 1 +read _ _ tx_bytes < <(n1 wg show wg0 transfer) +[[ $tx_bytes -eq 0 ]] +ip1 link set dev wg0 up +read _ _ tx_bytes < <(n1 wg show wg0 transfer) +[[ $tx_bytes -eq 0 ]] +n1 wg set wg0 private-key <(echo "$key1") +read _ _ tx_bytes < <(n1 wg show wg0 transfer) +[[ $tx_bytes -gt 0 ]] +ip1 link del dev veth1 +ip1 link del dev wg0 # We test that Netlink/IPC is working properly by doing things that usually cause split responses ip0 link add dev wg0 type wireguard diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c index 3ca7a3853943..245e9344932b 100644 --- a/tools/tracing/rtla/src/osnoise.c +++ b/tools/tracing/rtla/src/osnoise.c @@ -841,6 +841,67 @@ static void osnoise_put_irq_disable(struct osnoise_context *context) context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL; } +static int osnoise_get_workload(struct osnoise_context *context) +{ + if (context->opt_workload != OSNOISE_OPTION_INIT_VAL) + return context->opt_workload; + + if (context->orig_opt_workload != OSNOISE_OPTION_INIT_VAL) + return context->orig_opt_workload; + + context->orig_opt_workload = osnoise_options_get_option("OSNOISE_WORKLOAD"); + + return context->orig_opt_workload; +} + +int osnoise_set_workload(struct osnoise_context *context, bool onoff) +{ + int opt_workload = osnoise_get_workload(context); + int retval; + + if (opt_workload == OSNOISE_OPTION_INIT_VAL) + return -1; + + if (opt_workload == onoff) + return 0; + + retval = osnoise_options_set_option("OSNOISE_WORKLOAD", onoff); + if (retval < 0) + return -1; + + context->opt_workload = onoff; + + return 0; +} + +static void osnoise_restore_workload(struct osnoise_context *context) +{ + int retval; + + if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL) + return; + + if (context->orig_opt_workload == context->opt_workload) + goto out_done; + + retval = osnoise_options_set_option("OSNOISE_WORKLOAD", context->orig_opt_workload); + if (retval < 0) + err_msg("Could not restore original OSNOISE_WORKLOAD option\n"); + +out_done: + context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL; +} + +static void osnoise_put_workload(struct osnoise_context *context) +{ + osnoise_restore_workload(context); + + if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL) + return; + + context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL; +} + /* * enable_osnoise - enable osnoise tracer in the trace_instance */ @@ -908,6 +969,9 @@ struct osnoise_context *osnoise_context_alloc(void) context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL; context->opt_irq_disable = OSNOISE_OPTION_INIT_VAL; + context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL; + context->opt_workload = OSNOISE_OPTION_INIT_VAL; + osnoise_get_context(context); return context; @@ -935,6 +999,7 @@ void osnoise_put_context(struct osnoise_context *context) osnoise_put_print_stack(context); osnoise_put_tracing_thresh(context); osnoise_put_irq_disable(context); + osnoise_put_workload(context); free(context); } diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h index 4dcf22ccd704..555f4f4903cc 100644 --- a/tools/tracing/rtla/src/osnoise.h +++ b/tools/tracing/rtla/src/osnoise.h @@ -42,6 +42,10 @@ struct osnoise_context { /* -1 as init value because 0 is off */ int orig_opt_irq_disable; int opt_irq_disable; + + /* -1 as init value because 0 is off */ + int orig_opt_workload; + int opt_workload; }; /* @@ -84,6 +88,7 @@ int osnoise_set_print_stack(struct osnoise_context *context, long long print_stack); int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff); +int osnoise_set_workload(struct osnoise_context *context, bool onoff); /* * osnoise_tool - osnoise based tool definition. diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 13e1233690bb..8f81fa007364 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -3,6 +3,7 @@ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ +#define _GNU_SOURCE #include <getopt.h> #include <stdlib.h> #include <string.h> @@ -11,14 +12,16 @@ #include <errno.h> #include <stdio.h> #include <time.h> +#include <sched.h> #include "utils.h" #include "osnoise.h" struct osnoise_hist_params { char *cpus; - char *monitored_cpus; + cpu_set_t monitored_cpus; char *trace_output; + char *cgroup_name; unsigned long long runtime; unsigned long long period; long long threshold; @@ -28,6 +31,9 @@ struct osnoise_hist_params { int duration; int set_sched; int output_divisor; + int cgroup; + int hk_cpus; + cpu_set_t hk_cpu_set; struct sched_attr sched_param; struct trace_events *events; @@ -268,7 +274,7 @@ static void osnoise_hist_header(struct osnoise_tool *tool) trace_seq_printf(s, "Index"); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -299,7 +305,7 @@ osnoise_print_summary(struct osnoise_hist_params *params, trace_seq_printf(trace->seq, "count:"); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -313,7 +319,7 @@ osnoise_print_summary(struct osnoise_hist_params *params, trace_seq_printf(trace->seq, "min: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -328,7 +334,7 @@ osnoise_print_summary(struct osnoise_hist_params *params, trace_seq_printf(trace->seq, "avg: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -346,7 +352,7 @@ osnoise_print_summary(struct osnoise_hist_params *params, trace_seq_printf(trace->seq, "max: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -381,7 +387,7 @@ osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *too bucket * data->bucket_size); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -405,7 +411,7 @@ osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *too trace_seq_printf(trace->seq, "over: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].count) @@ -432,8 +438,8 @@ static void osnoise_hist_usage(char *usage) "", " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", " [-T us] [-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\", - " [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] [--no-index] \\", - " [--with-zeros]", + " [-c cpu-list] [-H cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\", + " [--no-index] [--with-zeros] [-C[=cgroup_name]]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -443,6 +449,8 @@ static void osnoise_hist_usage(char *usage) " -S/--stop-total us: stop trace if the total sample is higher than the argument in us", " -T/--threshold us: the minimum delta to be considered a noise", " -c/--cpus cpu-list: list of cpus to run osnoise threads", + " -H/--house-keeping cpus: run rtla control threads only on the given cpus", + " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited", " -d/--duration time[s|m|h|d]: duration of the session", " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", @@ -501,8 +509,10 @@ static struct osnoise_hist_params {"bucket-size", required_argument, 0, 'b'}, {"entries", required_argument, 0, 'E'}, {"cpus", required_argument, 0, 'c'}, + {"cgroup", optional_argument, 0, 'C'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, + {"house-keeping", required_argument, 0, 'H'}, {"help", no_argument, 0, 'h'}, {"period", required_argument, 0, 'p'}, {"priority", required_argument, 0, 'P'}, @@ -524,7 +534,7 @@ static struct osnoise_hist_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhp:P:r:s:S:t::T:01234:5:", + c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:p:P:r:s:S:t::T:01234:5:", long_options, &option_index); /* detect the end of the options. */ @@ -549,11 +559,21 @@ static struct osnoise_hist_params osnoise_hist_usage("Bucket size needs to be > 0 and <= 1000000\n"); break; case 'c': - retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); + retval = parse_cpu_set(optarg, ¶ms->monitored_cpus); if (retval) osnoise_hist_usage("\nInvalid -c cpu list\n"); params->cpus = optarg; break; + case 'C': + params->cgroup = 1; + if (!optarg) { + /* will inherit this cgroup */ + params->cgroup_name = NULL; + } else if (*optarg == '=') { + /* skip the = */ + params->cgroup_name = ++optarg; + } + break; case 'D': config_debug = 1; break; @@ -583,6 +603,14 @@ static struct osnoise_hist_params case '?': osnoise_hist_usage(NULL); break; + case 'H': + params->hk_cpus = 1; + retval = parse_cpu_set(optarg, ¶ms->hk_cpu_set); + if (retval) { + err_msg("Error parsing house keeping CPUs\n"); + exit(EXIT_FAILURE); + } + break; case 'p': params->period = get_llong_from_str(optarg); if (params->period > 10000000) @@ -718,6 +746,24 @@ osnoise_hist_apply_config(struct osnoise_tool *tool, struct osnoise_hist_params } } + if (params->hk_cpus) { + retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set), + ¶ms->hk_cpu_set); + if (retval == -1) { + err_msg("Failed to set rtla to the house keeping CPUs\n"); + goto out_err; + } + } else if (params->cpus) { + /* + * Even if the user do not set a house-keeping CPU, try to + * move rtla to a CPU set different to the one where the user + * set the workload to run. + * + * No need to check results as this is an automatic attempt. + */ + auto_house_keeping(¶ms->monitored_cpus); + } + return 0; out_err: @@ -816,7 +862,13 @@ int osnoise_hist_main(int argc, char *argv[]) } } - trace_instance_start(trace); + if (params->cgroup) { + retval = set_comm_cgroup("timerlat/", params->cgroup_name); + if (!retval) { + err_msg("Failed to move threads to cgroup\n"); + goto out_free; + } + } if (params->trace_output) { record = osnoise_init_trace_tool("osnoise"); @@ -831,9 +883,19 @@ int osnoise_hist_main(int argc, char *argv[]) goto out_hist; } - trace_instance_start(&record->trace); } + /* + * Start the tracer here, after having set all instances. + * + * Let the trace instance start first for the case of hitting a stop + * tracing while enabling other instances. The trace instance is the + * one with most valuable information. + */ + if (params->trace_output) + trace_instance_start(&record->trace); + trace_instance_start(trace); + tool->start_time = time(NULL); osnoise_hist_set_signals(params); diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index 562f2e4b18c5..f7c959be8677 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -3,6 +3,7 @@ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ +#define _GNU_SOURCE #include <getopt.h> #include <stdlib.h> #include <string.h> @@ -10,6 +11,7 @@ #include <unistd.h> #include <stdio.h> #include <time.h> +#include <sched.h> #include "osnoise.h" #include "utils.h" @@ -24,8 +26,9 @@ enum osnoise_mode { */ struct osnoise_top_params { char *cpus; - char *monitored_cpus; + cpu_set_t monitored_cpus; char *trace_output; + char *cgroup_name; unsigned long long runtime; unsigned long long period; long long threshold; @@ -35,6 +38,9 @@ struct osnoise_top_params { int duration; int quiet; int set_sched; + int cgroup; + int hk_cpus; + cpu_set_t hk_cpu_set; struct sched_attr sched_param; struct trace_events *events; enum osnoise_mode mode; @@ -257,7 +263,7 @@ osnoise_print_stats(struct osnoise_top_params *params, struct osnoise_tool *top) osnoise_top_header(top); for (i = 0; i < nr_cpus; i++) { - if (params->cpus && !params->monitored_cpus[i]) + if (params->cpus && !CPU_ISSET(i, ¶ms->monitored_cpus)) continue; osnoise_top_print(top, i); } @@ -276,7 +282,7 @@ static void osnoise_top_usage(struct osnoise_top_params *params, char *usage) static const char * const msg[] = { " [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", " [-T us] [-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\", - " [-c cpu-list] [-P priority]", + " [-c cpu-list] [-H cpu-list] [-P priority] [-C[=cgroup_name]]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -286,6 +292,8 @@ static void osnoise_top_usage(struct osnoise_top_params *params, char *usage) " -S/--stop-total us: stop trace if the total sample is higher than the argument in us", " -T/--threshold us: the minimum delta to be considered a noise", " -c/--cpus cpu-list: list of cpus to run osnoise threads", + " -H/--house-keeping cpus: run rtla control threads only on the given cpus", + " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited", " -d/--duration time[s|m|h|d]: duration of the session", " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", @@ -340,16 +348,24 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) if (!params) exit(1); - if (strcmp(argv[0], "hwnoise") == 0) + if (strcmp(argv[0], "hwnoise") == 0) { params->mode = MODE_HWNOISE; + /* + * Reduce CPU usage for 75% to avoid killing the system. + */ + params->runtime = 750000; + params->period = 1000000; + } while (1) { static struct option long_options[] = { {"auto", required_argument, 0, 'a'}, {"cpus", required_argument, 0, 'c'}, + {"cgroup", optional_argument, 0, 'C'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, {"event", required_argument, 0, 'e'}, + {"house-keeping", required_argument, 0, 'H'}, {"help", no_argument, 0, 'h'}, {"period", required_argument, 0, 'p'}, {"priority", required_argument, 0, 'P'}, @@ -367,7 +383,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:0:1:", + c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:", long_options, &option_index); /* Detect the end of the options. */ @@ -387,11 +403,21 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) break; case 'c': - retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); + retval = parse_cpu_set(optarg, ¶ms->monitored_cpus); if (retval) osnoise_top_usage(params, "\nInvalid -c cpu list\n"); params->cpus = optarg; break; + case 'C': + params->cgroup = 1; + if (!optarg) { + /* will inherit this cgroup */ + params->cgroup_name = NULL; + } else if (*optarg == '=') { + /* skip the = */ + params->cgroup_name = ++optarg; + } + break; case 'D': config_debug = 1; break; @@ -416,6 +442,14 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) case '?': osnoise_top_usage(params, NULL); break; + case 'H': + params->hk_cpus = 1; + retval = parse_cpu_set(optarg, ¶ms->hk_cpu_set); + if (retval) { + err_msg("Error parsing house keeping CPUs\n"); + exit(EXIT_FAILURE); + } + break; case 'p': params->period = get_llong_from_str(optarg); if (params->period > 10000000) @@ -547,6 +581,24 @@ osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *p } } + if (params->hk_cpus) { + retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set), + ¶ms->hk_cpu_set); + if (retval == -1) { + err_msg("Failed to set rtla to the house keeping CPUs\n"); + goto out_err; + } + } else if (params->cpus) { + /* + * Even if the user do not set a house-keeping CPU, try to + * move rtla to a CPU set different to the one where the user + * set the workload to run. + * + * No need to check results as this is an automatic attempt. + */ + auto_house_keeping(¶ms->monitored_cpus); + } + return 0; out_err: @@ -643,7 +695,13 @@ int osnoise_top_main(int argc, char **argv) } } - trace_instance_start(trace); + if (params->cgroup) { + retval = set_comm_cgroup("osnoise/", params->cgroup_name); + if (!retval) { + err_msg("Failed to move threads to cgroup\n"); + goto out_free; + } + } if (params->trace_output) { record = osnoise_init_trace_tool("osnoise"); @@ -657,9 +715,18 @@ int osnoise_top_main(int argc, char **argv) if (retval) goto out_top; } + } + /* + * Start the tracer here, after having set all instances. + * + * Let the trace instance start first for the case of hitting a stop + * tracing while enabling other instances. The trace instance is the + * one with most valuable information. + */ + if (params->trace_output) trace_instance_start(&record->trace); - } + trace_instance_start(trace); tool->start_time = time(NULL); osnoise_top_set_signals(params); diff --git a/tools/tracing/rtla/src/timerlat_aa.c b/tools/tracing/rtla/src/timerlat_aa.c index 1843fff66da5..e0ffe69c271c 100644 --- a/tools/tracing/rtla/src/timerlat_aa.c +++ b/tools/tracing/rtla/src/timerlat_aa.c @@ -8,6 +8,7 @@ #include "utils.h" #include "osnoise.h" #include "timerlat.h" +#include <unistd.h> enum timelat_state { TIMERLAT_INIT = 0, @@ -233,7 +234,7 @@ static int timerlat_aa_thread_latency(struct timerlat_aa_data *taa_data, * * Returns 0 on success, -1 otherwise. */ -int timerlat_aa_handler(struct trace_seq *s, struct tep_record *record, +static int timerlat_aa_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context) { struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); @@ -665,6 +666,25 @@ print_total: ns_to_usf(total)); } +static int timerlat_auto_analysis_collect_trace(struct timerlat_aa_context *taa_ctx) +{ + struct trace_instance *trace = &taa_ctx->tool->trace; + int retval; + + retval = tracefs_iterate_raw_events(trace->tep, + trace->inst, + NULL, + 0, + collect_registered_events, + trace); + if (retval < 0) { + err_msg("Error iterating on events\n"); + return 0; + } + + return 1; +} + /** * timerlat_auto_analysis - Analyze the collected data */ @@ -677,6 +697,8 @@ void timerlat_auto_analysis(int irq_thresh, int thread_thresh) struct tep_handle *tep; int cpu; + timerlat_auto_analysis_collect_trace(taa_ctx); + /* bring stop tracing to the ns scale */ irq_thresh = irq_thresh * 1000; thread_thresh = thread_thresh * 1000; @@ -838,6 +860,10 @@ out_err: */ static void timerlat_aa_unregister_events(struct osnoise_tool *tool, int dump_tasks) { + + tep_unregister_event_handler(tool->trace.tep, -1, "ftrace", "timerlat", + timerlat_aa_handler, tool); + tracefs_event_disable(tool->trace.inst, "osnoise", NULL); tep_unregister_event_handler(tool->trace.tep, -1, "osnoise", "nmi_noise", @@ -875,6 +901,10 @@ static int timerlat_aa_register_events(struct osnoise_tool *tool, int dump_tasks { int retval; + tep_register_event_handler(tool->trace.tep, -1, "ftrace", "timerlat", + timerlat_aa_handler, tool); + + /* * register auto-analysis handlers. */ @@ -955,8 +985,9 @@ out_ctx: * * Returns 0 on success, -1 otherwise. */ -int timerlat_aa_init(struct osnoise_tool *tool, int nr_cpus, int dump_tasks) +int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks) { + int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); struct timerlat_aa_context *taa_ctx; int retval; diff --git a/tools/tracing/rtla/src/timerlat_aa.h b/tools/tracing/rtla/src/timerlat_aa.h index d4f6ca7e342a..cea4bb1531a8 100644 --- a/tools/tracing/rtla/src/timerlat_aa.h +++ b/tools/tracing/rtla/src/timerlat_aa.h @@ -3,10 +3,7 @@ * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ -int timerlat_aa_init(struct osnoise_tool *tool, int nr_cpus, int dump_task); +int timerlat_aa_init(struct osnoise_tool *tool, int dump_task); void timerlat_aa_destroy(void); -int timerlat_aa_handler(struct trace_seq *s, struct tep_record *record, - struct tep_event *event, void *context); - void timerlat_auto_analysis(int irq_thresh, int thread_thresh); diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 4b48af8a8309..47d3d8b53cb2 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -3,6 +3,7 @@ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ +#define _GNU_SOURCE #include <getopt.h> #include <stdlib.h> #include <string.h> @@ -10,15 +11,20 @@ #include <unistd.h> #include <stdio.h> #include <time.h> +#include <sched.h> +#include <pthread.h> #include "utils.h" #include "osnoise.h" #include "timerlat.h" +#include "timerlat_aa.h" +#include "timerlat_u.h" struct timerlat_hist_params { char *cpus; - char *monitored_cpus; + cpu_set_t monitored_cpus; char *trace_output; + char *cgroup_name; unsigned long long runtime; long long stop_us; long long stop_total_us; @@ -29,9 +35,14 @@ struct timerlat_hist_params { int duration; int set_sched; int dma_latency; + int cgroup; + int hk_cpus; + int no_aa; + int dump_tasks; + int user_hist; + cpu_set_t hk_cpu_set; struct sched_attr sched_param; struct trace_events *events; - char no_irq; char no_thread; char no_header; @@ -45,9 +56,11 @@ struct timerlat_hist_params { struct timerlat_hist_cpu { int *irq; int *thread; + int *user; int irq_count; int thread_count; + int user_count; unsigned long long min_irq; unsigned long long sum_irq; @@ -56,6 +69,10 @@ struct timerlat_hist_cpu { unsigned long long min_thread; unsigned long long sum_thread; unsigned long long max_thread; + + unsigned long long min_user; + unsigned long long sum_user; + unsigned long long max_user; }; struct timerlat_hist_data { @@ -80,6 +97,10 @@ timerlat_free_histogram(struct timerlat_hist_data *data) if (data->hist[cpu].thread) free(data->hist[cpu].thread); + + if (data->hist[cpu].user) + free(data->hist[cpu].user); + } /* one set of histograms per CPU */ @@ -116,15 +137,21 @@ static struct timerlat_hist_data data->hist[cpu].irq = calloc(1, sizeof(*data->hist->irq) * (entries + 1)); if (!data->hist[cpu].irq) goto cleanup; + data->hist[cpu].thread = calloc(1, sizeof(*data->hist->thread) * (entries + 1)); if (!data->hist[cpu].thread) goto cleanup; + + data->hist[cpu].user = calloc(1, sizeof(*data->hist->user) * (entries + 1)); + if (!data->hist[cpu].user) + goto cleanup; } /* set the min to max */ for (cpu = 0; cpu < nr_cpus; cpu++) { data->hist[cpu].min_irq = ~0; data->hist[cpu].min_thread = ~0; + data->hist[cpu].min_user = ~0; } return data; @@ -139,7 +166,7 @@ cleanup: */ static void timerlat_hist_update(struct osnoise_tool *tool, int cpu, - unsigned long long thread, + unsigned long long context, unsigned long long latency) { struct timerlat_hist_params *params = tool->params; @@ -154,18 +181,24 @@ timerlat_hist_update(struct osnoise_tool *tool, int cpu, if (data->bucket_size) bucket = latency / data->bucket_size; - if (!thread) { + if (!context) { hist = data->hist[cpu].irq; data->hist[cpu].irq_count++; update_min(&data->hist[cpu].min_irq, &latency); update_sum(&data->hist[cpu].sum_irq, &latency); update_max(&data->hist[cpu].max_irq, &latency); - } else { + } else if (context == 1) { hist = data->hist[cpu].thread; data->hist[cpu].thread_count++; update_min(&data->hist[cpu].min_thread, &latency); update_sum(&data->hist[cpu].sum_thread, &latency); update_max(&data->hist[cpu].max_thread, &latency); + } else { /* user */ + hist = data->hist[cpu].user; + data->hist[cpu].user_count++; + update_min(&data->hist[cpu].min_user, &latency); + update_sum(&data->hist[cpu].sum_user, &latency); + update_max(&data->hist[cpu].max_user, &latency); } if (bucket < entries) @@ -182,16 +215,16 @@ timerlat_hist_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *data) { struct trace_instance *trace = data; - unsigned long long thread, latency; + unsigned long long context, latency; struct osnoise_tool *tool; int cpu = record->cpu; tool = container_of(trace, struct osnoise_tool, trace); - tep_get_field_val(s, event, "context", record, &thread, 1); + tep_get_field_val(s, event, "context", record, &context, 1); tep_get_field_val(s, event, "timer_latency", record, &latency, 1); - timerlat_hist_update(tool, cpu, thread, latency); + timerlat_hist_update(tool, cpu, context, latency); return 0; } @@ -222,7 +255,7 @@ static void timerlat_hist_header(struct osnoise_tool *tool) trace_seq_printf(s, "Index"); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -233,6 +266,9 @@ static void timerlat_hist_header(struct osnoise_tool *tool) if (!params->no_thread) trace_seq_printf(s, " Thr-%03d", cpu); + + if (params->user_hist) + trace_seq_printf(s, " Usr-%03d", cpu); } trace_seq_printf(s, "\n"); @@ -258,7 +294,7 @@ timerlat_print_summary(struct timerlat_hist_params *params, trace_seq_printf(trace->seq, "count:"); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -271,6 +307,10 @@ timerlat_print_summary(struct timerlat_hist_params *params, if (!params->no_thread) trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].thread_count); + + if (params->user_hist) + trace_seq_printf(trace->seq, "%9d ", + data->hist[cpu].user_count); } trace_seq_printf(trace->seq, "\n"); @@ -278,7 +318,7 @@ timerlat_print_summary(struct timerlat_hist_params *params, trace_seq_printf(trace->seq, "min: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -291,6 +331,10 @@ timerlat_print_summary(struct timerlat_hist_params *params, if (!params->no_thread) trace_seq_printf(trace->seq, "%9llu ", data->hist[cpu].min_thread); + + if (params->user_hist) + trace_seq_printf(trace->seq, "%9llu ", + data->hist[cpu].min_user); } trace_seq_printf(trace->seq, "\n"); @@ -298,7 +342,7 @@ timerlat_print_summary(struct timerlat_hist_params *params, trace_seq_printf(trace->seq, "avg: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -315,7 +359,15 @@ timerlat_print_summary(struct timerlat_hist_params *params, if (!params->no_thread) { if (data->hist[cpu].thread_count) trace_seq_printf(trace->seq, "%9llu ", - data->hist[cpu].sum_thread / data->hist[cpu].thread_count); + data->hist[cpu].sum_thread / data->hist[cpu].thread_count); + else + trace_seq_printf(trace->seq, " - "); + } + + if (params->user_hist) { + if (data->hist[cpu].user_count) + trace_seq_printf(trace->seq, "%9llu ", + data->hist[cpu].sum_user / data->hist[cpu].user_count); else trace_seq_printf(trace->seq, " - "); } @@ -326,7 +378,7 @@ timerlat_print_summary(struct timerlat_hist_params *params, trace_seq_printf(trace->seq, "max: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -339,6 +391,10 @@ timerlat_print_summary(struct timerlat_hist_params *params, if (!params->no_thread) trace_seq_printf(trace->seq, "%9llu ", data->hist[cpu].max_thread); + + if (params->user_hist) + trace_seq_printf(trace->seq, "%9llu ", + data->hist[cpu].max_user); } trace_seq_printf(trace->seq, "\n"); trace_seq_do_printf(trace->seq); @@ -366,7 +422,7 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t bucket * data->bucket_size); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -384,6 +440,12 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t data->hist[cpu].thread[bucket]); } + if (params->user_hist) { + total += data->hist[cpu].user[bucket]; + trace_seq_printf(trace->seq, "%9d ", + data->hist[cpu].user[bucket]); + } + } if (total == 0 && !params->with_zeros) { @@ -400,7 +462,7 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t trace_seq_printf(trace->seq, "over: "); for (cpu = 0; cpu < data->nr_cpus; cpu++) { - if (params->cpus && !params->monitored_cpus[cpu]) + if (params->cpus && !CPU_ISSET(cpu, ¶ms->monitored_cpus)) continue; if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) @@ -413,6 +475,10 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t if (!params->no_thread) trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].thread[data->entries]); + + if (params->user_hist) + trace_seq_printf(trace->seq, "%9d ", + data->hist[cpu].user[data->entries]); } trace_seq_printf(trace->seq, "\n"); trace_seq_do_printf(trace->seq); @@ -431,9 +497,9 @@ static void timerlat_hist_usage(char *usage) char *msg[] = { "", " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", - " [-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] \\", + " [-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\", " [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\", - " [--no-index] [--with-zeros] [--dma-latency us]", + " [--no-index] [--with-zeros] [--dma-latency us] [-C[=cgroup_name]] [--no-aa] [--dump-task] [-u]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -442,13 +508,17 @@ static void timerlat_hist_usage(char *usage) " -T/--thread us: stop trace if the thread latency is higher than the argument in us", " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us", " -c/--cpus cpus: run the tracer only on the given cpus", + " -H/--house-keeping cpus: run rtla control threads only on the given cpus", + " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited", " -d/--duration time[m|h|d]: duration of the session in seconds", + " --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)", " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed", " --filter <filter>: enable a trace event filter to the previous -e event", " --trigger <trigger>: enable a trace event trigger to the previous -e event", " -n/--nano: display data in nanoseconds", + " --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage", " -b/--bucket-size N: set the histogram bucket size (default 1)", " -E/--entries N: set the number of entries of the histogram (default 256)", " --no-irq: ignore IRQ latencies", @@ -464,6 +534,7 @@ static void timerlat_hist_usage(char *usage) " f:prio - use SCHED_FIFO with prio", " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period", " in nanoseconds", + " -u/--user-threads: use rtla user-space threads instead of in-kernel timerlat threads", NULL, }; @@ -506,10 +577,12 @@ static struct timerlat_hist_params static struct option long_options[] = { {"auto", required_argument, 0, 'a'}, {"cpus", required_argument, 0, 'c'}, + {"cgroup", optional_argument, 0, 'C'}, {"bucket-size", required_argument, 0, 'b'}, {"debug", no_argument, 0, 'D'}, {"entries", required_argument, 0, 'E'}, {"duration", required_argument, 0, 'd'}, + {"house-keeping", required_argument, 0, 'H'}, {"help", no_argument, 0, 'h'}, {"irq", required_argument, 0, 'i'}, {"nano", no_argument, 0, 'n'}, @@ -518,6 +591,7 @@ static struct timerlat_hist_params {"stack", required_argument, 0, 's'}, {"thread", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, + {"user-threads", no_argument, 0, 'u'}, {"event", required_argument, 0, 'e'}, {"no-irq", no_argument, 0, '0'}, {"no-thread", no_argument, 0, '1'}, @@ -528,13 +602,15 @@ static struct timerlat_hist_params {"trigger", required_argument, 0, '6'}, {"filter", required_argument, 0, '7'}, {"dma-latency", required_argument, 0, '8'}, + {"no-aa", no_argument, 0, '9'}, + {"dump-task", no_argument, 0, '\1'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:0123456:7:8:", + c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:i:np:P:s:t::T:u0123456:7:8:9\1", long_options, &option_index); /* detect the end of the options. */ @@ -547,6 +623,7 @@ static struct timerlat_hist_params /* set thread stop to auto_thresh */ params->stop_total_us = auto_thresh; + params->stop_us = auto_thresh; /* get stack trace */ params->print_stack = auto_thresh; @@ -556,11 +633,21 @@ static struct timerlat_hist_params break; case 'c': - retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); + retval = parse_cpu_set(optarg, ¶ms->monitored_cpus); if (retval) timerlat_hist_usage("\nInvalid -c cpu list\n"); params->cpus = optarg; break; + case 'C': + params->cgroup = 1; + if (!optarg) { + /* will inherit this cgroup */ + params->cgroup_name = NULL; + } else if (*optarg == '=') { + /* skip the = */ + params->cgroup_name = ++optarg; + } + break; case 'b': params->bucket_size = get_llong_from_str(optarg); if ((params->bucket_size == 0) || (params->bucket_size >= 1000000)) @@ -595,6 +682,14 @@ static struct timerlat_hist_params case '?': timerlat_hist_usage(NULL); break; + case 'H': + params->hk_cpus = 1; + retval = parse_cpu_set(optarg, ¶ms->hk_cpu_set); + if (retval) { + err_msg("Error parsing house keeping CPUs\n"); + exit(EXIT_FAILURE); + } + break; case 'i': params->stop_us = get_llong_from_str(optarg); break; @@ -625,6 +720,9 @@ static struct timerlat_hist_params else params->trace_output = "timerlat_trace.txt"; break; + case 'u': + params->user_hist = 1; + break; case '0': /* no irq */ params->no_irq = 1; break; @@ -672,6 +770,12 @@ static struct timerlat_hist_params exit(EXIT_FAILURE); } break; + case '9': + params->no_aa = 1; + break; + case '\1': + params->dump_tasks = 1; + break; default: timerlat_hist_usage("Invalid option"); } @@ -688,6 +792,12 @@ static struct timerlat_hist_params if (params->no_index && !params->with_zeros) timerlat_hist_usage("no-index set with with-zeros is not set - it does not make sense"); + /* + * Auto analysis only happens if stop tracing, thus: + */ + if (!params->stop_us && !params->stop_total_us) + params->no_aa = 1; + return params; } @@ -697,7 +807,7 @@ static struct timerlat_hist_params static int timerlat_hist_apply_config(struct osnoise_tool *tool, struct timerlat_hist_params *params) { - int retval; + int retval, i; if (!params->sleep_time) params->sleep_time = 1; @@ -708,6 +818,9 @@ timerlat_hist_apply_config(struct osnoise_tool *tool, struct timerlat_hist_param err_msg("Failed to apply CPUs config\n"); goto out_err; } + } else { + for (i = 0; i < sysconf(_SC_NPROCESSORS_CONF); i++) + CPU_SET(i, ¶ms->monitored_cpus); } if (params->stop_us) { @@ -742,6 +855,32 @@ timerlat_hist_apply_config(struct osnoise_tool *tool, struct timerlat_hist_param } } + if (params->hk_cpus) { + retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set), + ¶ms->hk_cpu_set); + if (retval == -1) { + err_msg("Failed to set rtla to the house keeping CPUs\n"); + goto out_err; + } + } else if (params->cpus) { + /* + * Even if the user do not set a house-keeping CPU, try to + * move rtla to a CPU set different to the one where the user + * set the workload to run. + * + * No need to check results as this is an automatic attempt. + */ + auto_house_keeping(¶ms->monitored_cpus); + } + + if (params->user_hist) { + retval = osnoise_set_workload(tool->context, 0); + if (retval) { + err_msg("Failed to set OSNOISE_WORKLOAD option\n"); + goto out_err; + } + } + return 0; out_err: @@ -802,10 +941,13 @@ int timerlat_hist_main(int argc, char *argv[]) { struct timerlat_hist_params *params; struct osnoise_tool *record = NULL; + struct timerlat_u_params params_u; struct osnoise_tool *tool = NULL; + struct osnoise_tool *aa = NULL; struct trace_instance *trace; int dma_latency_fd = -1; int return_value = 1; + pthread_t timerlat_u; int retval; params = timerlat_hist_parse_args(argc, argv); @@ -840,6 +982,14 @@ int timerlat_hist_main(int argc, char *argv[]) } } + if (params->cgroup && !params->user_hist) { + retval = set_comm_cgroup("timerlat/", params->cgroup_name); + if (!retval) { + err_msg("Failed to move threads to cgroup\n"); + goto out_free; + } + } + if (params->dma_latency >= 0) { dma_latency_fd = set_cpu_dma_latency(params->dma_latency); if (dma_latency_fd < 0) { @@ -848,8 +998,6 @@ int timerlat_hist_main(int argc, char *argv[]) } } - trace_instance_start(trace); - if (params->trace_output) { record = osnoise_init_trace_tool("timerlat"); if (!record) { @@ -862,13 +1010,61 @@ int timerlat_hist_main(int argc, char *argv[]) if (retval) goto out_hist; } + } - trace_instance_start(&record->trace); + if (!params->no_aa) { + aa = osnoise_init_tool("timerlat_aa"); + if (!aa) + goto out_hist; + + retval = timerlat_aa_init(aa, params->dump_tasks); + if (retval) { + err_msg("Failed to enable the auto analysis instance\n"); + goto out_hist; + } + + retval = enable_timerlat(&aa->trace); + if (retval) { + err_msg("Failed to enable timerlat tracer\n"); + goto out_hist; + } } + /* + * Start the tracers here, after having set all instances. + * + * Let the trace instance start first for the case of hitting a stop + * tracing while enabling other instances. The trace instance is the + * one with most valuable information. + */ + if (params->trace_output) + trace_instance_start(&record->trace); + if (!params->no_aa) + trace_instance_start(&aa->trace); + trace_instance_start(trace); + tool->start_time = time(NULL); timerlat_hist_set_signals(params); + if (params->user_hist) { + /* rtla asked to stop */ + params_u.should_run = 1; + /* all threads left */ + params_u.stopped_running = 0; + + params_u.set = ¶ms->monitored_cpus; + if (params->set_sched) + params_u.sched_param = ¶ms->sched_param; + else + params_u.sched_param = NULL; + + params_u.cgroup_name = params->cgroup_name; + + retval = pthread_create(&timerlat_u, NULL, timerlat_u_dispatcher, ¶ms_u); + if (retval) + err_msg("Error creating timerlat user-space threads\n"); + } + while (!stop_tracing) { sleep(params->sleep_time); @@ -885,6 +1081,18 @@ int timerlat_hist_main(int argc, char *argv[]) if (trace_is_off(&tool->trace, &record->trace)) break; + + /* is there still any user-threads ? */ + if (params->user_hist) { + if (params_u.stopped_running) { + debug_msg("timerlat user-space threads stopped!\n"); + break; + } + } + } + if (params->user_hist && !params_u.stopped_running) { + params_u.should_run = 0; + sleep(1); } timerlat_print_stats(params, tool); @@ -893,6 +1101,10 @@ int timerlat_hist_main(int argc, char *argv[]) if (trace_is_off(&tool->trace, &record->trace)) { printf("rtla timerlat hit stop tracing\n"); + + if (!params->no_aa) + timerlat_auto_analysis(params->stop_us, params->stop_total_us); + if (params->trace_output) { printf(" Saving trace to %s\n", params->trace_output); save_trace_to_file(record->trace.inst, params->trace_output); @@ -900,12 +1112,14 @@ int timerlat_hist_main(int argc, char *argv[]) } out_hist: + timerlat_aa_destroy(); if (dma_latency_fd >= 0) close(dma_latency_fd); trace_events_destroy(&record->trace, params->events); params->events = NULL; out_free: timerlat_free_histogram(tool->data); + osnoise_destroy_tool(aa); osnoise_destroy_tool(record); osnoise_destroy_tool(tool); free(params); diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 92c658c64f28..1640f121baca 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -3,6 +3,7 @@ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ +#define _GNU_SOURCE #include <getopt.h> #include <stdlib.h> #include <string.h> @@ -11,16 +12,20 @@ #include <stdio.h> #include <time.h> #include <errno.h> +#include <sched.h> +#include <pthread.h> #include "utils.h" #include "osnoise.h" #include "timerlat.h" #include "timerlat_aa.h" +#include "timerlat_u.h" struct timerlat_top_params { char *cpus; - char *monitored_cpus; + cpu_set_t monitored_cpus; char *trace_output; + char *cgroup_name; unsigned long long runtime; long long stop_us; long long stop_total_us; @@ -35,6 +40,10 @@ struct timerlat_top_params { int no_aa; int aa_only; int dump_tasks; + int cgroup; + int hk_cpus; + int user_top; + cpu_set_t hk_cpu_set; struct sched_attr sched_param; struct trace_events *events; }; @@ -42,6 +51,7 @@ struct timerlat_top_params { struct timerlat_top_cpu { int irq_count; int thread_count; + int user_count; unsigned long long cur_irq; unsigned long long min_irq; @@ -52,6 +62,11 @@ struct timerlat_top_cpu { unsigned long long min_thread; unsigned long long sum_thread; unsigned long long max_thread; + + unsigned long long cur_user; + unsigned long long min_user; + unsigned long long sum_user; + unsigned long long max_user; }; struct timerlat_top_data { @@ -92,6 +107,7 @@ static struct timerlat_top_data *timerlat_alloc_top(int nr_cpus) for (cpu = 0; cpu < nr_cpus; cpu++) { data->cpu_data[cpu].min_irq = ~0; data->cpu_data[cpu].min_thread = ~0; + data->cpu_data[cpu].min_user = ~0; } return data; @@ -118,12 +134,18 @@ timerlat_top_update(struct osnoise_tool *tool, int cpu, update_min(&cpu_data->min_irq, &latency); update_sum(&cpu_data->sum_irq, &latency); update_max(&cpu_data->max_irq, &latency); - } else { + } else if (thread == 1) { cpu_data->thread_count++; cpu_data->cur_thread = latency; update_min(&cpu_data->min_thread, &latency); update_sum(&cpu_data->sum_thread, &latency); update_max(&cpu_data->max_thread, &latency); + } else { + cpu_data->user_count++; + cpu_data->cur_user = latency; + update_min(&cpu_data->min_user, &latency); + update_sum(&cpu_data->sum_user, &latency); + update_max(&cpu_data->max_user, &latency); } } @@ -150,9 +172,6 @@ timerlat_top_handler(struct trace_seq *s, struct tep_record *record, timerlat_top_update(top, cpu, thread, latency); } - if (!params->no_aa) - timerlat_aa_handler(s, record, event, context); - return 0; } @@ -169,15 +188,25 @@ static void timerlat_top_header(struct osnoise_tool *top) trace_seq_printf(s, "\033[2;37;40m"); trace_seq_printf(s, " Timer Latency "); + if (params->user_top) + trace_seq_printf(s, " "); trace_seq_printf(s, "\033[0;0;0m"); trace_seq_printf(s, "\n"); - trace_seq_printf(s, "%-6s | IRQ Timer Latency (%s) | Thread Timer Latency (%s)\n", duration, + trace_seq_printf(s, "%-6s | IRQ Timer Latency (%s) | Thread Timer Latency (%s)", duration, params->output_divisor == 1 ? "ns" : "us", params->output_divisor == 1 ? "ns" : "us"); + if (params->user_top) { + trace_seq_printf(s, " | Ret user Timer Latency (%s)", + params->output_divisor == 1 ? "ns" : "us"); + } + + trace_seq_printf(s, "\n"); trace_seq_printf(s, "\033[2;30;47m"); trace_seq_printf(s, "CPU COUNT | cur min avg max | cur min avg max"); + if (params->user_top) + trace_seq_printf(s, " | cur min avg max"); trace_seq_printf(s, "\033[0;0;0m"); trace_seq_printf(s, "\n"); } @@ -230,7 +259,27 @@ static void timerlat_top_print(struct osnoise_tool *top, int cpu) trace_seq_printf(s, "%9llu ", cpu_data->min_thread / divisor); trace_seq_printf(s, "%9llu ", (cpu_data->sum_thread / cpu_data->thread_count) / divisor); - trace_seq_printf(s, "%9llu\n", cpu_data->max_thread / divisor); + trace_seq_printf(s, "%9llu", cpu_data->max_thread / divisor); + } + + if (!params->user_top) { + trace_seq_printf(s, "\n"); + return; + } + + trace_seq_printf(s, " |"); + + if (!cpu_data->user_count) { + trace_seq_printf(s, " - "); + trace_seq_printf(s, " - "); + trace_seq_printf(s, " - "); + trace_seq_printf(s, " -\n"); + } else { + trace_seq_printf(s, "%9llu ", cpu_data->cur_user / divisor); + trace_seq_printf(s, "%9llu ", cpu_data->min_user / divisor); + trace_seq_printf(s, "%9llu ", + (cpu_data->sum_user / cpu_data->user_count) / divisor); + trace_seq_printf(s, "%9llu\n", cpu_data->max_user / divisor); } } @@ -265,7 +314,7 @@ timerlat_print_stats(struct timerlat_top_params *params, struct osnoise_tool *to timerlat_top_header(top); for (i = 0; i < nr_cpus; i++) { - if (params->cpus && !params->monitored_cpus[i]) + if (params->cpus && !CPU_ISSET(i, ¶ms->monitored_cpus)) continue; timerlat_top_print(top, i); } @@ -284,8 +333,8 @@ static void timerlat_top_usage(char *usage) static const char *const msg[] = { "", " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", - " [[-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] \\", - " [-P priority] [--dma-latency us] [--aa-only us]", + " [[-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\", + " [-P priority] [--dma-latency us] [--aa-only us] [-C[=cgroup_name]] [-u]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -295,6 +344,8 @@ static void timerlat_top_usage(char *usage) " -T/--thread us: stop trace if the thread latency is higher than the argument in us", " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us", " -c/--cpus cpus: run the tracer only on the given cpus", + " -H/--house-keeping cpus: run rtla control threads only on the given cpus", + " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited", " -d/--duration time[m|h|d]: duration of the session in seconds", " -D/--debug: print debug info", " --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)", @@ -312,6 +363,7 @@ static void timerlat_top_usage(char *usage) " f:prio - use SCHED_FIFO with prio", " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period", " in nanoseconds", + " -u/--user-threads: use rtla user-space threads instead of in-kernel timerlat threads", NULL, }; @@ -352,10 +404,12 @@ static struct timerlat_top_params static struct option long_options[] = { {"auto", required_argument, 0, 'a'}, {"cpus", required_argument, 0, 'c'}, + {"cgroup", optional_argument, 0, 'C'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, {"event", required_argument, 0, 'e'}, {"help", no_argument, 0, 'h'}, + {"house-keeping", required_argument, 0, 'H'}, {"irq", required_argument, 0, 'i'}, {"nano", no_argument, 0, 'n'}, {"period", required_argument, 0, 'p'}, @@ -364,6 +418,7 @@ static struct timerlat_top_params {"stack", required_argument, 0, 's'}, {"thread", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, + {"user-threads", no_argument, 0, 'u'}, {"trigger", required_argument, 0, '0'}, {"filter", required_argument, 0, '1'}, {"dma-latency", required_argument, 0, '2'}, @@ -376,7 +431,7 @@ static struct timerlat_top_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:1:2:345:", + c = getopt_long(argc, argv, "a:c:C::d:De:hH:i:np:P:qs:t::T:u0:1:2:345:", long_options, &option_index); /* detect the end of the options. */ @@ -412,11 +467,21 @@ static struct timerlat_top_params params->aa_only = 1; break; case 'c': - retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); + retval = parse_cpu_set(optarg, ¶ms->monitored_cpus); if (retval) timerlat_top_usage("\nInvalid -c cpu list\n"); params->cpus = optarg; break; + case 'C': + params->cgroup = 1; + if (!optarg) { + /* will inherit this cgroup */ + params->cgroup_name = NULL; + } else if (*optarg == '=') { + /* skip the = */ + params->cgroup_name = ++optarg; + } + break; case 'D': config_debug = 1; break; @@ -440,6 +505,14 @@ static struct timerlat_top_params case '?': timerlat_top_usage(NULL); break; + case 'H': + params->hk_cpus = 1; + retval = parse_cpu_set(optarg, ¶ms->hk_cpu_set); + if (retval) { + err_msg("Error parsing house keeping CPUs\n"); + exit(EXIT_FAILURE); + } + break; case 'i': params->stop_us = get_llong_from_str(optarg); break; @@ -474,6 +547,9 @@ static struct timerlat_top_params params->trace_output = "timerlat_trace.txt"; break; + case 'u': + params->user_top = true; + break; case '0': /* trigger */ if (params->events) { retval = trace_event_add_trigger(params->events, optarg); @@ -538,6 +614,7 @@ static int timerlat_top_apply_config(struct osnoise_tool *top, struct timerlat_top_params *params) { int retval; + int i; if (!params->sleep_time) params->sleep_time = 1; @@ -548,6 +625,9 @@ timerlat_top_apply_config(struct osnoise_tool *top, struct timerlat_top_params * err_msg("Failed to apply CPUs config\n"); goto out_err; } + } else { + for (i = 0; i < sysconf(_SC_NPROCESSORS_CONF); i++) + CPU_SET(i, ¶ms->monitored_cpus); } if (params->stop_us) { @@ -584,6 +664,32 @@ timerlat_top_apply_config(struct osnoise_tool *top, struct timerlat_top_params * } } + if (params->hk_cpus) { + retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set), + ¶ms->hk_cpu_set); + if (retval == -1) { + err_msg("Failed to set rtla to the house keeping CPUs\n"); + goto out_err; + } + } else if (params->cpus) { + /* + * Even if the user do not set a house-keeping CPU, try to + * move rtla to a CPU set different to the one where the user + * set the workload to run. + * + * No need to check results as this is an automatic attempt. + */ + auto_house_keeping(¶ms->monitored_cpus); + } + + if (params->user_top) { + retval = osnoise_set_workload(top->context, 0); + if (retval) { + err_msg("Failed to set OSNOISE_WORKLOAD option\n"); + goto out_err; + } + } + return 0; out_err: @@ -598,7 +704,6 @@ static struct osnoise_tool { struct osnoise_tool *top; int nr_cpus; - int retval; nr_cpus = sysconf(_SC_NPROCESSORS_CONF); @@ -615,16 +720,6 @@ static struct osnoise_tool tep_register_event_handler(top->trace.tep, -1, "ftrace", "timerlat", timerlat_top_handler, top); - /* - * If no auto analysis, we are ready. - */ - if (params->no_aa) - return top; - - retval = timerlat_aa_init(top, nr_cpus, params->dump_tasks); - if (retval) - goto out_err; - return top; out_err: @@ -655,9 +750,12 @@ int timerlat_top_main(int argc, char *argv[]) { struct timerlat_top_params *params; struct osnoise_tool *record = NULL; + struct timerlat_u_params params_u; struct osnoise_tool *top = NULL; + struct osnoise_tool *aa = NULL; struct trace_instance *trace; int dma_latency_fd = -1; + pthread_t timerlat_u; int return_value = 1; char *max_lat; int retval; @@ -694,6 +792,14 @@ int timerlat_top_main(int argc, char *argv[]) } } + if (params->cgroup && !params->user_top) { + retval = set_comm_cgroup("timerlat/", params->cgroup_name); + if (!retval) { + err_msg("Failed to move threads to cgroup\n"); + goto out_free; + } + } + if (params->dma_latency >= 0) { dma_latency_fd = set_cpu_dma_latency(params->dma_latency); if (dma_latency_fd < 0) { @@ -702,8 +808,6 @@ int timerlat_top_main(int argc, char *argv[]) } } - trace_instance_start(trace); - if (params->trace_output) { record = osnoise_init_trace_tool("timerlat"); if (!record) { @@ -716,13 +820,70 @@ int timerlat_top_main(int argc, char *argv[]) if (retval) goto out_top; } + } - trace_instance_start(&record->trace); + if (!params->no_aa) { + if (params->aa_only) { + /* as top is not used for display, use it for aa */ + aa = top; + } else { + /* otherwise, a new instance is needed */ + aa = osnoise_init_tool("timerlat_aa"); + if (!aa) + goto out_top; + } + + retval = timerlat_aa_init(aa, params->dump_tasks); + if (retval) { + err_msg("Failed to enable the auto analysis instance\n"); + goto out_top; + } + + /* if it is re-using the main instance, there is no need to start it */ + if (aa != top) { + retval = enable_timerlat(&aa->trace); + if (retval) { + err_msg("Failed to enable timerlat tracer\n"); + goto out_top; + } + } } + /* + * Start the tracers here, after having set all instances. + * + * Let the trace instance start first for the case of hitting a stop + * tracing while enabling other instances. The trace instance is the + * one with most valuable information. + */ + if (params->trace_output) + trace_instance_start(&record->trace); + if (!params->no_aa && aa != top) + trace_instance_start(&aa->trace); + trace_instance_start(trace); + top->start_time = time(NULL); timerlat_top_set_signals(params); + if (params->user_top) { + /* rtla asked to stop */ + params_u.should_run = 1; + /* all threads left */ + params_u.stopped_running = 0; + + params_u.set = ¶ms->monitored_cpus; + if (params->set_sched) + params_u.sched_param = ¶ms->sched_param; + else + params_u.sched_param = NULL; + + params_u.cgroup_name = params->cgroup_name; + + retval = pthread_create(&timerlat_u, NULL, timerlat_u_dispatcher, ¶ms_u); + if (retval) + err_msg("Error creating timerlat user-space threads\n"); + } + while (!stop_tracing) { sleep(params->sleep_time); @@ -746,6 +907,18 @@ int timerlat_top_main(int argc, char *argv[]) if (trace_is_off(&top->trace, &record->trace)) break; + /* is there still any user-threads ? */ + if (params->user_top) { + if (params_u.stopped_running) { + debug_msg("timerlat user space threads stopped!\n"); + break; + } + } + } + + if (params->user_top && !params_u.stopped_running) { + params_u.should_run = 0; + sleep(1); } timerlat_print_stats(params, top); @@ -775,13 +948,15 @@ int timerlat_top_main(int argc, char *argv[]) } out_top: + timerlat_aa_destroy(); if (dma_latency_fd >= 0) close(dma_latency_fd); trace_events_destroy(&record->trace, params->events); params->events = NULL; out_free: timerlat_free_top(top->data); - timerlat_aa_destroy(); + if (aa && aa != top) + osnoise_destroy_tool(aa); osnoise_destroy_tool(record); osnoise_destroy_tool(top); free(params); diff --git a/tools/tracing/rtla/src/timerlat_u.c b/tools/tracing/rtla/src/timerlat_u.c new file mode 100644 index 000000000000..05e310696dd5 --- /dev/null +++ b/tools/tracing/rtla/src/timerlat_u.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ + +#define _GNU_SOURCE +#include <sched.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <tracefs.h> +#include <pthread.h> +#include <sys/wait.h> +#include <sys/prctl.h> + +#include "utils.h" +#include "timerlat_u.h" + +/* + * This is the user-space main for the tool timerlatu/ threads. + * + * It is as simple as this: + * - set affinity + * - set priority + * - open tracer fd + * - spin + * - close + */ +static int timerlat_u_main(int cpu, struct timerlat_u_params *params) +{ + struct sched_param sp = { .sched_priority = 95 }; + char buffer[1024]; + int timerlat_fd; + cpu_set_t set; + int retval; + + /* + * This all is only setting up the tool. + */ + CPU_ZERO(&set); + CPU_SET(cpu, &set); + + retval = sched_setaffinity(gettid(), sizeof(set), &set); + if (retval == -1) { + err_msg("Error setting user thread affinity\n"); + exit(1); + } + + if (!params->sched_param) { + retval = sched_setscheduler(0, SCHED_FIFO, &sp); + if (retval < 0) { + err_msg("Error setting timerlat u default priority: %s\n", strerror(errno)); + exit(1); + } + } else { + retval = __set_sched_attr(getpid(), params->sched_param); + if (retval) { + /* __set_sched_attr prints an error message, so */ + exit(0); + } + } + + if (params->cgroup_name) { + retval = set_pid_cgroup(gettid(), params->cgroup_name); + if (!retval) { + err_msg("Error setting timerlat u cgroup pid\n"); + pthread_exit(&retval); + } + } + + /* + * This is the tool's loop. If you want to use as base for your own tool... + * go ahead. + */ + snprintf(buffer, sizeof(buffer), "osnoise/per_cpu/cpu%d/timerlat_fd", cpu); + + timerlat_fd = tracefs_instance_file_open(NULL, buffer, O_RDONLY); + if (timerlat_fd < 0) { + err_msg("Error opening %s:%s\n", buffer, strerror(errno)); + exit(1); + } + + debug_msg("User-space timerlat pid %d on cpu %d\n", gettid(), cpu); + + /* add should continue with a signal handler */ + while (true) { + retval = read(timerlat_fd, buffer, 1024); + if (retval < 0) + break; + } + + close(timerlat_fd); + + debug_msg("Leaving timerlat pid %d on cpu %d\n", gettid(), cpu); + exit(0); +} + +/* + * timerlat_u_send_kill - send a kill signal for all processes + * + * Return the number of processes that received the kill. + */ +static int timerlat_u_send_kill(pid_t *procs, int nr_cpus) +{ + int killed = 0; + int i, retval; + + for (i = 0; i < nr_cpus; i++) { + if (!procs[i]) + continue; + retval = kill(procs[i], SIGKILL); + if (!retval) + killed++; + else + err_msg("Error killing child process %d\n", procs[i]); + } + + return killed; +} + +/** + * timerlat_u_dispatcher - dispatch one timerlatu/ process per monitored CPU + * + * This is a thread main that will fork one new process for each monitored + * CPU. It will wait for: + * + * - rtla to tell to kill the child processes + * - some child process to die, and the cleanup all the processes + * + * whichever comes first. + * + */ +void *timerlat_u_dispatcher(void *data) +{ + int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + struct timerlat_u_params *params = data; + char proc_name[128]; + int procs_count = 0; + int retval = 1; + pid_t *procs; + int wstatus; + pid_t pid; + int i; + + debug_msg("Dispatching timerlat u procs\n"); + + procs = calloc(nr_cpus, sizeof(pid_t)); + if (!procs) + pthread_exit(&retval); + + for (i = 0; i < nr_cpus; i++) { + if (params->set && !CPU_ISSET(i, params->set)) + continue; + + pid = fork(); + + /* child */ + if (!pid) { + + /* + * rename the process + */ + snprintf(proc_name, sizeof(proc_name), "timerlatu/%d", i); + pthread_setname_np(pthread_self(), proc_name); + prctl(PR_SET_NAME, (unsigned long)proc_name, 0, 0, 0); + + timerlat_u_main(i, params); + /* timerlat_u_main should exit()! Anyways... */ + pthread_exit(&retval); + } + + /* parent */ + if (pid == -1) { + timerlat_u_send_kill(procs, nr_cpus); + debug_msg("Failed to create child processes"); + pthread_exit(&retval); + } + + procs_count++; + procs[i] = pid; + } + + while (params->should_run) { + /* check if processes died */ + pid = waitpid(-1, &wstatus, WNOHANG); + if (pid != 0) { + for (i = 0; i < nr_cpus; i++) { + if (procs[i] == pid) { + procs[i] = 0; + procs_count--; + } + } + break; + } + + sleep(1); + } + + timerlat_u_send_kill(procs, nr_cpus); + + while (procs_count) { + pid = waitpid(-1, &wstatus, 0); + if (pid == -1) { + err_msg("Failed to monitor child processes"); + pthread_exit(&retval); + } + for (i = 0; i < nr_cpus; i++) { + if (procs[i] == pid) { + procs[i] = 0; + procs_count--; + } + } + } + + params->stopped_running = 1; + + free(procs); + retval = 0; + pthread_exit(&retval); + +} diff --git a/tools/tracing/rtla/src/timerlat_u.h b/tools/tracing/rtla/src/timerlat_u.h new file mode 100644 index 000000000000..661511908957 --- /dev/null +++ b/tools/tracing/rtla/src/timerlat_u.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ + +struct timerlat_u_params { + /* timerlat -> timerlat_u: user-space threads can keep running */ + int should_run; + /* timerlat_u -> timerlat: all timerlat_u threads left, no reason to continue */ + int stopped_running; + + /* threads config */ + cpu_set_t *set; + char *cgroup_name; + struct sched_attr *sched_param; +}; + +void *timerlat_u_dispatcher(void *data); diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c index 663a047f794d..623a38908ed5 100644 --- a/tools/tracing/rtla/src/utils.c +++ b/tools/tracing/rtla/src/utils.c @@ -3,6 +3,7 @@ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ +#define _GNU_SOURCE #include <dirent.h> #include <stdarg.h> #include <stdlib.h> @@ -88,27 +89,24 @@ void get_duration(time_t start_time, char *output, int output_size) } /* - * parse_cpu_list - parse a cpu_list filling a char vector with cpus set + * parse_cpu_set - parse a cpu_list filling cpu_set_t argument * - * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set the char - * in the monitored_cpus. + * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set + * filling cpu_set_t argument. * - * XXX: convert to a bitmask. + * Returns 1 on success, 0 otherwise. */ -int parse_cpu_list(char *cpu_list, char **monitored_cpus) +int parse_cpu_set(char *cpu_list, cpu_set_t *set) { - char *mon_cpus; const char *p; int end_cpu; int nr_cpus; int cpu; int i; - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + CPU_ZERO(set); - mon_cpus = calloc(nr_cpus, sizeof(char)); - if (!mon_cpus) - goto err; + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); for (p = cpu_list; *p; ) { cpu = atoi(p); @@ -128,12 +126,12 @@ int parse_cpu_list(char *cpu_list, char **monitored_cpus) end_cpu = cpu; if (cpu == end_cpu) { - debug_msg("cpu_list: adding cpu %d\n", cpu); - mon_cpus[cpu] = 1; + debug_msg("cpu_set: adding cpu %d\n", cpu); + CPU_SET(cpu, set); } else { for (i = cpu; i <= end_cpu; i++) { - debug_msg("cpu_list: adding cpu %d\n", i); - mon_cpus[i] = 1; + debug_msg("cpu_set: adding cpu %d\n", i); + CPU_SET(i, set); } } @@ -141,12 +139,9 @@ int parse_cpu_list(char *cpu_list, char **monitored_cpus) p++; } - *monitored_cpus = mon_cpus; - return 0; - err: - debug_msg("Error parsing the cpu list %s", cpu_list); + debug_msg("Error parsing the cpu set %s\n", cpu_list); return 1; } @@ -529,3 +524,296 @@ int set_cpu_dma_latency(int32_t latency) return fd; } + +#define _STR(x) #x +#define STR(x) _STR(x) + +/* + * find_mount - find a the mount point of a given fs + * + * Returns 0 if mount is not found, otherwise return 1 and fill mp + * with the mount point. + */ +static const int find_mount(const char *fs, char *mp, int sizeof_mp) +{ + char mount_point[MAX_PATH]; + char type[100]; + int found; + FILE *fp; + + fp = fopen("/proc/mounts", "r"); + if (!fp) + return 0; + + while (fscanf(fp, "%*s %" STR(MAX_PATH) "s %99s %*s %*d %*d\n", mount_point, type) == 2) { + if (strcmp(type, fs) == 0) { + found = 1; + break; + } + } + fclose(fp); + + if (!found) + return 0; + + memset(mp, 0, sizeof_mp); + strncpy(mp, mount_point, sizeof_mp - 1); + + debug_msg("Fs %s found at %s\n", fs, mp); + return 1; +} + +/* + * get_self_cgroup - get the current thread cgroup path + * + * Parse /proc/$$/cgroup file to get the thread's cgroup. As an example of line to parse: + * + * 0::/user.slice/user-0.slice/session-3.scope'\n' + * + * This function is interested in the content after the second : and before the '\n'. + * + * Returns 1 if a string was found, 0 otherwise. + */ +static int get_self_cgroup(char *self_cg, int sizeof_self_cg) +{ + char path[MAX_PATH], *start; + int fd, retval; + + snprintf(path, MAX_PATH, "/proc/%d/cgroup", getpid()); + + fd = open(path, O_RDONLY); + if (fd < 0) + return 0; + + retval = read(fd, path, MAX_PATH); + + close(fd); + + if (retval <= 0) + return 0; + + start = path; + + start = strstr(start, ":"); + if (!start) + return 0; + + /* skip ":" */ + start++; + + start = strstr(start, ":"); + if (!start) + return 0; + + /* skip ":" */ + start++; + + if (strlen(start) >= sizeof_self_cg) + return 0; + + snprintf(self_cg, sizeof_self_cg, "%s", start); + + /* Swap '\n' with '\0' */ + start = strstr(self_cg, "\n"); + + /* there must be '\n' */ + if (!start) + return 0; + + /* ok, it found a string after the second : and before the \n */ + *start = '\0'; + + return 1; +} + +/* + * set_comm_cgroup - Set cgroup to pid_t pid + * + * If cgroup argument is not NULL, the threads will move to the given cgroup. + * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used. + * + * Supports cgroup v2. + * + * Returns 1 on success, 0 otherwise. + */ +int set_pid_cgroup(pid_t pid, const char *cgroup) +{ + char cgroup_path[MAX_PATH - strlen("/cgroup.procs")]; + char cgroup_procs[MAX_PATH]; + char pid_str[24]; + int retval; + int cg_fd; + + retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path)); + if (!retval) { + err_msg("Did not find cgroupv2 mount point\n"); + return 0; + } + + if (!cgroup) { + retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)], + sizeof(cgroup_path) - strlen(cgroup_path)); + if (!retval) { + err_msg("Did not find self cgroup\n"); + return 0; + } + } else { + snprintf(&cgroup_path[strlen(cgroup_path)], + sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup); + } + + snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path); + + debug_msg("Using cgroup path at: %s\n", cgroup_procs); + + cg_fd = open(cgroup_procs, O_RDWR); + if (cg_fd < 0) + return 0; + + snprintf(pid_str, sizeof(pid_str), "%d\n", pid); + + retval = write(cg_fd, pid_str, strlen(pid_str)); + if (retval < 0) + err_msg("Error setting cgroup attributes for pid:%s - %s\n", + pid_str, strerror(errno)); + else + debug_msg("Set cgroup attributes for pid:%s\n", pid_str); + + close(cg_fd); + + return (retval >= 0); +} + +/** + * set_comm_cgroup - Set cgroup to threads starting with char *comm_prefix + * + * If cgroup argument is not NULL, the threads will move to the given cgroup. + * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used. + * + * Supports cgroup v2. + * + * Returns 1 on success, 0 otherwise. + */ +int set_comm_cgroup(const char *comm_prefix, const char *cgroup) +{ + char cgroup_path[MAX_PATH - strlen("/cgroup.procs")]; + char cgroup_procs[MAX_PATH]; + struct dirent *proc_entry; + DIR *procfs; + int retval; + int cg_fd; + + if (strlen(comm_prefix) >= MAX_PATH) { + err_msg("Command prefix is too long: %d < strlen(%s)\n", + MAX_PATH, comm_prefix); + return 0; + } + + retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path)); + if (!retval) { + err_msg("Did not find cgroupv2 mount point\n"); + return 0; + } + + if (!cgroup) { + retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)], + sizeof(cgroup_path) - strlen(cgroup_path)); + if (!retval) { + err_msg("Did not find self cgroup\n"); + return 0; + } + } else { + snprintf(&cgroup_path[strlen(cgroup_path)], + sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup); + } + + snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path); + + debug_msg("Using cgroup path at: %s\n", cgroup_procs); + + cg_fd = open(cgroup_procs, O_RDWR); + if (cg_fd < 0) + return 0; + + procfs = opendir("/proc"); + if (!procfs) { + err_msg("Could not open procfs\n"); + goto out_cg; + } + + while ((proc_entry = readdir(procfs))) { + + retval = procfs_is_workload_pid(comm_prefix, proc_entry); + if (!retval) + continue; + + retval = write(cg_fd, proc_entry->d_name, strlen(proc_entry->d_name)); + if (retval < 0) { + err_msg("Error setting cgroup attributes for pid:%s - %s\n", + proc_entry->d_name, strerror(errno)); + goto out_procfs; + } + + debug_msg("Set cgroup attributes for pid:%s\n", proc_entry->d_name); + } + + closedir(procfs); + close(cg_fd); + return 1; + +out_procfs: + closedir(procfs); +out_cg: + close(cg_fd); + return 0; +} + +/** + * auto_house_keeping - Automatically move rtla out of measurement threads + * + * Try to move rtla away from the tracer, if possible. + * + * Returns 1 on success, 0 otherwise. + */ +int auto_house_keeping(cpu_set_t *monitored_cpus) +{ + cpu_set_t rtla_cpus, house_keeping_cpus; + int retval; + + /* first get the CPUs in which rtla can actually run. */ + retval = sched_getaffinity(getpid(), sizeof(rtla_cpus), &rtla_cpus); + if (retval == -1) { + debug_msg("Could not get rtla affinity, rtla might run with the threads!\n"); + return 0; + } + + /* then check if the existing setup is already good. */ + CPU_AND(&house_keeping_cpus, &rtla_cpus, monitored_cpus); + if (!CPU_COUNT(&house_keeping_cpus)) { + debug_msg("rtla and the monitored CPUs do not share CPUs."); + debug_msg("Skipping auto house-keeping\n"); + return 1; + } + + /* remove the intersection */ + CPU_XOR(&house_keeping_cpus, &rtla_cpus, monitored_cpus); + + /* get only those that rtla can run */ + CPU_AND(&house_keeping_cpus, &house_keeping_cpus, &rtla_cpus); + + /* is there any cpu left? */ + if (!CPU_COUNT(&house_keeping_cpus)) { + debug_msg("Could not find any CPU for auto house-keeping\n"); + return 0; + } + + retval = sched_setaffinity(getpid(), sizeof(house_keeping_cpus), &house_keeping_cpus); + if (retval == -1) { + debug_msg("Could not set affinity for auto house-keeping\n"); + return 0; + } + + debug_msg("rtla automatically moved to an auto house-keeping cpu set\n"); + + return 1; +} diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h index 90e4f52a030b..04ed1e650495 100644 --- a/tools/tracing/rtla/src/utils.h +++ b/tools/tracing/rtla/src/utils.h @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 + #include <stdint.h> #include <time.h> +#include <sched.h> /* * '18446744073709551615\0' @@ -54,8 +56,13 @@ struct sched_attr { }; int parse_prio(char *arg, struct sched_attr *sched_param); +int parse_cpu_set(char *cpu_list, cpu_set_t *set); +int __set_sched_attr(int pid, struct sched_attr *attr); int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr); +int set_comm_cgroup(const char *comm_prefix, const char *cgroup); +int set_pid_cgroup(pid_t pid, const char *cgroup); int set_cpu_dma_latency(int32_t latency); +int auto_house_keeping(cpu_set_t *monitored_cpus); #define ns_to_usf(x) (((double)x/1000)) #define ns_to_per(total, part) ((part * 100) / (double)total) diff --git a/tools/usb/usbip/configure.ac b/tools/usb/usbip/configure.ac index 607d05c5ccfd..8debf934f8b7 100644 --- a/tools/usb/usbip/configure.ac +++ b/tools/usb/usbip/configure.ac @@ -94,11 +94,11 @@ AC_SUBST([USBIDS_DIR]) AC_MSG_CHECKING([whether to use fortify]) AC_ARG_WITH([fortify], [AS_HELP_STRING([--with-fortify], - [use _FORTIFY_SROUCE option when compiling)])], + [use _FORTIFY_SOURCE=2 option when compiling)])], dnl [ACTION-IF-GIVEN] [if test "$withval" = "yes"; then AC_MSG_RESULT([yes]) - CFLAGS="$CFLAGS -D_FORTIFY_SOURCE -O" + CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2 -O" else AC_MSG_RESULT([no]) CFLAGS="$CFLAGS -U_FORTIFY_SOURCE" diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c index b4aeb9f1f493..531a415538f9 100644 --- a/tools/usb/usbip/src/usbip_attach.c +++ b/tools/usb/usbip/src/usbip_attach.c @@ -86,7 +86,7 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) rc = usbip_vhci_driver_open(); if (rc < 0) { - err("open vhci_driver"); + err("open vhci_driver (is vhci_hcd loaded?)"); goto err_out; } diff --git a/tools/usb/usbip/src/usbip_detach.c b/tools/usb/usbip/src/usbip_detach.c index aec993159036..b29101986b5a 100644 --- a/tools/usb/usbip/src/usbip_detach.c +++ b/tools/usb/usbip/src/usbip_detach.c @@ -50,7 +50,7 @@ static int detach_port(char *port) ret = usbip_vhci_driver_open(); if (ret < 0) { - err("open vhci_driver"); + err("open vhci_driver (is vhci_hcd loaded?)"); return -1; } diff --git a/tools/usb/usbip/src/usbip_port.c b/tools/usb/usbip/src/usbip_port.c index 4d14387df13d..21a20e378419 100644 --- a/tools/usb/usbip/src/usbip_port.c +++ b/tools/usb/usbip/src/usbip_port.c @@ -18,7 +18,7 @@ static int list_imported_devices(void) ret = usbip_vhci_driver_open(); if (ret < 0) { - err("open vhci_driver"); + err("open vhci_driver (is vhci_hcd loaded?)"); goto err_names_free; } diff --git a/tools/virtio/Makefile b/tools/virtio/Makefile index 7b7139d97d74..d128925980e0 100644 --- a/tools/virtio/Makefile +++ b/tools/virtio/Makefile @@ -4,7 +4,18 @@ test: virtio_test vringh_test virtio_test: virtio_ring.o virtio_test.o vringh_test: vringh_test.o vringh.o virtio_ring.o -CFLAGS += -g -O2 -Werror -Wno-maybe-uninitialized -Wall -I. -I../include/ -I ../../usr/include/ -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE -include ../../include/linux/kconfig.h -mfunction-return=thunk -fcf-protection=none -mindirect-branch-register +try-run = $(shell set -e; \ + if ($(1)) >/dev/null 2>&1; \ + then echo "$(2)"; \ + else echo "$(3)"; \ + fi) + +__cc-option = $(call try-run,\ + $(1) -Werror $(2) -c -x c /dev/null -o /dev/null,$(2),) +cc-option = $(call __cc-option, $(CC),$(1)) + +CFLAGS += -g -O2 -Werror -Wno-maybe-uninitialized -Wall -I. -I../include/ -I ../../usr/include/ -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE -include ../../include/linux/kconfig.h $(call cc-option,-mfunction-return=thunk) $(call cc-option,-fcf-protection=none) $(call cc-option,-mindirect-branch-register) + CFLAGS += -pthread LDFLAGS += -pthread vpath %.c ../../drivers/virtio ../../drivers/vhost diff --git a/tools/virtio/ringtest/.gitignore b/tools/virtio/ringtest/.gitignore new file mode 100644 index 000000000000..100b9e30c0f4 --- /dev/null +++ b/tools/virtio/ringtest/.gitignore @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +/noring +/ptr_ring +/ring +/virtio_ring_0_9 +/virtio_ring_inorder +/virtio_ring_poll diff --git a/tools/virtio/ringtest/main.h b/tools/virtio/ringtest/main.h index b68920d52750..d18dd317e27f 100644 --- a/tools/virtio/ringtest/main.h +++ b/tools/virtio/ringtest/main.h @@ -8,6 +8,7 @@ #ifndef MAIN_H #define MAIN_H +#include <assert.h> #include <stdbool.h> extern int param; @@ -95,6 +96,8 @@ extern unsigned ring_size; #define cpu_relax() asm ("rep; nop" ::: "memory") #elif defined(__s390x__) #define cpu_relax() barrier() +#elif defined(__aarch64__) +#define cpu_relax() asm ("yield" ::: "memory") #else #define cpu_relax() assert(0) #endif @@ -112,6 +115,8 @@ static inline void busy_wait(void) #if defined(__x86_64__) || defined(__i386__) #define smp_mb() asm volatile("lock; addl $0,-132(%%rsp)" ::: "memory", "cc") +#elif defined(__aarch64__) +#define smp_mb() asm volatile("dmb ish" ::: "memory") #else /* * Not using __ATOMIC_SEQ_CST since gcc docs say they are only synchronized @@ -136,10 +141,16 @@ static inline void busy_wait(void) #if defined(__i386__) || defined(__x86_64__) || defined(__s390x__) #define smp_wmb() barrier() +#elif defined(__aarch64__) +#define smp_wmb() asm volatile("dmb ishst" ::: "memory") #else #define smp_wmb() smp_release() #endif +#ifndef __always_inline +#define __always_inline inline __attribute__((always_inline)) +#endif + static __always_inline void __read_once_size(const volatile void *p, void *res, int size) { diff --git a/tools/virtio/virtio-trace/README b/tools/virtio/virtio-trace/README index 4fb9368bf751..0127ff0c54b0 100644 --- a/tools/virtio/virtio-trace/README +++ b/tools/virtio/virtio-trace/README @@ -95,7 +95,7 @@ Run 1) Enable ftrace in the guest <Example> - # echo 1 > /sys/kernel/debug/tracing/events/sched/enable + # echo 1 > /sys/kernel/tracing/events/sched/enable 2) Run trace agent in the guest This agent must be operated as root. diff --git a/tools/virtio/virtio-trace/trace-agent.c b/tools/virtio/virtio-trace/trace-agent.c index cdfe77c2b4c8..7e2d9bbf0b84 100644 --- a/tools/virtio/virtio-trace/trace-agent.c +++ b/tools/virtio/virtio-trace/trace-agent.c @@ -18,8 +18,9 @@ #define PIPE_DEF_BUFS 16 #define PIPE_MIN_SIZE (PAGE_SIZE*PIPE_DEF_BUFS) #define PIPE_MAX_SIZE (1024*1024) -#define READ_PATH_FMT \ - "/sys/kernel/debug/tracing/per_cpu/cpu%d/trace_pipe_raw" +#define TRACEFS "/sys/kernel/tracing" +#define DEBUGFS "/sys/kernel/debug/tracing" +#define READ_PATH_FMT "%s/per_cpu/cpu%d/trace_pipe_raw" #define WRITE_PATH_FMT "/dev/virtio-ports/trace-path-cpu%d" #define CTL_PATH "/dev/virtio-ports/agent-ctl-path" @@ -120,9 +121,12 @@ static const char *make_path(int cpu_num, bool this_is_write_path) if (this_is_write_path) /* write(output) path */ ret = snprintf(buf, PATH_MAX, WRITE_PATH_FMT, cpu_num); - else + else { /* read(input) path */ - ret = snprintf(buf, PATH_MAX, READ_PATH_FMT, cpu_num); + ret = snprintf(buf, PATH_MAX, READ_PATH_FMT, TRACEFS, cpu_num); + if (ret > 0 && access(buf, F_OK) != 0) + ret = snprintf(buf, PATH_MAX, READ_PATH_FMT, DEBUGFS, cpu_num); + } if (ret <= 0) { pr_err("Failed to generate %s path(CPU#%d):%d\n", diff --git a/tools/workqueue/wq_monitor.py b/tools/workqueue/wq_monitor.py new file mode 100644 index 000000000000..6e258d123e8c --- /dev/null +++ b/tools/workqueue/wq_monitor.py @@ -0,0 +1,168 @@ +#!/usr/bin/env drgn +# +# Copyright (C) 2023 Tejun Heo <tj@kernel.org> +# Copyright (C) 2023 Meta Platforms, Inc. and affiliates. + +desc = """ +This is a drgn script to monitor workqueues. For more info on drgn, visit +https://github.com/osandov/drgn. + + total Total number of work items executed by the workqueue. + + infl The number of currently in-flight work items. + + CPUtime Total CPU time consumed by the workqueue in seconds. This is + sampled from scheduler ticks and only provides ballpark + measurement. "nohz_full=" CPUs are excluded from measurement. + + CPUitsv The number of times a concurrency-managed work item hogged CPU + longer than the threshold (workqueue.cpu_intensive_thresh_us) + and got excluded from concurrency management to avoid stalling + other work items. + + CMwake The number of concurrency-management wake-ups while executing a + work item of the workqueue. + + mayday The number of times the rescuer was requested while waiting for + new worker creation. + + rescued The number of work items executed by the rescuer. +""" + +import sys +import signal +import os +import re +import time +import json + +import drgn +from drgn.helpers.linux.list import list_for_each_entry,list_empty +from drgn.helpers.linux.cpumask import for_each_possible_cpu + +import argparse +parser = argparse.ArgumentParser(description=desc, + formatter_class=argparse.RawTextHelpFormatter) +parser.add_argument('workqueue', metavar='REGEX', nargs='*', + help='Target workqueue name patterns (all if empty)') +parser.add_argument('-i', '--interval', metavar='SECS', type=float, default=1, + help='Monitoring interval (0 to print once and exit)') +parser.add_argument('-j', '--json', action='store_true', + help='Output in json') +args = parser.parse_args() + +def err(s): + print(s, file=sys.stderr, flush=True) + sys.exit(1) + +workqueues = prog['workqueues'] + +WQ_UNBOUND = prog['WQ_UNBOUND'] +WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM'] + +PWQ_STAT_STARTED = prog['PWQ_STAT_STARTED'] # work items started execution +PWQ_STAT_COMPLETED = prog['PWQ_STAT_COMPLETED'] # work items completed execution +PWQ_STAT_CPU_TIME = prog['PWQ_STAT_CPU_TIME'] # total CPU time consumed +PWQ_STAT_CPU_INTENSIVE = prog['PWQ_STAT_CPU_INTENSIVE'] # wq_cpu_intensive_thresh_us violations +PWQ_STAT_CM_WAKEUP = prog['PWQ_STAT_CM_WAKEUP'] # concurrency-management worker wakeups +PWQ_STAT_MAYDAY = prog['PWQ_STAT_MAYDAY'] # maydays to rescuer +PWQ_STAT_RESCUED = prog['PWQ_STAT_RESCUED'] # linked work items executed by rescuer +PWQ_NR_STATS = prog['PWQ_NR_STATS'] + +class WqStats: + def __init__(self, wq): + self.name = wq.name.string_().decode() + self.unbound = wq.flags & WQ_UNBOUND != 0 + self.mem_reclaim = wq.flags & WQ_MEM_RECLAIM != 0 + self.stats = [0] * PWQ_NR_STATS + for pwq in list_for_each_entry('struct pool_workqueue', wq.pwqs.address_of_(), 'pwqs_node'): + for i in range(PWQ_NR_STATS): + self.stats[i] += int(pwq.stats[i]) + + def dict(self, now): + return { 'timestamp' : now, + 'name' : self.name, + 'unbound' : self.unbound, + 'mem_reclaim' : self.mem_reclaim, + 'started' : self.stats[PWQ_STAT_STARTED], + 'completed' : self.stats[PWQ_STAT_COMPLETED], + 'cpu_time' : self.stats[PWQ_STAT_CPU_TIME], + 'cpu_intensive' : self.stats[PWQ_STAT_CPU_INTENSIVE], + 'cm_wakeup' : self.stats[PWQ_STAT_CM_WAKEUP], + 'mayday' : self.stats[PWQ_STAT_MAYDAY], + 'rescued' : self.stats[PWQ_STAT_RESCUED], } + + def table_header_str(): + return f'{"":>24} {"total":>8} {"infl":>5} {"CPUtime":>8} '\ + f'{"CPUitsv":>7} {"CMwake":>7} {"mayday":>7} {"rescued":>7}' + + def table_row_str(self): + cpu_intensive = '-' + cm_wakeup = '-' + mayday = '-' + rescued = '-' + + if not self.unbound: + cpu_intensive = str(self.stats[PWQ_STAT_CPU_INTENSIVE]) + cm_wakeup = str(self.stats[PWQ_STAT_CM_WAKEUP]) + + if self.mem_reclaim: + mayday = str(self.stats[PWQ_STAT_MAYDAY]) + rescued = str(self.stats[PWQ_STAT_RESCUED]) + + out = f'{self.name[-24:]:24} ' \ + f'{self.stats[PWQ_STAT_STARTED]:8} ' \ + f'{max(self.stats[PWQ_STAT_STARTED] - self.stats[PWQ_STAT_COMPLETED], 0):5} ' \ + f'{self.stats[PWQ_STAT_CPU_TIME] / 1000000:8.1f} ' \ + f'{cpu_intensive:>7} ' \ + f'{cm_wakeup:>7} ' \ + f'{mayday:>7} ' \ + f'{rescued:>7} ' + return out.rstrip(':') + +exit_req = False + +def sigint_handler(signr, frame): + global exit_req + exit_req = True + +def main(): + # handle args + table_fmt = not args.json + interval = args.interval + + re_str = None + if args.workqueue: + for r in args.workqueue: + if re_str is None: + re_str = r + else: + re_str += '|' + r + + filter_re = re.compile(re_str) if re_str else None + + # monitoring loop + signal.signal(signal.SIGINT, sigint_handler) + + while not exit_req: + now = time.time() + + if table_fmt: + print() + print(WqStats.table_header_str()) + + for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): + stats = WqStats(wq) + if filter_re and not filter_re.search(stats.name): + continue + if table_fmt: + print(stats.table_row_str()) + else: + print(stats.dict(now)) + + if interval == 0: + break + time.sleep(interval) + +if __name__ == "__main__": + main() |