453 lines
11 KiB
C
453 lines
11 KiB
C
/*
|
|
* mods_acpi.c - This file is part of NVIDIA MODS kernel driver.
|
|
*
|
|
* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* NVIDIA MODS kernel driver is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* NVIDIA MODS kernel driver 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with NVIDIA MODS kernel driver.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "mods_internal.h"
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/device.h>
|
|
#include <acpi/acpi.h>
|
|
#include <acpi/acpi_bus.h>
|
|
|
|
static acpi_status mods_acpi_find_acpi_handler(acpi_handle,
|
|
u32,
|
|
void *,
|
|
void **);
|
|
|
|
/*********************
|
|
* PRIVATE FUNCTIONS *
|
|
*********************/
|
|
|
|
/* store handle if found. */
|
|
static void mods_acpi_handle_init(char *method_name, acpi_handle *handler)
|
|
{
|
|
MODS_ACPI_WALK_NAMESPACE(ACPI_TYPE_ANY,
|
|
ACPI_ROOT_OBJECT,
|
|
ACPI_UINT32_MAX,
|
|
mods_acpi_find_acpi_handler,
|
|
method_name,
|
|
handler);
|
|
|
|
if (!(*handler)) {
|
|
mods_debug_printk(DEBUG_ACPI, "ACPI method %s not found\n",
|
|
method_name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static acpi_status mods_acpi_find_acpi_handler(
|
|
acpi_handle handle,
|
|
u32 nest_level,
|
|
void *dummy1,
|
|
void **dummy2
|
|
)
|
|
{
|
|
acpi_handle acpi_method_handler_temp;
|
|
|
|
if (!acpi_get_handle(handle, dummy1, &acpi_method_handler_temp))
|
|
*dummy2 = acpi_method_handler_temp;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int mods_extract_acpi_object(
|
|
char *method,
|
|
union acpi_object *obj,
|
|
u8 **buf,
|
|
u8 *buf_end
|
|
)
|
|
{
|
|
int ret = OK;
|
|
|
|
switch (obj->type) {
|
|
|
|
case ACPI_TYPE_BUFFER:
|
|
if (obj->buffer.length == 0) {
|
|
mods_error_printk(
|
|
"empty ACPI output buffer from ACPI method %s\n",
|
|
method);
|
|
ret = -EINVAL;
|
|
} else if (obj->buffer.length <= buf_end-*buf) {
|
|
u32 size = obj->buffer.length;
|
|
|
|
memcpy(*buf, obj->buffer.pointer, size);
|
|
*buf += size;
|
|
} else {
|
|
mods_error_printk(
|
|
"output buffer too small for ACPI method %s\n",
|
|
method);
|
|
ret = -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case ACPI_TYPE_INTEGER:
|
|
if (buf_end - *buf >= 4) {
|
|
if (obj->integer.value > 0xFFFFFFFFU) {
|
|
mods_error_printk(
|
|
"integer value from ACPI method %s out of range\n",
|
|
method);
|
|
ret = -EINVAL;
|
|
} else {
|
|
memcpy(*buf, &obj->integer.value, 4);
|
|
*buf += 4;
|
|
}
|
|
} else {
|
|
mods_error_printk(
|
|
"output buffer too small for ACPI method %s\n",
|
|
method);
|
|
ret = -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case ACPI_TYPE_PACKAGE:
|
|
if (obj->package.count == 0) {
|
|
mods_error_printk(
|
|
"empty ACPI output package from ACPI method %s\n",
|
|
method);
|
|
ret = -EINVAL;
|
|
} else {
|
|
union acpi_object *elements = obj->package.elements;
|
|
u32 size = 0;
|
|
u32 i;
|
|
|
|
for (i = 0; i < obj->package.count; i++) {
|
|
u8 *old_buf = *buf;
|
|
|
|
ret = mods_extract_acpi_object(method,
|
|
&elements[i],
|
|
buf,
|
|
buf_end);
|
|
if (ret == OK) {
|
|
u32 new_size = *buf - old_buf;
|
|
|
|
if (size == 0) {
|
|
size = new_size;
|
|
} else if (size != new_size) {
|
|
mods_error_printk(
|
|
"ambiguous package element size from ACPI method %s\n",
|
|
method);
|
|
ret = -EINVAL;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
mods_error_printk(
|
|
"unsupported ACPI output type 0x%02x from method %s\n",
|
|
(unsigned int)obj->type, method);
|
|
ret = -EINVAL;
|
|
break;
|
|
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mods_eval_acpi_method(struct file *pfile,
|
|
struct MODS_EVAL_ACPI_METHOD *p,
|
|
struct mods_pci_dev_2 *pdevice)
|
|
{
|
|
int ret = OK;
|
|
int i;
|
|
acpi_status status;
|
|
struct acpi_object_list input;
|
|
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
union acpi_object *acpi_method = NULL;
|
|
union acpi_object acpi_params[ACPI_MAX_ARGUMENT_NUMBER];
|
|
acpi_handle acpi_method_handler = NULL;
|
|
|
|
if (pdevice) {
|
|
unsigned int devfn;
|
|
struct pci_dev *dev;
|
|
|
|
mods_debug_printk(DEBUG_ACPI,
|
|
"ACPI %s for device %04x:%x:%02x.%x\n",
|
|
p->method_name,
|
|
(unsigned int)pdevice->domain,
|
|
(unsigned int)pdevice->bus,
|
|
(unsigned int)pdevice->device,
|
|
(unsigned int)pdevice->function);
|
|
|
|
devfn = PCI_DEVFN(pdevice->device, pdevice->function);
|
|
dev = MODS_PCI_GET_SLOT(pdevice->domain, pdevice->bus, devfn);
|
|
if (!dev) {
|
|
mods_error_printk("ACPI: PCI device not found\n");
|
|
return -EINVAL;
|
|
}
|
|
acpi_method_handler = MODS_ACPI_HANDLE(&dev->dev);
|
|
} else {
|
|
mods_debug_printk(DEBUG_ACPI, "ACPI %s\n", p->method_name);
|
|
mods_acpi_handle_init(p->method_name, &acpi_method_handler);
|
|
}
|
|
|
|
if (!acpi_method_handler) {
|
|
mods_debug_printk(DEBUG_ACPI, "ACPI: handle for %s not found\n",
|
|
p->method_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (p->argument_count >= ACPI_MAX_ARGUMENT_NUMBER) {
|
|
mods_error_printk("invalid argument count for ACPI call\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < p->argument_count; i++) {
|
|
switch (p->argument[i].type) {
|
|
case ACPI_MODS_TYPE_INTEGER: {
|
|
acpi_params[i].integer.type = ACPI_TYPE_INTEGER;
|
|
acpi_params[i].integer.value
|
|
= p->argument[i].integer.value;
|
|
break;
|
|
}
|
|
case ACPI_MODS_TYPE_BUFFER: {
|
|
acpi_params[i].buffer.type = ACPI_TYPE_BUFFER;
|
|
acpi_params[i].buffer.length
|
|
= p->argument[i].buffer.length;
|
|
acpi_params[i].buffer.pointer
|
|
= p->in_buffer + p->argument[i].buffer.offset;
|
|
break;
|
|
}
|
|
default: {
|
|
mods_error_printk("unsupported ACPI argument type\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
input.count = p->argument_count;
|
|
input.pointer = acpi_params;
|
|
|
|
status = acpi_evaluate_object(acpi_method_handler,
|
|
pdevice ? p->method_name : NULL,
|
|
&input,
|
|
&output);
|
|
|
|
if (ACPI_FAILURE(status)) {
|
|
mods_error_printk("ACPI method %s failed\n", p->method_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
acpi_method = output.pointer;
|
|
if (!acpi_method) {
|
|
mods_error_printk("missing output from ACPI method %s\n",
|
|
p->method_name);
|
|
ret = -EINVAL;
|
|
} else {
|
|
u8 *buf = p->out_buffer;
|
|
|
|
ret = mods_extract_acpi_object(p->method_name,
|
|
acpi_method,
|
|
&buf,
|
|
buf+sizeof(p->out_buffer));
|
|
p->out_data_size = (ret == OK) ? (buf - p->out_buffer) : 0;
|
|
}
|
|
|
|
kfree(output.pointer);
|
|
return ret;
|
|
}
|
|
|
|
static int mods_acpi_get_ddc(struct file *pfile,
|
|
struct MODS_ACPI_GET_DDC_2 *p,
|
|
struct mods_pci_dev_2 *pci_device)
|
|
{
|
|
acpi_status status;
|
|
struct acpi_device *device = NULL;
|
|
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
union acpi_object *ddc;
|
|
union acpi_object ddc_arg0 = { ACPI_TYPE_INTEGER };
|
|
struct acpi_object_list input = { 1, &ddc_arg0 };
|
|
struct list_head *node, *next;
|
|
u32 i;
|
|
acpi_handle dev_handle = NULL;
|
|
acpi_handle lcd_dev_handle = NULL;
|
|
|
|
mods_debug_printk(DEBUG_ACPI,
|
|
"ACPI _DDC (EDID) for device %04x:%x:%02x.%x\n",
|
|
(unsigned int)pci_device->domain,
|
|
(unsigned int)pci_device->bus,
|
|
(unsigned int)pci_device->device,
|
|
(unsigned int)pci_device->function);
|
|
|
|
{
|
|
unsigned int devfn = PCI_DEVFN(pci_device->device,
|
|
pci_device->function);
|
|
struct pci_dev *dev = MODS_PCI_GET_SLOT(pci_device->domain,
|
|
pci_device->bus, devfn);
|
|
if (!dev) {
|
|
mods_error_printk("ACPI: PCI device not found\n");
|
|
return -EINVAL;
|
|
}
|
|
dev_handle = MODS_ACPI_HANDLE(&dev->dev);
|
|
}
|
|
if (!dev_handle) {
|
|
mods_debug_printk(DEBUG_ACPI,
|
|
"ACPI: handle for _DDC not found\n");
|
|
return -EINVAL;
|
|
}
|
|
status = acpi_bus_get_device(dev_handle, &device);
|
|
|
|
if (ACPI_FAILURE(status) || !device) {
|
|
mods_error_printk("ACPI: device for _DDC not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
list_for_each_safe(node, next, &device->children) {
|
|
#ifdef MODS_ACPI_DEVID_64
|
|
unsigned long long
|
|
#else
|
|
unsigned long
|
|
#endif
|
|
device_id = 0;
|
|
|
|
struct acpi_device *dev =
|
|
list_entry(node, struct acpi_device, node);
|
|
|
|
if (!dev)
|
|
continue;
|
|
|
|
status = acpi_evaluate_integer(dev->handle,
|
|
"_ADR",
|
|
NULL,
|
|
&device_id);
|
|
if (ACPI_FAILURE(status))
|
|
/* Couldnt query device_id for this device */
|
|
continue;
|
|
|
|
device_id = (device_id & 0xffff);
|
|
|
|
if ((device_id == 0x0110) || /* Only for an LCD*/
|
|
(device_id == 0x0118) ||
|
|
(device_id == 0x0400)) {
|
|
|
|
lcd_dev_handle = dev->handle;
|
|
mods_debug_printk(DEBUG_ACPI,
|
|
"ACPI: Found LCD 0x%x on device %04x:%x:%02x.%x\n",
|
|
(unsigned int)device_id,
|
|
(unsigned int)p->device.domain,
|
|
(unsigned int)p->device.bus,
|
|
(unsigned int)p->device.device,
|
|
(unsigned int)p->device.function);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (lcd_dev_handle == NULL) {
|
|
mods_error_printk(
|
|
"ACPI: LCD not found for device %04x:%x:%02x.%x\n",
|
|
(unsigned int)p->device.domain,
|
|
(unsigned int)p->device.bus,
|
|
(unsigned int)p->device.device,
|
|
(unsigned int)p->device.function);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* As per ACPI Spec 3.0:
|
|
* ARG0 = 0x1 for 128 bytes EDID buffer
|
|
* ARG0 = 0x2 for 256 bytes EDID buffer
|
|
*/
|
|
for (i = 1; i <= 2; i++) {
|
|
ddc_arg0.integer.value = i;
|
|
status = acpi_evaluate_object(lcd_dev_handle,
|
|
"_DDC",
|
|
&input,
|
|
&output);
|
|
if (ACPI_SUCCESS(status))
|
|
break;
|
|
}
|
|
|
|
if (ACPI_FAILURE(status)) {
|
|
mods_error_printk("ACPI method _DDC (EDID) failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ddc = output.pointer;
|
|
if (ddc && (ddc->type == ACPI_TYPE_BUFFER)
|
|
&& (ddc->buffer.length > 0)) {
|
|
|
|
if (ddc->buffer.length <= sizeof(p->out_buffer)) {
|
|
p->out_data_size = ddc->buffer.length;
|
|
memcpy(p->out_buffer,
|
|
ddc->buffer.pointer,
|
|
p->out_data_size);
|
|
} else {
|
|
mods_error_printk(
|
|
"output buffer too small for ACPI method _DDC (EDID)\n");
|
|
kfree(output.pointer);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
mods_error_printk("unsupported ACPI output type\n");
|
|
kfree(output.pointer);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kfree(output.pointer);
|
|
return OK;
|
|
}
|
|
|
|
/*************************
|
|
* ESCAPE CALL FUNCTIONS *
|
|
*************************/
|
|
|
|
int esc_mods_eval_acpi_method(struct file *pfile,
|
|
struct MODS_EVAL_ACPI_METHOD *p)
|
|
{
|
|
return mods_eval_acpi_method(pfile, p, 0);
|
|
}
|
|
|
|
int esc_mods_eval_dev_acpi_method_2(struct file *pfile,
|
|
struct MODS_EVAL_DEV_ACPI_METHOD_2 *p)
|
|
{
|
|
return mods_eval_acpi_method(pfile, &p->method, &p->device);
|
|
}
|
|
|
|
int esc_mods_eval_dev_acpi_method(struct file *pfile,
|
|
struct MODS_EVAL_DEV_ACPI_METHOD *p)
|
|
{
|
|
struct mods_pci_dev_2 device = {0};
|
|
|
|
device.domain = 0;
|
|
device.bus = p->device.bus;
|
|
device.device = p->device.device;
|
|
device.function = p->device.function;
|
|
return mods_eval_acpi_method(pfile, &p->method, &device);
|
|
}
|
|
|
|
int esc_mods_acpi_get_ddc_2(struct file *pfile,
|
|
struct MODS_ACPI_GET_DDC_2 *p)
|
|
{
|
|
return mods_acpi_get_ddc(pfile, p, &p->device);
|
|
}
|
|
|
|
int esc_mods_acpi_get_ddc(struct file *pfile, struct MODS_ACPI_GET_DDC *p)
|
|
{
|
|
struct MODS_ACPI_GET_DDC_2 *pp = (struct MODS_ACPI_GET_DDC_2 *) p;
|
|
struct mods_pci_dev_2 device = {0};
|
|
|
|
device.domain = 0;
|
|
device.bus = p->device.bus;
|
|
device.device = p->device.device;
|
|
device.function = p->device.function;
|
|
|
|
return mods_acpi_get_ddc(pfile, pp, &device);
|
|
}
|