diff options
author | Lukas Wunner <lukas@wunner.de> | 2020-12-06 13:46:00 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-12-11 13:38:00 +0100 |
commit | 0870525cf94bc27907e94ce99afb6d7239ffd2f5 (patch) | |
tree | dabb7e45f95981a8508908a7236b4cea2015749a /drivers/spi | |
parent | 2b8d8c968b495c3a4cb1edb5a43a4bbf9ee9587f (diff) | |
download | linux-stable-0870525cf94bc27907e94ce99afb6d7239ffd2f5.tar.gz linux-stable-0870525cf94bc27907e94ce99afb6d7239ffd2f5.tar.bz2 linux-stable-0870525cf94bc27907e94ce99afb6d7239ffd2f5.zip |
spi: Introduce device-managed SPI controller allocation
[ Upstream commit 5e844cc37a5cbaa460e68f9a989d321d63088a89 ]
SPI driver probing currently comprises two steps, whereas removal
comprises only one step:
spi_alloc_master()
spi_register_master()
spi_unregister_master()
That's because spi_unregister_master() calls device_unregister()
instead of device_del(), thereby releasing the reference on the
spi_master which was obtained by spi_alloc_master().
An SPI driver's private data is contained in the same memory allocation
as the spi_master struct. Thus, once spi_unregister_master() has been
called, the private data is inaccessible. But some drivers need to
access it after spi_unregister_master() to perform further teardown
steps.
Introduce devm_spi_alloc_master(), which releases a reference on the
spi_master struct only after the driver has unbound, thereby keeping the
memory allocation accessible. Change spi_unregister_master() to not
release a reference if the spi_master was allocated by the new devm
function.
The present commit is small enough to be backportable to stable.
It allows fixing drivers which use the private data in their ->remove()
hook after it's been freed. It also allows fixing drivers which neglect
to release a reference on the spi_master in the probe error path.
Long-term, most SPI drivers shall be moved over to the devm function
introduced herein. The few that can't shall be changed in a treewide
commit to explicitly release the last reference on the master.
That commit shall amend spi_unregister_master() to no longer release
a reference, thereby completing the migration.
As a result, the behaviour will be less surprising and more consistent
with subsystems such as IIO, which also includes the private data in the
allocation of the generic iio_dev struct, but calls device_del() in
iio_device_unregister().
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Link: https://lore.kernel.org/r/272bae2ef08abd21388c98e23729886663d19192.1605121038.git.lukas@wunner.de
Signed-off-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/spi.c | 54 |
1 files changed, 53 insertions, 1 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 41dfb51bf1d2..e0632ee1723b 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1827,6 +1827,46 @@ struct spi_master *spi_alloc_master(struct device *dev, unsigned size) } EXPORT_SYMBOL_GPL(spi_alloc_master); +static void devm_spi_release_master(struct device *dev, void *master) +{ + spi_master_put(*(struct spi_master **)master); +} + +/** + * devm_spi_alloc_master - resource-managed spi_alloc_master() + * @dev: physical device of SPI master + * @size: how much zeroed driver-private data to allocate + * Context: can sleep + * + * Allocate an SPI master and automatically release a reference on it + * when @dev is unbound from its driver. Drivers are thus relieved from + * having to call spi_master_put(). + * + * The arguments to this function are identical to spi_alloc_master(). + * + * Return: the SPI master structure on success, else NULL. + */ +struct spi_master *devm_spi_alloc_master(struct device *dev, unsigned int size) +{ + struct spi_master **ptr, *master; + + ptr = devres_alloc(devm_spi_release_master, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return NULL; + + master = spi_alloc_master(dev, size); + if (master) { + *ptr = master; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return master; +} +EXPORT_SYMBOL_GPL(devm_spi_alloc_master); + #ifdef CONFIG_OF static int of_spi_register_master(struct spi_master *master) { @@ -2007,6 +2047,11 @@ int devm_spi_register_master(struct device *dev, struct spi_master *master) } EXPORT_SYMBOL_GPL(devm_spi_register_master); +static int devm_spi_match_master(struct device *dev, void *res, void *master) +{ + return *(struct spi_master **)res == master; +} + static int __unregister(struct device *dev, void *null) { spi_unregister_device(to_spi_device(dev)); @@ -2036,7 +2081,14 @@ void spi_unregister_master(struct spi_master *master) list_del(&master->list); mutex_unlock(&board_lock); - device_unregister(&master->dev); + device_del(&master->dev); + + /* Release the last reference on the master if its driver + * has not yet been converted to devm_spi_alloc_master(). + */ + if (!devres_find(master->dev.parent, devm_spi_release_master, + devm_spi_match_master, master)) + put_device(&master->dev); } EXPORT_SYMBOL_GPL(spi_unregister_master); |