aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/hotplug/pciehp_acpi.c
blob: 88a5c57f2e5b712ec0091986c27037f23fe3a3b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/*
 * ACPI related functions for PCI Express Hot Plug driver.
 *
 * Copyright (C) 2008 Kenji Kaneshige
 * Copyright (C) 2008 Fujitsu Limited.
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
 * NON INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/acpi.h>
#include "pciehp.h"

#define PCIEHP_DETECT_PCIE	(0)
#define PCIEHP_DETECT_ACPI	(1)
#define PCIEHP_DETECT_AUTO	(2)
#define PCIEHP_DETECT_DEFAULT	PCIEHP_DETECT_AUTO

static int slot_detection_mode;
static char *pciehp_detect_mode;
module_param(pciehp_detect_mode, charp, 0444);
MODULE_PARM_DESC(pciehp_detect_mode,
	 "Slot detection mode: pcie, acpi, auto\n"
	 "  pcie          - Use PCIe based slot detection\n"
	 "  acpi          - Use ACPI for slot detection\n"
	 "  auto(default) - Auto select mode. Use acpi option if duplicate\n"
	 "                  slot ids are found. Otherwise, use pcie option\n");

static int is_ejectable(acpi_handle handle)
{
	acpi_status status;
	acpi_handle tmp;
	unsigned long long removable;
	status = acpi_get_handle(handle, "_ADR", &tmp);
	if (ACPI_FAILURE(status))
		return 0;
	status = acpi_get_handle(handle, "_EJ0", &tmp);
	if (ACPI_SUCCESS(status))
		return 1;
	status = acpi_evaluate_integer(handle, "_RMV", NULL, &removable);
	if (ACPI_SUCCESS(status) && removable)
		return 1;
	return 0;
}

static acpi_status
check_hotplug(acpi_handle handle, u32 lvl, void *context, void **rv)
{
	int *found = (int *)context;
	if (is_ejectable(handle)) {
		*found = 1;
		return AE_CTRL_TERMINATE;
	}
	return AE_OK;
}

static int pciehp_detect_acpi_slot(struct pci_bus *pbus)
{
	acpi_handle handle;
	struct pci_dev *pdev = pbus->self;
	int found = 0;

	if (!pdev){
		int seg = pci_domain_nr(pbus), busnr = pbus->number;
		handle = acpi_get_pci_rootbridge_handle(seg, busnr);
	} else
		handle = DEVICE_ACPI_HANDLE(&(pdev->dev));

	if (!handle)
		return 0;

	acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1,
			    check_hotplug, (void *)&found, NULL);
	return found;
}

int pciehp_acpi_slot_detection_check(struct pci_dev *dev)
{
	if (slot_detection_mode != PCIEHP_DETECT_ACPI)
		return 0;
	if (pciehp_detect_acpi_slot(dev->subordinate))
		return 0;
	return -ENODEV;
}

static int __init parse_detect_mode(void)
{
	if (!pciehp_detect_mode)
		return PCIEHP_DETECT_DEFAULT;
	if (!strcmp(pciehp_detect_mode, "pcie"))
		return PCIEHP_DETECT_PCIE;
	if (!strcmp(pciehp_detect_mode, "acpi"))
		return PCIEHP_DETECT_ACPI;
	if (!strcmp(pciehp_detect_mode, "auto"))
		return PCIEHP_DETECT_AUTO;
	warn("bad specifier '%s' for pciehp_detect_mode. Use default\n",
	     pciehp_detect_mode);
	return PCIEHP_DETECT_DEFAULT;
}

static struct pcie_port_service_id __initdata port_pci_ids[] = {
	{
		.vendor = PCI_ANY_ID,
		.device = PCI_ANY_ID,
		.port_type = PCIE_ANY_PORT,
		.service_type = PCIE_PORT_SERVICE_HP,
		.driver_data =  0,
        }, { /* end: all zeroes */ }
};

static int __initdata dup_slot_id;
static int __initdata acpi_slot_detected;
static struct list_head __initdata dummy_slots = LIST_HEAD_INIT(dummy_slots);

/* Dummy driver for dumplicate name detection */
static int __init dummy_probe(struct pcie_device *dev,
			      const struct pcie_port_service_id *id)
{
	int pos;
	u32 slot_cap;
	struct slot *slot, *tmp;
	struct pci_dev *pdev = dev->port;
	if (!(slot = kzalloc(sizeof(*slot), GFP_KERNEL)))
		return -ENOMEM;
	/* Note: pciehp_detect_mode != PCIEHP_DETECT_ACPI here */
	if (pciehp_get_hp_hw_control_from_firmware(pdev))
		return -ENODEV;
	if (!(pos = pci_find_capability(pdev, PCI_CAP_ID_EXP)))
		return -ENODEV;
	pci_read_config_dword(pdev, pos + PCI_EXP_SLTCAP, &slot_cap);
	slot->number = slot_cap >> 19;
	list_for_each_entry(tmp, &dummy_slots, slot_list) {
		if (tmp->number == slot->number)
			dup_slot_id++;
	}
	list_add_tail(&slot->slot_list, &dummy_slots);
	if (!acpi_slot_detected && pciehp_detect_acpi_slot(pdev->subordinate))
		acpi_slot_detected = 1;
	return -ENODEV;         /* dummy driver always returns error */
}

static struct pcie_port_service_driver __initdata dummy_driver = {
        .name           = "pciehp_dummy",
        .id_table       = port_pci_ids,
        .probe          = dummy_probe,
};

static int __init select_detection_mode(void)
{
	struct slot *slot, *tmp;
	pcie_port_service_register(&dummy_driver);
	pcie_port_service_unregister(&dummy_driver);
	list_for_each_entry_safe(slot, tmp, &dummy_slots, slot_list) {
		list_del(&slot->slot_list);
		kfree(slot);
	}
	if (acpi_slot_detected && dup_slot_id)
		return PCIEHP_DETECT_ACPI;
	return PCIEHP_DETECT_PCIE;
}

void __init pciehp_acpi_slot_detection_init(void)
{
	slot_detection_mode = parse_detect_mode();
	if (slot_detection_mode != PCIEHP_DETECT_AUTO)
		goto out;
	slot_detection_mode = select_detection_mode();
out:
	if (slot_detection_mode == PCIEHP_DETECT_ACPI)
		info("Using ACPI for slot detection.\n");
}