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 */ 28 struct 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 40 static void set_node_dbginfo(struct devres *dr, const char *name, size_t size) 41 { 42 dr->name = name; 43 dr->size = size; 44 } 45 46 static 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 58 void *__devres_alloc(dr_release_t release, size_t size, gfp_t gfp, 59 const char *name) 60 #else 61 void *_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 78 void 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 88 void 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 98 void *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 114 void *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 131 void *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 147 int 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 160 int 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 174 static 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 189 void devres_release_probe(struct udevice *dev) 190 { 191 release_nodes(dev, &dev->devres_head, true); 192 } 193 194 void devres_release_all(struct udevice *dev) 195 { 196 release_nodes(dev, &dev->devres_head, false); 197 } 198 199 #ifdef CONFIG_DEBUG_DEVRES 200 static 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 216 void 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 */ 229 static void devm_kmalloc_release(struct udevice *dev, void *res) 230 { 231 /* noop */ 232 } 233 234 static int devm_kmalloc_match(struct udevice *dev, void *res, void *data) 235 { 236 return res == data; 237 } 238 239 void *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 252 void 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