~ K A L I ~
UNAME : Linux web62.extendcp.co.uk 4.18.0-553.58.1.el8_10.x86_64 #1 SMP Wed Jun 25 06:37:46 EDT 2025 x86_64SERVER IP : 10.0.187.62 -________-
CLIENT IP : 216.73.216.199 MINI SHELL D ZAB '
Current File : //opt/dell/srvadmin/src/srvadmin-hapi/dks/dcdbas/kernel24/dcdbas.c |
/*
* dcdbas.c: Dell Systems Management Base Driver
*
* Copyright (C) 1995-2006 Dell Inc.
*
* The Dell Systems Management Base driver is a character driver that
* implements ioctls for Dell systems management software to use to
* communicate with the driver. The driver provides support for Dell
* systems management software to manage the following Dell PowerEdge
* systems: 300, 1300, 1400, 400SC, 500SC, 1500SC, 1550, 600SC, 1600SC,
* 650, 1655MC, 700, and 750.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2.0 as published by
* the Free Software Foundation.
*
* 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. See the
* GNU General Public License for more details.
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/mc146818rtc.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/version.h>
#include <asm/io.h>
#include <asm/scatterlist.h>
#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include "dcdbas.h"
#define DRIVER_NAME "dcdbas"
#define DRIVER_VERSION "5.6.0-1.1"
#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver"
static int driver_major;
static int hold_on_shutdown;
static struct semaphore tvm_lock;
static u8 *tvm_dma_buf;
static u32 tvm_dma_buf_phys_addr;
static unsigned int tvm_dma_buf_size;
static u8 tvm_hc_action;
static u8 tvm_smi_type;
/**
* dcdbas_alloc_32bit - allocate 32-bit addressable memory
*/
static void *dcdbas_alloc_32bit(size_t size, unsigned int flags)
{
void *mem;
u64 mask = 0xffffffff;
unsigned int order = get_order(size);
while ((mem = (void *)__get_free_pages(flags, order)) != NULL) {
if (((u64)virt_to_phys(mem) & ~mask) == 0) {
memset(mem, 0, size);
break;
}
free_pages((unsigned long)mem, order);
mem = NULL;
if (flags & GFP_DMA)
break;
flags |= GFP_DMA;
}
return mem;
}
/**
* dcdbas_free_32bit - free 32-bit addressable memory
*/
static void dcdbas_free_32bit(void *mem, size_t size)
{
free_pages((unsigned long)mem, get_order(size));
}
/**
* tvm_free_dma_buf - free buffer allocated for TVM systems management
*/
static void tvm_free_dma_buf(void)
{
if (tvm_dma_buf == NULL)
return;
pr_debug("%s: phys: %x size: %u\n",
__FUNCTION__, tvm_dma_buf_phys_addr, tvm_dma_buf_size);
dcdbas_free_32bit(tvm_dma_buf, tvm_dma_buf_size);
tvm_dma_buf = NULL;
tvm_dma_buf_phys_addr = 0;
tvm_dma_buf_size = 0;
}
/**
* tvm_realloc_dma_buf - reallocate buffer for TVM systems management if needed
*/
static int tvm_realloc_dma_buf(unsigned int size)
{
void *buf;
if (size > MAX_TVM_DMA_BUF_SIZE)
return -EINVAL;
if (tvm_dma_buf_size >= size) {
if ((size != 0) && (tvm_dma_buf == NULL)) {
pr_debug("%s: corruption detected\n", __FUNCTION__);
return -EFAULT;
}
/* current buffer is big enough */
return 0;
}
/* new buffer is needed */
buf = dcdbas_alloc_32bit(size, GFP_KERNEL);
if (buf == NULL) {
printk(KERN_INFO"%s: failed to allocate TVM memory size %u\n",
DRIVER_NAME, size);
return -ENOMEM;
}
/* free any existing buffer */
tvm_free_dma_buf();
/* set up new buffer for use */
tvm_dma_buf = buf;
tvm_dma_buf_phys_addr = (u32)virt_to_phys(buf);
tvm_dma_buf_size = size;
pr_debug("%s: phys: %x size: %u\n",
__FUNCTION__, tvm_dma_buf_phys_addr, tvm_dma_buf_size);
return 0;
}
/**
* tvm_read_dma_buf - read systems management command response from TVM buffer
* @ireq: IOCTL data
*/
static int tvm_read_dma_buf(struct dcdbas_ioctl_req *ireq)
{
struct dcdbas_tvm_mem_read *tmr;
struct apm_cmd *apm_cmd;
unsigned int data_size_needed, buf_size_needed;
data_size_needed = sizeof(struct dcdbas_tvm_mem_read);
if (ireq->hdr.data_size < data_size_needed)
return -EINVAL;
tmr = &ireq->data.tvm_mem_read;
pr_debug("%s: size: %u\n", __FUNCTION__, tmr->size);
if (tmr->size < ESM_APM_CMD_HEADER_SIZE ||
tmr->size > MAX_DCDBAS_IOCTL_DATA_SIZE)
return -EINVAL;
data_size_needed = sizeof(struct dcdbas_tvm_mem_read) -
sizeof(tmr->buffer) +
tmr->size;
if (ireq->hdr.data_size < data_size_needed)
return -EINVAL;
if (tvm_dma_buf == NULL ||
tvm_dma_buf_size < ESM_APM_CMD_HEADER_SIZE) {
ireq->hdr.status = ESM_STATUS_CMD_DEVICE_BAD;
return 0;
}
apm_cmd = (struct apm_cmd *)tvm_dma_buf;
buf_size_needed = tmr->size;
if (apm_cmd->command & ESM_APM_LONG_CMD_FORMAT)
buf_size_needed +=
(sizeof(struct apm_cmd) - ESM_APM_CMD_HEADER_SIZE);
if (tvm_dma_buf_size < buf_size_needed) {
ireq->hdr.status = ESM_STATUS_CMD_DEVICE_BAD;
return 0;
}
if (apm_cmd->command & ESM_APM_LONG_CMD_FORMAT) {
/* long command */
memcpy(tmr->buffer, tvm_dma_buf, ESM_APM_CMD_HEADER_SIZE);
if (tmr->size > ESM_APM_CMD_HEADER_SIZE) {
memcpy(tmr->buffer + ESM_APM_CMD_HEADER_SIZE,
tvm_dma_buf + sizeof(struct apm_cmd),
tmr->size - ESM_APM_CMD_HEADER_SIZE);
}
} else {
/* short command */
memcpy(tmr->buffer, tvm_dma_buf, tmr->size);
}
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
return 0;
}
/**
* tvm_write_dma_buf - write systems management command request to TVM buffer
* @ireq: IOCTL data
*/
static int tvm_write_dma_buf(struct dcdbas_ioctl_req *ireq)
{
struct dcdbas_tvm_mem_write *tmw;
struct apm_cmd *apm_cmd;
unsigned int data_size_needed, buf_size_needed;
int ret;
data_size_needed = sizeof(struct dcdbas_tvm_mem_write);
if (ireq->hdr.data_size < data_size_needed)
return -EINVAL;
tmw = &ireq->data.tvm_mem_write;
pr_debug("%s: size: %u\n", __FUNCTION__, tmw->size);
if (tmw->size < ESM_APM_CMD_HEADER_SIZE ||
tmw->size > MAX_DCDBAS_IOCTL_DATA_SIZE)
return -EINVAL;
data_size_needed = sizeof(struct dcdbas_tvm_mem_write) -
sizeof(tmw->buffer) +
tmw->size;
if (ireq->hdr.data_size < data_size_needed)
return -EINVAL;
apm_cmd = (struct apm_cmd *)tmw->buffer;
buf_size_needed = tmw->size;
if (apm_cmd->command & ESM_APM_LONG_CMD_FORMAT)
buf_size_needed +=
(sizeof(struct apm_cmd) - ESM_APM_CMD_HEADER_SIZE);
/* make sure buffer is big enough for command */
ret = tvm_realloc_dma_buf(buf_size_needed);
if (ret)
return ret;
if (apm_cmd->command & ESM_APM_LONG_CMD_FORMAT) {
/* long command */
memcpy(tvm_dma_buf, tmw->buffer, ESM_APM_CMD_HEADER_SIZE);
if (tmw->size > ESM_APM_CMD_HEADER_SIZE) {
memcpy(tvm_dma_buf + sizeof(struct apm_cmd),
tmw->buffer + ESM_APM_CMD_HEADER_SIZE,
tmw->size - ESM_APM_CMD_HEADER_SIZE);
}
/* create scatter/gather list */
apm_cmd = (struct apm_cmd *)tvm_dma_buf;
apm_cmd->parameters.longreq.num_sg_entries = 1;
apm_cmd->parameters.longreq.sglist[0].size =
(tmw->size - ESM_APM_CMD_HEADER_SIZE);
apm_cmd->parameters.longreq.sglist[0].addr =
(tvm_dma_buf_phys_addr + sizeof(struct apm_cmd));
} else {
/* short command */
memcpy(tvm_dma_buf, tmw->buffer, tmw->size);
}
tmw->phys_address = tvm_dma_buf_phys_addr;
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
return 0;
}
/**
* tvm_alloc_dma_buf - allocate buffer for TVM systems management
* @ireq: IOCTL data
*/
static int tvm_alloc_dma_buf(struct dcdbas_ioctl_req *ireq)
{
struct dcdbas_tvm_mem_alloc *tma;
int ret;
if (ireq->hdr.data_size < sizeof(struct dcdbas_tvm_mem_alloc))
return -EINVAL;
tma = &ireq->data.tvm_mem_alloc;
pr_debug("%s: size: %u\n", __FUNCTION__, tma->size);
ret = tvm_realloc_dma_buf(tma->size);
if (ret)
return ret;
tma->phys_address = tvm_dma_buf_phys_addr;
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
return 0;
}
/**
* tvm_set_hc_action - set TVM system host control action
* @ireq: IOCTL data
*/
static int tvm_set_hc_action(struct dcdbas_ioctl_req *ireq)
{
struct dcdbas_tvm_hc_action *thca;
int ret;
if (ireq->hdr.data_size < sizeof(struct dcdbas_tvm_hc_action))
return -EINVAL;
/* make sure buffer is available for host control command */
ret = tvm_realloc_dma_buf(sizeof(struct apm_cmd));
if (ret)
return ret;
thca = &ireq->data.tvm_hc_action;
pr_debug("%s: action_bitmap: %x smi_type: %u\n",
__FUNCTION__, thca->action_bitmap, thca->smi_type);
tvm_hc_action = thca->action_bitmap;
tvm_smi_type = thca->smi_type;
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
return 0;
}
/**
* tvm_perform_cmd - perform command for TVM systems management
*
* The caller must set up the command in tvm_dma_buf.
*/
static s32 tvm_perform_cmd(void)
{
struct apm_cmd *apm_cmd;
u8 *data;
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)
unsigned long flags;
#endif
u32 num_ticks;
s8 cmd_status;
u8 index;
apm_cmd = (struct apm_cmd *)tvm_dma_buf;
apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL;
switch (tvm_smi_type) {
case TVM_SMITYPE_TYPE1:
{
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)
spin_lock_irqsave(&rtc_lock, flags);
#endif
/* write physical address one byte at a time */
data = (u8 *)&tvm_dma_buf_phys_addr;
for (index = PE1300_CMOS_CMD_STRUCT_PTR;
index < (PE1300_CMOS_CMD_STRUCT_PTR + 4);
index++, data++) {
outb(index,
(CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4));
outb(*data,
(CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4));
}
/* first set status to -1 as called by spec */
cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL;
outb((u8)cmd_status, PCAT_APM_STATUS_PORT);
/* generate SMM call */
outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)
spin_unlock_irqrestore(&rtc_lock, flags);
#endif
/* wait a few to see if it executed */
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
while ((cmd_status = inb(PCAT_APM_STATUS_PORT))
== ESM_STATUS_CMD_UNSUCCESSFUL) {
num_ticks--;
if (num_ticks == EXPIRED_TIMER)
return ESM_STATUS_CMD_TIMEOUT;
}
}
break;
case TVM_SMITYPE_TYPE2:
case TVM_SMITYPE_TYPE3:
{
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)
spin_lock_irqsave(&rtc_lock, flags);
#endif
/* write physical address one byte at a time */
data = (u8 *)&tvm_dma_buf_phys_addr;
for (index = PE1400_CMOS_CMD_STRUCT_PTR;
index < (PE1400_CMOS_CMD_STRUCT_PTR + 4);
index++, data++) {
outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT));
outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT));
}
/* generate SMM call */
if (tvm_smi_type == TVM_SMITYPE_TYPE3)
outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT);
else
outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT);
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)
/* restore RTC index pointer since it was written to above */
CMOS_READ(RTC_REG_C);
spin_unlock_irqrestore(&rtc_lock, flags);
#endif
/* read control port back to serialize write */
cmd_status = inb(PE1400_APM_CONTROL_PORT);
/* wait a few to see if it executed */
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) {
num_ticks--;
if (num_ticks == EXPIRED_TIMER)
return ESM_STATUS_CMD_TIMEOUT;
}
}
break;
default:
return ESM_STATUS_CMD_NOT_IMPLEMENTED;
}
return ESM_STATUS_CMD_SUCCESS;
}
/**
* tvm_host_control - initiate host control action on TVM system
*/
static s32 tvm_host_control(void)
{
struct apm_cmd *apm_cmd;
if (tvm_dma_buf == NULL ||
tvm_dma_buf_size < sizeof(struct apm_cmd)) {
pr_debug("%s: TVM buffer error\n", __FUNCTION__);
return ESM_STATUS_CMD_DEVICE_BAD;
}
apm_cmd = (struct apm_cmd *)tvm_dma_buf;
/* power off takes precedence */
if (tvm_hc_action & HC_ACTION_HOST_CONTROL_POWEROFF) {
tvm_hc_action = HC_ACTION_NONE;
apm_cmd->command = ESM_APM_POWER_CYCLE;
apm_cmd->reserved = 0;
*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16)0;
return tvm_perform_cmd();
}
if (tvm_hc_action & HC_ACTION_HOST_CONTROL_POWERCYCLE) {
tvm_hc_action = HC_ACTION_NONE;
apm_cmd->command = ESM_APM_POWER_CYCLE;
apm_cmd->reserved = 0;
*((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16)20;
return tvm_perform_cmd();
}
tvm_hc_action = HC_ACTION_NONE;
return ESM_STATUS_CMD_UNSUCCESSFUL;
}
/**
* callintf_generate_smi - generate SMI for calling interface request
* @ireq: IOCTL data
*/
static int callintf_generate_smi(struct dcdbas_ioctl_req *ireq)
{
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)
struct dcdbas_callintf_cmd *ci_cmd;
u32 command_buffer_phys_addr;
#ifdef CONFIG_SMP
cpumask_t old_mask;
#endif
if (ireq->hdr.data_size < sizeof(struct dcdbas_callintf_cmd))
return -EINVAL;
ci_cmd = &ireq->data.callintf_cmd;
command_buffer_phys_addr = virt_to_phys(ci_cmd->command_buffer);
#ifdef CONFIG_SMP
/* SMI requires CPU 0 */
old_mask = current->cpus_allowed;
set_cpus_allowed(current, cpumask_of_cpu(0));
if (smp_processor_id() != 0) {
pr_debug("%s: failed to get CPU 0\n", __FUNCTION__);
set_cpus_allowed(current, old_mask);
return -EBUSY;
}
#endif
/*
* SMI requires command buffer physical address in ebx and
* signature in ecx.
*/
/* generate SMI */
asm volatile (
"outb %b0,%w1"
: /* no output args */
: "a" (ci_cmd->command_code),
"d" (ci_cmd->command_address),
"b" (command_buffer_phys_addr),
"c" (ci_cmd->signature)
: "memory"
);
#ifdef CONFIG_SMP
set_cpus_allowed(current, old_mask);
#endif
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
return 0;
#else
return -ENOSYS;
#endif
}
/**
* dcdbas_host_control - initiate host control action
*/
static void dcdbas_host_control(void)
{
if (tvm_hc_action != HC_ACTION_NONE)
tvm_host_control();
}
/**
* dcdbas_dispatch_ioctl - dispatch IOCTL request
* @ireq: IOCTL request
*/
static int dcdbas_dispatch_ioctl(struct dcdbas_ioctl_req *ireq)
{
int retval = 0;
pr_debug("%s: req_type: %u\n", __FUNCTION__, ireq->hdr.req_type);
switch (ireq->hdr.req_type) {
case ESM_TVM_READ_MEM:
if (down_interruptible(&tvm_lock))
return -ERESTARTSYS;
retval = tvm_read_dma_buf(ireq);
up(&tvm_lock);
break;
case ESM_TVM_WRITE_MEM:
if (down_interruptible(&tvm_lock))
return -ERESTARTSYS;
retval = tvm_write_dma_buf(ireq);
up(&tvm_lock);
break;
case ESM_TVM_ALLOC_MEM:
if (down_interruptible(&tvm_lock))
return -ERESTARTSYS;
retval = tvm_alloc_dma_buf(ireq);
up(&tvm_lock);
break;
case ESM_TVM_HC_ACTION:
if (down_interruptible(&tvm_lock))
return -ERESTARTSYS;
retval = tvm_set_hc_action(ireq);
up(&tvm_lock);
break;
case ESM_CALLINTF_REQ:
retval = callintf_generate_smi(ireq);
break;
case ESM_HOLD_OS_ON_SHUTDOWN:
/* firmware is going to perform host control action */
hold_on_shutdown = 1;
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
break;
case ESM_CANCEL_HOLD_OS_ON_SHUTDOWN:
hold_on_shutdown = 0;
ireq->hdr.status = ESM_STATUS_CMD_SUCCESS;
break;
default:
pr_debug("%s: unsupported req_type\n", __FUNCTION__);
ireq->hdr.status = ESM_STATUS_CMD_NOT_IMPLEMENTED;
break;
}
return retval;
}
/**
* dcdbas_do_ioctl - process ioctl request
* @filp: file object for device
* @cmd: IOCTL command
* @arg: IOCTL request data
*/
static int dcdbas_do_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct dcdbas_ioctl_req __user *ubuf;
struct dcdbas_ioctl_req *kbuf;
struct dcdbas_ioctl_hdr hdr;
unsigned long size;
int ret;
if (cmd != IOCTL_DCDBAS_CMD) {
ret = -EINVAL;
goto out1;
}
ubuf = (struct dcdbas_ioctl_req __user *)arg;
if (copy_from_user(&hdr, ubuf, sizeof(struct dcdbas_ioctl_hdr))) {
ret = -EFAULT;
goto out1;
}
if (hdr.data_size > MAX_DCDBAS_IOCTL_DATA_SIZE) {
ret = -EINVAL;
goto out1;
}
size = sizeof(struct dcdbas_ioctl_hdr) + hdr.data_size;
if ((kbuf = dcdbas_alloc_32bit(size, GFP_KERNEL)) == NULL) {
printk(KERN_INFO
"%s: failed to allocate ioctl memory size %lu\n",
DRIVER_NAME, size);
ret = -ENOMEM;
goto out1;
}
if (copy_from_user(kbuf, ubuf, size)) {
ret = -EFAULT;
goto out2;
}
if ((ret = dcdbas_dispatch_ioctl(kbuf)) != 0)
goto out2;
if (copy_to_user(ubuf, kbuf, size))
ret = -EFAULT;
out2:
dcdbas_free_32bit(kbuf, size);
out1:
return ret;
}
/**
* dcdbas_ioctl - ioctl handler
* @inode: inode for device
* @filp: file object for device
* @cmd: IOCTL command
* @arg: IOCTL request data
*/
static int dcdbas_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
return dcdbas_do_ioctl(filp, cmd, arg);
}
#ifdef HAVE_COMPAT_IOCTL
/**
* dcdbas_compat_ioctl - compat ioctl handler
* @filp: file object for device
* @cmd: IOCTL command
* @arg: IOCTL request data
*/
static long dcdbas_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
return dcdbas_do_ioctl(filp, cmd, arg);
}
#endif
/**
* dcdbas_reboot_notify - handle reboot notification
* @nb: info about registered reboot notifier
* @code: notification code
* @unused: unused argument
*/
static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code,
void *unused)
{
static unsigned int notify_cnt = 0;
switch (code) {
case SYS_DOWN:
case SYS_HALT:
case SYS_POWER_OFF:
if (hold_on_shutdown) {
/* firmware is going to perform host control action */
if (++notify_cnt == 2) {
printk(KERN_WARNING
"Please wait for shutdown "
"action to complete...\n");
dcdbas_host_control();
}
/*
* register again and initiate the host control
* action on the second notification to allow
* everyone that registered to be notified
*/
register_reboot_notifier(nb);
}
break;
}
return NOTIFY_DONE;
}
static struct file_operations dcdbas_fops = {
.owner = THIS_MODULE,
.ioctl = dcdbas_ioctl,
#ifdef HAVE_COMPAT_IOCTL
.compat_ioctl = dcdbas_compat_ioctl,
#endif
};
static struct notifier_block dcdbas_reboot_nb = {
.notifier_call = dcdbas_reboot_notify,
.next = NULL,
.priority = 0
};
/**
* dcdbas_init - initialize driver
*/
static int __init dcdbas_init(void)
{
int ret;
tvm_hc_action = HC_ACTION_NONE;
tvm_smi_type = TVM_SMITYPE_NONE;
sema_init(&tvm_lock, 1);
ret = register_chrdev(0, DRIVER_NAME, &dcdbas_fops);
if (ret < 0) {
printk(KERN_INFO"%s: register_chrdev failed with error %d\n",
DRIVER_NAME, ret);
goto error1;
}
driver_major = ret;
register_reboot_notifier(&dcdbas_reboot_nb);
dcdbas_register_ioctl32(IOCTL_DCDBAS_CMD);
printk(KERN_INFO"%s: %s (version %s)\n",
DRIVER_NAME, DRIVER_DESCRIPTION, DRIVER_VERSION);
return 0;
error1:
return ret;
}
/**
* dcdbas_exit - perform driver cleanup
*/
static void __exit dcdbas_exit(void)
{
dcdbas_unregister_ioctl32(IOCTL_DCDBAS_CMD);
unregister_reboot_notifier(&dcdbas_reboot_nb);
unregister_chrdev(driver_major, DRIVER_NAME);
tvm_free_dma_buf();
}
module_init(dcdbas_init);
module_exit(dcdbas_exit);
MODULE_DESCRIPTION(DRIVER_DESCRIPTION" (version "DRIVER_VERSION")");
MODULE_VERSION(DRIVER_VERSION);
MODULE_AUTHOR("Dell Inc.");
MODULE_LICENSE("GPL");
Coded by KALI :v Greetz to DR HARD ../ kali.zbi@hotmail.com