#include <sys/cdefs.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/list.h>
struct devres {
struct list_head entry;
void (*release)(struct device *, void *);
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)
{
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);
}
void
lkpi_devres_release_free_list(struct device *dev)
{
struct devres *dr, *next;
void *p;
list_for_each_entry_safe(dr, next, &dev->devres_head, entry) {
p = (void *)(dr+1);
if (dr->release != NULL)
dr->release(dev, p);
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);
}
void
lkpi_devm_kmalloc_release(struct device *dev __unused, void *p __unused)
{
}
static int
lkpi_devm_kmalloc_match(struct device *dev __unused, void *p, void *mp)
{
return (p == mp);
}
void
lkpi_devm_kfree(struct device *dev, const void *p)
{
void *mp;
int error;
if (p == NULL)
return;
mp = __DECONST(void *, p);
error = lkpi_devres_destroy(dev, lkpi_devm_kmalloc_release,
lkpi_devm_kmalloc_match, mp);
if (error != 0)
dev_warn(dev, "%s: lkpi_devres_destroy failed with %d\n",
__func__, error);
}
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);
}