diff options
Diffstat (limited to 'drivers/platform')
47 files changed, 5932 insertions, 252 deletions
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 971426bb4302..18fc6a08569e 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -13,3 +13,5 @@ source "drivers/platform/chrome/Kconfig" source "drivers/platform/mellanox/Kconfig" source "drivers/platform/olpc/Kconfig" + +source "drivers/platform/surface/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 6fda58c021ca..4de08ef4ec9d 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_MIPS) += mips/ obj-$(CONFIG_OLPC_EC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ +obj-$(CONFIG_SURFACE_PLATFORMS) += surface/ diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig index 916b39dc11bc..edd17e1a1f88 100644 --- a/drivers/platform/mellanox/Kconfig +++ b/drivers/platform/mellanox/Kconfig @@ -56,4 +56,14 @@ config MLXBF_BOOTCTL to the userspace tools, to be used in conjunction with the eMMC device driver to do necessary initial swap of the boot partition. +config MLXBF_PMC + tristate "Mellanox BlueField Performance Monitoring Counters driver" + depends on ARM64 + depends on HWMON + depends on ACPI + help + Say y here to enable PMC support. The PMC driver provides access + to performance monitoring counters within various blocks in the + Mellanox BlueField SoC via a sysfs interface. + endif # MELLANOX_PLATFORM diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile index 499623ccf2fe..000ddaa74c98 100644 --- a/drivers/platform/mellanox/Makefile +++ b/drivers/platform/mellanox/Makefile @@ -4,6 +4,7 @@ # Mellanox Platform-Specific Drivers # obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o +obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c new file mode 100644 index 000000000000..35883984251f --- /dev/null +++ b/drivers/platform/mellanox/mlxbf-pmc.c @@ -0,0 +1,1478 @@ +// SPDX-License-Identifier: GPL-2.0-only OR Linux-OpenIB +/* + * Mellanox BlueField Performance Monitoring Counters driver + * + * This driver provides a sysfs interface for monitoring + * performance statistics in BlueField SoC. + * + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + */ + +#include <linux/acpi.h> +#include <linux/arm-smccc.h> +#include <linux/bitfield.h> +#include <linux/errno.h> +#include <linux/hwmon.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <uapi/linux/psci.h> + +#define MLXBF_PMC_WRITE_REG_32 0x82000009 +#define MLXBF_PMC_READ_REG_32 0x8200000A +#define MLXBF_PMC_WRITE_REG_64 0x8200000B +#define MLXBF_PMC_READ_REG_64 0x8200000C +#define MLXBF_PMC_SIP_SVC_UID 0x8200ff01 +#define MLXBF_PMC_SIP_SVC_VERSION 0x8200ff03 +#define MLXBF_PMC_SVC_REQ_MAJOR 0 +#define MLXBF_PMC_SVC_MIN_MINOR 3 + +#define MLXBF_PMC_SMCCC_ACCESS_VIOLATION -4 + +#define MLXBF_PMC_EVENT_SET_BF1 0 +#define MLXBF_PMC_EVENT_SET_BF2 1 +#define MLXBF_PMC_EVENT_INFO_LEN 100 + +#define MLXBF_PMC_MAX_BLOCKS 30 +#define MLXBF_PMC_MAX_ATTRS 30 +#define MLXBF_PMC_INFO_SZ 4 +#define MLXBF_PMC_REG_SIZE 8 +#define MLXBF_PMC_L3C_REG_SIZE 4 + +#define MLXBF_PMC_TYPE_COUNTER 1 +#define MLXBF_PMC_TYPE_REGISTER 0 + +#define MLXBF_PMC_PERFCTL 0 +#define MLXBF_PMC_PERFEVT 1 +#define MLXBF_PMC_PERFACC0 4 + +#define MLXBF_PMC_PERFMON_CONFIG_WR_R_B BIT(0) +#define MLXBF_PMC_PERFMON_CONFIG_STROBE BIT(1) +#define MLXBF_PMC_PERFMON_CONFIG_ADDR GENMASK_ULL(4, 2) +#define MLXBF_PMC_PERFMON_CONFIG_WDATA GENMASK_ULL(60, 5) + +#define MLXBF_PMC_PERFCTL_FM0 GENMASK_ULL(18, 16) +#define MLXBF_PMC_PERFCTL_MS0 GENMASK_ULL(21, 20) +#define MLXBF_PMC_PERFCTL_ACCM0 GENMASK_ULL(26, 24) +#define MLXBF_PMC_PERFCTL_AD0 BIT(27) +#define MLXBF_PMC_PERFCTL_ETRIG0 GENMASK_ULL(29, 28) +#define MLXBF_PMC_PERFCTL_EB0 BIT(30) +#define MLXBF_PMC_PERFCTL_EN0 BIT(31) + +#define MLXBF_PMC_PERFEVT_EVTSEL GENMASK_ULL(31, 24) + +#define MLXBF_PMC_L3C_PERF_CNT_CFG 0x0 +#define MLXBF_PMC_L3C_PERF_CNT_SEL 0x10 +#define MLXBF_PMC_L3C_PERF_CNT_SEL_1 0x14 +#define MLXBF_PMC_L3C_PERF_CNT_LOW 0x40 +#define MLXBF_PMC_L3C_PERF_CNT_HIGH 0x60 + +#define MLXBF_PMC_L3C_PERF_CNT_CFG_EN BIT(0) +#define MLXBF_PMC_L3C_PERF_CNT_CFG_RST BIT(1) +#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0 GENMASK(5, 0) +#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1 GENMASK(13, 8) +#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2 GENMASK(21, 16) +#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3 GENMASK(29, 24) + +#define MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4 GENMASK(5, 0) + +#define MLXBF_PMC_L3C_PERF_CNT_LOW_VAL GENMASK(31, 0) +#define MLXBF_PMC_L3C_PERF_CNT_HIGH_VAL GENMASK(24, 0) + +/** + * Structure to hold attribute and block info for each sysfs entry + * @dev_attr: Device attribute struct + * @index: index to identify counter number within a block + * @nr: block number to which the sysfs belongs + */ +struct mlxbf_pmc_attribute { + struct device_attribute dev_attr; + int index; + int nr; +}; + +/** + * Structure to hold info for each HW block + * + * @mmio_base: The VA at which the PMC block is mapped + * @blk_size: Size of each mapped region + * @counters: Number of counters in the block + * @type: Type of counters in the block + * @attr_counter: Attributes for "counter" sysfs files + * @attr_event: Attributes for "event" sysfs files + * @attr_event_list: Attributes for "event_list" sysfs files + * @attr_enable: Attributes for "enable" sysfs files + * @block_attr: All attributes needed for the block + * @blcok_attr_grp: Attribute group for the block + */ +struct mlxbf_pmc_block_info { + void __iomem *mmio_base; + size_t blk_size; + size_t counters; + int type; + struct mlxbf_pmc_attribute *attr_counter; + struct mlxbf_pmc_attribute *attr_event; + struct mlxbf_pmc_attribute attr_event_list; + struct mlxbf_pmc_attribute attr_enable; + struct attribute *block_attr[MLXBF_PMC_MAX_ATTRS]; + struct attribute_group block_attr_grp; +}; + +/** + * Structure to hold PMC context info + * + * @pdev: The kernel structure representing the device + * @total_blocks: Total number of blocks + * @tile_count: Number of tiles in the system + * @hwmon_dev: Hwmon device for bfperf + * @block_name: Block name + * @block: Block info + * @groups: Attribute groups from each block + * @sv_sreg_support: Whether SMCs are used to access performance registers + * @sreg_tbl_perf: Secure register access table number + * @event_set: Event set to use + */ +struct mlxbf_pmc_context { + struct platform_device *pdev; + uint32_t total_blocks; + uint32_t tile_count; + struct device *hwmon_dev; + const char *block_name[MLXBF_PMC_MAX_BLOCKS]; + struct mlxbf_pmc_block_info block[MLXBF_PMC_MAX_BLOCKS]; + const struct attribute_group *groups[MLXBF_PMC_MAX_BLOCKS]; + bool svc_sreg_support; + uint32_t sreg_tbl_perf; + unsigned int event_set; +}; + +/** + * Structure to hold supported events for each block + * @evt_num: Event number used to program counters + * @evt_name: Name of the event + */ +struct mlxbf_pmc_events { + int evt_num; + char *evt_name; +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_pcie_events[] = { + { 0x0, "IN_P_PKT_CNT" }, + { 0x10, "IN_NP_PKT_CNT" }, + { 0x18, "IN_C_PKT_CNT" }, + { 0x20, "OUT_P_PKT_CNT" }, + { 0x28, "OUT_NP_PKT_CNT" }, + { 0x30, "OUT_C_PKT_CNT" }, + { 0x38, "IN_P_BYTE_CNT" }, + { 0x40, "IN_NP_BYTE_CNT" }, + { 0x48, "IN_C_BYTE_CNT" }, + { 0x50, "OUT_P_BYTE_CNT" }, + { 0x58, "OUT_NP_BYTE_CNT" }, + { 0x60, "OUT_C_BYTE_CNT" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_smgen_events[] = { + { 0x0, "AW_REQ" }, + { 0x1, "AW_BEATS" }, + { 0x2, "AW_TRANS" }, + { 0x3, "AW_RESP" }, + { 0x4, "AW_STL" }, + { 0x5, "AW_LAT" }, + { 0x6, "AW_REQ_TBU" }, + { 0x8, "AR_REQ" }, + { 0x9, "AR_BEATS" }, + { 0xa, "AR_TRANS" }, + { 0xb, "AR_STL" }, + { 0xc, "AR_LAT" }, + { 0xd, "AR_REQ_TBU" }, + { 0xe, "TBU_MISS" }, + { 0xf, "TX_DAT_AF" }, + { 0x10, "RX_DAT_AF" }, + { 0x11, "RETRYQ_CRED" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_trio_events_1[] = { + { 0xa0, "TPIO_DATA_BEAT" }, + { 0xa1, "TDMA_DATA_BEAT" }, + { 0xa2, "MAP_DATA_BEAT" }, + { 0xa3, "TXMSG_DATA_BEAT" }, + { 0xa4, "TPIO_DATA_PACKET" }, + { 0xa5, "TDMA_DATA_PACKET" }, + { 0xa6, "MAP_DATA_PACKET" }, + { 0xa7, "TXMSG_DATA_PACKET" }, + { 0xa8, "TDMA_RT_AF" }, + { 0xa9, "TDMA_PBUF_MAC_AF" }, + { 0xaa, "TRIO_MAP_WRQ_BUF_EMPTY" }, + { 0xab, "TRIO_MAP_CPL_BUF_EMPTY" }, + { 0xac, "TRIO_MAP_RDQ0_BUF_EMPTY" }, + { 0xad, "TRIO_MAP_RDQ1_BUF_EMPTY" }, + { 0xae, "TRIO_MAP_RDQ2_BUF_EMPTY" }, + { 0xaf, "TRIO_MAP_RDQ3_BUF_EMPTY" }, + { 0xb0, "TRIO_MAP_RDQ4_BUF_EMPTY" }, + { 0xb1, "TRIO_MAP_RDQ5_BUF_EMPTY" }, + { 0xb2, "TRIO_MAP_RDQ6_BUF_EMPTY" }, + { 0xb3, "TRIO_MAP_RDQ7_BUF_EMPTY" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_trio_events_2[] = { + { 0xa0, "TPIO_DATA_BEAT" }, + { 0xa1, "TDMA_DATA_BEAT" }, + { 0xa2, "MAP_DATA_BEAT" }, + { 0xa3, "TXMSG_DATA_BEAT" }, + { 0xa4, "TPIO_DATA_PACKET" }, + { 0xa5, "TDMA_DATA_PACKET" }, + { 0xa6, "MAP_DATA_PACKET" }, + { 0xa7, "TXMSG_DATA_PACKET" }, + { 0xa8, "TDMA_RT_AF" }, + { 0xa9, "TDMA_PBUF_MAC_AF" }, + { 0xaa, "TRIO_MAP_WRQ_BUF_EMPTY" }, + { 0xab, "TRIO_MAP_CPL_BUF_EMPTY" }, + { 0xac, "TRIO_MAP_RDQ0_BUF_EMPTY" }, + { 0xad, "TRIO_MAP_RDQ1_BUF_EMPTY" }, + { 0xae, "TRIO_MAP_RDQ2_BUF_EMPTY" }, + { 0xaf, "TRIO_MAP_RDQ3_BUF_EMPTY" }, + { 0xb0, "TRIO_MAP_RDQ4_BUF_EMPTY" }, + { 0xb1, "TRIO_MAP_RDQ5_BUF_EMPTY" }, + { 0xb2, "TRIO_MAP_RDQ6_BUF_EMPTY" }, + { 0xb3, "TRIO_MAP_RDQ7_BUF_EMPTY" }, + { 0xb4, "TRIO_RING_TX_FLIT_CH0" }, + { 0xb5, "TRIO_RING_TX_FLIT_CH1" }, + { 0xb6, "TRIO_RING_TX_FLIT_CH2" }, + { 0xb7, "TRIO_RING_TX_FLIT_CH3" }, + { 0xb8, "TRIO_RING_TX_FLIT_CH4" }, + { 0xb9, "TRIO_RING_RX_FLIT_CH0" }, + { 0xba, "TRIO_RING_RX_FLIT_CH1" }, + { 0xbb, "TRIO_RING_RX_FLIT_CH2" }, + { 0xbc, "TRIO_RING_RX_FLIT_CH3" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_ecc_events[] = { + { 0x100, "ECC_SINGLE_ERROR_CNT" }, + { 0x104, "ECC_DOUBLE_ERROR_CNT" }, + { 0x114, "SERR_INJ" }, + { 0x118, "DERR_INJ" }, + { 0x124, "ECC_SINGLE_ERROR_0" }, + { 0x164, "ECC_DOUBLE_ERROR_0" }, + { 0x340, "DRAM_ECC_COUNT" }, + { 0x344, "DRAM_ECC_INJECT" }, + { 0x348, "DRAM_ECC_ERROR" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_mss_events[] = { + { 0xc0, "RXREQ_MSS" }, + { 0xc1, "RXDAT_MSS" }, + { 0xc2, "TXRSP_MSS" }, + { 0xc3, "TXDAT_MSS" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_hnf_events[] = { + { 0x45, "HNF_REQUESTS" }, + { 0x46, "HNF_REJECTS" }, + { 0x47, "ALL_BUSY" }, + { 0x48, "MAF_BUSY" }, + { 0x49, "MAF_REQUESTS" }, + { 0x4a, "RNF_REQUESTS" }, + { 0x4b, "REQUEST_TYPE" }, + { 0x4c, "MEMORY_READS" }, + { 0x4d, "MEMORY_WRITES" }, + { 0x4e, "VICTIM_WRITE" }, + { 0x4f, "POC_FULL" }, + { 0x50, "POC_FAIL" }, + { 0x51, "POC_SUCCESS" }, + { 0x52, "POC_WRITES" }, + { 0x53, "POC_READS" }, + { 0x54, "FORWARD" }, + { 0x55, "RXREQ_HNF" }, + { 0x56, "RXRSP_HNF" }, + { 0x57, "RXDAT_HNF" }, + { 0x58, "TXREQ_HNF" }, + { 0x59, "TXRSP_HNF" }, + { 0x5a, "TXDAT_HNF" }, + { 0x5b, "TXSNP_HNF" }, + { 0x5c, "INDEX_MATCH" }, + { 0x5d, "A72_ACCESS" }, + { 0x5e, "IO_ACCESS" }, + { 0x5f, "TSO_WRITE" }, + { 0x60, "TSO_CONFLICT" }, + { 0x61, "DIR_HIT" }, + { 0x62, "HNF_ACCEPTS" }, + { 0x63, "REQ_BUF_EMPTY" }, + { 0x64, "REQ_BUF_IDLE_MAF" }, + { 0x65, "TSO_NOARB" }, + { 0x66, "TSO_NOARB_CYCLES" }, + { 0x67, "MSS_NO_CREDIT" }, + { 0x68, "TXDAT_NO_LCRD" }, + { 0x69, "TXSNP_NO_LCRD" }, + { 0x6a, "TXRSP_NO_LCRD" }, + { 0x6b, "TXREQ_NO_LCRD" }, + { 0x6c, "TSO_CL_MATCH" }, + { 0x6d, "MEMORY_READS_BYPASS" }, + { 0x6e, "TSO_NOARB_TIMEOUT" }, + { 0x6f, "ALLOCATE" }, + { 0x70, "VICTIM" }, + { 0x71, "A72_WRITE" }, + { 0x72, "A72_READ" }, + { 0x73, "IO_WRITE" }, + { 0x74, "IO_READ" }, + { 0x75, "TSO_REJECT" }, + { 0x80, "TXREQ_RN" }, + { 0x81, "TXRSP_RN" }, + { 0x82, "TXDAT_RN" }, + { 0x83, "RXSNP_RN" }, + { 0x84, "RXRSP_RN" }, + { 0x85, "RXDAT_RN" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_hnfnet_events[] = { + { 0x12, "CDN_REQ" }, + { 0x13, "DDN_REQ" }, + { 0x14, "NDN_REQ" }, + { 0x15, "CDN_DIAG_N_OUT_OF_CRED" }, + { 0x16, "CDN_DIAG_S_OUT_OF_CRED" }, + { 0x17, "CDN_DIAG_E_OUT_OF_CRED" }, + { 0x18, "CDN_DIAG_W_OUT_OF_CRED" }, + { 0x19, "CDN_DIAG_C_OUT_OF_CRED" }, + { 0x1a, "CDN_DIAG_N_EGRESS" }, + { 0x1b, "CDN_DIAG_S_EGRESS" }, + { 0x1c, "CDN_DIAG_E_EGRESS" }, + { 0x1d, "CDN_DIAG_W_EGRESS" }, + { 0x1e, "CDN_DIAG_C_EGRESS" }, + { 0x1f, "CDN_DIAG_N_INGRESS" }, + { 0x20, "CDN_DIAG_S_INGRESS" }, + { 0x21, "CDN_DIAG_E_INGRESS" }, + { 0x22, "CDN_DIAG_W_INGRESS" }, + { 0x23, "CDN_DIAG_C_INGRESS" }, + { 0x24, "CDN_DIAG_CORE_SENT" }, + { 0x25, "DDN_DIAG_N_OUT_OF_CRED" }, + { 0x26, "DDN_DIAG_S_OUT_OF_CRED" }, + { 0x27, "DDN_DIAG_E_OUT_OF_CRED" }, + { 0x28, "DDN_DIAG_W_OUT_OF_CRED" }, + { 0x29, "DDN_DIAG_C_OUT_OF_CRED" }, + { 0x2a, "DDN_DIAG_N_EGRESS" }, + { 0x2b, "DDN_DIAG_S_EGRESS" }, + { 0x2c, "DDN_DIAG_E_EGRESS" }, + { 0x2d, "DDN_DIAG_W_EGRESS" }, + { 0x2e, "DDN_DIAG_C_EGRESS" }, + { 0x2f, "DDN_DIAG_N_INGRESS" }, + { 0x30, "DDN_DIAG_S_INGRESS" }, + { 0x31, "DDN_DIAG_E_INGRESS" }, + { 0x32, "DDN_DIAG_W_INGRESS" }, + { 0x33, "DDN_DIAG_C_INGRESS" }, + { 0x34, "DDN_DIAG_CORE_SENT" }, + { 0x35, "NDN_DIAG_S_OUT_OF_CRED" }, + { 0x36, "NDN_DIAG_S_OUT_OF_CRED" }, + { 0x37, "NDN_DIAG_E_OUT_OF_CRED" }, + { 0x38, "NDN_DIAG_W_OUT_OF_CRED" }, + { 0x39, "NDN_DIAG_C_OUT_OF_CRED" }, + { 0x3a, "NDN_DIAG_N_EGRESS" }, + { 0x3b, "NDN_DIAG_S_EGRESS" }, + { 0x3c, "NDN_DIAG_E_EGRESS" }, + { 0x3d, "NDN_DIAG_W_EGRESS" }, + { 0x3e, "NDN_DIAG_C_EGRESS" }, + { 0x3f, "NDN_DIAG_N_INGRESS" }, + { 0x40, "NDN_DIAG_S_INGRESS" }, + { 0x41, "NDN_DIAG_E_INGRESS" }, + { 0x42, "NDN_DIAG_W_INGRESS" }, + { 0x43, "NDN_DIAG_C_INGRESS" }, + { 0x44, "NDN_DIAG_CORE_SENT" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_l3c_events[] = { + { 0x00, "DISABLE" }, + { 0x01, "CYCLES" }, + { 0x02, "TOTAL_RD_REQ_IN" }, + { 0x03, "TOTAL_WR_REQ_IN" }, + { 0x04, "TOTAL_WR_DBID_ACK" }, + { 0x05, "TOTAL_WR_DATA_IN" }, + { 0x06, "TOTAL_WR_COMP" }, + { 0x07, "TOTAL_RD_DATA_OUT" }, + { 0x08, "TOTAL_CDN_REQ_IN_BANK0" }, + { 0x09, "TOTAL_CDN_REQ_IN_BANK1" }, + { 0x0a, "TOTAL_DDN_REQ_IN_BANK0" }, + { 0x0b, "TOTAL_DDN_REQ_IN_BANK1" }, + { 0x0c, "TOTAL_EMEM_RD_RES_IN_BANK0" }, + { 0x0d, "TOTAL_EMEM_RD_RES_IN_BANK1" }, + { 0x0e, "TOTAL_CACHE_RD_RES_IN_BANK0" }, + { 0x0f, "TOTAL_CACHE_RD_RES_IN_BANK1" }, + { 0x10, "TOTAL_EMEM_RD_REQ_BANK0" }, + { 0x11, "TOTAL_EMEM_RD_REQ_BANK1" }, + { 0x12, "TOTAL_EMEM_WR_REQ_BANK0" }, + { 0x13, "TOTAL_EMEM_WR_REQ_BANK1" }, + { 0x14, "TOTAL_RD_REQ_OUT" }, + { 0x15, "TOTAL_WR_REQ_OUT" }, + { 0x16, "TOTAL_RD_RES_IN" }, + { 0x17, "HITS_BANK0" }, + { 0x18, "HITS_BANK1" }, + { 0x19, "MISSES_BANK0" }, + { 0x1a, "MISSES_BANK1" }, + { 0x1b, "ALLOCATIONS_BANK0" }, + { 0x1c, "ALLOCATIONS_BANK1" }, + { 0x1d, "EVICTIONS_BANK0" }, + { 0x1e, "EVICTIONS_BANK1" }, + { 0x1f, "DBID_REJECT" }, + { 0x20, "WRDB_REJECT_BANK0" }, + { 0x21, "WRDB_REJECT_BANK1" }, + { 0x22, "CMDQ_REJECT_BANK0" }, + { 0x23, "CMDQ_REJECT_BANK1" }, + { 0x24, "COB_REJECT_BANK0" }, + { 0x25, "COB_REJECT_BANK1" }, + { 0x26, "TRB_REJECT_BANK0" }, + { 0x27, "TRB_REJECT_BANK1" }, + { 0x28, "TAG_REJECT_BANK0" }, + { 0x29, "TAG_REJECT_BANK1" }, + { 0x2a, "ANY_REJECT_BANK0" }, + { 0x2b, "ANY_REJECT_BANK1" }, +}; + +static struct mlxbf_pmc_context *pmc; + +/* UUID used to probe ATF service. */ +static const char *mlxbf_pmc_svc_uuid_str = "89c036b4-e7d7-11e6-8797-001aca00bfc4"; + +/* Calls an SMC to access a performance register */ +static int mlxbf_pmc_secure_read(void __iomem *addr, uint32_t command, + uint64_t *result) +{ + struct arm_smccc_res res; + int status, err = 0; + + arm_smccc_smc(command, pmc->sreg_tbl_perf, (uintptr_t)addr, 0, 0, 0, 0, + 0, &res); + + status = res.a0; + + switch (status) { + case PSCI_RET_NOT_SUPPORTED: + err = -EINVAL; + break; + case MLXBF_PMC_SMCCC_ACCESS_VIOLATION: + err = -EACCES; + break; + default: + *result = res.a1; + break; + } + + return err; +} + +/* Read from a performance counter */ +static int mlxbf_pmc_read(void __iomem *addr, uint32_t command, + uint64_t *result) +{ + if (pmc->svc_sreg_support) + return mlxbf_pmc_secure_read(addr, command, result); + + if (command == MLXBF_PMC_READ_REG_32) + *result = readl(addr); + else + *result = readq(addr); + + return 0; +} + +/* Convenience function for 32-bit reads */ +static int mlxbf_pmc_readl(void __iomem *addr, uint32_t *result) +{ + uint64_t read_out; + int status; + + status = mlxbf_pmc_read(addr, MLXBF_PMC_READ_REG_32, &read_out); + if (status) + return status; + *result = (uint32_t)read_out; + + return 0; +} + +/* Calls an SMC to access a performance register */ +static int mlxbf_pmc_secure_write(void __iomem *addr, uint32_t command, + uint64_t value) +{ + struct arm_smccc_res res; + int status, err = 0; + + arm_smccc_smc(command, pmc->sreg_tbl_perf, value, (uintptr_t)addr, 0, 0, + 0, 0, &res); + + status = res.a0; + + switch (status) { + case PSCI_RET_NOT_SUPPORTED: + err = -EINVAL; + break; + case MLXBF_PMC_SMCCC_ACCESS_VIOLATION: + err = -EACCES; + break; + } + + return err; +} + +/* Write to a performance counter */ +static int mlxbf_pmc_write(void __iomem *addr, int command, uint64_t value) +{ + if (pmc->svc_sreg_support) + return mlxbf_pmc_secure_write(addr, command, value); + + if (command == MLXBF_PMC_WRITE_REG_32) + writel(value, addr); + else + writeq(value, addr); + + return 0; +} + +/* Check if the register offset is within the mapped region for the block */ +static bool mlxbf_pmc_valid_range(int blk_num, uint32_t offset) +{ + if ((offset >= 0) && !(offset % MLXBF_PMC_REG_SIZE) && + (offset + MLXBF_PMC_REG_SIZE <= pmc->block[blk_num].blk_size)) + return true; /* inside the mapped PMC space */ + + return false; +} + +/* Get the event list corresponding to a certain block */ +static const struct mlxbf_pmc_events *mlxbf_pmc_event_list(const char *blk, + int *size) +{ + const struct mlxbf_pmc_events *events; + + if (strstr(blk, "tilenet")) { + events = mlxbf_pmc_hnfnet_events; + *size = ARRAY_SIZE(mlxbf_pmc_hnfnet_events); + } else if (strstr(blk, "tile")) { + events = mlxbf_pmc_hnf_events; + *size = ARRAY_SIZE(mlxbf_pmc_hnf_events); + } else if (strstr(blk, "triogen")) { + events = mlxbf_pmc_smgen_events; + *size = ARRAY_SIZE(mlxbf_pmc_smgen_events); + } else if (strstr(blk, "trio")) { + switch (pmc->event_set) { + case MLXBF_PMC_EVENT_SET_BF1: + events = mlxbf_pmc_trio_events_1; + *size = ARRAY_SIZE(mlxbf_pmc_trio_events_1); + break; + case MLXBF_PMC_EVENT_SET_BF2: + events = mlxbf_pmc_trio_events_2; + *size = ARRAY_SIZE(mlxbf_pmc_trio_events_2); + break; + default: + events = NULL; + *size = 0; + break; + } + } else if (strstr(blk, "mss")) { + events = mlxbf_pmc_mss_events; + *size = ARRAY_SIZE(mlxbf_pmc_mss_events); + } else if (strstr(blk, "ecc")) { + events = mlxbf_pmc_ecc_events; + *size = ARRAY_SIZE(mlxbf_pmc_ecc_events); + } else if (strstr(blk, "pcie")) { + events = mlxbf_pmc_pcie_events; + *size = ARRAY_SIZE(mlxbf_pmc_pcie_events); + } else if (strstr(blk, "l3cache")) { + events = mlxbf_pmc_l3c_events; + *size = ARRAY_SIZE(mlxbf_pmc_l3c_events); + } else if (strstr(blk, "gic")) { + events = mlxbf_pmc_smgen_events; + *size = ARRAY_SIZE(mlxbf_pmc_smgen_events); + } else if (strstr(blk, "smmu")) { + events = mlxbf_pmc_smgen_events; + *size = ARRAY_SIZE(mlxbf_pmc_smgen_events); + } else { + events = NULL; + *size = 0; + } + + return events; +} + +/* Get the event number given the name */ +static int mlxbf_pmc_get_event_num(const char *blk, const char *evt) +{ + const struct mlxbf_pmc_events *events; + int i, size; + + events = mlxbf_pmc_event_list(blk, &size); + if (!events) + return -EINVAL; + + for (i = 0; i < size; ++i) { + if (!strcmp(evt, events[i].evt_name)) + return events[i].evt_num; + } + + return -ENODEV; +} + +/* Get the event number given the name */ +static char *mlxbf_pmc_get_event_name(const char *blk, int evt) +{ + const struct mlxbf_pmc_events *events; + int i, size; + + events = mlxbf_pmc_event_list(blk, &size); + if (!events) + return NULL; + + for (i = 0; i < size; ++i) { + if (evt == events[i].evt_num) + return events[i].evt_name; + } + + return NULL; +} + +/* Method to enable/disable/reset l3cache counters */ +static int mlxbf_pmc_config_l3_counters(int blk_num, bool enable, bool reset) +{ + uint32_t perfcnt_cfg = 0; + + if (enable) + perfcnt_cfg |= MLXBF_PMC_L3C_PERF_CNT_CFG_EN; + if (reset) + perfcnt_cfg |= MLXBF_PMC_L3C_PERF_CNT_CFG_RST; + + return mlxbf_pmc_write(pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_CFG, + MLXBF_PMC_WRITE_REG_32, perfcnt_cfg); +} + +/* Method to handle l3cache counter programming */ +static int mlxbf_pmc_program_l3_counter(int blk_num, uint32_t cnt_num, + uint32_t evt) +{ + uint32_t perfcnt_sel_1 = 0; + uint32_t perfcnt_sel = 0; + uint32_t *wordaddr; + void __iomem *pmcaddr; + int ret; + + /* Disable all counters before programming them */ + if (mlxbf_pmc_config_l3_counters(blk_num, false, false)) + return -EINVAL; + + /* Select appropriate register information */ + switch (cnt_num) { + case 0 ... 3: + pmcaddr = pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_SEL; + wordaddr = &perfcnt_sel; + break; + case 4: + pmcaddr = pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_SEL_1; + wordaddr = &perfcnt_sel_1; + break; + default: + return -EINVAL; + } + + ret = mlxbf_pmc_readl(pmcaddr, wordaddr); + if (ret) + return ret; + + switch (cnt_num) { + case 0: + perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0; + perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0, + evt); + break; + case 1: + perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1; + perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1, + evt); + break; + case 2: + perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2; + perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2, + evt); + break; + case 3: + perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3; + perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3, + evt); + break; + case 4: + perfcnt_sel_1 &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4; + perfcnt_sel_1 |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4, + evt); + break; + default: + return -EINVAL; + } + + return mlxbf_pmc_write(pmcaddr, MLXBF_PMC_WRITE_REG_32, *wordaddr); +} + +/* Method to program a counter to monitor an event */ +static int mlxbf_pmc_program_counter(int blk_num, uint32_t cnt_num, + uint32_t evt, bool is_l3) +{ + uint64_t perfctl, perfevt, perfmon_cfg; + + if (cnt_num >= pmc->block[blk_num].counters) + return -ENODEV; + + if (is_l3) + return mlxbf_pmc_program_l3_counter(blk_num, cnt_num, evt); + + /* Configure the counter */ + perfctl = FIELD_PREP(MLXBF_PMC_PERFCTL_EN0, 1); + perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_EB0, 0); + perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_ETRIG0, 1); + perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_AD0, 0); + perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_ACCM0, 0); + perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_MS0, 0); + perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_FM0, 0); + + perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WDATA, perfctl); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR, + MLXBF_PMC_PERFCTL); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 1); + + if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + + cnt_num * MLXBF_PMC_REG_SIZE, + MLXBF_PMC_WRITE_REG_64, perfmon_cfg)) + return -EFAULT; + + /* Select the event */ + perfevt = FIELD_PREP(MLXBF_PMC_PERFEVT_EVTSEL, evt); + + perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WDATA, perfevt); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR, + MLXBF_PMC_PERFEVT); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 1); + + if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + + cnt_num * MLXBF_PMC_REG_SIZE, + MLXBF_PMC_WRITE_REG_64, perfmon_cfg)) + return -EFAULT; + + /* Clear the accumulator */ + perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR, + MLXBF_PMC_PERFACC0); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 1); + + if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + + cnt_num * MLXBF_PMC_REG_SIZE, + MLXBF_PMC_WRITE_REG_64, perfmon_cfg)) + return -EFAULT; + + return 0; +} + +/* Method to handle l3 counter reads */ +static int mlxbf_pmc_read_l3_counter(int blk_num, uint32_t cnt_num, + uint64_t *result) +{ + uint32_t perfcnt_low = 0, perfcnt_high = 0; + uint64_t value; + int status = 0; + + status = mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_LOW + + cnt_num * MLXBF_PMC_L3C_REG_SIZE, + &perfcnt_low); + + if (status) + return status; + + status = mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_HIGH + + cnt_num * MLXBF_PMC_L3C_REG_SIZE, + &perfcnt_high); + + if (status) + return status; + + value = perfcnt_high; + value = value << 32; + value |= perfcnt_low; + *result = value; + + return 0; +} + +/* Method to read the counter value */ +static int mlxbf_pmc_read_counter(int blk_num, uint32_t cnt_num, bool is_l3, + uint64_t *result) +{ + uint32_t perfcfg_offset, perfval_offset; + uint64_t perfmon_cfg; + int status; + + if (cnt_num >= pmc->block[blk_num].counters) + return -EINVAL; + + if (is_l3) + return mlxbf_pmc_read_l3_counter(blk_num, cnt_num, result); + + perfcfg_offset = cnt_num * MLXBF_PMC_REG_SIZE; + perfval_offset = perfcfg_offset + + pmc->block[blk_num].counters * MLXBF_PMC_REG_SIZE; + + /* Set counter in "read" mode */ + perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR, + MLXBF_PMC_PERFACC0); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 0); + + status = mlxbf_pmc_write(pmc->block[blk_num].mmio_base + perfcfg_offset, + MLXBF_PMC_WRITE_REG_64, perfmon_cfg); + + if (status) + return status; + + /* Get the counter value */ + return mlxbf_pmc_read(pmc->block[blk_num].mmio_base + perfval_offset, + MLXBF_PMC_READ_REG_64, result); +} + +/* Method to read L3 block event */ +static int mlxbf_pmc_read_l3_event(int blk_num, uint32_t cnt_num, + uint64_t *result) +{ + uint32_t perfcnt_sel = 0, perfcnt_sel_1 = 0; + uint32_t *wordaddr; + void __iomem *pmcaddr; + uint64_t evt; + + /* Select appropriate register information */ + switch (cnt_num) { + case 0 ... 3: + pmcaddr = pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_SEL; + wordaddr = &perfcnt_sel; + break; + case 4: + pmcaddr = pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_SEL_1; + wordaddr = &perfcnt_sel_1; + break; + default: + return -EINVAL; + } + + if (mlxbf_pmc_readl(pmcaddr, wordaddr)) + return -EINVAL; + + /* Read from appropriate register field for the counter */ + switch (cnt_num) { + case 0: + evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0, perfcnt_sel); + break; + case 1: + evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1, perfcnt_sel); + break; + case 2: + evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2, perfcnt_sel); + break; + case 3: + evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3, perfcnt_sel); + break; + case 4: + evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4, + perfcnt_sel_1); + break; + default: + return -EINVAL; + } + *result = evt; + + return 0; +} + +/* Method to find the event currently being monitored by a counter */ +static int mlxbf_pmc_read_event(int blk_num, uint32_t cnt_num, bool is_l3, + uint64_t *result) +{ + uint32_t perfcfg_offset, perfval_offset; + uint64_t perfmon_cfg, perfevt, perfctl; + + if (cnt_num >= pmc->block[blk_num].counters) + return -EINVAL; + + if (is_l3) + return mlxbf_pmc_read_l3_event(blk_num, cnt_num, result); + + perfcfg_offset = cnt_num * MLXBF_PMC_REG_SIZE; + perfval_offset = perfcfg_offset + + pmc->block[blk_num].counters * MLXBF_PMC_REG_SIZE; + + /* Set counter in "read" mode */ + perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR, + MLXBF_PMC_PERFCTL); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 0); + + if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + perfcfg_offset, + MLXBF_PMC_WRITE_REG_64, perfmon_cfg)) + return -EFAULT; + + /* Check if the counter is enabled */ + + if (mlxbf_pmc_read(pmc->block[blk_num].mmio_base + perfval_offset, + MLXBF_PMC_READ_REG_64, &perfctl)) + return -EFAULT; + + if (!FIELD_GET(MLXBF_PMC_PERFCTL_EN0, perfctl)) + return -EINVAL; + + /* Set counter in "read" mode */ + perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR, + MLXBF_PMC_PERFEVT); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1); + perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 0); + + if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + perfcfg_offset, + MLXBF_PMC_WRITE_REG_64, perfmon_cfg)) + return -EFAULT; + + /* Get the event number */ + if (mlxbf_pmc_read(pmc->block[blk_num].mmio_base + perfval_offset, + MLXBF_PMC_READ_REG_64, &perfevt)) + return -EFAULT; + + *result = FIELD_GET(MLXBF_PMC_PERFEVT_EVTSEL, perfevt); + + return 0; +} + +/* Method to read a register */ +static int mlxbf_pmc_read_reg(int blk_num, uint32_t offset, uint64_t *result) +{ + uint32_t ecc_out; + + if (strstr(pmc->block_name[blk_num], "ecc")) { + if (mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + offset, + &ecc_out)) + return -EFAULT; + + *result = ecc_out; + return 0; + } + + if (mlxbf_pmc_valid_range(blk_num, offset)) + return mlxbf_pmc_read(pmc->block[blk_num].mmio_base + offset, + MLXBF_PMC_READ_REG_64, result); + + return -EINVAL; +} + +/* Method to write to a register */ +static int mlxbf_pmc_write_reg(int blk_num, uint32_t offset, uint64_t data) +{ + if (strstr(pmc->block_name[blk_num], "ecc")) { + return mlxbf_pmc_write(pmc->block[blk_num].mmio_base + offset, + MLXBF_PMC_WRITE_REG_32, data); + } + + if (mlxbf_pmc_valid_range(blk_num, offset)) + return mlxbf_pmc_write(pmc->block[blk_num].mmio_base + offset, + MLXBF_PMC_WRITE_REG_64, data); + + return -EINVAL; +} + +/* Show function for "counter" sysfs files */ +static ssize_t mlxbf_pmc_counter_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mlxbf_pmc_attribute *attr_counter = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + int blk_num, cnt_num, offset; + bool is_l3 = false; + uint64_t value; + + blk_num = attr_counter->nr; + cnt_num = attr_counter->index; + + if (strstr(pmc->block_name[blk_num], "l3cache")) + is_l3 = true; + + if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_COUNTER) { + if (mlxbf_pmc_read_counter(blk_num, cnt_num, is_l3, &value)) + return -EINVAL; + } else if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_REGISTER) { + offset = mlxbf_pmc_get_event_num(pmc->block_name[blk_num], + attr->attr.name); + if (offset < 0) + return -EINVAL; + if (mlxbf_pmc_read_reg(blk_num, offset, &value)) + return -EINVAL; + } else + return -EINVAL; + + return sprintf(buf, "0x%llx\n", value); +} + +/* Store function for "counter" sysfs files */ +static ssize_t mlxbf_pmc_counter_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mlxbf_pmc_attribute *attr_counter = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + int blk_num, cnt_num, offset, err, data; + bool is_l3 = false; + uint64_t evt_num; + + blk_num = attr_counter->nr; + cnt_num = attr_counter->index; + + err = kstrtoint(buf, 0, &data); + if (err < 0) + return err; + + /* Allow non-zero writes only to the ecc regs */ + if (!(strstr(pmc->block_name[blk_num], "ecc")) && data) + return -EINVAL; + + /* Do not allow writes to the L3C regs */ + if (strstr(pmc->block_name[blk_num], "l3cache")) + return -EINVAL; + + if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_COUNTER) { + err = mlxbf_pmc_read_event(blk_num, cnt_num, is_l3, &evt_num); + if (err) + return err; + err = mlxbf_pmc_program_counter(blk_num, cnt_num, evt_num, + is_l3); + if (err) + return err; + } else if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_REGISTER) { + offset = mlxbf_pmc_get_event_num(pmc->block_name[blk_num], + attr->attr.name); + if (offset < 0) + return -EINVAL; + err = mlxbf_pmc_write_reg(blk_num, offset, data); + if (err) + return err; + } else + return -EINVAL; + + return count; +} + +/* Show function for "event" sysfs files */ +static ssize_t mlxbf_pmc_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mlxbf_pmc_attribute *attr_event = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + int blk_num, cnt_num, err; + bool is_l3 = false; + uint64_t evt_num; + char *evt_name; + + blk_num = attr_event->nr; + cnt_num = attr_event->index; + + if (strstr(pmc->block_name[blk_num], "l3cache")) + is_l3 = true; + + err = mlxbf_pmc_read_event(blk_num, cnt_num, is_l3, &evt_num); + if (err) + return sprintf(buf, "No event being monitored\n"); + + evt_name = mlxbf_pmc_get_event_name(pmc->block_name[blk_num], evt_num); + if (!evt_name) + return -EINVAL; + + return sprintf(buf, "0x%llx: %s\n", evt_num, evt_name); +} + +/* Store function for "event" sysfs files */ +static ssize_t mlxbf_pmc_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mlxbf_pmc_attribute *attr_event = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + int blk_num, cnt_num, evt_num, err; + bool is_l3 = false; + + blk_num = attr_event->nr; + cnt_num = attr_event->index; + + if (isalpha(buf[0])) { + evt_num = mlxbf_pmc_get_event_num(pmc->block_name[blk_num], + buf); + if (evt_num < 0) + return -EINVAL; + } else { + err = kstrtoint(buf, 0, &evt_num); + if (err < 0) + return err; + } + + if (strstr(pmc->block_name[blk_num], "l3cache")) + is_l3 = true; + + err = mlxbf_pmc_program_counter(blk_num, cnt_num, evt_num, is_l3); + if (err) + return err; + + return count; +} + +/* Show function for "event_list" sysfs files */ +static ssize_t mlxbf_pmc_event_list_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mlxbf_pmc_attribute *attr_event_list = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + int blk_num, i, size, len = 0, ret = 0; + const struct mlxbf_pmc_events *events; + char e_info[MLXBF_PMC_EVENT_INFO_LEN]; + + blk_num = attr_event_list->nr; + + events = mlxbf_pmc_event_list(pmc->block_name[blk_num], &size); + if (!events) + return -EINVAL; + + for (i = 0, buf[0] = '\0'; i < size; ++i) { + len += sprintf(e_info, "0x%x: %s\n", events[i].evt_num, + events[i].evt_name); + if (len > PAGE_SIZE) + break; + strcat(buf, e_info); + ret = len; + } + + return ret; +} + +/* Show function for "enable" sysfs files - only for l3cache */ +static ssize_t mlxbf_pmc_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mlxbf_pmc_attribute *attr_enable = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + uint32_t perfcnt_cfg; + int blk_num, value; + + blk_num = attr_enable->nr; + + if (mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + + MLXBF_PMC_L3C_PERF_CNT_CFG, + &perfcnt_cfg)) + return -EINVAL; + + value = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_CFG_EN, perfcnt_cfg); + + return sprintf(buf, "%d\n", value); +} + +/* Store function for "enable" sysfs files - only for l3cache */ +static ssize_t mlxbf_pmc_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mlxbf_pmc_attribute *attr_enable = container_of( + attr, struct mlxbf_pmc_attribute, dev_attr); + int err, en, blk_num; + + blk_num = attr_enable->nr; + + err = kstrtoint(buf, 0, &en); + if (err < 0) + return err; + + if (!en) { + err = mlxbf_pmc_config_l3_counters(blk_num, false, false); + if (err) + return err; + } else if (en == 1) { + err = mlxbf_pmc_config_l3_counters(blk_num, false, true); + if (err) + return err; + err = mlxbf_pmc_config_l3_counters(blk_num, true, false); + if (err) + return err; + } else + return -EINVAL; + + return count; +} + +/* Populate attributes for blocks with counters to monitor performance */ +static int mlxbf_pmc_init_perftype_counter(struct device *dev, int blk_num) +{ + struct mlxbf_pmc_attribute *attr; + int i = 0, j = 0; + + /* "event_list" sysfs to list events supported by the block */ + attr = &pmc->block[blk_num].attr_event_list; + attr->dev_attr.attr.mode = 0444; + attr->dev_attr.show = mlxbf_pmc_event_list_show; + attr->nr = blk_num; + attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, "event_list"); + pmc->block[blk_num].block_attr[i] = &attr->dev_attr.attr; + attr = NULL; + + /* "enable" sysfs to start/stop the counters. Only in L3C blocks */ + if (strstr(pmc->block_name[blk_num], "l3cache")) { + attr = &pmc->block[blk_num].attr_enable; + attr->dev_attr.attr.mode = 0644; + attr->dev_attr.show = mlxbf_pmc_enable_show; + attr->dev_attr.store = mlxbf_pmc_enable_store; + attr->nr = blk_num; + attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "enable"); + pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr; + attr = NULL; + } + + pmc->block[blk_num].attr_counter = devm_kcalloc( + dev, pmc->block[blk_num].counters, + sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL); + if (!pmc->block[blk_num].attr_counter) + return -ENOMEM; + + pmc->block[blk_num].attr_event = devm_kcalloc( + dev, pmc->block[blk_num].counters, + sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL); + if (!pmc->block[blk_num].attr_event) + return -ENOMEM; + + /* "eventX" and "counterX" sysfs to program and read counter values */ + for (j = 0; j < pmc->block[blk_num].counters; ++j) { + attr = &pmc->block[blk_num].attr_counter[j]; + attr->dev_attr.attr.mode = 0644; + attr->dev_attr.show = mlxbf_pmc_counter_show; + attr->dev_attr.store = mlxbf_pmc_counter_store; + attr->index = j; + attr->nr = blk_num; + attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "counter%d", j); + pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr; + attr = NULL; + + attr = &pmc->block[blk_num].attr_event[j]; + attr->dev_attr.attr.mode = 0644; + attr->dev_attr.show = mlxbf_pmc_event_show; + attr->dev_attr.store = mlxbf_pmc_event_store; + attr->index = j; + attr->nr = blk_num; + attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "event%d", j); + pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr; + attr = NULL; + } + + return 0; +} + +/* Populate attributes for blocks with registers to monitor performance */ +static int mlxbf_pmc_init_perftype_reg(struct device *dev, int blk_num) +{ + struct mlxbf_pmc_attribute *attr; + const struct mlxbf_pmc_events *events; + int i = 0, j = 0; + + events = mlxbf_pmc_event_list(pmc->block_name[blk_num], &j); + if (!events) + return -EINVAL; + + pmc->block[blk_num].attr_event = devm_kcalloc( + dev, j, sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL); + if (!pmc->block[blk_num].attr_event) + return -ENOMEM; + + while (j > 0) { + --j; + attr = &pmc->block[blk_num].attr_event[j]; + attr->dev_attr.attr.mode = 0644; + attr->dev_attr.show = mlxbf_pmc_counter_show; + attr->dev_attr.store = mlxbf_pmc_counter_store; + attr->nr = blk_num; + attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + events[j].evt_name); + pmc->block[blk_num].block_attr[i] = &attr->dev_attr.attr; + attr = NULL; + i++; + } + + return 0; +} + +/* Helper to create the bfperf sysfs sub-directories and files */ +static int mlxbf_pmc_create_groups(struct device *dev, int blk_num) +{ + int err; + + /* Populate attributes based on counter type */ + if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_COUNTER) + err = mlxbf_pmc_init_perftype_counter(dev, blk_num); + else if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_REGISTER) + err = mlxbf_pmc_init_perftype_reg(dev, blk_num); + else + err = -EINVAL; + + if (err) + return err; + + /* Add a new attribute_group for the block */ + pmc->block[blk_num].block_attr_grp.attrs = pmc->block[blk_num].block_attr; + pmc->block[blk_num].block_attr_grp.name = devm_kasprintf( + dev, GFP_KERNEL, pmc->block_name[blk_num]); + pmc->groups[blk_num] = &pmc->block[blk_num].block_attr_grp; + + return 0; +} + +static bool mlxbf_pmc_guid_match(const guid_t *guid, + const struct arm_smccc_res *res) +{ + guid_t id = GUID_INIT(res->a0, res->a1, res->a1 >> 16, res->a2, + res->a2 >> 8, res->a2 >> 16, res->a2 >> 24, + res->a3, res->a3 >> 8, res->a3 >> 16, + res->a3 >> 24); + + return guid_equal(guid, &id); +} + +/* Helper to map the Performance Counters from the varios blocks */ +static int mlxbf_pmc_map_counters(struct device *dev) +{ + uint64_t info[MLXBF_PMC_INFO_SZ]; + int i, tile_num, ret; + + for (i = 0; i < pmc->total_blocks; ++i) { + if (strstr(pmc->block_name[i], "tile")) { + ret = sscanf(pmc->block_name[i], "tile%d", &tile_num); + if (ret < 0) + return ret; + + if (tile_num >= pmc->tile_count) + continue; + } + ret = device_property_read_u64_array(dev, pmc->block_name[i], + info, MLXBF_PMC_INFO_SZ); + if (ret) + return ret; + + /* + * Do not remap if the proper SMC calls are supported, + * since the SMC calls expect physical addresses. + */ + if (pmc->svc_sreg_support) + pmc->block[i].mmio_base = (void __iomem *)info[0]; + else + pmc->block[i].mmio_base = + devm_ioremap(dev, info[0], info[1]); + + pmc->block[i].blk_size = info[1]; + pmc->block[i].counters = info[2]; + pmc->block[i].type = info[3]; + + if (IS_ERR(pmc->block[i].mmio_base)) + return PTR_ERR(pmc->block[i].mmio_base); + + ret = mlxbf_pmc_create_groups(dev, i); + if (ret) + return ret; + } + + return 0; +} + +static int mlxbf_pmc_probe(struct platform_device *pdev) +{ + struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); + const char *hid = acpi_device_hid(acpi_dev); + struct device *dev = &pdev->dev; + struct arm_smccc_res res; + guid_t guid; + int ret; + + /* Ensure we have the UUID we expect for this service. */ + arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + guid_parse(mlxbf_pmc_svc_uuid_str, &guid); + if (!mlxbf_pmc_guid_match(&guid, &res)) + return -ENODEV; + + pmc = devm_kzalloc(dev, sizeof(struct mlxbf_pmc_context), GFP_KERNEL); + if (!pmc) + return -ENOMEM; + + /* + * ACPI indicates whether we use SMCs to access registers or not. + * If sreg_tbl_perf is not present, just assume we're not using SMCs. + */ + ret = device_property_read_u32(dev, "sec_reg_block", + &pmc->sreg_tbl_perf); + if (ret) { + pmc->svc_sreg_support = false; + } else { + /* + * Check service version to see if we actually do support the + * needed SMCs. If we have the calls we need, mark support for + * them in the pmc struct. + */ + arm_smccc_smc(MLXBF_PMC_SIP_SVC_VERSION, 0, 0, 0, 0, 0, 0, 0, + &res); + if (res.a0 == MLXBF_PMC_SVC_REQ_MAJOR && + res.a1 >= MLXBF_PMC_SVC_MIN_MINOR) + pmc->svc_sreg_support = true; + else + return -EINVAL; + } + + if (!strcmp(hid, "MLNXBFD0")) + pmc->event_set = MLXBF_PMC_EVENT_SET_BF1; + else if (!strcmp(hid, "MLNXBFD1")) + pmc->event_set = MLXBF_PMC_EVENT_SET_BF2; + else + return -ENODEV; + + ret = device_property_read_u32(dev, "block_num", &pmc->total_blocks); + if (ret) + return ret; + + ret = device_property_read_string_array(dev, "block_name", + pmc->block_name, + pmc->total_blocks); + if (ret != pmc->total_blocks) + return -EFAULT; + + ret = device_property_read_u32(dev, "tile_num", &pmc->tile_count); + if (ret) + return ret; + + pmc->pdev = pdev; + + ret = mlxbf_pmc_map_counters(dev); + if (ret) + return ret; + + pmc->hwmon_dev = devm_hwmon_device_register_with_groups( + dev, "bfperf", pmc, pmc->groups); + platform_set_drvdata(pdev, pmc); + + return 0; +} + +static const struct acpi_device_id mlxbf_pmc_acpi_ids[] = { { "MLNXBFD0", 0 }, + { "MLNXBFD1", 0 }, + {}, }; + +MODULE_DEVICE_TABLE(acpi, mlxbf_pmc_acpi_ids); +static struct platform_driver pmc_driver = { + .driver = { .name = "mlxbf-pmc", + .acpi_match_table = ACPI_PTR(mlxbf_pmc_acpi_ids), }, + .probe = mlxbf_pmc_probe, +}; + +module_platform_driver(pmc_driver); + +MODULE_AUTHOR("Shravan Kumar Ramani <sramani@mellanox.com>"); +MODULE_DESCRIPTION("Mellanox PMC driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig new file mode 100644 index 000000000000..33040b0b3b79 --- /dev/null +++ b/drivers/platform/surface/Kconfig @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Microsoft Surface Platform-Specific Drivers +# + +menuconfig SURFACE_PLATFORMS + bool "Microsoft Surface Platform-Specific Device Drivers" + default y + help + Say Y here to get to see options for platform-specific device drivers + for Microsoft Surface devices. This option alone does not add any + kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if SURFACE_PLATFORMS + +config SURFACE3_WMI + tristate "Surface 3 WMI Driver" + depends on ACPI_WMI + depends on DMI + depends on INPUT + depends on SPI + help + Say Y here if you have a Surface 3. + + To compile this driver as a module, choose M here: the module will + be called surface3-wmi. + +config SURFACE_3_BUTTON + tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" + depends on ACPI && KEYBOARD_GPIO && I2C + help + This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. + +config SURFACE_3_POWER_OPREGION + tristate "Surface 3 battery platform operation region support" + depends on ACPI && I2C + help + This driver provides support for ACPI operation + region of the Surface 3 battery platform driver. + +config SURFACE_GPE + tristate "Surface GPE/Lid Support Driver" + depends on ACPI + depends on DMI + help + This driver marks the GPEs related to the ACPI lid device found on + Microsoft Surface devices as wakeup sources and prepares them + accordingly. It is required on those devices to allow wake-ups from + suspend by opening the lid. + +config SURFACE_PRO3_BUTTON + tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" + depends on ACPI && INPUT + help + This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. + +endif # SURFACE_PLATFORMS diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile new file mode 100644 index 000000000000..cedfb027ded1 --- /dev/null +++ b/drivers/platform/surface/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/surface +# Microsoft Surface Platform-Specific Drivers +# + +obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o +obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o +obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o +obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o +obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 130b6f52a600..130b6f52a600 100644 --- a/drivers/platform/x86/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/surface/surface3_button.c index 48d77e7aae76..48d77e7aae76 100644 --- a/drivers/platform/x86/surface3_button.c +++ b/drivers/platform/surface/surface3_button.c diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/surface/surface3_power.c index cc4f9cba6856..cc4f9cba6856 100644 --- a/drivers/platform/x86/surface3_power.c +++ b/drivers/platform/surface/surface3_power.c diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c new file mode 100644 index 000000000000..e49e5d6d5d4e --- /dev/null +++ b/drivers/platform/surface/surface_gpe.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Surface GPE/Lid driver to enable wakeup from suspend via the lid by + * properly configuring the respective GPEs. Required for wakeup via lid on + * newer Intel-based Microsoft Surface devices. + * + * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +/* + * Note: The GPE numbers for the lid devices found below have been obtained + * from ACPI/the DSDT table, specifically from the GPE handler for the + * lid. + */ + +static const struct property_entry lid_device_props_l17[] = { + PROPERTY_ENTRY_U32("gpe", 0x17), + {}, +}; + +static const struct property_entry lid_device_props_l4D[] = { + PROPERTY_ENTRY_U32("gpe", 0x4D), + {}, +}; + +static const struct property_entry lid_device_props_l4F[] = { + PROPERTY_ENTRY_U32("gpe", 0x4F), + {}, +}; + +static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +}; + +/* + * Note: When changing this, don't forget to check that the MODULE_ALIAS below + * still fits. + */ +static const struct dmi_system_id dmi_lid_device_table[] = { + { + .ident = "Surface Pro 4", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)lid_device_props_l17, + }, + { + .ident = "Surface Pro 5", + .matches = { + /* + * We match for SKU here due to generic product name + * "Surface Pro". + */ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)lid_device_props_l4F, + }, + { + .ident = "Surface Pro 5 (LTE)", + .matches = { + /* + * We match for SKU here due to generic product name + * "Surface Pro" + */ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)lid_device_props_l4F, + }, + { + .ident = "Surface Pro 6", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)lid_device_props_l4F, + }, + { + .ident = "Surface Pro 7", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), + }, + .driver_data = (void *)lid_device_props_l4D, + }, + { + .ident = "Surface Book 1", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)lid_device_props_l17, + }, + { + .ident = "Surface Book 2", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)lid_device_props_l17, + }, + { + .ident = "Surface Book 3", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"), + }, + .driver_data = (void *)lid_device_props_l4D, + }, + { + .ident = "Surface Laptop 1", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)lid_device_props_l57, + }, + { + .ident = "Surface Laptop 2", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)lid_device_props_l57, + }, + { + .ident = "Surface Laptop 3 (Intel 13\")", + .matches = { + /* + * We match for SKU here due to different variants: The + * AMD (15") version does not rely on GPEs. + */ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), + }, + .driver_data = (void *)lid_device_props_l4D, + }, + { + .ident = "Surface Laptop 3 (Intel 15\")", + .matches = { + /* + * We match for SKU here due to different variants: The + * AMD (15") version does not rely on GPEs. + */ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"), + }, + .driver_data = (void *)lid_device_props_l4D, + }, + { } +}; + +struct surface_lid_device { + u32 gpe_number; +}; + +static int surface_lid_enable_wakeup(struct device *dev, bool enable) +{ + const struct surface_lid_device *lid = dev_get_drvdata(dev); + int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; + acpi_status status; + + status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action); + if (ACPI_FAILURE(status)) { + dev_err(dev, "failed to set GPE wake mask: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } + + return 0; +} + +static int surface_gpe_suspend(struct device *dev) +{ + return surface_lid_enable_wakeup(dev, true); +} + +static int surface_gpe_resume(struct device *dev) +{ + return surface_lid_enable_wakeup(dev, false); +} + +static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume); + +static int surface_gpe_probe(struct platform_device *pdev) +{ + struct surface_lid_device *lid; + u32 gpe_number; + acpi_status status; + int ret; + + ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number); + if (ret) { + dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret); + return ret; + } + + lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL); + if (!lid) + return -ENOMEM; + + lid->gpe_number = gpe_number; + platform_set_drvdata(pdev, lid); + + status = acpi_mark_gpe_for_wake(NULL, gpe_number); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } + + status = acpi_enable_gpe(NULL, gpe_number); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "failed to enable GPE: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } + + ret = surface_lid_enable_wakeup(&pdev->dev, false); + if (ret) + acpi_disable_gpe(NULL, gpe_number); + + return ret; +} + +static int surface_gpe_remove(struct platform_device *pdev) +{ + struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev); + + /* restore default behavior without this module */ + surface_lid_enable_wakeup(&pdev->dev, false); + acpi_disable_gpe(NULL, lid->gpe_number); + + return 0; +} + +static struct platform_driver surface_gpe_driver = { + .probe = surface_gpe_probe, + .remove = surface_gpe_remove, + .driver = { + .name = "surface_gpe", + .pm = &surface_gpe_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static struct platform_device *surface_gpe_device; + +static int __init surface_gpe_init(void) +{ + const struct dmi_system_id *match; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + int status; + + match = dmi_first_match(dmi_lid_device_table); + if (!match) { + pr_info("no compatible Microsoft Surface device found, exiting\n"); + return -ENODEV; + } + + status = platform_driver_register(&surface_gpe_driver); + if (status) + return status; + + fwnode = fwnode_create_software_node(match->driver_data, NULL); + if (IS_ERR(fwnode)) { + status = PTR_ERR(fwnode); + goto err_node; + } + + pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); + if (!pdev) { + status = -ENOMEM; + goto err_alloc; + } + + pdev->dev.fwnode = fwnode; + + status = platform_device_add(pdev); + if (status) + goto err_add; + + surface_gpe_device = pdev; + return 0; + +err_add: + platform_device_put(pdev); +err_alloc: + fwnode_remove_software_node(fwnode); +err_node: + platform_driver_unregister(&surface_gpe_driver); + return status; +} +module_init(surface_gpe_init); + +static void __exit surface_gpe_exit(void) +{ + struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode; + + platform_device_unregister(surface_gpe_device); + platform_driver_unregister(&surface_gpe_driver); + fwnode_remove_software_node(fwnode); +} +module_exit(surface_gpe_exit); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Surface GPE/Lid Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*"); diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index d8afed5db94c..d8afed5db94c 100644 --- a/drivers/platform/x86/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ba34153571b8..91e6176cdfbd 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -191,6 +191,20 @@ config ACER_WMI If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M here. +config AMD_PMC + tristate "AMD SoC PMC driver" + depends on ACPI && PCI + help + The driver provides support for AMD Power Management Controller + primarily responsible for S2Idle transactions that are driven from + a platform firmware running on SMU. This driver also provides a debug + mechanism to investigate the S2Idle transactions and failures. + + Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU. + + If you choose to compile this driver as a module the module will be + called amd-pmc. + config APPLE_GMUX tristate "Apple Gmux Driver" depends on ACPI && PCI @@ -441,6 +455,18 @@ config DELL_WMI To compile this driver as a module, choose M here: the module will be called dell-wmi. +config DELL_WMI_SYSMAN + tristate "Dell WMI-based Systems management driver" + depends on ACPI_WMI + depends on DMI + select NLS + help + This driver allows changing BIOS settings on many Dell machines from + 2018 and newer without the use of any additional software. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-sysman. + config DELL_WMI_DESCRIPTOR tristate depends on ACPI_WMI @@ -881,37 +907,6 @@ config INTEL_VBTN To compile this driver as a module, choose M here: the module will be called intel_vbtn. -config SURFACE3_WMI - tristate "Surface 3 WMI Driver" - depends on ACPI_WMI - depends on DMI - depends on INPUT - depends on SPI - help - Say Y here if you have a Surface 3. - - To compile this driver as a module, choose M here: the module will - be called surface3-wmi. - -config SURFACE_3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" - depends on ACPI && KEYBOARD_GPIO && I2C - help - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - -config SURFACE_3_POWER_OPREGION - tristate "Surface 3 battery platform operation region support" - depends on ACPI && I2C - help - This driver provides support for ACPI operation - region of the Surface 3 battery platform driver. - -config SURFACE_PRO3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" - depends on ACPI && INPUT - help - This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. - config MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI @@ -1373,6 +1368,40 @@ config INTEL_PMC_CORE - LTR Ignore - MPHY/PLL gating status (Sunrisepoint PCH only) +config INTEL_PMT_CLASS + tristate "Intel Platform Monitoring Technology (PMT) Class driver" + help + The Intel Platform Monitoring Technology (PMT) class driver provides + the basic sysfs interface and file hierarchy uses by PMT devices. + + For more information, see: + <file:Documentation/ABI/testing/sysfs-class-intel_pmt> + + To compile this driver as a module, choose M here: the module + will be called intel_pmt_class. + +config INTEL_PMT_TELEMETRY + tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver" + select INTEL_PMT_CLASS + help + The Intel Platform Monitory Technology (PMT) Telemetry driver provides + access to hardware telemetry metrics on devices that support the + feature. + + To compile this driver as a module, choose M here: the module + will be called intel_pmt_telemetry. + +config INTEL_PMT_CRASHLOG + tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver" + select INTEL_PMT_CLASS + help + The Intel Platform Monitoring Technology (PMT) crashlog driver provides + access to hardware crashlog capabilities on devices that support the + feature. + + To compile this driver as a module, choose M here: the module + will be called intel_pmt_crashlog. + config INTEL_PUNIT_IPC tristate "Intel P-Unit IPC Driver" help diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index a34875d833dd..581475f59819 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -22,6 +22,9 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o +# AMD +obj-$(CONFIG_AMD_PMC) += amd-pmc.o + # Apple obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o @@ -47,6 +50,7 @@ obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ # Fujitsu obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o @@ -84,12 +88,6 @@ obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o -# Microsoft -obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o -obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o -obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - # MSI obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o @@ -143,6 +141,9 @@ obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o +obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o +obj-$(CONFIG_INTEL_PMT_TELEMETRY) += intel_pmt_telemetry.o +obj-$(CONFIG_INTEL_PMT_CRASHLOG) += intel_pmt_crashlog.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c index e0976180532a..1b5d935d085a 100644 --- a/drivers/platform/x86/acer-wireless.c +++ b/drivers/platform/x86/acer-wireless.c @@ -28,6 +28,7 @@ static void acer_wireless_notify(struct acpi_device *adev, u32 event) return; } input_report_key(idev, KEY_RFKILL, 1); + input_sync(idev); input_report_key(idev, KEY_RFKILL, 0); input_sync(idev); } diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 5592a929b593..c1a5357da885 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -30,6 +30,7 @@ #include <linux/input/sparse-keymap.h> #include <acpi/video.h> +ACPI_MODULE_NAME(KBUILD_MODNAME); MODULE_AUTHOR("Carlos Corbacho"); MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); MODULE_LICENSE("GPL"); @@ -80,7 +81,7 @@ MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026"); enum acer_wmi_event_ids { WMID_HOTKEY_EVENT = 0x1, - WMID_ACCEL_EVENT = 0x5, + WMID_ACCEL_OR_KBD_DOCK_EVENT = 0x5, }; static const struct key_entry acer_wmi_keymap[] __initconst = { @@ -128,7 +129,9 @@ struct event_return_value { u8 function; u8 key_num; u16 device_state; - u32 reserved; + u16 reserved1; + u8 kbd_dock_state; + u8 reserved2; } __attribute__((packed)); /* @@ -206,14 +209,13 @@ struct hotkey_function_type_aa { /* * Interface capability flags */ -#define ACER_CAP_MAILLED (1<<0) -#define ACER_CAP_WIRELESS (1<<1) -#define ACER_CAP_BLUETOOTH (1<<2) -#define ACER_CAP_BRIGHTNESS (1<<3) -#define ACER_CAP_THREEG (1<<4) -#define ACER_CAP_ACCEL (1<<5) -#define ACER_CAP_RFBTN (1<<6) -#define ACER_CAP_ANY (0xFFFFFFFF) +#define ACER_CAP_MAILLED BIT(0) +#define ACER_CAP_WIRELESS BIT(1) +#define ACER_CAP_BLUETOOTH BIT(2) +#define ACER_CAP_BRIGHTNESS BIT(3) +#define ACER_CAP_THREEG BIT(4) +#define ACER_CAP_SET_FUNCTION_MODE BIT(5) +#define ACER_CAP_KBD_DOCK BIT(6) /* * Interface type flags @@ -236,6 +238,7 @@ static int mailled = -1; static int brightness = -1; static int threeg = -1; static int force_series; +static int force_caps = -1; static bool ec_raw_mode; static bool has_type_aa; static u16 commun_func_bitmap; @@ -245,11 +248,13 @@ module_param(mailled, int, 0444); module_param(brightness, int, 0444); module_param(threeg, int, 0444); module_param(force_series, int, 0444); +module_param(force_caps, int, 0444); module_param(ec_raw_mode, bool, 0444); MODULE_PARM_DESC(mailled, "Set initial state of Mail LED"); MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness"); MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware"); MODULE_PARM_DESC(force_series, "Force a different laptop series"); +MODULE_PARM_DESC(force_caps, "Force the capability bitmask to this value"); MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode"); struct acer_data { @@ -303,9 +308,6 @@ static struct quirk_entry *quirks; static void __init set_quirks(void) { - if (!interface) - return; - if (quirks->mailled) interface->capability |= ACER_CAP_MAILLED; @@ -319,6 +321,15 @@ static int __init dmi_matched(const struct dmi_system_id *dmi) return 1; } +static int __init set_force_caps(const struct dmi_system_id *dmi) +{ + if (force_caps == -1) { + force_caps = (uintptr_t)dmi->driver_data; + pr_info("Found %s, set force_caps to 0x%x\n", dmi->ident, force_caps); + } + return 1; +} + static struct quirk_entry quirk_unknown = { }; @@ -497,6 +508,33 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, .driver_data = &quirk_acer_travelmate_2490, }, + { + .callback = set_force_caps, + .ident = "Acer Aspire Switch 10E SW3-016", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW3-016"), + }, + .driver_data = (void *)ACER_CAP_KBD_DOCK, + }, + { + .callback = set_force_caps, + .ident = "Acer Aspire Switch 10 SW5-012", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"), + }, + .driver_data = (void *)ACER_CAP_KBD_DOCK, + }, + { + .callback = set_force_caps, + .ident = "Acer One 10 (S1003)", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"), + }, + .driver_data = (void *)ACER_CAP_KBD_DOCK, + }, {} }; @@ -649,8 +687,6 @@ static void __init find_quirks(void) if (quirks == NULL) quirks = &quirk_unknown; - - set_quirks(); } /* @@ -793,7 +829,6 @@ static acpi_status AMW0_set_u32(u32 value, u32 cap) switch (quirks->brightness) { default: return ec_write(0x83, value); - break; } default: return AE_ERROR; @@ -1253,10 +1288,8 @@ static void __init type_aa_dmi_decode(const struct dmi_header *header, void *d) interface->capability |= ACER_CAP_THREEG; if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH) interface->capability |= ACER_CAP_BLUETOOTH; - if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_RFBTN) { - interface->capability |= ACER_CAP_RFBTN; + if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_RFBTN) commun_func_bitmap &= ~ACER_WMID3_GDS_RFBTN; - } commun_fn_key_number = type_aa->commun_fn_key_number; } @@ -1520,7 +1553,7 @@ static int acer_gsensor_event(void) struct acpi_buffer output; union acpi_object out_obj[5]; - if (!has_cap(ACER_CAP_ACCEL)) + if (!acer_wmi_accel_dev) return -1; output.length = sizeof(out_obj); @@ -1544,6 +1577,71 @@ static int acer_gsensor_event(void) } /* + * Switch series keyboard dock status + */ +static int acer_kbd_dock_state_to_sw_tablet_mode(u8 kbd_dock_state) +{ + switch (kbd_dock_state) { + case 0x01: /* Docked, traditional clamshell laptop mode */ + return 0; + case 0x04: /* Stand-alone tablet */ + case 0x40: /* Docked, tent mode, keyboard not usable */ + return 1; + default: + pr_warn("Unknown kbd_dock_state 0x%02x\n", kbd_dock_state); + } + + return 0; +} + +static void acer_kbd_dock_get_initial_state(void) +{ + u8 *output, input[8] = { 0x05, 0x00, }; + struct acpi_buffer input_buf = { sizeof(input), input }; + struct acpi_buffer output_buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + int sw_tablet_mode; + + status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input_buf, &output_buf); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Error getting keyboard-dock initial status")); + return; + } + + obj = output_buf.pointer; + if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length != 8) { + pr_err("Unexpected output format getting keyboard-dock initial status\n"); + goto out_free_obj; + } + + output = obj->buffer.pointer; + if (output[0] != 0x00 || (output[3] != 0x05 && output[3] != 0x45)) { + pr_err("Unexpected output [0]=0x%02x [3]=0x%02x getting keyboard-dock initial status\n", + output[0], output[3]); + goto out_free_obj; + } + + sw_tablet_mode = acer_kbd_dock_state_to_sw_tablet_mode(output[4]); + input_report_switch(acer_wmi_input_dev, SW_TABLET_MODE, sw_tablet_mode); + +out_free_obj: + kfree(obj); +} + +static void acer_kbd_dock_event(const struct event_return_value *event) +{ + int sw_tablet_mode; + + if (!has_cap(ACER_CAP_KBD_DOCK)) + return; + + sw_tablet_mode = acer_kbd_dock_state_to_sw_tablet_mode(event->kbd_dock_state); + input_report_switch(acer_wmi_input_dev, SW_TABLET_MODE, sw_tablet_mode); + input_sync(acer_wmi_input_dev); +} + +/* * Rfkill devices */ static void acer_rfkill_update(struct work_struct *ignored); @@ -1770,8 +1868,9 @@ static void acer_wmi_notify(u32 value, void *context) sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true); } break; - case WMID_ACCEL_EVENT: + case WMID_ACCEL_OR_KBD_DOCK_EVENT: acer_gsensor_event(); + acer_kbd_dock_event(&return_value); break; default: pr_warn("Unknown function number - %d - %d\n", @@ -1894,8 +1993,6 @@ static int __init acer_wmi_accel_setup(void) gsensor_handle = acpi_device_handle(adev); acpi_dev_put(adev); - interface->capability |= ACER_CAP_ACCEL; - acer_wmi_accel_dev = input_allocate_device(); if (!acer_wmi_accel_dev) return -ENOMEM; @@ -1921,11 +2018,6 @@ err_free_dev: return err; } -static void acer_wmi_accel_destroy(void) -{ - input_unregister_device(acer_wmi_accel_dev); -} - static int __init acer_wmi_input_setup(void) { acpi_status status; @@ -1943,6 +2035,9 @@ static int __init acer_wmi_input_setup(void) if (err) goto err_free_dev; + if (has_cap(ACER_CAP_KBD_DOCK)) + input_set_capability(acer_wmi_input_dev, EV_SW, SW_TABLET_MODE); + status = wmi_install_notify_handler(ACERWMID_EVENT_GUID, acer_wmi_notify, NULL); if (ACPI_FAILURE(status)) { @@ -1950,6 +2045,9 @@ static int __init acer_wmi_input_setup(void) goto err_free_dev; } + if (has_cap(ACER_CAP_KBD_DOCK)) + acer_kbd_dock_get_initial_state(); + err = input_register_device(acer_wmi_input_dev); if (err) goto err_uninstall_notifier; @@ -2080,7 +2178,7 @@ static int acer_resume(struct device *dev) if (has_cap(ACER_CAP_BRIGHTNESS)) set_u32(data->brightness, ACER_CAP_BRIGHTNESS); - if (has_cap(ACER_CAP_ACCEL)) + if (acer_wmi_accel_dev) acer_gsensor_init(); return 0; @@ -2181,7 +2279,7 @@ static int __init acer_wmi_init(void) } /* WMID always provides brightness methods */ interface->capability |= ACER_CAP_BRIGHTNESS; - } else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa) { + } else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa && force_caps == -1) { pr_err("No WMID device detection method found\n"); return -ENODEV; } @@ -2211,7 +2309,14 @@ static int __init acer_wmi_init(void) if (acpi_video_get_backlight_type() != acpi_backlight_vendor) interface->capability &= ~ACER_CAP_BRIGHTNESS; - if (wmi_has_guid(WMID_GUID3)) { + if (wmi_has_guid(WMID_GUID3)) + interface->capability |= ACER_CAP_SET_FUNCTION_MODE; + + if (force_caps != -1) + interface->capability = force_caps; + + if (wmi_has_guid(WMID_GUID3) && + (interface->capability & ACER_CAP_SET_FUNCTION_MODE)) { if (ACPI_FAILURE(acer_wmi_enable_rf_button())) pr_warn("Cannot enable RF Button Driver\n"); @@ -2270,8 +2375,8 @@ error_device_alloc: error_platform_register: if (wmi_has_guid(ACERWMID_EVENT_GUID)) acer_wmi_input_destroy(); - if (has_cap(ACER_CAP_ACCEL)) - acer_wmi_accel_destroy(); + if (acer_wmi_accel_dev) + input_unregister_device(acer_wmi_accel_dev); return err; } @@ -2281,8 +2386,8 @@ static void __exit acer_wmi_exit(void) if (wmi_has_guid(ACERWMID_EVENT_GUID)) acer_wmi_input_destroy(); - if (has_cap(ACER_CAP_ACCEL)) - acer_wmi_accel_destroy(); + if (acer_wmi_accel_dev) + input_unregister_device(acer_wmi_accel_dev); remove_debugfs(); platform_device_unregister(acer_platform_device); diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c new file mode 100644 index 000000000000..0102bf1c7916 --- /dev/null +++ b/drivers/platform/x86/amd-pmc.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD SoC Power Management Controller Driver + * + * Copyright (c) 2020, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> + +/* SMU communication registers */ +#define AMD_PMC_REGISTER_MESSAGE 0x538 +#define AMD_PMC_REGISTER_RESPONSE 0x980 +#define AMD_PMC_REGISTER_ARGUMENT 0x9BC + +/* Base address of SMU for mapping physical address to virtual address */ +#define AMD_PMC_SMU_INDEX_ADDRESS 0xB8 +#define AMD_PMC_SMU_INDEX_DATA 0xBC +#define AMD_PMC_MAPPING_SIZE 0x01000 +#define AMD_PMC_BASE_ADDR_OFFSET 0x10000 +#define AMD_PMC_BASE_ADDR_LO 0x13B102E8 +#define AMD_PMC_BASE_ADDR_HI 0x13B102EC +#define AMD_PMC_BASE_ADDR_LO_MASK GENMASK(15, 0) +#define AMD_PMC_BASE_ADDR_HI_MASK GENMASK(31, 20) + +/* SMU Response Codes */ +#define AMD_PMC_RESULT_OK 0x01 +#define AMD_PMC_RESULT_CMD_REJECT_BUSY 0xFC +#define AMD_PMC_RESULT_CMD_REJECT_PREREQ 0xFD +#define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE +#define AMD_PMC_RESULT_FAILED 0xFF + +/* List of supported CPU ids */ +#define AMD_CPU_ID_RV 0x15D0 +#define AMD_CPU_ID_RN 0x1630 +#define AMD_CPU_ID_PCO AMD_CPU_ID_RV +#define AMD_CPU_ID_CZN AMD_CPU_ID_RN + +#define AMD_SMU_FW_VERSION 0x0 +#define PMC_MSG_DELAY_MIN_US 100 +#define RESPONSE_REGISTER_LOOP_MAX 200 + +enum amd_pmc_def { + MSG_TEST = 0x01, + MSG_OS_HINT_PCO, + MSG_OS_HINT_RN, +}; + +struct amd_pmc_dev { + void __iomem *regbase; + void __iomem *smu_base; + u32 base_addr; + u32 cpu_id; + struct device *dev; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs_dir; +#endif /* CONFIG_DEBUG_FS */ +}; + +static struct amd_pmc_dev pmc; + +static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset) +{ + return ioread32(dev->regbase + reg_offset); +} + +static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u32 val) +{ + iowrite32(val, dev->regbase + reg_offset); +} + +#if CONFIG_DEBUG_FS +static int smu_fw_info_show(struct seq_file *s, void *unused) +{ + struct amd_pmc_dev *dev = s->private; + u32 value; + + value = ioread32(dev->smu_base + AMD_SMU_FW_VERSION); + seq_printf(s, "SMU FW Info: %x\n", value); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smu_fw_info); + +static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) +{ + debugfs_remove_recursive(dev->dbgfs_dir); +} + +static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) +{ + dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL); + debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev, + &smu_fw_info_fops); +} +#else +static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) +{ +} + +static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) +{ + u32 value; + + value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE); + dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value); + + value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value); + + value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE); + dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); +} + +static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) +{ + int rc; + u8 msg; + u32 val; + + /* Wait until we get a valid response */ + rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + val, val > 0, PMC_MSG_DELAY_MIN_US, + PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { + dev_err(dev->dev, "failed to talk to SMU\n"); + return rc; + } + + /* Write zero to response register */ + amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0); + + /* Write argument into response register */ + amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set); + + /* Write message ID to message ID register */ + msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; + amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); + return 0; +} + +static int __maybe_unused amd_pmc_suspend(struct device *dev) +{ + struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + int rc; + + rc = amd_pmc_send_cmd(pdev, 1); + if (rc) + dev_err(pdev->dev, "suspend failed\n"); + + amd_pmc_dump_registers(pdev); + return 0; +} + +static int __maybe_unused amd_pmc_resume(struct device *dev) +{ + struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + int rc; + + rc = amd_pmc_send_cmd(pdev, 0); + if (rc) + dev_err(pdev->dev, "resume failed\n"); + + amd_pmc_dump_registers(pdev); + return 0; +} + +static const struct dev_pm_ops amd_pmc_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(amd_pmc_suspend, amd_pmc_resume) +}; + +static const struct pci_device_id pmc_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RV) }, + { } +}; + +static int amd_pmc_probe(struct platform_device *pdev) +{ + struct amd_pmc_dev *dev = &pmc; + struct pci_dev *rdev; + u32 base_addr_lo; + u32 base_addr_hi; + u64 base_addr; + int err; + u32 val; + + dev->dev = &pdev->dev; + + rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); + if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) + return -ENODEV; + + dev->cpu_id = rdev->device; + err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_LO); + if (err) { + dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS); + return pcibios_err_to_errno(err); + } + + err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val); + if (err) + return pcibios_err_to_errno(err); + + base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK; + + err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_HI); + if (err) { + dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS); + return pcibios_err_to_errno(err); + } + + err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val); + if (err) + return pcibios_err_to_errno(err); + + base_addr_hi = val & AMD_PMC_BASE_ADDR_LO_MASK; + pci_dev_put(rdev); + base_addr = ((u64)base_addr_hi << 32 | base_addr_lo); + + dev->smu_base = devm_ioremap(dev->dev, base_addr, AMD_PMC_MAPPING_SIZE); + if (!dev->smu_base) + return -ENOMEM; + + dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET, + AMD_PMC_MAPPING_SIZE); + if (!dev->regbase) + return -ENOMEM; + + amd_pmc_dump_registers(dev); + + platform_set_drvdata(pdev, dev); + amd_pmc_dbgfs_register(dev); + return 0; +} + +static int amd_pmc_remove(struct platform_device *pdev) +{ + struct amd_pmc_dev *dev = platform_get_drvdata(pdev); + + amd_pmc_dbgfs_unregister(dev); + return 0; +} + +static const struct acpi_device_id amd_pmc_acpi_ids[] = { + {"AMDI0005", 0}, + {"AMD0004", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids); + +static struct platform_driver amd_pmc_driver = { + .driver = { + .name = "amd_pmc", + .acpi_match_table = amd_pmc_acpi_ids, + .pm = &amd_pmc_pm_ops, + }, + .probe = amd_pmc_probe, + .remove = amd_pmc_remove, +}; +module_platform_driver(amd_pmc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("AMD PMC Driver"); diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 1d9fbabd02fb..d41d7ad14be0 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -119,6 +119,11 @@ static struct quirk_entry quirk_asus_use_kbd_dock_devid = { .use_kbd_dock_devid = true, }; +static struct quirk_entry quirk_asus_use_lid_flip_devid = { + .wmi_backlight_set_devstate = true, + .use_lid_flip_devid = true, +}; + static int dmi_matched(const struct dmi_system_id *dmi) { pr_info("Identified laptop model '%s'\n", dmi->ident); @@ -520,6 +525,16 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_use_kbd_dock_devid, }, + { + .callback = dmi_matched, + .ident = "ASUS ZenBook Flip UX360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + /* Match UX360* */ + DMI_MATCH(DMI_PRODUCT_NAME, "UX360"), + }, + .driver_data = &quirk_asus_use_lid_flip_devid, + }, {}, }; diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 39e1a6396e08..9ca15f724343 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -63,6 +63,7 @@ MODULE_LICENSE("GPL"); #define NOTIFY_KBD_BRTTOGGLE 0xc7 #define NOTIFY_KBD_FBM 0x99 #define NOTIFY_KBD_TTP 0xae +#define NOTIFY_LID_FLIP 0xfa #define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0) @@ -375,6 +376,20 @@ static int asus_wmi_input_init(struct asus_wmi *asus) } } + if (asus->driver->quirks->use_lid_flip_devid) { + result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP); + if (result < 0) + asus->driver->quirks->use_lid_flip_devid = 0; + if (result >= 0) { + input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE); + input_report_switch(asus->inputdev, SW_TABLET_MODE, result); + } else if (result == -ENODEV) { + pr_err("This device has lid_flip quirk but got ENODEV checking it. This is a bug."); + } else { + pr_err("Error checking for lid-flip: %d\n", result); + } + } + err = input_register_device(asus->inputdev); if (err) goto err_free_dev; @@ -394,6 +409,18 @@ static void asus_wmi_input_exit(struct asus_wmi *asus) asus->inputdev = NULL; } +/* Tablet mode ****************************************************************/ + +static void lid_flip_tablet_mode_get_state(struct asus_wmi *asus) +{ + int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP); + + if (result >= 0) { + input_report_switch(asus->inputdev, SW_TABLET_MODE, result); + input_sync(asus->inputdev); + } +} + /* Battery ********************************************************************/ /* The battery maximum charging percentage */ @@ -1663,6 +1690,10 @@ static int fan_boost_mode_write(struct asus_wmi *asus) pr_info("Set fan boost mode: %u\n", value); err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_BOOST_MODE, value, &retval); + + sysfs_notify(&asus->platform_device->dev.kobj, NULL, + "fan_boost_mode"); + if (err) { pr_warn("Failed to set fan boost mode: %d\n", err); return err; @@ -1774,6 +1805,10 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus) err = asus_wmi_set_devstate(ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY, value, &retval); + + sysfs_notify(&asus->platform_device->dev.kobj, NULL, + "throttle_thermal_policy"); + if (err) { pr_warn("Failed to set throttle thermal policy: %d\n", err); return err; @@ -2128,6 +2163,11 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) return; } + if (asus->driver->quirks->use_lid_flip_devid && code == NOTIFY_LID_FLIP) { + lid_flip_tablet_mode_get_state(asus); + return; + } + if (asus->fan_boost_mode_available && code == NOTIFY_KBD_FBM) { fan_boost_mode_switch_next(asus); return; @@ -2719,6 +2759,10 @@ static int asus_hotk_resume(struct device *device) if (asus_wmi_has_fnlock_key(asus)) asus_wmi_fnlock_update(asus); + + if (asus->driver->quirks->use_lid_flip_devid) + lid_flip_tablet_mode_get_state(asus); + return 0; } @@ -2757,6 +2801,10 @@ static int asus_hotk_restore(struct device *device) if (asus_wmi_has_fnlock_key(asus)) asus_wmi_fnlock_update(asus); + + if (asus->driver->quirks->use_lid_flip_devid) + lid_flip_tablet_mode_get_state(asus); + return 0; } diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 1a95c172f94b..b302415bf1d9 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -34,6 +34,7 @@ struct quirk_entry { bool wmi_backlight_set_devstate; bool wmi_force_als_set; bool use_kbd_dock_devid; + bool use_lid_flip_devid; int wapf; /* * For machines with AMD graphic chips, it will send out WMI event diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index af063f690846..3e03e8d3a07f 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -1023,6 +1023,8 @@ static int cmpc_keys_codes[] = { KEY_CAMERA, KEY_BACK, KEY_FORWARD, + KEY_UNKNOWN, + KEY_WLAN, /* NL3: 0x8b (press), 0x9b (release) */ KEY_MAX }; diff --git a/drivers/platform/x86/dell-smbios-base.c b/drivers/platform/x86/dell-smbios-base.c index 2e2cd565926a..3a1dbf199441 100644 --- a/drivers/platform/x86/dell-smbios-base.c +++ b/drivers/platform/x86/dell-smbios-base.c @@ -594,6 +594,7 @@ static int __init dell_smbios_init(void) if (wmi && smm) { pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", wmi, smm); + ret = -ENODEV; goto fail_create_group; } diff --git a/drivers/platform/x86/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell-wmi-sysman/Makefile new file mode 100644 index 000000000000..825fb2fbeea8 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o +dell-wmi-sysman-objs := sysman.o \ + enum-attributes.o \ + int-attributes.o \ + string-attributes.o \ + passobj-attributes.o \ + biosattr-interface.o \ + passwordattr-interface.o diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c new file mode 100644 index 000000000000..f95d8ddace5a --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET methods under BIOS attributes interface GUID for use + * with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include <linux/wmi.h> +#include "dell-wmi-sysman.h" + +#define SETDEFAULTVALUES_METHOD_ID 0x02 +#define SETBIOSDEFAULTS_METHOD_ID 0x03 +#define SETATTRIBUTE_METHOD_ID 0x04 + +static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, + int method_id) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + if (wmi_priv.pending_changes == 0) { + wmi_priv.pending_changes = 1; + /* let userland know it may need to check reboot pending again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + } + kfree(output.pointer); + return map_wmi_error(ret); +} + +/** + * set_attribute() - Update an attribute value + * @a_name: The attribute name + * @a_value: The attribute value + * + * Sets an attribute to new value + */ +int set_attribute(const char *a_name, const char *a_value) +{ + size_t security_area_size, buffer_size; + size_t a_name_size, a_value_size; + char *buffer = NULL, *start; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + a_name_size = calculate_string_buffer(a_name); + a_value_size = calculate_string_buffer(a_value); + buffer_size = security_area_size + a_name_size + a_value_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, a_name_size, a_name); + if (ret < 0) + goto out; + start += ret; + ret = populate_string_buffer(start, a_value_size, a_value); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, + buffer, buffer_size, + SETATTRIBUTE_METHOD_ID); + if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +/** + * set_bios_defaults() - Resets BIOS defaults + * @deftype: the type of BIOS value reset to issue. + * + * Resets BIOS defaults + */ +int set_bios_defaults(u8 deftype) +{ + size_t security_area_size, buffer_size; + size_t integer_area_size = sizeof(u8); + char *buffer = NULL; + u8 *defaultType; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + buffer_size = security_area_size + integer_area_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + defaultType = buffer + security_area_size; + *defaultType = deftype; + + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, + SETBIOSDEFAULTS_METHOD_ID); + if (ret) + dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret); + + kfree(buffer); +out: + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_set_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_set_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_set_interface_driver = { + .driver = { + .name = DRIVER_NAME + }, + .probe = bios_attr_set_interface_probe, + .remove = bios_attr_set_interface_remove, + .id_table = bios_attr_set_interface_id_table, +}; + +int init_bios_attr_set_interface(void) +{ + return wmi_driver_register(&bios_attr_set_interface_driver); +} + +void exit_bios_attr_set_interface(void) +{ + wmi_driver_unregister(&bios_attr_set_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h new file mode 100644 index 000000000000..b80f2a62ea3f --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Definitions for kernel modules using Dell WMI System Management Driver + * + * Copyright (c) 2020 Dell Inc. + */ + +#ifndef _DELL_WMI_BIOS_ATTR_H_ +#define _DELL_WMI_BIOS_ATTR_H_ + +#include <linux/wmi.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/capability.h> + +#define DRIVER_NAME "dell-wmi-sysman" +#define MAX_BUFF 512 + +#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5" +#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA" +#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9" +#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4" +#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4" +#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A" + +struct enumeration_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char dell_value_modifier[MAX_BUFF]; + char possible_values[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; +}; + +struct integer_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; + int scalar_increment; + int default_value; + int min_value; + int max_value; +}; + +struct str_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char display_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + int min_length; + int max_length; +}; + +struct po_data { + struct kobject *attr_name_kobj; + char attribute_name[MAX_BUFF]; + int min_password_length; + int max_password_length; +}; + +struct wmi_sysman_priv { + char current_admin_password[MAX_BUFF]; + char current_system_password[MAX_BUFF]; + struct wmi_device *password_attr_wdev; + struct wmi_device *bios_attr_wdev; + struct kset *authentication_dir_kset; + struct kset *main_dir_kset; + struct device *class_dev; + struct enumeration_data *enumeration_data; + int enumeration_instances_count; + struct integer_data *integer_data; + int integer_instances_count; + struct str_data *str_data; + int str_instances_count; + struct po_data *po_data; + int po_instances_count; + bool pending_changes; + struct mutex mutex; +}; + +/* global structure used by multiple WMI interfaces */ +extern struct wmi_sysman_priv wmi_priv; + +enum { ENUM, INT, STR, PO }; + +enum { + ATTR_NAME, + DISPL_NAME_LANG_CODE, + DISPLAY_NAME, + DEFAULT_VAL, + CURRENT_VAL, + MODIFIER +}; + +#define get_instance_id(type) \ +static int get_##type##_instance_id(struct kobject *kobj) \ +{ \ + int i; \ + for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ + return -EIO; \ +} + +#define attribute_s_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_n_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_property_store(curr_val, type) \ +static ssize_t curr_val##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + char *p, *buf_cp; \ + int i, ret = -EIO; \ + buf_cp = kstrdup(buf, GFP_KERNEL); \ + if (!buf_cp) \ + return -ENOMEM; \ + p = memchr(buf_cp, '\n', count); \ + \ + if (p != NULL) \ + *p = '\0'; \ + i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + ret = validate_##type##_input(i, buf_cp); \ + if (!ret) \ + ret = set_attribute(kobj->name, buf_cp); \ + kfree(buf_cp); \ + return ret ? ret : count; \ +} + +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); +int get_instance_count(const char *guid_string); +void strlcpy_attr(char *dest, char *src); + +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_enum_data(void); +void exit_enum_attributes(void); + +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_int_data(void); +void exit_int_attributes(void); + +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_str_data(void); +void exit_str_attributes(void); + +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_po_data(void); +void exit_po_attributes(void); + +int set_attribute(const char *a_name, const char *a_value); +int set_bios_defaults(u8 defType); + +void exit_bios_attr_set_interface(void); +int init_bios_attr_set_interface(void); +int map_wmi_error(int error_code); +size_t calculate_string_buffer(const char *str); +size_t calculate_security_buffer(char *authentication); +void populate_security_buffer(char *buffer, char *authentication); +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); +int set_new_password(const char *password_type, const char *new); +int init_bios_attr_pass_interface(void); +void exit_bios_attr_pass_interface(void); + +#endif diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c new file mode 100644 index 000000000000..80f4b7785c6c --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to enumeration type attributes under + * BIOS Enumeration GUID for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +get_instance_id(enumeration); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_enumeration_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_enumeration_input() - Validate input of current_value against possible values + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_enumeration_input(int instance_id, const char *buf) +{ + char *options, *tmp, *p; + int ret = -EINVAL; + + options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values, + GFP_KERNEL); + if (!options) + return -ENOMEM; + + while ((p = strsep(&options, ";")) != NULL) { + if (!*p) + continue; + if (!strcasecmp(p, buf)) { + ret = 0; + break; + } + } + + kfree(tmp); + return ret; +} + +attribute_s_property_show(display_name_language_code, enumeration); +static struct kobj_attribute displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, enumeration); +static struct kobj_attribute displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, enumeration); +static struct kobj_attribute default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, enumeration); +static struct kobj_attribute current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, enumeration); +static struct kobj_attribute modifier = + __ATTR_RO(dell_modifier); + +attribute_s_property_show(dell_value_modifier, enumeration); +static struct kobj_attribute value_modfr = + __ATTR_RO(dell_value_modifier); + +attribute_s_property_show(possible_values, enumeration); +static struct kobj_attribute poss_val = + __ATTR_RO(possible_values); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "enumeration\n"); +} +static struct kobj_attribute type = + __ATTR_RO(type); + +static struct attribute *enumeration_attrs[] = { + &displ_langcode.attr, + &displ_name.attr, + &default_val.attr, + ¤t_val.attr, + &modifier.attr, + &value_modfr.attr, + &poss_val.attr, + &type.attr, + NULL, +}; + +static const struct attribute_group enumeration_attr_group = { + .attrs = enumeration_attrs, +}; + +int alloc_enum_data(void) +{ + int ret = 0; + + wmi_priv.enumeration_instances_count = + get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count, + sizeof(struct enumeration_data), GFP_KERNEL); + if (!wmi_priv.enumeration_data) { + wmi_priv.enumeration_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_enum_data() - Populate all properties of an instance under enumeration attribute + * @enumeration_obj: ACPI object with enumeration data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + int i, next_obj, value_modifier_count, possible_values_count; + + wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, + enumeration_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, + enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, + enumeration_obj[DISPLAY_NAME].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, + enumeration_obj[DEFAULT_VAL].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, + enumeration_obj[MODIFIER].string.pointer); + + next_obj = MODIFIER + 1; + + value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; + + for (i = 0; i < value_modifier_count; i++) { + strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, + enumeration_obj[++next_obj].string.pointer); + strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); + } + + possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; + + for (i = 0; i < possible_values_count; i++) { + strcat(wmi_priv.enumeration_data[instance_id].possible_values, + enumeration_obj[++next_obj].string.pointer); + strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); + } + + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); +} + +/** + * exit_enum_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_enum_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) { + if (wmi_priv.enumeration_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj, + &enumeration_attr_group); + } + kfree(wmi_priv.enumeration_data); +} diff --git a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c new file mode 100644 index 000000000000..75aedbb733be --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to integer type attributes under BIOS Integer GUID for use with + * dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR}; + +get_instance_id(integer); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_integer_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value); + kfree(obj); + return ret; +} + +/** + * validate_integer_input() - Validate input of current_value against lower and upper bound + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_integer_input(int instance_id, char *buf) +{ + int in_val; + int ret; + + ret = kstrtoint(buf, 0, &in_val); + if (ret) + return ret; + if (in_val < wmi_priv.integer_data[instance_id].min_value || + in_val > wmi_priv.integer_data[instance_id].max_value) + return -EINVAL; + + /* workaround for BIOS error. + * validate input to avoid setting 0 when integer input passed with + sign + */ + if (*buf == '+') + memmove(buf, (buf + 1), strlen(buf + 1) + 1); + + return ret; +} + +attribute_s_property_show(display_name_language_code, integer); +static struct kobj_attribute integer_displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, integer); +static struct kobj_attribute integer_displ_name = + __ATTR_RO(display_name); + +attribute_n_property_show(default_value, integer); +static struct kobj_attribute integer_default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, integer); +static struct kobj_attribute integer_current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, integer); +static struct kobj_attribute integer_modifier = + __ATTR_RO(dell_modifier); + +attribute_n_property_show(min_value, integer); +static struct kobj_attribute integer_lower_bound = + __ATTR_RO(min_value); + +attribute_n_property_show(max_value, integer); +static struct kobj_attribute integer_upper_bound = + __ATTR_RO(max_value); + +attribute_n_property_show(scalar_increment, integer); +static struct kobj_attribute integer_scalar_increment = + __ATTR_RO(scalar_increment); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "integer\n"); +} +static struct kobj_attribute integer_type = + __ATTR_RO(type); + +static struct attribute *integer_attrs[] = { + &integer_displ_langcode.attr, + &integer_displ_name.attr, + &integer_default_val.attr, + &integer_current_val.attr, + &integer_modifier.attr, + &integer_lower_bound.attr, + &integer_upper_bound.attr, + &integer_scalar_increment.attr, + &integer_type.attr, + NULL, +}; + +static const struct attribute_group integer_attr_group = { + .attrs = integer_attrs, +}; + +int alloc_int_data(void) +{ + int ret = 0; + + wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count, + sizeof(struct integer_data), GFP_KERNEL); + if (!wmi_priv.integer_data) { + wmi_priv.integer_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_int_data() - Populate all properties of an instance under integer attribute + * @integer_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, + integer_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, + integer_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, + integer_obj[DISPLAY_NAME].string.pointer); + wmi_priv.integer_data[instance_id].default_value = + (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; + strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, + integer_obj[MODIFIER].string.pointer); + wmi_priv.integer_data[instance_id].min_value = + (uintptr_t)integer_obj[MIN_VALUE].string.pointer; + wmi_priv.integer_data[instance_id].max_value = + (uintptr_t)integer_obj[MAX_VALUE].string.pointer; + wmi_priv.integer_data[instance_id].scalar_increment = + (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; + + return sysfs_create_group(attr_name_kobj, &integer_attr_group); +} + +/** + * exit_int_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_int_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) { + if (wmi_priv.integer_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj, + &integer_attr_group); + } + kfree(wmi_priv.integer_data); +} diff --git a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c new file mode 100644 index 000000000000..3abcd95477c0 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to password object type attributes under BIOS Password Object GUID for + * use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN}; + +get_instance_id(po); + +static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int instance_id = get_po_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value); + kfree(obj); + return ret; +} + +static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled); + +static ssize_t current_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *target = NULL; + int length; + + length = strlen(buf); + if (buf[length-1] == '\n') + length--; + + /* firmware does verifiation of min/max password length, + * hence only check for not exceeding MAX_BUFF here. + */ + if (length >= MAX_BUFF) + return -EINVAL; + + if (strcmp(kobj->name, "Admin") == 0) + target = wmi_priv.current_admin_password; + else if (strcmp(kobj->name, "System") == 0) + target = wmi_priv.current_system_password; + if (!target) + return -EIO; + memcpy(target, buf, length); + target[length] = '\0'; + + return count; +} + +static struct kobj_attribute po_current_password = __ATTR_WO(current_password); + +static ssize_t new_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *p, *buf_cp; + int ret; + + buf_cp = kstrdup(buf, GFP_KERNEL); + if (!buf_cp) + return -ENOMEM; + p = memchr(buf_cp, '\n', count); + + if (p != NULL) + *p = '\0'; + if (strlen(buf_cp) > MAX_BUFF) { + ret = -EINVAL; + goto out; + } + + ret = set_new_password(kobj->name, buf_cp); + +out: + kfree(buf_cp); + return ret ? ret : count; +} + +static struct kobj_attribute po_new_password = __ATTR_WO(new_password); + +attribute_n_property_show(min_password_length, po); +static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length); + +attribute_n_property_show(max_password_length, po); +static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length); + +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "password\n"); +} + +static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism); + +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (strcmp(kobj->name, "Admin") == 0) + return sprintf(buf, "bios-admin\n"); + else if (strcmp(kobj->name, "System") == 0) + return sprintf(buf, "power-on\n"); + return -EIO; +} + +static struct kobj_attribute po_role = __ATTR_RO(role); + +static struct attribute *po_attrs[] = { + &po_is_pass_set.attr, + &po_min_pass_length.attr, + &po_max_pass_length.attr, + &po_current_password.attr, + &po_new_password.attr, + &po_role.attr, + &po_mechanism.attr, + NULL, +}; + +static const struct attribute_group po_attr_group = { + .attrs = po_attrs, +}; + +int alloc_po_data(void) +{ + int ret = 0; + + wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL); + if (!wmi_priv.po_data) { + wmi_priv.po_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_po_data() - Populate all properties of an instance under password object attribute + * @po_obj: ACPI object with password object data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) +{ + wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, + po_obj[ATTR_NAME].string.pointer); + wmi_priv.po_data[instance_id].min_password_length = + (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; + wmi_priv.po_data[instance_id].max_password_length = + (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; + + return sysfs_create_group(attr_name_kobj, &po_attr_group); +} + +/** + * exit_po_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_po_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) { + if (wmi_priv.po_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj, + &po_attr_group); + } + kfree(wmi_priv.po_data); +} diff --git a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c new file mode 100644 index 000000000000..5780b4d94759 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET password methods under BIOS attributes interface GUID + * + * Copyright (c) 2020 Dell Inc. + */ + +#include <linux/wmi.h> +#include "dell-wmi-sysman.h" + +static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + kfree(output.pointer); + /* let userland know it may need to check is_password_set again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + return map_wmi_error(ret); +} + +/** + * set_new_password() - Sets a system admin password + * @password_type: The type of password to set + * @new: The new password + * + * Sets the password using plaintext interface + */ +int set_new_password(const char *password_type, const char *new) +{ + size_t password_type_size, current_password_size, new_size; + size_t security_area_size, buffer_size; + char *buffer = NULL, *start; + char *current_password; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.password_attr_wdev) { + ret = -ENODEV; + goto out; + } + if (strcmp(password_type, "Admin") == 0) { + current_password = wmi_priv.current_admin_password; + } else if (strcmp(password_type, "System") == 0) { + current_password = wmi_priv.current_system_password; + } else { + ret = -EINVAL; + dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n", + password_type); + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + password_type_size = calculate_string_buffer(password_type); + current_password_size = calculate_string_buffer(current_password); + new_size = calculate_string_buffer(new); + buffer_size = security_area_size + password_type_size + current_password_size + new_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, password_type_size, password_type); + if (ret < 0) + goto out; + + start += ret; + ret = populate_string_buffer(start, current_password_size, current_password); + if (ret < 0) + goto out; + + start += ret; + ret = populate_string_buffer(start, new_size, new); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); + /* clear current_password here and use user input from wmi_priv.current_password */ + if (!ret) + memset(current_password, 0, MAX_BUFF); + /* explain to user the detailed failure reason */ + else if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + + return ret; +} + +static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.password_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_pass_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.password_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_pass_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_pass_interface_driver = { + .driver = { + .name = DRIVER_NAME"-password" + }, + .probe = bios_attr_pass_interface_probe, + .remove = bios_attr_pass_interface_remove, + .id_table = bios_attr_pass_interface_id_table, +}; + +int init_bios_attr_pass_interface(void) +{ + return wmi_driver_register(&bios_attr_pass_interface_driver); +} + +void exit_bios_attr_pass_interface(void) +{ + wmi_driver_unregister(&bios_attr_pass_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c new file mode 100644 index 000000000000..ac75dce88a4c --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to string type attributes under BIOS String GUID for use with + * dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum string_properties {MIN_LEN = 6, MAX_LEN}; + +get_instance_id(str); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_str_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return -EIO; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_str_input() - Validate input of current_value against min and max lengths + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_str_input(int instance_id, const char *buf) +{ + int in_len = strlen(buf); + + if ((in_len < wmi_priv.str_data[instance_id].min_length) || + (in_len > wmi_priv.str_data[instance_id].max_length)) + return -EINVAL; + + return 0; +} + +attribute_s_property_show(display_name_language_code, str); +static struct kobj_attribute str_displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, str); +static struct kobj_attribute str_displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, str); +static struct kobj_attribute str_default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, str); +static struct kobj_attribute str_current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, str); +static struct kobj_attribute str_modifier = + __ATTR_RO(dell_modifier); + +attribute_n_property_show(min_length, str); +static struct kobj_attribute str_min_length = + __ATTR_RO(min_length); + +attribute_n_property_show(max_length, str); +static struct kobj_attribute str_max_length = + __ATTR_RO(max_length); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "string\n"); +} +static struct kobj_attribute str_type = + __ATTR_RO(type); + +static struct attribute *str_attrs[] = { + &str_displ_langcode.attr, + &str_displ_name.attr, + &str_default_val.attr, + &str_current_val.attr, + &str_modifier.attr, + &str_min_length.attr, + &str_max_length.attr, + &str_type.attr, + NULL, +}; + +static const struct attribute_group str_attr_group = { + .attrs = str_attrs, +}; + +int alloc_str_data(void) +{ + int ret = 0; + + wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count, + sizeof(struct str_data), GFP_KERNEL); + if (!wmi_priv.str_data) { + wmi_priv.str_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_str_data() - Populate all properties of an instance under string attribute + * @str_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) +{ + wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, + str_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, + str_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].display_name, + str_obj[DISPLAY_NAME].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].default_value, + str_obj[DEFAULT_VAL].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, + str_obj[MODIFIER].string.pointer); + wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; + wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; + + return sysfs_create_group(attr_name_kobj, &str_attr_group); +} + +/** + * exit_str_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_str_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) { + if (wmi_priv.str_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj, + &str_attr_group); + } + kfree(wmi_priv.str_data); +} diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell-wmi-sysman/sysman.c new file mode 100644 index 000000000000..dc6dd531c996 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/sysman.c @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common methods for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/fs.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/wmi.h> +#include "dell-wmi-sysman.h" + +#define MAX_TYPES 4 +#include <linux/nls.h> + +static struct class firmware_attributes_class = { + .name = "firmware-attributes", +}; + +struct wmi_sysman_priv wmi_priv = { + .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), +}; + +/* reset bios to defaults */ +static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; +static int reset_option = -1; + + +/** + * populate_string_buffer() - populates a string buffer + * @buffer: the start of the destination buffer + * @buffer_len: length of the destination buffer + * @str: the string to insert into buffer + */ +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) +{ + u16 *length = (u16 *)buffer; + u16 *target = length + 1; + int ret; + + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, + target, buffer_len - sizeof(u16)); + if (ret < 0) { + dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n"); + return ret; + } + + if ((ret * sizeof(u16)) > U16_MAX) { + dev_err(wmi_priv.class_dev, "Error string too long\n"); + return -ERANGE; + } + + *length = ret * sizeof(u16); + return sizeof(u16) + *length; +} + +/** + * calculate_string_buffer() - determines size of string buffer for use with BIOS communication + * @str: the string to calculate based upon + * + */ +size_t calculate_string_buffer(const char *str) +{ + /* u16 length field + one UTF16 char for each input char */ + return sizeof(u16) + strlen(str) * sizeof(u16); +} + +/** + * calculate_security_buffer() - determines size of security buffer for authentication scheme + * @authentication: the authentication content + * + * Currently only supported type is Admin password + */ +size_t calculate_security_buffer(char *authentication) +{ + if (strlen(authentication) > 0) { + return (sizeof(u32) * 2) + strlen(authentication) + + strlen(authentication) % 2; + } + return sizeof(u32) * 2; +} + +/** + * populate_security_buffer() - builds a security buffer for authentication scheme + * @buffer: the buffer to populate + * @authentication: the authentication content + * + * Currently only supported type is PLAIN TEXT + */ +void populate_security_buffer(char *buffer, char *authentication) +{ + char *auth = buffer + sizeof(u32) * 2; + u32 *sectype = (u32 *) buffer; + u32 *seclen = sectype + 1; + + *sectype = strlen(authentication) > 0 ? 1 : 0; + *seclen = strlen(authentication); + + /* plain text */ + if (strlen(authentication) > 0) + memcpy(auth, authentication, *seclen); +} + +/** + * map_wmi_error() - map errors from WMI methods to kernel error codes + * @error_code: integer error code returned from Dell's firmware + */ +int map_wmi_error(int error_code) +{ + switch (error_code) { + case 0: + /* success */ + return 0; + case 1: + /* failed */ + return -EIO; + case 2: + /* invalid parameter */ + return -EINVAL; + case 3: + /* access denied */ + return -EACCES; + case 4: + /* not supported */ + return -EOPNOTSUPP; + case 5: + /* memory error */ + return -ENOMEM; + case 6: + /* protocol error */ + return -EPROTO; + } + /* unspecified error */ + return -EIO; +} + +/** + * reset_bios_show() - sysfs implementaton for read reset_bios + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer to display to userspace + */ +static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + char *start = buf; + int i; + + for (i = 0; i < MAX_TYPES; i++) { + if (i == reset_option) + buf += sprintf(buf, "[%s] ", reset_types[i]); + else + buf += sprintf(buf, "%s ", reset_types[i]); + } + buf += sprintf(buf, "\n"); + return buf-start; +} + +/** + * reset_bios_store() - sysfs implementaton for write reset_bios + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer from userspace + * @count: the size of the buffer from userspace + */ +static ssize_t reset_bios_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int type = sysfs_match_string(reset_types, buf); + int ret; + + if (type < 0) + return type; + + ret = set_bios_defaults(type); + pr_debug("reset all attributes request type %d: %d\n", type, ret); + if (!ret) { + reset_option = type; + ret = count; + } + + return ret; +} + +/** + * pending_reboot_show() - sysfs implementaton for read pending_reboot + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer to display to userspace + * + * Stores default value as 0 + * When current_value is changed this attribute is set to 1 to notify reboot may be required + */ +static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", wmi_priv.pending_changes); +} + +static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); + + +/** + * create_attributes_level_sysfs_files() - Creates reset_bios and + * pending_reboot attributes + */ +static int create_attributes_level_sysfs_files(void) +{ + int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + + if (ret) { + pr_debug("could not create reset_bios file\n"); + return ret; + } + + ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); + if (ret) { + pr_debug("could not create changing_pending_reboot file\n"); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + } + return ret; +} + +static void release_reset_bios_data(void) +{ + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); +} + +static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct kobj_attribute *kattr; + ssize_t ret = -EIO; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->show) + ret = kattr->show(kobj, kattr, buf); + return ret; +} + +static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct kobj_attribute *kattr; + ssize_t ret = -EIO; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->store) + ret = kattr->store(kobj, kattr, buf, count); + return ret; +} + +static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { + .show = wmi_sysman_attr_show, + .store = wmi_sysman_attr_store, +}; + +static void attr_name_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static struct kobj_type attr_name_ktype = { + .release = attr_name_release, + .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, +}; + +/** + * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks + * @dest: Where to copy the string to + * @src: Where to copy the string from + */ +void strlcpy_attr(char *dest, char *src) +{ + size_t len = strlen(src) + 1; + + if (len > 1 && len <= MAX_BUFF) + strlcpy(dest, src, len); + + /*len can be zero because any property not-applicable to attribute can + * be empty so check only for too long buffers and log error + */ + if (len > MAX_BUFF) + pr_err("Source string returned from BIOS is out of bound!\n"); +} + +/** + * get_wmiobj_pointer() - Get Content of WMI block for particular instance + * @instance_id: WMI instance ID + * @guid_string: WMI GUID (in str form) + * + * Fetches the content for WMI block (instance_id) under GUID (guid_string) + * Caller must kfree the return + */ +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + status = wmi_query_block(guid_string, instance_id, &out); + + return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; +} + +/** + * get_instance_count() - Compute total number of instances under guid_string + * @guid_string: WMI GUID (in string form) + */ +int get_instance_count(const char *guid_string) +{ + union acpi_object *wmi_obj = NULL; + int i = 0; + + do { + kfree(wmi_obj); + wmi_obj = get_wmiobj_pointer(i, guid_string); + i++; + } while (wmi_obj); + + return (i-1); +} + +/** + * alloc_attributes_data() - Allocate attributes data for a particular type + * @attr_type: Attribute type to allocate + */ +static int alloc_attributes_data(int attr_type) +{ + int retval = 0; + + switch (attr_type) { + case ENUM: + retval = alloc_enum_data(); + break; + case INT: + retval = alloc_int_data(); + break; + case STR: + retval = alloc_str_data(); + break; + case PO: + retval = alloc_po_data(); + break; + default: + break; + } + + return retval; +} + +/** + * destroy_attribute_objs() - Free a kset of kobjects + * @kset: The kset to destroy + * + * Fress kobjects created for each attribute_name under attribute type kset + */ +static void destroy_attribute_objs(struct kset *kset) +{ + struct kobject *pos, *next; + + list_for_each_entry_safe(pos, next, &kset->list, entry) { + kobject_put(pos); + } +} + +/** + * release_attributes_data() - Clean-up all sysfs directories and files created + */ +static void release_attributes_data(void) +{ + release_reset_bios_data(); + + mutex_lock(&wmi_priv.mutex); + exit_enum_attributes(); + exit_int_attributes(); + exit_str_attributes(); + exit_po_attributes(); + if (wmi_priv.authentication_dir_kset) { + destroy_attribute_objs(wmi_priv.authentication_dir_kset); + kset_unregister(wmi_priv.authentication_dir_kset); + wmi_priv.authentication_dir_kset = NULL; + } + if (wmi_priv.main_dir_kset) { + destroy_attribute_objs(wmi_priv.main_dir_kset); + kset_unregister(wmi_priv.main_dir_kset); + } + mutex_unlock(&wmi_priv.mutex); + +} + +/** + * init_bios_attributes() - Initialize all attributes for a type + * @attr_type: The attribute type to initialize + * @guid: The WMI GUID associated with this type to initialize + * + * Initialiaze all 4 types of attributes enumeration, integer, string and password object. + * Populates each attrbute typ's respective properties under sysfs files + */ +static int init_bios_attributes(int attr_type, const char *guid) +{ + struct kobject *attr_name_kobj; //individual attribute names + union acpi_object *obj = NULL; + union acpi_object *elements; + struct kset *tmp_set; + + /* instance_id needs to be reset for each type GUID + * also, instance IDs are unique within GUID but not across + */ + int instance_id = 0; + int retval = 0; + + retval = alloc_attributes_data(attr_type); + if (retval) + return retval; + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, guid); + if (!obj) + return -ENODEV; + elements = obj->package.elements; + + mutex_lock(&wmi_priv.mutex); + while (elements) { + /* sanity checking */ + if (strlen(elements[ATTR_NAME].string.pointer) == 0) { + pr_debug("empty attribute found\n"); + goto nextobj; + } + if (attr_type == PO) + tmp_set = wmi_priv.authentication_dir_kset; + else + tmp_set = wmi_priv.main_dir_kset; + + if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) { + pr_debug("duplicate attribute name found - %s\n", + elements[ATTR_NAME].string.pointer); + goto nextobj; + } + + /* build attribute */ + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); + if (!attr_name_kobj) { + retval = -ENOMEM; + goto err_attr_init; + } + + attr_name_kobj->kset = tmp_set; + + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s", + elements[ATTR_NAME].string.pointer); + if (retval) { + kobject_put(attr_name_kobj); + goto err_attr_init; + } + + /* enumerate all of this attribute */ + switch (attr_type) { + case ENUM: + retval = populate_enum_data(elements, instance_id, attr_name_kobj); + break; + case INT: + retval = populate_int_data(elements, instance_id, attr_name_kobj); + break; + case STR: + retval = populate_str_data(elements, instance_id, attr_name_kobj); + break; + case PO: + retval = populate_po_data(elements, instance_id, attr_name_kobj); + break; + default: + break; + } + + if (retval) { + pr_debug("failed to populate %s\n", + elements[ATTR_NAME].string.pointer); + goto err_attr_init; + } + +nextobj: + kfree(obj); + instance_id++; + obj = get_wmiobj_pointer(instance_id, guid); + elements = obj ? obj->package.elements : NULL; + } + + mutex_unlock(&wmi_priv.mutex); + return 0; + +err_attr_init: + mutex_unlock(&wmi_priv.mutex); + release_attributes_data(); + kfree(obj); + return retval; +} + +static int __init sysman_init(void) +{ + int ret = 0; + + if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { + pr_err("Unable to run on non-Dell system\n"); + return -ENODEV; + } + + ret = init_bios_attr_set_interface(); + if (ret || !wmi_priv.bios_attr_wdev) { + pr_debug("failed to initialize set interface\n"); + goto fail_set_interface; + } + + ret = init_bios_attr_pass_interface(); + if (ret || !wmi_priv.password_attr_wdev) { + pr_debug("failed to initialize pass interface\n"); + goto fail_pass_interface; + } + + ret = class_register(&firmware_attributes_class); + if (ret) + goto fail_class; + + wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(wmi_priv.class_dev)) { + ret = PTR_ERR(wmi_priv.class_dev); + goto fail_classdev; + } + + wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, + &wmi_priv.class_dev->kobj); + if (!wmi_priv.main_dir_kset) { + ret = -ENOMEM; + goto fail_main_kset; + } + + wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, + &wmi_priv.class_dev->kobj); + if (!wmi_priv.authentication_dir_kset) { + ret = -ENOMEM; + goto fail_authentication_kset; + } + + ret = create_attributes_level_sysfs_files(); + if (ret) { + pr_debug("could not create reset BIOS attribute\n"); + goto fail_reset_bios; + } + + ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate enumeration type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate integer type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate string type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate pass object type attributes\n"); + goto fail_create_group; + } + + return 0; + +fail_create_group: + release_attributes_data(); + +fail_reset_bios: + if (wmi_priv.authentication_dir_kset) { + kset_unregister(wmi_priv.authentication_dir_kset); + wmi_priv.authentication_dir_kset = NULL; + } + +fail_authentication_kset: + if (wmi_priv.main_dir_kset) { + kset_unregister(wmi_priv.main_dir_kset); + wmi_priv.main_dir_kset = NULL; + } + +fail_main_kset: + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + +fail_classdev: + class_unregister(&firmware_attributes_class); + +fail_class: + exit_bios_attr_pass_interface(); + +fail_pass_interface: + exit_bios_attr_set_interface(); + +fail_set_interface: + return ret; +} + +static void __exit sysman_exit(void) +{ + release_attributes_data(); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + class_unregister(&firmware_attributes_class); + exit_bios_attr_set_interface(); + exit_bios_attr_pass_interface(); +} + +module_init(sysman_init); +module_exit(sysman_exit); + +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_AUTHOR("Prasanth Ksr <prasanth.ksr@dell.com>"); +MODULE_AUTHOR("Divya Bharathi <divya.bharathi@dell.com>"); +MODULE_DESCRIPTION("Dell platform setting control interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/i2c-multi-instantiate.c b/drivers/platform/x86/i2c-multi-instantiate.c index 6acc8457866e..b457b0babde3 100644 --- a/drivers/platform/x86/i2c-multi-instantiate.c +++ b/drivers/platform/x86/i2c-multi-instantiate.c @@ -13,6 +13,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/types.h> #define IRQ_RESOURCE_TYPE GENMASK(1, 0) @@ -59,7 +60,6 @@ static int i2c_multi_inst_count_resources(struct acpi_device *adev) static int i2c_multi_inst_probe(struct platform_device *pdev) { struct i2c_multi_inst_data *multi; - const struct acpi_device_id *match; const struct i2c_inst_data *inst_data; struct i2c_board_info board_info = {}; struct device *dev = &pdev->dev; @@ -67,12 +67,11 @@ static int i2c_multi_inst_probe(struct platform_device *pdev) char name[32]; int i, ret; - match = acpi_match_device(dev->driver->acpi_match_table, dev); - if (!match) { + inst_data = device_get_match_data(dev); + if (!inst_data) { dev_err(dev, "Error ACPI match data is missing\n"); return -ENODEV; } - inst_data = (const struct i2c_inst_data *)match->driver_data; adev = ACPI_COMPANION(dev); @@ -118,9 +117,8 @@ static int i2c_multi_inst_probe(struct platform_device *pdev) } multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info); if (IS_ERR(multi->clients[i])) { - ret = PTR_ERR(multi->clients[i]); - if (ret != -EPROBE_DEFER) - dev_err(dev, "Error creating i2c-client, idx %d\n", i); + ret = dev_err_probe(dev, PTR_ERR(multi->clients[i]), + "Error creating i2c-client, idx %d\n", i); goto error; } } @@ -189,7 +187,7 @@ MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids); static struct platform_driver i2c_multi_inst_driver = { .driver = { .name = "I2C multi instantiate pseudo device driver", - .acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids), + .acpi_match_table = i2c_multi_inst_acpi_ids, }, .probe = i2c_multi_inst_probe, .remove = i2c_multi_inst_remove, diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index 86261970bd8f..2f5b8d09143e 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -15,12 +15,16 @@ #include <linux/platform_device.h> #include <linux/suspend.h> +/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ +#define TABLET_MODE_FLAG BIT(6) + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alex Hung"); static const struct acpi_device_id intel_hid_ids[] = { {"INT33D5", 0}, {"INTC1051", 0}, + {"INTC1054", 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, intel_hid_ids); @@ -89,9 +93,26 @@ static const struct dmi_system_id button_array_table[] = { { } }; +/* + * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE, + * these need to be compared via a DMI based authorization list because some + * models have unreliable VGBS return which could cause incorrect + * SW_TABLET_MODE report. + */ +static const struct dmi_system_id dmi_vgbs_allow_list[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"), + }, + }, + { } +}; + struct intel_hid_priv { struct input_dev *input_dev; struct input_dev *array; + struct input_dev *switches; bool wakeup_mode; }; @@ -141,7 +162,7 @@ static bool intel_hid_execute_method(acpi_handle handle, method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; - if (!(intel_hid_dsm_fn_mask & fn_index)) + if (!(intel_hid_dsm_fn_mask & BIT(fn_index))) goto skip_dsm_exec; /* All methods expects a package with one integer element */ @@ -214,7 +235,19 @@ static void intel_hid_init_dsm(acpi_handle handle) obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL, ACPI_TYPE_BUFFER); if (obj) { - intel_hid_dsm_fn_mask = *obj->buffer.pointer; + switch (obj->buffer.length) { + default: + case 2: + intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer; + break; + case 1: + intel_hid_dsm_fn_mask = *obj->buffer.pointer; + break; + case 0: + acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n"); + intel_hid_dsm_fn_mask = 0; + break; + } ACPI_FREE(obj); } @@ -347,11 +380,90 @@ static int intel_button_array_input_setup(struct platform_device *device) return input_register_device(priv->array); } +static int intel_hid_switches_setup(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + + /* Setup input device for switches */ + priv->switches = devm_input_allocate_device(&device->dev); + if (!priv->switches) + return -ENOMEM; + + __set_bit(EV_SW, priv->switches->evbit); + __set_bit(SW_TABLET_MODE, priv->switches->swbit); + + priv->switches->name = "Intel HID switches"; + priv->switches->id.bustype = BUS_HOST; + return input_register_device(priv->switches); +} + +static void report_tablet_mode_state(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long vgbs; + int m; + + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs)) + return; + + m = !(vgbs & TABLET_MODE_FLAG); + input_report_switch(priv->switches, SW_TABLET_MODE, m); + input_sync(priv->switches); +} + +static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event) +{ + if (!input_dev) + return false; + + switch (event) { + case 0xcc: + input_report_switch(input_dev, SW_TABLET_MODE, 1); + input_sync(input_dev); + return true; + case 0xcd: + input_report_switch(input_dev, SW_TABLET_MODE, 0); + input_sync(input_dev); + return true; + default: + return false; + } +} + static void notify_handler(acpi_handle handle, u32 event, void *context) { struct platform_device *device = context; struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); unsigned long long ev_index; + int err; + + /* + * Some convertible have unreliable VGBS return which could cause incorrect + * SW_TABLET_MODE report, in these cases we enable support when receiving + * the first event instead of during driver setup. + * + * Some 360 degree hinges (yoga) style 2-in-1 devices use 2 accelerometers + * to allow the OS to determine the angle between the display and the base + * of the device. On Windows these are read by a special HingeAngleService + * process which calls an ACPI DSM (Device Specific Method) on the + * ACPI KIOX010A device node for the sensor in the display, to let the + * firmware know if the 2-in-1 is in tablet- or laptop-mode so that it can + * disable the kbd and touchpad to avoid spurious input in tablet-mode. + * + * The linux kxcjk1013 driver calls the DSM for this once at probe time + * to ensure that the builtin kbd and touchpad work. On some devices this + * causes a "spurious" 0xcd event on the intel-hid ACPI dev. In this case + * there is not a functional tablet-mode switch, so we should not register + * the tablet-mode switch device. + */ + if (!priv->switches && (event == 0xcc || event == 0xcd) && + !acpi_dev_present("KIOX010A", NULL, -1)) { + dev_info(&device->dev, "switch event received, enable switches supports\n"); + err = intel_hid_switches_setup(device); + if (err) + pr_err("Failed to setup Intel HID switches\n"); + } if (priv->wakeup_mode) { /* @@ -363,6 +475,13 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) if (event == 0xce) goto wakeup; + /* + * Switch events will wake the device and report the new switch + * position to the input subsystem. + */ + if (priv->switches && (event == 0xcc || event == 0xcd)) + goto wakeup; + /* Wake up on 5-button array events only. */ if (event == 0xc0 || !priv->array) return; @@ -374,6 +493,10 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) wakeup: pm_wakeup_hard_event(&device->dev); + + if (report_tablet_mode_event(priv->switches, event)) + return; + return; } @@ -398,6 +521,9 @@ wakeup: } } + if (report_tablet_mode_event(priv->switches, event)) + return; + /* 0xC0 is for HID events, other values are for 5 button array */ if (event != 0xc0) { if (!priv->array || @@ -485,6 +611,16 @@ static int intel_hid_probe(struct platform_device *device) pr_err("Failed to setup Intel 5 button array hotkeys\n"); } + /* Setup switches for devices that we know VGBS return correctly */ + if (dmi_check_system(dmi_vgbs_allow_list)) { + dev_info(&device->dev, "platform supports switches\n"); + err = intel_hid_switches_setup(device); + if (err) + pr_err("Failed to setup Intel HID switches\n"); + else + report_tablet_mode_state(device); + } + status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler, diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 0419c8001fe3..3b49a1f4061b 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -15,9 +15,13 @@ #include <linux/platform_device.h> #include <linux/suspend.h> +/* Returned when NOT in tablet mode on some HP Stream x360 11 models */ +#define VGBS_TABLET_MODE_FLAG_ALT 0x10 /* When NOT in tablet mode, VGBS returns with the flag 0x40 */ -#define TABLET_MODE_FLAG 0x40 -#define DOCK_MODE_FLAG 0x80 +#define VGBS_TABLET_MODE_FLAG 0x40 +#define VGBS_DOCK_MODE_FLAG 0x80 + +#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT) MODULE_LICENSE("GPL"); MODULE_AUTHOR("AceLan Kao"); @@ -72,9 +76,9 @@ static void detect_tablet_mode(struct platform_device *device) if (ACPI_FAILURE(status)) return; - m = !(vgbs & TABLET_MODE_FLAG); + m = !(vgbs & VGBS_TABLET_MODE_FLAGS); input_report_switch(priv->input_dev, SW_TABLET_MODE, m); - m = (vgbs & DOCK_MODE_FLAG) ? 1 : 0; + m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0; input_report_switch(priv->input_dev, SW_DOCK, m); } @@ -212,6 +216,12 @@ static const struct dmi_system_id dmi_switches_allow_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"), }, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"), + }, + }, {} /* Array terminator */ }; diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index 3e5fe66333f1..ee2f757515b0 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -929,7 +929,7 @@ static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset) fd |= CNP_PMC_LATCH_SLPS0_EVENTS; pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd); - slps0_dbg_latch = 0; + slps0_dbg_latch = false; out_unlock: mutex_unlock(&pmcdev->lock); diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c new file mode 100644 index 000000000000..c8939fba4509 --- /dev/null +++ b/drivers/platform/x86/intel_pmt_class.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitory Technology Telemetry driver + * + * Copyright (c) 2020, Intel Corporation. + * All Rights Reserved. + * + * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/pci.h> + +#include "intel_pmt_class.h" + +#define PMT_XA_START 0 +#define PMT_XA_MAX INT_MAX +#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX) + +/* + * sysfs + */ +static ssize_t +intel_pmt_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct intel_pmt_entry *entry = container_of(attr, + struct intel_pmt_entry, + pmt_bin_attr); + + if (off < 0) + return -EINVAL; + + if (off >= entry->size) + return 0; + + if (count > entry->size - off) + count = entry->size - off; + + memcpy_fromio(buf, entry->base + off, count); + + return count; +} + +static int +intel_pmt_mmap(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, struct vm_area_struct *vma) +{ + struct intel_pmt_entry *entry = container_of(attr, + struct intel_pmt_entry, + pmt_bin_attr); + unsigned long vsize = vma->vm_end - vma->vm_start; + struct device *dev = kobj_to_dev(kobj); + unsigned long phys = entry->base_addr; + unsigned long pfn = PFN_DOWN(phys); + unsigned long psize; + + if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE)) + return -EROFS; + + psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE; + if (vsize > psize) { + dev_err(dev, "Requested mmap size is too large\n"); + return -EINVAL; + } + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + if (io_remap_pfn_range(vma, vma->vm_start, pfn, + vsize, vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static ssize_t +guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + + return sprintf(buf, "0x%x\n", entry->guid); +} +static DEVICE_ATTR_RO(guid); + +static ssize_t size_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + + return sprintf(buf, "%zu\n", entry->size); +} +static DEVICE_ATTR_RO(size); + +static ssize_t +offset_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr)); +} +static DEVICE_ATTR_RO(offset); + +static struct attribute *intel_pmt_attrs[] = { + &dev_attr_guid.attr, + &dev_attr_size.attr, + &dev_attr_offset.attr, + NULL +}; +ATTRIBUTE_GROUPS(intel_pmt); + +static struct class intel_pmt_class = { + .name = "intel_pmt", + .owner = THIS_MODULE, + .dev_groups = intel_pmt_groups, +}; + +static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev, + struct resource *disc_res) +{ + struct pci_dev *pci_dev = to_pci_dev(dev->parent); + u8 bir; + + /* + * The base offset should always be 8 byte aligned. + * + * For non-local access types the lower 3 bits of base offset + * contains the index of the base address register where the + * telemetry can be found. + */ + bir = GET_BIR(header->base_offset); + + /* Local access and BARID only for now */ + switch (header->access_type) { + case ACCESS_LOCAL: + if (bir) { + dev_err(dev, + "Unsupported BAR index %d for access type %d\n", + bir, header->access_type); + return -EINVAL; + } + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + */ + entry->base_addr = disc_res->end + 1 + header->base_offset; + break; + case ACCESS_BARID: + /* + * If another BAR was specified then the base offset + * represents the offset within that BAR. SO retrieve the + * address from the parent PCI device and add offset. + */ + entry->base_addr = pci_resource_start(pci_dev, bir) + + GET_ADDRESS(header->base_offset); + break; + default: + dev_err(dev, "Unsupported access type %d\n", + header->access_type); + return -EINVAL; + } + + entry->guid = header->guid; + entry->size = header->size; + + return 0; +} + +static int intel_pmt_dev_register(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns, + struct device *parent) +{ + struct resource res; + struct device *dev; + int ret; + + ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL); + if (ret) + return ret; + + dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry, + "%s%d", ns->name, entry->devid); + + if (IS_ERR(dev)) { + dev_err(parent, "Could not create %s%d device node\n", + ns->name, entry->devid); + ret = PTR_ERR(dev); + goto fail_dev_create; + } + + entry->kobj = &dev->kobj; + + if (ns->attr_grp) { + ret = sysfs_create_group(entry->kobj, ns->attr_grp); + if (ret) + goto fail_sysfs; + } + + /* if size is 0 assume no data buffer, so no file needed */ + if (!entry->size) + return 0; + + res.start = entry->base_addr; + res.end = res.start + entry->size - 1; + res.flags = IORESOURCE_MEM; + + entry->base = devm_ioremap_resource(dev, &res); + if (IS_ERR(entry->base)) { + ret = PTR_ERR(entry->base); + goto fail_ioremap; + } + + sysfs_bin_attr_init(&entry->pmt_bin_attr); + entry->pmt_bin_attr.attr.name = ns->name; + entry->pmt_bin_attr.attr.mode = 0440; + entry->pmt_bin_attr.mmap = intel_pmt_mmap; + entry->pmt_bin_attr.read = intel_pmt_read; + entry->pmt_bin_attr.size = entry->size; + + ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr); + if (!ret) + return 0; + +fail_ioremap: + if (ns->attr_grp) + sysfs_remove_group(entry->kobj, ns->attr_grp); +fail_sysfs: + device_unregister(dev); +fail_dev_create: + xa_erase(ns->xa, entry->devid); + + return ret; +} + +int intel_pmt_dev_create(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns, + struct platform_device *pdev, int idx) +{ + struct intel_pmt_header header; + struct resource *disc_res; + int ret = -ENODEV; + + disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx); + if (!disc_res) + return ret; + + entry->disc_table = devm_platform_ioremap_resource(pdev, idx); + if (IS_ERR(entry->disc_table)) + return PTR_ERR(entry->disc_table); + + ret = ns->pmt_header_decode(entry, &header, &pdev->dev); + if (ret) + return ret; + + ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res); + if (ret) + return ret; + + return intel_pmt_dev_register(entry, ns, &pdev->dev); + +} +EXPORT_SYMBOL_GPL(intel_pmt_dev_create); + +void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns) +{ + struct device *dev = kobj_to_dev(entry->kobj); + + if (entry->size) + sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr); + + if (ns->attr_grp) + sysfs_remove_group(entry->kobj, ns->attr_grp); + + device_unregister(dev); + xa_erase(ns->xa, entry->devid); +} +EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy); + +static int __init pmt_class_init(void) +{ + return class_register(&intel_pmt_class); +} + +static void __exit pmt_class_exit(void) +{ + class_unregister(&intel_pmt_class); +} + +module_init(pmt_class_init); +module_exit(pmt_class_exit); + +MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>"); +MODULE_DESCRIPTION("Intel PMT Class driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_pmt_class.h b/drivers/platform/x86/intel_pmt_class.h new file mode 100644 index 000000000000..de8f8139ba31 --- /dev/null +++ b/drivers/platform/x86/intel_pmt_class.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INTEL_PMT_CLASS_H +#define _INTEL_PMT_CLASS_H + +#include <linux/platform_device.h> +#include <linux/xarray.h> +#include <linux/types.h> +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/io.h> + +/* PMT access types */ +#define ACCESS_BARID 2 +#define ACCESS_LOCAL 3 + +/* PMT discovery base address/offset register layout */ +#define GET_BIR(v) ((v) & GENMASK(2, 0)) +#define GET_ADDRESS(v) ((v) & GENMASK(31, 3)) + +struct intel_pmt_entry { + struct bin_attribute pmt_bin_attr; + struct kobject *kobj; + void __iomem *disc_table; + void __iomem *base; + unsigned long base_addr; + size_t size; + u32 guid; + int devid; +}; + +struct intel_pmt_header { + u32 base_offset; + u32 size; + u32 guid; + u8 access_type; +}; + +struct intel_pmt_namespace { + const char *name; + struct xarray *xa; + const struct attribute_group *attr_grp; + int (*pmt_header_decode)(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev); +}; + +int intel_pmt_dev_create(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns, + struct platform_device *pdev, int idx); +void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns); +#endif diff --git a/drivers/platform/x86/intel_pmt_crashlog.c b/drivers/platform/x86/intel_pmt_crashlog.c new file mode 100644 index 000000000000..97dd749c8290 --- /dev/null +++ b/drivers/platform/x86/intel_pmt_crashlog.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitoring Technology Crashlog driver + * + * Copyright (c) 2020, Intel Corporation. + * All Rights Reserved. + * + * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/overflow.h> + +#include "intel_pmt_class.h" + +#define DRV_NAME "pmt_crashlog" + +/* Crashlog discovery header types */ +#define CRASH_TYPE_OOBMSM 1 + +/* Control Flags */ +#define CRASHLOG_FLAG_DISABLE BIT(27) + +/* + * Bits 28 and 29 control the state of bit 31. + * + * Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured. + * Bit 29 will immediately trigger a crashlog to be generated, setting bit 31. + * Bit 30 is read-only and reserved as 0. + * Bit 31 is the read-only status with a 1 indicating log is complete. + */ +#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(28) +#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(29) +#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31) +#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28) + +/* Crashlog Discovery Header */ +#define CONTROL_OFFSET 0x0 +#define GUID_OFFSET 0x4 +#define BASE_OFFSET 0x8 +#define SIZE_OFFSET 0xC +#define GET_ACCESS(v) ((v) & GENMASK(3, 0)) +#define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4) +#define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16) +/* size is in bytes */ +#define GET_SIZE(v) ((v) * sizeof(u32)) + +struct crashlog_entry { + /* entry must be first member of struct */ + struct intel_pmt_entry entry; + struct mutex control_mutex; +}; + +struct pmt_crashlog_priv { + int num_entries; + struct crashlog_entry entry[]; +}; + +/* + * I/O + */ +static bool pmt_crashlog_complete(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + /* return current value of the crashlog complete flag */ + return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE); +} + +static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + /* return current value of the crashlog disabled flag */ + return !!(control & CRASHLOG_FLAG_DISABLE); +} + +static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) +{ + u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET); + u32 crash_type, version; + + crash_type = GET_TYPE(discovery_header); + version = GET_VERSION(discovery_header); + + /* + * Currently we only recognize OOBMSM version 0 devices. + * We can ignore all other crashlog devices in the system. + */ + return crash_type == CRASH_TYPE_OOBMSM && version == 0; +} + +static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry, + bool disable) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + /* clear trigger bits so we are only modifying disable flag */ + control &= ~CRASHLOG_FLAG_TRIGGER_MASK; + + if (disable) + control |= CRASHLOG_FLAG_DISABLE; + else + control &= ~CRASHLOG_FLAG_DISABLE; + + writel(control, entry->disc_table + CONTROL_OFFSET); +} + +static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + control &= ~CRASHLOG_FLAG_TRIGGER_MASK; + control |= CRASHLOG_FLAG_TRIGGER_CLEAR; + + writel(control, entry->disc_table + CONTROL_OFFSET); +} + +static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + control &= ~CRASHLOG_FLAG_TRIGGER_MASK; + control |= CRASHLOG_FLAG_TRIGGER_EXECUTE; + + writel(control, entry->disc_table + CONTROL_OFFSET); +} + +/* + * sysfs + */ +static ssize_t +enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + int enabled = !pmt_crashlog_disabled(entry); + + return sprintf(buf, "%d\n", enabled); +} + +static ssize_t +enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct crashlog_entry *entry; + bool enabled; + int result; + + entry = dev_get_drvdata(dev); + + result = kstrtobool(buf, &enabled); + if (result) + return result; + + mutex_lock(&entry->control_mutex); + pmt_crashlog_set_disable(&entry->entry, !enabled); + mutex_unlock(&entry->control_mutex); + + return count; +} +static DEVICE_ATTR_RW(enable); + +static ssize_t +trigger_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry; + int trigger; + + entry = dev_get_drvdata(dev); + trigger = pmt_crashlog_complete(entry); + + return sprintf(buf, "%d\n", trigger); +} + +static ssize_t +trigger_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct crashlog_entry *entry; + bool trigger; + int result; + + entry = dev_get_drvdata(dev); + + result = kstrtobool(buf, &trigger); + if (result) + return result; + + mutex_lock(&entry->control_mutex); + + if (!trigger) { + pmt_crashlog_set_clear(&entry->entry); + } else if (pmt_crashlog_complete(&entry->entry)) { + /* we cannot trigger a new crash if one is still pending */ + result = -EEXIST; + goto err; + } else if (pmt_crashlog_disabled(&entry->entry)) { + /* if device is currently disabled, return busy */ + result = -EBUSY; + goto err; + } else { + pmt_crashlog_set_execute(&entry->entry); + } + + result = count; +err: + mutex_unlock(&entry->control_mutex); + return result; +} +static DEVICE_ATTR_RW(trigger); + +static struct attribute *pmt_crashlog_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_trigger.attr, + NULL +}; + +static struct attribute_group pmt_crashlog_group = { + .attrs = pmt_crashlog_attrs, +}; + +static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev) +{ + void __iomem *disc_table = entry->disc_table; + struct crashlog_entry *crashlog; + + if (!pmt_crashlog_supported(entry)) + return 1; + + /* initialize control mutex */ + crashlog = container_of(entry, struct crashlog_entry, entry); + mutex_init(&crashlog->control_mutex); + + header->access_type = GET_ACCESS(readl(disc_table)); + header->guid = readl(disc_table + GUID_OFFSET); + header->base_offset = readl(disc_table + BASE_OFFSET); + + /* Size is measured in DWORDS, but accessor returns bytes */ + header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET)); + + return 0; +} + +static DEFINE_XARRAY_ALLOC(crashlog_array); +static struct intel_pmt_namespace pmt_crashlog_ns = { + .name = "crashlog", + .xa = &crashlog_array, + .attr_grp = &pmt_crashlog_group, + .pmt_header_decode = pmt_crashlog_header_decode, +}; + +/* + * initialization + */ +static int pmt_crashlog_remove(struct platform_device *pdev) +{ + struct pmt_crashlog_priv *priv = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < priv->num_entries; i++) + intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns); + + return 0; +} + +static int pmt_crashlog_probe(struct platform_device *pdev) +{ + struct pmt_crashlog_priv *priv; + size_t size; + int i, ret; + + size = struct_size(priv, entry, pdev->num_resources); + priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + for (i = 0; i < pdev->num_resources; i++) { + struct intel_pmt_entry *entry = &priv->entry[i].entry; + + ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, pdev, i); + if (ret < 0) + goto abort_probe; + if (ret) + continue; + + priv->num_entries++; + } + + return 0; +abort_probe: + pmt_crashlog_remove(pdev); + return ret; +} + +static struct platform_driver pmt_crashlog_driver = { + .driver = { + .name = DRV_NAME, + }, + .remove = pmt_crashlog_remove, + .probe = pmt_crashlog_probe, +}; + +static int __init pmt_crashlog_init(void) +{ + return platform_driver_register(&pmt_crashlog_driver); +} + +static void __exit pmt_crashlog_exit(void) +{ + platform_driver_unregister(&pmt_crashlog_driver); + xa_destroy(&crashlog_array); +} + +module_init(pmt_crashlog_init); +module_exit(pmt_crashlog_exit); + +MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>"); +MODULE_DESCRIPTION("Intel PMT Crashlog driver"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_pmt_telemetry.c b/drivers/platform/x86/intel_pmt_telemetry.c new file mode 100644 index 000000000000..f8a87614efa4 --- /dev/null +++ b/drivers/platform/x86/intel_pmt_telemetry.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitory Technology Telemetry driver + * + * Copyright (c) 2020, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" <david.e.box@linux.intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/overflow.h> + +#include "intel_pmt_class.h" + +#define TELEM_DEV_NAME "pmt_telemetry" + +#define TELEM_SIZE_OFFSET 0x0 +#define TELEM_GUID_OFFSET 0x4 +#define TELEM_BASE_OFFSET 0x8 +#define TELEM_ACCESS(v) ((v) & GENMASK(3, 0)) +/* size is in bytes */ +#define TELEM_SIZE(v) (((v) & GENMASK(27, 12)) >> 10) + +/* Used by client hardware to identify a fixed telemetry entry*/ +#define TELEM_CLIENT_FIXED_BLOCK_GUID 0x10000000 + +struct pmt_telem_priv { + int num_entries; + struct intel_pmt_entry entry[]; +}; + +/* + * Early implementations of PMT on client platforms have some + * differences from the server platforms (which use the Out Of Band + * Management Services Module OOBMSM). This list tracks those + * platforms as needed to handle those differences. Newer client + * platforms are expected to be fully compatible with server. + */ +static const struct pci_device_id pmt_telem_early_client_pci_ids[] = { + { PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */ + { PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */ + { } +}; + +static bool intel_pmt_is_early_client_hw(struct device *dev) +{ + struct pci_dev *parent = to_pci_dev(dev->parent); + + return !!pci_match_id(pmt_telem_early_client_pci_ids, parent); +} + +static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry, + struct device *dev) +{ + u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET); + + if (guid != TELEM_CLIENT_FIXED_BLOCK_GUID) + return false; + + return intel_pmt_is_early_client_hw(dev); +} + +static int pmt_telem_header_decode(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev) +{ + void __iomem *disc_table = entry->disc_table; + + if (pmt_telem_region_overlaps(entry, dev)) + return 1; + + header->access_type = TELEM_ACCESS(readl(disc_table)); + header->guid = readl(disc_table + TELEM_GUID_OFFSET); + header->base_offset = readl(disc_table + TELEM_BASE_OFFSET); + + /* Size is measured in DWORDS, but accessor returns bytes */ + header->size = TELEM_SIZE(readl(disc_table)); + + return 0; +} + +static DEFINE_XARRAY_ALLOC(telem_array); +static struct intel_pmt_namespace pmt_telem_ns = { + .name = "telem", + .xa = &telem_array, + .pmt_header_decode = pmt_telem_header_decode, +}; + +static int pmt_telem_remove(struct platform_device *pdev) +{ + struct pmt_telem_priv *priv = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < priv->num_entries; i++) + intel_pmt_dev_destroy(&priv->entry[i], &pmt_telem_ns); + + return 0; +} + +static int pmt_telem_probe(struct platform_device *pdev) +{ + struct pmt_telem_priv *priv; + size_t size; + int i, ret; + + size = struct_size(priv, entry, pdev->num_resources); + priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + for (i = 0; i < pdev->num_resources; i++) { + struct intel_pmt_entry *entry = &priv->entry[i]; + + ret = intel_pmt_dev_create(entry, &pmt_telem_ns, pdev, i); + if (ret < 0) + goto abort_probe; + if (ret) + continue; + + priv->num_entries++; + } + + return 0; +abort_probe: + pmt_telem_remove(pdev); + return ret; +} + +static struct platform_driver pmt_telem_driver = { + .driver = { + .name = TELEM_DEV_NAME, + }, + .remove = pmt_telem_remove, + .probe = pmt_telem_probe, +}; + +static int __init pmt_telem_init(void) +{ + return platform_driver_register(&pmt_telem_driver); +} +module_init(pmt_telem_init); + +static void __exit pmt_telem_exit(void) +{ + platform_driver_unregister(&pmt_telem_driver); + xa_destroy(&telem_array); +} +module_exit(pmt_telem_exit); + +MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); +MODULE_DESCRIPTION("Intel PMT Telemetry driver"); +MODULE_ALIAS("platform:" TELEM_DEV_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h index 4f6f7f0761fc..fdecdae248d7 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -10,11 +10,11 @@ #ifndef __ISST_IF_COMMON_H #define __ISST_IF_COMMON_H -#define INTEL_RAPL_PRIO_DEVID_0 0x3451 -#define INTEL_CFG_MBOX_DEVID_0 0x3459 +#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451 +#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459 -#define INTEL_RAPL_PRIO_DEVID_1 0x3251 -#define INTEL_CFG_MBOX_DEVID_1 0x3259 +#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251 +#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259 /* * Validate maximum commands in a single request. diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c index 95f01e7a87d5..a2a2d923e60c 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c @@ -146,8 +146,8 @@ static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) } static const struct pci_device_id isst_if_mbox_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_1)}, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)}, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)}, { 0 }, }; MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c index aa17fd7817f8..ff49025ec085 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c @@ -20,15 +20,21 @@ struct isst_mmio_range { int end; }; -struct isst_mmio_range mmio_range[] = { +static struct isst_mmio_range mmio_range_devid_0[] = { {0x04, 0x14}, {0x20, 0xD0}, }; +static struct isst_mmio_range mmio_range_devid_1[] = { + {0x04, 0x14}, + {0x20, 0x11C}, +}; + struct isst_if_device { void __iomem *punit_mmio; u32 range_0[5]; - u32 range_1[45]; + u32 range_1[64]; + struct isst_mmio_range *mmio_range; struct mutex mutex; }; @@ -39,7 +45,8 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) struct pci_dev *pdev; io_reg = (struct isst_if_io_reg *)cmd_ptr; - if (io_reg->reg < 0x04 || io_reg->reg > 0xD0) + + if (io_reg->reg % 4) return -EINVAL; if (io_reg->read_write && !capable(CAP_SYS_ADMIN)) @@ -53,6 +60,10 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) if (!punit_dev) return -EINVAL; + if (io_reg->reg < punit_dev->mmio_range[0].beg || + io_reg->reg > punit_dev->mmio_range[1].end) + return -EINVAL; + /* * Ensure that operation is complete on a PCI device to avoid read * write race by using per PCI device mutex. @@ -71,8 +82,8 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) } static const struct pci_device_id isst_if_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_0)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_1)}, + { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)}, + { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)}, { 0 }, }; MODULE_DEVICE_TABLE(pci, isst_if_ids); @@ -109,6 +120,7 @@ static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent) mutex_init(&punit_dev->mutex); pci_set_drvdata(pdev, punit_dev); + punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data; memset(&cb, 0, sizeof(cb)); cb.cmd_size = sizeof(struct isst_if_io_reg); @@ -138,10 +150,15 @@ static int __maybe_unused isst_if_suspend(struct device *device) for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) punit_dev->range_0[i] = readl(punit_dev->punit_mmio + - mmio_range[0].beg + 4 * i); - for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) - punit_dev->range_1[i] = readl(punit_dev->punit_mmio + - mmio_range[1].beg + 4 * i); + punit_dev->mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) { + u32 addr; + + addr = punit_dev->mmio_range[1].beg + 4 * i; + if (addr > punit_dev->mmio_range[1].end) + break; + punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr); + } return 0; } @@ -153,10 +170,16 @@ static int __maybe_unused isst_if_resume(struct device *device) for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) writel(punit_dev->range_0[i], punit_dev->punit_mmio + - mmio_range[0].beg + 4 * i); - for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) - writel(punit_dev->range_1[i], punit_dev->punit_mmio + - mmio_range[1].beg + 4 * i); + punit_dev->mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) { + u32 addr; + + addr = punit_dev->mmio_range[1].beg + 4 * i; + if (addr > punit_dev->mmio_range[1].end) + break; + + writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr); + } return 0; } diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 986ad3dda1c1..8bce3da32a42 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -319,15 +319,6 @@ static struct i2c_mux_reg_platform_data mlxplat_extended_mux_data[] = { }; /* Platform hotplug devices */ -static struct i2c_board_info mlxplat_mlxcpld_psu[] = { - { - I2C_BOARD_INFO("24c02", 0x51), - }, - { - I2C_BOARD_INFO("24c02", 0x50), - }, -}; - static struct i2c_board_info mlxplat_mlxcpld_pwr[] = { { I2C_BOARD_INFO("dps460", 0x59), @@ -383,15 +374,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { .label = "psu1", .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, .mask = BIT(0), - .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], - .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, }, { .label = "psu2", .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, .mask = BIT(1), - .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], - .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, }, }; @@ -458,7 +447,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = { .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF, .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, .mask = MLXPLAT_CPLD_PSU_MASK, - .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_psu_items_data), .inversed = 1, .health = false, }, @@ -467,7 +456,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = { .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, .mask = MLXPLAT_CPLD_PWR_MASK, - .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_pwr_items_data), .inversed = 0, .health = false, }, @@ -476,7 +465,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = { .aggr_mask = MLXPLAT_CPLD_AGGR_FAN_MASK_DEF, .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, .mask = MLXPLAT_CPLD_FAN_MASK, - .count = ARRAY_SIZE(mlxplat_mlxcpld_fan), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_items_data), .inversed = 1, .health = false, }, @@ -497,7 +486,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = { .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER, .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, .mask = MLXPLAT_CPLD_PSU_MASK, - .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_psu_items_data), .inversed = 1, .health = false, }, @@ -506,7 +495,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = { .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER, .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, .mask = MLXPLAT_CPLD_PWR_MASK, - .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_pwr_items_data), .inversed = 0, .health = false, }, @@ -515,7 +504,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = { .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER, .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, .mask = MLXPLAT_CPLD_FAN_MASK, - .count = ARRAY_SIZE(mlxplat_mlxcpld_fan), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_items_data), .inversed = 1, .health = false, }, @@ -603,15 +592,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn274x_psu_items_data[] = { .label = "psu1", .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, .mask = BIT(0), - .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], - .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, }, { .label = "psu2", .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, .mask = BIT(1), - .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], - .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, }, }; diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 59e38a1d2830..6388c3c705a6 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -12,6 +12,22 @@ *--------------------------------------------------------------------------- * * ChangeLog: + * Aug.18, 2020 Kenneth Chan <kenneth.t.chan@gmail.com> + * -v0.98 add platform devices for firmware brightness registers + * add support for battery charging threshold (eco mode) + * resolve hotkey double trigger + * add write support to mute + * fix sticky_key init bug + * fix naming of platform files for consistency with other + * modules + * split MODULE_AUTHOR() by one author per macro call + * replace ACPI prints with pr_*() macros + * -v0.97 add support for cdpower hardware switch + * -v0.96 merge Lucina's enhancement + * Jan.13, 2009 Martin Lucina <mato@kotelna.sk> + * - add support for optical driver power in + * Y and W series + * * Sep.23, 2008 Harald Welte <laforge@gnumonks.org> * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to * drivers/misc/panasonic-laptop.c @@ -115,14 +131,14 @@ #include <linux/acpi.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> +#include <linux/platform_device.h> -#ifndef ACPI_HOTKEY_COMPONENT -#define ACPI_HOTKEY_COMPONENT 0x10000000 -#endif - -#define _COMPONENT ACPI_HOTKEY_COMPONENT -MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte"); +MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>"); +MODULE_AUTHOR("David Bronaugh <dbronaugh@linuxboxen.org>"); +MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); +MODULE_AUTHOR("Martin Lucina <mato@kotelna.sk>"); +MODULE_AUTHOR("Kenneth Chan <kenneth.t.chan@gmail.com>"); MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops"); MODULE_LICENSE("GPL"); @@ -134,7 +150,10 @@ MODULE_LICENSE("GPL"); #define METHOD_HKEY_SQTY "SQTY" #define METHOD_HKEY_SINF "SINF" #define METHOD_HKEY_SSET "SSET" -#define HKEY_NOTIFY 0x80 +#define METHOD_ECWR "\\_SB.ECWR" +#define HKEY_NOTIFY 0x80 +#define ECO_MODE_OFF 0x00 +#define ECO_MODE_ON 0x80 #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" #define ACPI_PCC_DEVICE_NAME "Hotkey" @@ -143,7 +162,7 @@ MODULE_LICENSE("GPL"); #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent - ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00 + ECO_MODEs: 0x03 = off, 0x83 = on */ enum SINF_BITS { SINF_NUM_BATTERIES = 0, SINF_LCD_TYPE, @@ -155,7 +174,8 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0, SINF_DC_CUR_BRIGHT, SINF_MUTE, SINF_RESERVED, - SINF_ENV_STATE, + SINF_ECO_MODE = 0x0A, + SINF_CUR_BRIGHT = 0x0D, SINF_STICKY_KEY = 0x80, }; /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */ @@ -208,11 +228,17 @@ static const struct key_entry panasonic_keymap[] = { struct pcc_acpi { acpi_handle handle; unsigned long num_sifr; - int sticky_mode; + int sticky_key; + int eco_mode; + int mute; + int ac_brightness; + int dc_brightness; + int current_brightness; u32 *sinf; struct acpi_device *device; struct input_dev *input_dev; struct backlight_device *backlight; + struct platform_device *platform; }; /* method access functions */ @@ -246,8 +272,7 @@ static inline int acpi_pcc_get_sqty(struct acpi_device *device) if (ACPI_SUCCESS(status)) return s; else { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "evaluation error HKEY.SQTY\n")); + pr_err("evaluation error HKEY.SQTY\n"); return -EINVAL; } } @@ -262,21 +287,19 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL, &buffer); if (ACPI_FAILURE(status)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "evaluation error HKEY.SINF\n")); + pr_err("evaluation error HKEY.SINF\n"); return 0; } hkey = buffer.pointer; if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n")); + pr_err("Invalid HKEY.SINF\n"); status = AE_ERROR; goto end; } if (pcc->num_sifr < hkey->package.count) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "SQTY reports bad SINF length\n")); + pr_err("SQTY reports bad SINF length\n"); status = AE_ERROR; goto end; } @@ -286,8 +309,7 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) if (likely(element->type == ACPI_TYPE_INTEGER)) { pcc->sinf[i] = element->integer.value; } else - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Invalid HKEY.SINF data\n")); + pr_err("Invalid HKEY.SINF data\n"); } pcc->sinf[hkey->package.count] = -1; @@ -345,9 +367,101 @@ static const struct backlight_ops pcc_backlight_ops = { }; +/* returns ACPI_SUCCESS if methods to control optical drive are present */ + +static acpi_status check_optd_present(void) +{ + acpi_status status = AE_OK; + acpi_handle handle; + + status = acpi_get_handle(NULL, "\\_SB.STAT", &handle); + if (ACPI_FAILURE(status)) + goto out; + status = acpi_get_handle(NULL, "\\_SB.FBAY", &handle); + if (ACPI_FAILURE(status)) + goto out; + status = acpi_get_handle(NULL, "\\_SB.CDDI", &handle); + if (ACPI_FAILURE(status)) + goto out; + +out: + return status; +} + +/* get optical driver power state */ + +static int get_optd_power_state(void) +{ + acpi_status status; + unsigned long long state; + int result; + + status = acpi_evaluate_integer(NULL, "\\_SB.STAT", NULL, &state); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.STAT\n"); + result = -EIO; + goto out; + } + switch (state) { + case 0: /* power off */ + result = 0; + break; + case 0x0f: /* power on */ + result = 1; + break; + default: + result = -EIO; + break; + } + +out: + return result; +} + +/* set optical drive power state */ + +static int set_optd_power_state(int new_state) +{ + int result; + acpi_status status; + + result = get_optd_power_state(); + if (result < 0) + goto out; + if (new_state == result) + goto out; + + switch (new_state) { + case 0: /* power off */ + /* Call CDDR instead, since they both call the same method + * while CDDI takes 1 arg and we are not quite sure what it is. + */ + status = acpi_evaluate_object(NULL, "\\_SB.CDDR", NULL, NULL); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.CDDR\n"); + result = -EIO; + } + break; + case 1: /* power on */ + status = acpi_evaluate_object(NULL, "\\_SB.FBAY", NULL, NULL); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.FBAY\n"); + result = -EIO; + } + break; + default: + result = -EINVAL; + break; + } + +out: + return result; +} + + /* sysfs user interface functions */ -static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr, +static ssize_t numbatt_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -359,7 +473,7 @@ static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr, return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]); } -static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr, +static ssize_t lcdtype_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -371,7 +485,7 @@ static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr, return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]); } -static ssize_t show_mute(struct device *dev, struct device_attribute *attr, +static ssize_t mute_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -383,7 +497,25 @@ static ssize_t show_mute(struct device *dev, struct device_attribute *attr, return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]); } -static ssize_t show_sticky(struct device *dev, struct device_attribute *attr, +static ssize_t mute_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val == 0 || val == 1) { + acpi_pcc_write_sset(pcc, SINF_MUTE, val); + pcc->mute = val; + } + + return count; +} + +static ssize_t sticky_key_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -392,35 +524,227 @@ static ssize_t show_sticky(struct device *dev, struct device_attribute *attr, if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; - return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]); + return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sticky_key); } -static ssize_t set_sticky(struct device *dev, struct device_attribute *attr, +static ssize_t sticky_key_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct acpi_device *acpi = to_acpi_device(dev); struct pcc_acpi *pcc = acpi_driver_data(acpi); - int val; + int err, val; - if (count && sscanf(buf, "%i", &val) == 1 && - (val == 0 || val == 1)) { + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val == 0 || val == 1) { acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val); - pcc->sticky_mode = val; + pcc->sticky_key = val; } return count; } -static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL); -static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL); -static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL); -static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky); +static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int result; + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + switch (pcc->sinf[SINF_ECO_MODE]) { + case (ECO_MODE_OFF + 3): + result = 0; + break; + case (ECO_MODE_ON + 3): + result = 1; + break; + default: + result = -EIO; + break; + } + return snprintf(buf, PAGE_SIZE, "%u\n", result); +} + +static ssize_t eco_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, state; + + union acpi_object param[2]; + struct acpi_object_list input; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x15; + param[1].type = ACPI_TYPE_INTEGER; + input.count = 2; + input.pointer = param; + + err = kstrtoint(buf, 0, &state); + if (err) + return err; + + switch (state) { + case 0: + param[1].integer.value = ECO_MODE_OFF; + pcc->sinf[SINF_ECO_MODE] = 0; + pcc->eco_mode = 0; + break; + case 1: + param[1].integer.value = ECO_MODE_ON; + pcc->sinf[SINF_ECO_MODE] = 1; + pcc->eco_mode = 1; + break; + default: + /* nothing to do */ + return count; + } + + status = acpi_evaluate_object(NULL, METHOD_ECWR, + &input, NULL); + if (ACPI_FAILURE(status)) { + pr_err("%s evaluation failed\n", METHOD_ECWR); + return -EINVAL; + } + + return count; +} + +static ssize_t ac_brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_AC_CUR_BRIGHT]); +} + +static ssize_t ac_brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val >= 0 && val <= 255) { + acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, val); + pcc->ac_brightness = val; + } + + return count; +} + +static ssize_t dc_brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_DC_CUR_BRIGHT]); +} + +static ssize_t dc_brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val >= 0 && val <= 255) { + acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, val); + pcc->dc_brightness = val; + } + + return count; +} + +static ssize_t current_brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_CUR_BRIGHT]); +} + +static ssize_t current_brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + + if (val >= 0 && val <= 255) { + err = acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, val); + pcc->current_brightness = val; + } + + return count; +} + +static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", get_optd_power_state()); +} + +static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, val; + + err = kstrtoint(buf, 10, &val); + if (err) + return err; + set_optd_power_state(val); + return count; +} + +static DEVICE_ATTR_RO(numbatt); +static DEVICE_ATTR_RO(lcdtype); +static DEVICE_ATTR_RW(mute); +static DEVICE_ATTR_RW(sticky_key); +static DEVICE_ATTR_RW(eco_mode); +static DEVICE_ATTR_RW(ac_brightness); +static DEVICE_ATTR_RW(dc_brightness); +static DEVICE_ATTR_RW(current_brightness); +static DEVICE_ATTR_RW(cdpower); static struct attribute *pcc_sysfs_entries[] = { &dev_attr_numbatt.attr, &dev_attr_lcdtype.attr, &dev_attr_mute.attr, &dev_attr_sticky_key.attr, + &dev_attr_eco_mode.attr, + &dev_attr_ac_brightness.attr, + &dev_attr_dc_brightness.attr, + &dev_attr_current_brightness.attr, + &dev_attr_cdpower.attr, NULL, }; @@ -442,8 +766,7 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY, NULL, &result); if (ACPI_FAILURE(rc)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "error getting hotkey status\n")); + pr_err("error getting hotkey status\n"); return; } @@ -456,10 +779,11 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) result & 0xf, 0x80, false); } - if (!sparse_keymap_report_event(hotk_input_dev, - result & 0xf, result & 0x80, false)) - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Unknown hotkey event: %d\n", result)); + if ((result & 0xf) == 0x7 || (result & 0xf) == 0x9 || (result & 0xf) == 0xa) { + if (!sparse_keymap_report_event(hotk_input_dev, + result & 0xf, result & 0x80, false)) + pr_err("Unknown hotkey event: 0x%04llx\n", result); + } } static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) @@ -476,6 +800,50 @@ static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) } } +static void pcc_optd_notify(acpi_handle handle, u32 event, void *data) +{ + if (event != ACPI_NOTIFY_EJECT_REQUEST) + return; + + set_optd_power_state(0); +} + +static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node) +{ + acpi_status status; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) { + status = acpi_install_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + pcc_optd_notify, pcc); + if (ACPI_FAILURE(status)) + pr_err("Failed to register notify on %s\n", node); + } else + return -ENODEV; + + return 0; +} + +static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node) +{ + acpi_status status = AE_OK; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) { + status = acpi_remove_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + pcc_optd_notify); + if (ACPI_FAILURE(status)) + pr_err("Error removing optd notify handler %s\n", + node); + } +} + static int acpi_pcc_init_input(struct pcc_acpi *pcc) { struct input_dev *input_dev; @@ -494,15 +862,13 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc) error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL); if (error) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Unable to setup input device keymap\n")); + pr_err("Unable to setup input device keymap\n"); goto err_free_dev; } error = input_register_device(input_dev); if (error) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Unable to register input device\n")); + pr_err("Unable to register input device\n"); goto err_free_dev; } @@ -528,10 +894,14 @@ static int acpi_pcc_hotkey_resume(struct device *dev) if (!pcc) return -EINVAL; - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n", - pcc->sticky_mode)); + acpi_pcc_write_sset(pcc, SINF_MUTE, pcc->mute); + acpi_pcc_write_sset(pcc, SINF_ECO_MODE, pcc->eco_mode); + acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_key); + acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, pcc->ac_brightness); + acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, pcc->dc_brightness); + acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, pcc->current_brightness); - return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); + return 0; } #endif @@ -547,14 +917,13 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) num_sifr = acpi_pcc_get_sqty(device); if (num_sifr < 0 || num_sifr > 255) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range")); + pr_err("num_sifr out of range"); return -ENODEV; } pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL); if (!pcc) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Couldn't allocate mem for pcc")); + pr_err("Couldn't allocate mem for pcc"); return -ENOMEM; } @@ -573,15 +942,13 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) result = acpi_pcc_init_input(pcc); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Error installing keyinput handler\n")); + pr_err("Error installing keyinput handler\n"); goto out_sinf; } if (!acpi_pcc_retrieve_biosdata(pcc)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Couldn't retrieve BIOS data\n")); result = -EIO; + pr_err("Couldn't retrieve BIOS data\n"); goto out_input; } /* initialize backlight */ @@ -598,16 +965,42 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) /* read the initial brightness setting from the hardware */ pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; - /* read the initial sticky key mode from the hardware */ - pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY]; + /* Reset initial sticky key mode since the hardware register state is not consistent */ + acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, 0); + pcc->sticky_key = 0; + + pcc->eco_mode = pcc->sinf[SINF_ECO_MODE]; + pcc->mute = pcc->sinf[SINF_MUTE]; + pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; + pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT]; + result = pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT]; /* add sysfs attributes */ result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group); if (result) goto out_backlight; + /* optical drive initialization */ + if (ACPI_SUCCESS(check_optd_present())) { + pcc->platform = platform_device_register_simple("panasonic", + -1, NULL, 0); + if (IS_ERR(pcc->platform)) { + result = PTR_ERR(pcc->platform); + goto out_backlight; + } + result = device_create_file(&pcc->platform->dev, + &dev_attr_cdpower); + pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); + if (result) + goto out_platform; + } else { + pcc->platform = NULL; + } + return 0; +out_platform: + platform_device_unregister(pcc->platform); out_backlight: backlight_device_unregister(pcc->backlight); out_input: @@ -627,6 +1020,12 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device) if (!device || !pcc) return -EINVAL; + if (pcc->platform) { + device_remove_file(&pcc->platform->dev, &dev_attr_cdpower); + platform_device_unregister(pcc->platform); + } + pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); + sysfs_remove_group(&device->dev.kobj, &pcc_attr_group); backlight_device_unregister(pcc->backlight); diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index e5a1b5533408..704813374922 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -2467,13 +2467,11 @@ static int __sony_nc_gfx_switch_status_get(void) * 0: integrated GFX (stamina) */ return result & 0x1 ? SPEED : STAMINA; - break; case 0x015B: /* 0: discrete GFX (speed) * 1: integrated GFX (stamina) */ return result & 0x1 ? STAMINA : SPEED; - break; case 0x0128: /* it's a more elaborated bitmask, for now: * 2: integrated GFX (stamina) @@ -2482,7 +2480,6 @@ static int __sony_nc_gfx_switch_status_get(void) dprintk("GFX Status: 0x%x\n", result); return result & 0x80 ? AUTO : result & 0x02 ? STAMINA : SPEED; - break; } return -EINVAL; } diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c404706379d9..e03df2881dc6 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1025,7 +1025,7 @@ static struct attribute_set *create_attr_set(unsigned int max_members, } #define destroy_attr_set(_set) \ - kfree(_set); + kfree(_set) /* not multi-threaded safe, use it in a single thread per set */ static int add_to_attr_set(struct attribute_set *s, struct attribute *attr) @@ -4028,6 +4028,7 @@ static bool hotkey_notify_usrevent(const u32 hkey, } static void thermal_dump_all_sensors(void); +static void palmsensor_refresh(void); static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev, @@ -4094,8 +4095,8 @@ static bool hotkey_notify_6xxx(const u32 hkey, case TP_HKEY_EV_PALM_DETECTED: case TP_HKEY_EV_PALM_UNDETECTED: - /* palm detected hovering the keyboard, forward to user-space - * via netlink for consumption */ + /* palm detected - pass on to event handler */ + palmsensor_refresh(); return true; default: @@ -9839,102 +9840,146 @@ static struct ibm_struct lcdshadow_driver_data = { }; /************************************************************************* - * DYTC subdriver, for the Lenovo lapmode feature + * Thinkpad sensor interfaces */ #define DYTC_CMD_GET 2 /* To get current IC function and mode */ #define DYTC_GET_LAPMODE_BIT 17 /* Set when in lapmode */ -static bool dytc_lapmode; +#define PALMSENSOR_PRESENT_BIT 0 /* Determine if psensor present */ +#define PALMSENSOR_ON_BIT 1 /* psensor status */ -static void dytc_lapmode_notify_change(void) -{ - sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode"); -} +static bool has_palmsensor; +static bool has_lapsensor; +static bool palm_state; +static bool lap_state; -static int dytc_command(int command, int *output) +static int lapsensor_get(bool *present, bool *state) { acpi_handle dytc_handle; + int output; - if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) { - /* Platform doesn't support DYTC */ + *present = false; + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) return -ENODEV; - } - if (!acpi_evalf(dytc_handle, output, NULL, "dd", command)) + if (!acpi_evalf(dytc_handle, &output, NULL, "dd", DYTC_CMD_GET)) return -EIO; + + *present = true; /*If we get his far, we have lapmode support*/ + *state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false; return 0; } -static int dytc_lapmode_get(bool *state) +static int palmsensor_get(bool *present, bool *state) { - int output, err; + acpi_handle psensor_handle; + int output; - err = dytc_command(DYTC_CMD_GET, &output); - if (err) - return err; - *state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false; + *present = false; + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GPSS", &psensor_handle))) + return -ENODEV; + if (!acpi_evalf(psensor_handle, &output, NULL, "d")) + return -EIO; + + *present = output & BIT(PALMSENSOR_PRESENT_BIT) ? true : false; + *state = output & BIT(PALMSENSOR_ON_BIT) ? true : false; return 0; } -static void dytc_lapmode_refresh(void) +static void lapsensor_refresh(void) { - bool new_state; + bool state; int err; - err = dytc_lapmode_get(&new_state); - if (err || (new_state == dytc_lapmode)) - return; + if (has_lapsensor) { + err = lapsensor_get(&has_lapsensor, &state); + if (err) + return; + if (lap_state != state) { + lap_state = state; + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode"); + } + } +} - dytc_lapmode = new_state; - dytc_lapmode_notify_change(); +static void palmsensor_refresh(void) +{ + bool state; + int err; + + if (has_palmsensor) { + err = palmsensor_get(&has_palmsensor, &state); + if (err) + return; + if (palm_state != state) { + palm_state = state; + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "palmsensor"); + } + } } -/* sysfs lapmode entry */ static ssize_t dytc_lapmode_show(struct device *dev, struct device_attribute *attr, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d\n", dytc_lapmode); + if (has_lapsensor) + return sysfs_emit(buf, "%d\n", lap_state); + return sysfs_emit(buf, "\n"); } - static DEVICE_ATTR_RO(dytc_lapmode); -static struct attribute *dytc_attributes[] = { - &dev_attr_dytc_lapmode.attr, - NULL, -}; - -static const struct attribute_group dytc_attr_group = { - .attrs = dytc_attributes, -}; +static ssize_t palmsensor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (has_palmsensor) + return sysfs_emit(buf, "%d\n", palm_state); + return sysfs_emit(buf, "\n"); +} +static DEVICE_ATTR_RO(palmsensor); -static int tpacpi_dytc_init(struct ibm_init_struct *iibm) +static int tpacpi_proxsensor_init(struct ibm_init_struct *iibm) { - int err; + int palm_err, lap_err, err; - err = dytc_lapmode_get(&dytc_lapmode); - /* If support isn't available (ENODEV) then don't return an error - * but just don't create the sysfs group + palm_err = palmsensor_get(&has_palmsensor, &palm_state); + lap_err = lapsensor_get(&has_lapsensor, &lap_state); + /* + * If support isn't available (ENODEV) for both devices then quit, but + * don't return an error. */ - if (err == -ENODEV) + if ((palm_err == -ENODEV) && (lap_err == -ENODEV)) return 0; - /* For all other errors we can flag the failure */ - if (err) - return err; - - /* Platform supports this feature - create the group */ - err = sysfs_create_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group); - return err; + /* Otherwise, if there was an error return it */ + if (palm_err && (palm_err != ENODEV)) + return palm_err; + if (lap_err && (lap_err != ENODEV)) + return lap_err; + + if (has_palmsensor) { + err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr); + if (err) + return err; + } + if (has_lapsensor) { + err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr); + if (err) + return err; + } + return 0; } -static void dytc_exit(void) +static void proxsensor_exit(void) { - sysfs_remove_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group); + if (has_lapsensor) + sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr); + if (has_palmsensor) + sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr); } -static struct ibm_struct dytc_driver_data = { - .name = "dytc", - .exit = dytc_exit, +static struct ibm_struct proxsensor_driver_data = { + .name = "proximity-sensor", + .exit = proxsensor_exit, }; /**************************************************************************** @@ -9986,8 +10031,7 @@ static void tpacpi_driver_event(const unsigned int hkey_event) } if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) - dytc_lapmode_refresh(); - + lapsensor_refresh(); } static void hotkey_driver_event(const unsigned int scancode) @@ -10427,8 +10471,8 @@ static struct ibm_init_struct ibms_init[] __initdata = { .data = &lcdshadow_driver_data, }, { - .init = tpacpi_dytc_init, - .data = &dytc_driver_data, + .init = tpacpi_proxsensor_init, + .data = &proxsensor_driver_data, }, }; diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index d88f388a3450..c669676ea8e8 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1260,13 +1260,10 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, switch (result) { case -EINVAL: return AE_BAD_PARAMETER; - break; case -ENODEV: return AE_NOT_FOUND; - break; case -ETIME: return AE_TIME; - break; default: return AE_OK; } @@ -1347,7 +1344,7 @@ static int acpi_wmi_remove(struct platform_device *device) acpi_remove_address_space_handler(acpi_device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); wmi_free_devices(acpi_device); - device_destroy(&wmi_bus_class, MKDEV(0, 0)); + device_unregister((struct device *)dev_get_drvdata(&device->dev)); return 0; } @@ -1401,7 +1398,7 @@ static int acpi_wmi_probe(struct platform_device *device) return 0; err_remove_busdev: - device_destroy(&wmi_bus_class, MKDEV(0, 0)); + device_unregister(wmi_bus_dev); err_remove_notify_handler: acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, |