Blame view

drivers/acpi/acpi_pad.c 13.4 KB
8e0af5141   Shaohua Li   ACPI: create Proc...
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
  /*
   * acpi_pad.c ACPI Processor Aggregator Driver
   *
   * Copyright (c) 2009, Intel Corporation.
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms and conditions of the GNU General Public License,
   * version 2, as published by the Free Software Foundation.
   *
   * This program is distributed in the hope it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  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.,
   * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
   *
   */
  
  #include <linux/kernel.h>
  #include <linux/cpumask.h>
  #include <linux/module.h>
  #include <linux/init.h>
  #include <linux/types.h>
  #include <linux/kthread.h>
  #include <linux/freezer.h>
  #include <linux/cpu.h>
  #include <linux/clockchips.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
30
  #include <linux/slab.h>
8e0af5141   Shaohua Li   ACPI: create Proc...
31
32
  #include <acpi/acpi_bus.h>
  #include <acpi/acpi_drivers.h>
bc83cccc7   H. Peter Anvin   x86, mwait: Move ...
33
  #include <asm/mwait.h>
8e0af5141   Shaohua Li   ACPI: create Proc...
34

a40770a95   Dan Carpenter   acpi_pad: "proces...
35
  #define ACPI_PROCESSOR_AGGREGATOR_CLASS	"acpi_pad"
8e0af5141   Shaohua Li   ACPI: create Proc...
36
37
38
  #define ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME "Processor Aggregator"
  #define ACPI_PROCESSOR_AGGREGATOR_NOTIFY 0x80
  static DEFINE_MUTEX(isolated_cpus_lock);
8e0af5141   Shaohua Li   ACPI: create Proc...
39
  static unsigned long power_saving_mwait_eax;
0dc698b93   Venkatesh Pallipadi   ACPI: Don't let a...
40
41
42
  
  static unsigned char tsc_detected_unstable;
  static unsigned char tsc_marked_unstable;
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
43
44
  static unsigned char lapic_detected_unstable;
  static unsigned char lapic_marked_unstable;
0dc698b93   Venkatesh Pallipadi   ACPI: Don't let a...
45

8e0af5141   Shaohua Li   ACPI: create Proc...
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
  static void power_saving_mwait_init(void)
  {
  	unsigned int eax, ebx, ecx, edx;
  	unsigned int highest_cstate = 0;
  	unsigned int highest_subcstate = 0;
  	int i;
  
  	if (!boot_cpu_has(X86_FEATURE_MWAIT))
  		return;
  	if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
  		return;
  
  	cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx);
  
  	if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) ||
  	    !(ecx & CPUID5_ECX_INTERRUPT_BREAK))
  		return;
  
  	edx >>= MWAIT_SUBSTATE_SIZE;
  	for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
  		if (edx & MWAIT_SUBSTATE_MASK) {
  			highest_cstate = i;
  			highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
  		}
  	}
  	power_saving_mwait_eax = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
  		(highest_subcstate - 1);
592913ecb   John Stultz   time: Kill off CO...
73
  #if defined(CONFIG_X86)
