⛏️ index : haiku.git

/*
 * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
 * Distributed under the terms of the MIT license.
 */


#define DRIVER_NAME "wmi_acpi"
#include "WMIPrivate.h"


#define ACPI_NAME_ACPI_WMI "PNP0C14"

#define ACPI_WMI_REGFLAG_EXPENSIVE	(1 << 0)
#define ACPI_WMI_REGFLAG_METHOD		(1 << 1)
#define ACPI_WMI_REGFLAG_STRING		(1 << 2)
#define ACPI_WMI_REGFLAG_EVENT		(1 << 3)


device_manager_info *gDeviceManager;
smbios_module_info *gSMBios;


acpi_status wmi_acpi_adr_space_handler(uint32 function,
	acpi_physical_address address, uint32 bitWidth, int *value,
	void *handlerContext, void *regionContext)
{
	return B_OK;
}


WMIACPI::WMIACPI(device_node *node)
	:
	fNode(node)
{
	CALLED();

	device_node *parent;
	parent = gDeviceManager->get_parent_node(node);
	gDeviceManager->get_driver(parent, (driver_module_info **)&acpi,
		(void **)&acpi_cookie);
	gDeviceManager->get_attr_string(parent, ACPI_DEVICE_UID_ITEM, &fUid,
		false);
	gDeviceManager->put_node(parent);

	// install notify handler
	fStatus = acpi->install_notify_handler(acpi_cookie,
		ACPI_ALL_NOTIFY, _NotifyHandler, this);
	if (fStatus != B_OK) {
		ERROR("install_notify_handler failed\n");
		return;
	}

	fStatus = acpi->install_address_space_handler(acpi_cookie,
		ACPI_ADR_SPACE_EC, wmi_acpi_adr_space_handler, NULL, this);
	if (fStatus != B_OK) {
		ERROR("wmi_acpi_adr_space_handler failed\n");
		return;
	}

	acpi_data buffer = {ACPI_ALLOCATE_BUFFER, NULL};
	fStatus = acpi->evaluate_method(acpi_cookie, "_WDG", NULL, &buffer);
	if (fStatus != B_OK) {
		ERROR("Method call _WDG failed\n");
		return;
	}

	acpi_object_type* object = (acpi_object_type*)buffer.pointer;
	fWMIInfoCount = object->buffer.length / sizeof(guid_info);
	guid_info *info = (guid_info*)object->buffer.buffer;
	fWMIInfos = (wmi_info *)calloc(fWMIInfoCount, sizeof(wmi_info));
	TRACE("found %" B_PRIu32 " objects\n", fWMIInfoCount);
	for (uint32 i = 0; i < fWMIInfoCount; i++, info++) {
		wmi_info *wmi = &fWMIInfos[i];
		wmi->guid = *info;
		fList.Add(wmi);
	}
	free(object);
}


WMIACPI::~WMIACPI()
{
	free(fWMIInfos);

	acpi->remove_notify_handler(acpi_cookie,
		ACPI_ALL_NOTIFY, _NotifyHandler);
}


status_t
WMIACPI::InitCheck()
{
	return fStatus;
}


status_t
WMIACPI::Scan()
{
	CALLED();
	status_t status;
	wmi_info* wmiInfo = NULL;
	uint32 index = 0;
	for (WMIInfoList::Iterator it = fList.GetIterator();
			(wmiInfo = it.Next()) != NULL; index++) {
		uint8* guid = wmiInfo->guid.guid;
		char guidString[37] = {};
		_GuidToGuidString(guid, guidString);
		device_attr attrs[] = {
			// connection
			{ WMI_GUID_STRING_ITEM, B_STRING_TYPE, { .string = guidString }},

			{ WMI_BUS_COOKIE, B_UINT32_TYPE, { .ui32 = index }},

			// description of peripheral drivers
			{ B_DEVICE_BUS, B_STRING_TYPE, { .string = "wmi" }},

			{ B_DEVICE_FLAGS, B_UINT32_TYPE,
				{ .ui32 = B_FIND_MULTIPLE_CHILDREN }},

			{ NULL }
		};

		status = gDeviceManager->register_node(fNode, WMI_DEVICE_MODULE_NAME,
			attrs, NULL, NULL);
		if (status != B_OK)
			return status;
	}

	return B_OK;

}


