LinuxKPI: implement devres() framework parts and two examples

This code implements a version of the devres framework found
working for various iwlwifi use cases and also providing functions
for ttm_page_alloc_dma.c from DRM.

Part of the framework replicates the consumed KPI, while others
are internal helper functions.

In addition the simple devm_k*malloc() consumers were implemented
and kvasprintf() was enhanced to also work for the devm_kasprintf()
case.
Addmittingly lkpi_devm_kmalloc_release() could be avoided but for
the overall understanding of the code and possible memory tracing
it may still be helpful.

Further devsres consumer are implemented for iwlwifi but will follow
later as the main reason for this change is to sort out overlap with
DRM.

Sponsored-by:	The FreeBSD Foundation
Obtained-from:	bz_iwlwifi
MFC After:	3 days
Reviewed-by:	hselasky, manu
Differential Revision:	https://reviews.freebsd.org/D28189
This commit is contained in:
Bjoern A. Zeeb 2021-01-28 16:32:43 +00:00
parent 1fac2cb4d6
commit fa765ca73e
5 changed files with 297 additions and 3 deletions

View File

@ -562,5 +562,47 @@ dev_to_node(struct device *dev)
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_ */

View File

@ -1854,8 +1854,8 @@ vunmap(void *addr)
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 @@ kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
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, ...)
{

View File

@ -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 <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. */
}

View File

@ -4557,6 +4557,8 @@ compat/linuxkpi/common/src/linux_compat.c optional compat_linuxkpi \
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 \

View File

@ -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 \