8e0af5141   Shaohua Li   ACPI: create Proc...
74
75
76
77
78
79
80
  	switch (boot_cpu_data.x86_vendor) {
  	case X86_VENDOR_AMD:
  	case X86_VENDOR_INTEL:
  		/*
  		 * AMD Fam10h TSC will tick in all
  		 * C/P/S0/S1 states when this bit is set.
  		 */
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
81
82
83
84
85
  		if (!boot_cpu_has(X86_FEATURE_NONSTOP_TSC))
  			tsc_detected_unstable = 1;
  		if (!boot_cpu_has(X86_FEATURE_ARAT))
  			lapic_detected_unstable = 1;
  		break;
8e0af5141   Shaohua Li   ACPI: create Proc...
86
  	default:
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
87
  		/* TSC & LAPIC could halt in idle */
0dc698b93   Venkatesh Pallipadi   ACPI: Don't let a...
88
  		tsc_detected_unstable = 1;
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
89
  		lapic_detected_unstable = 1;
8e0af5141   Shaohua Li   ACPI: create Proc...
90
91
92
93
94
95
96
97
98
99
100
101
  	}
  #endif
  }
  
  static unsigned long cpu_weight[NR_CPUS];
  static int tsk_in_cpu[NR_CPUS] = {[0 ... NR_CPUS-1] = -1};
  static DECLARE_BITMAP(pad_busy_cpus_bits, NR_CPUS);
  static void round_robin_cpu(unsigned int tsk_index)
  {
  	struct cpumask *pad_busy_cpus = to_cpumask(pad_busy_cpus_bits);
  	cpumask_var_t tmp;
  	int cpu;
f67538f81   Andrew Morton   acpi_pad: squish ...
102
103
  	unsigned long min_weight = -1;
  	unsigned long uninitialized_var(preferred_cpu);
8e0af5141   Shaohua Li   ACPI: create Proc...
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
  
  	if (!alloc_cpumask_var(&tmp, GFP_KERNEL))
  		return;
  
  	mutex_lock(&isolated_cpus_lock);
  	cpumask_clear(tmp);
  	for_each_cpu(cpu, pad_busy_cpus)
  		cpumask_or(tmp, tmp, topology_thread_cpumask(cpu));
  	cpumask_andnot(tmp, cpu_online_mask, tmp);
  	/* avoid HT sibilings if possible */
  	if (cpumask_empty(tmp))
  		cpumask_andnot(tmp, cpu_online_mask, pad_busy_cpus);
  	if (cpumask_empty(tmp)) {
  		mutex_unlock(&isolated_cpus_lock);
  		return;
  	}
  	for_each_cpu(cpu, tmp) {
  		if (cpu_weight[cpu] < min_weight) {
  			min_weight = cpu_weight[cpu];
  			preferred_cpu = cpu;
  		}
  	}
  
  	if (tsk_in_cpu[tsk_index] != -1)
  		cpumask_clear_cpu(tsk_in_cpu[tsk_index], pad_busy_cpus);
  	tsk_in_cpu[tsk_index] = preferred_cpu;
  	cpumask_set_cpu(preferred_cpu, pad_busy_cpus);
  	cpu_weight[preferred_cpu]++;
  	mutex_unlock(&isolated_cpus_lock);
  
  	set_cpus_allowed_ptr(current, cpumask_of(preferred_cpu));
  }
  
  static void exit_round_robin(unsigned int tsk_index)
  {
  	struct cpumask *pad_busy_cpus = to_cpumask(pad_busy_cpus_bits);
  	cpumask_clear_cpu(tsk_in_cpu[tsk_index], pad_busy_cpus);
  	tsk_in_cpu[tsk_index] = -1;
  }
  
  static unsigned int idle_pct = 5; /* percentage */
  static unsigned int round_robin_time = 10; /* second */
  static int power_saving_thread(void *data)
  {
  	struct sched_param param = {.sched_priority = 1};
  	int do_sleep;
  	unsigned int tsk_index = (unsigned long)data;
  	u64 last_jiffies = 0;
  
  	sched_setscheduler(current, SCHED_RR, &param);
  
  	while (!kthread_should_stop()) {
  		int cpu;
  		u64 expire_time;
  
  		try_to_freeze();
  
  		/* round robin to cpus */
  		if (last_jiffies + round_robin_time * HZ < jiffies) {
  			last_jiffies = jiffies;
  			round_robin_cpu(tsk_index);
  		}
  
  		do_sleep = 0;
8e0af5141   Shaohua Li   ACPI: create Proc...
168
169
170
  		expire_time = jiffies + HZ * (100 - idle_pct) / 100;
  
  		while (!need_resched()) {
0dc698b93   Venkatesh Pallipadi   ACPI: Don't let a...
171
172
173
174
175
  			if (tsc_detected_unstable && !tsc_marked_unstable) {
  				/* TSC could halt in idle, so notify users */
  				mark_tsc_unstable("TSC halts in idle");
  				tsc_marked_unstable = 1;
  			}
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
176
177
178
179
180
181
182
183
184
  			if (lapic_detected_unstable && !lapic_marked_unstable) {
  				int i;
  				/* LAPIC could halt in idle, so notify users */
  				for_each_online_cpu(i)
  					clockevents_notify(
  						CLOCK_EVT_NOTIFY_BROADCAST_ON,
  						&i);
  				lapic_marked_unstable = 1;
  			}
8e0af5141   Shaohua Li   ACPI: create Proc...
185
186
  			local_irq_disable();
  			cpu = smp_processor_id();
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
187
188
189
  			if (lapic_marked_unstable)
  				clockevents_notify(
  					CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu);
8e0af5141   Shaohua Li   ACPI: create Proc...
190
191
192
193
194
195
196
197
  			stop_critical_timings();
  
  			__monitor((void *)&current_thread_info()->flags, 0, 0);
  			smp_mb();
  			if (!need_resched())
  				__mwait(power_saving_mwait_eax, 1);
  
  			start_critical_timings();
8aa4b14eb   Chen Gong   ACPI: acpi_pad: D...
198
199
200
  			if (lapic_marked_unstable)
  				clockevents_notify(
  					CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu);
8e0af5141   Shaohua Li   ACPI: create Proc...
201
202
203
204
205
206
207
  			local_irq_enable();
  
  			if (jiffies > expire_time) {
  				do_sleep = 1;
  				break;
  			}
  		}
8e0af5141   Shaohua Li   ACPI: create Proc...
208
209
210
211
212
213
214
  		/*
  		 * current sched_rt has threshold for rt task running time.
  		 * When a rt task uses 95% CPU time, the rt thread will be
  		 * scheduled out for 5% CPU time to not starve other tasks. But
  		 * the mechanism only works when all CPUs have RT task running,
  		 * as if one CPU hasn't RT task, RT task from other CPUs will
  		 * borrow CPU time from this CPU and cause RT task use > 95%
3b8cb427e   Chen Gong   acpi_pad: fix err...
215
  		 * CPU time. To make 'avoid starvation' work, takes a nap here.
8e0af5141   Shaohua Li   ACPI: create Proc...
216
217
218
219
220
221
222
223
224
225
226
227
228
  		 */
  		if (do_sleep)
  			schedule_timeout_killable(HZ * idle_pct / 100);
  	}
  
  	exit_round_robin(tsk_index);
  	return 0;
  }
  
  static struct task_struct *ps_tsks[NR_CPUS];
  static unsigned int ps_tsk_num;
  static int create_power_saving_task(void)
  {
3b8cb427e   Chen Gong   acpi_pad: fix err...
229
  	int rc = -ENOMEM;
8e0af5141   Shaohua Li   ACPI: create Proc...
230
231
232
  	ps_tsks[ps_tsk_num] = kthread_run(power_saving_thread,
  		(void *)(unsigned long)ps_tsk_num,
  		"power_saving/%d", ps_tsk_num);
3b8cb427e   Chen Gong   acpi_pad: fix err...
233
234
  	rc = IS_ERR(ps_tsks[ps_tsk_num]) ? PTR_ERR(ps_tsks[ps_tsk_num]) : 0;
  	if (!rc)
8e0af5141   Shaohua Li   ACPI: create Proc...
235
  		ps_tsk_num++;
3b8cb427e   Chen Gong   acpi_pad: fix err...
236
237
238
239
  	else
  		ps_tsks[ps_tsk_num] = NULL;
  
  	return rc;
8e0af5141   Shaohua Li   ACPI: create Proc...
240
241
242
243
244
245
246
  }
  
  static void destroy_power_saving_task(void)
  {
  	if (ps_tsk_num > 0) {
  		ps_tsk_num--;
  		kthread_stop(ps_tsks[ps_tsk_num]);
3b8cb427e   Chen Gong   acpi_pad: fix err...
247
  		ps_tsks[ps_tsk_num] = NULL;
8e0af5141   Shaohua Li   ACPI: create Proc...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
  	}
  }
  
  static void set_power_saving_task_num(unsigned int num)
  {
  	if (num > ps_tsk_num) {
  		while (ps_tsk_num < num) {
  			if (create_power_saving_task())
  				return;
  		}
  	} else if (num < ps_tsk_num) {
  		while (ps_tsk_num > num)
  			destroy_power_saving_task();
  	}
  }