status_t
WMIACPI::GetBlock(uint32 busCookie, uint8 instance, uint32 methodId,
	acpi_data* out)
{
	CALLED();
	if (busCookie >= fWMIInfoCount)
		return B_BAD_VALUE;
	wmi_info* info = &fWMIInfos[busCookie];
	if ((info->guid.flags & ACPI_WMI_REGFLAG_METHOD) != 0
		|| (info->guid.flags & ACPI_WMI_REGFLAG_EVENT) != 0) {
		return B_BAD_VALUE;
	} else if (instance > info->guid.max_instance)
		return B_BAD_VALUE;

	char method[5] = "WQ";
	strncat(method, info->guid.oid, 2);
	char wcMethod[5] = "WC";
	strncat(wcMethod, info->guid.oid, 2);
	status_t wcStatus = B_OK;
	status_t status = B_OK;

	if ((info->guid.flags & ACPI_WMI_REGFLAG_EXPENSIVE) != 0)
		 wcStatus = _EvaluateMethodSimple(wcMethod, 1);

	acpi_object_type object;
	object.object_type = ACPI_TYPE_INTEGER;
	object.integer.integer = instance;
	acpi_objects objects = { 1, &object};
	TRACE("GetBlock calling %s\n", method);
	status = acpi->evaluate_method(acpi_cookie, method, &objects, out);

	if ((info->guid.flags & ACPI_WMI_REGFLAG_EXPENSIVE) != 0
		&& wcStatus == B_OK) {
		 _EvaluateMethodSimple(wcMethod, 0);
	}

	return status;
}


status_t
WMIACPI::SetBlock(uint32 busCookie, uint8 instance, uint32 methodId,
	const acpi_data* in)
{
	CALLED();
	if (busCookie >= fWMIInfoCount)
		return B_BAD_VALUE;
	wmi_info* info = &fWMIInfos[busCookie];
	if ((info->guid.flags & ACPI_WMI_REGFLAG_METHOD) != 0
		|| (info->guid.flags & ACPI_WMI_REGFLAG_EVENT) != 0) {
		return B_BAD_VALUE;
	} else if (instance > info->guid.max_instance)
		return B_BAD_VALUE;

	char method[5] = "WS";
	strncat(method, info->guid.oid, 2);

	acpi_object_type object[2];
	object[0].object_type = ACPI_TYPE_INTEGER;
	object[0].integer.integer = instance;
	object[1].object_type = ACPI_TYPE_BUFFER;
	if ((info->guid.flags & ACPI_WMI_REGFLAG_STRING) != 0)
		object[1].object_type = ACPI_TYPE_STRING;
	object[1].buffer.buffer = in->pointer;
	object[1].buffer.length = in->length;
	acpi_objects objects = { 2, object};
	TRACE("SetBlock calling %s\n", method);
	return acpi->evaluate_method(acpi_cookie, method, &objects, NULL);
}


status_t
WMIACPI::EvaluateMethod(uint32 busCookie, uint8 instance, uint32 methodId,
	const acpi_data* in, acpi_data* out)
{
	CALLED();
	if (busCookie >= fWMIInfoCount)
		return B_BAD_VALUE;
	wmi_info* info = &fWMIInfos[busCookie];
	if ((info->guid.flags & ACPI_WMI_REGFLAG_METHOD) == 0)
		return B_BAD_VALUE;

	char method[5] = "WM";
	strncat(method, info->guid.oid, 2);

	acpi_object_type object[3];
	object[0].object_type = ACPI_TYPE_INTEGER;
	object[0].integer.integer = instance;
	object[1].object_type = ACPI_TYPE_INTEGER;
	object[1].integer.integer = methodId;
	uint32 count = 2;
	if (in != NULL) {
		object[2].object_type = ACPI_TYPE_BUFFER;
		if ((info->guid.flags & ACPI_WMI_REGFLAG_STRING) != 0)
			object[2].object_type = ACPI_TYPE_STRING;
		object[2].buffer.buffer = in->pointer;
		object[2].buffer.length = in->length;
		count++;
	}
	acpi_objects objects = { count, object};
	TRACE("EvaluateMethod calling %s\n", method);
	return acpi->evaluate_method(acpi_cookie, method, &objects, out);
}


status_t
WMIACPI::InstallEventHandler(const char* guidString,
	acpi_notify_handler handler, void* context)
{
	CALLED();
	char string[37] = {};
	for (uint32 i = 0; i < fWMIInfoCount; i++) {
		wmi_info* info = &fWMIInfos[i];
		_GuidToGuidString(info->guid.guid, string);
		if (strcmp(guidString, string) == 0) {
			status_t status = B_OK;
			if (info->handler == NULL)
				status = _SetEventGeneration(info, true);
			if (status == B_OK) {
				info->handler = handler;
				info->handler_context = context;
			}
			return status;
		}
	}
	return B_ENTRY_NOT_FOUND;
}


status_t
WMIACPI::RemoveEventHandler(const char* guidString)
{
	CALLED();
	char string[37] = {};
	for (uint32 i = 0; i < fWMIInfoCount; i++) {
		wmi_info* info = &fWMIInfos[i];
		_GuidToGuidString(info->guid.guid, string);
		if (strcmp(guidString, string) == 0) {
			status_t status = _SetEventGeneration(info, false);
			info->handler = NULL;
			info->handler_context = NULL;
			return status;
		}
	}
	return B_ENTRY_NOT_FOUND;
}


