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>
This commit is contained in:
Lukas Wunner 2020-12-06 13:46:00 +01:00 committed by Greg Kroah-Hartman
parent 2b8d8c968b
commit 0870525cf9
2 changed files with 55 additions and 1 deletions

View File

@ -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);

View File

@ -601,6 +601,8 @@ extern void spi_finalize_current_transfer(struct spi_master *master);
/* the spi driver core manages memory for the spi_master classdev */
extern struct spi_master *
spi_alloc_master(struct device *host, unsigned size);
extern struct spi_master *
devm_spi_alloc_master(struct device *dev, unsigned int size);
extern int spi_register_master(struct spi_master *master);
extern int devm_spi_register_master(struct device *dev,