/*- * 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 #include #include #include #include /* * 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); }