status_t
WMIACPI::GetEventData(uint32 notify, acpi_data* out)
{
	CALLED();

	acpi_object_type object;
	object.object_type = ACPI_TYPE_INTEGER;
	object.integer.integer = notify;
	acpi_objects objects = { 1, &object };

	for (uint32 i = 0; i < fWMIInfoCount; i++) {
		wmi_info* info = &fWMIInfos[i];
		if (info->guid.notify_id == notify
			&& (info->guid.flags & ACPI_WMI_REGFLAG_EVENT) != 0) {
			return acpi->evaluate_method(acpi_cookie, "_WED", &objects, out);
		}
	}
	return B_ENTRY_NOT_FOUND;
}


const char*
WMIACPI::GetUid(uint32 busCookie)
{
	return fUid;
}


status_t
WMIACPI::_SetEventGeneration(wmi_info* info, bool enabled)
{
	char method[5];
	sprintf(method, "WE%02X", info->guid.notify_id);
	TRACE("_SetEventGeneration calling %s\n", method);
	status_t status = _EvaluateMethodSimple(method, enabled ? 1 : 0);
	// the method is allowed not to exist
	if (status == B_ERROR)
		status = B_OK;
	return status;
}


status_t
WMIACPI::_EvaluateMethodSimple(const char* method, uint64 integer)
{
	acpi_object_type object;
	object.object_type = ACPI_TYPE_INTEGER;
	object.integer.integer = integer;
	acpi_objects objects = { 1, &object};
	return acpi->evaluate_method(acpi_cookie, method, &objects, NULL);
}


void
WMIACPI::_NotifyHandler(acpi_handle device, uint32 value, void *context)
{
	WMIACPI* bus = (WMIACPI*)context;
	bus->_Notify(device, value);
}


void
WMIACPI::_Notify(acpi_handle device, uint32 value)
{
	for (uint32 i = 0; i < fWMIInfoCount; i++) {
		wmi_info* wmi = &fWMIInfos[i];
		if (wmi->guid.notify_id == value) {
			TRACE("_Notify found event 0x%" B_PRIx32 "\n", value);
			if (wmi->handler != NULL) {
				TRACE("_Notify found handler for event 0x%" B_PRIx32 "\n",
					value);
				wmi->handler(device, value, wmi->handler_context);
			}
			break;
		}
	}
}


void
WMIACPI::_GuidToGuidString(uint8 guid[16], char* guidString)
{
	sprintf(guidString,
		"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
		guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6],
		guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14],
		guid[15]);
}


//	#pragma mark - driver module API


static float
wmi_acpi_support(device_node *parent)
{
	CALLED();

	// make sure parent is really the ACPI bus manager
	const char *bus;
	if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
		return -1;

	if (strcmp(bus, "acpi"))
		return 0.0;

	// check whether it's really a device
	uint32 device_type;
	if (gDeviceManager->get_attr_uint32(parent, ACPI_DEVICE_TYPE_ITEM,
			&device_type, false) != B_OK
		|| device_type != ACPI_TYPE_DEVICE) {
		return 0.0;
	}

	// check whether it's an acpi wmi device
	const char *name;
	if (gDeviceManager->get_attr_string(parent, ACPI_DEVICE_HID_ITEM, &name,
		false) != B_OK || strcmp(name, ACPI_NAME_ACPI_WMI) != 0) {
		return 0.0;
	}

	TRACE("found an acpi wmi device\n");

	return 0.6;
}


static status_t
wmi_acpi_register_device(device_node *node)
{
	CALLED();
	device_attr attrs[] = {
		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "WMI ACPI" }},
		{ NULL }
	};

	return gDeviceManager->register_node(node, WMI_ACPI_DRIVER_NAME, attrs,
		NULL, NULL);
}


static status_t
wmi_acpi_init_driver(device_node *node, void **driverCookie)
{
	CALLED();
	WMIACPI* device = new(std::nothrow) WMIACPI(node);
	if (device == NULL)
		return B_NO_MEMORY;

	*driverCookie = device;

	return B_OK;
}


static void
wmi_acpi_uninit_driver(void *driverCookie)
{
	CALLED();
	WMIACPI *device = (WMIACPI*)driverCookie;

	delete device;
}


static status_t
wmi_acpi_register_child_devices(void *cookie)
{
	CALLED();
	WMIACPI *device = (WMIACPI*)cookie;
	return device->Scan();
}


module_dependency module_dependencies[] = {
	{ B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&gDeviceManager },
	{ SMBIOS_MODULE_NAME, (module_info**)&gSMBios },
	{}
};


static driver_module_info sWMIACPIDriverModule = {
	{
		WMI_ACPI_DRIVER_NAME,
		0,
		NULL
	},

	wmi_acpi_support,
	wmi_acpi_register_device,
	wmi_acpi_init_driver,
	wmi_acpi_uninit_driver,
	wmi_acpi_register_child_devices,
	NULL,	// rescan
	NULL,	// removed
};


module_info *modules[] = {
	(module_info *)&sWMIACPIDriverModule,
	(module_info *)&gWMIAsusDriverModule,
	(module_info *)&gWMIDeviceModule,
	NULL
};