glue.c 8.6 KB
Newer Older
1 2 3 4 5 6 7 8
/*
 * Link physical devices with ACPI devices support
 *
 * Copyright (c) 2005 David Shaohua Li <shaohua.li@intel.com>
 * Copyright (c) 2005 Intel Corp.
 *
 * This file is released under the GPLv2.
 */
9
#include <linux/export.h>
10 11 12
#include <linux/init.h>
#include <linux/list.h>
#include <linux/device.h>
13
#include <linux/slab.h>
14 15 16
#include <linux/rwsem.h>
#include <linux/acpi.h>

17 18
#include "internal.h"

19 20
#define ACPI_GLUE_DEBUG	0
#if ACPI_GLUE_DEBUG
21 22
#define DBG(fmt, ...)						\
	printk(KERN_DEBUG PREFIX fmt, ##__VA_ARGS__)
23
#else
24 25 26 27 28
#define DBG(fmt, ...)						\
do {								\
	if (0)							\
		printk(KERN_DEBUG PREFIX fmt, ##__VA_ARGS__);	\
} while (0)
29 30 31 32
#endif
static LIST_HEAD(bus_type_list);
static DECLARE_RWSEM(bus_type_sem);

33
#define PHYSICAL_NODE_STRING "physical_node"
34
#define PHYSICAL_NODE_NAME_SIZE (sizeof(PHYSICAL_NODE_STRING) + 10)
35

36 37 38 39
int register_acpi_bus_type(struct acpi_bus_type *type)
{
	if (acpi_disabled)
		return -ENODEV;
40
	if (type && type->match && type->find_device) {
41 42 43
		down_write(&bus_type_sem);
		list_add_tail(&type->list, &bus_type_list);
		up_write(&bus_type_sem);
44
		printk(KERN_INFO PREFIX "bus type %s registered\n", type->name);
45 46 47 48
		return 0;
	}
	return -ENODEV;
}
49
EXPORT_SYMBOL_GPL(register_acpi_bus_type);
50 51 52 53 54 55 56 57 58

int unregister_acpi_bus_type(struct acpi_bus_type *type)
{
	if (acpi_disabled)
		return 0;
	if (type) {
		down_write(&bus_type_sem);
		list_del_init(&type->list);
		up_write(&bus_type_sem);
59 60
		printk(KERN_INFO PREFIX "bus type %s unregistered\n",
		       type->name);
61 62 63 64
		return 0;
	}
	return -ENODEV;
}
65
EXPORT_SYMBOL_GPL(unregister_acpi_bus_type);
66

67
static struct acpi_bus_type *acpi_get_bus_type(struct device *dev)
68 69 70 71 72
{
	struct acpi_bus_type *tmp, *ret = NULL;

	down_read(&bus_type_sem);
	list_for_each_entry(tmp, &bus_type_list, list) {
73
		if (tmp->match(dev)) {
74 75 76 77 78 79 80 81
			ret = tmp;
			break;
		}
	}
	up_read(&bus_type_sem);
	return ret;
}

82 83 84
#define FIND_CHILD_MIN_SCORE	1
#define FIND_CHILD_MAX_SCORE	2

85
static int find_child_checks(struct acpi_device *adev, bool check_children)
86
{
87
	bool sta_present = true;
88 89 90
	unsigned long long sta;
	acpi_status status;

91
	status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta);
92 93 94 95
	if (status == AE_NOT_FOUND)
		sta_present = false;
	else if (ACPI_FAILURE(status) || !(sta & ACPI_STA_DEVICE_ENABLED))
		return -ENODEV;
96

97 98
	if (check_children && list_empty(&adev->children))
		return -ENODEV;
99

100
	return sta_present ? FIND_CHILD_MAX_SCORE : FIND_CHILD_MIN_SCORE;
101 102
}

103 104
struct acpi_device *acpi_find_child_device(struct acpi_device *parent,
					   u64 address, bool check_children)
105
{
106 107 108
	struct acpi_device *adev, *ret = NULL;
	int ret_score = 0;

109 110 111
	if (!parent)
		return NULL;

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
	list_for_each_entry(adev, &parent->children, node) {
		unsigned long long addr;
		acpi_status status;
		int score;

		status = acpi_evaluate_integer(adev->handle, METHOD_NAME__ADR,
					       NULL, &addr);
		if (ACPI_FAILURE(status) || addr != address)
			continue;

		if (!ret) {
			/* This is the first matching object.  Save it. */
			ret = adev;
			continue;
		}
		/*
		 * There is more than one matching device object with the same
		 * _ADR value.  That really is unexpected, so we are kind of
		 * beyond the scope of the spec here.  We have to choose which
		 * one to return, though.
		 *
		 * First, check if the previously found object is good enough
		 * and return it if so.  Second, do the same for the object that
		 * we've just found.
		 */
		if (!ret_score) {
			ret_score = find_child_checks(ret, check_children);
			if (ret_score == FIND_CHILD_MAX_SCORE)
				return ret;
		}
		score = find_child_checks(adev, check_children);
		if (score == FIND_CHILD_MAX_SCORE) {
			return adev;
		} else if (score > ret_score) {
			ret = adev;
			ret_score = score;
		}
149
	}
150
	return ret;
151 152
}

153
acpi_handle acpi_get_child(acpi_handle handle, u64 addr)
154
{
155 156 157 158 159
	struct acpi_device *adev;

	if (!handle || acpi_bus_get_device(handle, &adev))
		return NULL;

160
	adev = acpi_find_child_device(adev, addr, false);
161
	return adev ? adev->handle : NULL;
162
}
163
EXPORT_SYMBOL_GPL(acpi_get_child);
164

165 166 167 168 169 170 171 172 173
static void acpi_physnode_link_name(char *buf, unsigned int node_id)
{
	if (node_id > 0)
		snprintf(buf, PHYSICAL_NODE_NAME_SIZE,
			 PHYSICAL_NODE_STRING "%u", node_id);
	else
		strcpy(buf, PHYSICAL_NODE_STRING);
}

174
int acpi_bind_one(struct device *dev, acpi_handle handle)
175
{
176
	struct acpi_device *acpi_dev = NULL;
177
	struct acpi_device_physical_node *physical_node, *pn;
178 179 180
	char physical_node_name[PHYSICAL_NODE_NAME_SIZE];
	struct list_head *physnode_list;
	unsigned int node_id;
181
	int retval = -EINVAL;
182

183
	if (ACPI_COMPANION(dev)) {
184
		if (handle) {
185
			dev_warn(dev, "ACPI companion already set\n");
186 187
			return -EINVAL;
		} else {
188
			acpi_dev = ACPI_COMPANION(dev);
189
		}
190 191
	} else {
		acpi_bus_get_device(handle, &acpi_dev);
192
	}
193
	if (!acpi_dev)
194
		return -EINVAL;
195

196
	get_device(&acpi_dev->dev);
197
	get_device(dev);
198
	physical_node = kzalloc(sizeof(*physical_node), GFP_KERNEL);
199 200 201
	if (!physical_node) {
		retval = -ENOMEM;
		goto err;
202 203
	}

204
	mutex_lock(&acpi_dev->physical_node_lock);
205

206 207 208 209 210 211 212 213
	/*
	 * Keep the list sorted by node_id so that the IDs of removed nodes can
	 * be recycled easily.
	 */
	physnode_list = &acpi_dev->physical_node_list;
	node_id = 0;
	list_for_each_entry(pn, &acpi_dev->physical_node_list, node) {
		/* Sanity check. */
214
		if (pn->dev == dev) {
215 216
			mutex_unlock(&acpi_dev->physical_node_lock);

217
			dev_warn(dev, "Already associated with ACPI node\n");
218
			kfree(physical_node);
219
			if (ACPI_COMPANION(dev) != acpi_dev)
220
				goto err;
221

222
			put_device(dev);
223
			put_device(&acpi_dev->dev);
224
			return 0;
225
		}
226 227 228 229
		if (pn->node_id == node_id) {
			physnode_list = &pn->node;
			node_id++;
		}
230 231
	}

232
	physical_node->node_id = node_id;
233
	physical_node->dev = dev;
234
	list_add(&physical_node->node, physnode_list);
235
	acpi_dev->physical_node_count++;
236

237 238
	if (!ACPI_COMPANION(dev))
		ACPI_COMPANION_SET(dev, acpi_dev);
239

240
	acpi_physnode_link_name(physical_node_name, node_id);
241
	retval = sysfs_create_link(&acpi_dev->dev.kobj, &dev->kobj,
242
				   physical_node_name);
243 244 245 246
	if (retval)
		dev_err(&acpi_dev->dev, "Failed to create link %s (%d)\n",
			physical_node_name, retval);

247
	retval = sysfs_create_link(&dev->kobj, &acpi_dev->dev.kobj,
248
				   "firmware_node");
249 250 251
	if (retval)
		dev_err(dev, "Failed to create link firmware_node (%d)\n",
			retval);
252

253 254
	mutex_unlock(&acpi_dev->physical_node_lock);

255 256 257
	if (acpi_dev->wakeup.flags.valid)
		device_set_wakeup_capable(dev, true);

258
	return 0;
259 260

 err:
261
	ACPI_COMPANION_SET(dev, NULL);
262
	put_device(dev);
263
	put_device(&acpi_dev->dev);
264
	return retval;
265
}
266
EXPORT_SYMBOL_GPL(acpi_bind_one);
267

268
int acpi_unbind_one(struct device *dev)
269
{
270
	struct acpi_device *acpi_dev = ACPI_COMPANION(dev);
271 272
	struct acpi_device_physical_node *entry;

273
	if (!acpi_dev)
274
		return 0;
275

276
	mutex_lock(&acpi_dev->physical_node_lock);
277 278 279 280 281 282 283 284 285 286 287

	list_for_each_entry(entry, &acpi_dev->physical_node_list, node)
		if (entry->dev == dev) {
			char physnode_name[PHYSICAL_NODE_NAME_SIZE];

			list_del(&entry->node);
			acpi_dev->physical_node_count--;

			acpi_physnode_link_name(physnode_name, entry->node_id);
			sysfs_remove_link(&acpi_dev->dev.kobj, physnode_name);
			sysfs_remove_link(&dev->kobj, "firmware_node");
288
			ACPI_COMPANION_SET(dev, NULL);
289
			/* Drop references taken by acpi_bind_one(). */
290
			put_device(dev);
291
			put_device(&acpi_dev->dev);
292 293 294 295
			kfree(entry);
			break;
		}

296
	mutex_unlock(&acpi_dev->physical_node_lock);
297 298
	return 0;
}
299
EXPORT_SYMBOL_GPL(acpi_unbind_one);
300

301 302 303 304 305 306 307 308 309
void acpi_preset_companion(struct device *dev, acpi_handle parent, u64 addr)
{
	struct acpi_device *adev;

	if (!acpi_bus_get_device(acpi_get_child(parent, addr), &adev))
		ACPI_COMPANION_SET(dev, adev);
}
EXPORT_SYMBOL_GPL(acpi_preset_companion);

310 311
static int acpi_platform_notify(struct device *dev)
{
312
	struct acpi_bus_type *type = acpi_get_bus_type(dev);
313
	acpi_handle handle;
314
	int ret;
315

316
	ret = acpi_bind_one(dev, NULL);
317
	if (ret && type) {
318 319 320 321 322 323 324 325
		ret = type->find_device(dev, &handle);
		if (ret) {
			DBG("Unable to get handle for %s\n", dev_name(dev));
			goto out;
		}
		ret = acpi_bind_one(dev, handle);
		if (ret)
			goto out;
326
	}
327 328 329

	if (type && type->setup)
		type->setup(dev);
330

331
 out:
332 333 334 335
#if ACPI_GLUE_DEBUG
	if (!ret) {
		struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };

336
		acpi_get_name(ACPI_HANDLE(dev), ACPI_FULL_PATHNAME, &buffer);
337
		DBG("Device %s -> %s\n", dev_name(dev), (char *)buffer.pointer);
338
		kfree(buffer.pointer);
339
	} else
340
		DBG("Device %s -> No ACPI support\n", dev_name(dev));
341 342 343 344 345 346 347
#endif

	return ret;
}

static int acpi_platform_notify_remove(struct device *dev)
{
348 349
	struct acpi_bus_type *type;

350
	type = acpi_get_bus_type(dev);
351 352 353
	if (type && type->cleanup)
		type->cleanup(dev);

354 355 356 357
	acpi_unbind_one(dev);
	return 0;
}

358
int __init init_acpi_device_notify(void)
359 360 361 362 363 364 365 366 367
{
	if (platform_notify || platform_notify_remove) {
		printk(KERN_ERR PREFIX "Can't use platform_notify\n");
		return 0;
	}
	platform_notify = acpi_platform_notify;
	platform_notify_remove = acpi_platform_notify_remove;
	return 0;
}