open-gpu-kernel-modules/kernel-open/nvidia/nv-acpi.c
2022-08-02 08:35:13 -07:00

1412 lines
37 KiB
C

/*
* SPDX-FileCopyrightText: Copyright (c) 1999-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#define __NO_VERSION__
#include "os-interface.h"
#include "nv-linux.h"
#include "nv-reg.h"
#include <linux/acpi.h>
#if defined(NV_LINUX_ACPI_EVENTS_SUPPORTED)
static NV_STATUS nv_acpi_extract_integer (const union acpi_object *, void *, NvU32, NvU32 *);
static NV_STATUS nv_acpi_extract_buffer (const union acpi_object *, void *, NvU32, NvU32 *);
static NV_STATUS nv_acpi_extract_package (const union acpi_object *, void *, NvU32, NvU32 *);
static NV_STATUS nv_acpi_extract_object (const union acpi_object *, void *, NvU32, NvU32 *);
static void nv_acpi_powersource_hotplug_event(acpi_handle, u32, void *);
static acpi_status nv_acpi_find_methods (acpi_handle, u32, void *, void **);
static NV_STATUS nv_acpi_nvif_method (NvU32, NvU32, void *, NvU16, NvU32 *, void *, NvU16 *);
static NV_STATUS nv_acpi_wmmx_method (NvU32, NvU8 *, NvU16 *);
static acpi_handle nvif_handle = NULL;
static acpi_handle wmmx_handle = NULL;
// Used for AC Power Source Hotplug Handling
static acpi_handle psr_handle = NULL;
static acpi_handle psr_device_handle = NULL;
static nv_acpi_t *psr_nv_acpi_object = NULL;
static NvBool battery_present = NV_FALSE;
#define BIX_BATTERY_TECHNOLOGY_OFFSET 0x4
#define BIF_BATTERY_TECHNOLOGY_OFFSET 0x3
#define BATTERY_RECHARGABLE 0x1
/* Moved into acpi/video.h in Linux 4.10 */
#ifndef ACPI_VIDEO_NOTIFY_PROBE
#define ACPI_VIDEO_NOTIFY_PROBE 0x81
#endif
/* Added to acpi/video.h in Linux 3.1 */
#ifndef ACPI_VIDEO_CLASS
#define ACPI_VIDEO_CLASS "video"
#endif
static int nv_acpi_get_device_handle(nv_state_t *nv, acpi_handle *dev_handle)
{
nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
#if defined(DEVICE_ACPI_HANDLE)
*dev_handle = DEVICE_ACPI_HANDLE(nvl->dev);
return NV_TRUE;
#elif defined (ACPI_HANDLE)
*dev_handle = ACPI_HANDLE(nvl->dev);
return NV_TRUE;
#else
return NV_FALSE;
#endif
}
static int nv_acpi_notify(struct notifier_block *nb,
unsigned long val, void *data)
{
struct acpi_bus_event *info = data;
nv_stack_t *sp = NULL;
nv_linux_state_t *nvl = container_of(nb, nv_linux_state_t, acpi_nb);
nv_state_t *nv = NV_STATE_PTR(nvl);
if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) {
if (nv_kmem_cache_alloc_stack(&sp) == 0) {
/*
* Function to handle device specific ACPI events
* such as display hotplug and D-notifier events.
*/
rm_acpi_notify(sp, nv, info->type);
nv_kmem_cache_free_stack(sp);
}
else
nv_printf(NV_DBG_ERRORS,
"NVRM: nv_acpi_notify: failed to allocate stack\n");
/*
* Special case for ACPI_VIDEO_NOTIFY_PROBE event: intentionally return
* NOTIFY_BAD to inform acpi-video to stop generating keypresses for
* this event.
*/
if (info->type == ACPI_VIDEO_NOTIFY_PROBE) {
return NOTIFY_BAD;
}
}
return NOTIFY_DONE;
}
void nv_acpi_register_notifier(nv_linux_state_t *nvl)
{
nvl->acpi_nb.notifier_call = nv_acpi_notify;
register_acpi_notifier(&nvl->acpi_nb);
}
void nv_acpi_unregister_notifier(nv_linux_state_t *nvl)
{
unregister_acpi_notifier(&nvl->acpi_nb);
}
NV_STATUS NV_API_CALL nv_acpi_get_powersource(NvU32 *ac_plugged)
{
unsigned long long val;
int status = 0;
if (!ac_plugged)
return NV_ERR_INVALID_ARGUMENT;
if (!psr_device_handle)
return NV_ERR_INVALID_ARGUMENT;
// Check whether or not AC power is plugged in
status = acpi_evaluate_integer(psr_device_handle, "_PSR", NULL, &val);
if (ACPI_FAILURE(status))
return NV_ERR_GENERIC;
// AC Power Source Plug State
// - 0x0 unplugged
// - 0x1 plugged
*ac_plugged = (val == 0x1);
return NV_OK;
}
#define ACPI_POWER_SOURCE_CHANGE_EVENT 0x80
static void nv_acpi_powersource_hotplug_event(acpi_handle handle, u32 event_type, void *data)
{
/*
* This function will handle acpi events from the linux kernel, used
* to detect notifications from Power Source device
*/
nv_acpi_t *pNvAcpiObject = data;
u32 ac_plugged = 0;
if (event_type == ACPI_POWER_SOURCE_CHANGE_EVENT)
{
if (nv_acpi_get_powersource(&ac_plugged) != NV_OK)
return;
rm_system_event(pNvAcpiObject->sp, NV_SYSTEM_ACPI_BATTERY_POWER_EVENT, !ac_plugged);
}
}
/*
* End of ACPI event handler functions
*/
/* Do the necessary allocations and install notifier "handler" on the device-node "device" */
static nv_acpi_t* nv_install_notifier(struct acpi_handle *handle, acpi_notify_handler handler)
{
nvidia_stack_t *sp = NULL;
nv_acpi_t *pNvAcpiObject = NULL;
NV_STATUS rmStatus = NV_ERR_GENERIC;
acpi_status status = -1;
if (!handle)
return NULL;
if (nv_kmem_cache_alloc_stack(&sp) != 0)
{
return NULL;
}
rmStatus = os_alloc_mem((void **) &pNvAcpiObject, sizeof(nv_acpi_t));
if (rmStatus != NV_OK)
goto return_error;
os_mem_set((void *)pNvAcpiObject, 0, sizeof(nv_acpi_t));
// store a handle reference in our object
pNvAcpiObject->handle = handle;
pNvAcpiObject->sp = sp;
status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY,
handler, pNvAcpiObject);
if (!ACPI_FAILURE(status))
{
pNvAcpiObject->notify_handler_installed = 1;
return pNvAcpiObject;
}
return_error:
nv_kmem_cache_free_stack(sp);
if (pNvAcpiObject)
os_free_mem((void *)pNvAcpiObject);
return NULL;
}
/* Tear-down and remove whatever nv_install_notifier did */
static void nv_uninstall_notifier(nv_acpi_t *pNvAcpiObject, acpi_notify_handler handler)
{
acpi_status status;
if (pNvAcpiObject && pNvAcpiObject->notify_handler_installed)
{
status = acpi_remove_notify_handler(pNvAcpiObject->handle, ACPI_DEVICE_NOTIFY, handler);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,
"NVRM: nv_acpi_methods_uninit: failed to remove event notification handler (%d)!\n", status);
}
else
{
nv_kmem_cache_free_stack(pNvAcpiObject->sp);
os_free_mem((void *)pNvAcpiObject);
}
}
return;
}
/*
* acpi methods init function.
* check if the NVIF, _DSM and WMMX methods are present in the acpi namespace.
* store NVIF, _DSM and WMMX handle if found.
*/
void NV_API_CALL nv_acpi_methods_init(NvU32 *handlesPresent)
{
if (!handlesPresent) // Caller passed us invalid pointer.
return;
*handlesPresent = 0;
NV_ACPI_WALK_NAMESPACE(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, nv_acpi_find_methods, NULL, NULL);
if (nvif_handle)
{
*handlesPresent = NV_ACPI_NVIF_HANDLE_PRESENT;
}
if (wmmx_handle)
*handlesPresent = *handlesPresent | NV_ACPI_WMMX_HANDLE_PRESENT;
if (psr_handle)
{
// Since _PSR is not a per-GPU construct we only need to register a
// single notifier for the _PSR event. Skip registration for subsequent
// devices
if (psr_nv_acpi_object == NULL)
{
psr_nv_acpi_object = nv_install_notifier(psr_device_handle, nv_acpi_powersource_hotplug_event);
}
}
return;
}
acpi_status nv_acpi_find_methods(
acpi_handle handle,
u32 nest_level,
void *dummy1,
void **dummy2
)
{
acpi_handle method_handle;
if (!acpi_get_handle(handle, "NVIF", &method_handle))
{
nvif_handle = method_handle;
}
if (!acpi_get_handle(handle, "WMMX", &method_handle))
{
wmmx_handle = method_handle;
}
if (!acpi_get_handle(handle, "_PSR", &method_handle))
{
psr_handle = method_handle;
psr_device_handle = handle;
}
return 0;
}
void NV_API_CALL nv_acpi_methods_uninit(void)
{
nvif_handle = NULL;
wmmx_handle = NULL;
if (psr_nv_acpi_object != NULL)
{
nv_uninstall_notifier(psr_nv_acpi_object, nv_acpi_powersource_hotplug_event);
psr_handle = NULL;
psr_device_handle = NULL;
psr_nv_acpi_object = NULL;
}
}
static NV_STATUS nv_acpi_extract_integer(
const union acpi_object *acpi_object,
void *buffer,
NvU32 buffer_size,
NvU32 *data_size
)
{
if (acpi_object->type != ACPI_TYPE_INTEGER)
return NV_ERR_INVALID_ARGUMENT;
if (acpi_object->integer.value & ~0xffffffffULL)
*data_size = sizeof(acpi_object->integer.value);
else
*data_size = sizeof(NvU32);
if ((buffer_size < sizeof(NvU32)) ||
((buffer_size < sizeof(acpi_object->integer.value)) &&
(acpi_object->integer.value & ~0xffffffffULL)))
{
return NV_ERR_BUFFER_TOO_SMALL;
}
memcpy(buffer, &acpi_object->integer.value, *data_size);
return NV_OK;
}
static NV_STATUS nv_acpi_extract_buffer(
const union acpi_object *acpi_object,
void *buffer,
NvU32 buffer_size,
NvU32 *data_size
)
{
if (acpi_object->type != ACPI_TYPE_BUFFER)
return NV_ERR_INVALID_ARGUMENT;
*data_size = acpi_object->buffer.length;
if (buffer_size < acpi_object->buffer.length)
return NV_ERR_BUFFER_TOO_SMALL;
memcpy(buffer, acpi_object->buffer.pointer, *data_size);
return NV_OK;
}
static NV_STATUS nv_acpi_extract_package(
const union acpi_object *acpi_object,
void *buffer,
NvU32 buffer_size,
NvU32 *data_size
)
{
NV_STATUS status = NV_OK;
NvU32 i, element_size = 0;
if (acpi_object->type != ACPI_TYPE_PACKAGE)
return NV_ERR_INVALID_ARGUMENT;
*data_size = 0;
for (i = 0; i < acpi_object->package.count; i++)
{
buffer = ((char *)buffer + element_size);
buffer_size -= element_size;
status = nv_acpi_extract_object(&acpi_object->package.elements[i],
buffer, buffer_size, &element_size);
if (status != NV_OK)
break;
*data_size += element_size;
}
return status;
}
static NV_STATUS nv_acpi_extract_object(
const union acpi_object *acpi_object,
void *buffer,
NvU32 buffer_size,
NvU32 *data_size
)
{
NV_STATUS status;
switch (acpi_object->type)
{
case ACPI_TYPE_INTEGER:
status = nv_acpi_extract_integer(acpi_object, buffer,
buffer_size, data_size);
break;
case ACPI_TYPE_BUFFER:
status = nv_acpi_extract_buffer(acpi_object, buffer,
buffer_size, data_size);
break;
case ACPI_TYPE_PACKAGE:
status = nv_acpi_extract_package(acpi_object, buffer,
buffer_size, data_size);
break;
case ACPI_TYPE_ANY:
/*
* ACPI_TYPE_ANY is used to represent a NULL/Uninitialized object which is objectType 0
* in the ACPI SPEC. This should not be treated as error.
*/
status = NV_OK;
break;
default:
status = NV_ERR_NOT_SUPPORTED;
}
return status;
}
NV_STATUS NV_API_CALL nv_acpi_method(
NvU32 acpi_method,
NvU32 function,
NvU32 subFunction,
void *inParams,
NvU16 inParamSize,
NvU32 *outStatus,
void *outData,
NvU16 *outDataSize
)
{
NV_STATUS status;
switch (acpi_method)
{
case NV_EVAL_ACPI_METHOD_NVIF:
status = nv_acpi_nvif_method(function,
subFunction,
inParams,
inParamSize,
outStatus,
outData,
outDataSize);
break;
case NV_EVAL_ACPI_METHOD_WMMX:
status = nv_acpi_wmmx_method(function, outData, outDataSize);
break;
default:
status = NV_ERR_NOT_SUPPORTED;
}
return status;
}
/*
* This function executes an NVIF ACPI method.
*/
static NV_STATUS nv_acpi_nvif_method(
NvU32 function,
NvU32 subFunction,
void *inParams,
NvU16 inParamSize,
NvU32 *outStatus,
void *outData,
NvU16 *outDataSize
)
{
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *nvif = NULL;
union acpi_object nvif_params[3];
NvU16 localOutDataSize;
NvU8 localInParams[8];
if (!nvif_handle)
return NV_ERR_NOT_SUPPORTED;
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_ERRORS,
"NVRM: nv_acpi_nvif_method: invalid context!\n");
#endif
return NV_ERR_NOT_SUPPORTED;
}
nvif_params[0].integer.type = ACPI_TYPE_INTEGER;
nvif_params[0].integer.value = function;
nvif_params[1].integer.type = ACPI_TYPE_INTEGER;
nvif_params[1].integer.value = subFunction;
nvif_params[2].buffer.type = ACPI_TYPE_BUFFER;
if (inParams && (inParamSize > 0))
{
nvif_params[2].buffer.length = inParamSize;
nvif_params[2].buffer.pointer = inParams;
}
else
{
memset(localInParams, 0, 8);
nvif_params[2].buffer.length = 8;
nvif_params[2].buffer.pointer = localInParams;
}
input.count = 3;
input.pointer = nvif_params;
status = acpi_evaluate_object(nvif_handle, NULL, &input, &output);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,
"NVRM: nv_acpi_nvif_method: failed to get NVIF data, "
"status 0x%x, function 0x%x, subFunction 0x%x!\n",
status, function, subFunction);
return NV_ERR_GENERIC;
}
nvif = output.pointer;
if (nvif && (nvif->type == ACPI_TYPE_BUFFER) && (nvif->buffer.length >= 4))
{
if (outStatus)
{
*outStatus = nvif->buffer.pointer[3] << 24 |
nvif->buffer.pointer[2] << 16 |
nvif->buffer.pointer[1] << 8 |
nvif->buffer.pointer[0];
}
if (outData && outDataSize)
{
localOutDataSize = nvif->buffer.length - 4;
if (localOutDataSize <= *outDataSize)
{
*outDataSize = NV_MIN(*outDataSize, localOutDataSize);
memcpy(outData, &nvif->buffer.pointer[4], *outDataSize);
}
else
{
*outDataSize = localOutDataSize;
kfree(output.pointer);
return NV_ERR_BUFFER_TOO_SMALL;
}
}
}
else
{
nv_printf(NV_DBG_INFO,
"NVRM: nv_acpi_nvif_method: NVIF data invalid, function 0x%x, "
"subFunction 0x%x!\n", function, subFunction);
kfree(output.pointer);
return NV_ERR_GENERIC;
}
kfree(output.pointer);
return NV_OK;
}
#define MAX_INPUT_PARAM_SIZE 1024
/*
* This function executes a _DSM ACPI method.
*/
NV_STATUS NV_API_CALL nv_acpi_dsm_method(
nv_state_t *nv,
NvU8 *pAcpiDsmGuid,
NvU32 acpiDsmRev,
NvBool acpiNvpcfDsmFunction,
NvU32 acpiDsmSubFunction,
void *pInParams,
NvU16 inParamSize,
NvU32 *outStatus,
void *pOutData,
NvU16 *pSize
)
{
NV_STATUS status = NV_ERR_OPERATING_SYSTEM;
acpi_status acpi_status;
struct acpi_object_list input;
union acpi_object *dsm = NULL;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object dsm_params[4];
NvU8 *argument3 = NULL;
NvU32 data_size;
acpi_handle dev_handle = NULL;
if (!nv_acpi_get_device_handle(nv, &dev_handle))
return NV_ERR_NOT_SUPPORTED;
if (!dev_handle)
return NV_ERR_INVALID_ARGUMENT;
if ((!pInParams) || (inParamSize > MAX_INPUT_PARAM_SIZE) || (!pOutData) || (!pSize))
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: invalid argument(s)!\n", __FUNCTION__);
return NV_ERR_INVALID_ARGUMENT;
}
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_INFO,
"NVRM: %s: invalid argument(s)!\n", __FUNCTION__);
#endif
return NV_ERR_NOT_SUPPORTED;
}
status = os_alloc_mem((void **)&argument3, inParamSize);
if (status != NV_OK)
return status;
//
// dsm_params[0].buffer.pointer and dsm_params[1].integer.value set in
// switch below based on acpiDsmFunction
//
dsm_params[0].buffer.type = ACPI_TYPE_BUFFER;
dsm_params[0].buffer.length = 0x10;
dsm_params[0].buffer.pointer = pAcpiDsmGuid;
dsm_params[1].integer.type = ACPI_TYPE_INTEGER;
dsm_params[1].integer.value = acpiDsmRev;
dsm_params[2].integer.type = ACPI_TYPE_INTEGER;
dsm_params[2].integer.value = acpiDsmSubFunction;
dsm_params[3].buffer.type = ACPI_TYPE_BUFFER;
dsm_params[3].buffer.length = inParamSize;
memcpy(argument3, pInParams, dsm_params[3].buffer.length);
dsm_params[3].buffer.pointer = argument3;
// parameters for dsm calls (GUID, rev, subfunction, data)
input.count = 4;
input.pointer = dsm_params;
if (acpiNvpcfDsmFunction)
{
//
// acpi_evaluate_object() can operate with either valid object pathname or
// valid object handle. For NVPCF DSM function, use valid pathname as we do
// not have device handle for NVPCF device
//
dev_handle = NULL;
acpi_status = acpi_evaluate_object(dev_handle, "\\_SB.NPCF._DSM", &input, &output);
}
else
{
acpi_status = acpi_evaluate_object(dev_handle, "_DSM", &input, &output);
}
if (ACPI_FAILURE(acpi_status))
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: failed to evaluate _DSM method!\n", __FUNCTION__);
goto exit;
}
dsm = output.pointer;
if (dsm != NULL)
{
if (outStatus)
{
*outStatus = dsm->buffer.pointer[3] << 24 |
dsm->buffer.pointer[2] << 16 |
dsm->buffer.pointer[1] << 8 |
dsm->buffer.pointer[0];
}
status = nv_acpi_extract_object(dsm, pOutData, *pSize, &data_size);
*pSize = data_size;
kfree(output.pointer);
}
if (status != NV_OK)
{
nv_printf(NV_DBG_ERRORS,
"NVRM: %s: DSM data invalid!\n", __FUNCTION__);
}
exit:
os_free_mem(argument3);
return status;
}
/*
* This function executes a _DDC ACPI method.
*/
NV_STATUS NV_API_CALL nv_acpi_ddc_method(
nv_state_t *nv,
void *pEdidBuffer,
NvU32 *pSize,
NvBool bReadMultiBlock
)
{
acpi_status status;
union acpi_object *ddc = NULL;
NvU32 i, largestEdidSize;
acpi_handle dev_handle = NULL;
acpi_handle lcd_dev_handle = NULL;
acpi_handle handle = NULL;
if (!nv_acpi_get_device_handle(nv, &dev_handle))
return NV_ERR_NOT_SUPPORTED;
if (!dev_handle)
return NV_ERR_INVALID_ARGUMENT;
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_ERRORS,
"NVRM: %s: invalid context!\n",
__FUNCTION__);
#endif
return NV_ERR_NOT_SUPPORTED;
}
while (lcd_dev_handle == NULL)
{
unsigned long long device_id = 0;
status = acpi_get_next_object(ACPI_TYPE_DEVICE, dev_handle,
handle, &handle);
if (ACPI_FAILURE(status) || (handle == NULL))
break;
status = acpi_evaluate_integer(handle, "_ADR", NULL, &device_id);
if (ACPI_FAILURE(status))
/* Couldnt query device_id for this device */
continue;
switch (device_id & 0xffff) {
case 0x0110:
case 0x0118:
case 0x0400:
case 0xA420:
lcd_dev_handle = handle;
nv_printf(NV_DBG_INFO, "NVRM: %s Found LCD: %x\n",
__FUNCTION__, device_id);
break;
default:
break;
}
}
if (lcd_dev_handle == NULL)
{
nv_printf(NV_DBG_INFO, "NVRM: %s LCD not found\n", __FUNCTION__);
return NV_ERR_GENERIC;
}
//
// As per ACPI Spec 3.0:
// ARG0 = 0x1 for 128 bytes edid buffer
// ARG0 = 0x2 for 256 bytes edid buffer
//
largestEdidSize = bReadMultiBlock ? 2 : 1;
for (i = largestEdidSize; i >= 1; i--)
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object ddc_arg0 = { ACPI_TYPE_INTEGER };
struct acpi_object_list input = { 1, &ddc_arg0 };
ddc_arg0.integer.value = i;
status = acpi_evaluate_object(lcd_dev_handle, "_DDC", &input, &output);
if (ACPI_SUCCESS(status)) {
ddc = output.pointer;
break;
}
}
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: failed status: %08x \n",
__FUNCTION__,
status);
return NV_ERR_GENERIC;
}
else
{
if (ddc && (ddc->type == ACPI_TYPE_BUFFER) && (ddc->buffer.length > 0))
{
if (ddc->buffer.length <= *pSize)
{
*pSize = NV_MIN(*pSize, ddc->buffer.length);
memcpy(pEdidBuffer, ddc->buffer.pointer, *pSize);
}
else
{
kfree(ddc);
return NV_ERR_BUFFER_TOO_SMALL;
}
}
}
kfree(ddc);
return NV_OK;
}
/*
* This function executes a _ROM ACPI method.
*/
NV_STATUS NV_API_CALL nv_acpi_rom_method(
nv_state_t *nv,
NvU32 *pInData,
NvU32 *pOutData
)
{
acpi_status status;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *rom;
union acpi_object rom_arg[2];
struct acpi_object_list input = { 2, rom_arg };
acpi_handle dev_handle = NULL;
uint32_t offset, length;
if (!nv_acpi_get_device_handle(nv, &dev_handle))
return NV_ERR_NOT_SUPPORTED;
if (!dev_handle)
return NV_ERR_INVALID_ARGUMENT;
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_ERRORS,
"NVRM: %s: invalid context!\n", __FUNCTION__);
#endif
return NV_ERR_NOT_SUPPORTED;
}
offset = pInData[0];
length = pInData[1];
rom_arg[0].type = ACPI_TYPE_INTEGER;
rom_arg[0].integer.value = offset;
rom_arg[1].type = ACPI_TYPE_INTEGER;
rom_arg[1].integer.value = length;
status = acpi_evaluate_object(dev_handle, "_ROM", &input, &output);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: failed to evaluate _ROM method!\n", __FUNCTION__);
return NV_ERR_GENERIC;
}
else
{
rom = output.pointer;
if ((rom != NULL) && (rom->type == ACPI_TYPE_BUFFER) &&
(rom->buffer.length >= length))
{
memcpy(pOutData, rom->buffer.pointer, length);
}
else
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: Invalid _ROM data\n", __FUNCTION__);
kfree(output.pointer);
return NV_ERR_GENERIC;
}
}
kfree(output.pointer);
return NV_OK;
}
/*
* This function executes a _DOD ACPI method.
*/
NV_STATUS NV_API_CALL nv_acpi_dod_method(
nv_state_t *nv,
NvU32 *pOutData,
NvU32 *pSize
)
{
acpi_status status;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *dod;
acpi_handle dev_handle = NULL;
NvU32 i, count = (*pSize / sizeof(NvU32));
if (!nv_acpi_get_device_handle(nv, &dev_handle))
return NV_ERR_NOT_SUPPORTED;
if (!dev_handle)
return NV_ERR_INVALID_ARGUMENT;
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_ERRORS,
"NVRM: %s: invalid context!\n", __FUNCTION__);
#endif
return NV_ERR_NOT_SUPPORTED;
}
status = acpi_evaluate_object(dev_handle, "_DOD", NULL, &output);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: failed to evaluate _DOD method!\n", __FUNCTION__);
return NV_ERR_GENERIC;
}
else
{
dod = output.pointer;
*pSize = 0;
if ((dod != NULL) && (dod->type == ACPI_TYPE_PACKAGE) &&
(dod->package.count <= count))
{
for (i = 0; i < dod->package.count; i++)
{
if (dod->package.elements[i].type != ACPI_TYPE_INTEGER)
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: _DOD entry invalid!\n", __FUNCTION__);
kfree(output.pointer);
return NV_ERR_GENERIC;
}
pOutData[i] = dod->package.elements[i].integer.value;
*pSize += sizeof(NvU32);
}
}
else
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: _DOD data too large!\n", __FUNCTION__);
kfree(output.pointer);
return NV_ERR_GENERIC;
}
}
kfree(output.pointer);
return NV_OK;
}
/*
* This function executes a WMMX ACPI method.
*/
static NV_STATUS nv_acpi_wmmx_method(
NvU32 arg2,
NvU8 *outData,
NvU16 *outDataSize
)
{
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *mmx = NULL;
union acpi_object mmx_params[3];
if (!wmmx_handle)
return NV_ERR_NOT_SUPPORTED;
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_ERRORS,
"NVRM: nv_acpi_wmmx_method: invalid context!\n");
#endif
return NV_ERR_NOT_SUPPORTED;
}
/* argument 0 and argument 1 are not used in WMMX method, passing 0 */
mmx_params[0].integer.type = ACPI_TYPE_INTEGER;
mmx_params[0].integer.value = 0;
mmx_params[1].integer.type = ACPI_TYPE_INTEGER;
mmx_params[1].integer.value = 0;
mmx_params[2].integer.type = ACPI_TYPE_INTEGER;
mmx_params[2].integer.value = arg2;
input.count = 3;
input.pointer = mmx_params;
status = acpi_evaluate_object(wmmx_handle, NULL, &input, &output);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,
"NVRM: nv_acpi_wmmx_method: failed to get WMMX data, "
"status 0x%x!\n", status);
return NV_ERR_GENERIC;
}
mmx = output.pointer;
if (mmx && (mmx->type == ACPI_TYPE_BUFFER) && (mmx->buffer.length > 0))
{
if (outData && outDataSize)
{
if (mmx->buffer.length <= *outDataSize)
{
*outDataSize = NV_MIN(*outDataSize, mmx->buffer.length);
memcpy(outData, mmx->buffer.pointer, *outDataSize);
}
else
{
kfree(output.pointer);
return NV_ERR_BUFFER_TOO_SMALL;
}
}
}
else
{
nv_printf(NV_DBG_ERRORS,
"NVRM: nv_acpi_wmmx_method: WMMX data invalid.\n");
kfree(output.pointer);
return NV_ERR_GENERIC;
}
kfree(output.pointer);
return NV_OK;
}
NvBool nv_acpi_power_resource_method_present(
struct pci_dev *pdev
)
{
acpi_handle handle = NULL;
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *object_package, *object_reference;
acpi_status status;
#if defined(DEVICE_ACPI_HANDLE)
handle = DEVICE_ACPI_HANDLE(&pdev->dev);
#elif defined (ACPI_HANDLE)
handle = ACPI_HANDLE(&pdev->dev);
#endif
if (!handle)
return NV_FALSE;
status = acpi_evaluate_object(handle, "_PR3", NULL, &buf);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO,"NVRM: Failed to evaluate _PR3 object\n");
return NV_FALSE;
}
if (!buf.pointer)
{
nv_printf(NV_DBG_INFO, "NVRM: output buffer pointer is null"
" for _PR3 method\n");
return NV_FALSE;
}
object_package = buf.pointer;
/*
* _PR3 object should be of type package and
* it should contain only one reference
*/
if ((object_package->type != ACPI_TYPE_PACKAGE) &&
(object_package->package.count != 0x1))
{
nv_printf(NV_DBG_ERRORS,"NVRM: _PR3 object is not a type 'package'\n");
return NV_FALSE;
}
object_reference = object_package->package.elements;
/* Check for the reference and the actual type of the reference. */
if ((object_reference->reference.actual_type != ACPI_TYPE_POWER) &&
(object_reference->type != ACPI_TYPE_LOCAL_REFERENCE))
{
nv_printf(NV_DBG_ERRORS,
"NVRM: _PR3 object does not contain POWER Reference\n");
return NV_FALSE;
}
return NV_TRUE;
}
/*
* This function executes MUX ACPI methods.
*/
NV_STATUS NV_API_CALL nv_acpi_mux_method(
nv_state_t *nv,
NvU32 *pInOut,
NvU32 muxAcpiId,
const char *pMethodName
)
{
acpi_status status;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *mux = NULL;
union acpi_object mux_arg = { ACPI_TYPE_INTEGER };
struct acpi_object_list input = { 1, &mux_arg };
acpi_handle dev_handle = NULL;
acpi_handle mux_dev_handle = NULL;
acpi_handle handle = NULL;
unsigned long long device_id = 0;
if ((strcmp(pMethodName, "MXDS") != 0)
&& (strcmp(pMethodName, "MXDM") != 0))
{
nv_printf(NV_DBG_ERRORS, "NVRM: %s: Unsupported ACPI method %s\n",
__FUNCTION__, pMethodName);
return NV_ERR_NOT_SUPPORTED;
}
else
{
nv_printf(NV_DBG_INFO, "NVRM: %s: Call for %s ACPI method \n",
__FUNCTION__, pMethodName);
}
if (!nv_acpi_get_device_handle(nv, &dev_handle))
return NV_ERR_NOT_SUPPORTED;
if (!dev_handle)
return NV_ERR_INVALID_ARGUMENT;
if (!NV_MAY_SLEEP())
{
#if defined(DEBUG)
nv_printf(NV_DBG_ERRORS, "NVRM: %s: invalid context!\n", __FUNCTION__);
#endif
return NV_ERR_NOT_SUPPORTED;
}
while (mux_dev_handle == NULL)
{
status = acpi_get_next_object(ACPI_TYPE_DEVICE, dev_handle,
handle, &handle);
if (ACPI_FAILURE(status) || (handle == NULL))
break;
status = acpi_evaluate_integer(handle, "_ADR", NULL, &device_id);
if (ACPI_SUCCESS(status) && (device_id == muxAcpiId))
mux_dev_handle = handle;
}
if (mux_dev_handle == NULL)
{
nv_printf(NV_DBG_INFO,
"NVRM: %s Mux device handle not found\n", __FUNCTION__);
return NV_ERR_GENERIC;
}
mux_arg.integer.type = ACPI_TYPE_INTEGER;
mux_arg.integer.value = (NvU64) *pInOut;
status = acpi_evaluate_object(mux_dev_handle, (acpi_string)pMethodName,
&input, &output);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO, "NVRM: %s: Failed to evaluate %s method!\n",
__FUNCTION__, pMethodName);
return NV_ERR_GENERIC;
}
else
{
mux = output.pointer;
if (mux && (mux->type == ACPI_TYPE_INTEGER))
{
*pInOut = mux->integer.value;
}
else
{
nv_printf(NV_DBG_INFO,
"NVRM: %s: Invalid MUX data\n", __FUNCTION__);
kfree(output.pointer);
return NV_ERR_GENERIC;
}
}
kfree(output.pointer);
return NV_OK;
}
static acpi_status nv_acpi_find_battery_info(
acpi_handle handle,
NvBool bUseBix
)
{
acpi_status status = AE_OK;
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *object_package;
NvU32 battery_technology_offset;
status = acpi_evaluate_object(handle, NULL, NULL, &buf);
if (ACPI_FAILURE(status))
{
nv_printf(NV_DBG_INFO, "NVRM: Failed to evaluate battery's object\n");
return AE_OK;
}
if (!buf.pointer)
{
nv_printf(NV_DBG_INFO, "NVRM: Battery object output buffer is null\n");
return AE_OK;
}
object_package = buf.pointer;
if (object_package->type != ACPI_TYPE_PACKAGE)
{
nv_printf(NV_DBG_INFO, "NVRM: Battery method output is not package\n");
return AE_OK;
}
if (bUseBix)
{
battery_technology_offset = BIX_BATTERY_TECHNOLOGY_OFFSET;
}
else
{
battery_technology_offset = BIF_BATTERY_TECHNOLOGY_OFFSET;
}
/*
* Only checking here for Battery technology type.
* Other fields like Battery Model/Serial number could also be checked but
* driver need to support the case where user has removed battery from the
* system.
* _STA method on the battery device handle couldn't be used due to the same
* reason.
* Hence just cheking if battery technology of slot is rechargable or not.
*/
if ((object_package->package.elements[battery_technology_offset].type != ACPI_TYPE_INTEGER) ||
(object_package->package.elements[battery_technology_offset].integer.value != BATTERY_RECHARGABLE))
{
return AE_OK;
}
battery_present = NV_TRUE;
/* Stop traversing acpi tree. */
return AE_CTRL_TERMINATE;
}
static acpi_status nv_acpi_find_battery_device(
acpi_handle handle,
u32 nest_level,
void *dummy1,
void **dummy2
)
{
acpi_handle bif_method_handle;
acpi_handle bix_method_handle;
acpi_status status = AE_OK;
// Find method Battery Information /Extended/ (_BIX or _BIF) and then Battery type.
if (!acpi_get_handle(handle, "_BIX", &bix_method_handle))
{
status = nv_acpi_find_battery_info(bix_method_handle, NV_TRUE/*bUseBix*/);
}
if ((battery_present == NV_FALSE) &&
!acpi_get_handle(handle, "_BIF", &bif_method_handle))
{
status = nv_acpi_find_battery_info(bif_method_handle, NV_FALSE/*bUseBix*/);
}
return status;
}
NvBool NV_API_CALL nv_acpi_is_battery_present(void)
{
NV_ACPI_WALK_NAMESPACE(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX,
nv_acpi_find_battery_device, NULL, NULL);
if (battery_present == NV_TRUE)
{
return NV_TRUE;
}
return NV_FALSE;
}
#else // NV_LINUX_ACPI_EVENTS_SUPPORTED
void NV_API_CALL nv_acpi_methods_init(NvU32 *handlePresent)
{
*handlePresent = 0;
}
void NV_API_CALL nv_acpi_methods_uninit(void)
{
return;
}
NV_STATUS NV_API_CALL nv_acpi_method(
NvU32 acpi_method,
NvU32 function,
NvU32 subFunction,
void *inParams,
NvU16 inParamSize,
NvU32 *outStatus,
void *outData,
NvU16 *outDataSize
)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS NV_API_CALL nv_acpi_dsm_method(
nv_state_t *nv,
NvU8 *pAcpiDsmGuid,
NvU32 acpiDsmRev,
NvBool acpiNvpcfDsmFunction,
NvU32 acpiDsmSubFunction,
void *pInParams,
NvU16 inParamSize,
NvU32 *outStatus,
void *pOutData,
NvU16 *pSize
)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS NV_API_CALL nv_acpi_ddc_method(
nv_state_t *nv,
void *pEdidBuffer,
NvU32 *pSize,
NvBool bReadMultiBlock
)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS NV_API_CALL nv_acpi_rom_method(
nv_state_t *nv,
NvU32 *pInData,
NvU32 *pOutData
)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS NV_API_CALL nv_acpi_dod_method(
nv_state_t *nv,
NvU32 *pOutData,
NvU32 *pSize
)
{
return NV_ERR_NOT_SUPPORTED;
}
NvBool nv_acpi_power_resource_method_present(
struct pci_dev *pdev
)
{
return NV_FALSE;
}
NV_STATUS NV_API_CALL nv_acpi_get_powersource(NvU32 *ac_plugged)
{
return NV_ERR_NOT_SUPPORTED;
}
void nv_acpi_register_notifier(nv_linux_state_t *nvl)
{
return;
}
void nv_acpi_unregister_notifier(nv_linux_state_t *nvl)
{
return;
}
NV_STATUS NV_API_CALL nv_acpi_mux_method(
nv_state_t *nv,
NvU32 *pInOut,
NvU32 muxAcpiId,
const char *pMethodName
)
{
return NV_ERR_NOT_SUPPORTED;
}
NvBool NV_API_CALL nv_acpi_is_battery_present(void)
{
return NV_FALSE;
}
#endif