aboutsummaryrefslogtreecommitdiff
path: root/sys/compat/linuxkpi/common/src/linux_devres.c
diff options
context:
space:
mode:
authorBjoern A. Zeeb <bz@FreeBSD.org>2021-01-28 16:32:43 +0000
committerBjoern A. Zeeb <bz@FreeBSD.org>2021-01-28 16:32:43 +0000
commitfa765ca73e553399ffbad382e579e4c2b4d0fc12 (patch)
tree065e49d3754eac5f8dc36812a39aba65075731fb /sys/compat/linuxkpi/common/src/linux_devres.c
parent1fac2cb4d6e5cfa1b8ff689213011b0fe077ffa7 (diff)
downloadsrc-fa765ca73e553399ffbad382e579e4c2b4d0fc12.tar.gz
src-fa765ca73e553399ffbad382e579e4c2b4d0fc12.zip
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
Diffstat (limited to 'sys/compat/linuxkpi/common/src/linux_devres.c')
-rw-r--r--sys/compat/linuxkpi/common/src/linux_devres.c226
1 files changed, 226 insertions, 0 deletions
diff --git a/sys/compat/linuxkpi/common/src/linux_devres.c b/sys/compat/linuxkpi/common/src/linux_devres.c
new file mode 100644
index 000000000000..96ff3e486d1d
--- /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 <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. */
+}