Commit 266a5579 authored by Alexei Starovoitov's avatar Alexei Starovoitov Committed by Andrii Nakryiko
Browse files

Merge branch 'fix-lockdep-warning-for-htab-of-map'



Hou Tao says:

====================
The patch set fixes a lockdep warning for htab of map. The
warning is found when running test_maps. The warning occurs when
htab_put_fd_value() attempts to acquire map_idr_lock to free the map id
of the inner map while already holding the bucket lock (raw_spinlock_t).

The fix moves the invocation of free_htab_elem() after
htab_unlock_bucket() and adds a test case to verify the solution.
====================

Acked-by: default avatarSebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents 269e7c97 cb55657c
Loading
Loading
Loading
Loading
+39 −17
Original line number Diff line number Diff line
@@ -896,9 +896,12 @@ static int htab_map_get_next_key(struct bpf_map *map, void *key, void *next_key)
static void htab_elem_free(struct bpf_htab *htab, struct htab_elem *l)
{
	check_and_free_fields(htab, l);

	migrate_disable();
	if (htab->map.map_type == BPF_MAP_TYPE_PERCPU_HASH)
		bpf_mem_cache_free(&htab->pcpu_ma, l->ptr_to_pptr);
	bpf_mem_cache_free(&htab->ma, l);
	migrate_enable();
}

static void htab_put_fd_value(struct bpf_htab *htab, struct htab_elem *l)
@@ -948,7 +951,7 @@ static void free_htab_elem(struct bpf_htab *htab, struct htab_elem *l)
	if (htab_is_prealloc(htab)) {
		bpf_map_dec_elem_count(&htab->map);
		check_and_free_fields(htab, l);
		__pcpu_freelist_push(&htab->freelist, &l->fnode);
		pcpu_freelist_push(&htab->freelist, &l->fnode);
	} else {
		dec_elem_count(htab);
		htab_elem_free(htab, l);
@@ -1018,7 +1021,6 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
			 */
			pl_new = this_cpu_ptr(htab->extra_elems);
			l_new = *pl_new;
			htab_put_fd_value(htab, old_elem);
			*pl_new = old_elem;
		} else {
			struct pcpu_freelist_node *l;
@@ -1105,6 +1107,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
	struct htab_elem *l_new = NULL, *l_old;
	struct hlist_nulls_head *head;
	unsigned long flags;
	void *old_map_ptr;
	struct bucket *b;
	u32 key_size, hash;
	int ret;
@@ -1183,12 +1186,27 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
	hlist_nulls_add_head_rcu(&l_new->hash_node, head);
	if (l_old) {
		hlist_nulls_del_rcu(&l_old->hash_node);

		/* l_old has already been stashed in htab->extra_elems, free
		 * its special fields before it is available for reuse. Also
		 * save the old map pointer in htab of maps before unlock
		 * and release it after unlock.
		 */
		old_map_ptr = NULL;
		if (htab_is_prealloc(htab)) {
			if (map->ops->map_fd_put_ptr)
				old_map_ptr = fd_htab_map_get_ptr(map, l_old);
			check_and_free_fields(htab, l_old);
		}
	}
	htab_unlock_bucket(htab, b, hash, flags);
	if (l_old) {
		if (old_map_ptr)
			map->ops->map_fd_put_ptr(map, old_map_ptr, true);
		if (!htab_is_prealloc(htab))
			free_htab_elem(htab, l_old);
		else
			check_and_free_fields(htab, l_old);
	}
	ret = 0;
	return 0;
err:
	htab_unlock_bucket(htab, b, hash, flags);
	return ret;
@@ -1432,15 +1450,15 @@ static long htab_map_delete_elem(struct bpf_map *map, void *key)
		return ret;

	l = lookup_elem_raw(head, hash, key, key_size);

	if (l) {
	if (l)
		hlist_nulls_del_rcu(&l->hash_node);
		free_htab_elem(htab, l);
	} else {
	else
		ret = -ENOENT;
	}

	htab_unlock_bucket(htab, b, hash, flags);

	if (l)
		free_htab_elem(htab, l);
	return ret;
}

