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