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