@@ -1853,13 +1871,14 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map,
			 * may cause deadlock. See comments in function
			 * prealloc_lru_pop(). Let us do bpf_lru_push_free()
			 * after releasing the bucket lock.
			 *
			 * For htab of maps, htab_put_fd_value() in
			 * free_htab_elem() may acquire a spinlock with bucket
			 * lock being held and it violates the lock rule, so
			 * invoke free_htab_elem() after unlock as well.
			 */
			if (is_lru_map) {
			l->batch_flink = node_to_free;
			node_to_free = l;
			} else {
				free_htab_elem(htab, l);
			}
		}
		dst_key += key_size;
		dst_val += value_size;
@@ -1871,7 +1890,10 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map,
	while (node_to_free) {
		l = node_to_free;
		node_to_free = node_to_free->batch_flink;
		if (is_lru_map)
			htab_lru_push_free(htab, l);
		else
			free_htab_elem(htab, l);
	}

next_batch:
+3 −0
Original line number Diff line number Diff line
@@ -67,5 +67,8 @@ static inline void bpf_strlcpy(char *dst, const char *src, size_t sz)
#define sys_gettid() syscall(SYS_gettid)
#endif

#ifndef ENOTSUPP
#define ENOTSUPP 524
#endif

#endif /* __BPF_UTIL__ */
+0 −4
Original line number Diff line number Diff line
@@ -16,10 +16,6 @@
#include "tcp_ca_kfunc.skel.h"
#include "bpf_cc_cubic.skel.h"

#ifndef ENOTSUPP
#define ENOTSUPP 524
#endif

static const unsigned int total_bytes = 10 * 1024 * 1024;
static int expected_stg = 0xeB9F;

+0 −4
Original line number Diff line number Diff line
@@ -10,10 +10,6 @@
#include "cgroup_helpers.h"
#include "network_helpers.h"

#ifndef ENOTSUPP
#define ENOTSUPP 524
#endif

static struct btf *btf;

static __u32 query_prog_cnt(int cgroup_fd, const char *attach_func)
+131 −1
Original line number Diff line number Diff line
@@ -5,7 +5,9 @@
#include <sys/syscall.h>
#include <test_progs.h>
#include <bpf/btf.h>

#include "access_map_in_map.skel.h"
#include "update_map_in_htab.skel.h"

struct thread_ctx {
	pthread_barrier_t barrier;
@@ -127,6 +129,131 @@ static void test_map_in_map_access(const char *prog_name, const char *map_name)
	access_map_in_map__destroy(skel);
}

static void add_del_fd_htab(int outer_fd)
{
	int inner_fd, err;
	int key = 1;

	inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr1", 4, 4, 1, NULL);
	if (!ASSERT_OK_FD(inner_fd, "inner1"))
		return;
	err = bpf_map_update_elem(outer_fd, &key, &inner_fd, BPF_NOEXIST);
	close(inner_fd);
	if (!ASSERT_OK(err, "add"))
		return;

	/* Delete */
	err = bpf_map_delete_elem(outer_fd, &key);
	ASSERT_OK(err, "del");
}

static void overwrite_fd_htab(int outer_fd)
{
	int inner_fd, err;
	int key = 1;

	inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr1", 4, 4, 1, NULL);
	if (!ASSERT_OK_FD(inner_fd, "inner1"))
		return;
	err = bpf_map_update_elem(outer_fd, &key, &inner_fd, BPF_NOEXIST);
	close(inner_fd);
	if (!ASSERT_OK(err, "add"))
		return;

	/* Overwrite */
	inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr2", 4, 4, 1, NULL);
	if (!ASSERT_OK_FD(inner_fd, "inner2"))
		goto out;
	err = bpf_map_update_elem(outer_fd, &key, &inner_fd, BPF_EXIST);
	close(inner_fd);
	if (!ASSERT_OK(err, "overwrite"))
		goto out;

	err = bpf_map_delete_elem(outer_fd, &key);
	ASSERT_OK(err, "del");
	return;
out:
	bpf_map_delete_elem(outer_fd, &key);
}

