newbus: Limit units to [0, INT_MAX)

Limit the number of units a newbus device can have to be a positive
number. Reserve and reject the unit INT_MAX so that we can set maxunit
to INT_MAX without ill effect and so the normal signed int math
works. Add sanity checks to make sure we don't get negative unit numbers
from bus routines that can set the unit. Remove now-redundant check for
unit >=0 since it must be after an earlier check.

This should be largely a nop, since we'll likely run out of memory
before we have 2^31 devices. Also, finding unit number is O(n^2) since
we do linear searches for the next unit number, which even on very fast
machines will grind to a halt well before we reach this limit...

Add note to device_find_free_unit that says it can return INT_MAX when
all the unit numbers are in use. The one user in the tree
(ata_pci_attach) will then add a child with this unit and it will fail
and that failure will be handled properly. Hardware limitations, though
mean there will never be more than tens of units, let alone billions.

Update docs to document that EINVAL can be returned for bogus unit
numbers, or when we run out.

Sponsored-by:		Netflix
Reviewed-by:		jhb
Differential-Revision:	https://reviews.freebsd.org/D47359
Co-Authored-by: Elliott Mitchell <ehem+freebsd@m5p.com>
This commit is contained in:
Warner Losh 2024-10-31 16:50:54 -06:00
parent f3d3c63442
commit 055b41056e

View File

@ -1121,7 +1121,8 @@ devclass_get_maxunit(devclass_t dc)
* @brief Find a free unit number in a devclass
*
* This function searches for the first unused unit number greater
* that or equal to @p unit.
* that or equal to @p unit. Note: This can return INT_MAX which
* may be rejected elsewhere.
*
* @param dc the devclass to examine
* @param unit the first unit number to check
@ -1188,6 +1189,7 @@ devclass_get_sysctl_tree(devclass_t dc)
* @retval 0 success
* @retval EEXIST the requested unit number is already allocated
* @retval ENOMEM memory allocation failure
* @retval EINVAL unit is negative or we've run out of units
*/
static int
devclass_alloc_unit(devclass_t dc, device_t dev, int *unitp)
@ -1202,10 +1204,13 @@ devclass_alloc_unit(devclass_t dc, device_t dev, int *unitp)
BUS_HINT_DEVICE_UNIT(device_get_parent(dev), dev, dc->name,
&unit);
/* Unit numbers are either DEVICE_UNIT_ANY or in [0,INT_MAX) */
if ((unit < 0 && unit != DEVICE_UNIT_ANY) || unit == INT_MAX)
return (EINVAL);
/* If we were given a wired unit number, check for existing device */
if (unit != DEVICE_UNIT_ANY) {
if (unit >= 0 && unit < dc->maxunit &&
dc->devices[unit] != NULL) {
if (unit < dc->maxunit && dc->devices[unit] != NULL) {
if (bootverbose)
printf("%s: %s%d already exists; skipping it\n",
dc->name, dc->name, *unitp);
@ -1214,7 +1219,7 @@ devclass_alloc_unit(devclass_t dc, device_t dev, int *unitp)
} else {
/* Unwired device, find the next available slot for it */
unit = 0;
for (unit = 0;; unit++) {
for (unit = 0; unit < INT_MAX; unit++) {
/* If this device slot is already in use, skip it. */
if (unit < dc->maxunit && dc->devices[unit] != NULL)
continue;
@ -1228,6 +1233,15 @@ devclass_alloc_unit(devclass_t dc, device_t dev, int *unitp)
}
}
/*
* Unit numbers must be in the range [0, INT_MAX), so exclude INT_MAX as
* too large. We constrain maxunit below to be <= INT_MAX. This means we
* can treat unit and maxunit as normal integers with normal math
* everywhere and we only have to flag INT_MAX as invalid.
*/
if (unit == INT_MAX)
return (EINVAL);
/*
* We've selected a unit beyond the length of the table, so let's extend
* the table to make room for all units up to and including this one.
@ -1263,6 +1277,7 @@ devclass_alloc_unit(devclass_t dc, device_t dev, int *unitp)
* @retval 0 success
* @retval EEXIST the requested unit number is already allocated
* @retval ENOMEM memory allocation failure
* @retval EINVAL Unit number invalid or too many units
*/
static int
devclass_add_device(devclass_t dc, device_t dev)