mirror of
https://github.com/opnsense/src.git
synced 2026-02-24 10:20:24 -05:00
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
269 lines
6.4 KiB
C
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);
|
|
}
|