static void lookup_delete_fd_htab(int outer_fd)
{
	int key = 1, value;
	int inner_fd, err;

	inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr1", 4, 4, 1, NULL);
	if (!ASSERT_OK_FD(inner_fd, "inner1"))
		return;
	err = bpf_map_update_elem(outer_fd, &key, &inner_fd, BPF_NOEXIST);
	close(inner_fd);
	if (!ASSERT_OK(err, "add"))
		return;

	/* lookup_and_delete is not supported for htab of maps */
	err = bpf_map_lookup_and_delete_elem(outer_fd, &key, &value);
	ASSERT_EQ(err, -ENOTSUPP, "lookup_del");

	err = bpf_map_delete_elem(outer_fd, &key);
	ASSERT_OK(err, "del");
}

static void batched_lookup_delete_fd_htab(int outer_fd)
{
	int keys[2] = {1, 2}, values[2];
	unsigned int cnt, batch;
	int inner_fd, err;

	inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr1", 4, 4, 1, NULL);
	if (!ASSERT_OK_FD(inner_fd, "inner1"))
		return;

	err = bpf_map_update_elem(outer_fd, &keys[0], &inner_fd, BPF_NOEXIST);
	close(inner_fd);
	if (!ASSERT_OK(err, "add1"))
		return;

	inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr2", 4, 4, 1, NULL);
	if (!ASSERT_OK_FD(inner_fd, "inner2"))
		goto out;
	err = bpf_map_update_elem(outer_fd, &keys[1], &inner_fd, BPF_NOEXIST);
	close(inner_fd);
	if (!ASSERT_OK(err, "add2"))
		goto out;

	/* batched lookup_and_delete */
	cnt = ARRAY_SIZE(keys);
	err = bpf_map_lookup_and_delete_batch(outer_fd, NULL, &batch, keys, values, &cnt, NULL);
	ASSERT_TRUE((!err || err == -ENOENT), "delete_batch ret");
	ASSERT_EQ(cnt, ARRAY_SIZE(keys), "delete_batch cnt");

out:
	bpf_map_delete_elem(outer_fd, &keys[0]);
}

static void test_update_map_in_htab(bool preallocate)
{
	struct update_map_in_htab *skel;
	int err, fd;

	skel = update_map_in_htab__open();
	if (!ASSERT_OK_PTR(skel, "open"))
		return;

	err = update_map_in_htab__load(skel);
	if (!ASSERT_OK(err, "load"))
		goto out;

	fd = preallocate ? bpf_map__fd(skel->maps.outer_htab_map) :
			   bpf_map__fd(skel->maps.outer_alloc_htab_map);

	add_del_fd_htab(fd);
	overwrite_fd_htab(fd);
	lookup_delete_fd_htab(fd);
	batched_lookup_delete_fd_htab(fd);
out:
	update_map_in_htab__destroy(skel);
}

void test_map_in_map(void)
{
	if (test__start_subtest("acc_map_in_array"))
@@ -137,5 +264,8 @@ void test_map_in_map(void)
		test_map_in_map_access("access_map_in_htab", "outer_htab_map");
	if (test__start_subtest("sleepable_acc_map_in_htab"))
		test_map_in_map_access("sleepable_access_map_in_htab", "outer_htab_map");
	if (test__start_subtest("update_map_in_htab"))
		test_update_map_in_htab(true);
	if (test__start_subtest("update_map_in_alloc_htab"))
		test_update_map_in_htab(false);
}
Loading