opnsense-src/sys/compat/linuxkpi/common/src/linux_devres.c
Emmanuel Vadot aaf6129c9d linuxkpi: Add devm_add_action and devm_add_action_or_reset
Those adds a new devres and will exectute some function on release.

Reviewed by:	bz
Differential Revision:	https://reviews.freebsd.org/D39142
Sponsored by:	Beckhoff Automation GmbH & Co. KG
2023-03-28 09:19:05 +02:00

269 lines
6.4 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2020-2021 The FreeBSD Foundation
*
* This software was developed by Bj\xc3\xb6rn Zeeb under sponsorship from
* the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/list.h>
/*
* Linux devres KPI implementation.
*/
struct devres {
struct list_head entry;
void (*release)(struct device *, void *);
/* Must come last. */
uint8_t __drdata[0] __aligned(CACHE_LINE_SIZE);
};
void *
lkpi_devres_alloc(void(*release)(struct device *, void *),
size_t size, gfp_t gfp)
{
void *p;
struct devres *dr;
size_t total;
if (size == 0)
return (NULL);
total = sizeof(*dr) + size;
dr = kmalloc(total, gfp);
if (dr == NULL)
return (NULL);
INIT_LIST_HEAD(&dr->entry);
dr->release = release;
p = (void *)(dr+1);
return (p);
}
static void
lkpi_devres_free_dr(struct devres *dr)
{
/*
* We have no dev, so cannot lock. This means someone else has
* to do this prior to us if devres_add() had been called.
*/
KASSERT(list_empty_careful(&dr->entry),
("%s: dr %p still on devres_head\n", __func__, dr));
kfree(dr);
}
void
lkpi_devres_free(void *p)
{
struct devres *dr;
if (p == NULL)
return;
dr = container_of(p, struct devres, __drdata);
lkpi_devres_free_dr(dr);
}
void
lkpi_devres_add(struct device *dev, void *p)
{
struct devres *dr;
KASSERT(dev != NULL && p != NULL, ("%s: dev %p p %p\n",
__func__, dev, p));
dr = container_of(p, struct devres, __drdata);
spin_lock(&dev->devres_lock);
list_add(&dr->entry, &dev->devres_head);
spin_unlock(&dev->devres_lock);
}
static struct devres *
lkpi_devres_find_dr(struct device *dev, void(*release)(struct device *, void *),
int (*match)(struct device *, void *, void *), void *mp)
{
struct devres *dr, *next;
void *p;
KASSERT(dev != NULL, ("%s: dev %p\n", __func__, dev));
assert_spin_locked(&dev->devres_lock);
list_for_each_entry_safe(dr, next, &dev->devres_head, entry) {
if (dr->release != release)
continue;
p = (void *)(dr+1);
if (match != NULL && match(dev, p, mp) == false)
continue;
return (dr);
}
return (NULL);
}
void *
lkpi_devres_find(struct device *dev, void(*release)(struct device *, void *),
int (*match)(struct device *, void *, void *), void *mp)
{
struct devres *dr;
KASSERT(dev != NULL, ("%s: dev %p\n", __func__, dev));
spin_lock(&dev->devres_lock);
dr = lkpi_devres_find_dr(dev, release, match, mp);
spin_unlock(&dev->devres_lock);
if (dr == NULL)
return (NULL);
return ((void *)(dr + 1));
}
static void
lkpi_devres_unlink_locked(struct device *dev, struct devres *dr)
{
KASSERT(dev != NULL, ("%s: dev %p\n", __func__, dev));
KASSERT(dr != NULL, ("%s: dr %p\n", __func__, dr));
assert_spin_locked(&dev->devres_lock);
list_del_init(&dr->entry);
}
void
lkpi_devres_unlink(struct device *dev, void *p)
{
struct devres *dr;
KASSERT(dev != NULL && p != NULL, ("%s: dev %p p %p\n",
__func__, dev, p));
dr = container_of(p, struct devres, __drdata);
spin_lock(&dev->devres_lock);
lkpi_devres_unlink_locked(dev, dr);
spin_unlock(&dev->devres_lock);
}
/* This is called on device free. */
void
lkpi_devres_release_free_list(struct device *dev)
{
struct devres *dr, *next;
void *p;
/* Free any resources allocated on the device. */
/* No need to lock anymore. */
list_for_each_entry_safe(dr, next, &dev->devres_head, entry) {
p = (void *)(dr+1);
if (dr->release != NULL)
dr->release(dev, p);
/* This should probably be a function of some kind. */
list_del_init(&dr->entry);
lkpi_devres_free(p);
}
}
int
lkpi_devres_destroy(struct device *dev, void(*release)(struct device *, void *),
int (*match)(struct device *, void *, void *), void *mp)
{
struct devres *dr;
spin_lock(&dev->devres_lock);
dr = lkpi_devres_find_dr(dev, release, match, mp);
if (dr != NULL)
lkpi_devres_unlink_locked(dev, dr);
spin_unlock(&dev->devres_lock);
if (dr == NULL)
return (-ENOENT);
lkpi_devres_free_dr(dr);
return (0);
}
/*
* Devres release function for k*malloc().
* While there is nothing to do here adding, e.g., tracing would be
* possible so we leave the empty function here.
* Also good for documentation as it is the simplest example.
*/
void
lkpi_devm_kmalloc_release(struct device *dev __unused, void *p __unused)
{
/* Nothing to do. Freed with the devres. */
}
struct devres_action {
void *data;
void (*action)(void *);
};
static void
lkpi_devm_action_release(struct device *dev, void *res)
{
struct devres_action *devres;
devres = (struct devres_action *)res;
devres->action(devres->data);
}
int
lkpi_devm_add_action(struct device *dev, void (*action)(void *), void *data)
{
struct devres_action *devres;
KASSERT(action != NULL, ("%s: action is NULL\n", __func__));
devres = lkpi_devres_alloc(lkpi_devm_action_release,
sizeof(struct devres_action), GFP_KERNEL);
if (devres == NULL)
return (-ENOMEM);
devres->data = data;
devres->action = action;
devres_add(dev, devres);
return (0);
}
int
lkpi_devm_add_action_or_reset(struct device *dev, void (*action)(void *), void *data)
{
int rv;
rv = lkpi_devm_add_action(dev, action, data);
if (rv != 0)
action(data);
return (rv);
}