Root/
1 | /* |
2 | * dcdbas.c: Dell Systems Management Base Driver |
3 | * |
4 | * The Dell Systems Management Base Driver provides a sysfs interface for |
5 | * systems management software to perform System Management Interrupts (SMIs) |
6 | * and Host Control Actions (power cycle or power off after OS shutdown) on |
7 | * Dell systems. |
8 | * |
9 | * See Documentation/dcdbas.txt for more information. |
10 | * |
11 | * Copyright (C) 1995-2006 Dell Inc. |
12 | * |
13 | * This program is free software; you can redistribute it and/or modify |
14 | * it under the terms of the GNU General Public License v2.0 as published by |
15 | * the Free Software Foundation. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | */ |
22 | |
23 | #include <linux/platform_device.h> |
24 | #include <linux/dma-mapping.h> |
25 | #include <linux/errno.h> |
26 | #include <linux/gfp.h> |
27 | #include <linux/init.h> |
28 | #include <linux/kernel.h> |
29 | #include <linux/mc146818rtc.h> |
30 | #include <linux/module.h> |
31 | #include <linux/reboot.h> |
32 | #include <linux/sched.h> |
33 | #include <linux/smp.h> |
34 | #include <linux/spinlock.h> |
35 | #include <linux/string.h> |
36 | #include <linux/types.h> |
37 | #include <linux/mutex.h> |
38 | #include <asm/io.h> |
39 | |
40 | #include "dcdbas.h" |
41 | |
42 | #define DRIVER_NAME "dcdbas" |
43 | #define DRIVER_VERSION "5.6.0-3.2" |
44 | #define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" |
45 | |
46 | static struct platform_device *dcdbas_pdev; |
47 | |
48 | static u8 *smi_data_buf; |
49 | static dma_addr_t smi_data_buf_handle; |
50 | static unsigned long smi_data_buf_size; |
51 | static u32 smi_data_buf_phys_addr; |
52 | static DEFINE_MUTEX(smi_data_lock); |
53 | |
54 | static unsigned int host_control_action; |
55 | static unsigned int host_control_smi_type; |
56 | static unsigned int host_control_on_shutdown; |
57 | |
58 | /** |
59 | * smi_data_buf_free: free SMI data buffer |
60 | */ |
61 | static void smi_data_buf_free(void) |
62 | { |
63 | if (!smi_data_buf) |
64 | return; |
65 | |
66 | dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", |
67 | __func__, smi_data_buf_phys_addr, smi_data_buf_size); |
68 | |
69 | dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf, |
70 | smi_data_buf_handle); |
71 | smi_data_buf = NULL; |
72 | smi_data_buf_handle = 0; |
73 | smi_data_buf_phys_addr = 0; |
74 | smi_data_buf_size = 0; |
75 | } |
76 | |
77 | /** |
78 | * smi_data_buf_realloc: grow SMI data buffer if needed |
79 | */ |
80 | static int smi_data_buf_realloc(unsigned long size) |
81 | { |
82 | void *buf; |
83 | dma_addr_t handle; |
84 | |
85 | if (smi_data_buf_size >= size) |
86 | return 0; |
87 | |
88 | if (size > MAX_SMI_DATA_BUF_SIZE) |
89 | return -EINVAL; |
90 | |
91 | /* new buffer is needed */ |
92 | buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL); |
93 | if (!buf) { |
94 | dev_dbg(&dcdbas_pdev->dev, |
95 | "%s: failed to allocate memory size %lu\n", |
96 | __func__, size); |
97 | return -ENOMEM; |
98 | } |
99 | /* memory zeroed by dma_alloc_coherent */ |
100 | |
101 | if (smi_data_buf) |
102 | memcpy(buf, smi_data_buf, smi_data_buf_size); |
103 | |
104 | /* free any existing buffer */ |
105 | smi_data_buf_free(); |
106 | |
107 | /* set up new buffer for use */ |
108 | smi_data_buf = buf; |
109 | smi_data_buf_handle = handle; |
110 | smi_data_buf_phys_addr = (u32) virt_to_phys(buf); |
111 | smi_data_buf_size = size; |
112 | |
113 | dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", |
114 | __func__, smi_data_buf_phys_addr, smi_data_buf_size); |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | static ssize_t smi_data_buf_phys_addr_show(struct device *dev, |
120 | struct device_attribute *attr, |
121 | char *buf) |
122 | { |
123 | return sprintf(buf, "%x\n", smi_data_buf_phys_addr); |
124 | } |
125 | |
126 | static ssize_t smi_data_buf_size_show(struct device *dev, |
127 | struct device_attribute *attr, |
128 | char *buf) |
129 | { |
130 | return sprintf(buf, "%lu\n", smi_data_buf_size); |
131 | } |
132 | |
133 | static ssize_t smi_data_buf_size_store(struct device *dev, |
134 | struct device_attribute *attr, |
135 | const char *buf, size_t count) |
136 | { |
137 | unsigned long buf_size; |
138 | ssize_t ret; |
139 | |
140 | buf_size = simple_strtoul(buf, NULL, 10); |
141 | |
142 | /* make sure SMI data buffer is at least buf_size */ |
143 | mutex_lock(&smi_data_lock); |
144 | ret = smi_data_buf_realloc(buf_size); |
145 | mutex_unlock(&smi_data_lock); |
146 | if (ret) |
147 | return ret; |
148 | |
149 | return count; |
150 | } |
151 | |
152 | static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, |
153 | struct bin_attribute *bin_attr, |
154 | char *buf, loff_t pos, size_t count) |
155 | { |
156 | ssize_t ret; |
157 | |
158 | mutex_lock(&smi_data_lock); |
159 | ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf, |
160 | smi_data_buf_size); |
161 | mutex_unlock(&smi_data_lock); |
162 | return ret; |
163 | } |
164 | |
165 | static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, |
166 | struct bin_attribute *bin_attr, |
167 | char *buf, loff_t pos, size_t count) |
168 | { |
169 | ssize_t ret; |
170 | |
171 | if ((pos + count) > MAX_SMI_DATA_BUF_SIZE) |
172 | return -EINVAL; |
173 | |
174 | mutex_lock(&smi_data_lock); |
175 | |
176 | ret = smi_data_buf_realloc(pos + count); |
177 | if (ret) |
178 | goto out; |
179 | |
180 | memcpy(smi_data_buf + pos, buf, count); |
181 | ret = count; |
182 | out: |
183 | mutex_unlock(&smi_data_lock); |
184 | return ret; |
185 | } |
186 | |
187 | static ssize_t host_control_action_show(struct device *dev, |
188 | struct device_attribute *attr, |
189 | char *buf) |
190 | { |
191 | return sprintf(buf, "%u\n", host_control_action); |
192 | } |
193 | |
194 | static ssize_t host_control_action_store(struct device *dev, |
195 | struct device_attribute *attr, |
196 | const char *buf, size_t count) |
197 | { |
198 | ssize_t ret; |
199 | |
200 | /* make sure buffer is available for host control command */ |
201 | mutex_lock(&smi_data_lock); |
202 | ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); |
203 | mutex_unlock(&smi_data_lock); |
204 | if (ret) |
205 | return ret; |
206 | |
207 | host_control_action = simple_strtoul(buf, NULL, 10); |
208 | return count; |
209 | } |
210 | |
211 | static ssize_t host_control_smi_type_show(struct device *dev, |
212 | struct device_attribute *attr, |
213 | char *buf) |
214 | { |
215 | return sprintf(buf, "%u\n", host_control_smi_type); |
216 | } |
217 | |
218 | static ssize_t host_control_smi_type_store(struct device *dev, |
219 | struct device_attribute *attr, |
220 | const char *buf, size_t count) |
221 | { |
222 | host_control_smi_type = simple_strtoul(buf, NULL, 10); |
223 | return count; |
224 | } |
225 | |
226 | static ssize_t host_control_on_shutdown_show(struct device *dev, |
227 | struct device_attribute *attr, |
228 | char *buf) |
229 | { |
230 | return sprintf(buf, "%u\n", host_control_on_shutdown); |
231 | } |
232 | |
233 | static ssize_t host_control_on_shutdown_store(struct device *dev, |
234 | struct device_attribute *attr, |
235 | const char *buf, size_t count) |
236 | { |
237 | host_control_on_shutdown = simple_strtoul(buf, NULL, 10); |
238 | return count; |
239 | } |
240 | |
241 | /** |
242 | * dcdbas_smi_request: generate SMI request |
243 | * |
244 | * Called with smi_data_lock. |
245 | */ |
246 | int dcdbas_smi_request(struct smi_cmd *smi_cmd) |
247 | { |
248 | cpumask_var_t old_mask; |
249 | int ret = 0; |
250 | |
251 | if (smi_cmd->magic != SMI_CMD_MAGIC) { |
252 | dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", |
253 | __func__); |
254 | return -EBADR; |
255 | } |
256 | |
257 | /* SMI requires CPU 0 */ |
258 | if (!alloc_cpumask_var(&old_mask, GFP_KERNEL)) |
259 | return -ENOMEM; |
260 | |
261 | cpumask_copy(old_mask, ¤t->cpus_allowed); |
262 | set_cpus_allowed_ptr(current, cpumask_of(0)); |
263 | if (smp_processor_id() != 0) { |
264 | dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", |
265 | __func__); |
266 | ret = -EBUSY; |
267 | goto out; |
268 | } |
269 | |
270 | /* generate SMI */ |
271 | /* inb to force posted write through and make SMI happen now */ |
272 | asm volatile ( |
273 | "outb %b0,%w1\n" |
274 | "inb %w1" |
275 | : /* no output args */ |
276 | : "a" (smi_cmd->command_code), |
277 | "d" (smi_cmd->command_address), |
278 | "b" (smi_cmd->ebx), |
279 | "c" (smi_cmd->ecx) |
280 | : "memory" |
281 | ); |
282 | |
283 | out: |
284 | set_cpus_allowed_ptr(current, old_mask); |
285 | free_cpumask_var(old_mask); |
286 | return ret; |
287 | } |
288 | |
289 | /** |
290 | * smi_request_store: |
291 | * |
292 | * The valid values are: |
293 | * 0: zero SMI data buffer |
294 | * 1: generate calling interface SMI |
295 | * 2: generate raw SMI |
296 | * |
297 | * User application writes smi_cmd to smi_data before telling driver |
298 | * to generate SMI. |
299 | */ |
300 | static ssize_t smi_request_store(struct device *dev, |
301 | struct device_attribute *attr, |
302 | const char *buf, size_t count) |
303 | { |
304 | struct smi_cmd *smi_cmd; |
305 | unsigned long val = simple_strtoul(buf, NULL, 10); |
306 | ssize_t ret; |
307 | |
308 | mutex_lock(&smi_data_lock); |
309 | |
310 | if (smi_data_buf_size < sizeof(struct smi_cmd)) { |
311 | ret = -ENODEV; |
312 | goto out; |
313 | } |
314 | smi_cmd = (struct smi_cmd *)smi_data_buf; |
315 | |
316 | switch (val) { |
317 | case 2: |
318 | /* Raw SMI */ |
319 | ret = dcdbas_smi_request(smi_cmd); |
320 | if (!ret) |
321 | ret = count; |
322 | break; |
323 | case 1: |
324 | /* Calling Interface SMI */ |
325 | smi_cmd->ebx = (u32) virt_to_phys(smi_cmd->command_buffer); |
326 | ret = dcdbas_smi_request(smi_cmd); |
327 | if (!ret) |
328 | ret = count; |
329 | break; |
330 | case 0: |
331 | memset(smi_data_buf, 0, smi_data_buf_size); |
332 | ret = count; |
333 | break; |
334 | default: |
335 | ret = -EINVAL; |
336 | break; |
337 | } |
338 | |
339 | out: |
340 | mutex_unlock(&smi_data_lock); |
341 | return ret; |
342 | } |
343 | EXPORT_SYMBOL(dcdbas_smi_request); |
344 | |
345 | /** |
346 | * host_control_smi: generate host control SMI |
347 | * |
348 | * Caller must set up the host control command in smi_data_buf. |
349 | */ |
350 | static int host_control_smi(void) |
351 | { |
352 | struct apm_cmd *apm_cmd; |
353 | u8 *data; |
354 | unsigned long flags; |
355 | u32 num_ticks; |
356 | s8 cmd_status; |
357 | u8 index; |
358 | |
359 | apm_cmd = (struct apm_cmd *)smi_data_buf; |
360 | apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; |
361 | |
362 | switch (host_control_smi_type) { |
363 | case HC_SMITYPE_TYPE1: |
364 | spin_lock_irqsave(&rtc_lock, flags); |
365 | /* write SMI data buffer physical address */ |
366 | data = (u8 *)&smi_data_buf_phys_addr; |
367 | for (index = PE1300_CMOS_CMD_STRUCT_PTR; |
368 | index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); |
369 | index++, data++) { |
370 | outb(index, |
371 | (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); |
372 | outb(*data, |
373 | (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); |
374 | } |
375 | |
376 | /* first set status to -1 as called by spec */ |
377 | cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; |
378 | outb((u8) cmd_status, PCAT_APM_STATUS_PORT); |
379 | |
380 | /* generate SMM call */ |
381 | outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); |
382 | spin_unlock_irqrestore(&rtc_lock, flags); |
383 | |
384 | /* wait a few to see if it executed */ |
385 | num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; |
386 | while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) |
387 | == ESM_STATUS_CMD_UNSUCCESSFUL) { |
388 | num_ticks--; |
389 | if (num_ticks == EXPIRED_TIMER) |
390 | return -ETIME; |
391 | } |
392 | break; |
393 | |
394 | case HC_SMITYPE_TYPE2: |
395 | case HC_SMITYPE_TYPE3: |
396 | spin_lock_irqsave(&rtc_lock, flags); |
397 | /* write SMI data buffer physical address */ |
398 | data = (u8 *)&smi_data_buf_phys_addr; |
399 | for (index = PE1400_CMOS_CMD_STRUCT_PTR; |
400 | index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); |
401 | index++, data++) { |
402 | outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); |
403 | outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); |
404 | } |
405 | |
406 | /* generate SMM call */ |
407 | if (host_control_smi_type == HC_SMITYPE_TYPE3) |
408 | outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); |
409 | else |
410 | outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); |
411 | |
412 | /* restore RTC index pointer since it was written to above */ |
413 | CMOS_READ(RTC_REG_C); |
414 | spin_unlock_irqrestore(&rtc_lock, flags); |
415 | |
416 | /* read control port back to serialize write */ |
417 | cmd_status = inb(PE1400_APM_CONTROL_PORT); |
418 | |
419 | /* wait a few to see if it executed */ |
420 | num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; |
421 | while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { |
422 | num_ticks--; |
423 | if (num_ticks == EXPIRED_TIMER) |
424 | return -ETIME; |
425 | } |
426 | break; |
427 | |
428 | default: |
429 | dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", |
430 | __func__, host_control_smi_type); |
431 | return -ENOSYS; |
432 | } |
433 | |
434 | return 0; |
435 | } |
436 | |
437 | /** |
438 | * dcdbas_host_control: initiate host control |
439 | * |
440 | * This function is called by the driver after the system has |
441 | * finished shutting down if the user application specified a |
442 | * host control action to perform on shutdown. It is safe to |
443 | * use smi_data_buf at this point because the system has finished |
444 | * shutting down and no userspace apps are running. |
445 | */ |
446 | static void dcdbas_host_control(void) |
447 | { |
448 | struct apm_cmd *apm_cmd; |
449 | u8 action; |
450 | |
451 | if (host_control_action == HC_ACTION_NONE) |
452 | return; |
453 | |
454 | action = host_control_action; |
455 | host_control_action = HC_ACTION_NONE; |
456 | |
457 | if (!smi_data_buf) { |
458 | dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); |
459 | return; |
460 | } |
461 | |
462 | if (smi_data_buf_size < sizeof(struct apm_cmd)) { |
463 | dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", |
464 | __func__); |
465 | return; |
466 | } |
467 | |
468 | apm_cmd = (struct apm_cmd *)smi_data_buf; |
469 | |
470 | /* power off takes precedence */ |
471 | if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { |
472 | apm_cmd->command = ESM_APM_POWER_CYCLE; |
473 | apm_cmd->reserved = 0; |
474 | *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; |
475 | host_control_smi(); |
476 | } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { |
477 | apm_cmd->command = ESM_APM_POWER_CYCLE; |
478 | apm_cmd->reserved = 0; |
479 | *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; |
480 | host_control_smi(); |
481 | } |
482 | } |
483 | |
484 | /** |
485 | * dcdbas_reboot_notify: handle reboot notification for host control |
486 | */ |
487 | static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, |
488 | void *unused) |
489 | { |
490 | switch (code) { |
491 | case SYS_DOWN: |
492 | case SYS_HALT: |
493 | case SYS_POWER_OFF: |
494 | if (host_control_on_shutdown) { |
495 | /* firmware is going to perform host control action */ |
496 | printk(KERN_WARNING "Please wait for shutdown " |
497 | "action to complete...\n"); |
498 | dcdbas_host_control(); |
499 | } |
500 | break; |
501 | } |
502 | |
503 | return NOTIFY_DONE; |
504 | } |
505 | |
506 | static struct notifier_block dcdbas_reboot_nb = { |
507 | .notifier_call = dcdbas_reboot_notify, |
508 | .next = NULL, |
509 | .priority = INT_MIN |
510 | }; |
511 | |
512 | static DCDBAS_BIN_ATTR_RW(smi_data); |
513 | |
514 | static struct bin_attribute *dcdbas_bin_attrs[] = { |
515 | &bin_attr_smi_data, |
516 | NULL |
517 | }; |
518 | |
519 | static DCDBAS_DEV_ATTR_RW(smi_data_buf_size); |
520 | static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); |
521 | static DCDBAS_DEV_ATTR_WO(smi_request); |
522 | static DCDBAS_DEV_ATTR_RW(host_control_action); |
523 | static DCDBAS_DEV_ATTR_RW(host_control_smi_type); |
524 | static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); |
525 | |
526 | static struct attribute *dcdbas_dev_attrs[] = { |
527 | &dev_attr_smi_data_buf_size.attr, |
528 | &dev_attr_smi_data_buf_phys_addr.attr, |
529 | &dev_attr_smi_request.attr, |
530 | &dev_attr_host_control_action.attr, |
531 | &dev_attr_host_control_smi_type.attr, |
532 | &dev_attr_host_control_on_shutdown.attr, |
533 | NULL |
534 | }; |
535 | |
536 | static struct attribute_group dcdbas_attr_group = { |
537 | .attrs = dcdbas_dev_attrs, |
538 | }; |
539 | |
540 | static int dcdbas_probe(struct platform_device *dev) |
541 | { |
542 | int i, error; |
543 | |
544 | host_control_action = HC_ACTION_NONE; |
545 | host_control_smi_type = HC_SMITYPE_NONE; |
546 | |
547 | /* |
548 | * BIOS SMI calls require buffer addresses be in 32-bit address space. |
549 | * This is done by setting the DMA mask below. |
550 | */ |
551 | dcdbas_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); |
552 | dcdbas_pdev->dev.dma_mask = &dcdbas_pdev->dev.coherent_dma_mask; |
553 | |
554 | error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); |
555 | if (error) |
556 | return error; |
557 | |
558 | for (i = 0; dcdbas_bin_attrs[i]; i++) { |
559 | error = sysfs_create_bin_file(&dev->dev.kobj, |
560 | dcdbas_bin_attrs[i]); |
561 | if (error) { |
562 | while (--i >= 0) |
563 | sysfs_remove_bin_file(&dev->dev.kobj, |
564 | dcdbas_bin_attrs[i]); |
565 | sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); |
566 | return error; |
567 | } |
568 | } |
569 | |
570 | register_reboot_notifier(&dcdbas_reboot_nb); |
571 | |
572 | dev_info(&dev->dev, "%s (version %s)\n", |
573 | DRIVER_DESCRIPTION, DRIVER_VERSION); |
574 | |
575 | return 0; |
576 | } |
577 | |
578 | static int dcdbas_remove(struct platform_device *dev) |
579 | { |
580 | int i; |
581 | |
582 | unregister_reboot_notifier(&dcdbas_reboot_nb); |
583 | for (i = 0; dcdbas_bin_attrs[i]; i++) |
584 | sysfs_remove_bin_file(&dev->dev.kobj, dcdbas_bin_attrs[i]); |
585 | sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); |
586 | |
587 | return 0; |
588 | } |
589 | |
590 | static struct platform_driver dcdbas_driver = { |
591 | .driver = { |
592 | .name = DRIVER_NAME, |
593 | .owner = THIS_MODULE, |
594 | }, |
595 | .probe = dcdbas_probe, |
596 | .remove = dcdbas_remove, |
597 | }; |
598 | |
599 | /** |
600 | * dcdbas_init: initialize driver |
601 | */ |
602 | static int __init dcdbas_init(void) |
603 | { |
604 | int error; |
605 | |
606 | error = platform_driver_register(&dcdbas_driver); |
607 | if (error) |
608 | return error; |
609 | |
610 | dcdbas_pdev = platform_device_alloc(DRIVER_NAME, -1); |
611 | if (!dcdbas_pdev) { |
612 | error = -ENOMEM; |
613 | goto err_unregister_driver; |
614 | } |
615 | |
616 | error = platform_device_add(dcdbas_pdev); |
617 | if (error) |
618 | goto err_free_device; |
619 | |
620 | return 0; |
621 | |
622 | err_free_device: |
623 | platform_device_put(dcdbas_pdev); |
624 | err_unregister_driver: |
625 | platform_driver_unregister(&dcdbas_driver); |
626 | return error; |
627 | } |
628 | |
629 | /** |
630 | * dcdbas_exit: perform driver cleanup |
631 | */ |
632 | static void __exit dcdbas_exit(void) |
633 | { |
634 | /* |
635 | * make sure functions that use dcdbas_pdev are called |
636 | * before platform_device_unregister |
637 | */ |
638 | unregister_reboot_notifier(&dcdbas_reboot_nb); |
639 | |
640 | /* |
641 | * We have to free the buffer here instead of dcdbas_remove |
642 | * because only in module exit function we can be sure that |
643 | * all sysfs attributes belonging to this module have been |
644 | * released. |
645 | */ |
646 | smi_data_buf_free(); |
647 | platform_device_unregister(dcdbas_pdev); |
648 | platform_driver_unregister(&dcdbas_driver); |
649 | } |
650 | |
651 | module_init(dcdbas_init); |
652 | module_exit(dcdbas_exit); |
653 | |
654 | MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); |
655 | MODULE_VERSION(DRIVER_VERSION); |
656 | MODULE_AUTHOR("Dell Inc."); |
657 | MODULE_LICENSE("GPL"); |
658 | /* Any System or BIOS claiming to be by Dell */ |
659 | MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); |
660 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9