uboot/drivers/core/devres.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com>
   3 *
   4 * Based on the original work in Linux by
   5 * Copyright (c) 2006  SUSE Linux Products GmbH
   6 * Copyright (c) 2006  Tejun Heo <teheo@suse.de>
   7 *
   8 * SPDX-License-Identifier:     GPL-2.0+
   9 */
  10
  11#include <common.h>
  12#include <linux/compat.h>
  13#include <linux/kernel.h>
  14#include <linux/list.h>
  15#include <dm/device.h>
  16#include <dm/root.h>
  17#include <dm/util.h>
  18
  19/**
  20 * struct devres - Bookkeeping info for managed device resource
  21 * @entry: List to associate this structure with a device
  22 * @release: Callback invoked when this resource is released
  23 * @probe: Flag to show when this resource was allocated
  24           (true = probe, false = bind)
  25 * @name: Name of release function
  26 * @size: Size of resource data
  27 * @data: Resource data
  28 */
  29struct devres {
  30        struct list_head                entry;
  31        dr_release_t                    release;
  32        bool                            probe;
  33#ifdef CONFIG_DEBUG_DEVRES
  34        const char                      *name;
  35        size_t                          size;
  36#endif
  37        unsigned long long              data[];
  38};
  39
  40#ifdef CONFIG_DEBUG_DEVRES
  41static void set_node_dbginfo(struct devres *dr, const char *name, size_t size)
  42{
  43        dr->name = name;
  44        dr->size = size;
  45}
  46
  47static void devres_log(struct udevice *dev, struct devres *dr,
  48                       const char *op)
  49{
  50        printf("%s: DEVRES %3s %p %s (%lu bytes)\n",
  51               dev->name, op, dr, dr->name, (unsigned long)dr->size);
  52}
  53#else /* CONFIG_DEBUG_DEVRES */
  54#define set_node_dbginfo(dr, n, s)      do {} while (0)
  55#define devres_log(dev, dr, op)         do {} while (0)
  56#endif
  57
  58#if CONFIG_DEBUG_DEVRES
  59void *__devres_alloc(dr_release_t release, size_t size, gfp_t gfp,
  60                     const char *name)
  61#else
  62void *_devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
  63#endif
  64{
  65        size_t tot_size = sizeof(struct devres) + size;
  66        struct devres *dr;
  67
  68        dr = kmalloc(tot_size, gfp);
  69        if (unlikely(!dr))
  70                return NULL;
  71
  72        INIT_LIST_HEAD(&dr->entry);
  73        dr->release = release;
  74        set_node_dbginfo(dr, name, size);
  75
  76        return dr->data;
  77}
  78
  79void devres_free(void *res)
  80{
  81        if (res) {
  82                struct devres *dr = container_of(res, struct devres, data);
  83
  84                BUG_ON(!list_empty(&dr->entry));
  85                kfree(dr);
  86        }
  87}
  88
  89void devres_add(struct udevice *dev, void *res)
  90{
  91        struct devres *dr = container_of(res, struct devres, data);
  92
  93        devres_log(dev, dr, "ADD");
  94        BUG_ON(!list_empty(&dr->entry));
  95        dr->probe = dev->flags & DM_FLAG_BOUND ? true : false;
  96        list_add_tail(&dr->entry, &dev->devres_head);
  97}
  98
  99void *devres_find(struct udevice *dev, dr_release_t release,
 100                  dr_match_t match, void *match_data)
 101{
 102        struct devres *dr;
 103
 104        list_for_each_entry_reverse(dr, &dev->devres_head, entry) {
 105                if (dr->release != release)
 106                        continue;
 107                if (match && !match(dev, dr->data, match_data))
 108                        continue;
 109                return dr->data;
 110        }
 111
 112        return NULL;
 113}
 114
 115void *devres_get(struct udevice *dev, void *new_res,
 116                 dr_match_t match, void *match_data)
 117{
 118        struct devres *new_dr = container_of(new_res, struct devres, data);
 119        void *res;
 120
 121        res = devres_find(dev, new_dr->release, match, match_data);
 122        if (!res) {
 123                devres_add(dev, new_res);
 124                res = new_res;
 125                new_res = NULL;
 126        }
 127        devres_free(new_res);
 128
 129        return res;
 130}
 131
 132void *devres_remove(struct udevice *dev, dr_release_t release,
 133                    dr_match_t match, void *match_data)
 134{
 135        void *res;
 136
 137        res = devres_find(dev, release, match, match_data);
 138        if (res) {
 139                struct devres *dr = container_of(res, struct devres, data);
 140
 141                list_del_init(&dr->entry);
 142                devres_log(dev, dr, "REM");
 143        }
 144
 145        return res;
 146}
 147
 148int devres_destroy(struct udevice *dev, dr_release_t release,
 149                   dr_match_t match, void *match_data)
 150{
 151        void *res;
 152
 153        res = devres_remove(dev, release, match, match_data);
 154        if (unlikely(!res))
 155                return -ENOENT;
 156
 157        devres_free(res);
 158        return 0;
 159}
 160
 161int devres_release(struct udevice *dev, dr_release_t release,
 162                   dr_match_t match, void *match_data)
 163{
 164        void *res;
 165
 166        res = devres_remove(dev, release, match, match_data);
 167        if (unlikely(!res))
 168                return -ENOENT;
 169
 170        (*release)(dev, res);
 171        devres_free(res);
 172        return 0;
 173}
 174
 175static void release_nodes(struct udevice *dev, struct list_head *head,
 176                          bool probe_only)
 177{
 178        struct devres *dr, *tmp;
 179
 180        list_for_each_entry_safe_reverse(dr, tmp, head, entry)  {
 181                if (probe_only && !dr->probe)
 182                        break;
 183                devres_log(dev, dr, "REL");
 184                dr->release(dev, dr->data);
 185                list_del(&dr->entry);
 186                kfree(dr);
 187        }
 188}
 189
 190void devres_release_probe(struct udevice *dev)
 191{
 192        release_nodes(dev, &dev->devres_head, true);
 193}
 194
 195void devres_release_all(struct udevice *dev)
 196{
 197        release_nodes(dev, &dev->devres_head, false);
 198}
 199
 200#ifdef CONFIG_DEBUG_DEVRES
 201static void dump_resources(struct udevice *dev, int depth)
 202{
 203        struct devres *dr;
 204        struct udevice *child;
 205
 206        printf("- %s\n", dev->name);
 207
 208        list_for_each_entry(dr, &dev->devres_head, entry)
 209                printf("    %p (%lu byte) %s  %s\n", dr,
 210                       (unsigned long)dr->size, dr->name,
 211                       dr->probe ? "PROBE" : "BIND");
 212
 213        list_for_each_entry(child, &dev->child_head, sibling_node)
 214                dump_resources(child, depth + 1);
 215}
 216
 217void dm_dump_devres(void)
 218{
 219        struct udevice *root;
 220
 221        root = dm_root();
 222        if (root)
 223                dump_resources(root, 0);
 224}
 225#endif
 226
 227/*
 228 * Managed kmalloc/kfree
 229 */
 230static void devm_kmalloc_release(struct udevice *dev, void *res)
 231{
 232        /* noop */
 233}
 234
 235static int devm_kmalloc_match(struct udevice *dev, void *res, void *data)
 236{
 237        return res == data;
 238}
 239
 240void *devm_kmalloc(struct udevice *dev, size_t size, gfp_t gfp)
 241{
 242        void *data;
 243
 244        data = _devres_alloc(devm_kmalloc_release, size, gfp);
 245        if (unlikely(!data))
 246                return NULL;
 247
 248        devres_add(dev, data);
 249
 250        return data;
 251}
 252
 253void devm_kfree(struct udevice *dev, void *p)
 254{
 255        int rc;
 256
 257        rc = devres_destroy(dev, devm_kmalloc_release, devm_kmalloc_match, p);
 258        WARN_ON(rc);
 259}
 260