From 596212e17aad7811125bebfd175dc4372a55a8a9 Mon Sep 17 00:00:00 2001 From: Christoffer Sandberg Date: Mon, 7 Dec 2020 16:47:34 +0100 Subject: [PATCH] tuxedo_io: include tuxedo_cc_wmi as tuxedo_io - Clevo part now using exported interface from clevo_interfaces - Uniwill part still works as previously - Additional module alias for ACPI interface needed and added --- Makefile | 5 +- dkms.conf | 4 + src/tuxedo_io/tongfang_wmi.h | 291 ++++++++++++++++++++++++++ src/tuxedo_io/tuxedo_io.c | 351 ++++++++++++++++++++++++++++++++ src/tuxedo_io/tuxedo_io_ioctl.h | 86 ++++++++ 5 files changed, 736 insertions(+), 1 deletion(-) create mode 100644 src/tuxedo_io/tongfang_wmi.h create mode 100644 src/tuxedo_io/tuxedo_io.c create mode 100644 src/tuxedo_io/tuxedo_io_ioctl.h diff --git a/Makefile b/Makefile index 7723023..8d031d9 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,10 @@ # You should have received a copy of the GNU General Public License # along with this software. If not, see . # -obj-m := ./src/tuxedo_keyboard.o ./src/clevo_wmi.o ./src/clevo_acpi.o +obj-m := ./src/tuxedo_keyboard.o \ + ./src/clevo_wmi.o \ + ./src/clevo_acpi.o \ + ./src/tuxedo_io/tuxedo_io.o PWD := $(shell pwd) KDIR := /lib/modules/$(shell uname -r)/build diff --git a/dkms.conf b/dkms.conf index f3b5925..7e8238e 100644 --- a/dkms.conf +++ b/dkms.conf @@ -13,6 +13,10 @@ DEST_MODULE_LOCATION[2]="/kernel/lib/" BUILT_MODULE_NAME[2]="clevo_acpi" BUILT_MODULE_LOCATION[2]="src/" +DEST_MODULE_LOCATION[3]="/kernel/lib/" +BUILT_MODULE_NAME[3]="tuxedo_io" +BUILT_MODULE_LOCATION[3]="src/tuxedo_io" + MAKE[0]="make KDIR=/lib/modules/${kernelver}/build" CLEAN="make clean" AUTOINSTALL="yes" diff --git a/src/tuxedo_io/tongfang_wmi.h b/src/tuxedo_io/tongfang_wmi.h new file mode 100644 index 0000000..cfabbb3 --- /dev/null +++ b/src/tuxedo_io/tongfang_wmi.h @@ -0,0 +1,291 @@ +/*! + * Copyright (c) 2020 TUXEDO Computers GmbH + * + * This file is part of tuxedo-cc-wmi. + * + * tuxedo-cc-wmi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This software 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 this software. If not, see . + */ +#include +#include +#include +#include + +#define UNIWILL_WMI_MGMT_GUID_BA "ABBC0F6D-8EA1-11D1-00A0-C90629100000" +#define UNIWILL_WMI_MGMT_GUID_BB "ABBC0F6E-8EA1-11D1-00A0-C90629100000" +#define UNIWILL_WMI_MGMT_GUID_BC "ABBC0F6F-8EA1-11D1-00A0-C90629100000" + +#define UNIWILL_WMI_EVENT_GUID_0 "ABBC0F70-8EA1-11D1-00A0-C90629100000" +#define UNIWILL_WMI_EVENT_GUID_1 "ABBC0F71-8EA1-11D1-00A0-C90629100000" +#define UNIWILL_WMI_EVENT_GUID_2 "ABBC0F72-8EA1-11D1-00A0-C90629100000" + +#define UNIWILL_EC_REG_LDAT 0x8a +#define UNIWILL_EC_REG_HDAT 0x8b +#define UNIWILL_EC_REG_FLAGS 0x8c +#define UNIWILL_EC_REG_CMDL 0x8d +#define UNIWILL_EC_REG_CMDH 0x8e + +#define UNIWILL_EC_BIT_RFLG 0 +#define UNIWILL_EC_BIT_WFLG 1 +#define UNIWILL_EC_BIT_BFLG 2 +#define UNIWILL_EC_BIT_CFLG 3 +#define UNIWILL_EC_BIT_DRDY 7 + +#define UW_EC_WAIT_CYCLES 0x50 + +union uw_ec_read_return { + u32 dword; + struct { + u8 data_low; + u8 data_high; + } bytes; +}; + +union uw_ec_write_return { + u32 dword; + struct { + u8 addr_low; + u8 addr_high; + u8 data_low; + u8 data_high; + } bytes; +}; + +static bool uniwill_ec_direct = true; + +DEFINE_MUTEX(uniwill_ec_lock); + +static u32 uw_wmi_ec_evaluate(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, u8 read_flag, u32 *return_buffer) +{ + acpi_status status; + union acpi_object *out_acpi; + u32 e_result = 0; + + // Kernel buffer for input argument + u32 *wmi_arg = (u32 *) kmalloc(sizeof(u32)*10, GFP_KERNEL); + // Byte reference to the input buffer + u8 *wmi_arg_bytes = (u8 *) wmi_arg; + + u8 wmi_instance = 0x00; + u32 wmi_method_id = 0x04; + struct acpi_buffer wmi_in = { (acpi_size) sizeof(wmi_arg), wmi_arg}; + struct acpi_buffer wmi_out = { ACPI_ALLOCATE_BUFFER, NULL }; + + mutex_lock(&uniwill_ec_lock); + + // Zero input buffer + memset(wmi_arg, 0x00, 10 * sizeof(u32)); + + // Configure the input buffer + wmi_arg_bytes[0] = addr_low; + wmi_arg_bytes[1] = addr_high; + wmi_arg_bytes[2] = data_low; + wmi_arg_bytes[3] = data_high; + + if (read_flag != 0) { + wmi_arg_bytes[5] = 0x01; + } + + status = wmi_evaluate_method(UNIWILL_WMI_MGMT_GUID_BC, wmi_instance, wmi_method_id, &wmi_in, &wmi_out); + out_acpi = (union acpi_object *) wmi_out.pointer; + + if (out_acpi && out_acpi->type == ACPI_TYPE_BUFFER) { + memcpy(return_buffer, out_acpi->buffer.pointer, out_acpi->buffer.length); + } /* else if (out_acpi && out_acpi->type == ACPI_TYPE_INTEGER) { + e_result = (u32) out_acpi->integer.value; + }*/ + if (ACPI_FAILURE(status)) { + pr_err("uniwill_wmi.h: Error evaluating method\n"); + e_result = -EIO; + } + + kfree(out_acpi); + kfree(wmi_arg); + + mutex_unlock(&uniwill_ec_lock); + + return e_result; +} + +/** + * EC address read through WMI + */ +static u32 uw_ec_read_addr_wmi(u8 addr_low, u8 addr_high, union uw_ec_read_return *output) +{ + u32 uw_data[10]; + u32 ret = uw_wmi_ec_evaluate(addr_low, addr_high, 0x00, 0x00, 1, uw_data); + output->dword = uw_data[0]; + pr_debug("addr: 0x%02x%02x value: %0#4x (high: %0#4x) result: %d\n", addr_high, addr_low, output->bytes.data_low, output->bytes.data_high, ret); + return ret; +} + +/** + * EC address write through WMI + */ +static u32 uw_ec_write_addr_wmi(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output) +{ + u32 uw_data[10]; + u32 ret = uw_wmi_ec_evaluate(addr_low, addr_high, data_low, data_high, 0, uw_data); + output->dword = uw_data[0]; + return ret; +} + +/** + * Direct EC address read + */ +static u32 uw_ec_read_addr_direct(u8 addr_low, u8 addr_high, union uw_ec_read_return *output) +{ + u32 result; + u8 tmp, count, flags; + + mutex_lock(&uniwill_ec_lock); + + ec_write(UNIWILL_EC_REG_LDAT, addr_low); + ec_write(UNIWILL_EC_REG_HDAT, addr_high); + + flags = (0 << UNIWILL_EC_BIT_DRDY) | (1 << UNIWILL_EC_BIT_RFLG); + ec_write(UNIWILL_EC_REG_FLAGS, flags); + + // Wait for ready flag + count = UW_EC_WAIT_CYCLES; + ec_read(UNIWILL_EC_REG_FLAGS, &tmp); + while (((tmp & (1 << UNIWILL_EC_BIT_DRDY)) == 0) && count != 0) { + msleep(1); + ec_read(UNIWILL_EC_REG_FLAGS, &tmp); + count -= 1; + } + + if (count != 0) { + output->dword = 0; + ec_read(UNIWILL_EC_REG_CMDL, &tmp); + output->bytes.data_low = tmp; + ec_read(UNIWILL_EC_REG_CMDH, &tmp); + output->bytes.data_high = tmp; + result = 0; + } else { + output->dword = 0xfefefefe; + result = -EIO; + } + + ec_write(UNIWILL_EC_REG_FLAGS, 0x00); + + mutex_unlock(&uniwill_ec_lock); + + pr_debug("addr: 0x%02x%02x value: %0#4x result: %d\n", addr_high, addr_low, output->bytes.data_low, result); + + return result; +} + +static u32 uw_ec_write_addr_direct(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output) +{ + u32 result = 0; + u8 tmp, count, flags; + + mutex_lock(&uniwill_ec_lock); + + ec_write(UNIWILL_EC_REG_LDAT, addr_low); + ec_write(UNIWILL_EC_REG_HDAT, addr_high); + ec_write(UNIWILL_EC_REG_CMDL, data_low); + ec_write(UNIWILL_EC_REG_CMDH, data_high); + + flags = (0 << UNIWILL_EC_BIT_DRDY) | (1 << UNIWILL_EC_BIT_WFLG); + ec_write(UNIWILL_EC_REG_FLAGS, flags); + + // Wait for ready flag + count = UW_EC_WAIT_CYCLES; + ec_read(UNIWILL_EC_REG_FLAGS, &tmp); + while (((tmp & (1 << UNIWILL_EC_BIT_DRDY)) == 0) && count != 0) { + msleep(1); + ec_read(UNIWILL_EC_REG_FLAGS, &tmp); + count -= 1; + } + + // Replicate wmi output depending on success + if (count != 0) { + output->bytes.addr_low = addr_low; + output->bytes.addr_high = addr_high; + output->bytes.data_low = data_low; + output->bytes.data_high = data_high; + result = 0; + } else { + output->dword = 0xfefefefe; + result = -EIO; + } + + ec_write(UNIWILL_EC_REG_FLAGS, 0x00); + + mutex_unlock(&uniwill_ec_lock); + + return result; +} + +u32 uw_ec_read_addr(u8 addr_low, u8 addr_high, union uw_ec_read_return *output) +{ + if (uniwill_ec_direct) { + return uw_ec_read_addr_direct(addr_low, addr_high, output); + } else { + return uw_ec_read_addr_wmi(addr_low, addr_high, output); + } +} +EXPORT_SYMBOL(uw_ec_read_addr); + +u32 uw_ec_write_addr(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output) +{ + if (uniwill_ec_direct) { + return uw_ec_write_addr_direct(addr_low, addr_high, data_low, data_high, output); + } else { + return uw_ec_write_addr_wmi(addr_low, addr_high, data_low, data_high, output); + } +} +EXPORT_SYMBOL(uw_ec_write_addr); + +static u32 uniwill_identify(void) +{ + int status; + + // Look for for GUIDs used on uniwill devices + status = + wmi_has_guid(UNIWILL_WMI_EVENT_GUID_0) && + wmi_has_guid(UNIWILL_WMI_EVENT_GUID_1) && + wmi_has_guid(UNIWILL_WMI_EVENT_GUID_2) && + wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BA) && + wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BB) && + wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BC); + + if (!status) + { + pr_debug("probe: At least one Uniwill GUID missing\n"); + return -ENODEV; + } + + return 0; +} + +static void uniwill_init(void) +{ + union uw_ec_write_return reg_write_return; + + // Enable manual mode + uw_ec_write_addr(0x41, 0x07, 0x01, 0x00, ®_write_return); + + // Zero second fan temp for detection + uw_ec_write_addr(0x4f, 0x04, 0x00, 0x00, ®_write_return); +} + +static void uniwill_exit(void) +{ + union uw_ec_write_return reg_write_return; + + // Disable manual mode + uw_ec_write_addr(0x41, 0x07, 0x00, 0x00, ®_write_return); +} diff --git a/src/tuxedo_io/tuxedo_io.c b/src/tuxedo_io/tuxedo_io.c new file mode 100644 index 0000000..c83cba6 --- /dev/null +++ b/src/tuxedo_io/tuxedo_io.c @@ -0,0 +1,351 @@ +/*! + * Copyright (c) 2019-2020 TUXEDO Computers GmbH + * + * This file is part of tuxedo-cc-wmi. + * + * tuxedo-cc-wmi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This software 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 this software. If not, see . + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../clevo_interfaces.h" +#include "tongfang_wmi.h" +#include "tuxedo_io_ioctl.h" + +MODULE_DESCRIPTION("Hardware interface for TUXEDO laptops"); +MODULE_AUTHOR("TUXEDO Computers GmbH "); +MODULE_VERSION("0.1.7"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS_CLEVO_INTERFACES(); +MODULE_ALIAS("wmi:" CLEVO_WMI_METHOD_GUID); +MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BA); +MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BB); +MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BC); + +// Initialized in module init, global for ioctl interface +static u32 id_check_clevo; +static u32 id_check_uniwill; + +static u32 clevo_identify(void) +{ + return clevo_get_active_interface_id(&dummy) == 0 ? 1 : 0; +} + +/*static int fop_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int fop_release(struct inode *inode, struct file *file) +{ + return 0; +}*/ + +static long clevo_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg) +{ + u32 result = 0, status; + u32 copy_result; + u32 argument = (u32) arg; + + switch (cmd) { + case R_FANINFO1: + status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO1, 0, &result); + copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); + break; + case R_FANINFO2: + status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO2, 0, &result); + copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); + break; + case R_FANINFO3: + status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO3, 0, &result); + copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); + break; + /*case R_FANINFO4: + status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO4, 0); + copy_to_user((int32_t *) arg, &result, sizeof(result)); + break;*/ + case R_WEBCAM_SW: + status = clevo_evaluate_method(CLEVO_CMD_GET_WEBCAM_SW, 0, &result); + copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); + break; + case R_FLIGHTMODE_SW: + status = clevo_evaluate_method(CLEVO_CMD_GET_FLIGHTMODE_SW, 0, &result); + copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); + break; + case R_TOUCHPAD_SW: + status = clevo_evaluate_method(CLEVO_CMD_GET_TOUCHPAD_SW, 0, &result); + copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); + break; + } + + switch (cmd) { + case W_FANSPEED: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + clevo_evaluate_method(CLEVO_CMD_SET_FANSPEED_VALUE, argument, &result); + // Note: Delay needed to let hardware catch up with the written value. + // No known ready flag. If the value is read too soon, the old value + // will still be read out. + // (Theoretically needed for other methods as well.) + // Can it be lower? 50ms is too low + msleep(100); + break; + case W_FANAUTO: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + clevo_evaluate_method(CLEVO_CMD_SET_FANSPEED_AUTO, argument, &result); + break; + case W_WEBCAM_SW: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + status = clevo_evaluate_method(CLEVO_CMD_GET_WEBCAM_SW, 0, &result); + // Only set status if it isn't already the right value + // (workaround for old and/or buggy WMI interfaces that toggle on write) + if ((argument & 0x01) != (result & 0x01)) { + clevo_evaluate_method(CLEVO_CMD_SET_WEBCAM_SW, argument, &result); + } + break; + case W_FLIGHTMODE_SW: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + clevo_evaluate_method(CLEVO_CMD_SET_FLIGHTMODE_SW, argument, &result); + break; + case W_TOUCHPAD_SW: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + clevo_evaluate_method(CLEVO_CMD_SET_TOUCHPAD_SW, argument, &result); + break; + } + + return 0; +} + +static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg) +{ + u32 result = 0; + u32 copy_result; + u32 argument; + union uw_ec_read_return reg_read_return; + union uw_ec_write_return reg_write_return; + +#ifdef DEBUG + u32 uw_arg[10]; + u32 uw_result[10]; + int i; + for (i = 0; i < 10; ++i) { + uw_result[i] = 0xdeadbeef; + } +#endif + + switch (cmd) { + case R_UW_FANSPEED: + uw_ec_read_addr(0x04, 0x18, ®_read_return); + result = reg_read_return.bytes.data_low; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_FANSPEED2: + uw_ec_read_addr(0x09, 0x18, ®_read_return); + result = reg_read_return.bytes.data_low; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_FAN_TEMP: + uw_ec_read_addr(0x3e, 0x04, ®_read_return); + result = reg_read_return.bytes.data_low; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_FAN_TEMP2: + uw_ec_read_addr(0x4f, 0x04, ®_read_return); + result = reg_read_return.bytes.data_low; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_MODE: + uw_ec_read_addr(0x51, 0x07, ®_read_return); + result = reg_read_return.bytes.data_low; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_MODE_ENABLE: + uw_ec_read_addr(0x41, 0x07, ®_read_return); + result = reg_read_return.bytes.data_low; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; +#ifdef DEBUG + case R_TF_BC: + copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg)); + pr_info("R_TF_BC args [%0#2x, %0#2x, %0#2x, %0#2x]\n", uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3]); + if (uniwill_ec_direct) { + result = uw_ec_read_addr_direct(uw_arg[0], uw_arg[1], ®_read_return); + copy_result = copy_to_user((void *) arg, ®_read_return.dword, sizeof(reg_read_return.dword)); + } else { + result = uw_wmi_ec_evaluate(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], 1, uw_result); + copy_result = copy_to_user((void *) arg, &uw_result, sizeof(uw_result)); + } + break; +#endif + } + + switch (cmd) { + case W_UW_FANSPEED: + // Get fan speed argument + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + + // Check current mode + uw_ec_read_addr(0x51, 0x07, ®_read_return); + if (reg_read_return.bytes.data_low != 0x40) { + // If not "full fan mode" (ie. 0x40) switch to it (required for fancontrol) + uw_ec_write_addr(0x51, 0x07, 0x40, 0x00, ®_write_return); + } + // Set speed + uw_ec_write_addr(0x04, 0x18, argument & 0xff, 0x00, ®_write_return); + break; + case W_UW_FANSPEED2: + // Get fan speed argument + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + + // Check current mode + uw_ec_read_addr(0x51, 0x07, ®_read_return); + if (reg_read_return.bytes.data_low != 0x40) { + // If not "full fan mode" (ie. 0x40) switch to it (required for fancontrol) + uw_ec_write_addr(0x51, 0x07, 0x40, 0x00, ®_write_return); + } + // Set speed + uw_ec_write_addr(0x09, 0x18, argument & 0xff, 0x00, ®_write_return); + break; + case W_UW_MODE: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + uw_ec_write_addr(0x51, 0x07, argument & 0xff, 0x00, ®_write_return); + break; + case W_UW_MODE_ENABLE: + // Note: Is for the moment set and cleared on init/exit of module (uniwill mode) + /* + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + uw_ec_write_addr(0x41, 0x07, argument & 0x01, 0x00, ®_write_return); + */ + break; +#ifdef DEBUG + case W_TF_BC: + copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg)); + if (uniwill_ec_direct) { + result = uw_ec_write_addr_direct(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], ®_write_return); + copy_result = copy_to_user((void *) arg, ®_write_return.dword, sizeof(reg_write_return.dword)); + } else { + result = uw_wmi_ec_evaluate(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], 0, uw_result); + copy_result = copy_to_user((void *) arg, &uw_result, sizeof(uw_result)); + reg_write_return.dword = uw_result[0]; + } + /*pr_info("data_high %0#2x\n", reg_write_return.bytes.data_high); + pr_info("data_low %0#2x\n", reg_write_return.bytes.data_low); + pr_info("addr_high %0#2x\n", reg_write_return.bytes.addr_high); + pr_info("addr_low %0#2x\n", reg_write_return.bytes.addr_low);*/ + break; +#endif + } + + return 0; +} + +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + u32 status; + // u32 result = 0; + u32 copy_result; + + const char *module_version = THIS_MODULE->version; + switch (cmd) { + case R_MOD_VERSION: + copy_result = copy_to_user((char *) arg, module_version, strlen(module_version) + 1); + break; + // Hardware id checks, 1 = positive, 0 = negative + case R_HWCHECK_CL: + id_check_clevo = clevo_identify(); + copy_result = copy_to_user((void *) arg, (void *) &id_check_clevo, sizeof(id_check_clevo)); + break; + case R_HWCHECK_UW: + copy_result = copy_to_user((void *) arg, (void *) &id_check_uniwill, sizeof(id_check_uniwill)); + break; + } + + status = clevo_ioctl_interface(file, cmd, arg); + if (status != 0) return status; + status = uniwill_ioctl_interface(file, cmd, arg); + if (status != 0) return status; + + return 0; +} + +static struct file_operations fops_dev = { + .owner = THIS_MODULE, + .unlocked_ioctl = fop_ioctl +// .open = fop_open, +// .release = fop_release +}; + +struct class *tuxedo_io_device_class; +dev_t tuxedo_io_device_handle; + +static struct cdev tuxedo_io_cdev; + +static int __init tuxedo_io_init(void) +{ + int err; + + // Hardware identification + id_check_clevo = clevo_identify(); + id_check_uniwill = uniwill_identify() == 0 ? 1 : 0; + + if (id_check_uniwill == 1) { + uniwill_init(); + } + +#ifdef DEBUG + if (id_check_clevo == 0 && id_check_uniwill == 0) { + pr_debug("No matching hardware found\n"); + } +#endif + + err = alloc_chrdev_region(&tuxedo_io_device_handle, 0, 1, "tuxedo_io_cdev"); + if (err != 0) { + pr_err("Failed to allocate chrdev region\n"); + return err; + } + cdev_init(&tuxedo_io_cdev, &fops_dev); + err = (cdev_add(&tuxedo_io_cdev, tuxedo_io_device_handle, 1)); + if (err < 0) { + pr_err("Failed to add cdev\n"); + unregister_chrdev_region(tuxedo_io_device_handle, 1); + } + tuxedo_io_device_class = class_create(THIS_MODULE, "tuxedo_io"); + device_create(tuxedo_io_device_class, NULL, tuxedo_io_device_handle, NULL, "tuxedo_io"); + pr_debug("Module init successful\n"); + + return 0; +} + +static void __exit tuxedo_io_exit(void) +{ + if (id_check_uniwill == 1) { + uniwill_exit(); + } + + device_destroy(tuxedo_io_device_class, tuxedo_io_device_handle); + class_destroy(tuxedo_io_device_class); + cdev_del(&tuxedo_io_cdev); + unregister_chrdev_region(tuxedo_io_device_handle, 1); + pr_debug("Module exit\n"); +} + +module_init(tuxedo_io_init); +module_exit(tuxedo_io_exit); diff --git a/src/tuxedo_io/tuxedo_io_ioctl.h b/src/tuxedo_io/tuxedo_io_ioctl.h new file mode 100644 index 0000000..c97b228 --- /dev/null +++ b/src/tuxedo_io/tuxedo_io_ioctl.h @@ -0,0 +1,86 @@ +/*! + * Copyright (c) 2019-2020 TUXEDO Computers GmbH + * + * This file is part of tuxedo-cc-wmi. + * + * tuxedo-cc-wmi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This software 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 this software. If not, see . + */ +#ifndef TUXEDO_IO_IOCTL_H +#define TUXEDO_IO_IOCTL_H + +#define IOCTL_MAGIC 0xEC + +#define MAGIC_READ_CL IOCTL_MAGIC +#define MAGIC_WRITE_CL IOCTL_MAGIC + 1 + +#define MAGIC_READ_UW IOCTL_MAGIC + 2 +#define MAGIC_WRITE_UW IOCTL_MAGIC + 3 + + +// General +#define R_MOD_VERSION _IOR(IOCTL_MAGIC, 0x00, char*) + +#define R_HWCHECK_CL _IOR(IOCTL_MAGIC, 0x05, char*) +#define R_HWCHECK_UW _IOR(IOCTL_MAGIC, 0x06, char*) + +/** + * Clevo interface + */ + +// Read +#define R_FANINFO1 _IOR(MAGIC_READ_CL, 0x10, int32_t*) +#define R_FANINFO2 _IOR(MAGIC_READ_CL, 0x11, int32_t*) +#define R_FANINFO3 _IOR(MAGIC_READ_CL, 0x12, int32_t*) +// #define R_FANINFO4 _IOR(MAGIC_READ_CL, 0x04, int32_t*) + +#define R_WEBCAM_SW _IOR(MAGIC_READ_CL, 0x13, int32_t*) +#define R_FLIGHTMODE_SW _IOR(MAGIC_READ_CL, 0x14, int32_t*) +#define R_TOUCHPAD_SW _IOR(MAGIC_READ_CL, 0x15, int32_t*) + +#ifdef DEBUG +#define R_TF_BC _IOW(MAGIC_READ_CL, 0x91, uint32_t*) +#endif + +// Write +#define W_FANSPEED _IOW(MAGIC_WRITE_CL, 0x10, int32_t*) +#define W_FANAUTO _IOW(MAGIC_WRITE_CL, 0x11, int32_t*) + +#define W_WEBCAM_SW _IOW(MAGIC_WRITE_CL, 0x12, int32_t*) +#define W_FLIGHTMODE_SW _IOW(MAGIC_WRITE_CL, 0x13, int32_t*) +#define W_TOUCHPAD_SW _IOW(MAGIC_WRITE_CL, 0x14, int32_t*) + +#ifdef DEBUG +#define W_TF_BC _IOW(MAGIC_WRITE_CL, 0x91, uint32_t*) +#endif + +/** + * Uniwill interface + */ + +// Read +#define R_UW_FANSPEED _IOR(MAGIC_READ_UW, 0x10, int32_t*) +#define R_UW_FANSPEED2 _IOR(MAGIC_READ_UW, 0x11, int32_t*) +#define R_UW_FAN_TEMP _IOR(MAGIC_READ_UW, 0x12, int32_t*) +#define R_UW_FAN_TEMP2 _IOR(MAGIC_READ_UW, 0x13, int32_t*) + +#define R_UW_MODE _IOR(MAGIC_READ_UW, 0x14, int32_t*) +#define R_UW_MODE_ENABLE _IOR(MAGIC_READ_UW, 0x15, int32_t*) + +// Write +#define W_UW_FANSPEED _IOW(MAGIC_WRITE_UW, 0x10, int32_t*) +#define W_UW_FANSPEED2 _IOW(MAGIC_WRITE_UW, 0x11, int32_t*) +#define W_UW_MODE _IOW(MAGIC_WRITE_UW, 0x12, int32_t*) +#define W_UW_MODE_ENABLE _IOW(MAGIC_WRITE_UW, 0x13, int32_t*) + +#endif