| // SPDX-License-Identifier: GPL-2.0 | |
| #include <test_progs.h> | |
| #include "bpf/libbpf_internal.h" | |
| #include "test_global_percpu_data.skel.h" | |
| #include "test_global_percpu_data.lskel.h" | |
| void test_global_data_init(void) | |
| { | |
| const char *file = "./test_global_data.bpf.o"; | |
| int err = -ENOMEM, map_fd, zero = 0; | |
| __u8 *buff = NULL, *newval = NULL; | |
| struct bpf_object *obj; | |
| struct bpf_map *map; | |
| __u32 duration = 0; | |
| size_t sz; | |
| obj = bpf_object__open_file(file, NULL); | |
| err = libbpf_get_error(obj); | |
| if (CHECK_FAIL(err)) | |
| return; | |
| map = bpf_object__find_map_by_name(obj, ".rodata"); | |
| if (CHECK_FAIL(!map || !bpf_map__is_internal(map))) | |
| goto out; | |
| sz = bpf_map__value_size(map); | |
| newval = malloc(sz); | |
| if (CHECK_FAIL(!newval)) | |
| goto out; | |
| memset(newval, 0, sz); | |
| /* wrong size, should fail */ | |
| err = bpf_map__set_initial_value(map, newval, sz - 1); | |
| if (CHECK(!err, "reject set initial value wrong size", "err %d\n", err)) | |
| goto out; | |
| err = bpf_map__set_initial_value(map, newval, sz); | |
| if (CHECK(err, "set initial value", "err %d\n", err)) | |
| goto out; | |
| err = bpf_object__load(obj); | |
| if (CHECK_FAIL(err)) | |
| goto out; | |
| map_fd = bpf_map__fd(map); | |
| if (CHECK_FAIL(map_fd < 0)) | |
| goto out; | |
| buff = malloc(sz); | |
| if (buff) | |
| err = bpf_map_lookup_elem(map_fd, &zero, buff); | |
| if (CHECK(!buff || err || memcmp(buff, newval, sz), | |
| "compare .rodata map data override", | |
| "err %d errno %d\n", err, errno)) | |
| goto out; | |
| memset(newval, 1, sz); | |
| /* object loaded - should fail */ | |
| err = bpf_map__set_initial_value(map, newval, sz); | |
| CHECK(!err, "reject set initial value after load", "err %d\n", err); | |
| out: | |
| free(buff); | |
| free(newval); | |
| bpf_object__close(obj); | |
| } | |
| static void test_percpu_data_on_cpus(struct bpf_map *map, int map_fd, int prog_fd, int *runp) | |
| { | |
| struct test_global_percpu_data__percpu *data = NULL; | |
| int i, err, key = 0, num_online, run = 0; | |
| __u64 args[2] = {0x1234ULL, 0x5678ULL}; | |
| size_t data_sz; | |
| bool *online; | |
| LIBBPF_OPTS(bpf_test_run_opts, topts, | |
| .ctx_in = args, | |
| .ctx_size_in = sizeof(args), | |
| .flags = BPF_F_TEST_RUN_ON_CPU, | |
| ); | |
| err = parse_cpu_mask_file("/sys/devices/system/cpu/online", &online, &num_online); | |
| if (!ASSERT_OK(err, "parse_cpu_mask_file")) | |
| return; | |
| data_sz = map ? bpf_map__value_size(map) : sizeof(*data); | |
| data = calloc(1, data_sz); | |
| if (!ASSERT_OK_PTR(data, "calloc percpu data")) | |
| goto out; | |
| /* run on every online-CPU */ | |
| for (i = 0; i < num_online; i++) { | |
| __u64 flags; | |
| if (!online[i]) | |
| continue; | |
| topts.cpu = i; | |
| topts.retval = -1; | |
| err = bpf_prog_test_run_opts(prog_fd, &topts); | |
| ASSERT_OK(err, "bpf_prog_test_run_opts"); | |
| ASSERT_EQ(topts.retval, 0, "bpf_prog_test_run_opts retval"); | |
| memset(data, 0, data_sz); | |
| flags = ((__u64) i << 32) | BPF_F_CPU; | |
| if (map) | |
| err = bpf_map__lookup_elem(map, &key, sizeof(key), data, data_sz, flags); | |
| else | |
| err = bpf_map_lookup_elem_flags(map_fd, &key, data, flags); | |
| if (!ASSERT_OK(err, "lookup_elem on cpu")) | |
| break; | |
| ASSERT_EQ(*runp, ++run, "run"); | |
| ASSERT_EQ(data->cpu_id[0], i, "cpu_id"); | |
| ASSERT_EQ(data->data, 1, "data"); | |
| ASSERT_TRUE(data->set, "set"); | |
| ASSERT_EQ(data->nums[6], 0xc0de, "nums[6]"); | |
| ASSERT_EQ(data->struct_data.i, 1, "struct_data.i"); | |
| ASSERT_TRUE(data->struct_data.set, "struct_data.set"); | |
| ASSERT_EQ(data->struct_data.nums[6], 0xc0de, "struct_data.nums[6]"); | |
| } | |
| out: | |
| free(data); | |
| free(online); | |
| } | |
| static void test_global_percpu_data_init(void) | |
| { | |
| struct test_global_percpu_data__percpu init_value = {}; | |
| struct test_global_percpu_data__percpu *init_data; | |
| const __u32 desired_sz = sysconf(_SC_PAGE_SIZE); | |
| struct test_global_percpu_data *skel = NULL; | |
| size_t init_data_sz; | |
| struct bpf_map *map; | |
| int prog_fd, err; | |
| skel = test_global_percpu_data__open(); | |
| if (!ASSERT_OK_PTR(skel, "test_global_percpu_data__open")) | |
| goto out; | |
| if (!ASSERT_OK_PTR(skel->percpu, "skel->percpu")) | |
| goto out; | |
| if (!ASSERT_OK_PTR(skel->data_percpu, "skel->data_percpu")) | |
| goto out; | |
| if (!ASSERT_OK_PTR(skel->percpu_data, "skel->percpu_data")) | |
| goto out; | |
| if (!ASSERT_OK_PTR(skel->percpu_looooooooong, "skel->percpu_looooooooong")) | |
| goto out; | |
| ASSERT_STREQ(bpf_map__name(skel->maps.percpu_data), ".percpu.data", | |
| ".percpu.data map name"); | |
| ASSERT_STREQ(bpf_map__name(skel->maps.data_percpu), ".data.percpu", | |
| ".data.percpu map name"); | |
| ASSERT_STREQ(bpf_map__name(skel->maps.percpu_looooooooong), ".percpu.looooooooong", | |
| "long map name"); | |
| ASSERT_STREQ(bpf_map__name(skel->maps.percpu), ".percpu", "map name"); | |
| ASSERT_EQ(skel->percpu->data, -1, "skel->percpu->data"); | |
| ASSERT_FALSE(skel->percpu->set, "skel->percpu->set"); | |
| ASSERT_EQ(skel->percpu->nums[6], 0, "skel->percpu->nums[6]"); | |
| ASSERT_EQ(skel->percpu->struct_data.i, -1, "struct_data.i"); | |
| ASSERT_FALSE(skel->percpu->struct_data.set, "struct_data.set"); | |
| ASSERT_EQ(skel->percpu->struct_data.nums[6], 0, "struct_data.nums[6]"); | |
| map = skel->maps.percpu; | |
| if (!ASSERT_EQ(bpf_map__type(map), BPF_MAP_TYPE_PERCPU_ARRAY, "bpf_map__type")) | |
| goto out; | |
| init_value.data = 2; | |
| init_value.nums[6] = -1; | |
| init_value.struct_data.i = 2; | |
| init_value.struct_data.nums[6] = -1; | |
| err = bpf_map__set_initial_value(map, &init_value, sizeof(init_value)); | |
| if (!ASSERT_OK(err, "bpf_map__set_initial_value")) | |
| goto out; | |
| init_data = bpf_map__initial_value(map, &init_data_sz); | |
| if (!ASSERT_OK_PTR(init_data, "bpf_map__initial_value")) | |
| goto out; | |
| ASSERT_EQ(init_data->data, init_value.data, "init_value data"); | |
| ASSERT_EQ(init_data->set, init_value.set, "init_value set"); | |
| ASSERT_EQ(init_data->struct_data.i, init_value.struct_data.i, "init_value struct_data.i"); | |
| ASSERT_EQ(init_data->struct_data.nums[6], init_value.struct_data.nums[6], | |
| "init_value struct_data.nums[6]"); | |
| ASSERT_EQ(init_data_sz, sizeof(init_value), "init_value size"); | |
| ASSERT_EQ((void *) init_data, (void *) skel->percpu, "skel->percpu eq init_data"); | |
| ASSERT_EQ(skel->percpu->data, init_value.data, "skel->percpu->data"); | |
| ASSERT_EQ(skel->percpu->set, init_value.set, "skel->percpu->set"); | |
| ASSERT_EQ(skel->percpu->struct_data.i, init_value.struct_data.i, | |
| "skel->percpu->struct_data.i"); | |
| ASSERT_EQ(skel->percpu->struct_data.nums[6], init_value.struct_data.nums[6], | |
| "skel->percpu->struct_data.nums[6]"); | |
| ASSERT_GT(desired_sz, sizeof(init_value), "desired_sz"); | |
| err = bpf_map__set_value_size(map, desired_sz); | |
| if (!ASSERT_OK(err, "bpf_map__set_value_size")) | |
| goto out; | |
| if (!ASSERT_EQ(bpf_map__value_size(map), desired_sz, "percpu value size")) | |
| goto out; | |
| if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, "percpu BTF value type")) | |
| goto out; | |
| init_data = bpf_map__initial_value(map, &init_data_sz); | |
| if (!ASSERT_OK_PTR(init_data, "resized bpf_map__initial_value")) | |
| goto out; | |
| if (!ASSERT_EQ(init_data_sz, desired_sz, "resized initial value size")) | |
| goto out; | |
| if (!ASSERT_EQ(init_data->data, init_value.data, "resized initial value data")) | |
| goto out; | |
| err = test_global_percpu_data__load(skel); | |
| if (!ASSERT_OK(err, "test_global_percpu_data__load")) | |
| goto out; | |
| ASSERT_OK_PTR(skel->percpu, "skel->percpu"); | |
| prog_fd = bpf_program__fd(skel->progs.update_percpu_data); | |
| test_percpu_data_on_cpus(map, bpf_map__fd(map), prog_fd, &skel->bss->run); | |
| out: | |
| test_global_percpu_data__destroy(skel); | |
| } | |
| static void test_global_percpu_data_lskel(void) | |
| { | |
| struct test_global_percpu_data_lskel *lskel = NULL; | |
| int prog_fd, map_fd; | |
| lskel = test_global_percpu_data_lskel__open_and_load(); | |
| if (!ASSERT_OK_PTR(lskel, "test_global_percpu_data_lskel__open_and_load")) | |
| goto out; | |
| map_fd = lskel->maps.percpu.map_fd; | |
| prog_fd = lskel->progs.update_percpu_data.prog_fd; | |
| test_percpu_data_on_cpus(NULL, map_fd, prog_fd, &lskel->bss->run); | |
| out: | |
| test_global_percpu_data_lskel__destroy(lskel); | |
| } | |
| static int create_rdonly_percpu_array(void) | |
| { | |
| LIBBPF_OPTS(bpf_map_create_opts, map_opts, | |
| .map_flags = BPF_F_RDONLY_PROG, | |
| ); | |
| int key = 0, map_fd, err; | |
| __u64 value = 0; | |
| map_fd = bpf_map_create(BPF_MAP_TYPE_PERCPU_ARRAY, "percpu_ro_map", sizeof(int), | |
| sizeof(__u64), 1, &map_opts); | |
| if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) | |
| return -1; | |
| err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS); | |
| if (!ASSERT_OK(err, "bpf_map_update_elem")) | |
| goto out; | |
| err = bpf_map_freeze(map_fd); | |
| if (!ASSERT_OK(err, "bpf_map_freeze")) | |
| goto out; | |
| return map_fd; | |
| out: | |
| close(map_fd); | |
| return -1; | |
| } | |
| static void test_global_percpu_data_rdonly_direct_read(void) | |
| { | |
| /* | |
| * Raw instructions with manually prepared rdonly percpu_array map | |
| * for testing direct-read global percpu data, because libbpf | |
| * doesn't have rdonly internal percpu_array map support for | |
| * global percpu data. | |
| */ | |
| struct bpf_insn insns[] = { | |
| BPF_LD_MAP_VALUE(BPF_REG_1, 0, 0), | |
| BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, 0), | |
| BPF_EXIT_INSN(), | |
| }; | |
| int map_fd, prog_fd; | |
| map_fd = create_rdonly_percpu_array(); | |
| if (map_fd < 0) | |
| return; | |
| insns[0].imm = map_fd; | |
| prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "percpu_ro_prog", "GPL", insns, | |
| ARRAY_SIZE(insns), NULL); | |
| if (ASSERT_GE(prog_fd, 0, "bpf_prog_load")) | |
| close(prog_fd); | |
| close(map_fd); | |
| } | |
| static void test_global_percpu_data_rdonly_direct_write(void) | |
| { | |
| LIBBPF_OPTS(bpf_prog_load_opts, prog_opts); | |
| /* See the comment in test_global_percpu_data_rdonly_direct_read() */ | |
| struct bpf_insn insns[] = { | |
| BPF_LD_MAP_VALUE(BPF_REG_1, 0, 0), | |
| BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, 0), | |
| BPF_ST_MEM(BPF_DW, BPF_REG_1, 0, 0), | |
| BPF_EXIT_INSN(), | |
| }; | |
| char log_buf[256] = {}; | |
| int map_fd, prog_fd; | |
| prog_opts.log_buf = log_buf; | |
| prog_opts.log_size = sizeof(log_buf); | |
| prog_opts.log_level = 1; | |
| map_fd = create_rdonly_percpu_array(); | |
| if (map_fd < 0) | |
| return; | |
| insns[0].imm = map_fd; | |
| prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "percpu_ro_prog", "GPL", insns, | |
| ARRAY_SIZE(insns), &prog_opts); | |
| if (!ASSERT_LT(prog_fd, 0, "bpf_prog_load")) | |
| close(prog_fd); | |
| else | |
| ASSERT_HAS_SUBSTR(log_buf, "write into map forbidden", "verifier log"); | |
| close(map_fd); | |
| } | |
| static void test_global_percpu_data_verifier_log(void) | |
| { | |
| RUN_TESTS(test_global_percpu_data); | |
| } | |
| static void test_global_percpu_data_iter(void) | |
| { | |
| DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); | |
| struct test_global_percpu_data *skel; | |
| union bpf_iter_link_info linfo = {}; | |
| struct bpf_link *link = NULL; | |
| int fd, num_cpus, len, err; | |
| char buf[16]; | |
| num_cpus = libbpf_num_possible_cpus(); | |
| if (!ASSERT_GT(num_cpus, 0, "libbpf_num_possible_cpus")) | |
| return; | |
| skel = test_global_percpu_data__open(); | |
| if (!ASSERT_OK_PTR(skel, "test_global_percpu_data__open")) | |
| return; | |
| skel->rodata->num_cpus = num_cpus; | |
| skel->rodata->num_off = offsetof(struct test_global_percpu_data__percpu, | |
| struct_data.nums[6]); | |
| skel->rodata->elem_sz = roundup(sizeof(struct test_global_percpu_data__percpu), 8); | |
| skel->percpu->struct_data.nums[6] = 0xc0de; | |
| err = test_global_percpu_data__load(skel); | |
| if (!ASSERT_OK(err, "test_global_percpu_data__load")) | |
| goto out; | |
| linfo.map.map_fd = bpf_map__fd(skel->maps.percpu); | |
| opts.link_info = &linfo; | |
| opts.link_info_len = sizeof(linfo); | |
| link = bpf_program__attach_iter(skel->progs.dump_percpu_data, &opts); | |
| if (!ASSERT_OK_PTR(link, "bpf_program__attach_iter")) | |
| goto out; | |
| fd = bpf_iter_create(bpf_link__fd(link)); | |
| if (!ASSERT_GE(fd, 0, "bpf_iter_create")) | |
| goto out; | |
| while ((len = read(fd, buf, sizeof(buf))) > 0) | |
| do { } while (0); | |
| ASSERT_EQ(len, 0, "read iter"); | |
| ASSERT_TRUE(skel->bss->run_iter, "run_iter"); | |
| ASSERT_EQ(skel->bss->sum, 0xc0de * num_cpus, "sum"); | |
| close(fd); | |
| out: | |
| bpf_link__destroy(link); | |
| test_global_percpu_data__destroy(skel); | |
| } | |
| void test_global_percpu_data(void) | |
| { | |
| if (!feat_supported(NULL, FEAT_PERCPU_DATA)) { | |
| test__skip(); | |
| return; | |
| } | |
| if (test__start_subtest("init")) | |
| test_global_percpu_data_init(); | |
| if (test__start_subtest("lskel")) | |
| test_global_percpu_data_lskel(); | |
| if (test__start_subtest("rdonly_direct_read")) | |
| test_global_percpu_data_rdonly_direct_read(); | |
| if (test__start_subtest("rdonly_direct_write")) | |
| test_global_percpu_data_rdonly_direct_write(); | |
| test_global_percpu_data_verifier_log(); | |
| if (test__start_subtest("iter")) | |
| test_global_percpu_data_iter(); | |
| } |