diff --git a/sys/compat/linuxkpi/common/include/linux/device.h b/sys/compat/linuxkpi/common/include/linux/device.h --- a/sys/compat/linuxkpi/common/include/linux/device.h +++ b/sys/compat/linuxkpi/common/include/linux/device.h @@ -562,5 +562,47 @@ char *kvasprintf(gfp_t, const char *, va_list); char *kasprintf(gfp_t, const char *, ...); +char *lkpi_devm_kasprintf(struct device *, gfp_t, const char *, ...); + +#define devm_kasprintf(_dev, _gfp, _fmt, ...) \ + lkpi_devm_kasprintf(_dev, _gfp, _fmt, ##__VA_ARGS__) + +void *lkpi_devres_alloc(void(*release)(struct device *, void *), size_t, gfp_t); +void lkpi_devres_add(struct device *, void *); +void lkpi_devres_free(void *); +void *lkpi_devres_find(struct device *, void(*release)(struct device *, void *), + int (*match)(struct device *, void *, void *), void *); +int lkpi_devres_destroy(struct device *, void(*release)(struct device *, void *), + int (*match)(struct device *, void *, void *), void *); +#define devres_alloc(_r, _s, _g) lkpi_devres_alloc(_r, _s, _g) +#define devres_add(_d, _p) lkpi_devres_add(_d, _p) +#define devres_free(_p) lkpi_devres_free(_p) +#define devres_find(_d, _rfn, _mfn, _mp) \ + lkpi_devres_find(_d, _rfn, _mfn, _mp) +#define devres_destroy(_d, _rfn, _mfn, _mp) \ + lkpi_devres_destroy(_d, _rfn, _mfn, _mp) + +/* LinuxKPI internal functions. */ +void lkpi_devres_release_free_list(struct device *); +void lkpi_devres_unlink(struct device *, void *); +void lkpi_devm_kmalloc_release(struct device *, void *); + +static __inline void * +devm_kmalloc(struct device *dev, size_t size, gfp_t gfp) +{ + void *p; + + p = lkpi_devres_alloc(lkpi_devm_kmalloc_release, size, gfp); + if (p != NULL) + lkpi_devres_add(dev, p); + + return (p); +} + +#define devm_kzalloc(_dev, _size, _gfp) \ + devm_kmalloc((_dev), (_size), (_gfp) | __GFP_ZERO) + +#define devm_kcalloc(_dev, _sizen, _size, _gfp) \ + devm_kmalloc((_dev), ((_sizen) * (_size)), (_gfp) | __GFP_ZERO) #endif /* _LINUX_DEVICE_H_ */ diff --git a/sys/compat/linuxkpi/common/src/linux_compat.c b/sys/compat/linuxkpi/common/src/linux_compat.c --- a/sys/compat/linuxkpi/common/src/linux_compat.c +++ b/sys/compat/linuxkpi/common/src/linux_compat.c @@ -1854,8 +1854,8 @@ kfree(vmmap); } -char * -kvasprintf(gfp_t gfp, const char *fmt, va_list ap) +static char * +devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt, va_list ap) { unsigned int len; char *p; @@ -1865,13 +1865,36 @@ len = vsnprintf(NULL, 0, fmt, aq); va_end(aq); - p = kmalloc(len + 1, gfp); + if (dev != NULL) + p = devm_kmalloc(dev, len + 1, gfp); + else + p = kmalloc(len + 1, gfp); if (p != NULL) vsnprintf(p, len + 1, fmt, ap); return (p); } +char * +kvasprintf(gfp_t gfp, const char *fmt, va_list ap) +{ + + return (devm_kvasprintf(NULL, gfp, fmt, ap)); +} + +char * +lkpi_devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...) +{ + va_list ap; + char *p; + + va_start(ap, fmt); + p = devm_kvasprintf(dev, gfp, fmt, ap); + va_end(ap); + + return (p); +} + char * kasprintf(gfp_t gfp, const char *fmt, ...) { diff --git a/sys/compat/linuxkpi/common/src/linux_devres.c b/sys/compat/linuxkpi/common/src/linux_devres.c new file mode 100644 --- /dev/null +++ b/sys/compat/linuxkpi/common/src/linux_devres.c @@ -0,0 +1,226 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#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. */ +} diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -4557,6 +4557,8 @@ compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_current.c optional compat_linuxkpi \ compile-with "${LINUXKPI_C}" +compat/linuxkpi/common/src/linux_devres.c optional compat_linuxkpi \ + compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_dmi.c optional compat_linuxkpi \ compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_firmware.c optional compat_linuxkpi \ diff --git a/sys/modules/linuxkpi/Makefile b/sys/modules/linuxkpi/Makefile --- a/sys/modules/linuxkpi/Makefile +++ b/sys/modules/linuxkpi/Makefile @@ -4,6 +4,7 @@ KMOD= linuxkpi SRCS= linux_compat.c \ linux_current.c \ + linux_devres.c \ linux_dmi.c \ linux_firmware.c \ linux_hrtimer.c \