diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 463ededcdcfe..f36c41224bcc 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -770,22 +771,75 @@ void qdisc_class_hash_remove(struct Qdisc_class_hash *clhash, } EXPORT_SYMBOL(qdisc_class_hash_remove); -/* Allocate an unique handle from space managed by kernel - * Possible range is [8000-FFFF]:0000 (0x8000 values) - */ -static u32 qdisc_alloc_handle(struct net_device *dev) -{ - int i = 0x8000; - static u32 autohandle = TC_H_MAKE(0x80000000U, 0); +#define QDISC_AUTO_HANDLE_BASE 0x8000 /* first auto-handle major */ +#define QDISC_AUTO_HANDLE_END 0xFFFE /* last auto-handle major */ +#define QDISC_AUTO_HANDLE_COUNT (QDISC_AUTO_HANDLE_END - \ + QDISC_AUTO_HANDLE_BASE + 1) - do { - autohandle += TC_H_MAKE(0x10000U, 0); - if (autohandle == TC_H_MAKE(TC_H_ROOT, 0)) - autohandle = TC_H_MAKE(0x80000000U, 0); - if (!qdisc_lookup(dev, autohandle)) - return autohandle; - cond_resched(); - } while (--i > 0); +/* Allocate a unique handle from space managed by kernel + * Possible range is [8000-FFFE]:0000 (0x7FFF values); 0xFFFF is the + * TC_H_ROOT/ingress major. + */ +static int qdisc_alloc_handle(struct net_device *dev, u32 *handlep) +{ + static u32 autohandle = TC_H_MAKE(0x80000000U, 0); + unsigned long start, bit; + unsigned long *bitmap; + struct Qdisc *q; + u32 handle, maj; + int b; + + start = ((autohandle >> 16) - QDISC_AUTO_HANDLE_BASE + 1) % + QDISC_AUTO_HANDLE_COUNT; + + /* The cursor moves past every handle it hands out, so the next + * handle is normally free: try it with one hashed lookup before + * walking the whole hash. + */ + handle = (start + QDISC_AUTO_HANDLE_BASE) << 16; + if (!qdisc_lookup(dev, handle)) { + autohandle = handle; + *handlep = handle; + return 0; + } + + bitmap = bitmap_zalloc(QDISC_AUTO_HANDLE_COUNT, GFP_KERNEL); + if (!bitmap) + return -ENOMEM; + + /* Mark the occupied auto-handle majors: the hash holds all + * qdiscs except root and ingress ones, and the root can carry + * an auto-range handle. + */ + q = rtnl_dereference(dev->qdisc); + maj = TC_H_MAJ(q->handle) >> 16; + if (!(q->flags & TCQ_F_BUILTIN) && !TC_H_MIN(q->handle) && + maj >= QDISC_AUTO_HANDLE_BASE && maj <= QDISC_AUTO_HANDLE_END) + set_bit(maj - QDISC_AUTO_HANDLE_BASE, bitmap); + + hash_for_each(dev->qdisc_hash, b, q, hash) { + maj = TC_H_MAJ(q->handle) >> 16; + if (maj >= QDISC_AUTO_HANDLE_BASE && + maj <= QDISC_AUTO_HANDLE_END && !TC_H_MIN(q->handle)) + set_bit(maj - QDISC_AUTO_HANDLE_BASE, bitmap); + } + + bit = find_next_zero_bit(bitmap, QDISC_AUTO_HANDLE_COUNT, start); + if (bit >= QDISC_AUTO_HANDLE_COUNT) { + bit = find_first_zero_bit(bitmap, start); + if (bit >= start) + bit = QDISC_AUTO_HANDLE_COUNT; + } + + if (bit >= QDISC_AUTO_HANDLE_COUNT) { + bitmap_free(bitmap); + return -ENOSPC; + } + + handle = (bit + QDISC_AUTO_HANDLE_BASE) << 16; + autohandle = handle; + bitmap_free(bitmap); + *handlep = handle; return 0; } @@ -1308,10 +1362,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev, handle = TC_H_MAKE(TC_H_INGRESS, 0); } else { if (handle == 0) { - handle = qdisc_alloc_handle(dev); - if (handle == 0) { - NL_SET_ERR_MSG(extack, "Maximum number of qdisc handles was exceeded"); - err = -ENOSPC; + err = qdisc_alloc_handle(dev, &handle); + if (err) { + NL_SET_ERR_MSG(extack, + "Failed to allocate a qdisc handle"); goto err_out3; } }