3b8cb427e   Chen Gong   acpi_pad: fix err...
263
  static void acpi_pad_idle_cpus(unsigned int num_cpus)
8e0af5141   Shaohua Li   ACPI: create Proc...
264
265
266
267
268
269
270
  {
  	get_online_cpus();
  
  	num_cpus = min_t(unsigned int, num_cpus, num_online_cpus());
  	set_power_saving_task_num(num_cpus);
  
  	put_online_cpus();
8e0af5141   Shaohua Li   ACPI: create Proc...
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
  }
  
  static uint32_t acpi_pad_idle_cpus_num(void)
  {
  	return ps_tsk_num;
  }
  
  static ssize_t acpi_pad_rrtime_store(struct device *dev,
  	struct device_attribute *attr, const char *buf, size_t count)
  {
  	unsigned long num;
  	if (strict_strtoul(buf, 0, &num))
  		return -EINVAL;
  	if (num < 1 || num >= 100)
  		return -EINVAL;
  	mutex_lock(&isolated_cpus_lock);
  	round_robin_time = num;
  	mutex_unlock(&isolated_cpus_lock);
  	return count;
  }
  
  static ssize_t acpi_pad_rrtime_show(struct device *dev,
  	struct device_attribute *attr, char *buf)
  {
32297abd9   Naga Chumbalkar   ACPI: make acpi_p...
295
296
  	return scnprintf(buf, PAGE_SIZE, "%d
  ", round_robin_time);
8e0af5141   Shaohua Li   ACPI: create Proc...
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
  }
  static DEVICE_ATTR(rrtime, S_IRUGO|S_IWUSR,
  	acpi_pad_rrtime_show,
  	acpi_pad_rrtime_store);
  
  static ssize_t acpi_pad_idlepct_store(struct device *dev,
  	struct device_attribute *attr, const char *buf, size_t count)
  {
  	unsigned long num;
  	if (strict_strtoul(buf, 0, &num))
  		return -EINVAL;
  	if (num < 1 || num >= 100)
  		return -EINVAL;
  	mutex_lock(&isolated_cpus_lock);
  	idle_pct = num;
  	mutex_unlock(&isolated_cpus_lock);
  	return count;
  }
  
  static ssize_t acpi_pad_idlepct_show(struct device *dev,
  	struct device_attribute *attr, char *buf)
  {
32297abd9   Naga Chumbalkar   ACPI: make acpi_p...
319
320
  	return scnprintf(buf, PAGE_SIZE, "%d
  ", idle_pct);
8e0af5141   Shaohua Li   ACPI: create Proc...
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
  }
  static DEVICE_ATTR(idlepct, S_IRUGO|S_IWUSR,
  	acpi_pad_idlepct_show,
  	acpi_pad_idlepct_store);
  
  static ssize_t acpi_pad_idlecpus_store(struct device *dev,
  	struct device_attribute *attr, const char *buf, size_t count)
  {
  	unsigned long num;
  	if (strict_strtoul(buf, 0, &num))
  		return -EINVAL;
  	mutex_lock(&isolated_cpus_lock);
  	acpi_pad_idle_cpus(num);
  	mutex_unlock(&isolated_cpus_lock);
  	return count;
  }
  
  static ssize_t acpi_pad_idlecpus_show(struct device *dev,
  	struct device_attribute *attr, char *buf)
  {
32297abd9   Naga Chumbalkar   ACPI: make acpi_p...
341
342
343
344
345
346
  	int n = 0;
  	n = cpumask_scnprintf(buf, PAGE_SIZE-2, to_cpumask(pad_busy_cpus_bits));
  	buf[n++] = '
  ';
  	buf[n] = '\0';
  	return n;
8e0af5141   Shaohua Li   ACPI: create Proc...
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
  }
  static DEVICE_ATTR(idlecpus, S_IRUGO|S_IWUSR,
  	acpi_pad_idlecpus_show,
  	acpi_pad_idlecpus_store);
  
  static int acpi_pad_add_sysfs(struct acpi_device *device)
  {
  	int result;
  
  	result = device_create_file(&device->dev, &dev_attr_idlecpus);
  	if (result)
  		return -ENODEV;
  	result = device_create_file(&device->dev, &dev_attr_idlepct);
  	if (result) {
  		device_remove_file(&device->dev, &dev_attr_idlecpus);
  		return -ENODEV;
  	}
  	result = device_create_file(&device->dev, &dev_attr_rrtime);
  	if (result) {
  		device_remove_file(&device->dev, &dev_attr_idlecpus);
  		device_remove_file(&device->dev, &dev_attr_idlepct);
  		return -ENODEV;
  	}
  	return 0;
  }
  
  static void acpi_pad_remove_sysfs(struct acpi_device *device)
  {
  	device_remove_file(&device->dev, &dev_attr_idlecpus);
  	device_remove_file(&device->dev, &dev_attr_idlepct);
  	device_remove_file(&device->dev, &dev_attr_rrtime);
  }
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
379
380
381
382
383
  /*
   * Query firmware how many CPUs should be idle
   * return -1 on failure
   */
  static int acpi_pad_pur(acpi_handle handle)
8e0af5141   Shaohua Li   ACPI: create Proc...
384
385
  {
  	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
8e0af5141   Shaohua Li   ACPI: create Proc...
386
  	union acpi_object *package;
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
387
  	int num = -1;
8e0af5141   Shaohua Li   ACPI: create Proc...
388

3b8cb427e   Chen Gong   acpi_pad: fix err...
389
  	if (ACPI_FAILURE(acpi_evaluate_object(handle, "_PUR", NULL, &buffer)))
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
390
  		return num;
3b8cb427e   Chen Gong   acpi_pad: fix err...
391
392
  
  	if (!buffer.length || !buffer.pointer)
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
393
  		return num;
3b8cb427e   Chen Gong   acpi_pad: fix err...
394

8e0af5141   Shaohua Li   ACPI: create Proc...
395
  	package = buffer.pointer;
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
396
397
398
399
400
401
  
  	if (package->type == ACPI_TYPE_PACKAGE &&
  		package->package.count == 2 &&
  		package->package.elements[0].integer.value == 1) /* rev 1 */
  
  		num = package->package.elements[1].integer.value;
8e0af5141   Shaohua Li   ACPI: create Proc...
402
  	kfree(buffer.pointer);
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
403
  	return num;
8e0af5141   Shaohua Li   ACPI: create Proc...
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
  }
  
  /* Notify firmware how many CPUs are idle */
  static void acpi_pad_ost(acpi_handle handle, int stat,
  	uint32_t idle_cpus)
  {
  	union acpi_object params[3] = {
  		{.type = ACPI_TYPE_INTEGER,},
  		{.type = ACPI_TYPE_INTEGER,},
  		{.type = ACPI_TYPE_BUFFER,},
  	};
  	struct acpi_object_list arg_list = {3, params};
  
  	params[0].integer.value = ACPI_PROCESSOR_AGGREGATOR_NOTIFY;
  	params[1].integer.value =  stat;
  	params[2].buffer.length = 4;
  	params[2].buffer.pointer = (void *)&idle_cpus;
  	acpi_evaluate_object(handle, "_OST", &arg_list, NULL);
  }
  
  static void acpi_pad_handle_notify(acpi_handle handle)
  {
3b8cb427e   Chen Gong   acpi_pad: fix err...
426
  	int num_cpus;
8e0af5141   Shaohua Li   ACPI: create Proc...
427
428
429
  	uint32_t idle_cpus;
  
  	mutex_lock(&isolated_cpus_lock);
c9ad8e062   Len Brown   ACPI: acpi_pad: s...
430
431
  	num_cpus = acpi_pad_pur(handle);
  	if (num_cpus < 0) {
8e0af5141   Shaohua Li   ACPI: create Proc...
432
433
434
  		mutex_unlock(&isolated_cpus_lock);
  		return;
  	}
3b8cb427e   Chen Gong   acpi_pad: fix err...
435
  	acpi_pad_idle_cpus(num_cpus);
8e0af5141   Shaohua Li   ACPI: create Proc...
436
  	idle_cpus = acpi_pad_idle_cpus_num();
3b8cb427e   Chen Gong   acpi_pad: fix err...
437
  	acpi_pad_ost(handle, 0, idle_cpus);
8e0af5141   Shaohua Li   ACPI: create Proc...
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
  	mutex_unlock(&isolated_cpus_lock);
  }
  
  static void acpi_pad_notify(acpi_handle handle, u32 event,
  	void *data)
  {
  	struct acpi_device *device = data;
  
  	switch (event) {
  	case ACPI_PROCESSOR_AGGREGATOR_NOTIFY:
  		acpi_pad_handle_notify(handle);
  		acpi_bus_generate_proc_event(device, event, 0);
  		acpi_bus_generate_netlink_event(device->pnp.device_class,
  			dev_name(&device->dev), event, 0);
  		break;
  	default:
cdf2a4eed   Naga Chumbalkar   ACPI: minor print...
454
455
  		printk(KERN_WARNING "Unsupported event [0x%x]
  ", event);
8e0af5141   Shaohua Li   ACPI: create Proc...
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
  		break;
  	}
  }
  
  static int acpi_pad_add(struct acpi_device *device)
  {
  	acpi_status status;
  
  	strcpy(acpi_device_name(device), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME);
  	strcpy(acpi_device_class(device), ACPI_PROCESSOR_AGGREGATOR_CLASS);
  
  	if (acpi_pad_add_sysfs(device))
  		return -ENODEV;
  
  	status = acpi_install_notify_handler(device->handle,
  		ACPI_DEVICE_NOTIFY, acpi_pad_notify, device);
  	if (ACPI_FAILURE(status)) {
  		acpi_pad_remove_sysfs(device);
  		return -ENODEV;
  	}
  
  	return 0;
  }
  
  static int acpi_pad_remove(struct acpi_device *device,
  	int type)
  {
  	mutex_lock(&isolated_cpus_lock);
  	acpi_pad_idle_cpus(0);
  	mutex_unlock(&isolated_cpus_lock);
  
  	acpi_remove_notify_handler(device->handle,
  		ACPI_DEVICE_NOTIFY, acpi_pad_notify);
  	acpi_pad_remove_sysfs(device);
  	return 0;
  }
  
  static const struct acpi_device_id pad_device_ids[] = {
  	{"ACPI000C", 0},
  	{"", 0},
  };
  MODULE_DEVICE_TABLE(acpi, pad_device_ids);
  
  static struct acpi_driver acpi_pad_driver = {
  	.name = "processor_aggregator",
  	.class = ACPI_PROCESSOR_AGGREGATOR_CLASS,
  	.ids = pad_device_ids,
  	.ops = {
  		.add = acpi_pad_add,
  		.remove = acpi_pad_remove,
  	},
  };
  
  static int __init acpi_pad_init(void)
  {
  	power_saving_mwait_init();
  	if (power_saving_mwait_eax == 0)
  		return -EINVAL;
  
  	return acpi_bus_register_driver(&acpi_pad_driver);
  }
  
  static void __exit acpi_pad_exit(void)
  {
  	acpi_bus_unregister_driver(&acpi_pad_driver);
  }
  
  module_init(acpi_pad_init);
  module_exit(acpi_pad_exit);
  MODULE_AUTHOR("Shaohua Li<shaohua.li@intel.com>");
  MODULE_DESCRIPTION("ACPI Processor Aggregator Driver");
  MODULE_LICENSE("GPL");