diff options
183 files changed, 11941 insertions, 2251 deletions
diff --git a/Documentation/block/00-INDEX b/Documentation/block/00-INDEX index e840b47613f7..bc5148757edb 100644 --- a/Documentation/block/00-INDEX +++ b/Documentation/block/00-INDEX @@ -26,3 +26,9 @@ switching-sched.txt - Switching I/O schedulers at runtime writeback_cache_control.txt - Control of volatile write back caches +mmc-max-speed.txt + - eMMC layer speed simulation, related to /sys/block/mmcblk*/ + attributes: + max_read_speed + max_write_speed + cache_size diff --git a/Documentation/block/mmc-max-speed.txt b/Documentation/block/mmc-max-speed.txt new file mode 100644 index 000000000000..3f052b9fb999 --- /dev/null +++ b/Documentation/block/mmc-max-speed.txt @@ -0,0 +1,38 @@ +eMMC Block layer simulation speed controls in /sys/block/mmcblk*/ +=============================================== + +Turned on with CONFIG_MMC_SIMULATE_MAX_SPEED which enables MMC device speed +limiting. Used to test and simulate the behavior of the system when +confronted with a slow MMC. + +Enables max_read_speed, max_write_speed and cache_size attributes and module +default parameters to control the write or read maximum KB/second speed +behaviors. + +NB: There is room for improving the algorithm for aspects tied directly to +eMMC specific behavior. For instance, wear leveling and stalls from an +exhausted erase pool. We would expect that if there was a need to provide +similar speed simulation controls to other types of block devices, aspects of +their behavior are modelled separately (e.g. head seek times, heat assist, +shingling and rotational latency). + +/sys/block/mmcblk0/max_read_speed: + +Number of KB/second reads allowed to the block device. Used to test and +simulate the behavior of the system when confronted with a slow reading MMC. +Set to 0 or "off" to place no speed limit. + +/sys/block/mmcblk0/max_write_speed: + +Number of KB/second writes allowed to the block device. Used to test and +simulate the behavior of the system when confronted with a slow writing MMC. +Set to 0 or "off" to place no speed limit. + +/sys/block/mmcblk0/cache_size: + +Number of MB of high speed memory or high speed SLC cache expected on the +eMMC device being simulated. Used to help simulate the write-back behavior +more accurately. The assumption is the cache has no delay, but draws down +in the background to the MLC/TLC primary store at the max_write_speed rate. +Any write speed delays will show up when the cache is full, or when an I/O +request to flush is issued. diff --git a/Documentation/device-mapper/verity.txt b/Documentation/device-mapper/verity.txt index e15bc1a0fb98..89fd8f9a259f 100644 --- a/Documentation/device-mapper/verity.txt +++ b/Documentation/device-mapper/verity.txt @@ -18,11 +18,11 @@ Construction Parameters 0 is the original format used in the Chromium OS. The salt is appended when hashing, digests are stored continuously and - the rest of the block is padded with zeros. + the rest of the block is padded with zeroes. 1 is the current format that should be used for new devices. The salt is prepended when hashing and each digest is - padded with zeros to the power of two. + padded with zeroes to the power of two. <dev> This is the device containing data, the integrity of which needs to be @@ -79,6 +79,37 @@ restart_on_corruption not compatible with ignore_corruption and requires user space support to avoid restart loops. +ignore_zero_blocks + Do not verify blocks that are expected to contain zeroes and always return + zeroes instead. This may be useful if the partition contains unused blocks + that are not guaranteed to contain zeroes. + +use_fec_from_device <fec_dev> + Use forward error correction (FEC) to recover from corruption if hash + verification fails. Use encoding data from the specified device. This + may be the same device where data and hash blocks reside, in which case + fec_start must be outside data and hash areas. + + If the encoding data covers additional metadata, it must be accessible + on the hash device after the hash blocks. + + Note: block sizes for data and hash devices must match. Also, if the + verity <dev> is encrypted the <fec_dev> should be too. + +fec_roots <num> + Number of generator roots. This equals to the number of parity bytes in + the encoding data. For example, in RS(M, N) encoding, the number of roots + is M-N. + +fec_blocks <num> + The number of encoding data blocks on the FEC device. The block size for + the FEC device is <data_block_size>. + +fec_start <offset> + This is the offset, in <data_block_size> blocks, from the start of the + FEC device to the beginning of the encoding data. + + Theory of operation =================== @@ -98,6 +129,11 @@ per-block basis. This allows for a lightweight hash computation on first read into the page cache. Block hashes are stored linearly, aligned to the nearest block size. +If forward error correction (FEC) support is enabled any recovery of +corrupted data will be verified using the cryptographic hash of the +corresponding data. This is why combining error correction with +integrity checking is essential. + Hash Tree --------- diff --git a/Documentation/devicetree/bindings/arm/msm/spss_utils.txt b/Documentation/devicetree/bindings/arm/msm/spss_utils.txt new file mode 100644 index 000000000000..21b96377e5e4 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/spss_utils.txt @@ -0,0 +1,27 @@ +Qualcomm Technologies, Inc. Secure Processor SubSystem Utilities (spss_utils) + +The Secure Processor SubSystem (SPSS) is a dedicated subsystem for security. +It has its own CPU, memories, and cryptographic engine. +It shall provide cryptographic services to other subsystems. +The SPSS firmware is loaded by PIL driver. +The communication with SPSS is done via spcom driver, using glink. + +The spss_utils driver selects the SPSS firmware file, +according to a dedicated fuse and the platform HW version. + +Required properties: +-compatible : should be "qcom,spss_utils" +-qcom,spss-fuse-addr: fuse register physical address +-qcom,spss-fuse-bit: fuse relevant bit +-qcom,spss-test-firmware-name: test firmware file name +-qcom,spss-prod-firmware-name: production firmware file name + +Example: + qcom,spss_utils { + compatible = "qcom,spss-utils"; + + qcom,spss-fuse-addr = <0x007841c4 0x4>; /* spss test fuse physical address */ + qcom,spss-fuse-bit = <27>; + qcom,spss-test-firmware-name = "spss1t"; /* 8 chars max */ + qcom,spss-prod-firmware-name = "spss1p"; /* 8 chars max */ + }; diff --git a/Documentation/devicetree/bindings/cnss/icnss.txt b/Documentation/devicetree/bindings/cnss/icnss.txt index da5159006a98..505966fb9226 100644 --- a/Documentation/devicetree/bindings/cnss/icnss.txt +++ b/Documentation/devicetree/bindings/cnss/icnss.txt @@ -25,6 +25,8 @@ Required properties: uS. Optional properties: + - qcom,icnss-vadc: VADC handle for vph_pwr read APIs. + - qcom,icnss-adc_tm: VADC handle for vph_pwr notification APIs. Example: diff --git a/Documentation/devicetree/bindings/devfreq/bimc-bwmon.txt b/Documentation/devicetree/bindings/devfreq/bimc-bwmon.txt index d96c174d2581..c77f84b61eaf 100644 --- a/Documentation/devicetree/bindings/devfreq/bimc-bwmon.txt +++ b/Documentation/devicetree/bindings/devfreq/bimc-bwmon.txt @@ -5,8 +5,8 @@ can be used to measure the bandwidth of read/write traffic from the BIMC master ports. For example, the CPU subsystem sits on one BIMC master port. Required properties: -- compatible: Must be "qcom,bimc-bwmon", "qcom,bimc-bwmon2" or - "qcom,bimc-bwmon3" +- compatible: Must be "qcom,bimc-bwmon", "qcom,bimc-bwmon2", + "qcom,bimc-bwmon3" or "qcom,bimc-bwmon4" - reg: Pairs of physical base addresses and region sizes of memory mapped registers. - reg-names: Names of the bases for the above registers. Expected @@ -14,6 +14,8 @@ Required properties: - interrupts: Lists the threshold IRQ. - qcom,mport: The hardware master port that this device can monitor - qcom,target-dev: The DT device that corresponds to this master port +- qcom,hw-timer-hz: Hardware sampling rate in Hz. This field must be + specified for "qcom,bimc-bwmon4" Example: qcom,cpu-bwmon { @@ -23,4 +25,5 @@ Example: interrupts = <0 183 1>; qcom,mport = <0>; qcom,target-dev = <&cpubw>; + qcom,hw-timer-hz = <19200000>; }; diff --git a/Documentation/devicetree/bindings/input/touchscreen/gt9xx/gt9xx.txt b/Documentation/devicetree/bindings/input/touchscreen/gt9xx/gt9xx.txt index af09840bb053..4de22947b333 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/gt9xx/gt9xx.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/gt9xx/gt9xx.txt @@ -12,7 +12,7 @@ Required properties: - interrupt-parent : Parent of interrupt. - interrupts : Configuration of touch panel controller interrupt GPIO. - - goodix,family-id : Family identification of the controller. + - goodix,product-id : Product identification of the controller. - interrupt-gpios : Interrupt gpio which is to provide interrupts to host, same as "interrupts" node. - reset-gpios : Reset gpio to control the reset of chip. @@ -37,10 +37,24 @@ Optional properties: - goodix,no-force-update : To specify force update is allowed. - goodix,button-map : Button map of key codes. The number of key codes depend on panel. - - goodix,cfg-data : Touchpanel controller configuration data, ask vendor - to provide that. Default configuration will be - used if this property is not present. - + - goodix,cfg-data0 : Touch screen controller config data group 0. Ask vendor + to provide that. + Driver supports maximum six config groups. If more than one + groups are defined, driver will select config group depending + on hardware configuration. If only config group 0 is defined, + it will be used for all hardware configurations. + Touch screen controller will use its onchip default config data + if this property is not present. + - goodix,cfg-data1 : Touch screen controller config data group 1. Ask vendor + to provide that. + - goodix,cfg-data2 : Touch screen controller config data group 2. Ask vendor + to provide that. + - goodix,cfg-data3 : Touch screen controller config data group 3. Ask vendor + to provide that. + - goodix,cfg-data4 : Touch screen controller config data group 4. Ask vendor + to provide that. + - goodix,cfg-data5 : Touch screen controller config data group 5. Ask vendor + to provide that. Example: i2c@f9927000 { goodix@5d { @@ -54,26 +68,26 @@ i2c@f9927000 { goodix,panel-coords = <0 0 720 1200>; goodix,display-coords = <0 0 720 1080>; goodix,button-map= <158 102 139>; - goodix,family-id = <0x0>; - goodix,cfg-data = [ - 41 D0 02 00 05 0A 05 01 01 08 - 12 58 50 41 03 05 00 00 00 00 - 00 00 00 00 00 00 00 8C 2E 0E - 28 24 73 13 00 00 00 83 03 1D - 40 02 00 00 00 03 64 32 00 00 - 00 1A 38 94 C0 02 00 00 00 04 - 9E 1C 00 8D 20 00 7A 26 00 6D - 2C 00 60 34 00 60 10 38 68 00 - F0 50 35 FF FF 27 00 00 00 00 - 00 01 1B 14 0C 14 00 00 01 00 - 00 00 00 00 00 00 00 00 00 00 - 00 00 02 04 06 08 0A 0C 0E 10 - 12 14 16 18 1A 1C FF FF FF FF - FF FF FF FF FF FF FF FF FF FF - FF FF 00 02 04 06 08 0A 0C 0F - 10 12 13 14 16 18 1C 1D 1E 1F - 20 21 22 24 26 28 29 2A FF FF - FF FF FF FF FF FF FF 22 22 22 - 22 22 22 FF 07 01]; + goodix,product-id = "915"; + goodix,cfg-data0 = [ + 41 D0 02 00 05 0A 05 01 01 08 + 12 58 50 41 03 05 00 00 00 00 + 00 00 00 00 00 00 00 8C 2E 0E + 28 24 73 13 00 00 00 83 03 1D + 40 02 00 00 00 03 64 32 00 00 + 00 1A 38 94 C0 02 00 00 00 04 + 9E 1C 00 8D 20 00 7A 26 00 6D + 2C 00 60 34 00 60 10 38 68 00 + F0 50 35 FF FF 27 00 00 00 00 + 00 01 1B 14 0C 14 00 00 01 00 + 00 00 00 00 00 00 00 00 00 00 + 00 00 02 04 06 08 0A 0C 0E 10 + 12 14 16 18 1A 1C FF FF FF FF + FF FF FF FF FF FF FF FF FF FF + FF FF 00 02 04 06 08 0A 0C 0F + 10 12 13 14 16 18 1C 1D 1E 1F + 20 21 22 24 26 28 29 2A FF FF + FF FF FF FF FF FF FF 22 22 22 + 22 22 22 FF 07 01]; }; }; diff --git a/Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt b/Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt index 48fdd3e03947..bceee5e1747d 100644 --- a/Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt +++ b/Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt @@ -13,6 +13,9 @@ Required properties: - reg : <registers mapping> first entry should contain UFS host controller register address space (mandatory), second entry is the device ref. clock control register map (optional). +- reset : reset specifier pair consists of phandle for the reset provider + and reset lines used by this controller. +- reset-names : reset signal name strings sorted in the same order as the resets property. Optional properties: - phys : phandle to UFS PHY node @@ -76,6 +79,8 @@ Example: clocks = <&core 0>, <&ref 0>, <&iface 0>; clock-names = "core_clk", "ref_clk", "iface_clk"; freq-table-hz = <100000000 200000000>, <0 0>, <0 0>; + resets = <clock_gcc GCC_UFS_BCR>; + reset-names = "core_reset"; phys = <&ufsphy1>; phy-names = "ufsphy"; rpm-level = <3>; diff --git a/android/configs/android-base.cfg b/android/configs/android-base.cfg index a4cffb1840d7..fa53af0c37ad 100644 --- a/android/configs/android-base.cfg +++ b/android/configs/android-base.cfg @@ -21,6 +21,7 @@ CONFIG_CGROUP_SCHED=y CONFIG_CP15_BARRIER_EMULATION=y CONFIG_DM_CRYPT=y CONFIG_DM_VERITY=y +CONFIG_DM_VERITY_FEC=y CONFIG_EMBEDDED=y CONFIG_FB=y CONFIG_HIGH_RES_TIMERS=y @@ -28,6 +29,7 @@ CONFIG_INET6_AH=y CONFIG_INET6_ESP=y CONFIG_INET6_IPCOMP=y CONFIG_INET=y +CONFIG_INET_DIAG_DESTROY=y CONFIG_INET_ESP=y CONFIG_INET_XFRM_MODE_TUNNEL=y CONFIG_IP6_NF_FILTER=y diff --git a/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi b/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi index f0c13096cf37..3f1ffd497f2c 100644 --- a/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi +++ b/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi @@ -154,6 +154,7 @@ "usbin_i", "usbin_v"; + qcom,wipower-max-uw = <5000000>; dpdm-supply = <&qusb_phy0>; qcom,thermal-mitigation diff --git a/arch/arm/boot/dts/qcom/msm8996.dtsi b/arch/arm/boot/dts/qcom/msm8996.dtsi index e0d84e423e88..4b1b9796ebe6 100644 --- a/arch/arm/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm/boot/dts/qcom/msm8996.dtsi @@ -1246,6 +1246,9 @@ "HS_RB_G1_L1", "HS_RB_G2_L1", "HS_RB_G3_L1", "MAX"; + resets = <&clock_gcc UFS_BCR>; + reset-names = "core_reset"; + /* PM QoS */ qcom,pm-qos-cpu-groups = <0x03 0x0C>; qcom,pm-qos-cpu-group-latency-us = <70 70>; diff --git a/arch/arm/boot/dts/qcom/msmcobalt-gpu.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-gpu.dtsi index 617e7ddd9730..1267e578f9b4 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-gpu.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-gpu.dtsi @@ -15,7 +15,7 @@ pil_gpu: qcom,kgsl-hyp { compatible = "qcom,pil-tz-generic"; qcom,pas-id = <13>; - qcom,firmware-name = "a530_zap"; + qcom,firmware-name = "a540_zap"; }; msm_bus: qcom,kgsl-busmon{ diff --git a/arch/arm/boot/dts/qcom/msmcobalt-v2.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-v2.dtsi index 48a23b44b5b2..7ce95b04a509 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-v2.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-v2.dtsi @@ -746,6 +746,11 @@ }; }; +&spss_utils { + qcom,spss-test-firmware-name = "spss2t"; /* 8 chars max */ + qcom,spss-prod-firmware-name = "spss2p"; /* 8 chars max */ +}; + &ufs1 { clock-names = "core_clk", diff --git a/arch/arm/boot/dts/qcom/msmcobalt.dtsi b/arch/arm/boot/dts/qcom/msmcobalt.dtsi index 0a3eb6f70644..789a322f73bf 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt.dtsi @@ -542,12 +542,13 @@ }; qcom,cpu-bwmon { - compatible = "qcom,bimc-bwmon3"; + compatible = "qcom,bimc-bwmon4"; reg = <0x01008000 0x300>, <0x01001000 0x200>; reg-names = "base", "global_base"; interrupts = <0 183 4>; qcom,mport = <0>; qcom,target-dev = <&cpubw>; + qcom,hw-timer-hz = <19200000>; }; mincpubw: qcom,mincpubw { @@ -745,6 +746,7 @@ reg = <0xc8c0000 0x40000>; reg-names = "cc_base"; vdd_dig-supply = <&pmcobalt_s1_level>; + vdd_mmsscc_mx-supply = <&pmcobalt_s9_level>; clock-names = "xo", "gpll0", "gpll0_div", "pclk0_src", "pclk1_src", "byte0_src", "byte1_src", @@ -1634,6 +1636,16 @@ status = "ok"; }; + spss_utils: qcom,spss_utils { + compatible = "qcom,spss-utils"; + /* spss test fuse physical address */ + qcom,spss-fuse-addr = <0x007841c4 0x4>; + qcom,spss-fuse-bit = <27>; + qcom,spss-test-firmware-name = "spss"; /* default name */ + qcom,spss-prod-firmware-name = "spss1p"; /* 8 chars max */ + status = "ok"; + }; + sdhc_2: sdhci@c0a4900 { compatible = "qcom,sdhci-msm"; reg = <0xc0a4900 0x314>, <0xc0a4000 0x800>; @@ -1731,7 +1743,7 @@ "rx_lane0_sync_clk"; clocks = <&clock_gcc clk_gcc_ufs_axi_hw_ctl_clk>, - <&clock_gcc clk_gcc_aggre1_ufs_axi_clk>, + <&clock_gcc clk_gcc_aggre1_ufs_axi_hw_ctl_clk>, <&clock_gcc clk_gcc_ufs_ahb_clk>, <&clock_gcc clk_gcc_ufs_unipro_core_hw_ctl_clk>, <&clock_gcc clk_gcc_ufs_ice_core_hw_ctl_clk>, @@ -1790,6 +1802,9 @@ qcom,pm-qos-cpu-group-latency-us = <70 70>; qcom,pm-qos-default-cpu = <0>; + resets = <&clock_gcc UFS_BCR>; + reset-names = "core_reset"; + status = "disabled"; }; @@ -2866,6 +2881,8 @@ qcom,msm-bus,num-paths = <1>; qcom,msm-bus,vectors-KBps = <81 10065 0 0>, <81 10065 0 16000>; + qcom,icnss-vadc = <&pmcobalt_vadc>; + qcom,icnss-adc_tm = <&pmcobalt_adc_tm>; }; wil6210: qcom,wil6210 { diff --git a/arch/arm/boot/dts/qcom/msmfalcon-coresight.dtsi b/arch/arm/boot/dts/qcom/msmfalcon-coresight.dtsi new file mode 100644 index 000000000000..352856965373 --- /dev/null +++ b/arch/arm/boot/dts/qcom/msmfalcon-coresight.dtsi @@ -0,0 +1,194 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <dt-bindings/clock/qcom,gcc-msmfalcon.h> +#include <dt-bindings/clock/qcom,gpu-msmfalcon.h> +#include <dt-bindings/clock/qcom,mmcc-msmfalcon.h> + +&soc { + tmc_etr: tmc@6048000 { + compatible = "arm,primecell"; + arm,primecell-periphid = <0x0003b961>; + + reg = <0x6048000 0x1000>, + <0x6064000 0x15000>; + reg-names = "tmc-base", "bam-base"; + + arm,buffer-size = <0x400000>; + arm,sg-enable; + + coresight-name = "coresight-tmc-etr"; + + clocks = <&clock_gcc RPM_QDSS_CLK>, + <&clock_gcc RPM_QDSS_A_CLK>; + clock-names = "apb_pclk", "core_a_clk"; + + port{ + tmc_etr_in_replicator: endpoint { + slave-mode; + remote-endpoint = <&replicator_out_tmc_etr>; + }; + }; + }; + + replicator: replicator@6046000 { + compatible = "arm,coresight-replicator"; + + coresight-name = "coresight-replicator"; + + ports{ + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + replicator_out_tmc_etr:endpoint { + remote-endpoint = + <&tmc_etr_in_replicator>; + }; + }; + port@1 { + reg = <0>; + replicator_in_tmc_etf:endpoint { + slave-mode; + remote-endpoint = + <&tmc_etf_out_replicator>; + }; + }; + }; + }; + + tmc_etf: tmc@6047000 { + compatible = "arm,primecell"; + arm,primecell-periphid = <0x0003b961>; + + reg = <0x6047000 0x1000>; + reg-names = "tmc-base"; + + coresight-name = "coresight-tmc-etf"; + + clocks = <&clock_gcc RPM_QDSS_CLK>, + <&clock_gcc RPM_QDSS_A_CLK>; + clock-names = "apb_pclk", "core_a_clk"; + + ports{ + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + tmc_etf_out_replicator:endpoint { + remote-endpoint = + <&replicator_in_tmc_etf>; + }; + }; + port@1 { + reg = <0>; + tmc_etf_in_funnel_merg:endpoint { + slave-mode; + remote-endpoint = + <&funnel_merg_out_tmc_etf>; + }; + }; + }; + }; + + funnel_merg: funnel@6045000 { + compatible = "arm,primecell"; + arm,primecell-periphid = <0x0003b908>; + + reg = <0x6045000 0x1000>; + reg-names = "funnel-base"; + + coresight-name = "coresight-funnel-merg"; + + clocks = <&clock_gcc RPM_QDSS_CLK>, + <&clock_gcc RPM_QDSS_A_CLK>; + clock-names = "apb_pclk", "core_a_clk"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + funnel_merg_out_tmc_etf:endpoint { + remote-endpoint = + <&tmc_etf_in_funnel_merg>; + }; + }; + port@1 { + reg = <0>; + funnel_merg_in_funnel_in0:endpoint { + slave-mode; + remote-endpoint = + <&funnel_in0_out_funnel_merg>; + }; + }; + }; + }; + + funnel_in0: funnel@6041000 { + compatible = "arm,primecell"; + arm,primecell-periphid = <0x0003b908>; + + reg = <0x6041000 0x1000>; + reg-names = "funnel-base"; + + coresight-name = "coresight-funnel-in0"; + + clocks = <&clock_gcc RPM_QDSS_CLK>, + <&clock_gcc RPM_QDSS_A_CLK>; + clock-names = "apb_pclk", "core_a_clk"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + funnel_in0_out_funnel_merg: endpoint { + remote-endpoint = + <&funnel_merg_in_funnel_in0>; + }; + }; + port@4 { + reg = <7>; + funnel_in0_in_stm: endpoint { + slave-mode; + remote-endpoint = <&stm_out_funnel_in0>; + }; + }; + }; + }; + + stm: stm@6002000 { + compatible = "arm,primecell"; + arm,primecell-periphid = <0x0003b962>; + + reg = <0x6002000 0x1000>, + <0x16280000 0x180000>; + reg-names = "stm-base", "stm-data-base"; + + coresight-name = "coresight-stm"; + + clocks = <&clock_gcc RPM_QDSS_CLK>, + <&clock_gcc RPM_QDSS_A_CLK>; + clock-names = "apb_pclk", "core_a_clk"; + + port{ + stm_out_funnel_in0: endpoint { + remote-endpoint = <&funnel_in0_in_stm>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/qcom/msmfalcon.dtsi b/arch/arm/boot/dts/qcom/msmfalcon.dtsi index 7c4e7f0ee55a..246a6cf5371e 100644 --- a/arch/arm/boot/dts/qcom/msmfalcon.dtsi +++ b/arch/arm/boot/dts/qcom/msmfalcon.dtsi @@ -201,6 +201,7 @@ }; #include "msmfalcon-smp2p.dtsi" +#include "msmfalcon-coresight.dtsi" &soc { #address-cells = <1>; #size-cells = <1>; @@ -553,10 +554,6 @@ }; &gdsc_ufs { - clock-names = "bus_clk", "ice_clk", "unipro_clk"; - clocks = <&clock_gcc GCC_UFS_AXI_CLK>, - <&clock_gcc GCC_UFS_ICE_CORE_CLK>, - <&clock_gcc GCC_UFS_UNIPRO_CORE_CLK>; status = "ok"; }; @@ -572,50 +569,30 @@ status = "ok"; }; -&gdsc_hlos1_vote_lpass_core { - status = "ok"; -}; - &gdsc_venus { - clock-names = "bus_clk", "core_clk"; - clocks = <&clock_mmss MMSS_VIDEO_AXI_CLK>, - <&clock_mmss MMSS_VIDEO_CORE_CLK>; status = "ok"; }; &gdsc_venus_core0 { - clock-names = "core0_clk"; - clocks = <&clock_mmss MMSS_VIDEO_SUBCORE0_CLK>; qcom,support-hw-trigger; status = "ok"; }; &gdsc_camss_top { - clock-names = "bus_clk", "vfe_axi"; - clocks = <&clock_mmss MMSS_CAMSS_CPP_AXI_CLK>, - <&clock_mmss MMSS_CAMSS_VFE_VBIF_AXI_CLK>; status = "ok"; }; &gdsc_vfe0 { - clock-names = "core0_clk" , "core0_stream_clk"; - clocks = <&clock_mmss MMSS_CAMSS_VFE0_CLK>, - <&clock_mmss MMSS_CAMSS_VFE0_STREAM_CLK>; parent-supply = <&gdsc_camss_top>; status = "ok"; }; &gdsc_vfe1 { - clock-names = "core1_clk" , "core1_stream_clk"; - clocks = <&clock_mmss MMSS_CAMSS_VFE1_CLK>, - <&clock_mmss MMSS_CAMSS_VFE1_STREAM_CLK>; parent-supply = <&gdsc_camss_top>; status = "ok"; }; &gdsc_cpp { - clock-names = "core_clk"; - clocks = <&clock_mmss MMSS_CAMSS_CPP_CLK>; parent-supply = <&gdsc_camss_top>; status = "ok"; }; diff --git a/arch/arm/boot/dts/qcom/msmtriton.dtsi b/arch/arm/boot/dts/qcom/msmtriton.dtsi index 7b7501dceff3..17198e462024 100644 --- a/arch/arm/boot/dts/qcom/msmtriton.dtsi +++ b/arch/arm/boot/dts/qcom/msmtriton.dtsi @@ -167,6 +167,11 @@ clock-frequency = <19200000>; }; + qcom,sps { + compatible = "qcom,msm_sps_4k"; + qcom,pipe-attr-ee; + }; + uartblsp1dm1: serial@0c170000 { compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm"; reg = <0xc170000 0x1000>; diff --git a/arch/arm/mm/mmap.c b/arch/arm/mm/mmap.c index 4b4058db0781..66353caa35b9 100644 --- a/arch/arm/mm/mmap.c +++ b/arch/arm/mm/mmap.c @@ -173,7 +173,7 @@ unsigned long arch_mmap_rnd(void) { unsigned long rnd; - rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1); + rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1); return rnd << PAGE_SHIFT; } diff --git a/arch/arm64/configs/msmcortex-perf_defconfig b/arch/arm64/configs/msmcortex-perf_defconfig index 5d76e41d4fed..c425514ddec5 100644 --- a/arch/arm64/configs/msmcortex-perf_defconfig +++ b/arch/arm64/configs/msmcortex-perf_defconfig @@ -494,6 +494,7 @@ CONFIG_MSM_GLINK_SMD_XPRT=y CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT=y CONFIG_MSM_GLINK_SPI_XPRT=y CONFIG_MSM_SPCOM=y +CONFIG_MSM_SPSS_UTILS=y CONFIG_MSM_SMEM_LOGGING=y CONFIG_MSM_SMP2P=y CONFIG_MSM_SMP2P_TEST=y diff --git a/arch/arm64/configs/msmcortex_defconfig b/arch/arm64/configs/msmcortex_defconfig index 367822dd0a94..27f7ac6663d6 100644 --- a/arch/arm64/configs/msmcortex_defconfig +++ b/arch/arm64/configs/msmcortex_defconfig @@ -506,6 +506,7 @@ CONFIG_MSM_GLINK_SMD_XPRT=y CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT=y CONFIG_MSM_GLINK_SPI_XPRT=y CONFIG_MSM_SPCOM=y +CONFIG_MSM_SPSS_UTILS=y CONFIG_MSM_SMEM_LOGGING=y CONFIG_MSM_SMP2P=y CONFIG_MSM_SMP2P_TEST=y diff --git a/arch/arm64/mm/mmap.c b/arch/arm64/mm/mmap.c index 4c893b5189dd..232f787a088a 100644 --- a/arch/arm64/mm/mmap.c +++ b/arch/arm64/mm/mmap.c @@ -53,10 +53,10 @@ unsigned long arch_mmap_rnd(void) #ifdef CONFIG_COMPAT if (test_thread_flag(TIF_32BIT)) - rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_compat_bits) - 1); + rnd = get_random_long() & ((1UL << mmap_rnd_compat_bits) - 1); else #endif - rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1); + rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1); return rnd << PAGE_SHIFT; } diff --git a/arch/mips/mm/mmap.c b/arch/mips/mm/mmap.c index 5c81fdd032c3..353037699512 100644 --- a/arch/mips/mm/mmap.c +++ b/arch/mips/mm/mmap.c @@ -146,7 +146,7 @@ unsigned long arch_mmap_rnd(void) { unsigned long rnd; - rnd = (unsigned long)get_random_int(); + rnd = get_random_long(); rnd <<= PAGE_SHIFT; if (TASK_IS_32BIT_ADDR) rnd &= 0xfffffful; @@ -174,7 +174,7 @@ void arch_pick_mmap_layout(struct mm_struct *mm) static inline unsigned long brk_rnd(void) { - unsigned long rnd = get_random_int(); + unsigned long rnd = get_random_long(); rnd = rnd << PAGE_SHIFT; /* 8MB for 32bit, 256MB for 64bit */ diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index ef2ad2d682da..36795d1e7558 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -1659,9 +1659,9 @@ static inline unsigned long brk_rnd(void) /* 8MB for 32bit, 1GB for 64bit */ if (is_32bit_task()) - rnd = (long)(get_random_int() % (1<<(23-PAGE_SHIFT))); + rnd = (get_random_long() % (1UL<<(23-PAGE_SHIFT))); else - rnd = (long)(get_random_int() % (1<<(30-PAGE_SHIFT))); + rnd = (get_random_long() % (1UL<<(30-PAGE_SHIFT))); return rnd << PAGE_SHIFT; } diff --git a/arch/powerpc/mm/mmap.c b/arch/powerpc/mm/mmap.c index 0f0502e12f6c..4087705ba90f 100644 --- a/arch/powerpc/mm/mmap.c +++ b/arch/powerpc/mm/mmap.c @@ -59,9 +59,9 @@ unsigned long arch_mmap_rnd(void) /* 8MB for 32bit, 1GB for 64bit */ if (is_32bit_task()) - rnd = (unsigned long)get_random_int() % (1<<(23-PAGE_SHIFT)); + rnd = get_random_long() % (1<<(23-PAGE_SHIFT)); else - rnd = (unsigned long)get_random_int() % (1<<(30-PAGE_SHIFT)); + rnd = get_random_long() % (1UL<<(30-PAGE_SHIFT)); return rnd << PAGE_SHIFT; } diff --git a/arch/sparc/kernel/sys_sparc_64.c b/arch/sparc/kernel/sys_sparc_64.c index c690c8e16a96..b489e9759518 100644 --- a/arch/sparc/kernel/sys_sparc_64.c +++ b/arch/sparc/kernel/sys_sparc_64.c @@ -264,7 +264,7 @@ static unsigned long mmap_rnd(void) unsigned long rnd = 0UL; if (current->flags & PF_RANDOMIZE) { - unsigned long val = get_random_int(); + unsigned long val = get_random_long(); if (test_thread_flag(TIF_32BIT)) rnd = (val % (1UL << (23UL-PAGE_SHIFT))); else diff --git a/arch/x86/mm/mmap.c b/arch/x86/mm/mmap.c index 96bd1e2bffaf..72bb52f93c3d 100644 --- a/arch/x86/mm/mmap.c +++ b/arch/x86/mm/mmap.c @@ -71,12 +71,12 @@ unsigned long arch_mmap_rnd(void) if (mmap_is_ia32()) #ifdef CONFIG_COMPAT - rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_compat_bits) - 1); + rnd = get_random_long() & ((1UL << mmap_rnd_compat_bits) - 1); #else - rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1); + rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1); #endif else - rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1); + rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1); return rnd << PAGE_SHIFT; } diff --git a/block/blk-merge.c b/block/blk-merge.c index 6e189a49dca6..2b27747b46d3 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -6,7 +6,8 @@ #include <linux/bio.h> #include <linux/blkdev.h> #include <linux/scatterlist.h> -#include <linux/security.h> +#include <linux/pfk.h> +#include <linux/pft.h> #include "blk.h" @@ -729,6 +730,12 @@ static void blk_account_io_merge(struct request *req) } } +static bool crypto_not_mergeable(const struct bio *bio, const struct bio *nxt) +{ + return (!pft_allow_merge_bio(bio, nxt) || + !pfk_allow_merge_bio(bio, nxt)); +} + /* * Has to be called with the request spinlock acquired */ @@ -756,6 +763,9 @@ static int attempt_merge(struct request_queue *q, struct request *req, !blk_write_same_mergeable(req->bio, next->bio)) return 0; + if (crypto_not_mergeable(req->bio, next->bio)) + return 0; + /* * If we are allowed to merge, then append bio list * from next to rq and release next. merge_requests_fn @@ -860,11 +870,9 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio)) return false; - /* Don't merge bios of files with different encryption */ - if (!security_allow_merge_bio(rq->bio, bio)) + if (crypto_not_mergeable(rq->bio, bio)) return false; - return true; } diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 5645850cb4d3..152c81ca50ea 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -1555,6 +1555,7 @@ _request_firmware_nowait( get_device(desc->device); INIT_WORK(&desc->work, request_firmware_work_func); + schedule_work(&desc->work); return 0; } diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index a54d810f2966..6ed8b9326629 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -35,6 +35,8 @@ #include <linux/timer.h> #include <linux/wakeup_reason.h> +#include <asm/current.h> + #include "../base.h" #include "power.h" @@ -1412,7 +1414,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) goto Complete; data.dev = dev; - data.tsk = get_current(); + data.tsk = current; init_timer_on_stack(&timer); timer.expires = jiffies + HZ * 12; timer.function = dpm_drv_timeout; diff --git a/drivers/char/diag/diagchar_core.c b/drivers/char/diag/diagchar_core.c index 9ed43cdc3845..39be6ef3735e 100644 --- a/drivers/char/diag/diagchar_core.c +++ b/drivers/char/diag/diagchar_core.c @@ -999,7 +999,7 @@ static int diag_send_raw_data_remote(int proc, void *buf, int len, (void *)driver->hdlc_encode_buf); send_data: - err = diagfwd_bridge_write(proc, driver->hdlc_encode_buf, + err = diagfwd_bridge_write(bridge_index, driver->hdlc_encode_buf, driver->hdlc_encode_buf_len); if (err) { pr_err_ratelimited("diag: Error writing Callback packet to proc: %d, err: %d\n", @@ -2570,7 +2570,7 @@ static int diag_user_process_raw_data(const char __user *buf, int len) } } if (remote_proc) { - ret = diag_send_raw_data_remote(remote_proc - 1, + ret = diag_send_raw_data_remote(remote_proc, (void *)(user_space_data + token_offset), len, USER_SPACE_RAW_DATA); if (ret) { @@ -3424,13 +3424,13 @@ static int __init diagchar_init(void) ret = diag_masks_init(); if (ret) goto fail; - ret = diag_mux_init(); + ret = diag_remote_init(); if (ret) goto fail; - ret = diagfwd_init(); + ret = diag_mux_init(); if (ret) goto fail; - ret = diag_remote_init(); + ret = diagfwd_init(); if (ret) goto fail; ret = diagfwd_bridge_init(); diff --git a/drivers/char/diag/diagfwd_bridge.c b/drivers/char/diag/diagfwd_bridge.c index 701677b30a71..0462a64614f3 100644 --- a/drivers/char/diag/diagfwd_bridge.c +++ b/drivers/char/diag/diagfwd_bridge.c @@ -107,7 +107,7 @@ static int diagfwd_bridge_mux_write_done(unsigned char *buf, int len, if (id < 0 || id >= NUM_REMOTE_DEV) return -EINVAL; - ch = &bridge_info[id]; + ch = &bridge_info[buf_ctx]; if (ch->dev_ops && ch->dev_ops->fwd_complete) ch->dev_ops->fwd_complete(ch->ctxt, buf, len, 0); return 0; diff --git a/drivers/char/random.c b/drivers/char/random.c index d0da5d852d41..b583e5336630 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -1819,6 +1819,28 @@ unsigned int get_random_int(void) EXPORT_SYMBOL(get_random_int); /* + * Same as get_random_int(), but returns unsigned long. + */ +unsigned long get_random_long(void) +{ + __u32 *hash; + unsigned long ret; + + if (arch_get_random_long(&ret)) + return ret; + + hash = get_cpu_var(get_random_int_hash); + + hash[0] += current->pid + jiffies + random_get_entropy(); + md5_transform(hash, random_int_secret); + ret = *(unsigned long *)hash; + put_cpu_var(get_random_int_hash); + + return ret; +} +EXPORT_SYMBOL(get_random_long); + +/* * randomize_range() returns a start address such that * * [...... <range> .....] diff --git a/drivers/clk/msm/clock-mmss-cobalt.c b/drivers/clk/msm/clock-mmss-cobalt.c index fbd83a02aa02..220a8ba377ba 100644 --- a/drivers/clk/msm/clock-mmss-cobalt.c +++ b/drivers/clk/msm/clock-mmss-cobalt.c @@ -83,6 +83,7 @@ DEFINE_EXT_CLK(ext_dp_phy_pll_vco, NULL); DEFINE_EXT_CLK(ext_dp_phy_pll_link, NULL); static DEFINE_VDD_REGULATORS(vdd_dig, VDD_DIG_NUM, 1, vdd_corner, NULL); +static DEFINE_VDD_REGULATORS(vdd_mmsscc_mx, VDD_DIG_NUM, 1, vdd_corner, NULL); static struct alpha_pll_masks pll_masks_p = { .lock_mask = BIT(31), @@ -102,7 +103,7 @@ static struct pll_vote_clk mmpll0_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll0", .ops = &clk_ops_pll_vote, - VDD_DIG_FMAX_MAP2(LOWER, 404000000, NOMINAL, 808000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 404000000, NOMINAL, 808000000), CLK_INIT(mmpll0_pll.c), }, }; @@ -119,7 +120,7 @@ static struct pll_vote_clk mmpll1_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll1_pll", .ops = &clk_ops_pll_vote, - VDD_DIG_FMAX_MAP2(LOWER, 406000000, NOMINAL, 812000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 406000000, NOMINAL, 812000000), CLK_INIT(mmpll1_pll.c), }, }; @@ -136,7 +137,7 @@ static struct alpha_pll_clk mmpll3_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll3_pll", .ops = &clk_ops_fixed_fabia_alpha_pll, - VDD_DIG_FMAX_MAP2(LOWER, 465000000, LOW, 930000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 465000000, LOW, 930000000), CLK_INIT(mmpll3_pll.c), }, }; @@ -153,7 +154,7 @@ static struct alpha_pll_clk mmpll4_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll4_pll", .ops = &clk_ops_fixed_fabia_alpha_pll, - VDD_DIG_FMAX_MAP2(LOWER, 384000000, LOW, 768000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 384000000, LOW, 768000000), CLK_INIT(mmpll4_pll.c), }, }; @@ -170,7 +171,7 @@ static struct alpha_pll_clk mmpll5_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll5_pll", .ops = &clk_ops_fixed_fabia_alpha_pll, - VDD_DIG_FMAX_MAP2(LOWER, 412500000, LOW, 825000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 412500000, LOW, 825000000), CLK_INIT(mmpll5_pll.c), }, }; @@ -187,7 +188,7 @@ static struct alpha_pll_clk mmpll6_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll6_pll", .ops = &clk_ops_fixed_fabia_alpha_pll, - VDD_DIG_FMAX_MAP2(LOWER, 360000000, NOMINAL, 720000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 360000000, NOMINAL, 720000000), CLK_INIT(mmpll6_pll.c), }, }; @@ -204,7 +205,7 @@ static struct alpha_pll_clk mmpll7_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll7_pll", .ops = &clk_ops_fixed_fabia_alpha_pll, - VDD_DIG_FMAX_MAP2(LOWER, 480000000, NOMINAL, 960000000), + VDD_MM_PLL_FMAX_MAP1(LOW, 960000000), CLK_INIT(mmpll7_pll.c), }, }; @@ -221,7 +222,7 @@ static struct alpha_pll_clk mmpll10_pll = { .parent = &mmsscc_xo.c, .dbg_name = "mmpll10_pll", .ops = &clk_ops_fixed_fabia_alpha_pll, - VDD_DIG_FMAX_MAP2(LOWER, 288000000, NOMINAL, 576000000), + VDD_MM_PLL_FMAX_MAP2(LOWER, 288000000, NOMINAL, 576000000), CLK_INIT(mmpll10_pll.c), }, }; @@ -2626,7 +2627,6 @@ static struct clk_lookup msm_clocks_mmss_cobalt[] = { CLK_LIST(mmss_misc_ahb_clk), CLK_LIST(mmss_misc_cxo_clk), CLK_LIST(mmss_mnoc_ahb_clk), - CLK_LIST(mmss_mnoc_maxi_clk), CLK_LIST(mmss_video_subcore0_clk), CLK_LIST(mmss_video_subcore1_clk), CLK_LIST(mmss_video_ahb_clk), @@ -2635,6 +2635,7 @@ static struct clk_lookup msm_clocks_mmss_cobalt[] = { CLK_LIST(mmss_video_maxi_clk), CLK_LIST(mmss_vmem_ahb_clk), CLK_LIST(mmss_vmem_maxi_clk), + CLK_LIST(mmss_mnoc_maxi_clk), CLK_LIST(mmss_debug_mux), }; @@ -2745,10 +2746,6 @@ static void msm_mmsscc_v2_fixup(void) csiphy_clk_src.c.fmax[VDD_DIG_LOW] = 256000000; dp_pixel_clk_src.c.fmax[VDD_DIG_LOWER] = 148380000; - - video_subcore0_clk_src.c.fmax[VDD_DIG_LOW_L1] = 355200000; - video_subcore1_clk_src.c.fmax[VDD_DIG_LOW_L1] = 355200000; - video_core_clk_src.c.fmax[VDD_DIG_LOW_L1] = 355200000; } int msm_mmsscc_cobalt_probe(struct platform_device *pdev) @@ -2783,6 +2780,14 @@ int msm_mmsscc_cobalt_probe(struct platform_device *pdev) return PTR_ERR(reg); } + reg = vdd_mmsscc_mx.regulator[0] = devm_regulator_get(&pdev->dev, + "vdd_mmsscc_mx"); + if (IS_ERR(reg)) { + if (PTR_ERR(reg) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get vdd_mmsscc_mx regulator!"); + return PTR_ERR(reg); + } + tmp = mmsscc_xo.c.parent = devm_clk_get(&pdev->dev, "xo"); if (IS_ERR(tmp)) { if (PTR_ERR(tmp) != -EPROBE_DEFER) diff --git a/drivers/clk/msm/vdd-level-cobalt.h b/drivers/clk/msm/vdd-level-cobalt.h index f847a4104d4d..c1897b7da7f7 100644 --- a/drivers/clk/msm/vdd-level-cobalt.h +++ b/drivers/clk/msm/vdd-level-cobalt.h @@ -24,6 +24,7 @@ [VDD_DIG_##l1] = (f1), \ }, \ .num_fmax = VDD_DIG_NUM + #define VDD_DIG_FMAX_MAP2(l1, f1, l2, f2) \ .vdd_class = &vdd_dig, \ .fmax = (unsigned long[VDD_DIG_NUM]) { \ @@ -40,6 +41,7 @@ [VDD_DIG_##l3] = (f3), \ }, \ .num_fmax = VDD_DIG_NUM + #define VDD_DIG_FMAX_MAP4(l1, f1, l2, f2, l3, f3, l4, f4) \ .vdd_class = &vdd_dig, \ .fmax = (unsigned long[VDD_DIG_NUM]) { \ @@ -66,6 +68,22 @@ }, \ .num_fmax = VDD_DIG_NUM +#define VDD_MM_PLL_FMAX_MAP1(l1, f1) \ + .vdd_class = &vdd_mmsscc_mx, \ + .fmax = (unsigned long[VDD_DIG_NUM]) { \ + [VDD_DIG_##l1] = (f1), \ + }, \ + .num_fmax = VDD_DIG_NUM + +#define VDD_MM_PLL_FMAX_MAP2(l1, f1, l2, f2) \ + .vdd_class = &vdd_mmsscc_mx, \ + .fmax = (unsigned long[VDD_DIG_NUM]) { \ + [VDD_DIG_##l1] = (f1), \ + [VDD_DIG_##l2] = (f2), \ + }, \ + .num_fmax = VDD_DIG_NUM + + #define VDD_GPU_PLL_FMAX_MAP1(l1, f1) \ .vdd_class = &vdd_gpucc_mx, \ .fmax = (unsigned long[VDD_DIG_NUM]) { \ diff --git a/drivers/clk/qcom/gcc-msm8996.c b/drivers/clk/qcom/gcc-msm8996.c index a63680290219..a7b8ac07e73a 100644 --- a/drivers/clk/qcom/gcc-msm8996.c +++ b/drivers/clk/qcom/gcc-msm8996.c @@ -21,6 +21,7 @@ #include <linux/clk-provider.h> #include <linux/regmap.h> #include <linux/reset-controller.h> +#include <linux/clk.h> #include <dt-bindings/clock/qcom,gcc-msm8996.h> @@ -30,7 +31,6 @@ #include "clk-rcg.h" #include "clk-branch.h" #include "reset.h" -#include "gdsc.h" #define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) } @@ -48,14 +48,6 @@ enum { P_GPLL1_EARLY_DIV }; -static const struct parent_map gcc_sleep_clk_map[] = { - { P_SLEEP_CLK, 5 } -}; - -static const char * const gcc_sleep_clk[] = { - "sleep_clk" -}; - static const struct parent_map gcc_xo_gpll0_map[] = { { P_XO, 0 }, { P_GPLL0, 1 } @@ -284,71 +276,6 @@ static struct clk_alpha_pll_postdiv gpll4 = { }, }; -static const struct freq_tbl ftbl_system_noc_clk_src[] = { - F(19200000, P_XO, 1, 0, 0), - F(50000000, P_GPLL0_EARLY_DIV, 6, 0, 0), - F(100000000, P_GPLL0, 6, 0, 0), - F(150000000, P_GPLL0, 4, 0, 0), - F(200000000, P_GPLL0, 3, 0, 0), - F(240000000, P_GPLL0, 2.5, 0, 0), - { } -}; - -static struct clk_rcg2 system_noc_clk_src = { - .cmd_rcgr = 0x0401c, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_gpll2_gpll3_gpll1_gpll2_early_gpll0_early_div_map, - .freq_tbl = ftbl_system_noc_clk_src, - .clkr.hw.init = &(struct clk_init_data){ - .name = "system_noc_clk_src", - .parent_names = gcc_xo_gpll0_gpll2_gpll3_gpll1_gpll2_early_gpll0_early_div, - .num_parents = 7, - .ops = &clk_rcg2_ops, - }, -}; - -static const struct freq_tbl ftbl_config_noc_clk_src[] = { - F(19200000, P_XO, 1, 0, 0), - F(37500000, P_GPLL0, 16, 0, 0), - F(75000000, P_GPLL0, 8, 0, 0), - { } -}; - -static struct clk_rcg2 config_noc_clk_src = { - .cmd_rcgr = 0x0500c, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_map, - .freq_tbl = ftbl_config_noc_clk_src, - .clkr.hw.init = &(struct clk_init_data){ - .name = "config_noc_clk_src", - .parent_names = gcc_xo_gpll0, - .num_parents = 2, - .ops = &clk_rcg2_ops, - }, -}; - -static const struct freq_tbl ftbl_periph_noc_clk_src[] = { - F(19200000, P_XO, 1, 0, 0), - F(37500000, P_GPLL0, 16, 0, 0), - F(50000000, P_GPLL0, 12, 0, 0), - F(75000000, P_GPLL0, 8, 0, 0), - F(100000000, P_GPLL0, 6, 0, 0), - { } -}; - -static struct clk_rcg2 periph_noc_clk_src = { - .cmd_rcgr = 0x06014, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_map, - .freq_tbl = ftbl_periph_noc_clk_src, - .clkr.hw.init = &(struct clk_init_data){ - .name = "periph_noc_clk_src", - .parent_names = gcc_xo_gpll0, - .num_parents = 2, - .ops = &clk_rcg2_ops, - }, -}; - static const struct freq_tbl ftbl_usb30_master_clk_src[] = { F(19200000, P_XO, 1, 0, 0), F(120000000, P_GPLL0, 5, 0, 0), @@ -464,10 +391,18 @@ static struct clk_rcg2 sdcc1_apps_clk_src = { }, }; +static struct freq_tbl ftbl_sdcc1_ice_core_clk_src[] = { + F(19200000, P_XO, 1, 0, 0), + F(150000000, P_GPLL0, 4, 0, 0), + F(300000000, P_GPLL0, 2, 0, 0), + { } +}; + static struct clk_rcg2 sdcc1_ice_core_clk_src = { .cmd_rcgr = 0x13024, .hid_width = 5, .parent_map = gcc_xo_gpll0_gpll4_gpll0_early_div_map, + .freq_tbl = ftbl_sdcc1_ice_core_clk_src, .clkr.hw.init = &(struct clk_init_data){ .name = "sdcc1_ice_core_clk_src", .parent_names = gcc_xo_gpll0_gpll4_gpll0_early_div, @@ -1104,18 +1039,6 @@ static struct clk_rcg2 tsif_ref_clk_src = { }, }; -static struct clk_rcg2 gcc_sleep_clk_src = { - .cmd_rcgr = 0x43014, - .hid_width = 5, - .parent_map = gcc_sleep_clk_map, - .clkr.hw.init = &(struct clk_init_data){ - .name = "gcc_sleep_clk_src", - .parent_names = gcc_sleep_clk, - .num_parents = 1, - .ops = &clk_rcg2_ops, - }, -}; - static struct clk_rcg2 hmss_rbcpr_clk_src = { .cmd_rcgr = 0x48040, .hid_width = 5, @@ -1230,10 +1153,18 @@ static struct clk_rcg2 ufs_axi_clk_src = { }, }; +static const struct freq_tbl ftbl_ufs_ice_core_clk_src[] = { + F(19200000, P_XO, 1, 0, 0), + F(150000000, P_GPLL0, 4, 0, 0), + F(300000000, P_GPLL0, 2, 0, 0), + { } +}; + static struct clk_rcg2 ufs_ice_core_clk_src = { .cmd_rcgr = 0x76014, .hid_width = 5, .parent_map = gcc_xo_gpll0_map, + .freq_tbl = ftbl_ufs_ice_core_clk_src, .clkr.hw.init = &(struct clk_init_data){ .name = "ufs_ice_core_clk_src", .parent_names = gcc_xo_gpll0, @@ -1242,10 +1173,19 @@ static struct clk_rcg2 ufs_ice_core_clk_src = { }, }; +static const struct freq_tbl ftbl_qspi_ser_clk_src[] = { + F(75000000, P_GPLL0, 8, 0, 0), + F(150000000, P_GPLL0, 4, 0, 0), + F(256000000, P_GPLL4, 1.5, 0, 0), + F(300000000, P_GPLL0, 2, 0, 0), + { } +}; + static struct clk_rcg2 qspi_ser_clk_src = { .cmd_rcgr = 0x8b00c, .hid_width = 5, .parent_map = gcc_xo_gpll0_gpll1_early_div_gpll1_gpll4_gpll0_early_div_map, + .freq_tbl = ftbl_qspi_ser_clk_src, .clkr.hw.init = &(struct clk_init_data){ .name = "qspi_ser_clk_src", .parent_names = gcc_xo_gpll0_gpll1_early_div_gpll1_gpll4_gpll0_early_div, @@ -1306,9 +1246,6 @@ static struct clk_branch gcc_mmss_noc_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_mmss_noc_cfg_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, .ops = &clk_branch2_ops, }, }, @@ -1316,12 +1253,12 @@ static struct clk_branch gcc_mmss_noc_cfg_ahb_clk = { static struct clk_branch gcc_mmss_bimc_gfx_clk = { .halt_reg = 0x9010, + .halt_check = BRANCH_HALT_NO_CHECK_ON_DISABLE, .clkr = { .enable_reg = 0x9010, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_mmss_bimc_gfx_clk", - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1349,9 +1286,6 @@ static struct clk_branch gcc_usb30_sleep_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb30_sleep_clk", - .parent_names = (const char *[]){ "gcc_sleep_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1387,17 +1321,26 @@ static struct clk_branch gcc_usb3_phy_aux_clk = { }, }; -static struct clk_branch gcc_usb3_phy_pipe_clk = { - .halt_reg = 0x50004, +static struct clk_gate2 gcc_usb3_phy_pipe_clk = { + .udelay = 50, .clkr = { .enable_reg = 0x50004, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb3_phy_pipe_clk", - .parent_names = (const char *[]){ "usb3_phy_pipe_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, + }, + }, +}; + +static struct clk_gate2 gpll0_out_msscc = { + .udelay = 1, + .clkr = { + .enable_reg = 0x5200c, + .enable_mask = BIT(2), + .hw.init = &(struct clk_init_data){ + .name = "gpll0_out_msscc", + .ops = &clk_gate2_ops, }, }, }; @@ -1424,9 +1367,6 @@ static struct clk_branch gcc_usb20_sleep_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb20_sleep_clk", - .parent_names = (const char *[]){ "gcc_sleep_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1454,9 +1394,6 @@ static struct clk_branch gcc_usb_phy_cfg_ahb2phy_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb_phy_cfg_ahb2phy_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1484,9 +1421,6 @@ static struct clk_branch gcc_sdcc1_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_sdcc1_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1529,9 +1463,6 @@ static struct clk_branch gcc_sdcc2_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_sdcc2_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1559,9 +1490,6 @@ static struct clk_branch gcc_sdcc3_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_sdcc3_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1589,9 +1517,6 @@ static struct clk_branch gcc_sdcc4_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_sdcc4_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1605,9 +1530,6 @@ static struct clk_branch gcc_blsp1_ahb_clk = { .enable_mask = BIT(17), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp1_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1621,9 +1543,6 @@ static struct clk_branch gcc_blsp1_sleep_clk = { .enable_mask = BIT(16), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp1_sleep_clk", - .parent_names = (const char *[]){ "gcc_sleep_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1907,9 +1826,6 @@ static struct clk_branch gcc_blsp2_ahb_clk = { .enable_mask = BIT(15), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp2_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1923,9 +1839,6 @@ static struct clk_branch gcc_blsp2_sleep_clk = { .enable_mask = BIT(14), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp2_sleep_clk", - .parent_names = (const char *[]){ "gcc_sleep_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2208,9 +2121,6 @@ static struct clk_branch gcc_pdm_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pdm_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2239,9 +2149,6 @@ static struct clk_branch gcc_prng_ahb_clk = { .enable_mask = BIT(13), .hw.init = &(struct clk_init_data){ .name = "gcc_prng_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2254,9 +2161,6 @@ static struct clk_branch gcc_tsif_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_tsif_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2277,21 +2181,6 @@ static struct clk_branch gcc_tsif_ref_clk = { }, }; -static struct clk_branch gcc_tsif_inactivity_timers_clk = { - .halt_reg = 0x3600c, - .clkr = { - .enable_reg = 0x3600c, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_tsif_inactivity_timers_clk", - .parent_names = (const char *[]){ "gcc_sleep_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_boot_rom_ahb_clk = { .halt_reg = 0x38004, .halt_check = BRANCH_HALT_VOTED, @@ -2300,9 +2189,6 @@ static struct clk_branch gcc_boot_rom_ahb_clk = { .enable_mask = BIT(10), .hw.init = &(struct clk_init_data){ .name = "gcc_boot_rom_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2315,7 +2201,6 @@ static struct clk_branch gcc_bimc_gfx_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_bimc_gfx_clk", - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2388,9 +2273,6 @@ static struct clk_branch gcc_pcie_0_slv_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_0_slv_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2403,9 +2285,6 @@ static struct clk_branch gcc_pcie_0_mstr_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_0_mstr_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2418,9 +2297,6 @@ static struct clk_branch gcc_pcie_0_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_0_cfg_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2441,17 +2317,14 @@ static struct clk_branch gcc_pcie_0_aux_clk = { }, }; -static struct clk_branch gcc_pcie_0_pipe_clk = { - .halt_reg = 0x6b018, +static struct clk_gate2 gcc_pcie_0_pipe_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x6b018, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_0_pipe_clk", - .parent_names = (const char *[]){ "pcie_0_pipe_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; @@ -2463,9 +2336,6 @@ static struct clk_branch gcc_pcie_1_slv_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_1_slv_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2478,9 +2348,6 @@ static struct clk_branch gcc_pcie_1_mstr_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_1_mstr_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2493,9 +2360,6 @@ static struct clk_branch gcc_pcie_1_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_1_cfg_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2516,17 +2380,14 @@ static struct clk_branch gcc_pcie_1_aux_clk = { }, }; -static struct clk_branch gcc_pcie_1_pipe_clk = { - .halt_reg = 0x6d018, +static struct clk_gate2 gcc_pcie_1_pipe_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x6d018, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_1_pipe_clk", - .parent_names = (const char *[]){ "pcie_1_pipe_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; @@ -2538,9 +2399,6 @@ static struct clk_branch gcc_pcie_2_slv_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_2_slv_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2553,9 +2411,6 @@ static struct clk_branch gcc_pcie_2_mstr_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_2_mstr_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2568,9 +2423,6 @@ static struct clk_branch gcc_pcie_2_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_2_cfg_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2591,17 +2443,14 @@ static struct clk_branch gcc_pcie_2_aux_clk = { }, }; -static struct clk_branch gcc_pcie_2_pipe_clk = { - .halt_reg = 0x6e108, +static struct clk_gate2 gcc_pcie_2_pipe_clk = { + .udelay = 500, .clkr = { - .enable_reg = 0x6e108, + .enable_reg = 0x6e018, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_2_pipe_clk", - .parent_names = (const char *[]){ "pcie_2_pipe_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; @@ -2613,9 +2462,6 @@ static struct clk_branch gcc_pcie_phy_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_phy_cfg_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2658,9 +2504,6 @@ static struct clk_branch gcc_ufs_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2720,47 +2563,39 @@ static struct clk_branch gcc_ufs_rx_cfg_clk = { }, }; -static struct clk_branch gcc_ufs_tx_symbol_0_clk = { - .halt_reg = 0x75018, +static struct clk_gate2 gcc_ufs_tx_symbol_0_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x75018, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_tx_symbol_0_clk", - .parent_names = (const char *[]){ "ufs_tx_symbol_0_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; -static struct clk_branch gcc_ufs_rx_symbol_0_clk = { - .halt_reg = 0x7501c, +static struct clk_gate2 gcc_ufs_rx_symbol_0_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x7501c, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_rx_symbol_0_clk", - .parent_names = (const char *[]){ "ufs_rx_symbol_0_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; -static struct clk_branch gcc_ufs_rx_symbol_1_clk = { - .halt_reg = 0x75020, +static struct clk_gate2 gcc_ufs_rx_symbol_1_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x75020, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_rx_symbol_1_clk", - .parent_names = (const char *[]){ "ufs_rx_symbol_1_clk_src" }, - .num_parents = 1, .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; @@ -2807,26 +2642,26 @@ static struct clk_branch gcc_ufs_ice_core_clk = { }, }; -static struct clk_branch gcc_ufs_sys_clk_core_clk = { - .halt_check = BRANCH_HALT_DELAY, +static struct clk_gate2 gcc_ufs_sys_clk_core_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x76030, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_sys_clk_core_clk", - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; -static struct clk_branch gcc_ufs_tx_symbol_clk_core_clk = { - .halt_check = BRANCH_HALT_DELAY, +static struct clk_gate2 gcc_ufs_tx_symbol_clk_core_clk = { + .udelay = 500, .clkr = { .enable_reg = 0x76034, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_tx_symbol_clk_core_clk", - .ops = &clk_branch2_ops, + .ops = &clk_gate2_ops, }, }, }; @@ -2838,9 +2673,6 @@ static struct clk_branch gcc_aggre0_snoc_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_aggre0_snoc_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2853,9 +2685,6 @@ static struct clk_branch gcc_aggre0_cnoc_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_aggre0_cnoc_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2863,29 +2692,37 @@ static struct clk_branch gcc_aggre0_cnoc_ahb_clk = { static struct clk_branch gcc_smmu_aggre0_axi_clk = { .halt_reg = 0x81014, + .halt_check = BRANCH_HALT_NO_CHECK_ON_DISABLE, .clkr = { .enable_reg = 0x81014, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_smmu_aggre0_axi_clk", - .parent_names = (const char *[]){ "system_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, }; +static struct clk_gate2 gcc_aggre0_noc_qosgen_extref_clk = { + .udelay = 500, + .clkr = { + .enable_reg = 0x8101c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_aggre0_noc_qosgen_extref_clk", + .ops = &clk_gate2_ops, + }, + }, +}; + static struct clk_branch gcc_smmu_aggre0_ahb_clk = { .halt_reg = 0x81018, + .halt_check = BRANCH_HALT_NO_CHECK_ON_DISABLE, .clkr = { .enable_reg = 0x81018, .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_smmu_aggre0_ahb_clk", - .parent_names = (const char *[]){ "config_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2898,9 +2735,6 @@ static struct clk_branch gcc_aggre1_pnoc_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_aggre1_pnoc_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2943,9 +2777,6 @@ static struct clk_branch gcc_qspi_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_qspi_ahb_clk", - .parent_names = (const char *[]){ "periph_noc_clk_src" }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -2973,8 +2804,6 @@ static struct clk_branch gcc_usb3_clkref_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb3_clkref_clk", - .parent_names = (const char *[]){ "xo" }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -2987,8 +2816,6 @@ static struct clk_branch gcc_hdmi_clkref_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_hdmi_clkref_clk", - .parent_names = (const char *[]){ "xo" }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -3001,8 +2828,6 @@ static struct clk_branch gcc_ufs_clkref_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_ufs_clkref_clk", - .parent_names = (const char *[]){ "xo" }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -3015,8 +2840,6 @@ static struct clk_branch gcc_pcie_clkref_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pcie_clkref_clk", - .parent_names = (const char *[]){ "xo" }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -3029,8 +2852,6 @@ static struct clk_branch gcc_rx2_usb2_clkref_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_rx2_usb2_clkref_clk", - .parent_names = (const char *[]){ "xo" }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -3043,96 +2864,142 @@ static struct clk_branch gcc_rx1_usb2_clkref_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_rx1_usb2_clkref_clk", - .parent_names = (const char *[]){ "xo" }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, }; -static struct clk_hw *gcc_msm8996_hws[] = { - &xo.hw, - &gpll0_early_div.hw, - &ufs_tx_cfg_clk_src.hw, - &ufs_rx_cfg_clk_src.hw, - &ufs_ice_core_postdiv_clk_src.hw, +static struct clk_branch hlos1_vote_lpass_core_smmu_clk = { + .halt_reg = 0x7d010, + .halt_check = BRANCH_HALT_NO_CHECK_ON_DISABLE, + .clkr = { + .enable_reg = 0x7d010, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "hlos1_vote_lpass_core_smmu_clk", + .ops = &clk_branch2_ops, + }, + }, }; -static struct gdsc aggre0_noc_gdsc = { - .gdscr = 0x81004, - .gds_hw_ctrl = 0x81028, - .pd = { - .name = "aggre0_noc", +static struct clk_branch hlos1_vote_lpass_adsp_smmu_clk = { + .halt_reg = 0x7d014, + .halt_check = BRANCH_HALT_NO_CHECK_ON_DISABLE, + .clkr = { + .enable_reg = 0x7d014, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "hlos1_vote_lpass_adsp_smmu_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, - .flags = VOTABLE, }; -static struct gdsc hlos1_vote_aggre0_noc_gdsc = { - .gdscr = 0x7d024, - .pd = { - .name = "hlos1_vote_aggre0_noc", +static struct clk_branch gcc_edp_clkref_clk = { + .halt_reg = 0x88004, + .clkr = { + .enable_reg = 0x88004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_edp_clkref_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, - .flags = VOTABLE, }; -static struct gdsc hlos1_vote_lpass_adsp_gdsc = { - .gdscr = 0x7d034, - .pd = { - .name = "hlos1_vote_lpass_adsp", +static struct clk_branch gcc_mss_cfg_ahb_clk = { + .halt_reg = 0x8a000, + .clkr = { + .enable_reg = 0x8a000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mss_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, - .flags = VOTABLE, }; -static struct gdsc hlos1_vote_lpass_core_gdsc = { - .gdscr = 0x7d038, - .pd = { - .name = "hlos1_vote_lpass_core", +static struct clk_branch gcc_mss_q6_bimc_axi_clk = { + .halt_reg = 0x8a028, + .clkr = { + .enable_reg = 0x8a028, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mss_q6_bimc_axi_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, - .flags = VOTABLE, }; -static struct gdsc usb30_gdsc = { - .gdscr = 0xf004, - .pd = { - .name = "usb30", +static struct clk_branch gcc_mss_snoc_axi_clk = { + .halt_reg = 0x8a024, + .clkr = { + .enable_reg = 0x8a024, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mss_snoc_axi_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, }; -static struct gdsc pcie0_gdsc = { - .gdscr = 0x6b004, - .pd = { - .name = "pcie0", +static struct clk_branch gcc_mss_mnoc_bimc_axi_clk = { + .halt_reg = 0x8a004, + .clkr = { + .enable_reg = 0x8a004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mss_mnoc_bimc_axi_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, }; -static struct gdsc pcie1_gdsc = { - .gdscr = 0x6d004, - .pd = { - .name = "pcie1", +static struct clk_branch gcc_dcc_ahb_clk = { + .halt_reg = 0x84004, + .clkr = { + .enable_reg = 0x84004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_dcc_ahb_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, }; -static struct gdsc pcie2_gdsc = { - .gdscr = 0x6e004, - .pd = { - .name = "pcie2", +static struct clk_branch gcc_aggre0_noc_mpu_cfg_ahb_clk = { + .halt_reg = 0x85000, + .clkr = { + .enable_reg = 0x85000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_aggre0_noc_mpu_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, }; -static struct gdsc ufs_gdsc = { - .gdscr = 0x75004, - .pd = { - .name = "ufs", +static struct clk_branch gcc_mmss_gpll0_div_clk = { + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x5200c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mmss_gpll0_div_clk", + .parent_names = (const char *[]){ "gpll0" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, }, - .pwrsts = PWRSTS_OFF_ON, +}; + +static struct clk_hw *gcc_msm8996_hws[] = { + &xo.hw, + &gpll0_early_div.hw, + &ufs_tx_cfg_clk_src.hw, + &ufs_rx_cfg_clk_src.hw, + &ufs_ice_core_postdiv_clk_src.hw, }; static struct clk_regmap *gcc_msm8996_clocks[] = { @@ -3140,9 +3007,6 @@ static struct clk_regmap *gcc_msm8996_clocks[] = { [GPLL0] = &gpll0.clkr, [GPLL4_EARLY] = &gpll4_early.clkr, [GPLL4] = &gpll4.clkr, - [SYSTEM_NOC_CLK_SRC] = &system_noc_clk_src.clkr, - [CONFIG_NOC_CLK_SRC] = &config_noc_clk_src.clkr, - [PERIPH_NOC_CLK_SRC] = &periph_noc_clk_src.clkr, [USB30_MASTER_CLK_SRC] = &usb30_master_clk_src.clkr, [USB30_MOCK_UTMI_CLK_SRC] = &usb30_mock_utmi_clk_src.clkr, [USB3_PHY_AUX_CLK_SRC] = &usb3_phy_aux_clk_src.clkr, @@ -3191,7 +3055,6 @@ static struct clk_regmap *gcc_msm8996_clocks[] = { [BLSP2_UART6_APPS_CLK_SRC] = &blsp2_uart6_apps_clk_src.clkr, [PDM2_CLK_SRC] = &pdm2_clk_src.clkr, [TSIF_REF_CLK_SRC] = &tsif_ref_clk_src.clkr, - [GCC_SLEEP_CLK_SRC] = &gcc_sleep_clk_src.clkr, [HMSS_RBCPR_CLK_SRC] = &hmss_rbcpr_clk_src.clkr, [HMSS_GPLL0_CLK_SRC] = &hmss_gpll0_clk_src.clkr, [GP1_CLK_SRC] = &gp1_clk_src.clkr, @@ -3269,7 +3132,6 @@ static struct clk_regmap *gcc_msm8996_clocks[] = { [GCC_PRNG_AHB_CLK] = &gcc_prng_ahb_clk.clkr, [GCC_TSIF_AHB_CLK] = &gcc_tsif_ahb_clk.clkr, [GCC_TSIF_REF_CLK] = &gcc_tsif_ref_clk.clkr, - [GCC_TSIF_INACTIVITY_TIMERS_CLK] = &gcc_tsif_inactivity_timers_clk.clkr, [GCC_BOOT_ROM_AHB_CLK] = &gcc_boot_rom_ahb_clk.clkr, [GCC_BIMC_GFX_CLK] = &gcc_bimc_gfx_clk.clkr, [GCC_HMSS_RBCPR_CLK] = &gcc_hmss_rbcpr_clk.clkr, @@ -3319,18 +3181,21 @@ static struct clk_regmap *gcc_msm8996_clocks[] = { [GCC_PCIE_CLKREF_CLK] = &gcc_pcie_clkref_clk.clkr, [GCC_RX2_USB2_CLKREF_CLK] = &gcc_rx2_usb2_clkref_clk.clkr, [GCC_RX1_USB2_CLKREF_CLK] = &gcc_rx1_usb2_clkref_clk.clkr, -}; - -static struct gdsc *gcc_msm8996_gdscs[] = { - [AGGRE0_NOC_GDSC] = &aggre0_noc_gdsc, - [HLOS1_VOTE_AGGRE0_NOC_GDSC] = &hlos1_vote_aggre0_noc_gdsc, - [HLOS1_VOTE_LPASS_ADSP_GDSC] = &hlos1_vote_lpass_adsp_gdsc, - [HLOS1_VOTE_LPASS_CORE_GDSC] = &hlos1_vote_lpass_core_gdsc, - [USB30_GDSC] = &usb30_gdsc, - [PCIE0_GDSC] = &pcie0_gdsc, - [PCIE1_GDSC] = &pcie1_gdsc, - [PCIE2_GDSC] = &pcie2_gdsc, - [UFS_GDSC] = &ufs_gdsc, + [GCC_AGGRE0_NOC_QOSGEN_EXTREF_CLK] = + &gcc_aggre0_noc_qosgen_extref_clk.clkr, + [GCC_HLOS1_VOTE_LPASS_CORE_SMMU_CLK] = + &hlos1_vote_lpass_core_smmu_clk.clkr, + [GCC_HLOS1_VOTE_LPASS_ADSP_SMMU_CLK] = + &hlos1_vote_lpass_adsp_smmu_clk.clkr, + [GCC_EDP_CLKREF_CLK] = &gcc_edp_clkref_clk.clkr, + [GCC_MSS_CFG_AHB_CLK] = &gcc_mss_cfg_ahb_clk.clkr, + [GCC_MSS_Q6_BIMC_AXI_CLK] = &gcc_mss_q6_bimc_axi_clk.clkr, + [GCC_MSS_SNOC_AXI_CLK] = &gcc_mss_snoc_axi_clk.clkr, + [GCC_MSS_MNOC_BIMC_AXI_CLK] = &gcc_mss_mnoc_bimc_axi_clk.clkr, + [GCC_DCC_AHB_ALK] = &gcc_dcc_ahb_clk.clkr, + [GCC_AGGRE0_NOC_MPU_CFG_AHB_CLK] = &gcc_aggre0_noc_mpu_cfg_ahb_clk.clkr, + [GCC_MMSS_GPLL0_DIV_CLK] = &gcc_mmss_gpll0_div_clk.clkr, + [GPLL0_OUT_MSSCC] = &gpll0_out_msscc.clkr, }; static const struct qcom_reset_map gcc_msm8996_resets[] = { @@ -3455,8 +3320,6 @@ static const struct qcom_cc_desc gcc_msm8996_desc = { .num_clks = ARRAY_SIZE(gcc_msm8996_clocks), .resets = gcc_msm8996_resets, .num_resets = ARRAY_SIZE(gcc_msm8996_resets), - .gdscs = gcc_msm8996_gdscs, - .num_gdscs = ARRAY_SIZE(gcc_msm8996_gdscs), }; static const struct of_device_id gcc_msm8996_match_table[] = { @@ -3469,18 +3332,15 @@ static int gcc_msm8996_probe(struct platform_device *pdev) { struct clk *clk; struct device *dev = &pdev->dev; - int i; + int i, ret = 0; struct regmap *regmap; regmap = qcom_cc_map(pdev, &gcc_msm8996_desc); if (IS_ERR(regmap)) return PTR_ERR(regmap); - /* - * Set the HMSS_AHB_CLK_SLEEP_ENA bit to allow the hmss_ahb_clk to be - * turned off by hardware during certain apps low power modes. - */ - regmap_update_bits(regmap, 0x52008, BIT(21), BIT(21)); + /* Set the HMSS_AHB_CLK_ENA bit to enable the hmss_ahb_clk */ + regmap_update_bits(regmap, 0x52004, BIT(21), BIT(21)); for (i = 0; i < ARRAY_SIZE(gcc_msm8996_hws); i++) { clk = devm_clk_register(dev, gcc_msm8996_hws[i]); @@ -3488,7 +3348,19 @@ static int gcc_msm8996_probe(struct platform_device *pdev) return PTR_ERR(clk); } - return qcom_cc_really_probe(pdev, &gcc_msm8996_desc, regmap); + ret = qcom_cc_really_probe(pdev, &gcc_msm8996_desc, regmap); + + if (ret) { + dev_err(&pdev->dev, "Failed to register GCC clocks\n"); + return ret; + } + + /* This clock is used for all MMSS register access */ + clk_prepare_enable(gcc_mmss_noc_cfg_ahb_clk.clkr.hw.clk); + + dev_info(&pdev->dev, "Registered GCC clocks\n"); + + return ret; } static struct platform_driver gcc_msm8996_driver = { diff --git a/drivers/crypto/msm/qce50.c b/drivers/crypto/msm/qce50.c index 61f99370863d..9b42e5ae129a 100644 --- a/drivers/crypto/msm/qce50.c +++ b/drivers/crypto/msm/qce50.c @@ -84,6 +84,8 @@ static LIST_HEAD(qce50_bam_list); /* Index to point the dummy request */ #define DUMMY_REQ_INDEX MAX_QCE_BAM_REQ +#define TOTAL_IOVEC_SPACE_PER_PIPE (QCE_MAX_NUM_DSCR * sizeof(struct sps_iovec)) + enum qce_owner { QCE_OWNER_NONE = 0, QCE_OWNER_CLIENT = 1, @@ -110,6 +112,8 @@ struct qce_device { unsigned char *coh_vmem; /* Allocated coherent virtual memory */ dma_addr_t coh_pmem; /* Allocated coherent physical memory */ int memsize; /* Memory allocated */ + unsigned char *iovec_vmem; /* Allocate iovec virtual memory */ + int iovec_memsize; /* Memory allocated */ uint32_t bam_mem; /* bam physical address, from DT */ uint32_t bam_mem_size; /* bam io size, from DT */ int is_shared; /* CE HW is shared */ @@ -4299,24 +4303,30 @@ static int qce_setup_ce_sps_data(struct qce_device *pce_dev) { unsigned char *vaddr; int i; + unsigned char *iovec_vaddr; + int iovec_memsize; vaddr = pce_dev->coh_vmem; vaddr = (unsigned char *)ALIGN(((uintptr_t)vaddr), pce_dev->ce_bam_info.ce_burst_size); + iovec_vaddr = pce_dev->iovec_vmem; + iovec_memsize = pce_dev->iovec_memsize; for (i = 0; i < MAX_QCE_ALLOC_BAM_REQ; i++) { /* Allow for 256 descriptor (cmd and data) entries per pipe */ pce_dev->ce_request_info[i].ce_sps.in_transfer.iovec = - (struct sps_iovec *)vaddr; + (struct sps_iovec *)iovec_vaddr; pce_dev->ce_request_info[i].ce_sps.in_transfer.iovec_phys = - (uintptr_t)GET_PHYS_ADDR(vaddr); - vaddr += QCE_MAX_NUM_DSCR * sizeof(struct sps_iovec); - + virt_to_phys(pce_dev->ce_request_info[i]. + ce_sps.in_transfer.iovec); + iovec_vaddr += TOTAL_IOVEC_SPACE_PER_PIPE; + iovec_memsize -= TOTAL_IOVEC_SPACE_PER_PIPE; pce_dev->ce_request_info[i].ce_sps.out_transfer.iovec = - (struct sps_iovec *)vaddr; + (struct sps_iovec *)iovec_vaddr; pce_dev->ce_request_info[i].ce_sps.out_transfer.iovec_phys = - (uintptr_t)GET_PHYS_ADDR(vaddr); - vaddr += QCE_MAX_NUM_DSCR * sizeof(struct sps_iovec); - + virt_to_phys(pce_dev->ce_request_info[i]. + ce_sps.out_transfer.iovec); + iovec_vaddr += TOTAL_IOVEC_SPACE_PER_PIPE; + iovec_memsize -= TOTAL_IOVEC_SPACE_PER_PIPE; if (pce_dev->support_cmd_dscr) qce_setup_cmdlistptrs(pce_dev, i, &vaddr); vaddr = (unsigned char *)ALIGN(((uintptr_t)vaddr), @@ -4343,7 +4353,8 @@ static int qce_setup_ce_sps_data(struct qce_device *pce_dev) } pce_dev->dummyreq.in_buf = (uint8_t *)vaddr; vaddr += DUMMY_REQ_DATA_LEN; - if ((vaddr - pce_dev->coh_vmem) > pce_dev->memsize) + if ((vaddr - pce_dev->coh_vmem) > pce_dev->memsize || + iovec_memsize < 0) panic("qce50: Not enough coherent memory. Allocate %x , need %lx\n", pce_dev->memsize, (uintptr_t)vaddr - (uintptr_t)pce_dev->coh_vmem); @@ -5933,12 +5944,19 @@ void *qce_open(struct platform_device *pdev, int *rc) pce_dev->memsize = 10 * PAGE_SIZE * MAX_QCE_ALLOC_BAM_REQ; pce_dev->coh_vmem = dma_alloc_coherent(pce_dev->pdev, pce_dev->memsize, &pce_dev->coh_pmem, GFP_KERNEL); + if (pce_dev->coh_vmem == NULL) { *rc = -ENOMEM; pr_err("Can not allocate coherent memory for sps data\n"); goto err_iobase; } + pce_dev->iovec_memsize = TOTAL_IOVEC_SPACE_PER_PIPE * + MAX_QCE_ALLOC_BAM_REQ * 2; + pce_dev->iovec_vmem = kzalloc(pce_dev->iovec_memsize, GFP_KERNEL); + if (pce_dev->iovec_vmem == NULL) + goto err_mem; + *rc = __qce_init_clk(pce_dev); if (*rc) goto err_mem; @@ -5978,6 +5996,7 @@ err_enable_clk: __qce_deinit_clk(pce_dev); err_mem: + kfree(pce_dev->iovec_vmem); if (pce_dev->coh_vmem) dma_free_coherent(pce_dev->pdev, pce_dev->memsize, pce_dev->coh_vmem, pce_dev->coh_pmem); @@ -6008,6 +6027,7 @@ int qce_close(void *handle) if (pce_dev->coh_vmem) dma_free_coherent(pce_dev->pdev, pce_dev->memsize, pce_dev->coh_vmem, pce_dev->coh_pmem); + kfree(pce_dev->iovec_vmem); qce_disable_clk(pce_dev); __qce_deinit_clk(pce_dev); diff --git a/drivers/devfreq/bimc-bwmon.c b/drivers/devfreq/bimc-bwmon.c index 2b0bacdb5f6a..707a244e62e9 100644 --- a/drivers/devfreq/bimc-bwmon.c +++ b/drivers/devfreq/bimc-bwmon.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -18,6 +18,7 @@ #include <linux/init.h> #include <linux/io.h> #include <linux/delay.h> +#include <linux/bitops.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/interrupt.h> @@ -40,10 +41,24 @@ #define MON_MASK(m) ((m)->base + 0x298) #define MON_MATCH(m) ((m)->base + 0x29C) +#define MON2_EN(m) ((m)->base + 0x2A0) +#define MON2_CLEAR(m) ((m)->base + 0x2A4) +#define MON2_SW(m) ((m)->base + 0x2A8) +#define MON2_THRES_HI(m) ((m)->base + 0x2AC) +#define MON2_THRES_MED(m) ((m)->base + 0x2B0) +#define MON2_THRES_LO(m) ((m)->base + 0x2B4) +#define MON2_ZONE_ACTIONS(m) ((m)->base + 0x2B8) +#define MON2_ZONE_CNT_THRES(m) ((m)->base + 0x2BC) +#define MON2_BYTE_CNT(m) ((m)->base + 0x2D0) +#define MON2_WIN_TIMER(m) ((m)->base + 0x2D4) +#define MON2_ZONE_CNT(m) ((m)->base + 0x2D8) +#define MON2_ZONE_MAX(m, zone) ((m)->base + 0x2E0 + 0x4 * zone) + struct bwmon_spec { bool wrap_on_thres; bool overflow; bool throt_adj; + bool hw_sampling; }; struct bwmon { @@ -54,24 +69,37 @@ struct bwmon { const struct bwmon_spec *spec; struct device *dev; struct bw_hwmon hw; + u32 hw_timer_hz; u32 throttle_adj; + u32 sample_size_ms; + u32 intr_status; }; #define to_bwmon(ptr) container_of(ptr, struct bwmon, hw) +#define has_hw_sampling(m) (m->spec->hw_sampling) #define ENABLE_MASK BIT(0) #define THROTTLE_MASK 0x1F #define THROTTLE_SHIFT 16 +#define INT_ENABLE_V1 0x1 +#define INT_STATUS_MASK 0x03 +#define INT_STATUS_MASK_HWS 0xF0 static DEFINE_SPINLOCK(glb_lock); static void mon_enable(struct bwmon *m) { - writel_relaxed((ENABLE_MASK | m->throttle_adj), MON_EN(m)); + if (has_hw_sampling(m)) + writel_relaxed((ENABLE_MASK | m->throttle_adj), MON2_EN(m)); + else + writel_relaxed((ENABLE_MASK | m->throttle_adj), MON_EN(m)); } static void mon_disable(struct bwmon *m) { - writel_relaxed(m->throttle_adj, MON_EN(m)); + if (has_hw_sampling(m)) + writel_relaxed(m->throttle_adj, MON2_EN(m)); + else + writel_relaxed(m->throttle_adj, MON_EN(m)); /* * mon_disable() and mon_irq_clear(), * If latter goes first and count happen to trigger irq, we would @@ -80,17 +108,46 @@ static void mon_disable(struct bwmon *m) mb(); } -static void mon_clear(struct bwmon *m) +#define MON_CLEAR_BIT 0x1 +#define MON_CLEAR_ALL_BIT 0x2 +static void mon_clear(struct bwmon *m, bool clear_all) { - writel_relaxed(0x1, MON_CLEAR(m)); + if (!has_hw_sampling(m)) { + writel_relaxed(MON_CLEAR_BIT, MON_CLEAR(m)); + goto out; + } + + if (clear_all) + writel_relaxed(MON_CLEAR_ALL_BIT, MON2_CLEAR(m)); + else + writel_relaxed(MON_CLEAR_BIT, MON2_CLEAR(m)); + /* * The counter clear and IRQ clear bits are not in the same 4KB * region. So, we need to make sure the counter clear is completed * before we try to clear the IRQ or do any other counter operations. */ +out: mb(); } +#define SAMPLE_WIN_LIM 0xFFFFF +static void mon_set_hw_sampling_window(struct bwmon *m, unsigned int sample_ms) +{ + u32 rate; + + if (unlikely(sample_ms != m->sample_size_ms)) { + rate = mult_frac(sample_ms, m->hw_timer_hz, MSEC_PER_SEC); + m->sample_size_ms = sample_ms; + if (unlikely(rate > SAMPLE_WIN_LIM)) { + rate = SAMPLE_WIN_LIM; + pr_warn("Sample window %u larger than hw limit: %u\n", + rate, SAMPLE_WIN_LIM); + } + writel_relaxed(rate, MON2_SW(m)); + } +} + static void mon_irq_enable(struct bwmon *m) { u32 val; @@ -99,11 +156,11 @@ static void mon_irq_enable(struct bwmon *m) val = readl_relaxed(GLB_INT_EN(m)); val |= 1 << m->mport; writel_relaxed(val, GLB_INT_EN(m)); - spin_unlock(&glb_lock); val = readl_relaxed(MON_INT_EN(m)); - val |= 0x1; + val |= has_hw_sampling(m) ? INT_STATUS_MASK_HWS : INT_ENABLE_V1; writel_relaxed(val, MON_INT_EN(m)); + spin_unlock(&glb_lock); /* * make Sure irq enable complete for local and global * to avoid race with other monitor calls @@ -119,11 +176,11 @@ static void mon_irq_disable(struct bwmon *m) val = readl_relaxed(GLB_INT_EN(m)); val &= ~(1 << m->mport); writel_relaxed(val, GLB_INT_EN(m)); - spin_unlock(&glb_lock); val = readl_relaxed(MON_INT_EN(m)); - val &= ~0x1; + val &= has_hw_sampling(m) ? ~INT_STATUS_MASK_HWS : ~INT_ENABLE_V1; writel_relaxed(val, MON_INT_EN(m)); + spin_unlock(&glb_lock); /* * make Sure irq disable complete for local and global * to avoid race with other monitor calls @@ -140,12 +197,18 @@ static unsigned int mon_irq_status(struct bwmon *m) dev_dbg(m->dev, "IRQ status p:%x, g:%x\n", mval, readl_relaxed(GLB_INT_STATUS(m))); + mval &= has_hw_sampling(m) ? INT_STATUS_MASK_HWS : INT_STATUS_MASK; + return mval; } static void mon_irq_clear(struct bwmon *m) { - writel_relaxed(0x3, MON_INT_CLR(m)); + u32 intclr; + + intclr = has_hw_sampling(m) ? INT_STATUS_MASK_HWS : INT_STATUS_MASK; + + writel_relaxed(intclr, MON_INT_CLR(m)); mb(); writel_relaxed(1 << m->mport, GLB_INT_CLR(m)); mb(); @@ -171,6 +234,90 @@ static u32 mon_get_throttle_adj(struct bw_hwmon *hw) return m->throttle_adj >> THROTTLE_SHIFT; } +#define ZONE1_SHIFT 8 +#define ZONE2_SHIFT 16 +#define ZONE3_SHIFT 24 +#define ZONE0_ACTION 0x01 /* Increment zone 0 count */ +#define ZONE1_ACTION 0x09 /* Increment zone 1 & clear lower zones */ +#define ZONE2_ACTION 0x25 /* Increment zone 2 & clear lower zones */ +#define ZONE3_ACTION 0x95 /* Increment zone 3 & clear lower zones */ +static u32 calc_zone_actions(void) +{ + u32 zone_actions; + + zone_actions = ZONE0_ACTION; + zone_actions |= ZONE1_ACTION << ZONE1_SHIFT; + zone_actions |= ZONE2_ACTION << ZONE2_SHIFT; + zone_actions |= ZONE3_ACTION << ZONE3_SHIFT; + + return zone_actions; +} + +#define ZONE_CNT_LIM 0xFFU +#define UP_CNT_1 1 +static u32 calc_zone_counts(struct bw_hwmon *hw) +{ + u32 zone_counts; + + zone_counts = ZONE_CNT_LIM; + zone_counts |= min(hw->down_cnt, ZONE_CNT_LIM) << ZONE1_SHIFT; + zone_counts |= ZONE_CNT_LIM << ZONE2_SHIFT; + zone_counts |= UP_CNT_1 << ZONE3_SHIFT; + + return zone_counts; +} + +static unsigned int mbps_to_mb(unsigned long mbps, unsigned int ms) +{ + mbps *= ms; + mbps = DIV_ROUND_UP(mbps, MSEC_PER_SEC); + return mbps; +} + +/* + * Define the 4 zones using HI, MED & LO thresholds: + * Zone 0: byte count < THRES_LO + * Zone 1: THRES_LO < byte count < THRES_MED + * Zone 2: THRES_MED < byte count < THRES_HI + * Zone 3: byte count > THRES_HI + */ +#define THRES_LIM 0x7FFU +static void set_zone_thres(struct bwmon *m, unsigned int sample_ms) +{ + struct bw_hwmon *hw = &(m->hw); + u32 hi, med, lo; + + hi = mbps_to_mb(hw->up_wake_mbps, sample_ms); + med = mbps_to_mb(hw->down_wake_mbps, sample_ms); + lo = 0; + + if (unlikely((hi > THRES_LIM) || (med > hi) || (lo > med))) { + pr_warn("Zone thres larger than hw limit: hi:%u med:%u lo:%u\n", + hi, med, lo); + hi = min(hi, THRES_LIM); + med = min(med, hi - 1); + lo = min(lo, med-1); + } + + writel_relaxed(hi, MON2_THRES_HI(m)); + writel_relaxed(med, MON2_THRES_MED(m)); + writel_relaxed(lo, MON2_THRES_LO(m)); + dev_dbg(m->dev, "Thres: hi:%u med:%u lo:%u\n", hi, med, lo); +} + +static void mon_set_zones(struct bwmon *m, unsigned int sample_ms) +{ + struct bw_hwmon *hw = &(m->hw); + u32 zone_cnt_thres = calc_zone_counts(hw); + + mon_set_hw_sampling_window(m, sample_ms); + set_zone_thres(m, sample_ms); + /* Set the zone count thresholds for interrupts */ + writel_relaxed(zone_cnt_thres, MON2_ZONE_CNT_THRES(m)); + + dev_dbg(m->dev, "Zone Count Thres: %0x\n", zone_cnt_thres); +} + static void mon_set_limit(struct bwmon *m, u32 count) { writel_relaxed(count, MON_THRES(m)); @@ -203,6 +350,41 @@ static unsigned long mon_get_count(struct bwmon *m) return count; } +static unsigned int get_zone(struct bwmon *m) +{ + u32 zone_counts; + u32 zone; + + zone = get_bitmask_order((m->intr_status & INT_STATUS_MASK_HWS) >> 4); + if (zone) { + zone--; + } else { + zone_counts = readl_relaxed(MON2_ZONE_CNT(m)); + if (zone_counts) { + zone = get_bitmask_order(zone_counts) - 1; + zone /= 8; + } + } + + m->intr_status = 0; + return zone; +} + +static unsigned long mon_get_zone_stats(struct bwmon *m) +{ + unsigned int zone; + unsigned long count = 0; + + zone = get_zone(m); + + count = readl_relaxed(MON2_ZONE_MAX(m, zone)); + count *= SZ_1M; + + dev_dbg(m->dev, "Zone%d Max byte count: %08lx\n", zone, count); + + return count; +} + /* ********** CPUBW specific code ********** */ /* Returns MBps of read/writes for the sampling window. */ @@ -222,8 +404,8 @@ static unsigned long get_bytes_and_clear(struct bw_hwmon *hw) unsigned long count; mon_disable(m); - count = mon_get_count(m); - mon_clear(m); + count = has_hw_sampling(m) ? mon_get_zone_stats(m) : mon_get_count(m); + mon_clear(m, false); mon_irq_clear(m); mon_enable(m); @@ -238,7 +420,7 @@ static unsigned long set_thres(struct bw_hwmon *hw, unsigned long bytes) mon_disable(m); count = mon_get_count(m); - mon_clear(m); + mon_clear(m, false); mon_irq_clear(m); if (likely(!m->spec->wrap_on_thres)) @@ -252,11 +434,26 @@ static unsigned long set_thres(struct bw_hwmon *hw, unsigned long bytes) return count; } +static unsigned long set_hw_events(struct bw_hwmon *hw, unsigned sample_ms) +{ + struct bwmon *m = to_bwmon(hw); + + mon_disable(m); + mon_clear(m, false); + mon_irq_clear(m); + + mon_set_zones(m, sample_ms); + mon_enable(m); + + return 0; +} + static irqreturn_t bwmon_intr_handler(int irq, void *dev) { struct bwmon *m = dev; - if (!mon_irq_status(m)) + m->intr_status = mon_irq_status(m); + if (!m->intr_status) return IRQ_NONE; if (bw_hwmon_sample_end(&m->hw) > 0) @@ -277,6 +474,7 @@ static int start_bw_hwmon(struct bw_hwmon *hw, unsigned long mbps) { struct bwmon *m = to_bwmon(hw); u32 limit; + u32 zone_actions = calc_zone_actions(); int ret; ret = request_threaded_irq(m->irq, bwmon_intr_handler, @@ -291,10 +489,16 @@ static int start_bw_hwmon(struct bw_hwmon *hw, unsigned long mbps) mon_disable(m); + mon_clear(m, true); limit = mbps_to_bytes(mbps, hw->df->profile->polling_ms, 0); - mon_set_limit(m, limit); + if (has_hw_sampling(m)) { + mon_set_zones(m, hw->df->profile->polling_ms); + /* Set the zone actions to increment appropriate counters */ + writel_relaxed(zone_actions, MON2_ZONE_ACTIONS(m)); + } else { + mon_set_limit(m, limit); + } - mon_clear(m); mon_irq_clear(m); mon_irq_enable(m); mon_enable(m); @@ -309,7 +513,7 @@ static void stop_bw_hwmon(struct bw_hwmon *hw) mon_irq_disable(m); free_irq(m->irq, m); mon_disable(m); - mon_clear(m); + mon_clear(m, true); mon_irq_clear(m); } @@ -330,7 +534,7 @@ static int resume_bw_hwmon(struct bw_hwmon *hw) struct bwmon *m = to_bwmon(hw); int ret; - mon_clear(m); + mon_clear(m, false); ret = request_threaded_irq(m->irq, bwmon_intr_handler, bwmon_intr_thread, IRQF_ONESHOT | IRQF_SHARED, @@ -350,15 +554,21 @@ static int resume_bw_hwmon(struct bw_hwmon *hw) /*************************************************************************/ static const struct bwmon_spec spec[] = { - { .wrap_on_thres = true, .overflow = false, .throt_adj = false}, - { .wrap_on_thres = false, .overflow = true, .throt_adj = false}, - { .wrap_on_thres = false, .overflow = true, .throt_adj = true}, + { .wrap_on_thres = true, .overflow = false, .throt_adj = false, + .hw_sampling = false}, + { .wrap_on_thres = false, .overflow = true, .throt_adj = false, + .hw_sampling = false}, + { .wrap_on_thres = false, .overflow = true, .throt_adj = true, + .hw_sampling = false}, + { .wrap_on_thres = false, .overflow = true, .throt_adj = true, + .hw_sampling = true}, }; static struct of_device_id match_table[] = { { .compatible = "qcom,bimc-bwmon", .data = &spec[0] }, { .compatible = "qcom,bimc-bwmon2", .data = &spec[1] }, { .compatible = "qcom,bimc-bwmon3", .data = &spec[2] }, + { .compatible = "qcom,bimc-bwmon4", .data = &spec[3] }, {} }; @@ -390,6 +600,16 @@ static int bimc_bwmon_driver_probe(struct platform_device *pdev) } m->spec = id->data; + if (has_hw_sampling(m)) { + ret = of_property_read_u32(dev->of_node, + "qcom,hw-timer-hz", &data); + if (ret) { + dev_err(dev, "HW sampling rate not specified!\n"); + return ret; + } + m->hw_timer_hz = data; + } + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base"); if (!res) { dev_err(dev, "base not found!\n"); @@ -426,7 +646,9 @@ static int bimc_bwmon_driver_probe(struct platform_device *pdev) m->hw.suspend_hwmon = &suspend_bw_hwmon; m->hw.resume_hwmon = &resume_bw_hwmon; m->hw.get_bytes_and_clear = &get_bytes_and_clear; - m->hw.set_thres = &set_thres; + m->hw.set_thres = &set_thres; + if (has_hw_sampling(m)) + m->hw.set_hw_events = &set_hw_events; if (m->spec->throt_adj) { m->hw.set_throttle_adj = &mon_set_throttle_adj; m->hw.get_throttle_adj = &mon_get_throttle_adj; diff --git a/drivers/gpu/msm/adreno-gpulist.h b/drivers/gpu/msm/adreno-gpulist.h index 778c76f52d0b..a3b25b3d8dd1 100644 --- a/drivers/gpu/msm/adreno-gpulist.h +++ b/drivers/gpu/msm/adreno-gpulist.h @@ -250,7 +250,7 @@ static const struct adreno_gpu_core adreno_gpulist[] = { ADRENO_GPMU | ADRENO_SPTP_PC, .pm4fw_name = "a530_pm4.fw", .pfpfw_name = "a530_pfp.fw", - .zap_name = "a530_zap", + .zap_name = "a540_zap", .gpudev = &adreno_a5xx_gpudev, .gmem_size = SZ_1M, .num_protected_regs = 0x20, diff --git a/drivers/gpu/msm/adreno_a5xx_preempt.c b/drivers/gpu/msm/adreno_a5xx_preempt.c index ffd7acbbe1f9..4baee4a5c0b1 100644 --- a/drivers/gpu/msm/adreno_a5xx_preempt.c +++ b/drivers/gpu/msm/adreno_a5xx_preempt.c @@ -311,6 +311,8 @@ void a5xx_preempt_callback(struct adreno_device *adreno_dev, int bit) adreno_dev->cur_rb->dispatch_q.expires); adreno_set_preempt_state(adreno_dev, ADRENO_PREEMPT_NONE); + + a5xx_preemption_trigger(adreno_dev); } void a5xx_preemption_schedule(struct adreno_device *adreno_dev) diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index f9eb080d903b..6e9abc99bcc4 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -4519,7 +4519,7 @@ int kgsl_device_platform_probe(struct kgsl_device *device) } device->events_wq = alloc_workqueue("kgsl-events", - WQ_UNBOUND | WQ_MEM_RECLAIM, 0); + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0); /* Initalize the snapshot engine */ kgsl_device_snapshot_init(device); @@ -4662,7 +4662,8 @@ static int __init kgsl_core_init(void) INIT_LIST_HEAD(&kgsl_driver.pagetable_list); - kgsl_driver.workqueue = create_singlethread_workqueue("kgsl-workqueue"); + kgsl_driver.workqueue = alloc_workqueue("kgsl-workqueue", + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0); kgsl_driver.mem_workqueue = alloc_workqueue("kgsl-mementry", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); diff --git a/drivers/gpu/msm/kgsl_pool.c b/drivers/gpu/msm/kgsl_pool.c index 7967b19779db..f5402fdc7e57 100644 --- a/drivers/gpu/msm/kgsl_pool.c +++ b/drivers/gpu/msm/kgsl_pool.c @@ -197,7 +197,7 @@ _kgsl_pool_shrink(struct kgsl_page_pool *pool, int num_pages) * starting from higher order pool. */ static unsigned long -kgsl_pool_reduce(unsigned int target_pages) +kgsl_pool_reduce(unsigned int target_pages, bool exit) { int total_pages = 0; int i; @@ -210,6 +210,14 @@ kgsl_pool_reduce(unsigned int target_pages) for (i = (KGSL_NUM_POOLS - 1); i >= 0; i--) { pool = &kgsl_pools[i]; + /* + * Only reduce the pool sizes for pools which are allowed to + * allocate memory unless we are at close, in which case the + * reserved memory for all pools needs to be freed + */ + if (!pool->allocation_allowed && !exit) + continue; + total_pages -= pcount; nr_removed = total_pages - target_pages; @@ -418,7 +426,7 @@ kgsl_pool_shrink_scan_objects(struct shrinker *shrinker, int target_pages = (nr > total_pages) ? 0 : (total_pages - nr); /* Reduce pool size to target_pages */ - return kgsl_pool_reduce(target_pages); + return kgsl_pool_reduce(target_pages, false); } static unsigned long @@ -449,7 +457,7 @@ void kgsl_init_page_pools(void) void kgsl_exit_page_pools(void) { /* Release all pages in pools, if any.*/ - kgsl_pool_reduce(0); + kgsl_pool_reduce(0, true); /* Unregister shrinker */ unregister_shrinker(&kgsl_pool_shrinker); diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 92870cdb52d9..8efaa88329aa 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -218,7 +218,8 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, goto done_proc; } - remaining_bytes = do_div(buffer_size, sizeof(__s32)); + remaining_bytes = buffer_size % sizeof(__s32); + buffer_size = buffer_size / sizeof(__s32); if (buffer_size) { for (i = 0; i < buffer_size; ++i) { hid_set_field(report->field[field_index], i, diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index d48d8485f979..306465ededf9 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -95,6 +95,7 @@ #define TMC_ETR_BAM_PIPE_INDEX 0 #define TMC_ETR_BAM_NR_PIPES 2 +#define TMC_ETFETB_DUMP_MAGIC_V2 (0x42445953) #define TMC_REG_DUMP_MAGIC_V2 (0x42445953) #define TMC_REG_DUMP_VER (1) @@ -178,6 +179,7 @@ struct tmc_drvdata { spinlock_t spinlock; int read_count; bool reading; + bool aborting; char *buf; dma_addr_t paddr; void __iomem *vaddr; @@ -883,11 +885,15 @@ static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata) for (i = 0; i < memwords; i++) { read_data = readl_relaxed(drvdata->base + TMC_RRD); if (read_data == 0xFFFFFFFF) - return; + goto out; memcpy(bufp, &read_data, 4); bufp += 4; } } + +out: + if (drvdata->aborting) + drvdata->buf_data.magic = TMC_ETFETB_DUMP_MAGIC_V2; } static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata) @@ -1070,6 +1076,8 @@ static void tmc_abort(struct coresight_device *csdev) unsigned long flags; enum tmc_mode mode; + drvdata->aborting = true; + spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) goto out0; @@ -1728,7 +1736,7 @@ static void __tmc_reg_dump(struct tmc_drvdata *drvdata) if (!drvdata->reg_buf) return; - else if (!drvdata->dump_reg) + else if (!drvdata->aborting && !drvdata->dump_reg) return; drvdata->reg_data.version = TMC_REG_DUMP_VER; diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index b9956170b909..49df5e0afbfb 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1217,4 +1217,15 @@ config TOUCHSCREEN_IT7260_I2C To compile this driver as a module, choose M here: the module will be called it7258_ts_i2c. +config TOUCHSCREEN_GT9XX + bool "Goodix touchpanel GT9xx series" + depends on I2C + help + Say Y here if you have a Goodix GT9xx touchscreen. + Gt9xx controllers are multi touch controllers which can + report 5 touches at a time. + + If unsure, say N. + +source "drivers/input/touchscreen/gt9xx/Kconfig" endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index e1777f11d77b..06953a69123f 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o obj-$(CONFIG_TOUCHSCREEN_INTEL_MID) += intel-mid-touch.o +obj-$(CONFIG_TOUCHSCREEN_IT7260_I2C) += it7258_ts_i2c.o obj-$(CONFIG_TOUCHSCREEN_IPROC) += bcm_iproc_tsc.o obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o @@ -98,3 +99,4 @@ obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o obj-$(CONFIG_TOUCHSCREEN_MSTAR21XX) += msg21xx_ts.o +obj-$(CONFIG_TOUCHSCREEN_GT9XX) += gt9xx/ diff --git a/drivers/input/touchscreen/gt9xx/Kconfig b/drivers/input/touchscreen/gt9xx/Kconfig new file mode 100644 index 000000000000..2e1b5ba567a0 --- /dev/null +++ b/drivers/input/touchscreen/gt9xx/Kconfig @@ -0,0 +1,51 @@ +# +# Goodix GT9xx Touchscreen driver +# + +config GT9XX_TOUCHPANEL_DRIVER + tristate "Goodix GT9xx touchpanel driver" + depends on TOUCHSCREEN_GT9XX + default n + help + This is the main file for touchpanel driver for Goodix GT9xx + touchscreens. + + Say Y here if you have a Goodix GT9xx touchscreen connected + to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gt9xx. + +config GT9XX_TOUCHPANEL_UPDATE + tristate "Goodix GT9xx touchpanel auto update support" + depends on GT9XX_TOUCHPANEL_DRIVER + default n + help + This enables support for firmware update for Goodix GT9xx + touchscreens. + + Say Y here if you have a Goodix GT9xx touchscreen connected + to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gt9xx_update. + +config GT9XX_TOUCHPANEL_DEBUG + tristate "Goodix GT9xx Tools for debuging" + depends on GT9XX_TOUCHPANEL_DRIVER + default n + help + This is application debug interface support for Goodix GT9xx + touchscreens. + + Say Y here if you want to have a Android app debug interface + to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gt9xx_tool. diff --git a/drivers/input/touchscreen/gt9xx/Makefile b/drivers/input/touchscreen/gt9xx/Makefile new file mode 100644 index 000000000000..482d869a2d37 --- /dev/null +++ b/drivers/input/touchscreen/gt9xx/Makefile @@ -0,0 +1,8 @@ +#gt915 touchpanel driver + + +obj-$(CONFIG_GT9XX_TOUCHPANEL_DRIVER) += gt9xx.o +#gt915 update file +obj-$(CONFIG_GT9XX_TOUCHPANEL_UPDATE) += gt9xx_update.o +#debug tool +obj-$(CONFIG_GT9XX_TOUCHPANEL_DEBUG) += goodix_tool.o diff --git a/drivers/input/touchscreen/gt9xx/goodix_tool.c b/drivers/input/touchscreen/gt9xx/goodix_tool.c index bef080671ab0..c67c4c8f1207 100644 --- a/drivers/input/touchscreen/gt9xx/goodix_tool.c +++ b/drivers/input/touchscreen/gt9xx/goodix_tool.c @@ -22,6 +22,7 @@ */ #include "gt9xx.h" +#include <linux/mutex.h> #define DATA_LENGTH_UINT 512 #define CMD_HEAD_LENGTH (sizeof(st_cmd_head) - sizeof(u8 *)) @@ -53,6 +54,8 @@ static struct i2c_client *gt_client; static struct proc_dir_entry *goodix_proc_entry; +static struct mutex lock; + static s32 goodix_tool_write(struct file *filp, const char __user *buff, unsigned long len, void *data); static s32 goodix_tool_read(char *page, char **start, off_t off, int count, @@ -88,18 +91,21 @@ static void tool_set_proc_name(char *procname) static s32 tool_i2c_read_no_extra(u8 *buf, u16 len) { s32 ret = -1; - s32 i = 0; - struct i2c_msg msgs[2]; - - msgs[0].flags = !I2C_M_RD; - msgs[0].addr = gt_client->addr; - msgs[0].len = cmd_head.addr_len; - msgs[0].buf = &buf[0]; - - msgs[1].flags = I2C_M_RD; - msgs[1].addr = gt_client->addr; - msgs[1].len = len; - msgs[1].buf = &buf[GTP_ADDR_LENGTH]; + u8 i = 0; + struct i2c_msg msgs[2] = { + { + .flags = !I2C_M_RD, + .addr = gt_client->addr, + .len = cmd_head.addr_len, + .buf = &buf[0], + }, + { + .flags = I2C_M_RD, + .addr = gt_client->addr, + .len = len, + .buf = &buf[GTP_ADDR_LENGTH], + }, + }; for (i = 0; i < cmd_head.retry; i++) { ret = i2c_transfer(gt_client->adapter, msgs, 2); @@ -107,25 +113,35 @@ static s32 tool_i2c_read_no_extra(u8 *buf, u16 len) break; } + if (i == cmd_head.retry) { + dev_err(&client->dev, "I2C read retry limit over.\n"); + ret = -EIO; + } + return ret; } static s32 tool_i2c_write_no_extra(u8 *buf, u16 len) { s32 ret = -1; - s32 i = 0; - struct i2c_msg msg; - - msg.flags = !I2C_M_RD; - msg.addr = gt_client->addr; - msg.len = len; - msg.buf = buf; + u8 i = 0; + struct i2c_msg msg = { + .flags = !I2C_M_RD, + .addr = gt_client->addr, + .len = len, + .buf = buf, + }; for (i = 0; i < cmd_head.retry; i++) { ret = i2c_transfer(gt_client->adapter, &msg, 1); if (ret > 0) break; - } + } + + if (i == cmd_head.retry) { + dev_err(&client->dev, "I2C write retry limit over.\n"); + ret = -EIO; + } return ret; } @@ -181,7 +197,7 @@ static void unregister_i2c_func(void) s32 init_wr_node(struct i2c_client *client) { - s32 i; + u8 i; gt_client = client; memset(&cmd_head, 0, sizeof(cmd_head)); @@ -189,14 +205,15 @@ s32 init_wr_node(struct i2c_client *client) i = 5; while ((!cmd_head.data) && i) { - cmd_head.data = kzalloc(i * DATA_LENGTH_UINT, GFP_KERNEL); - if (cmd_head.data != NULL) + cmd_head.data = devm_kzalloc(&client->dev, + i * DATA_LENGTH_UINT, GFP_KERNEL); + if (cmd_head.data) break; i--; } if (i) { - DATA_LENGTH = i * DATA_LENGTH_UINT + GTP_ADDR_LENGTH; - GTP_INFO("Applied memory size:%d.", DATA_LENGTH); + DATA_LENGTH = i * DATA_LENGTH_UINT; + dev_dbg(&client->dev, "Applied memory size:%d.", DATA_LENGTH); } else { GTP_ERROR("Apply for memory failed."); return FAIL; @@ -207,8 +224,9 @@ s32 init_wr_node(struct i2c_client *client) register_i2c_func(); + mutex_init(&lock); tool_set_proc_name(procname); - goodix_proc_entry = create_proc_entry(procname, 0666, NULL); + goodix_proc_entry = create_proc_entry(procname, 0660, NULL); if (goodix_proc_entry == NULL) { GTP_ERROR("Couldn't create proc entry!"); return FAIL; @@ -222,7 +240,6 @@ s32 init_wr_node(struct i2c_client *client) void uninit_wr_node(void) { - kfree(cmd_head.data); cmd_head.data = NULL; unregister_i2c_func(); remove_proc_entry(procname, NULL); @@ -330,9 +347,13 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, GTP_DEBUG_FUNC(); GTP_DEBUG_ARRAY((u8 *)buff, len); + mutex_lock(&lock); ret = copy_from_user(&cmd_head, buff, CMD_HEAD_LENGTH); - if (ret) + if (ret) { GTP_ERROR("copy_from_user failed."); + ret = -EACCES; + goto exit; + } GTP_DEBUG("wr :0x%02x.", cmd_head.wr); GTP_DEBUG("flag:0x%02x.", cmd_head.flag); @@ -350,6 +371,19 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, GTP_DEBUG("len:%d.", (s32)len); GTP_DEBUG("buf[20]:0x%02x.", buff[CMD_HEAD_LENGTH]); + if (cmd_head.data_len > (DATA_LENGTH - GTP_ADDR_LENGTH)) { + pr_err("data len %d > data buff %d, rejected!\n", + cmd_head.data_len, (DATA_LENGTH - GTP_ADDR_LENGTH)); + ret = -EINVAL; + goto exit; + } + if (cmd_head.addr_len > GTP_ADDR_LENGTH) { + pr_err(" addr len %d > data buff %d, rejected!\n", + cmd_head.addr_len, GTP_ADDR_LENGTH); + ret = -EINVAL; + goto exit; + } + if (cmd_head.wr == 1) { /* copy_from_user(&cmd_head.data[cmd_head.addr_len], * &buff[CMD_HEAD_LENGTH], cmd_head.data_len); @@ -370,7 +404,8 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, if (cmd_head.flag == 1) { if (comfirm() == FAIL) { GTP_ERROR("[WRITE]Comfirm fail!"); - return FAIL; + ret = -EINVAL; + goto exit; } } else if (cmd_head.flag == 2) { /* Need interrupt! */ @@ -379,7 +414,8 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, &cmd_head.data[GTP_ADDR_LENGTH - cmd_head.addr_len], cmd_head.data_len + cmd_head.addr_len) <= 0) { GTP_ERROR("[WRITE]Write data failed!"); - return FAIL; + ret = -EIO; + goto exit; } GTP_DEBUG_ARRAY( @@ -388,7 +424,8 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, if (cmd_head.delay) msleep(cmd_head.delay); - return cmd_head.data_len + CMD_HEAD_LENGTH; + ret = cmd_head.data_len + CMD_HEAD_LENGTH; + goto exit; } else if (cmd_head.wr == 3) { /* Write ic type */ ret = copy_from_user(&cmd_head.data[0], &buff[CMD_HEAD_LENGTH], @@ -396,30 +433,40 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, if (ret) GTP_ERROR("copy_from_user failed."); + if (cmd_head.data_len > sizeof(IC_TYPE)) { + pr_err("<<-GTP->> data len %d > data buff %d, rejected!\n", + cmd_head.data_len, sizeof(IC_TYPE)); + ret = -EINVAL; + goto exit; + } memcpy(IC_TYPE, cmd_head.data, cmd_head.data_len); register_i2c_func(); - return cmd_head.data_len + CMD_HEAD_LENGTH; - } else if (cmd_head.wr == 3) { + ret = cmd_head.data_len + CMD_HEAD_LENGTH; + goto exit; + } else if (cmd_head.wr == 5) { /* memcpy(IC_TYPE, cmd_head.data, cmd_head.data_len); */ - return cmd_head.data_len + CMD_HEAD_LENGTH; + ret = cmd_head.data_len + CMD_HEAD_LENGTH; + goto exit; } else if (cmd_head.wr == 7) { /* disable irq! */ gtp_irq_disable(i2c_get_clientdata(gt_client)); #if GTP_ESD_PROTECT gtp_esd_switch(gt_client, SWITCH_OFF); #endif - return CMD_HEAD_LENGTH; + ret = CMD_HEAD_LENGTH; + goto exit; } else if (cmd_head.wr == 9) { /* enable irq! */ gtp_irq_enable(i2c_get_clientdata(gt_client)); #if GTP_ESD_PROTECT gtp_esd_switch(gt_client, SWITCH_ON); #endif - return CMD_HEAD_LENGTH; + ret = CMD_HEAD_LENGTH; + goto exit; } else if (cmd_head.wr == 17) { struct goodix_ts_data *ts = i2c_get_clientdata(gt_client); @@ -434,27 +481,40 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, ts->gtp_rawdiff_mode = false; GTP_DEBUG("gtp leave rawdiff."); } - return CMD_HEAD_LENGTH; + ret = CMD_HEAD_LENGTH; + goto exit; } #ifdef UPDATE_FUNCTIONS else if (cmd_head.wr == 11) { /* Enter update mode! */ if (gup_enter_update_mode(gt_client) == FAIL) - return FAIL; + ret = -EBUSY; + goto exit; } else if (cmd_head.wr == 13) { /* Leave update mode! */ gup_leave_update_mode(); } else if (cmd_head.wr == 15) { /* Update firmware! */ show_len = 0; total_len = 0; + if (cmd_head.data_len + 1 > DATA_LENGTH) { + pr_err("<<-GTP->> data len %d > data buff %d, rejected!\n", + cmd_head.data_len + 1, DATA_LENGTH); + ret = -EINVAL; + goto exit; + } memset(cmd_head.data, 0, cmd_head.data_len + 1); memcpy(cmd_head.data, &buff[CMD_HEAD_LENGTH], cmd_head.data_len); - if (gup_update_proc((void *)cmd_head.data) == FAIL) - return FAIL; + if (gup_update_proc((void *)cmd_head.data) == FAIL) { + ret = -EBUSY; + goto exit; + } } #endif + ret = CMD_HEAD_LENGTH; - return CMD_HEAD_LENGTH; +exit: + mutex_unlock(&lock); + return ret; } /******************************************************* @@ -469,10 +529,14 @@ static s32 goodix_tool_write(struct file *filp, const char __user *buff, static s32 goodix_tool_read(char *page, char **start, off_t off, int count, int *eof, void *data) { + s32 ret; GTP_DEBUG_FUNC(); + mutex_lock(&lock); if (cmd_head.wr % 2) { - return FAIL; + pr_err("<< [READ]command head wrong\n"); + ret = -EINVAL; + goto exit; } else if (!cmd_head.wr) { u16 len = 0; s16 data_len = 0; @@ -481,7 +545,8 @@ static s32 goodix_tool_read(char *page, char **start, off_t off, int count, if (cmd_head.flag == 1) { if (comfirm() == FAIL) { GTP_ERROR("[READ]Comfirm fail!"); - return FAIL; + ret = -EINVAL; + goto exit; } } else if (cmd_head.flag == 2) { /* Need interrupt! */ @@ -504,11 +569,12 @@ static s32 goodix_tool_read(char *page, char **start, off_t off, int count, else len = data_len; - data_len -= DATA_LENGTH; + data_len -= len; if (tool_i2c_read(cmd_head.data, len) <= 0) { GTP_ERROR("[READ]Read data failed!"); - return FAIL; + ret = -EINVAL; + goto exit; } memcpy(&page[loc], &cmd_head.data[GTP_ADDR_LENGTH], len); @@ -525,15 +591,14 @@ static s32 goodix_tool_read(char *page, char **start, off_t off, int count, GTP_DEBUG("Return ic type:%s len:%d.", page, (s32)cmd_head.data_len); - return cmd_head.data_len; + ret = cmd_head.data_len; + goto exit; /* return sizeof(IC_TYPE_NAME); */ } else if (cmd_head.wr == 4) { page[0] = show_len >> 8; page[1] = show_len & 0xff; page[2] = total_len >> 8; page[3] = total_len & 0xff; - - return cmd_head.data_len; } else if (cmd_head.wr == 6) { /* Read error code! */ } else if (cmd_head.wr == 8) { /*Read driver version */ @@ -546,6 +611,9 @@ static s32 goodix_tool_read(char *page, char **start, off_t off, int count, memcpy(page, GTP_DRIVER_VERSION, tmp_len); page[tmp_len] = 0; } + ret = cmd_head.data_len; - return cmd_head.data_len; +exit: + mutex_unlock(&lock); + return ret; } diff --git a/drivers/input/touchscreen/gt9xx/gt9xx.c b/drivers/input/touchscreen/gt9xx/gt9xx.c index ebb9ce7ba6a4..6c3747108d75 100644 --- a/drivers/input/touchscreen/gt9xx/gt9xx.c +++ b/drivers/input/touchscreen/gt9xx/gt9xx.c @@ -45,11 +45,9 @@ #include <linux/of_gpio.h> -#if GTP_ICS_SLOT_REPORT #include <linux/input/mt.h> -#endif -#define GOODIX_DEV_NAME "Goodix Capacitive TouchScreen" +#define GOODIX_DEV_NAME "Goodix-CTP" #define CFG_MAX_TOUCH_POINTS 5 #define GOODIX_COORDS_ARR_SIZE 4 #define MAX_BUTTONS 4 @@ -70,7 +68,8 @@ #define RESET_DELAY_T3_US 200 /* T3: > 100us */ #define RESET_DELAY_T4 20 /* T4: > 5ms */ -#define PHY_BUF_SIZE 32 +#define PHY_BUF_SIZE 32 +#define PROP_NAME_SIZE 24 #define GTP_MAX_TOUCH 5 #define GTP_ESD_CHECK_CIRCLE_MS 2000 @@ -136,40 +135,40 @@ Output: int gtp_i2c_read(struct i2c_client *client, u8 *buf, int len) { struct goodix_ts_data *ts = i2c_get_clientdata(client); - struct i2c_msg msgs[2]; int ret = -EIO; - int retries = 0; - - GTP_DEBUG_FUNC(); - - msgs[0].flags = !I2C_M_RD; - msgs[0].addr = client->addr; - msgs[0].len = GTP_ADDR_LENGTH; - msgs[0].buf = &buf[0]; - - msgs[1].flags = I2C_M_RD; - msgs[1].addr = client->addr; - msgs[1].len = len - GTP_ADDR_LENGTH; - msgs[1].buf = &buf[GTP_ADDR_LENGTH]; - - while (retries < 5) { + u8 retries; + struct i2c_msg msgs[2] = { + { + .flags = !I2C_M_RD, + .addr = client->addr, + .len = GTP_ADDR_LENGTH, + .buf = &buf[0], + }, + { + .flags = I2C_M_RD, + .addr = client->addr, + .len = len - GTP_ADDR_LENGTH, + .buf = &buf[GTP_ADDR_LENGTH], + }, + }; + + for (retries = 0; retries < GTP_I2C_RETRY_5; retries++) { ret = i2c_transfer(client->adapter, msgs, 2); if (ret == 2) break; - retries++; + dev_err(&client->dev, "I2C retry: %d\n", retries + 1); } - if (retries >= 5) { + if (retries == GTP_I2C_RETRY_5) { #if GTP_SLIDE_WAKEUP /* reset chip would quit doze mode */ if (doze_status == DOZE_ENABLED) return ret; #endif - GTP_DEBUG("I2C communication timeout, resetting chip..."); if (init_done) gtp_reset_guitar(ts, 10); else dev_warn(&client->dev, - "<GTP> gtp_reset_guitar exit init_done=%d:\n", + "gtp_reset_guitar exit init_done=%d:\n", init_done); } return ret; @@ -190,38 +189,36 @@ Output: int gtp_i2c_write(struct i2c_client *client, u8 *buf, int len) { struct goodix_ts_data *ts = i2c_get_clientdata(client); - struct i2c_msg msg; int ret = -EIO; - int retries = 0; - - GTP_DEBUG_FUNC(); - - msg.flags = !I2C_M_RD; - msg.addr = client->addr; - msg.len = len; - msg.buf = buf; - - while (retries < 5) { + u8 retries; + struct i2c_msg msg = { + .flags = !I2C_M_RD, + .addr = client->addr, + .len = len, + .buf = buf, + }; + + for (retries = 0; retries < GTP_I2C_RETRY_5; retries++) { ret = i2c_transfer(client->adapter, &msg, 1); if (ret == 1) break; - retries++; + dev_err(&client->dev, "I2C retry: %d\n", retries + 1); } - if ((retries >= 5)) { + if (retries == GTP_I2C_RETRY_5) { #if GTP_SLIDE_WAKEUP if (doze_status == DOZE_ENABLED) return ret; #endif - GTP_DEBUG("I2C communication timeout, resetting chip..."); if (init_done) gtp_reset_guitar(ts, 10); else dev_warn(&client->dev, - "<GTP> gtp_reset_guitar exit init_done=%d:\n", + "gtp_reset_guitar exit init_done=%d:\n", init_done); } return ret; } + /******************************************************* Function: i2c read twice, compare the results @@ -241,7 +238,7 @@ int gtp_i2c_read_dbl_check(struct i2c_client *client, u8 confirm_buf[16] = {0}; u8 retry = 0; - while (retry++ < 3) { + while (retry++ < GTP_I2C_RETRY_3) { memset(buf, 0xAA, 16); buf[0] = (u8)(addr >> 8); buf[1] = (u8)(addr & 0xFF); @@ -255,7 +252,7 @@ int gtp_i2c_read_dbl_check(struct i2c_client *client, if (!memcmp(buf, confirm_buf, len + 2)) break; } - if (retry < 3) { + if (retry < GTP_I2C_RETRY_3) { memcpy(rxbuf, confirm_buf + 2, len); return SUCCESS; } @@ -284,7 +281,7 @@ static int gtp_send_cfg(struct goodix_ts_data *ts) "Ic fixed config, no config sent!"); ret = 2; } else { - for (retry = 0; retry < 5; retry++) { + for (retry = 0; retry < GTP_I2C_RETRY_5; retry++) { ret = gtp_i2c_write(ts->client, ts->config_data, GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH); @@ -309,8 +306,6 @@ void gtp_irq_disable(struct goodix_ts_data *ts) { unsigned long irqflags; - GTP_DEBUG_FUNC(); - spin_lock_irqsave(&ts->irq_lock, irqflags); if (!ts->irq_is_disabled) { ts->irq_is_disabled = true; @@ -331,8 +326,6 @@ void gtp_irq_enable(struct goodix_ts_data *ts) { unsigned long irqflags = 0; - GTP_DEBUG_FUNC(); - spin_lock_irqsave(&ts->irq_lock, irqflags); if (ts->irq_is_disabled) { enable_irq(ts->client->irq); @@ -357,26 +350,15 @@ static void gtp_touch_down(struct goodix_ts_data *ts, int id, int x, int y, int w) { #if GTP_CHANGE_X2Y - GTP_SWAP(x, y); + swap(x, y); #endif -#if GTP_ICS_SLOT_REPORT input_mt_slot(ts->input_dev, id); - input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, id); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y); input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w); input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); -#else - input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x); - input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y); - input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w); - input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); - input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, id); - input_mt_sync(ts->input_dev); -#endif - - GTP_DEBUG("ID:%d, X:%d, Y:%d, W:%d", id, x, y, w); } /******************************************************* @@ -389,15 +371,8 @@ Output: *********************************************************/ static void gtp_touch_up(struct goodix_ts_data *ts, int id) { -#if GTP_ICS_SLOT_REPORT input_mt_slot(ts->input_dev, id); - input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, -1); - GTP_DEBUG("Touch id[%2d] release!", id); -#else - input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); - input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0); - input_mt_sync(ts->input_dev); -#endif + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); } @@ -438,8 +413,6 @@ static void goodix_ts_work_func(struct work_struct *work) u8 doze_buf[3] = {0x81, 0x4B}; #endif - GTP_DEBUG_FUNC(); - ts = container_of(work, struct goodix_ts_data, work); #ifdef CONFIG_GT9XX_TOUCHPANEL_UPDATE if (ts->enter_update) @@ -449,7 +422,6 @@ static void goodix_ts_work_func(struct work_struct *work) #if GTP_SLIDE_WAKEUP if (doze_status == DOZE_ENABLED) { ret = gtp_i2c_read(ts->client, doze_buf, 3); - GTP_DEBUG("0x814B = 0x%02X", doze_buf[2]); if (ret > 0) { if (doze_buf[2] == 0xAA) { dev_dbg(&ts->client->dev, @@ -547,12 +519,9 @@ static void goodix_ts_work_func(struct work_struct *work) #endif pre_key = key_value; - GTP_DEBUG("pre_touch:%02x, finger:%02x.", pre_touch, finger); - -#if GTP_ICS_SLOT_REPORT #if GTP_WITH_PEN if (pre_pen && (touch_num == 0)) { - GTP_DEBUG("Pen touch UP(Slot)!"); + dev_dbg(&ts->client->dev, "Pen touch UP(Slot)!"); input_report_key(ts->input_dev, BTN_TOOL_PEN, 0); input_mt_slot(ts->input_dev, 5); input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, -1); @@ -569,7 +538,8 @@ static void goodix_ts_work_func(struct work_struct *work) #if GTP_WITH_PEN id = coor_data[pos]; if (id == 128) { - GTP_DEBUG("Pen touch DOWN(Slot)!"); + dev_dbg(&ts->client->dev, + "Pen touch DOWN(Slot)!"); input_x = coor_data[pos + 1] | (coor_data[pos + 2] << 8); input_y = coor_data[pos + 3] @@ -588,7 +558,8 @@ static void goodix_ts_work_func(struct work_struct *work) ABS_MT_POSITION_Y, input_y); input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w); - GTP_DEBUG("Pen/Stylus: (%d, %d)[%d]", + dev_dbg(&ts->client->dev, + "Pen/Stylus: (%d, %d)[%d]", input_x, input_y, input_w); pre_pen = 1; pre_touch = 0; @@ -598,8 +569,6 @@ static void goodix_ts_work_func(struct work_struct *work) touch_index |= (0x01<<id); } - GTP_DEBUG("id = %d,touch_index = 0x%x, pre_touch = 0x%x\n", - id, touch_index, pre_touch); for (i = 0; i < GTP_MAX_TOUCH; i++) { #if GTP_WITH_PEN if (pre_pen == 1) @@ -626,42 +595,6 @@ static void goodix_ts_work_func(struct work_struct *work) } } } -#else - input_report_key(ts->input_dev, BTN_TOUCH, (touch_num || key_value)); - if (touch_num) { - for (i = 0; i < touch_num; i++) { - coor_data = &point_data[i * 8 + 3]; - - id = coor_data[0]; - input_x = coor_data[1] | coor_data[2] << 8; - input_y = coor_data[3] | coor_data[4] << 8; - input_w = coor_data[5] | coor_data[6] << 8; -#if GTP_WITH_PEN - if (id == 128) { - GTP_DEBUG("Pen touch DOWN!"); - input_report_key(ts->input_dev, - BTN_TOOL_PEN, 1); - pre_pen = 1; - id = 0; - } -#endif - gtp_touch_down(ts, id, input_x, input_y, input_w); - } - } else if (pre_touch) { -#if GTP_WITH_PEN - if (pre_pen == 1) { - GTP_DEBUG("Pen touch UP!"); - input_report_key(ts->input_dev, BTN_TOOL_PEN, 0); - pre_pen = 0; - } -#endif - GTP_DEBUG("Touch Released!"); - gtp_touch_up(ts, 0); - } - - pre_touch = touch_num; -#endif - input_sync(ts->input_dev); exit_work_func: @@ -691,8 +624,6 @@ static enum hrtimer_restart goodix_ts_timer_handler(struct hrtimer *timer) struct goodix_ts_data *ts = container_of(timer, struct goodix_ts_data, timer); - GTP_DEBUG_FUNC(); - queue_work(ts->goodix_wq, &ts->work); hrtimer_start(&ts->timer, ktime_set(0, (GTP_POLL_TIME + 6) * 1000000), HRTIMER_MODE_REL); @@ -713,8 +644,6 @@ static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id) { struct goodix_ts_data *ts = dev_id; - GTP_DEBUG_FUNC(); - gtp_irq_disable(ts); queue_work(ts->goodix_wq, &ts->work); @@ -746,8 +675,6 @@ Output: *******************************************************/ static void gtp_reset_guitar(struct goodix_ts_data *ts, int ms) { - GTP_DEBUG_FUNC(); - /* This reset sequence will selcet I2C slave address */ gpio_direction_output(ts->pdata->reset_gpio, 0); msleep(ms); @@ -788,20 +715,17 @@ static s8 gtp_enter_doze(struct goodix_ts_data *ts) (u8)(GTP_REG_SLEEP >> 8), (u8)GTP_REG_SLEEP, 8}; - GTP_DEBUG_FUNC(); - #if GTP_DBL_CLK_WAKEUP i2c_control_buf[2] = 0x09; #endif gtp_irq_disable(ts); - GTP_DEBUG("entering doze mode..."); - while (retry++ < 5) { + while (retry++ < GTP_I2C_RETRY_3) { i2c_control_buf[0] = 0x80; i2c_control_buf[1] = 0x46; ret = gtp_i2c_write(ts->client, i2c_control_buf, 3); if (ret < 0) { - GTP_DEBUG( + dev_err(&ts->client->dev, "failed to set doze flag into 0x8046, %d", retry); continue; @@ -840,11 +764,9 @@ static s8 gtp_enter_sleep(struct goodix_ts_data *ts) (u8)(GTP_REG_SLEEP >> 8), (u8)GTP_REG_SLEEP, 5}; - GTP_DEBUG_FUNC(); - ret = gpio_direction_output(ts->pdata->irq_gpio, 0); usleep(5000); - while (retry++ < 5) { + while (retry++ < GTP_I2C_RETRY_5) { ret = gtp_i2c_write(ts->client, i2c_control_buf, 3); if (ret > 0) { dev_dbg(&ts->client->dev, @@ -872,8 +794,6 @@ static s8 gtp_wakeup_sleep(struct goodix_ts_data *ts) u8 retry = 0; s8 ret = -1; - GTP_DEBUG_FUNC(); - #if GTP_POWER_CTRL_SLEEP gtp_reset_guitar(ts, 20); @@ -884,7 +804,7 @@ static s8 gtp_wakeup_sleep(struct goodix_ts_data *ts) return 1; } #else - while (retry++ < 10) { + while (retry++ < GTP_I2C_RETRY_10) { #if GTP_SLIDE_WAKEUP /* wakeup not by slide */ if (doze_status != DOZE_WAKEUP) @@ -944,26 +864,9 @@ static int gtp_init_panel(struct goodix_ts_data *ts) u8 opr_buf[16]; u8 sensor_id = 0; - u8 cfg_info_group1[] = CTP_CFG_GROUP1; - u8 cfg_info_group2[] = CTP_CFG_GROUP2; - u8 cfg_info_group3[] = CTP_CFG_GROUP3; - u8 cfg_info_group4[] = CTP_CFG_GROUP4; - u8 cfg_info_group5[] = CTP_CFG_GROUP5; - u8 cfg_info_group6[] = CTP_CFG_GROUP6; - u8 *send_cfg_buf[] = {cfg_info_group1, cfg_info_group2, - cfg_info_group3, cfg_info_group4, - cfg_info_group5, cfg_info_group6}; - - u8 cfg_info_len[] = {ARRAY_SIZE(cfg_info_group1), - ARRAY_SIZE(cfg_info_group2), - ARRAY_SIZE(cfg_info_group3), - ARRAY_SIZE(cfg_info_group4), - ARRAY_SIZE(cfg_info_group5), - ARRAY_SIZE(cfg_info_group6)}; - - GTP_DEBUG("Config Groups\' Lengths: %d, %d, %d, %d, %d, %d", - cfg_info_len[0], cfg_info_len[1], cfg_info_len[2], - cfg_info_len[3], cfg_info_len[4], cfg_info_len[5]); + for (i = 0; i < GOODIX_MAX_CFG_GROUP; i++) + dev_dbg(&client->dev, "Config Groups(%d) Lengths: %d", + i, ts->pdata->config_data_len[i]); ret = gtp_i2c_read_dbl_check(ts->client, 0x41E4, opr_buf, 1); if (ret == SUCCESS) { @@ -974,14 +877,18 @@ static int gtp_init_panel(struct goodix_ts_data *ts) return -EINVAL; } } - if ((!cfg_info_len[1]) && (!cfg_info_len[2]) && (!cfg_info_len[3]) - && (!cfg_info_len[4]) && (!cfg_info_len[5])) { + + for (i = 1; i < GOODIX_MAX_CFG_GROUP; i++) { + if (ts->pdata->config_data_len[i]) + break; + } + if (i == GOODIX_MAX_CFG_GROUP) { sensor_id = 0; } else { ret = gtp_i2c_read_dbl_check(ts->client, GTP_REG_SENSOR_ID, &sensor_id, 1); if (ret == SUCCESS) { - if (sensor_id >= 0x06) { + if (sensor_id >= GOODIX_MAX_CFG_GROUP) { dev_err(&client->dev, "Invalid sensor_id(0x%02X), No Config Sent!", sensor_id); @@ -993,24 +900,26 @@ static int gtp_init_panel(struct goodix_ts_data *ts) return -EINVAL; } } - GTP_DEBUG("Sensor_ID: %d", sensor_id); - ts->gtp_cfg_len = cfg_info_len[sensor_id]; + dev_dbg(&client->dev, "Sensor ID selected: %d", sensor_id); - if (ts->gtp_cfg_len < GTP_CONFIG_MIN_LENGTH) { + if (ts->pdata->config_data_len[sensor_id] < GTP_CONFIG_MIN_LENGTH || + !ts->pdata->config_data[sensor_id]) { dev_err(&client->dev, - "Sensor_ID(%d) matches with NULL or INVALID CONFIG GROUP! NO Config Sent! You need to check you header file CFG_GROUP section!\n", + "Sensor_ID(%d) matches with NULL or invalid config group!\n", sensor_id); return -EINVAL; } + ret = gtp_i2c_read_dbl_check(ts->client, GTP_REG_CONFIG_DATA, &opr_buf[0], 1); - if (ret == SUCCESS) { if (opr_buf[0] < 90) { /* backup group config version */ - grp_cfg_version = send_cfg_buf[sensor_id][0]; - send_cfg_buf[sensor_id][0] = 0x00; + grp_cfg_version = + ts->pdata->config_data[sensor_id][GTP_ADDR_LENGTH]; + ts->pdata->config_data[sensor_id][GTP_ADDR_LENGTH] = + 0x00; ts->fixed_cfg = 0; } else { /* treated as fixed config, not send config */ @@ -1025,27 +934,9 @@ static int gtp_init_panel(struct goodix_ts_data *ts) return -EINVAL; } - if (ts->pdata->gtp_cfg_len) { - config_data = ts->pdata->config_data; - ts->config_data = ts->pdata->config_data; - ts->gtp_cfg_len = ts->pdata->gtp_cfg_len; - } else { - config_data = devm_kzalloc(&client->dev, - GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH, - GFP_KERNEL); - if (!config_data) { - dev_err(&client->dev, - "Not enough memory for panel config data\n"); - return -ENOMEM; - } - - ts->config_data = config_data; - config_data[0] = GTP_REG_CONFIG_DATA >> 8; - config_data[1] = GTP_REG_CONFIG_DATA & 0xff; - memset(&config_data[GTP_ADDR_LENGTH], 0, GTP_CONFIG_MAX_LENGTH); - memcpy(&config_data[GTP_ADDR_LENGTH], send_cfg_buf[sensor_id], - ts->gtp_cfg_len); - } + config_data = ts->pdata->config_data[sensor_id]; + ts->config_data = ts->pdata->config_data[sensor_id]; + ts->gtp_cfg_len = ts->pdata->config_data_len[sensor_id]; #if GTP_CUSTOM_CFG config_data[RESOLUTION_LOC] = @@ -1082,7 +973,6 @@ static int gtp_init_panel(struct goodix_ts_data *ts) } #endif /* !DRIVER NOT SEND CONFIG */ - GTP_DEBUG_FUNC(); if ((ts->abs_x_max == 0) && (ts->abs_y_max == 0)) { ts->abs_x_max = (config_data[RESOLUTION_LOC + 1] << 8) + config_data[RESOLUTION_LOC]; @@ -1094,49 +984,79 @@ static int gtp_init_panel(struct goodix_ts_data *ts) if (ret < 0) dev_err(&client->dev, "%s: Send config error.\n", __func__); - GTP_DEBUG("X_MAX = %d, Y_MAX = %d, TRIGGER = 0x%02x", - ts->abs_x_max, ts->abs_y_max, - ts->int_trigger_type); - msleep(20); return ret; } /******************************************************* Function: - Read chip version. + Read firmware version Input: client: i2c device version: buffer to keep ic firmware version Output: read operation return. - 2: succeed, otherwise: failed + 0: succeed, otherwise: failed *******************************************************/ -int gtp_read_version(struct i2c_client *client, u16 *version) +static int gtp_read_fw_version(struct i2c_client *client, u16 *version) { - int ret = -EIO; - u8 buf[8] = { GTP_REG_VERSION >> 8, GTP_REG_VERSION & 0xff }; - - GTP_DEBUG_FUNC(); + int ret = 0; + u8 buf[GTP_FW_VERSION_BUFFER_MAXSIZE] = { + GTP_REG_FW_VERSION >> 8, GTP_REG_FW_VERSION & 0xff }; ret = gtp_i2c_read(client, buf, sizeof(buf)); if (ret < 0) { dev_err(&client->dev, "GTP read version failed.\n"); - return ret; + return -EIO; } if (version) - *version = (buf[7] << 8) | buf[6]; + *version = (buf[3] << 8) | buf[2]; + + return ret; +} +/* + * Function: + * Read and check chip id. + * Input: + * client: i2c device + * Output: + * read operation return. + * 0: succeed, otherwise: failed + */ +static int gtp_check_product_id(struct i2c_client *client) +{ + int ret = 0; + char product_id[GTP_PRODUCT_ID_MAXSIZE]; + struct goodix_ts_data *ts = i2c_get_clientdata(client); + /* 04 bytes are used for the Product-id in the register space.*/ + u8 buf[GTP_PRODUCT_ID_BUFFER_MAXSIZE] = { + GTP_REG_PRODUCT_ID >> 8, GTP_REG_PRODUCT_ID & 0xff }; + + ret = gtp_i2c_read(client, buf, sizeof(buf)); + if (ret < 0) { + dev_err(&client->dev, "GTP read product_id failed.\n"); + return -EIO; + } if (buf[5] == 0x00) { - dev_dbg(&client->dev, "IC Version: %c%c%c_%02x%02x\n", buf[2], - buf[3], buf[4], buf[7], buf[6]); + /* copy (GTP_PRODUCT_ID_MAXSIZE - 1) from buffer. Ex: 915 */ + strlcpy(product_id, &buf[2], GTP_PRODUCT_ID_MAXSIZE - 1); } else { if (buf[5] == 'S' || buf[5] == 's') chip_gt9xxs = 1; - dev_dbg(&client->dev, "IC Version: %c%c%c%c_%02x%02x\n", buf[2], - buf[3], buf[4], buf[5], buf[7], buf[6]); + /* copy GTP_PRODUCT_ID_MAXSIZE from buffer. Ex: 915s */ + strlcpy(product_id, &buf[2], GTP_PRODUCT_ID_MAXSIZE); } + + dev_info(&client->dev, "Goodix Product ID = %s\n", product_id); + + if (!IS_ERR(ts->pdata->product_id)) + ret = strcmp(product_id, ts->pdata->product_id); + + if (ret != 0) + return -EINVAL; + return ret; } @@ -1152,11 +1072,9 @@ Output: static int gtp_i2c_test(struct i2c_client *client) { u8 buf[3] = { GTP_REG_CONFIG_DATA >> 8, GTP_REG_CONFIG_DATA & 0xff }; - int retry = 5; + int retry = GTP_I2C_RETRY_5; int ret = -EIO; - GTP_DEBUG_FUNC(); - while (retry--) { ret = gtp_i2c_read(client, buf, 3); if (ret > 0) @@ -1185,50 +1103,66 @@ static int gtp_request_io_port(struct goodix_ts_data *ts) if (gpio_is_valid(pdata->irq_gpio)) { ret = gpio_request(pdata->irq_gpio, "goodix_ts_irq_gpio"); if (ret) { - dev_err(&client->dev, "irq gpio request failed\n"); - goto pwr_off; + dev_err(&client->dev, "Unable to request irq gpio [%d]\n", + pdata->irq_gpio); + goto err_pwr_off; } ret = gpio_direction_input(pdata->irq_gpio); if (ret) { - dev_err(&client->dev, - "set_direction for irq gpio failed\n"); - goto free_irq_gpio; + dev_err(&client->dev, "Unable to set direction for irq gpio [%d]\n", + pdata->irq_gpio); + goto err_free_irq_gpio; } } else { - dev_err(&client->dev, "irq gpio is invalid!\n"); + dev_err(&client->dev, "Invalid irq gpio [%d]!\n", + pdata->irq_gpio); ret = -EINVAL; - goto free_irq_gpio; + goto err_pwr_off; } if (gpio_is_valid(pdata->reset_gpio)) { - ret = gpio_request(pdata->reset_gpio, "goodix_ts__reset_gpio"); + ret = gpio_request(pdata->reset_gpio, "goodix_ts_reset_gpio"); if (ret) { - dev_err(&client->dev, "reset gpio request failed\n"); - goto free_irq_gpio; + dev_err(&client->dev, "Unable to request reset gpio [%d]\n", + pdata->reset_gpio); + goto err_free_irq_gpio; } ret = gpio_direction_output(pdata->reset_gpio, 0); if (ret) { - dev_err(&client->dev, - "set_direction for reset gpio failed\n"); - goto free_reset_gpio; + dev_err(&client->dev, "Unable to set direction for reset gpio [%d]\n", + pdata->reset_gpio); + goto err_free_reset_gpio; } } else { - dev_err(&client->dev, "reset gpio is invalid!\n"); + dev_err(&client->dev, "Invalid irq gpio [%d]!\n", + pdata->reset_gpio); ret = -EINVAL; - goto free_reset_gpio; - } - gpio_direction_input(pdata->reset_gpio); + goto err_free_irq_gpio; + } + /* IRQ GPIO is an input signal, but we are setting it to output + * direction and pulling it down, to comply with power up timing + * requirements, mentioned in power up timing section of device + * datasheet. + */ + ret = gpio_direction_output(pdata->irq_gpio, 0); + if (ret) + dev_warn(&client->dev, + "pull down interrupt gpio failed\n"); + ret = gpio_direction_output(pdata->reset_gpio, 0); + if (ret) + dev_warn(&client->dev, + "pull down reset gpio failed\n"); return ret; -free_reset_gpio: +err_free_reset_gpio: if (gpio_is_valid(pdata->reset_gpio)) gpio_free(pdata->reset_gpio); -free_irq_gpio: +err_free_irq_gpio: if (gpio_is_valid(pdata->irq_gpio)) gpio_free(pdata->irq_gpio); -pwr_off: +err_pwr_off: return ret; } @@ -1246,9 +1180,6 @@ static int gtp_request_irq(struct goodix_ts_data *ts) int ret; const u8 irq_table[] = GTP_IRQ_TAB; - GTP_DEBUG("INT trigger type:%x, irq=%d", ts->int_trigger_type, - ts->client->irq); - ret = request_irq(ts->client->irq, goodix_ts_irq_handler, irq_table[ts->int_trigger_type], ts->client->name, ts); @@ -1287,8 +1218,6 @@ static int gtp_request_input_dev(struct goodix_ts_data *ts) int index = 0; #endif - GTP_DEBUG_FUNC(); - ts->input_dev = input_allocate_device(); if (ts->input_dev == NULL) { dev_err(&ts->client->dev, @@ -1298,12 +1227,10 @@ static int gtp_request_input_dev(struct goodix_ts_data *ts) ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); -#if GTP_ICS_SLOT_REPORT + set_bit(BTN_TOOL_FINGER, ts->input_dev->keybit); __set_bit(INPUT_PROP_DIRECT, ts->input_dev->propbit); - input_mt_init_slots(ts->input_dev, 10);/* in case of "out of memory" */ -#else - ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); -#endif + /* in case of "out of memory" */ + input_mt_init_slots(ts->input_dev, 10, 0); #if GTP_HAVE_TOUCH_KEY for (index = 0; index < ARRAY_SIZE(touch_key_array); index++) { @@ -1324,7 +1251,7 @@ static int gtp_request_input_dev(struct goodix_ts_data *ts) #endif #if GTP_CHANGE_X2Y - GTP_SWAP(ts->abs_x_max, ts->abs_y_max); + swap(ts->abs_x_max, ts->abs_y_max); #endif input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, @@ -1606,6 +1533,8 @@ static int goodix_parse_dt(struct device *dev, struct property *prop; u32 temp_val, num_buttons; u32 button_map[MAX_BUTTONS]; + char prop_name[PROP_NAME_SIZE]; + int i, read_cfg_num; rc = goodix_ts_get_dt_coords(dev, "goodix,panel-coords", pdata); if (rc && (rc != -EINVAL)) @@ -1631,10 +1560,9 @@ static int goodix_parse_dt(struct device *dev, if (pdata->irq_gpio < 0) return pdata->irq_gpio; - rc = of_property_read_u32(np, "goodix,family-id", &temp_val); - if (!rc) - pdata->family_id = temp_val; - else + rc = of_property_read_string(np, "goodix,product-id", + &pdata->product_id); + if (rc < 0 || strlen(pdata->product_id) > GTP_PRODUCT_ID_MAXSIZE) return rc; prop = of_find_property(np, "goodix,button-map", NULL); @@ -1652,24 +1580,32 @@ static int goodix_parse_dt(struct device *dev, } } - prop = of_find_property(np, "goodix,cfg-data", &pdata->gtp_cfg_len); - if (prop && prop->value) { - pdata->config_data = devm_kzalloc(dev, - GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH, GFP_KERNEL); - if (!pdata->config_data) + read_cfg_num = 0; + for (i = 0; i < GOODIX_MAX_CFG_GROUP; i++) { + snprintf(prop_name, sizeof(prop_name), "goodix,cfg-data%d", i); + prop = of_find_property(np, prop_name, + &pdata->config_data_len[i]); + if (!prop || !prop->value) { + pdata->config_data_len[i] = 0; + pdata->config_data[i] = NULL; + continue; + } + pdata->config_data[i] = devm_kzalloc(dev, + GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH, + GFP_KERNEL); + if (!pdata->config_data[i]) { + dev_err(dev, + "Not enough memory for panel config data %d\n", + i); return -ENOMEM; - - pdata->config_data[0] = GTP_REG_CONFIG_DATA >> 8; - pdata->config_data[1] = GTP_REG_CONFIG_DATA & 0xff; - memset(&pdata->config_data[GTP_ADDR_LENGTH], 0, - GTP_CONFIG_MAX_LENGTH); - memcpy(&pdata->config_data[GTP_ADDR_LENGTH], - prop->value, pdata->gtp_cfg_len); - } else { - dev_err(dev, - "Unable to get configure data, default will be used.\n"); - pdata->gtp_cfg_len = 0; + } + pdata->config_data[i][0] = GTP_REG_CONFIG_DATA >> 8; + pdata->config_data[i][1] = GTP_REG_CONFIG_DATA & 0xff; + memcpy(&pdata->config_data[i][GTP_ADDR_LENGTH], + prop->value, pdata->config_data_len[i]); + read_cfg_num++; } + dev_dbg(dev, "%d config data read from device tree.\n", read_cfg_num); return 0; } @@ -1721,7 +1657,7 @@ static int goodix_ts_probe(struct i2c_client *client, return -ENODEV; } - ts = kzalloc(sizeof(*ts), GFP_KERNEL); + ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; @@ -1735,10 +1671,16 @@ static int goodix_ts_probe(struct i2c_client *client, i2c_set_clientdata(client, ts); ts->gtp_rawdiff_mode = 0; + ret = gtp_request_io_port(ts); + if (ret) { + dev_err(&client->dev, "GTP request IO port failed.\n"); + goto exit_free_client_data; + } + ret = goodix_power_init(ts); if (ret) { dev_err(&client->dev, "GTP power init failed\n"); - goto exit_free_client_data; + goto exit_free_io_port; } ret = goodix_power_on(ts); @@ -1747,18 +1689,12 @@ static int goodix_ts_probe(struct i2c_client *client, goto exit_deinit_power; } - ret = gtp_request_io_port(ts); - if (ret) { - dev_err(&client->dev, "GTP request IO port failed.\n"); - goto exit_power_off; - } - gtp_reset_guitar(ts, 20); ret = gtp_i2c_test(client); if (ret != 2) { dev_err(&client->dev, "I2C communication ERROR!\n"); - goto exit_free_io_port; + goto exit_power_off; } #if GTP_AUTO_UPDATE @@ -1807,9 +1743,13 @@ static int goodix_ts_probe(struct i2c_client *client, else dev_info(&client->dev, "GTP works in interrupt mode.\n"); - ret = gtp_read_version(client, &version_info); - if (ret != 2) { - dev_err(&client->dev, "Read version failed.\n"); + ret = gtp_read_fw_version(client, &version_info); + if (ret != 2) + dev_err(&client->dev, "GTP firmware version read failed.\n"); + + ret = gtp_check_product_id(client); + if (ret != 0) { + dev_err(&client->dev, "GTP Product id doesn't match.\n"); goto exit_free_irq; } if (ts->use_irq) @@ -1847,18 +1787,17 @@ exit_free_irq: } exit_free_inputdev: kfree(ts->config_data); +exit_power_off: + goodix_power_off(ts); +exit_deinit_power: + goodix_power_deinit(ts); exit_free_io_port: if (gpio_is_valid(pdata->reset_gpio)) gpio_free(pdata->reset_gpio); if (gpio_is_valid(pdata->irq_gpio)) gpio_free(pdata->irq_gpio); -exit_power_off: - goodix_power_off(ts); -exit_deinit_power: - goodix_power_deinit(ts); exit_free_client_data: i2c_set_clientdata(client, NULL); - kfree(ts); return ret; } @@ -1874,7 +1813,6 @@ static int goodix_ts_remove(struct i2c_client *client) { struct goodix_ts_data *ts = i2c_get_clientdata(client); - GTP_DEBUG_FUNC(); #if defined(CONFIG_FB) if (fb_unregister_client(&ts->fb_notif)) dev_err(&client->dev, @@ -1908,7 +1846,6 @@ static int goodix_ts_remove(struct i2c_client *client) input_free_device(ts->input_dev); ts->input_dev = NULL; } - kfree(ts->config_data); if (gpio_is_valid(ts->pdata->reset_gpio)) gpio_free(ts->pdata->reset_gpio); @@ -1918,7 +1855,6 @@ static int goodix_ts_remove(struct i2c_client *client) goodix_power_off(ts); goodix_power_deinit(ts); i2c_set_clientdata(client, NULL); - kfree(ts); } return 0; @@ -1935,9 +1871,7 @@ Output: *******************************************************/ static void goodix_ts_suspend(struct goodix_ts_data *ts) { - int ret = -1; - - GTP_DEBUG_FUNC(); + int ret = -1, i; #if GTP_ESD_PROTECT ts->gtp_is_suspend = 1; @@ -1951,6 +1885,12 @@ static void goodix_ts_suspend(struct goodix_ts_data *ts) gtp_irq_disable(ts); else hrtimer_cancel(&ts->timer); + + for (i = 0; i < GTP_MAX_TOUCH; i++) + gtp_touch_up(ts, i); + + input_sync(ts->input_dev); + ret = gtp_enter_sleep(ts); #endif if (ret < 0) @@ -1973,8 +1913,6 @@ static void goodix_ts_resume(struct goodix_ts_data *ts) { int ret = -1; - GTP_DEBUG_FUNC(); - ret = gtp_wakeup_sleep(ts); #if GTP_SLIDE_WAKEUP @@ -2101,21 +2039,18 @@ static int gtp_init_ext_watchdog(struct i2c_client *client) int ret; int retries = 0; - GTP_DEBUG("Init external watchdog..."); - GTP_DEBUG_FUNC(); - msg.flags = !I2C_M_RD; msg.addr = client->addr; msg.len = 4; msg.buf = opr_buffer; - while (retries < 5) { + while (retries < GTP_I2C_RETRY_5) { ret = i2c_transfer(client->adapter, &msg, 1); if (ret == 1) return 1; retries++; } - if (retries >= 5) + if (retries == GTP_I2C_RETRY_5) dev_err(&client->dev, "init external watchdog failed!"); return 0; } @@ -2131,13 +2066,11 @@ Output: *******************************************************/ static void gtp_esd_check_func(struct work_struct *work) { - s32 i; + s32 retry; s32 ret = -1; struct goodix_ts_data *ts = NULL; u8 test[4] = {0x80, 0x40}; - GTP_DEBUG_FUNC(); - ts = i2c_get_clientdata(i2c_connect_client); if (ts->gtp_is_suspend) { @@ -2150,17 +2083,16 @@ static void gtp_esd_check_func(struct work_struct *work) return; #endif - for (i = 0; i < 3; i++) { + for (retry = 0; retry < GTP_I2C_RETRY_3; retry++) { ret = gtp_i2c_read(ts->client, test, 4); - GTP_DEBUG("0x8040 = 0x%02X, 0x8041 = 0x%02X", test[2], test[3]); if ((ret < 0)) { /* IC works abnormally..*/ continue; } else { if ((test[2] == 0xAA) || (test[3] != 0xAA)) { /* IC works abnormally..*/ - i = 3; + retry = GTP_I2C_RETRY_3; break; } /* IC works normally, Write 0x8040 0xAA*/ @@ -2169,7 +2101,7 @@ static void gtp_esd_check_func(struct work_struct *work) break; } } - if (i >= 3) { + if (retry == GTP_I2C_RETRY_3) { dev_err(&ts->client->dev, "IC Working ABNORMALLY, Resetting Guitar...\n"); gtp_reset_guitar(ts, 50); @@ -2224,7 +2156,6 @@ static int __init goodix_ts_init(void) { int ret; - GTP_DEBUG_FUNC(); #if GTP_ESD_PROTECT INIT_DELAYED_WORK(>p_esd_check_work, gtp_esd_check_func); gtp_esd_check_workqueue = create_workqueue("gtp_esd_check"); @@ -2243,7 +2174,6 @@ Output: ********************************************************/ static void __exit goodix_ts_exit(void) { - GTP_DEBUG_FUNC(); i2c_del_driver(&goodix_ts_driver); } diff --git a/drivers/input/touchscreen/gt9xx/gt9xx.h b/drivers/input/touchscreen/gt9xx/gt9xx.h index 185927c6d2b5..843e3d6c05b2 100644 --- a/drivers/input/touchscreen/gt9xx/gt9xx.h +++ b/drivers/input/touchscreen/gt9xx/gt9xx.h @@ -42,12 +42,13 @@ #define GOODIX_SUSPEND_LEVEL 1 #endif +#define GOODIX_MAX_CFG_GROUP 6 struct goodix_ts_platform_data { int irq_gpio; u32 irq_gpio_flags; int reset_gpio; u32 reset_gpio_flags; - u32 family_id; + const char *product_id; u32 x_max; u32 y_max; u32 x_min; @@ -58,8 +59,8 @@ struct goodix_ts_platform_data { u32 panel_maxy; bool no_force_update; bool i2c_pull_up; - int gtp_cfg_len; - u8 *config_data; + size_t config_data_len[GOODIX_MAX_CFG_GROUP]; + u8 *config_data[GOODIX_MAX_CFG_GROUP]; }; struct goodix_ts_data { spinlock_t irq_lock; @@ -104,7 +105,6 @@ extern u16 total_len; #define GTP_DRIVER_SEND_CFG 1 #define GTP_HAVE_TOUCH_KEY 1 #define GTP_POWER_CTRL_SLEEP 0 -#define GTP_ICS_SLOT_REPORT 1 /* auto updated by .bin file as default */ #define GTP_AUTO_UPDATE 0 @@ -121,7 +121,7 @@ extern u16 total_len; /* double-click wakeup, function together with GTP_SLIDE_WAKEUP */ #define GTP_DBL_CLK_WAKEUP 0 -#define GTP_DEBUG_ON 1 +#define GTP_DEBUG_ON 0 #define GTP_DEBUG_ARRAY_ON 0 #define GTP_DEBUG_FUNC_ON 0 @@ -135,56 +135,7 @@ extern u16 total_len; * GND NC/300K 3 * VDDIO NC/300K 4 * NC NC/300K 5 -*/ -/* Define your own default or for Sensor_ID == 0 config here */ -/* The predefined one is just a sample config, - * which is not suitable for your tp in most cases. */ -#define CTP_CFG_GROUP1 {\ - 0x41, 0x1C, 0x02, 0xC0, 0x03, 0x0A, 0x05, 0x01, 0x01, 0x0F,\ - 0x23, 0x0F, 0x5F, 0x41, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0x00, 0x0A,\ - 0x28, 0x00, 0xB8, 0x0B, 0x00, 0x00, 0x00, 0x9A, 0x03, 0x25,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x64, 0x32, 0x00, 0x00,\ - 0x00, 0x32, 0x8C, 0x94, 0x05, 0x01, 0x05, 0x00, 0x00, 0x96,\ - 0x0C, 0x22, 0xD8, 0x0E, 0x23, 0x56, 0x11, 0x25, 0xFF, 0x13,\ - 0x28, 0xA7, 0x15, 0x2E, 0x00, 0x00, 0x10, 0x30, 0x48, 0x00,\ - 0x56, 0x4A, 0x3A, 0xFF, 0xFF, 0x16, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x01, 0x1B, 0x14, 0x0D, 0x19, 0x00, 0x00, 0x01, 0x00,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x00, 0x1A, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0E, 0x0C,\ - 0x0A, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\ - 0xFF, 0xFF, 0x1D, 0x1E, 0x1F, 0x20, 0x22, 0x24, 0x28, 0x29,\ - 0x0C, 0x0A, 0x08, 0x00, 0x02, 0x04, 0x05, 0x06, 0x0E, 0xFF,\ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\ - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0x01\ - } - -/* Define your config for Sensor_ID == 1 here, if needed */ -#define CTP_CFG_GROUP2 {\ - } - -/* Define your config for Sensor_ID == 2 here, if needed */ -#define CTP_CFG_GROUP3 {\ - } - -/* Define your config for Sensor_ID == 3 here, if needed */ -#define CTP_CFG_GROUP4 {\ - } - -/* Define your config for Sensor_ID == 4 here, if needed */ -#define CTP_CFG_GROUP5 {\ - } - -/* Define your config for Sensor_ID == 5 here, if needed */ -#define CTP_CFG_GROUP6 {\ - } #define GTP_IRQ_TAB {\ IRQ_TYPE_EDGE_RISING,\ @@ -206,30 +157,38 @@ extern u16 total_len; #define GTP_INT_TRIGGER GTP_IRQ_TAB_FALLING #endif -#define GTP_MAX_TOUCH 5 -#define GTP_ESD_CHECK_CIRCLE 2000 /* jiffy: ms */ +#define GTP_PRODUCT_ID_MAXSIZE 5 +#define GTP_PRODUCT_ID_BUFFER_MAXSIZE 6 +#define GTP_FW_VERSION_BUFFER_MAXSIZE 4 +#define GTP_MAX_TOUCH 5 +#define GTP_ESD_CHECK_CIRCLE 2000 /* jiffy: ms */ /***************************PART3:OTHER define*********************************/ -#define GTP_DRIVER_VERSION "V1.8<2013/06/08>" -#define GTP_I2C_NAME "Goodix-TS" -#define GTP_POLL_TIME 10 /* jiffy: ms*/ -#define GTP_ADDR_LENGTH 2 +#define GTP_DRIVER_VERSION "V1.8.1<2013/09/01>" +#define GTP_I2C_NAME "Goodix-TS" +#define GTP_POLL_TIME 10 /* jiffy: ms*/ +#define GTP_ADDR_LENGTH 2 #define GTP_CONFIG_MIN_LENGTH 186 #define GTP_CONFIG_MAX_LENGTH 240 -#define FAIL 0 -#define SUCCESS 1 -#define SWITCH_OFF 0 -#define SWITCH_ON 1 +#define FAIL 0 +#define SUCCESS 1 +#define SWITCH_OFF 0 +#define SWITCH_ON 1 /* Registers define */ -#define GTP_READ_COOR_ADDR 0x814E -#define GTP_REG_SLEEP 0x8040 -#define GTP_REG_SENSOR_ID 0x814A -#define GTP_REG_CONFIG_DATA 0x8047 -#define GTP_REG_VERSION 0x8140 - -#define RESOLUTION_LOC 3 -#define TRIGGER_LOC 8 +#define GTP_READ_COOR_ADDR 0x814E +#define GTP_REG_SLEEP 0x8040 +#define GTP_REG_SENSOR_ID 0x814A +#define GTP_REG_CONFIG_DATA 0x8047 +#define GTP_REG_FW_VERSION 0x8144 +#define GTP_REG_PRODUCT_ID 0x8140 + +#define GTP_I2C_RETRY_3 3 +#define GTP_I2C_RETRY_5 5 +#define GTP_I2C_RETRY_10 10 + +#define RESOLUTION_LOC 3 +#define TRIGGER_LOC 8 /* Log define */ #define GTP_DEBUG(fmt, arg...) do {\ diff --git a/drivers/input/touchscreen/gt9xx/gt9xx_update.c b/drivers/input/touchscreen/gt9xx/gt9xx_update.c index 29459b6d9ad1..9fcf7f0bef86 100644 --- a/drivers/input/touchscreen/gt9xx/gt9xx_update.c +++ b/drivers/input/touchscreen/gt9xx/gt9xx_update.c @@ -109,24 +109,25 @@ Output: *********************************************************/ s32 gup_i2c_read(struct i2c_client *client, u8 *buf, s32 len) { - struct i2c_msg msgs[2]; s32 ret = -1; - s32 retries = 0; + u8 retries = 0; + struct i2c_msg msgs[2] = { + { + .flags = !I2C_M_RD, + .addr = client->addr, + .len = GTP_ADDR_LENGTH, + .buf = &buf[0], + }, + { + .flags = I2C_M_RD, + .addr = client->addr, + .len = len - GTP_ADDR_LENGTH, + .buf = &buf[GTP_ADDR_LENGTH], + }, + }; GTP_DEBUG_FUNC(); - msgs[0].flags = !I2C_M_RD; - msgs[0].addr = client->addr; - msgs[0].len = GTP_ADDR_LENGTH; - msgs[0].buf = &buf[0]; - /* msgs[0].scl_rate = 300 * 1000; (for Rockchip) */ - - msgs[1].flags = I2C_M_RD; - msgs[1].addr = client->addr; - msgs[1].len = len - GTP_ADDR_LENGTH; - msgs[1].buf = &buf[GTP_ADDR_LENGTH]; - /* msgs[1].scl_rate = 300 * 1000; */ - while (retries < 5) { ret = i2c_transfer(client->adapter, msgs, 2); if (ret == 2) @@ -134,6 +135,11 @@ s32 gup_i2c_read(struct i2c_client *client, u8 *buf, s32 len) retries++; } + if (retries == 5) { + dev_err(&client->dev, "I2C read retry limit over.\n"); + ret = -EIO; + } + return ret; } @@ -151,18 +157,17 @@ Output: *********************************************************/ s32 gup_i2c_write(struct i2c_client *client, u8 *buf, s32 len) { - struct i2c_msg msg; s32 ret = -1; - s32 retries = 0; + u8 retries = 0; + struct i2c_msg msg = { + .flags = !I2C_M_RD, + .addr = client->addr, + .len = len, + .buf = buf, + }; GTP_DEBUG_FUNC(); - msg.flags = !I2C_M_RD; - msg.addr = client->addr; - msg.len = len; - msg.buf = buf; - /* msg.scl_rate = 300 * 1000; (for Rockchip) */ - while (retries < 5) { ret = i2c_transfer(client->adapter, &msg, 1); if (ret == 1) @@ -170,6 +175,11 @@ s32 gup_i2c_write(struct i2c_client *client, u8 *buf, s32 len) retries++; } + if (retries == 5) { + dev_err(&client->dev, "I2C write retry limit over.\n"); + ret = -EIO; + } + return ret; } @@ -288,7 +298,7 @@ static s32 gup_init_panel(struct goodix_ts_data *ts) static u8 gup_get_ic_msg(struct i2c_client *client, u16 addr, u8 *msg, s32 len) { - s32 i = 0; + u8 i = 0; msg[0] = (addr >> 8) & 0xff; msg[1] = addr & 0xff; @@ -307,12 +317,12 @@ static u8 gup_get_ic_msg(struct i2c_client *client, u16 addr, u8 *msg, s32 len) static u8 gup_set_ic_msg(struct i2c_client *client, u16 addr, u8 val) { - s32 i = 0; - u8 msg[3]; - - msg[0] = (addr >> 8) & 0xff; - msg[1] = addr & 0xff; - msg[2] = val; + u8 i = 0; + u8 msg[3] = { + (addr >> 8) & 0xff, + addr & 0xff, + val, + }; for (i = 0; i < 5; i++) if (gup_i2c_write(client, msg, GTP_ADDR_LENGTH + 1) > 0) @@ -411,7 +421,7 @@ static u8 gup_get_ic_fw_msg(struct i2c_client *client) s32 gup_enter_update_mode(struct i2c_client *client) { s32 ret = -1; - s32 retry = 0; + u8 retry = 0; u8 rd_buf[3]; /* step1:RST output low last at least 2ms */ @@ -575,11 +585,11 @@ static u8 ascii2hex(u8 a) static s8 gup_update_config(struct i2c_client *client) { - s32 file_len = 0; + u32 file_len = 0; s32 ret = 0; s32 i = 0; s32 file_cfg_len = 0; - s32 chip_cfg_len = 0; + u32 chip_cfg_len = 0; s32 count = 0; u8 *buf; u8 *pre_buf; @@ -615,9 +625,19 @@ static s8 gup_update_config(struct i2c_client *client) return -EINVAL; } - buf = kzalloc(file_len, GFP_KERNEL); - pre_buf = kzalloc(file_len, GFP_KERNEL); - file_config = kzalloc(chip_cfg_len + GTP_ADDR_LENGTH, GFP_KERNEL); + buf = devm_kzalloc(&client->dev, file_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pre_buf = devm_kzalloc(&client->dev, file_len, GFP_KERNEL); + if (!pre_buf) + return -ENOMEM; + + file_config = devm_kzalloc(&client->dev, chip_cfg_len + GTP_ADDR_LENGTH, + GFP_KERNEL); + if (!file_config) + return -ENOMEM; + update_msg.cfg_file->f_op->llseek(update_msg.cfg_file, 0, SEEK_SET); GTP_DEBUG("[update_cfg]Read config from file."); @@ -625,7 +645,7 @@ static s8 gup_update_config(struct i2c_client *client) (char *)pre_buf, file_len, &update_msg.cfg_file->f_pos); if (ret < 0) { GTP_ERROR("[update_cfg]Read config file failed."); - goto update_cfg_file_failed; + return ret; } GTP_DEBUG("[update_cfg]Delete illegal character."); @@ -650,13 +670,13 @@ static s8 gup_update_config(struct i2c_client *client) if ((high == 0xFF) || (low == 0xFF)) { ret = 0; GTP_ERROR("[update_cfg]Illegal config file."); - goto update_cfg_file_failed; + return ret; } file_config[file_cfg_len++] = (high<<4) + low; } else { ret = 0; GTP_ERROR("[update_cfg]Illegal config file."); - goto update_cfg_file_failed; + return ret; } } @@ -680,10 +700,6 @@ static s8 gup_update_config(struct i2c_client *client) GTP_ERROR("[update_cfg]Send config i2c error."); } -update_cfg_file_failed: - kfree(pre_buf); - kfree(buf); - kfree(file_config); return ret; } @@ -787,16 +803,22 @@ static u8 gup_check_update_file(struct i2c_client *client, st_fw_head *fw_head, sizeof(UPDATE_FILE_PATH_2)); u8 cfp_len = max(sizeof(CONFIG_FILE_PATH_1), sizeof(CONFIG_FILE_PATH_2)); - u8 *search_update_path = kzalloc(fp_len, GFP_KERNEL); - u8 *search_cfg_path = kzalloc(cfp_len, GFP_KERNEL); + + u8 *search_update_path = devm_kzalloc(&client->dev, fp_len, + GFP_KERNEL); + if (!search_update_path) + goto load_failed; + + u8 *search_cfg_path = devm_kzalloc(&client->dev, cfp_len, + GFP_KERNEL); + if (!search_cfg_path) + goto load_failed; /* Begin to search update file,the config file & firmware * file must be in the same path,single or double. */ searching_file = 1; for (i = 0; i < GUP_SEARCH_FILE_TIMES; i++) { if (searching_file == 0) { - kfree(search_update_path); - kfree(search_cfg_path); GTP_INFO(".bin/.cfg update file search ", "forcely terminated!"); return FAIL; @@ -843,8 +865,6 @@ static u8 gup_check_update_file(struct i2c_client *client, st_fw_head *fw_head, } searching_file = 0; - kfree(search_update_path); - kfree(search_cfg_path); if (!got_file_flag) { GTP_ERROR("Can't find update file."); @@ -1168,7 +1188,8 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) /* step1:alloc memory */ GTP_DEBUG("[burn_dsp_isp]step1:alloc memory"); while (retry++ < 5) { - fw_dsp_isp = kzalloc(FW_DSP_ISP_LENGTH, GFP_KERNEL); + fw_dsp_isp = devm_kzalloc(&client->dev, FW_DSP_ISP_LENGTH, + GFP_KERNEL); if (fw_dsp_isp == NULL) { continue; } else { @@ -1177,7 +1198,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) break; } } - if (retry >= 5) { + if (retry == 5) { GTP_ERROR("[burn_dsp_isp]Alloc memory fail,exit."); return FAIL; } @@ -1188,7 +1209,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) FW_DSP_LENGTH + FW_BOOT_LENGTH), FW_DSP_ISP_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_dsp_isp]load firmware dsp_isp fail."); - goto exit_burn_dsp_isp; + return FAIL; } /* step3:disable wdt,clear cache enable */ @@ -1196,14 +1217,12 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _bRW_MISCTL__TMR0_EN, 0x00); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]disable wdt fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } ret = gup_set_ic_msg(client, _bRW_MISCTL__CACHE_EN, 0x00); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]clear cache enable fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } /* step4:hold ss51 & dsp */ @@ -1211,8 +1230,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__SWRST_B0_, 0x0C); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]hold ss51 & dsp fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } /* step5:set boot from sram */ @@ -1220,8 +1238,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOTCTL_B0_, 0x02); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]set boot from sram fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } /* step6:software reboot */ @@ -1229,8 +1246,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _bWO_MISCTL__CPU_SWRST_PULSE, 0x01); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]software reboot fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } /* step7:select bank2 */ @@ -1238,8 +1254,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _bRW_MISCTL__SRAM_BANK, 0x02); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]select bank2 fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } /* step8:enable accessing code */ @@ -1247,8 +1262,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _bRW_MISCTL__MEM_CD_EN, 0x01); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]enable accessing code fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } /* step9:burn 4k dsp_isp */ @@ -1256,7 +1270,7 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_burn_proc(client, fw_dsp_isp, 0xC000, FW_DSP_ISP_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_dsp_isp]burn dsp_isp fail."); - goto exit_burn_dsp_isp; + return FAIL; } /* step10:set scramble */ @@ -1264,14 +1278,10 @@ static u8 gup_burn_dsp_isp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_OPT_B0_, 0x00); if (ret <= 0) { GTP_ERROR("[burn_dsp_isp]set scramble fail."); - ret = FAIL; - goto exit_burn_dsp_isp; + return FAIL; } - ret = SUCCESS; -exit_burn_dsp_isp: - kfree(fw_dsp_isp); - return ret; + return SUCCESS; } static u8 gup_burn_fw_ss51(struct i2c_client *client) @@ -1285,7 +1295,8 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) /* step1:alloc memory */ GTP_DEBUG("[burn_fw_ss51]step1:alloc memory"); while (retry++ < 5) { - fw_ss51 = kzalloc(FW_SECTION_LENGTH, GFP_KERNEL); + fw_ss51 = devm_kzalloc(&client->dev, FW_SECTION_LENGTH, + GFP_KERNEL); if (fw_ss51 == NULL) { continue; } else { @@ -1294,7 +1305,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) break; } } - if (retry >= 5) { + if (retry == 5) { GTP_ERROR("[burn_fw_ss51]Alloc memory fail,exit."); return FAIL; } @@ -1304,7 +1315,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) ret = gup_load_section_file(fw_ss51, 0, FW_SECTION_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]load ss51 firmware section 1 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step3:clear control flag */ @@ -1312,8 +1323,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_CTL_, 0x00); if (ret <= 0) { GTP_ERROR("[burn_fw_ss51]clear control flag fail."); - ret = FAIL; - goto exit_burn_fw_ss51; + return FAIL; } /* step4:burn ss51 firmware section 1 */ @@ -1321,7 +1331,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) ret = gup_burn_fw_section(client, fw_ss51, 0xC000, 0x01); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]burn ss51 firmware section 1 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step5:load ss51 firmware section 2 file data */ @@ -1330,7 +1340,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) FW_SECTION_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]load ss51 firmware section 2 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step6:burn ss51 firmware section 2 */ @@ -1338,7 +1348,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) ret = gup_burn_fw_section(client, fw_ss51, 0xE000, 0x02); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]burn ss51 firmware section 2 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step7:load ss51 firmware section 3 file data */ @@ -1347,7 +1357,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) FW_SECTION_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]load ss51 firmware section 3 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step8:burn ss51 firmware section 3 */ @@ -1355,7 +1365,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) ret = gup_burn_fw_section(client, fw_ss51, 0xC000, 0x13); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]burn ss51 firmware section 3 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step9:load ss51 firmware section 4 file data */ @@ -1364,7 +1374,7 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) FW_SECTION_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]load ss51 firmware section 4 fail."); - goto exit_burn_fw_ss51; + return FAIL; } /* step10:burn ss51 firmware section 4 */ @@ -1372,14 +1382,10 @@ static u8 gup_burn_fw_ss51(struct i2c_client *client) ret = gup_burn_fw_section(client, fw_ss51, 0xE000, 0x14); if (ret == FAIL) { GTP_ERROR("[burn_fw_ss51]burn ss51 firmware section 4 fail."); - goto exit_burn_fw_ss51; + return FAIL; } - ret = SUCCESS; - -exit_burn_fw_ss51: - kfree(fw_ss51); - return ret; + return SUCCESS; } static u8 gup_burn_fw_dsp(struct i2c_client *client) @@ -1393,7 +1399,8 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) /* step1:alloc memory */ GTP_DEBUG("[burn_fw_dsp]step1:alloc memory"); while (retry++ < 5) { - fw_dsp = kzalloc(FW_DSP_LENGTH, GFP_KERNEL); + fw_dsp = devm_kzalloc(&client->dev, FW_DSP_LENGTH, + GFP_KERNEL); if (fw_dsp == NULL) { continue; } else { @@ -1402,7 +1409,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) break; } } - if (retry >= 5) { + if (retry == 5) { GTP_ERROR("[burn_fw_dsp]Alloc memory fail,exit."); return FAIL; } @@ -1412,7 +1419,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_load_section_file(fw_dsp, 4*FW_SECTION_LENGTH, FW_DSP_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_dsp]load firmware dsp fail."); - goto exit_burn_fw_dsp; + return ret; } /* step3:select bank3 */ @@ -1420,8 +1427,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_set_ic_msg(client, _bRW_MISCTL__SRAM_BANK, 0x03); if (ret <= 0) { GTP_ERROR("[burn_fw_dsp]select bank3 fail."); - ret = FAIL; - goto exit_burn_fw_dsp; + return FAIL; } /* Step4:hold ss51 & dsp */ @@ -1429,8 +1435,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__SWRST_B0_, 0x0C); if (ret <= 0) { GTP_ERROR("[burn_fw_dsp]hold ss51 & dsp fail."); - ret = FAIL; - goto exit_burn_fw_dsp; + return FAIL; } /* step5:set scramble */ @@ -1438,8 +1443,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_OPT_B0_, 0x00); if (ret <= 0) { GTP_ERROR("[burn_fw_dsp]set scramble fail."); - ret = FAIL; - goto exit_burn_fw_dsp; + return FAIL; } /* step6:release ss51 & dsp */ @@ -1447,8 +1451,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__SWRST_B0_, 0x04); if (ret <= 0) { GTP_ERROR("[burn_fw_dsp]release ss51 & dsp fail."); - ret = FAIL; - goto exit_burn_fw_dsp; + return FAIL; } /* must delay */ msleep(20); @@ -1458,7 +1461,7 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_burn_proc(client, fw_dsp, 0x9000, FW_DSP_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_dsp]burn fw_section fail."); - goto exit_burn_fw_dsp; + return ret; } /* step8:send burn cmd to move data to flash from sram */ @@ -1467,14 +1470,14 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_CTL_, 0x05); if (ret <= 0) { GTP_ERROR("[burn_fw_dsp]send burn cmd fail."); - goto exit_burn_fw_dsp; + return ret; } GTP_DEBUG("[burn_fw_dsp]Wait for the burn is complete......"); do { ret = gup_get_ic_msg(client, _rRW_MISCTL__BOOT_CTL_, rd_buf, 1); if (ret <= 0) { GTP_ERROR("[burn_fw_dsp]Get burn state fail"); - goto exit_burn_fw_dsp; + return ret; } msleep(20); /* GTP_DEBUG("[burn_fw_dsp]Get burn state:%d.", @@ -1487,14 +1490,10 @@ static u8 gup_burn_fw_dsp(struct i2c_client *client) ret = gup_recall_check(client, fw_dsp, 0x9000, FW_DSP_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_dsp]recall check 4k dsp firmware fail."); - goto exit_burn_fw_dsp; + return ret; } ret = SUCCESS; - -exit_burn_fw_dsp: - kfree(fw_dsp); - return ret; } static u8 gup_burn_fw_boot(struct i2c_client *client) @@ -1509,7 +1508,8 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) /* step1:Alloc memory */ GTP_DEBUG("[burn_fw_boot]step1:Alloc memory"); while (retry++ < 5) { - fw_boot = kzalloc(FW_BOOT_LENGTH, GFP_KERNEL); + fw_boot = devm_kzalloc(&client->dev, FW_BOOT_LENGTH, + GFP_KERNEL); if (fw_boot == NULL) { continue; } else { @@ -1518,7 +1518,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) break; } } - if (retry >= 5) { + if (retry == 5) { GTP_ERROR("[burn_fw_boot]Alloc memory fail,exit."); return FAIL; } @@ -1529,7 +1529,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) FW_DSP_LENGTH), FW_BOOT_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_boot]load firmware dsp fail."); - goto exit_burn_fw_boot; + return ret; } /* step3:hold ss51 & dsp */ @@ -1537,8 +1537,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__SWRST_B0_, 0x0C); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]hold ss51 & dsp fail."); - ret = FAIL; - goto exit_burn_fw_boot; + return FAIL; } /* step4:set scramble */ @@ -1546,8 +1545,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_OPT_B0_, 0x00); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]set scramble fail."); - ret = FAIL; - goto exit_burn_fw_boot; + return FAIL; } /* step5:release ss51 & dsp */ @@ -1555,8 +1553,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__SWRST_B0_, 0x04); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]release ss51 & dsp fail."); - ret = FAIL; - goto exit_burn_fw_boot; + return FAIL; } /* must delay */ msleep(20); @@ -1566,8 +1563,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _bRW_MISCTL__SRAM_BANK, 0x03); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]select bank3 fail."); - ret = FAIL; - goto exit_burn_fw_boot; + return FAIL; } /* step7:burn 2k bootloader firmware */ @@ -1575,7 +1571,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_burn_proc(client, fw_boot, 0x9000, FW_BOOT_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_boot]burn fw_section fail."); - goto exit_burn_fw_boot; + return ret; } /* step7:send burn cmd to move data to flash from sram */ @@ -1584,14 +1580,14 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_CTL_, 0x06); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]send burn cmd fail."); - goto exit_burn_fw_boot; + return ret; } GTP_DEBUG("[burn_fw_boot]Wait for the burn is complete......"); do { ret = gup_get_ic_msg(client, _rRW_MISCTL__BOOT_CTL_, rd_buf, 1); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]Get burn state fail"); - goto exit_burn_fw_boot; + return ret; } msleep(20); /* GTP_DEBUG("[burn_fw_boot]Get burn state:%d.", @@ -1604,7 +1600,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_recall_check(client, fw_boot, 0x9000, FW_BOOT_LENGTH); if (ret == FAIL) { GTP_ERROR("[burn_fw_boot]recall check 4k dsp firmware fail."); - goto exit_burn_fw_boot; + return ret; } /* step9:enable download DSP code */ @@ -1612,8 +1608,7 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__BOOT_CTL_, 0x99); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]enable download DSP code fail."); - ret = FAIL; - goto exit_burn_fw_boot; + return FAIL; } /* step10:release ss51 & hold dsp */ @@ -1621,15 +1616,10 @@ static u8 gup_burn_fw_boot(struct i2c_client *client) ret = gup_set_ic_msg(client, _rRW_MISCTL__SWRST_B0_, 0x08); if (ret <= 0) { GTP_ERROR("[burn_fw_boot]release ss51 & hold dsp fail."); - ret = FAIL; - goto exit_burn_fw_boot; + return FAIL; } - ret = SUCCESS; - -exit_burn_fw_boot: - kfree(fw_boot); - return ret; + return SUCCESS; } s32 gup_update_proc(void *dir) diff --git a/drivers/input/touchscreen/it7258_ts_i2c.c b/drivers/input/touchscreen/it7258_ts_i2c.c index 88b1d3c013c7..1a2afd1c1117 100644 --- a/drivers/input/touchscreen/it7258_ts_i2c.c +++ b/drivers/input/touchscreen/it7258_ts_i2c.c @@ -1383,7 +1383,7 @@ static int it7260_ts_chip_identify(struct it7260_ts_data *ts_data) static int reg_set_optimum_mode_check(struct regulator *reg, int load_uA) { return (regulator_count_voltages(reg) > 0) ? - regulator_set_optimum_mode(reg, load_uA) : 0; + regulator_set_load(reg, load_uA) : 0; } static int it7260_regulator_configure(struct it7260_ts_data *ts_data, bool on) @@ -1950,7 +1950,7 @@ static int it7260_ts_probe(struct i2c_client *client, dev_err(&client->dev, "Unable to register fb_notifier %d\n", ret); #endif - + it7260_i2c_write_no_ready_check(ts_data, BUF_COMMAND, cmd_start, sizeof(cmd_start)); msleep(pdata->reset_delay); diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig index 8810c9fb981a..998bd1ec0415 100644 --- a/drivers/md/Kconfig +++ b/drivers/md/Kconfig @@ -475,6 +475,18 @@ config DM_VERITY If unsure, say N. +config DM_VERITY_FEC + bool "Verity forward error correction support" + depends on DM_VERITY + select REED_SOLOMON + select REED_SOLOMON_DEC8 + ---help--- + Add forward error correction support to dm-verity. This option + makes it possible to use pre-generated error correction data to + recover from corrupted blocks. + + If unsure, say N. + config DM_SWITCH tristate "Switch target support (EXPERIMENTAL)" depends on BLK_DEV_DM diff --git a/drivers/md/Makefile b/drivers/md/Makefile index 02de7d19853e..d470143dcf40 100644 --- a/drivers/md/Makefile +++ b/drivers/md/Makefile @@ -16,6 +16,7 @@ dm-cache-mq-y += dm-cache-policy-mq.o dm-cache-smq-y += dm-cache-policy-smq.o dm-cache-cleaner-y += dm-cache-policy-cleaner.o dm-era-y += dm-era-target.o +dm-verity-y += dm-verity-target.o md-mod-y += md.o bitmap.o raid456-y += raid5.o raid5-cache.o @@ -64,3 +65,7 @@ obj-$(CONFIG_DM_REQ_CRYPT) += dm-req-crypt.o ifeq ($(CONFIG_DM_UEVENT),y) dm-mod-objs += dm-uevent.o endif + +ifeq ($(CONFIG_DM_VERITY_FEC),y) +dm-verity-objs += dm-verity-fec.o +endif diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 515f83e7d9ab..bb9b92ebbf8e 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -118,14 +118,12 @@ static void iot_io_end(struct io_tracker *iot, sector_t len) */ struct dm_hook_info { bio_end_io_t *bi_end_io; - void *bi_private; }; static void dm_hook_bio(struct dm_hook_info *h, struct bio *bio, bio_end_io_t *bi_end_io, void *bi_private) { h->bi_end_io = bio->bi_end_io; - h->bi_private = bio->bi_private; bio->bi_end_io = bi_end_io; bio->bi_private = bi_private; @@ -134,7 +132,6 @@ static void dm_hook_bio(struct dm_hook_info *h, struct bio *bio, static void dm_unhook_bio(struct dm_hook_info *h, struct bio *bio) { bio->bi_end_io = h->bi_end_io; - bio->bi_private = h->bi_private; } /*----------------------------------------------------------------*/ diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 3147c8d09ea8..e85bcae50f65 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -1864,16 +1864,24 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) } ret = -ENOMEM; - cc->io_queue = alloc_workqueue("kcryptd_io", WQ_MEM_RECLAIM, 1); + cc->io_queue = alloc_workqueue("kcryptd_io", + WQ_HIGHPRI | + WQ_MEM_RECLAIM, + 1); if (!cc->io_queue) { ti->error = "Couldn't create kcryptd io queue"; goto bad; } if (test_bit(DM_CRYPT_SAME_CPU, &cc->flags)) - cc->crypt_queue = alloc_workqueue("kcryptd", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM, 1); + cc->crypt_queue = alloc_workqueue("kcryptd", + WQ_HIGHPRI | + WQ_MEM_RECLAIM, 1); else - cc->crypt_queue = alloc_workqueue("kcryptd", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM | WQ_UNBOUND, + cc->crypt_queue = alloc_workqueue("kcryptd", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, num_online_cpus()); if (!cc->crypt_queue) { ti->error = "Couldn't create kcryptd queue"; diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index e108deebbaaa..e4d1bafe78c1 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -207,7 +207,6 @@ struct dm_snap_pending_exception { */ struct bio *full_bio; bio_end_io_t *full_bio_end_io; - void *full_bio_private; }; /* @@ -1495,10 +1494,8 @@ out: snapshot_bios = bio_list_get(&pe->snapshot_bios); origin_bios = bio_list_get(&pe->origin_bios); full_bio = pe->full_bio; - if (full_bio) { + if (full_bio) full_bio->bi_end_io = pe->full_bio_end_io; - full_bio->bi_private = pe->full_bio_private; - } increment_pending_exceptions_done_count(); up_write(&s->lock); @@ -1604,7 +1601,6 @@ static void start_full_bio(struct dm_snap_pending_exception *pe, pe->full_bio = bio; pe->full_bio_end_io = bio->bi_end_io; - pe->full_bio_private = bio->bi_private; callback_data = dm_kcopyd_prepare_callback(s->kcopyd_client, copy_callback, pe); diff --git a/drivers/md/dm-verity-fec.c b/drivers/md/dm-verity-fec.c new file mode 100644 index 000000000000..ad10d6d8ed28 --- /dev/null +++ b/drivers/md/dm-verity-fec.c @@ -0,0 +1,861 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Author: Sami Tolvanen <samitolvanen@google.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include "dm-verity-fec.h" +#include <linux/math64.h> +#include <linux/sysfs.h> + +#define DM_MSG_PREFIX "verity-fec" + +/* + * If error correction has been configured, returns true. + */ +bool verity_fec_is_enabled(struct dm_verity *v) +{ + return v->fec && v->fec->dev; +} + +/* + * Return a pointer to dm_verity_fec_io after dm_verity_io and its variable + * length fields. + */ +static inline struct dm_verity_fec_io *fec_io(struct dm_verity_io *io) +{ + return (struct dm_verity_fec_io *) verity_io_digest_end(io->v, io); +} + +/* + * Return an interleaved offset for a byte in RS block. + */ +static inline u64 fec_interleave(struct dm_verity *v, u64 offset) +{ + u32 mod; + + mod = do_div(offset, v->fec->rsn); + return offset + mod * (v->fec->rounds << v->data_dev_block_bits); +} + +/* + * Decode an RS block using Reed-Solomon. + */ +static int fec_decode_rs8(struct dm_verity *v, struct dm_verity_fec_io *fio, + u8 *data, u8 *fec, int neras) +{ + int i; + uint16_t par[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN]; + + for (i = 0; i < v->fec->roots; i++) + par[i] = fec[i]; + + return decode_rs8(fio->rs, data, par, v->fec->rsn, NULL, neras, + fio->erasures, 0, NULL); +} + +/* + * Read error-correcting codes for the requested RS block. Returns a pointer + * to the data block. Caller is responsible for releasing buf. + */ +static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index, + unsigned *offset, struct dm_buffer **buf) +{ + u64 position, block; + u8 *res; + + position = (index + rsb) * v->fec->roots; + block = position >> v->data_dev_block_bits; + *offset = (unsigned)(position - (block << v->data_dev_block_bits)); + + res = dm_bufio_read(v->fec->bufio, v->fec->start + block, buf); + if (unlikely(IS_ERR(res))) { + DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", + v->data_dev->name, (unsigned long long)rsb, + (unsigned long long)(v->fec->start + block), + PTR_ERR(res)); + *buf = NULL; + } + + return res; +} + +/* Loop over each preallocated buffer slot. */ +#define fec_for_each_prealloc_buffer(__i) \ + for (__i = 0; __i < DM_VERITY_FEC_BUF_PREALLOC; __i++) + +/* Loop over each extra buffer slot. */ +#define fec_for_each_extra_buffer(io, __i) \ + for (__i = DM_VERITY_FEC_BUF_PREALLOC; __i < DM_VERITY_FEC_BUF_MAX; __i++) + +/* Loop over each allocated buffer. */ +#define fec_for_each_buffer(io, __i) \ + for (__i = 0; __i < (io)->nbufs; __i++) + +/* Loop over each RS block in each allocated buffer. */ +#define fec_for_each_buffer_rs_block(io, __i, __j) \ + fec_for_each_buffer(io, __i) \ + for (__j = 0; __j < 1 << DM_VERITY_FEC_BUF_RS_BITS; __j++) + +/* + * Return a pointer to the current RS block when called inside + * fec_for_each_buffer_rs_block. + */ +static inline u8 *fec_buffer_rs_block(struct dm_verity *v, + struct dm_verity_fec_io *fio, + unsigned i, unsigned j) +{ + return &fio->bufs[i][j * v->fec->rsn]; +} + +/* + * Return an index to the current RS block when called inside + * fec_for_each_buffer_rs_block. + */ +static inline unsigned fec_buffer_rs_index(unsigned i, unsigned j) +{ + return (i << DM_VERITY_FEC_BUF_RS_BITS) + j; +} + +/* + * Decode all RS blocks from buffers and copy corrected bytes into fio->output + * starting from block_offset. + */ +static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio, + u64 rsb, int byte_index, unsigned block_offset, + int neras) +{ + int r, corrected = 0, res; + struct dm_buffer *buf; + unsigned n, i, offset; + u8 *par, *block; + + par = fec_read_parity(v, rsb, block_offset, &offset, &buf); + if (IS_ERR(par)) + return PTR_ERR(par); + + /* + * Decode the RS blocks we have in bufs. Each RS block results in + * one corrected target byte and consumes fec->roots parity bytes. + */ + fec_for_each_buffer_rs_block(fio, n, i) { + block = fec_buffer_rs_block(v, fio, n, i); + res = fec_decode_rs8(v, fio, block, &par[offset], neras); + if (res < 0) { + dm_bufio_release(buf); + + r = res; + goto error; + } + + corrected += res; + fio->output[block_offset] = block[byte_index]; + + block_offset++; + if (block_offset >= 1 << v->data_dev_block_bits) + goto done; + + /* read the next block when we run out of parity bytes */ + offset += v->fec->roots; + if (offset >= 1 << v->data_dev_block_bits) { + dm_bufio_release(buf); + + par = fec_read_parity(v, rsb, block_offset, &offset, &buf); + if (unlikely(IS_ERR(par))) + return PTR_ERR(par); + } + } +done: + r = corrected; +error: + if (r < 0 && neras) + DMERR_LIMIT("%s: FEC %llu: failed to correct: %d", + v->data_dev->name, (unsigned long long)rsb, r); + else if (r > 0) { + DMWARN_LIMIT("%s: FEC %llu: corrected %d errors", + v->data_dev->name, (unsigned long long)rsb, r); + atomic_add_unless(&v->fec->corrected, 1, INT_MAX); + } + + return r; +} + +/* + * Locate data block erasures using verity hashes. + */ +static int fec_is_erasure(struct dm_verity *v, struct dm_verity_io *io, + u8 *want_digest, u8 *data) +{ + if (unlikely(verity_hash(v, verity_io_hash_desc(v, io), + data, 1 << v->data_dev_block_bits, + verity_io_real_digest(v, io)))) + return 0; + + return memcmp(verity_io_real_digest(v, io), want_digest, + v->digest_size) != 0; +} + +/* + * Read data blocks that are part of the RS block and deinterleave as much as + * fits into buffers. Check for erasure locations if @neras is non-NULL. + */ +static int fec_read_bufs(struct dm_verity *v, struct dm_verity_io *io, + u64 rsb, u64 target, unsigned block_offset, + int *neras) +{ + bool is_zero; + int i, j, target_index = -1; + struct dm_buffer *buf; + struct dm_bufio_client *bufio; + struct dm_verity_fec_io *fio = fec_io(io); + u64 block, ileaved; + u8 *bbuf, *rs_block; + u8 want_digest[v->digest_size]; + unsigned n, k; + + if (neras) + *neras = 0; + + /* + * read each of the rsn data blocks that are part of the RS block, and + * interleave contents to available bufs + */ + for (i = 0; i < v->fec->rsn; i++) { + ileaved = fec_interleave(v, rsb * v->fec->rsn + i); + + /* + * target is the data block we want to correct, target_index is + * the index of this block within the rsn RS blocks + */ + if (ileaved == target) + target_index = i; + + block = ileaved >> v->data_dev_block_bits; + bufio = v->fec->data_bufio; + + if (block >= v->data_blocks) { + block -= v->data_blocks; + + /* + * blocks outside the area were assumed to contain + * zeros when encoding data was generated + */ + if (unlikely(block >= v->fec->hash_blocks)) + continue; + + block += v->hash_start; + bufio = v->bufio; + } + + bbuf = dm_bufio_read(bufio, block, &buf); + if (unlikely(IS_ERR(bbuf))) { + DMWARN_LIMIT("%s: FEC %llu: read failed (%llu): %ld", + v->data_dev->name, + (unsigned long long)rsb, + (unsigned long long)block, PTR_ERR(bbuf)); + + /* assume the block is corrupted */ + if (neras && *neras <= v->fec->roots) + fio->erasures[(*neras)++] = i; + + continue; + } + + /* locate erasures if the block is on the data device */ + if (bufio == v->fec->data_bufio && + verity_hash_for_block(v, io, block, want_digest, + &is_zero) == 0) { + /* skip known zero blocks entirely */ + if (is_zero) + continue; + + /* + * skip if we have already found the theoretical + * maximum number (i.e. fec->roots) of erasures + */ + if (neras && *neras <= v->fec->roots && + fec_is_erasure(v, io, want_digest, bbuf)) + fio->erasures[(*neras)++] = i; + } + + /* + * deinterleave and copy the bytes that fit into bufs, + * starting from block_offset + */ + fec_for_each_buffer_rs_block(fio, n, j) { + k = fec_buffer_rs_index(n, j) + block_offset; + + if (k >= 1 << v->data_dev_block_bits) + goto done; + + rs_block = fec_buffer_rs_block(v, fio, n, j); + rs_block[i] = bbuf[k]; + } +done: + dm_bufio_release(buf); + } + + return target_index; +} + +/* + * Allocate RS control structure and FEC buffers from preallocated mempools, + * and attempt to allocate as many extra buffers as available. + */ +static int fec_alloc_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio) +{ + unsigned n; + + if (!fio->rs) { + fio->rs = mempool_alloc(v->fec->rs_pool, 0); + if (unlikely(!fio->rs)) { + DMERR("failed to allocate RS"); + return -ENOMEM; + } + } + + fec_for_each_prealloc_buffer(n) { + if (fio->bufs[n]) + continue; + + fio->bufs[n] = mempool_alloc(v->fec->prealloc_pool, GFP_NOIO); + if (unlikely(!fio->bufs[n])) { + DMERR("failed to allocate FEC buffer"); + return -ENOMEM; + } + } + + /* try to allocate the maximum number of buffers */ + fec_for_each_extra_buffer(fio, n) { + if (fio->bufs[n]) + continue; + + fio->bufs[n] = mempool_alloc(v->fec->extra_pool, GFP_NOIO); + /* we can manage with even one buffer if necessary */ + if (unlikely(!fio->bufs[n])) + break; + } + fio->nbufs = n; + + if (!fio->output) { + fio->output = mempool_alloc(v->fec->output_pool, GFP_NOIO); + + if (!fio->output) { + DMERR("failed to allocate FEC page"); + return -ENOMEM; + } + } + + return 0; +} + +/* + * Initialize buffers and clear erasures. fec_read_bufs() assumes buffers are + * zeroed before deinterleaving. + */ +static void fec_init_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio) +{ + unsigned n; + + fec_for_each_buffer(fio, n) + memset(fio->bufs[n], 0, v->fec->rsn << DM_VERITY_FEC_BUF_RS_BITS); + + memset(fio->erasures, 0, sizeof(fio->erasures)); +} + +/* + * Decode all RS blocks in a single data block and return the target block + * (indicated by @offset) in fio->output. If @use_erasures is non-zero, uses + * hashes to locate erasures. + */ +static int fec_decode_rsb(struct dm_verity *v, struct dm_verity_io *io, + struct dm_verity_fec_io *fio, u64 rsb, u64 offset, + bool use_erasures) +{ + int r, neras = 0; + unsigned pos; + + r = fec_alloc_bufs(v, fio); + if (unlikely(r < 0)) + return r; + + for (pos = 0; pos < 1 << v->data_dev_block_bits; ) { + fec_init_bufs(v, fio); + + r = fec_read_bufs(v, io, rsb, offset, pos, + use_erasures ? &neras : NULL); + if (unlikely(r < 0)) + return r; + + r = fec_decode_bufs(v, fio, rsb, r, pos, neras); + if (r < 0) + return r; + + pos += fio->nbufs << DM_VERITY_FEC_BUF_RS_BITS; + } + + /* Always re-validate the corrected block against the expected hash */ + r = verity_hash(v, verity_io_hash_desc(v, io), fio->output, + 1 << v->data_dev_block_bits, + verity_io_real_digest(v, io)); + if (unlikely(r < 0)) + return r; + + if (memcmp(verity_io_real_digest(v, io), verity_io_want_digest(v, io), + v->digest_size)) { + DMERR_LIMIT("%s: FEC %llu: failed to correct (%d erasures)", + v->data_dev->name, (unsigned long long)rsb, neras); + return -EILSEQ; + } + + return 0; +} + +static int fec_bv_copy(struct dm_verity *v, struct dm_verity_io *io, u8 *data, + size_t len) +{ + struct dm_verity_fec_io *fio = fec_io(io); + + memcpy(data, &fio->output[fio->output_pos], len); + fio->output_pos += len; + + return 0; +} + +/* + * Correct errors in a block. Copies corrected block to dest if non-NULL, + * otherwise to a bio_vec starting from iter. + */ +int verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io, + enum verity_block_type type, sector_t block, u8 *dest, + struct bvec_iter *iter) +{ + int r; + struct dm_verity_fec_io *fio = fec_io(io); + u64 offset, res, rsb; + + if (!verity_fec_is_enabled(v)) + return -EOPNOTSUPP; + + if (type == DM_VERITY_BLOCK_TYPE_METADATA) + block += v->data_blocks; + + /* + * For RS(M, N), the continuous FEC data is divided into blocks of N + * bytes. Since block size may not be divisible by N, the last block + * is zero padded when decoding. + * + * Each byte of the block is covered by a different RS(M, N) code, + * and each code is interleaved over N blocks to make it less likely + * that bursty corruption will leave us in unrecoverable state. + */ + + offset = block << v->data_dev_block_bits; + + res = offset; + div64_u64(res, v->fec->rounds << v->data_dev_block_bits); + + /* + * The base RS block we can feed to the interleaver to find out all + * blocks required for decoding. + */ + rsb = offset - res * (v->fec->rounds << v->data_dev_block_bits); + + /* + * Locating erasures is slow, so attempt to recover the block without + * them first. Do a second attempt with erasures if the corruption is + * bad enough. + */ + r = fec_decode_rsb(v, io, fio, rsb, offset, false); + if (r < 0) { + r = fec_decode_rsb(v, io, fio, rsb, offset, true); + if (r < 0) + return r; + } + + if (dest) + memcpy(dest, fio->output, 1 << v->data_dev_block_bits); + else if (iter) { + fio->output_pos = 0; + r = verity_for_bv_block(v, io, iter, fec_bv_copy); + } + + return r; +} + +/* + * Clean up per-bio data. + */ +void verity_fec_finish_io(struct dm_verity_io *io) +{ + unsigned n; + struct dm_verity_fec *f = io->v->fec; + struct dm_verity_fec_io *fio = fec_io(io); + + if (!verity_fec_is_enabled(io->v)) + return; + + mempool_free(fio->rs, f->rs_pool); + + fec_for_each_prealloc_buffer(n) + mempool_free(fio->bufs[n], f->prealloc_pool); + + fec_for_each_extra_buffer(fio, n) + mempool_free(fio->bufs[n], f->extra_pool); + + mempool_free(fio->output, f->output_pool); +} + +/* + * Initialize per-bio data. + */ +void verity_fec_init_io(struct dm_verity_io *io) +{ + struct dm_verity_fec_io *fio = fec_io(io); + + if (!verity_fec_is_enabled(io->v)) + return; + + fio->rs = NULL; + memset(fio->bufs, 0, sizeof(fio->bufs)); + fio->nbufs = 0; + fio->output = NULL; +} + +/* + * Append feature arguments and values to the status table. + */ +unsigned verity_fec_status_table(struct dm_verity *v, unsigned sz, + char *result, unsigned maxlen) +{ + if (!verity_fec_is_enabled(v)) + return sz; + + DMEMIT(" " DM_VERITY_OPT_FEC_DEV " %s " + DM_VERITY_OPT_FEC_BLOCKS " %llu " + DM_VERITY_OPT_FEC_START " %llu " + DM_VERITY_OPT_FEC_ROOTS " %d", + v->fec->dev->name, + (unsigned long long)v->fec->blocks, + (unsigned long long)v->fec->start, + v->fec->roots); + + return sz; +} + +void verity_fec_dtr(struct dm_verity *v) +{ + struct dm_verity_fec *f = v->fec; + struct kobject *kobj = &f->kobj_holder.kobj; + + if (!verity_fec_is_enabled(v)) + goto out; + + mempool_destroy(f->rs_pool); + mempool_destroy(f->prealloc_pool); + mempool_destroy(f->extra_pool); + kmem_cache_destroy(f->cache); + + if (f->data_bufio) + dm_bufio_client_destroy(f->data_bufio); + if (f->bufio) + dm_bufio_client_destroy(f->bufio); + + if (f->dev) + dm_put_device(v->ti, f->dev); + + if (kobj->state_initialized) { + kobject_put(kobj); + wait_for_completion(dm_get_completion_from_kobject(kobj)); + } + +out: + kfree(f); + v->fec = NULL; +} + +static void *fec_rs_alloc(gfp_t gfp_mask, void *pool_data) +{ + struct dm_verity *v = (struct dm_verity *)pool_data; + + return init_rs(8, 0x11d, 0, 1, v->fec->roots); +} + +static void fec_rs_free(void *element, void *pool_data) +{ + struct rs_control *rs = (struct rs_control *)element; + + if (rs) + free_rs(rs); +} + +bool verity_is_fec_opt_arg(const char *arg_name) +{ + return (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_DEV) || + !strcasecmp(arg_name, DM_VERITY_OPT_FEC_BLOCKS) || + !strcasecmp(arg_name, DM_VERITY_OPT_FEC_START) || + !strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS)); +} + +int verity_fec_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, + unsigned *argc, const char *arg_name) +{ + int r; + struct dm_target *ti = v->ti; + const char *arg_value; + unsigned long long num_ll; + unsigned char num_c; + char dummy; + + if (!*argc) { + ti->error = "FEC feature arguments require a value"; + return -EINVAL; + } + + arg_value = dm_shift_arg(as); + (*argc)--; + + if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_DEV)) { + r = dm_get_device(ti, arg_value, FMODE_READ, &v->fec->dev); + if (r) { + ti->error = "FEC device lookup failed"; + return r; + } + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_BLOCKS)) { + if (sscanf(arg_value, "%llu%c", &num_ll, &dummy) != 1 || + ((sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT)) + >> (v->data_dev_block_bits - SECTOR_SHIFT) != num_ll)) { + ti->error = "Invalid " DM_VERITY_OPT_FEC_BLOCKS; + return -EINVAL; + } + v->fec->blocks = num_ll; + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_START)) { + if (sscanf(arg_value, "%llu%c", &num_ll, &dummy) != 1 || + ((sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT)) >> + (v->data_dev_block_bits - SECTOR_SHIFT) != num_ll)) { + ti->error = "Invalid " DM_VERITY_OPT_FEC_START; + return -EINVAL; + } + v->fec->start = num_ll; + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS)) { + if (sscanf(arg_value, "%hhu%c", &num_c, &dummy) != 1 || !num_c || + num_c < (DM_VERITY_FEC_RSM - DM_VERITY_FEC_MAX_RSN) || + num_c > (DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN)) { + ti->error = "Invalid " DM_VERITY_OPT_FEC_ROOTS; + return -EINVAL; + } + v->fec->roots = num_c; + + } else { + ti->error = "Unrecognized verity FEC feature request"; + return -EINVAL; + } + + return 0; +} + +static ssize_t corrected_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct dm_verity_fec *f = container_of(kobj, struct dm_verity_fec, + kobj_holder.kobj); + + return sprintf(buf, "%d\n", atomic_read(&f->corrected)); +} + +static struct kobj_attribute attr_corrected = __ATTR_RO(corrected); + +static struct attribute *fec_attrs[] = { + &attr_corrected.attr, + NULL +}; + +static struct kobj_type fec_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .default_attrs = fec_attrs +}; + +/* + * Allocate dm_verity_fec for v->fec. Must be called before verity_fec_ctr. + */ +int verity_fec_ctr_alloc(struct dm_verity *v) +{ + struct dm_verity_fec *f; + + f = kzalloc(sizeof(struct dm_verity_fec), GFP_KERNEL); + if (!f) { + v->ti->error = "Cannot allocate FEC structure"; + return -ENOMEM; + } + v->fec = f; + + return 0; +} + +/* + * Validate arguments and preallocate memory. Must be called after arguments + * have been parsed using verity_fec_parse_opt_args. + */ +int verity_fec_ctr(struct dm_verity *v) +{ + int r; + struct dm_verity_fec *f = v->fec; + struct dm_target *ti = v->ti; + struct mapped_device *md = dm_table_get_md(ti->table); + u64 hash_blocks; + + if (!verity_fec_is_enabled(v)) { + verity_fec_dtr(v); + return 0; + } + + /* Create a kobject and sysfs attributes */ + init_completion(&f->kobj_holder.completion); + + r = kobject_init_and_add(&f->kobj_holder.kobj, &fec_ktype, + &disk_to_dev(dm_disk(md))->kobj, "%s", "fec"); + if (r) { + ti->error = "Cannot create kobject"; + return r; + } + + /* + * FEC is computed over data blocks, possible metadata, and + * hash blocks. In other words, FEC covers total of fec_blocks + * blocks consisting of the following: + * + * data blocks | hash blocks | metadata (optional) + * + * We allow metadata after hash blocks to support a use case + * where all data is stored on the same device and FEC covers + * the entire area. + * + * If metadata is included, we require it to be available on the + * hash device after the hash blocks. + */ + + hash_blocks = v->hash_blocks - v->hash_start; + + /* + * Require matching block sizes for data and hash devices for + * simplicity. + */ + if (v->data_dev_block_bits != v->hash_dev_block_bits) { + ti->error = "Block sizes must match to use FEC"; + return -EINVAL; + } + + if (!f->roots) { + ti->error = "Missing " DM_VERITY_OPT_FEC_ROOTS; + return -EINVAL; + } + f->rsn = DM_VERITY_FEC_RSM - f->roots; + + if (!f->blocks) { + ti->error = "Missing " DM_VERITY_OPT_FEC_BLOCKS; + return -EINVAL; + } + + f->rounds = f->blocks; + if (sector_div(f->rounds, f->rsn)) + f->rounds++; + + /* + * Due to optional metadata, f->blocks can be larger than + * data_blocks and hash_blocks combined. + */ + if (f->blocks < v->data_blocks + hash_blocks || !f->rounds) { + ti->error = "Invalid " DM_VERITY_OPT_FEC_BLOCKS; + return -EINVAL; + } + + /* + * Metadata is accessed through the hash device, so we require + * it to be large enough. + */ + f->hash_blocks = f->blocks - v->data_blocks; + if (dm_bufio_get_device_size(v->bufio) < f->hash_blocks) { + ti->error = "Hash device is too small for " + DM_VERITY_OPT_FEC_BLOCKS; + return -E2BIG; + } + + f->bufio = dm_bufio_client_create(f->dev->bdev, + 1 << v->data_dev_block_bits, + 1, 0, NULL, NULL); + if (IS_ERR(f->bufio)) { + ti->error = "Cannot initialize FEC bufio client"; + return PTR_ERR(f->bufio); + } + + if (dm_bufio_get_device_size(f->bufio) < + ((f->start + f->rounds * f->roots) >> v->data_dev_block_bits)) { + ti->error = "FEC device is too small"; + return -E2BIG; + } + + f->data_bufio = dm_bufio_client_create(v->data_dev->bdev, + 1 << v->data_dev_block_bits, + 1, 0, NULL, NULL); + if (IS_ERR(f->data_bufio)) { + ti->error = "Cannot initialize FEC data bufio client"; + return PTR_ERR(f->data_bufio); + } + + if (dm_bufio_get_device_size(f->data_bufio) < v->data_blocks) { + ti->error = "Data device is too small"; + return -E2BIG; + } + + /* Preallocate an rs_control structure for each worker thread */ + f->rs_pool = mempool_create(num_online_cpus(), fec_rs_alloc, + fec_rs_free, (void *) v); + if (!f->rs_pool) { + ti->error = "Cannot allocate RS pool"; + return -ENOMEM; + } + + f->cache = kmem_cache_create("dm_verity_fec_buffers", + f->rsn << DM_VERITY_FEC_BUF_RS_BITS, + 0, 0, NULL); + if (!f->cache) { + ti->error = "Cannot create FEC buffer cache"; + return -ENOMEM; + } + + /* Preallocate DM_VERITY_FEC_BUF_PREALLOC buffers for each thread */ + f->prealloc_pool = mempool_create_slab_pool(num_online_cpus() * + DM_VERITY_FEC_BUF_PREALLOC, + f->cache); + if (!f->prealloc_pool) { + ti->error = "Cannot allocate FEC buffer prealloc pool"; + return -ENOMEM; + } + + f->extra_pool = mempool_create_slab_pool(0, f->cache); + if (!f->extra_pool) { + ti->error = "Cannot allocate FEC buffer extra pool"; + return -ENOMEM; + } + + /* Preallocate an output buffer for each thread */ + f->output_pool = mempool_create_kmalloc_pool(num_online_cpus(), + 1 << v->data_dev_block_bits); + if (!f->output_pool) { + ti->error = "Cannot allocate FEC output pool"; + return -ENOMEM; + } + + /* Reserve space for our per-bio data */ + ti->per_bio_data_size += sizeof(struct dm_verity_fec_io); + + return 0; +} diff --git a/drivers/md/dm-verity-fec.h b/drivers/md/dm-verity-fec.h new file mode 100644 index 000000000000..8c4bee052a73 --- /dev/null +++ b/drivers/md/dm-verity-fec.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 Google, Inc. + * + * Author: Sami Tolvanen <samitolvanen@google.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#ifndef DM_VERITY_FEC_H +#define DM_VERITY_FEC_H + +#include "dm.h" +#include "dm-verity.h" +#include <linux/rslib.h> + +/* Reed-Solomon(M, N) parameters */ +#define DM_VERITY_FEC_RSM 255 +#define DM_VERITY_FEC_MAX_RSN 253 +#define DM_VERITY_FEC_MIN_RSN 231 /* ~10% space overhead */ + +/* buffers for deinterleaving and decoding */ +#define DM_VERITY_FEC_BUF_PREALLOC 1 /* buffers to preallocate */ +#define DM_VERITY_FEC_BUF_RS_BITS 4 /* 1 << RS blocks per buffer */ +/* we need buffers for at most 1 << block size RS blocks */ +#define DM_VERITY_FEC_BUF_MAX \ + (1 << (PAGE_SHIFT - DM_VERITY_FEC_BUF_RS_BITS)) + +#define DM_VERITY_OPT_FEC_DEV "use_fec_from_device" +#define DM_VERITY_OPT_FEC_BLOCKS "fec_blocks" +#define DM_VERITY_OPT_FEC_START "fec_start" +#define DM_VERITY_OPT_FEC_ROOTS "fec_roots" + +/* configuration */ +struct dm_verity_fec { + struct dm_dev *dev; /* parity data device */ + struct dm_bufio_client *data_bufio; /* for data dev access */ + struct dm_bufio_client *bufio; /* for parity data access */ + sector_t start; /* parity data start in blocks */ + sector_t blocks; /* number of blocks covered */ + sector_t rounds; /* number of interleaving rounds */ + sector_t hash_blocks; /* blocks covered after v->hash_start */ + unsigned char roots; /* number of parity bytes, M-N of RS(M, N) */ + unsigned char rsn; /* N of RS(M, N) */ + mempool_t *rs_pool; /* mempool for fio->rs */ + mempool_t *prealloc_pool; /* mempool for preallocated buffers */ + mempool_t *extra_pool; /* mempool for extra buffers */ + mempool_t *output_pool; /* mempool for output */ + struct kmem_cache *cache; /* cache for buffers */ + atomic_t corrected; /* corrected errors */ + struct dm_kobject_holder kobj_holder; /* for sysfs attributes */ +}; + +/* per-bio data */ +struct dm_verity_fec_io { + struct rs_control *rs; /* Reed-Solomon state */ + int erasures[DM_VERITY_FEC_MAX_RSN]; /* erasures for decode_rs8 */ + u8 *bufs[DM_VERITY_FEC_BUF_MAX]; /* bufs for deinterleaving */ + unsigned nbufs; /* number of buffers allocated */ + u8 *output; /* buffer for corrected output */ + size_t output_pos; +}; + +#ifdef CONFIG_DM_VERITY_FEC + +/* each feature parameter requires a value */ +#define DM_VERITY_OPTS_FEC 8 + +extern bool verity_fec_is_enabled(struct dm_verity *v); + +extern int verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io, + enum verity_block_type type, sector_t block, + u8 *dest, struct bvec_iter *iter); + +extern unsigned verity_fec_status_table(struct dm_verity *v, unsigned sz, + char *result, unsigned maxlen); + +extern void verity_fec_finish_io(struct dm_verity_io *io); +extern void verity_fec_init_io(struct dm_verity_io *io); + +extern bool verity_is_fec_opt_arg(const char *arg_name); +extern int verity_fec_parse_opt_args(struct dm_arg_set *as, + struct dm_verity *v, unsigned *argc, + const char *arg_name); + +extern void verity_fec_dtr(struct dm_verity *v); + +extern int verity_fec_ctr_alloc(struct dm_verity *v); +extern int verity_fec_ctr(struct dm_verity *v); + +#else /* !CONFIG_DM_VERITY_FEC */ + +#define DM_VERITY_OPTS_FEC 0 + +static inline bool verity_fec_is_enabled(struct dm_verity *v) +{ + return false; +} + +static inline int verity_fec_decode(struct dm_verity *v, + struct dm_verity_io *io, + enum verity_block_type type, + sector_t block, u8 *dest, + struct bvec_iter *iter) +{ + return -EOPNOTSUPP; +} + +static inline unsigned verity_fec_status_table(struct dm_verity *v, + unsigned sz, char *result, + unsigned maxlen) +{ + return sz; +} + +static inline void verity_fec_finish_io(struct dm_verity_io *io) +{ +} + +static inline void verity_fec_init_io(struct dm_verity_io *io) +{ +} + +static inline bool verity_is_fec_opt_arg(const char *arg_name) +{ + return false; +} + +static inline int verity_fec_parse_opt_args(struct dm_arg_set *as, + struct dm_verity *v, + unsigned *argc, + const char *arg_name) +{ + return -EINVAL; +} + +static inline void verity_fec_dtr(struct dm_verity *v) +{ +} + +static inline int verity_fec_ctr_alloc(struct dm_verity *v) +{ + return 0; +} + +static inline int verity_fec_ctr(struct dm_verity *v) +{ + return 0; +} + +#endif /* CONFIG_DM_VERITY_FEC */ + +#endif /* DM_VERITY_FEC_H */ diff --git a/drivers/md/dm-verity.c b/drivers/md/dm-verity-target.c index ccf41886ebcf..5c5d30cb6ec5 100644 --- a/drivers/md/dm-verity.c +++ b/drivers/md/dm-verity-target.c @@ -14,12 +14,11 @@ * access behavior. */ -#include "dm-bufio.h" +#include "dm-verity.h" +#include "dm-verity-fec.h" #include <linux/module.h> -#include <linux/device-mapper.h> #include <linux/reboot.h> -#include <crypto/hash.h> #define DM_MSG_PREFIX "verity" @@ -28,83 +27,18 @@ #define DM_VERITY_DEFAULT_PREFETCH_SIZE 262144 -#define DM_VERITY_MAX_LEVELS 63 #define DM_VERITY_MAX_CORRUPTED_ERRS 100 #define DM_VERITY_OPT_LOGGING "ignore_corruption" #define DM_VERITY_OPT_RESTART "restart_on_corruption" +#define DM_VERITY_OPT_IGN_ZEROES "ignore_zero_blocks" + +#define DM_VERITY_OPTS_MAX (2 + DM_VERITY_OPTS_FEC) static unsigned dm_verity_prefetch_cluster = DM_VERITY_DEFAULT_PREFETCH_SIZE; module_param_named(prefetch_cluster, dm_verity_prefetch_cluster, uint, S_IRUGO | S_IWUSR); -enum verity_mode { - DM_VERITY_MODE_EIO, - DM_VERITY_MODE_LOGGING, - DM_VERITY_MODE_RESTART -}; - -enum verity_block_type { - DM_VERITY_BLOCK_TYPE_DATA, - DM_VERITY_BLOCK_TYPE_METADATA -}; - -struct dm_verity { - struct dm_dev *data_dev; - struct dm_dev *hash_dev; - struct dm_target *ti; - struct dm_bufio_client *bufio; - char *alg_name; - struct crypto_shash *tfm; - u8 *root_digest; /* digest of the root block */ - u8 *salt; /* salt: its size is salt_size */ - unsigned salt_size; - sector_t data_start; /* data offset in 512-byte sectors */ - sector_t hash_start; /* hash start in blocks */ - sector_t data_blocks; /* the number of data blocks */ - sector_t hash_blocks; /* the number of hash blocks */ - unsigned char data_dev_block_bits; /* log2(data blocksize) */ - unsigned char hash_dev_block_bits; /* log2(hash blocksize) */ - unsigned char hash_per_block_bits; /* log2(hashes in hash block) */ - unsigned char levels; /* the number of tree levels */ - unsigned char version; - unsigned digest_size; /* digest size for the current hash algorithm */ - unsigned shash_descsize;/* the size of temporary space for crypto */ - int hash_failed; /* set to 1 if hash of any block failed */ - enum verity_mode mode; /* mode for handling verification errors */ - unsigned corrupted_errs;/* Number of errors for corrupted blocks */ - - struct workqueue_struct *verify_wq; - - /* starting blocks for each tree level. 0 is the lowest level. */ - sector_t hash_level_block[DM_VERITY_MAX_LEVELS]; -}; - -struct dm_verity_io { - struct dm_verity *v; - - /* original values of bio->bi_end_io and bio->bi_private */ - bio_end_io_t *orig_bi_end_io; - void *orig_bi_private; - - sector_t block; - unsigned n_blocks; - - struct bvec_iter iter; - - struct work_struct work; - - /* - * Three variably-size fields follow this struct: - * - * u8 hash_desc[v->shash_descsize]; - * u8 real_digest[v->digest_size]; - * u8 want_digest[v->digest_size]; - * - * To access them use: io_hash_desc(), io_real_digest() and io_want_digest(). - */ -}; - struct dm_verity_prefetch_work { struct work_struct work; struct dm_verity *v; @@ -112,21 +46,6 @@ struct dm_verity_prefetch_work { unsigned n_blocks; }; -static struct shash_desc *io_hash_desc(struct dm_verity *v, struct dm_verity_io *io) -{ - return (struct shash_desc *)(io + 1); -} - -static u8 *io_real_digest(struct dm_verity *v, struct dm_verity_io *io) -{ - return (u8 *)(io + 1) + v->shash_descsize; -} - -static u8 *io_want_digest(struct dm_verity *v, struct dm_verity_io *io) -{ - return (u8 *)(io + 1) + v->shash_descsize + v->digest_size; -} - /* * Auxiliary structure appended to each dm-bufio buffer. If the value * hash_verified is nonzero, hash of the block has been verified. @@ -173,6 +92,84 @@ static sector_t verity_position_at_level(struct dm_verity *v, sector_t block, return block >> (level * v->hash_per_block_bits); } +/* + * Wrapper for crypto_shash_init, which handles verity salting. + */ +static int verity_hash_init(struct dm_verity *v, struct shash_desc *desc) +{ + int r; + + desc->tfm = v->tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + + r = crypto_shash_init(desc); + + if (unlikely(r < 0)) { + DMERR("crypto_shash_init failed: %d", r); + return r; + } + + if (likely(v->version >= 1)) { + r = crypto_shash_update(desc, v->salt, v->salt_size); + + if (unlikely(r < 0)) { + DMERR("crypto_shash_update failed: %d", r); + return r; + } + } + + return 0; +} + +static int verity_hash_update(struct dm_verity *v, struct shash_desc *desc, + const u8 *data, size_t len) +{ + int r = crypto_shash_update(desc, data, len); + + if (unlikely(r < 0)) + DMERR("crypto_shash_update failed: %d", r); + + return r; +} + +static int verity_hash_final(struct dm_verity *v, struct shash_desc *desc, + u8 *digest) +{ + int r; + + if (unlikely(!v->version)) { + r = crypto_shash_update(desc, v->salt, v->salt_size); + + if (r < 0) { + DMERR("crypto_shash_update failed: %d", r); + return r; + } + } + + r = crypto_shash_final(desc, digest); + + if (unlikely(r < 0)) + DMERR("crypto_shash_final failed: %d", r); + + return r; +} + +int verity_hash(struct dm_verity *v, struct shash_desc *desc, + const u8 *data, size_t len, u8 *digest) +{ + int r; + + r = verity_hash_init(v, desc); + if (unlikely(r < 0)) + return r; + + r = verity_hash_update(v, desc, data, len); + if (unlikely(r < 0)) + return r; + + return verity_hash_final(v, desc, digest); +} + static void verity_hash_at_level(struct dm_verity *v, sector_t block, int level, sector_t *hash_block, unsigned *offset) { @@ -246,17 +243,17 @@ out: * Verify hash of a metadata block pertaining to the specified data block * ("block" argument) at a specified level ("level" argument). * - * On successful return, io_want_digest(v, io) contains the hash value for - * a lower tree level or for the data block (if we're at the lowest leve). + * On successful return, verity_io_want_digest(v, io) contains the hash value + * for a lower tree level or for the data block (if we're at the lowest level). * * If "skip_unverified" is true, unverified buffer is skipped and 1 is returned. * If "skip_unverified" is false, unverified buffer is hashed and verified - * against current value of io_want_digest(v, io). + * against current value of verity_io_want_digest(v, io). */ -static int verity_verify_level(struct dm_verity_io *io, sector_t block, - int level, bool skip_unverified) +static int verity_verify_level(struct dm_verity *v, struct dm_verity_io *io, + sector_t block, int level, bool skip_unverified, + u8 *want_digest) { - struct dm_verity *v = io->v; struct dm_buffer *buf; struct buffer_aux *aux; u8 *data; @@ -273,72 +270,128 @@ static int verity_verify_level(struct dm_verity_io *io, sector_t block, aux = dm_bufio_get_aux_data(buf); if (!aux->hash_verified) { - struct shash_desc *desc; - u8 *result; - if (skip_unverified) { r = 1; goto release_ret_r; } - desc = io_hash_desc(v, io); - desc->tfm = v->tfm; - desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; - r = crypto_shash_init(desc); - if (r < 0) { - DMERR("crypto_shash_init failed: %d", r); + r = verity_hash(v, verity_io_hash_desc(v, io), + data, 1 << v->hash_dev_block_bits, + verity_io_real_digest(v, io)); + if (unlikely(r < 0)) goto release_ret_r; - } - - if (likely(v->version >= 1)) { - r = crypto_shash_update(desc, v->salt, v->salt_size); - if (r < 0) { - DMERR("crypto_shash_update failed: %d", r); - goto release_ret_r; - } - } - r = crypto_shash_update(desc, data, 1 << v->hash_dev_block_bits); - if (r < 0) { - DMERR("crypto_shash_update failed: %d", r); + if (likely(memcmp(verity_io_real_digest(v, io), want_digest, + v->digest_size) == 0)) + aux->hash_verified = 1; + else if (verity_fec_decode(v, io, + DM_VERITY_BLOCK_TYPE_METADATA, + hash_block, data, NULL) == 0) + aux->hash_verified = 1; + else if (verity_handle_err(v, + DM_VERITY_BLOCK_TYPE_METADATA, + hash_block)) { + r = -EIO; goto release_ret_r; } + } - if (!v->version) { - r = crypto_shash_update(desc, v->salt, v->salt_size); - if (r < 0) { - DMERR("crypto_shash_update failed: %d", r); - goto release_ret_r; - } - } + data += offset; + memcpy(want_digest, data, v->digest_size); + r = 0; - result = io_real_digest(v, io); - r = crypto_shash_final(desc, result); - if (r < 0) { - DMERR("crypto_shash_final failed: %d", r); - goto release_ret_r; - } - if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) { - if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_METADATA, - hash_block)) { - r = -EIO; - goto release_ret_r; - } - } else - aux->hash_verified = 1; +release_ret_r: + dm_bufio_release(buf); + return r; +} + +/* + * Find a hash for a given block, write it to digest and verify the integrity + * of the hash tree if necessary. + */ +int verity_hash_for_block(struct dm_verity *v, struct dm_verity_io *io, + sector_t block, u8 *digest, bool *is_zero) +{ + int r = 0, i; + + if (likely(v->levels)) { + /* + * First, we try to get the requested hash for + * the current block. If the hash block itself is + * verified, zero is returned. If it isn't, this + * function returns 1 and we fall back to whole + * chain verification. + */ + r = verity_verify_level(v, io, block, 0, true, digest); + if (likely(r <= 0)) + goto out; } - data += offset; + memcpy(digest, v->root_digest, v->digest_size); - memcpy(io_want_digest(v, io), data, v->digest_size); + for (i = v->levels - 1; i >= 0; i--) { + r = verity_verify_level(v, io, block, i, false, digest); + if (unlikely(r)) + goto out; + } +out: + if (!r && v->zero_digest) + *is_zero = !memcmp(v->zero_digest, digest, v->digest_size); + else + *is_zero = false; + + return r; +} + +/* + * Calls function process for 1 << v->data_dev_block_bits bytes in the bio_vec + * starting from iter. + */ +int verity_for_bv_block(struct dm_verity *v, struct dm_verity_io *io, + struct bvec_iter *iter, + int (*process)(struct dm_verity *v, + struct dm_verity_io *io, u8 *data, + size_t len)) +{ + unsigned todo = 1 << v->data_dev_block_bits; + struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_bio_data_size); + + do { + int r; + u8 *page; + unsigned len; + struct bio_vec bv = bio_iter_iovec(bio, *iter); + + page = kmap_atomic(bv.bv_page); + len = bv.bv_len; + + if (likely(len >= todo)) + len = todo; + + r = process(v, io, page + bv.bv_offset, len); + kunmap_atomic(page); + + if (r < 0) + return r; + + bio_advance_iter(bio, iter, len); + todo -= len; + } while (todo); - dm_bufio_release(buf); return 0; +} -release_ret_r: - dm_bufio_release(buf); +static int verity_bv_hash_update(struct dm_verity *v, struct dm_verity_io *io, + u8 *data, size_t len) +{ + return verity_hash_update(v, verity_io_hash_desc(v, io), data, len); +} - return r; +static int verity_bv_zero(struct dm_verity *v, struct dm_verity_io *io, + u8 *data, size_t len) +{ + memset(data, 0, len); + return 0; } /* @@ -346,99 +399,56 @@ release_ret_r: */ static int verity_verify_io(struct dm_verity_io *io) { + bool is_zero; struct dm_verity *v = io->v; - struct bio *bio = dm_bio_from_per_bio_data(io, - v->ti->per_bio_data_size); + struct bvec_iter start; unsigned b; - int i; for (b = 0; b < io->n_blocks; b++) { - struct shash_desc *desc; - u8 *result; int r; - unsigned todo; + struct shash_desc *desc = verity_io_hash_desc(v, io); + + r = verity_hash_for_block(v, io, io->block + b, + verity_io_want_digest(v, io), + &is_zero); + if (unlikely(r < 0)) + return r; - if (likely(v->levels)) { + if (is_zero) { /* - * First, we try to get the requested hash for - * the current block. If the hash block itself is - * verified, zero is returned. If it isn't, this - * function returns 0 and we fall back to whole - * chain verification. + * If we expect a zero block, don't validate, just + * return zeros. */ - int r = verity_verify_level(io, io->block + b, 0, true); - if (likely(!r)) - goto test_block_hash; - if (r < 0) + r = verity_for_bv_block(v, io, &io->iter, + verity_bv_zero); + if (unlikely(r < 0)) return r; - } - memcpy(io_want_digest(v, io), v->root_digest, v->digest_size); - - for (i = v->levels - 1; i >= 0; i--) { - int r = verity_verify_level(io, io->block + b, i, false); - if (unlikely(r)) - return r; + continue; } -test_block_hash: - desc = io_hash_desc(v, io); - desc->tfm = v->tfm; - desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; - r = crypto_shash_init(desc); - if (r < 0) { - DMERR("crypto_shash_init failed: %d", r); + r = verity_hash_init(v, desc); + if (unlikely(r < 0)) return r; - } - - if (likely(v->version >= 1)) { - r = crypto_shash_update(desc, v->salt, v->salt_size); - if (r < 0) { - DMERR("crypto_shash_update failed: %d", r); - return r; - } - } - todo = 1 << v->data_dev_block_bits; - do { - u8 *page; - unsigned len; - struct bio_vec bv = bio_iter_iovec(bio, io->iter); - - page = kmap_atomic(bv.bv_page); - len = bv.bv_len; - if (likely(len >= todo)) - len = todo; - r = crypto_shash_update(desc, page + bv.bv_offset, len); - kunmap_atomic(page); - - if (r < 0) { - DMERR("crypto_shash_update failed: %d", r); - return r; - } - - bio_advance_iter(bio, &io->iter, len); - todo -= len; - } while (todo); - if (!v->version) { - r = crypto_shash_update(desc, v->salt, v->salt_size); - if (r < 0) { - DMERR("crypto_shash_update failed: %d", r); - return r; - } - } + start = io->iter; + r = verity_for_bv_block(v, io, &io->iter, verity_bv_hash_update); + if (unlikely(r < 0)) + return r; - result = io_real_digest(v, io); - r = crypto_shash_final(desc, result); - if (r < 0) { - DMERR("crypto_shash_final failed: %d", r); + r = verity_hash_final(v, desc, verity_io_real_digest(v, io)); + if (unlikely(r < 0)) return r; - } - if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) { - if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA, - io->block + b)) - return -EIO; - } + + if (likely(memcmp(verity_io_real_digest(v, io), + verity_io_want_digest(v, io), v->digest_size) == 0)) + continue; + else if (verity_fec_decode(v, io, DM_VERITY_BLOCK_TYPE_DATA, + io->block + b, NULL, &start) == 0) + continue; + else if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA, + io->block + b)) + return -EIO; } return 0; @@ -453,9 +463,10 @@ static void verity_finish_io(struct dm_verity_io *io, int error) struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_bio_data_size); bio->bi_end_io = io->orig_bi_end_io; - bio->bi_private = io->orig_bi_private; bio->bi_error = error; + verity_fec_finish_io(io); + bio_endio(bio); } @@ -470,7 +481,7 @@ static void verity_end_io(struct bio *bio) { struct dm_verity_io *io = bio->bi_private; - if (bio->bi_error) { + if (bio->bi_error && !verity_fec_is_enabled(io->v)) { verity_finish_io(io, bio->bi_error); return; } @@ -566,7 +577,6 @@ static int verity_map(struct dm_target *ti, struct bio *bio) io = dm_per_bio_data(bio, ti->per_bio_data_size); io->v = v; io->orig_bi_end_io = bio->bi_end_io; - io->orig_bi_private = bio->bi_private; io->block = bio->bi_iter.bi_sector >> (v->data_dev_block_bits - SECTOR_SHIFT); io->n_blocks = bio->bi_iter.bi_size >> v->data_dev_block_bits; @@ -574,6 +584,8 @@ static int verity_map(struct dm_target *ti, struct bio *bio) bio->bi_private = io; io->iter = bio->bi_iter; + verity_fec_init_io(io); + verity_submit_prefetch(v, io); generic_make_request(bio); @@ -588,6 +600,7 @@ static void verity_status(struct dm_target *ti, status_type_t type, unsigned status_flags, char *result, unsigned maxlen) { struct dm_verity *v = ti->private; + unsigned args = 0; unsigned sz = 0; unsigned x; @@ -614,8 +627,17 @@ static void verity_status(struct dm_target *ti, status_type_t type, else for (x = 0; x < v->salt_size; x++) DMEMIT("%02x", v->salt[x]); + if (v->mode != DM_VERITY_MODE_EIO) + args++; + if (verity_fec_is_enabled(v)) + args += DM_VERITY_OPTS_FEC; + if (v->zero_digest) + args++; + if (!args) + return; + DMEMIT(" %u", args); if (v->mode != DM_VERITY_MODE_EIO) { - DMEMIT(" 1 "); + DMEMIT(" "); switch (v->mode) { case DM_VERITY_MODE_LOGGING: DMEMIT(DM_VERITY_OPT_LOGGING); @@ -627,6 +649,9 @@ static void verity_status(struct dm_target *ti, status_type_t type, BUG(); } } + if (v->zero_digest) + DMEMIT(" " DM_VERITY_OPT_IGN_ZEROES); + sz = verity_fec_status_table(v, sz, result, maxlen); break; } } @@ -677,6 +702,7 @@ static void verity_dtr(struct dm_target *ti) kfree(v->salt); kfree(v->root_digest); + kfree(v->zero_digest); if (v->tfm) crypto_free_shash(v->tfm); @@ -689,9 +715,94 @@ static void verity_dtr(struct dm_target *ti) if (v->data_dev) dm_put_device(ti, v->data_dev); + verity_fec_dtr(v); + kfree(v); } +static int verity_alloc_zero_digest(struct dm_verity *v) +{ + int r = -ENOMEM; + struct shash_desc *desc; + u8 *zero_data; + + v->zero_digest = kmalloc(v->digest_size, GFP_KERNEL); + + if (!v->zero_digest) + return r; + + desc = kmalloc(v->shash_descsize, GFP_KERNEL); + + if (!desc) + return r; /* verity_dtr will free zero_digest */ + + zero_data = kzalloc(1 << v->data_dev_block_bits, GFP_KERNEL); + + if (!zero_data) + goto out; + + r = verity_hash(v, desc, zero_data, 1 << v->data_dev_block_bits, + v->zero_digest); + +out: + kfree(desc); + kfree(zero_data); + + return r; +} + +static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v) +{ + int r; + unsigned argc; + struct dm_target *ti = v->ti; + const char *arg_name; + + static struct dm_arg _args[] = { + {0, DM_VERITY_OPTS_MAX, "Invalid number of feature args"}, + }; + + r = dm_read_arg_group(_args, as, &argc, &ti->error); + if (r) + return -EINVAL; + + if (!argc) + return 0; + + do { + arg_name = dm_shift_arg(as); + argc--; + + if (!strcasecmp(arg_name, DM_VERITY_OPT_LOGGING)) { + v->mode = DM_VERITY_MODE_LOGGING; + continue; + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_RESTART)) { + v->mode = DM_VERITY_MODE_RESTART; + continue; + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_IGN_ZEROES)) { + r = verity_alloc_zero_digest(v); + if (r) { + ti->error = "Cannot allocate zero digest"; + return r; + } + continue; + + } else if (verity_is_fec_opt_arg(arg_name)) { + r = verity_fec_parse_opt_args(as, v, &argc, arg_name); + if (r) + return r; + continue; + } + + ti->error = "Unrecognized verity feature request"; + return -EINVAL; + } while (argc && !r); + + return r; +} + /* * Target parameters: * <version> The current format is version 1. @@ -710,18 +821,13 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) { struct dm_verity *v; struct dm_arg_set as; - const char *opt_string; - unsigned int num, opt_params; + unsigned int num; unsigned long long num_ll; int r; int i; sector_t hash_position; char dummy; - static struct dm_arg _args[] = { - {0, 1, "Invalid number of feature args"}, - }; - v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL); if (!v) { ti->error = "Cannot allocate verity structure"; @@ -730,6 +836,10 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) ti->private = v; v->ti = ti; + r = verity_fec_ctr_alloc(v); + if (r) + goto bad; + if ((dm_table_get_mode(ti->table) & ~FMODE_READ)) { ti->error = "Device must be readonly"; r = -EINVAL; @@ -866,29 +976,9 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) as.argc = argc; as.argv = argv; - r = dm_read_arg_group(_args, &as, &opt_params, &ti->error); - if (r) + r = verity_parse_opt_args(&as, v); + if (r < 0) goto bad; - - while (opt_params) { - opt_params--; - opt_string = dm_shift_arg(&as); - if (!opt_string) { - ti->error = "Not enough feature arguments"; - r = -EINVAL; - goto bad; - } - - if (!strcasecmp(opt_string, DM_VERITY_OPT_LOGGING)) - v->mode = DM_VERITY_MODE_LOGGING; - else if (!strcasecmp(opt_string, DM_VERITY_OPT_RESTART)) - v->mode = DM_VERITY_MODE_RESTART; - else { - ti->error = "Invalid feature arguments"; - r = -EINVAL; - goto bad; - } - } } v->hash_per_block_bits = @@ -938,8 +1028,6 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) goto bad; } - ti->per_bio_data_size = roundup(sizeof(struct dm_verity_io) + v->shash_descsize + v->digest_size * 2, __alignof__(struct dm_verity_io)); - /* WQ_UNBOUND greatly improves performance when running on ramdisk */ v->verify_wq = alloc_workqueue("kverityd", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM | WQ_UNBOUND, num_online_cpus()); if (!v->verify_wq) { @@ -948,6 +1036,16 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) goto bad; } + ti->per_bio_data_size = sizeof(struct dm_verity_io) + + v->shash_descsize + v->digest_size * 2; + + r = verity_fec_ctr(v); + if (r) + goto bad; + + ti->per_bio_data_size = roundup(ti->per_bio_data_size, + __alignof__(struct dm_verity_io)); + return 0; bad: @@ -958,7 +1056,7 @@ bad: static struct target_type verity_target = { .name = "verity", - .version = {1, 2, 0}, + .version = {1, 3, 0}, .module = THIS_MODULE, .ctr = verity_ctr, .dtr = verity_dtr, diff --git a/drivers/md/dm-verity.h b/drivers/md/dm-verity.h new file mode 100644 index 000000000000..fb419f422d73 --- /dev/null +++ b/drivers/md/dm-verity.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * Copyright (C) 2015 Google, Inc. + * + * Author: Mikulas Patocka <mpatocka@redhat.com> + * + * Based on Chromium dm-verity driver (C) 2011 The Chromium OS Authors + * + * This file is released under the GPLv2. + */ + +#ifndef DM_VERITY_H +#define DM_VERITY_H + +#include "dm-bufio.h" +#include <linux/device-mapper.h> +#include <crypto/hash.h> + +#define DM_VERITY_MAX_LEVELS 63 + +enum verity_mode { + DM_VERITY_MODE_EIO, + DM_VERITY_MODE_LOGGING, + DM_VERITY_MODE_RESTART +}; + +enum verity_block_type { + DM_VERITY_BLOCK_TYPE_DATA, + DM_VERITY_BLOCK_TYPE_METADATA +}; + +struct dm_verity_fec; + +struct dm_verity { + struct dm_dev *data_dev; + struct dm_dev *hash_dev; + struct dm_target *ti; + struct dm_bufio_client *bufio; + char *alg_name; + struct crypto_shash *tfm; + u8 *root_digest; /* digest of the root block */ + u8 *salt; /* salt: its size is salt_size */ + u8 *zero_digest; /* digest for a zero block */ + unsigned salt_size; + sector_t data_start; /* data offset in 512-byte sectors */ + sector_t hash_start; /* hash start in blocks */ + sector_t data_blocks; /* the number of data blocks */ + sector_t hash_blocks; /* the number of hash blocks */ + unsigned char data_dev_block_bits; /* log2(data blocksize) */ + unsigned char hash_dev_block_bits; /* log2(hash blocksize) */ + unsigned char hash_per_block_bits; /* log2(hashes in hash block) */ + unsigned char levels; /* the number of tree levels */ + unsigned char version; + unsigned digest_size; /* digest size for the current hash algorithm */ + unsigned shash_descsize;/* the size of temporary space for crypto */ + int hash_failed; /* set to 1 if hash of any block failed */ + enum verity_mode mode; /* mode for handling verification errors */ + unsigned corrupted_errs;/* Number of errors for corrupted blocks */ + + struct workqueue_struct *verify_wq; + + /* starting blocks for each tree level. 0 is the lowest level. */ + sector_t hash_level_block[DM_VERITY_MAX_LEVELS]; + + struct dm_verity_fec *fec; /* forward error correction */ +}; + +struct dm_verity_io { + struct dm_verity *v; + + /* original value of bio->bi_end_io */ + bio_end_io_t *orig_bi_end_io; + + sector_t block; + unsigned n_blocks; + + struct bvec_iter iter; + + struct work_struct work; + + /* + * Three variably-size fields follow this struct: + * + * u8 hash_desc[v->shash_descsize]; + * u8 real_digest[v->digest_size]; + * u8 want_digest[v->digest_size]; + * + * To access them use: verity_io_hash_desc(), verity_io_real_digest() + * and verity_io_want_digest(). + */ +}; + +static inline struct shash_desc *verity_io_hash_desc(struct dm_verity *v, + struct dm_verity_io *io) +{ + return (struct shash_desc *)(io + 1); +} + +static inline u8 *verity_io_real_digest(struct dm_verity *v, + struct dm_verity_io *io) +{ + return (u8 *)(io + 1) + v->shash_descsize; +} + +static inline u8 *verity_io_want_digest(struct dm_verity *v, + struct dm_verity_io *io) +{ + return (u8 *)(io + 1) + v->shash_descsize + v->digest_size; +} + +static inline u8 *verity_io_digest_end(struct dm_verity *v, + struct dm_verity_io *io) +{ + return verity_io_want_digest(v, io) + v->digest_size; +} + +extern int verity_for_bv_block(struct dm_verity *v, struct dm_verity_io *io, + struct bvec_iter *iter, + int (*process)(struct dm_verity *v, + struct dm_verity_io *io, + u8 *data, size_t len)); + +extern int verity_hash(struct dm_verity *v, struct shash_desc *desc, + const u8 *data, size_t len, u8 *digest); + +extern int verity_hash_for_block(struct dm_verity *v, struct dm_verity_io *io, + sector_t block, u8 *digest, bool *is_zero); + +#endif /* DM_VERITY_H */ diff --git a/drivers/mfd/wcd934x-regmap.c b/drivers/mfd/wcd934x-regmap.c index b0e3bec683ed..b102264ca8fd 100644 --- a/drivers/mfd/wcd934x-regmap.c +++ b/drivers/mfd/wcd934x-regmap.c @@ -1848,6 +1848,20 @@ static bool wcd934x_is_volatile_register(struct device *dev, unsigned int reg) if (reg_tbl && reg_tbl[reg_offset] == WCD934X_READ) return true; + /* IIR Coeff registers are not cacheable */ + if ((reg >= WCD934X_CDC_SIDETONE_IIR0_IIR_COEF_B1_CTL) && + (reg <= WCD934X_CDC_SIDETONE_IIR1_IIR_COEF_B2_CTL)) + return true; + + if ((reg >= WCD934X_CDC_ANC0_IIR_COEFF_1_CTL) && + (reg <= WCD934X_CDC_ANC0_FB_GAIN_CTL)) + return true; + + if ((reg >= WCD934X_CDC_ANC1_IIR_COEFF_1_CTL) && + (reg <= WCD934X_CDC_ANC1_FB_GAIN_CTL)) + return true; + + /* * Need to mark volatile for registers that are writable but * only few bits are read-only diff --git a/drivers/misc/qseecom.c b/drivers/misc/qseecom.c index 3402a1b581cf..bd554667de96 100644 --- a/drivers/misc/qseecom.c +++ b/drivers/misc/qseecom.c @@ -3325,6 +3325,7 @@ static int __qseecom_update_cmd_buf_64(void *msg, bool cleanup, } } len = QSEECOM_SG_LIST_BUF_HDR_SZ_64BIT; + sg = sg_ptr->sgl; goto cleanup; } sg = sg_ptr->sgl; @@ -4091,12 +4092,21 @@ int qseecom_start_app(struct qseecom_handle **handle, data->client.user_virt_sb_base = 0; data->client.ihandle = NULL; + /* Allocate sglistinfo buffer for kernel client */ + data->sglistinfo_ptr = kzalloc(SGLISTINFO_TABLE_SIZE, GFP_KERNEL); + if (!(data->sglistinfo_ptr)) { + kfree(data); + kfree(*handle); + *handle = NULL; + return -ENOMEM; + } init_waitqueue_head(&data->abort_wq); data->client.ihandle = ion_alloc(qseecom.ion_clnt, size, 4096, ION_HEAP(ION_QSECOM_HEAP_ID), 0); if (IS_ERR_OR_NULL(data->client.ihandle)) { pr_err("Ion client could not retrieve the handle\n"); + kfree(data->sglistinfo_ptr); kfree(data); kfree(*handle); *handle = NULL; @@ -4194,6 +4204,7 @@ int qseecom_start_app(struct qseecom_handle **handle, return 0; err: + kfree(data->sglistinfo_ptr); kfree(data); kfree(*handle); *handle = NULL; @@ -4241,6 +4252,7 @@ int qseecom_shutdown_app(struct qseecom_handle **handle) mutex_unlock(&app_access_lock); if (ret == 0) { + kzfree(data->sglistinfo_ptr); kzfree(data); kzfree(*handle); kzfree(kclient); diff --git a/drivers/misc/uid_stat.c b/drivers/misc/uid_stat.c new file mode 100644 index 000000000000..185c69c9738a --- /dev/null +++ b/drivers/misc/uid_stat.c @@ -0,0 +1,153 @@ +/* drivers/misc/uid_stat.c + * + * Copyright (C) 2008 - 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/atomic.h> + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/stat.h> +#include <linux/uid_stat.h> +#include <net/activity_stats.h> + +static DEFINE_SPINLOCK(uid_lock); +static LIST_HEAD(uid_list); +static struct proc_dir_entry *parent; + +struct uid_stat { + struct list_head link; + uid_t uid; + atomic_t tcp_rcv; + atomic_t tcp_snd; +}; + +static struct uid_stat *find_uid_stat(uid_t uid) { + struct uid_stat *entry; + + list_for_each_entry(entry, &uid_list, link) { + if (entry->uid == uid) { + return entry; + } + } + return NULL; +} + +static int uid_stat_atomic_int_show(struct seq_file *m, void *v) +{ + unsigned int bytes; + atomic_t *counter = m->private; + + bytes = (unsigned int) (atomic_read(counter) + INT_MIN); + seq_printf(m, "%u\n", bytes); + return seq_has_overflowed(m) ? -ENOSPC : 0; +} + +static int uid_stat_read_atomic_int_open(struct inode *inode, struct file *file) +{ + return single_open(file, uid_stat_atomic_int_show, PDE_DATA(inode)); +} + +static const struct file_operations uid_stat_read_atomic_int_fops = { + .open = uid_stat_read_atomic_int_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* Create a new entry for tracking the specified uid. */ +static struct uid_stat *create_stat(uid_t uid) { + struct uid_stat *new_uid; + /* Create the uid stat struct and append it to the list. */ + new_uid = kmalloc(sizeof(struct uid_stat), GFP_ATOMIC); + if (!new_uid) + return NULL; + + new_uid->uid = uid; + /* Counters start at INT_MIN, so we can track 4GB of network traffic. */ + atomic_set(&new_uid->tcp_rcv, INT_MIN); + atomic_set(&new_uid->tcp_snd, INT_MIN); + + list_add_tail(&new_uid->link, &uid_list); + return new_uid; +} + +static void create_stat_proc(struct uid_stat *new_uid) +{ + char uid_s[32]; + struct proc_dir_entry *entry; + sprintf(uid_s, "%d", new_uid->uid); + entry = proc_mkdir(uid_s, parent); + + /* Keep reference to uid_stat so we know what uid to read stats from. */ + proc_create_data("tcp_snd", S_IRUGO, entry, + &uid_stat_read_atomic_int_fops, &new_uid->tcp_snd); + + proc_create_data("tcp_rcv", S_IRUGO, entry, + &uid_stat_read_atomic_int_fops, &new_uid->tcp_rcv); +} + +static struct uid_stat *find_or_create_uid_stat(uid_t uid) +{ + struct uid_stat *entry; + unsigned long flags; + spin_lock_irqsave(&uid_lock, flags); + entry = find_uid_stat(uid); + if (entry) { + spin_unlock_irqrestore(&uid_lock, flags); + return entry; + } + entry = create_stat(uid); + spin_unlock_irqrestore(&uid_lock, flags); + if (entry) + create_stat_proc(entry); + return entry; +} + +int uid_stat_tcp_snd(uid_t uid, int size) { + struct uid_stat *entry; + activity_stats_update(); + entry = find_or_create_uid_stat(uid); + if (!entry) + return -1; + atomic_add(size, &entry->tcp_snd); + return 0; +} + +int uid_stat_tcp_rcv(uid_t uid, int size) { + struct uid_stat *entry; + activity_stats_update(); + entry = find_or_create_uid_stat(uid); + if (!entry) + return -1; + atomic_add(size, &entry->tcp_rcv); + return 0; +} + +static int __init uid_stat_init(void) +{ + parent = proc_mkdir("uid_stat", NULL); + if (!parent) { + pr_err("uid_stat: failed to create proc entry\n"); + return -1; + } + return 0; +} + +__initcall(uid_stat_init); diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308699bc..6142ec1b9dfb 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -68,3 +68,15 @@ config MMC_TEST This driver is only of interest to those developing or testing a host driver. Most people should say N here. + +config MMC_SIMULATE_MAX_SPEED + bool "Turn on maximum speed control per block device" + depends on MMC_BLOCK + help + Say Y here to enable MMC device speed limiting. Used to test and + simulate the behavior of the system when confronted with a slow MMC. + + Enables max_read_speed, max_write_speed and cache_size attributes to + control the write or read maximum KB/second speed behaviors. + + If unsure, say N here. diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index b116122c5767..7547463928d6 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -38,7 +38,6 @@ #include <linux/pm_runtime.h> #include <linux/ioprio.h> -#define CREATE_TRACE_POINTS #include <trace/events/mmc.h> #include <linux/mmc/ioctl.h> @@ -329,6 +328,249 @@ out: return ret; } +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + +static int max_read_speed, max_write_speed, cache_size = 4; + +module_param(max_read_speed, int, S_IRUSR | S_IRGRP); +MODULE_PARM_DESC(max_read_speed, "maximum KB/s read speed 0=off"); +module_param(max_write_speed, int, S_IRUSR | S_IRGRP); +MODULE_PARM_DESC(max_write_speed, "maximum KB/s write speed 0=off"); +module_param(cache_size, int, S_IRUSR | S_IRGRP); +MODULE_PARM_DESC(cache_size, "MB high speed memory or SLC cache"); + +/* + * helper macros and expectations: + * size - unsigned long number of bytes + * jiffies - unsigned long HZ timestamp difference + * speed - unsigned KB/s transfer rate + */ +#define size_and_speed_to_jiffies(size, speed) \ + ((size) * HZ / (speed) / 1024UL) +#define jiffies_and_speed_to_size(jiffies, speed) \ + (((speed) * (jiffies) * 1024UL) / HZ) +#define jiffies_and_size_to_speed(jiffies, size) \ + ((size) * HZ / (jiffies) / 1024UL) + +/* Limits to report warning */ +/* jiffies_and_size_to_speed(10*HZ, queue_max_hw_sectors(q) * 512UL) ~ 25 */ +#define MIN_SPEED(q) 250 /* 10 times faster than a floppy disk */ +#define MAX_SPEED(q) jiffies_and_size_to_speed(1, queue_max_sectors(q) * 512UL) + +#define speed_valid(speed) ((speed) > 0) + +static const char off[] = "off\n"; + +static int max_speed_show(int speed, char *buf) +{ + if (speed) + return scnprintf(buf, PAGE_SIZE, "%uKB/s\n", speed); + else + return scnprintf(buf, PAGE_SIZE, off); +} + +static int max_speed_store(const char *buf, struct request_queue *q) +{ + unsigned int limit, set = 0; + + if (!strncasecmp(off, buf, sizeof(off) - 2)) + return set; + if (kstrtouint(buf, 0, &set) || (set > INT_MAX)) + return -EINVAL; + if (set == 0) + return set; + limit = MAX_SPEED(q); + if (set > limit) + pr_warn("max speed %u ineffective above %u\n", set, limit); + limit = MIN_SPEED(q); + if (set < limit) + pr_warn("max speed %u painful below %u\n", set, limit); + return set; +} + +static ssize_t max_write_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int ret = max_speed_show(atomic_read(&md->queue.max_write_speed), buf); + + mmc_blk_put(md); + return ret; +} + +static ssize_t max_write_speed_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int set = max_speed_store(buf, md->queue.queue); + + if (set < 0) { + mmc_blk_put(md); + return set; + } + + atomic_set(&md->queue.max_write_speed, set); + mmc_blk_put(md); + return count; +} + +static const DEVICE_ATTR(max_write_speed, S_IRUGO | S_IWUSR, + max_write_speed_show, max_write_speed_store); + +static ssize_t max_read_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int ret = max_speed_show(atomic_read(&md->queue.max_read_speed), buf); + + mmc_blk_put(md); + return ret; +} + +static ssize_t max_read_speed_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int set = max_speed_store(buf, md->queue.queue); + + if (set < 0) { + mmc_blk_put(md); + return set; + } + + atomic_set(&md->queue.max_read_speed, set); + mmc_blk_put(md); + return count; +} + +static const DEVICE_ATTR(max_read_speed, S_IRUGO | S_IWUSR, + max_read_speed_show, max_read_speed_store); + +static ssize_t cache_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + struct mmc_queue *mq = &md->queue; + int cache_size = atomic_read(&mq->cache_size); + int ret; + + if (!cache_size) + ret = scnprintf(buf, PAGE_SIZE, off); + else { + int speed = atomic_read(&mq->max_write_speed); + + if (!speed_valid(speed)) + ret = scnprintf(buf, PAGE_SIZE, "%uMB\n", cache_size); + else { /* We accept race between cache_jiffies and cache_used */ + unsigned long size = jiffies_and_speed_to_size( + jiffies - mq->cache_jiffies, speed); + long used = atomic_long_read(&mq->cache_used); + + if (size >= used) + size = 0; + else + size = (used - size) * 100 / cache_size + / 1024UL / 1024UL; + + ret = scnprintf(buf, PAGE_SIZE, "%uMB %lu%% used\n", + cache_size, size); + } + } + + mmc_blk_put(md); + return ret; +} + +static ssize_t cache_size_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mmc_blk_data *md; + unsigned int set = 0; + + if (strncasecmp(off, buf, sizeof(off) - 2) + && (kstrtouint(buf, 0, &set) || (set > INT_MAX))) + return -EINVAL; + + md = mmc_blk_get(dev_to_disk(dev)); + atomic_set(&md->queue.cache_size, set); + mmc_blk_put(md); + return count; +} + +static const DEVICE_ATTR(cache_size, S_IRUGO | S_IWUSR, + cache_size_show, cache_size_store); + +/* correct for write-back */ +static long mmc_blk_cache_used(struct mmc_queue *mq, unsigned long waitfor) +{ + long used = 0; + int speed = atomic_read(&mq->max_write_speed); + + if (speed_valid(speed)) { + unsigned long size = jiffies_and_speed_to_size( + waitfor - mq->cache_jiffies, speed); + used = atomic_long_read(&mq->cache_used); + + if (size >= used) + used = 0; + else + used -= size; + } + + atomic_long_set(&mq->cache_used, used); + mq->cache_jiffies = waitfor; + + return used; +} + +static void mmc_blk_simulate_delay( + struct mmc_queue *mq, + struct request *req, + unsigned long waitfor) +{ + int max_speed; + + if (!req) + return; + + max_speed = (rq_data_dir(req) == READ) + ? atomic_read(&mq->max_read_speed) + : atomic_read(&mq->max_write_speed); + if (speed_valid(max_speed)) { + unsigned long bytes = blk_rq_bytes(req); + + if (rq_data_dir(req) != READ) { + int cache_size = atomic_read(&mq->cache_size); + + if (cache_size) { + unsigned long size = cache_size * 1024L * 1024L; + long used = mmc_blk_cache_used(mq, waitfor); + + used += bytes; + atomic_long_set(&mq->cache_used, used); + bytes = 0; + if (used > size) + bytes = used - size; + } + } + waitfor += size_and_speed_to_jiffies(bytes, max_speed); + if (time_is_after_jiffies(waitfor)) { + long msecs = jiffies_to_msecs(waitfor - jiffies); + + if (likely(msecs > 0)) + msleep(msecs); + } + } +} + +#else + +#define mmc_blk_simulate_delay(mq, req, waitfor) + +#endif static ssize_t num_wr_reqs_to_start_packing_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1870,6 +2112,23 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) } end_req: +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + else if (atomic_read(&mq->cache_size)) { + long used = mmc_blk_cache_used(mq, jiffies); + + if (used) { + int speed = atomic_read(&mq->max_write_speed); + + if (speed_valid(speed)) { + unsigned long msecs = jiffies_to_msecs( + size_and_speed_to_jiffies( + used, speed)); + if (msecs) + msleep(msecs); + } + } + } +#endif blk_end_request_all(req, ret); return ret ? 0 : 1; @@ -3346,6 +3605,9 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) struct mmc_async_req *areq; const u8 packed_nr = 2; u8 reqs = 0; +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + unsigned long waitfor = jiffies; +#endif if (!rqc && !mq->mqrq_prev->req) return 0; @@ -3396,6 +3658,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) */ mmc_blk_reset_success(md, type); + mmc_blk_simulate_delay(mq, rqc, waitfor); + if (mmc_packed_cmd(mq_rq->cmd_type)) { ret = mmc_blk_end_packed_req(mq_rq); break; @@ -3968,6 +4232,14 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md) card->ext_csd.boot_ro_lockable) device_remove_file(disk_to_dev(md->disk), &md->power_ro_lock); +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + device_remove_file(disk_to_dev(md->disk), + &dev_attr_max_write_speed); + device_remove_file(disk_to_dev(md->disk), + &dev_attr_max_read_speed); + device_remove_file(disk_to_dev(md->disk), + &dev_attr_cache_size); +#endif del_gendisk(md->disk); } @@ -4003,6 +4275,24 @@ static int mmc_add_disk(struct mmc_blk_data *md) ret = device_create_file(disk_to_dev(md->disk), &md->force_ro); if (ret) goto force_ro_fail; +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + atomic_set(&md->queue.max_write_speed, max_write_speed); + ret = device_create_file(disk_to_dev(md->disk), + &dev_attr_max_write_speed); + if (ret) + goto max_write_speed_fail; + atomic_set(&md->queue.max_read_speed, max_read_speed); + ret = device_create_file(disk_to_dev(md->disk), + &dev_attr_max_read_speed); + if (ret) + goto max_read_speed_fail; + atomic_set(&md->queue.cache_size, cache_size); + atomic_long_set(&md->queue.cache_used, 0); + md->queue.cache_jiffies = jiffies; + ret = device_create_file(disk_to_dev(md->disk), &dev_attr_cache_size); + if (ret) + goto cache_size_fail; +#endif if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && card->ext_csd.boot_ro_lockable) { @@ -4056,6 +4346,14 @@ no_pack_for_random_fails: num_wr_reqs_to_start_packing_fail: device_remove_file(disk_to_dev(md->disk), &md->power_ro_lock); power_ro_lock_fail: +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + device_remove_file(disk_to_dev(md->disk), &dev_attr_cache_size); +cache_size_fail: + device_remove_file(disk_to_dev(md->disk), &dev_attr_max_read_speed); +max_read_speed_fail: + device_remove_file(disk_to_dev(md->disk), &dev_attr_max_write_speed); +max_write_speed_fail: +#endif device_remove_file(disk_to_dev(md->disk), &md->force_ro); force_ro_fail: del_gendisk(md->disk); diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index d8a33ac55c91..253979b51c84 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -77,6 +77,14 @@ struct mmc_queue { int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); void (*cmdq_shutdown)(struct mmc_queue *); +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED + atomic_t max_write_speed; + atomic_t max_read_speed; + atomic_t cache_size; + /* i/o tracking */ + atomic_long_t cache_used; + unsigned long cache_jiffies; +#endif }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b76bb7a74049..3762f698e1ee 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -33,6 +33,7 @@ #include <linux/pm.h> #include <linux/jiffies.h> +#define CREATE_TRACE_POINTS #include <trace/events/mmc.h> #include <linux/mmc/card.h> @@ -51,6 +52,11 @@ #include "sd_ops.h" #include "sdio_ops.h" +EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_erase_start); +EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_erase_end); +EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_rw_start); +EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_rw_end); + /* If the device is not responding */ #define MMC_CORE_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */ diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 895dfbb7fbf5..e7ebc3cb8eda 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -22,6 +22,7 @@ #include "core.h" #include "bus.h" +#include "host.h" #include "sd.h" #include "sdio_bus.h" #include "mmc_ops.h" diff --git a/drivers/net/ppp/Kconfig b/drivers/net/ppp/Kconfig index e4cf44b1e815..282aec4860eb 100644 --- a/drivers/net/ppp/Kconfig +++ b/drivers/net/ppp/Kconfig @@ -150,7 +150,7 @@ config PPPOL2TP if TTY config PPPOLAC - bool "PPP on L2TP Access Concentrator" + tristate "PPP on L2TP Access Concentrator" depends on PPP && INET help L2TP (RFC 2661) is a tunneling protocol widely used in virtual private @@ -159,7 +159,7 @@ config PPPOLAC fairly simple and suited for clients. config PPPOPNS - bool "PPP on PPTP Network Server" + tristate "PPP on PPTP Network Server" depends on PPP && INET help PPTP (RFC 2637) is a tunneling protocol widely used in virtual private diff --git a/drivers/net/ppp/pppolac.c b/drivers/net/ppp/pppolac.c index 1b8180cc1d4d..0184c96579e9 100644 --- a/drivers/net/ppp/pppolac.c +++ b/drivers/net/ppp/pppolac.c @@ -206,11 +206,10 @@ static void pppolac_xmit_core(struct work_struct *delivery_work) while ((skb = skb_dequeue(&delivery_queue))) { struct sock *sk_udp = skb->sk; struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; - struct msghdr msg = { - .msg_iov = (struct iovec *)&iov, - .msg_iovlen = 1, - .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, - }; + struct msghdr msg = { 0 }; + + iov_iter_kvec(&msg.msg_iter, WRITE | ITER_KVEC, &iov, 1, + skb->len); sk_udp->sk_prot->sendmsg(sk_udp, &msg, skb->len); kfree_skb(skb); } diff --git a/drivers/net/ppp/pppopns.c b/drivers/net/ppp/pppopns.c index 568bb45cfeac..d9e06039794e 100644 --- a/drivers/net/ppp/pppopns.c +++ b/drivers/net/ppp/pppopns.c @@ -189,11 +189,10 @@ static void pppopns_xmit_core(struct work_struct *delivery_work) while ((skb = skb_dequeue(&delivery_queue))) { struct sock *sk_raw = skb->sk; struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; - struct msghdr msg = { - .msg_iov = (struct iovec *)&iov, - .msg_iovlen = 1, - .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, - }; + struct msghdr msg = { 0 }; + + iov_iter_kvec(&msg.msg_iter, WRITE | ITER_KVEC, &iov, 1, + skb->len); sk_raw->sk_prot->sendmsg(sk_raw, &msg, skb->len); kfree_skb(skb); } diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h index 35179c8be471..7acef104d5b7 100644 --- a/drivers/phy/phy-qcom-ufs-i.h +++ b/drivers/phy/phy-qcom-ufs-i.h @@ -152,6 +152,7 @@ struct ufs_qcom_phy { * and writes to QSERDES_RX_SIGDET_CNTRL attribute * @configure_lpm: pointer to a function that configures the phy * for low power mode. + * @dbg_register_dump: pointer to a function that dumps phy registers for debug. */ struct ufs_qcom_phy_specific_ops { int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B); @@ -161,6 +162,7 @@ struct ufs_qcom_phy_specific_ops { void (*ctrl_rx_linecfg)(struct ufs_qcom_phy *phy, bool ctrl); void (*power_control)(struct ufs_qcom_phy *phy, bool val); int (*configure_lpm)(struct ufs_qcom_phy *phy, bool enable); + void (*dbg_register_dump)(struct ufs_qcom_phy *phy); }; struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy); @@ -184,5 +186,6 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, void ufs_qcom_phy_write_tbl(struct ufs_qcom_phy *ufs_qcom_phy, struct ufs_qcom_phy_calibration *tbl, int tbl_size); - +void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, + int offset, int len, char *prefix); #endif diff --git a/drivers/phy/phy-qcom-ufs-qmp-v3.c b/drivers/phy/phy-qcom-ufs-qmp-v3.c index 57c23f70eb63..a9ad3a6f87cc 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-v3.c +++ b/drivers/phy/phy-qcom-ufs-qmp-v3.c @@ -194,6 +194,22 @@ out: return err; } +static void ufs_qcom_phy_qmp_v3_dbg_register_dump(struct ufs_qcom_phy *phy) +{ + ufs_qcom_phy_dump_regs(phy, COM_BASE, COM_SIZE, + "PHY QSERDES COM Registers "); + ufs_qcom_phy_dump_regs(phy, PHY_BASE, PHY_SIZE, + "PHY Registers "); + ufs_qcom_phy_dump_regs(phy, RX_BASE(0), RX_SIZE, + "PHY RX0 Registers "); + ufs_qcom_phy_dump_regs(phy, TX_BASE(0), TX_SIZE, + "PHY TX0 Registers "); + ufs_qcom_phy_dump_regs(phy, RX_BASE(1), RX_SIZE, + "PHY RX1 Registers "); + ufs_qcom_phy_dump_regs(phy, TX_BASE(1), TX_SIZE, + "PHY TX1 Registers "); +} + struct phy_ops ufs_qcom_phy_qmp_v3_phy_ops = { .init = ufs_qcom_phy_qmp_v3_init, .exit = ufs_qcom_phy_exit, @@ -210,6 +226,7 @@ struct ufs_qcom_phy_specific_ops phy_v3_ops = { .ctrl_rx_linecfg = ufs_qcom_phy_qmp_v3_ctrl_rx_linecfg, .power_control = ufs_qcom_phy_qmp_v3_power_control, .configure_lpm = ufs_qcom_phy_qmp_v3_configure_lpm, + .dbg_register_dump = ufs_qcom_phy_qmp_v3_dbg_register_dump, }; static int ufs_qcom_phy_qmp_v3_probe(struct platform_device *pdev) diff --git a/drivers/phy/phy-qcom-ufs-qmp-v3.h b/drivers/phy/phy-qcom-ufs-qmp-v3.h index cda57855acb5..8b1e03eab639 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-v3.h +++ b/drivers/phy/phy-qcom-ufs-qmp-v3.h @@ -18,10 +18,18 @@ #include "phy-qcom-ufs-i.h" /* QCOM UFS PHY control registers */ -#define COM_OFF(x) (0x000 + x) -#define PHY_OFF(x) (0xC00 + x) -#define TX_OFF(n, x) (0x400 + (0x400 * n) + x) -#define RX_OFF(n, x) (0x600 + (0x400 * n) + x) +#define COM_BASE 0x000 +#define COM_SIZE 0x18C +#define PHY_BASE 0xC00 +#define PHY_SIZE 0x1DC +#define TX_BASE(n) (0x400 + (0x400 * n)) +#define TX_SIZE 0x128 +#define RX_BASE(n) (0x600 + (0x400 * n)) +#define RX_SIZE 0x1FC +#define COM_OFF(x) (COM_BASE + x) +#define PHY_OFF(x) (PHY_BASE + x) +#define TX_OFF(n, x) (TX_BASE(n) + x) +#define RX_OFF(n, x) (RX_BASE(n) + x) /* UFS PHY QSERDES COM registers */ #define QSERDES_COM_ATB_SEL1 COM_OFF(0x00) diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c index de32b75f4f57..b2c58430785a 100644 --- a/drivers/phy/phy-qcom-ufs.c +++ b/drivers/phy/phy-qcom-ufs.c @@ -788,3 +788,21 @@ int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable) return ret; } EXPORT_SYMBOL(ufs_qcom_phy_configure_lpm); + +void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, int offset, + int len, char *prefix) +{ + print_hex_dump(KERN_ERR, prefix, + len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE, + 16, 4, phy->mmio + offset, len, false); +} +EXPORT_SYMBOL(ufs_qcom_phy_dump_regs); + +void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + + if (ufs_qcom_phy->phy_spec_ops->dbg_register_dump) + ufs_qcom_phy->phy_spec_ops->dbg_register_dump(ufs_qcom_phy); +} +EXPORT_SYMBOL(ufs_qcom_phy_dbg_register_dump); diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_intf.c b/drivers/platform/msm/ipa/ipa_v2/ipa_intf.c index a1c927c5ec0f..249de808ec5c 100644 --- a/drivers/platform/msm/ipa/ipa_v2/ipa_intf.c +++ b/drivers/platform/msm/ipa/ipa_v2/ipa_intf.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -348,6 +348,11 @@ int ipa_query_intf_ext_props(struct ipa_ioc_query_intf_ext_props *ext) return result; } +static void ipa2_send_msg_free(void *buff, u32 len, u32 type) +{ + kfree(buff); +} + /** * ipa2_send_msg() - Send "message" from kernel client to IPA driver * @meta: [in] message meta-data @@ -367,6 +372,7 @@ int ipa2_send_msg(struct ipa_msg_meta *meta, void *buff, ipa_msg_free_fn callback) { struct ipa_push_msg *msg; + void *data = NULL; if (unlikely(!ipa_ctx)) { IPAERR("IPA driver was not initialized\n"); @@ -392,8 +398,17 @@ int ipa2_send_msg(struct ipa_msg_meta *meta, void *buff, } msg->meta = *meta; - msg->buff = buff; - msg->callback = callback; + if (meta->msg_len > 0 && buff) { + data = kmalloc(meta->msg_len, GFP_KERNEL); + if (data == NULL) { + IPAERR("fail to alloc data container\n"); + kfree(msg); + return -ENOMEM; + } + memcpy(data, buff, meta->msg_len); + msg->buff = data; + msg->callback = ipa2_send_msg_free; + } mutex_lock(&ipa_ctx->msg_lock); list_add_tail(&msg->link, &ipa_ctx->msg_list); @@ -401,6 +416,8 @@ int ipa2_send_msg(struct ipa_msg_meta *meta, void *buff, IPA_STATS_INC_CNT(ipa_ctx->stats.msg_w[meta->msg_type]); wake_up(&ipa_ctx->msg_waitq); + if (buff) + callback(buff, meta->msg_len, meta->msg_type); return 0; } diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_intf.c b/drivers/platform/msm/ipa/ipa_v3/ipa_intf.c index 32c5004dda95..22756c1fb168 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_intf.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_intf.c @@ -358,6 +358,11 @@ int ipa3_query_intf_ext_props(struct ipa_ioc_query_intf_ext_props *ext) return result; } +static void ipa3_send_msg_free(void *buff, u32 len, u32 type) +{ + kfree(buff); +} + /** * ipa3_send_msg() - Send "message" from kernel client to IPA driver * @meta: [in] message meta-data @@ -377,6 +382,7 @@ int ipa3_send_msg(struct ipa_msg_meta *meta, void *buff, ipa_msg_free_fn callback) { struct ipa3_push_msg *msg; + void *data = NULL; if (meta == NULL || (buff == NULL && callback != NULL) || (buff != NULL && callback == NULL)) { @@ -397,8 +403,17 @@ int ipa3_send_msg(struct ipa_msg_meta *meta, void *buff, } msg->meta = *meta; - msg->buff = buff; - msg->callback = callback; + if (meta->msg_len > 0 && buff) { + data = kmalloc(meta->msg_len, GFP_KERNEL); + if (data == NULL) { + IPAERR("fail to alloc data container\n"); + kfree(msg); + return -ENOMEM; + } + memcpy(data, buff, meta->msg_len); + msg->buff = data; + msg->callback = ipa3_send_msg_free; + } mutex_lock(&ipa3_ctx->msg_lock); list_add_tail(&msg->link, &ipa3_ctx->msg_list); @@ -406,6 +421,8 @@ int ipa3_send_msg(struct ipa_msg_meta *meta, void *buff, IPA_STATS_INC_CNT(ipa3_ctx->stats.msg_w[meta->msg_type]); wake_up(&ipa3_ctx->msg_waitq); + if (buff) + callback(buff, meta->msg_len, meta->msg_type); return 0; } diff --git a/drivers/platform/msm/sps/sps_bam.c b/drivers/platform/msm/sps/sps_bam.c index f34242f29e2b..38a6474dd06f 100644 --- a/drivers/platform/msm/sps/sps_bam.c +++ b/drivers/platform/msm/sps/sps_bam.c @@ -1083,6 +1083,7 @@ int sps_bam_pipe_disconnect(struct sps_bam *dev, u32 pipe_index) { struct sps_pipe *pipe; int result; + unsigned long flags; if (pipe_index >= dev->props.num_pipes) { SPS_ERR(dev, "sps:Invalid BAM %pa pipe: %d\n", BAM_ID(dev), @@ -1094,8 +1095,10 @@ int sps_bam_pipe_disconnect(struct sps_bam *dev, u32 pipe_index) pipe = dev->pipes[pipe_index]; if (BAM_PIPE_IS_ASSIGNED(pipe)) { if ((dev->pipe_active_mask & (1UL << pipe_index))) { + spin_lock_irqsave(&dev->isr_lock, flags); list_del(&pipe->list); dev->pipe_active_mask &= ~(1UL << pipe_index); + spin_unlock_irqrestore(&dev->isr_lock, flags); } dev->pipe_remote_mask &= ~(1UL << pipe_index); if (pipe->connect.options & SPS_O_NO_DISABLE) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 0be535194b49..a7dd0bfc66ad 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -279,10 +279,10 @@ static int smb2_parse_dt(struct smb2 *chip) if (rc < 0) chip->dt.dc_icl_ua = -EINVAL; - rc = of_property_read_u32(node, - "qcom,wipower-max-uw", &chip->dt.wipower_max_uw); + rc = of_property_read_u32(node, "qcom,wipower-max-uw", + &chip->dt.wipower_max_uw); if (rc < 0) - chip->dt.wipower_max_uw = SMB2_DEFAULT_WPWR_UW; + chip->dt.wipower_max_uw = -EINVAL; if (of_find_property(node, "qcom,thermal-mitigation", &byte_len)) { chg->thermal_mitigation = devm_kzalloc(chg->dev, byte_len, @@ -588,6 +588,7 @@ static enum power_supply_property smb2_batt_props[] = { POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, POWER_SUPPLY_PROP_CHARGER_TEMP, POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, }; static int smb2_batt_get_prop(struct power_supply *psy, @@ -625,6 +626,9 @@ static int smb2_batt_get_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX: rc = smblib_get_prop_charger_temp_max(chg, val); break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + rc = smblib_get_prop_input_current_limited(chg, val); + break; default: pr_err("batt power supply prop %d not supported\n", psp); return -EINVAL; @@ -862,6 +866,9 @@ static int smb2_config_wipower_input_power(struct smb2 *chip, int uw) struct smb_charger *chg = &chip->chg; s64 nw = (s64)uw * 1000; + if (uw < 0) + return 0; + ua = div_s64(nw, ZIN_ICL_PT_MAX_MV); rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_lv, ua); if (rc < 0) { @@ -1121,7 +1128,7 @@ static struct smb2_irq_info smb2_irqs[] = { { "wdog-bark", NULL }, { "aicl-fail", smblib_handle_debug }, { "aicl-done", smblib_handle_debug }, - { "high-duty-cycle", smblib_handle_debug }, + { "high-duty-cycle", smblib_handle_high_duty_cycle, true }, { "input-current-limiting", smblib_handle_debug }, { "temperature-change", smblib_handle_debug }, { "switcher-power-ok", smblib_handle_debug }, diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index ee4f65430d8b..21b330127369 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -988,6 +988,21 @@ int smblib_get_prop_system_temp_level(struct smb_charger *chg, return 0; } +int smblib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val) +{ + u8 stat; + int rc; + + rc = smblib_read(chg, AICL_STATUS_REG, &stat); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read AICL_STATUS rc=%d\n", rc); + return rc; + } + val->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc; + return 0; +} + /*********************** * BATTERY PSY SETTERS * ***********************/ @@ -1965,6 +1980,17 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) return IRQ_HANDLED; } +irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + chg->is_hdc = true; + schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60)); + + return IRQ_HANDLED; +} + /*************** * Work Queues * ***************/ @@ -2054,6 +2080,14 @@ done: vote(chg->awake_votable, PL_VOTER, false, 0); } +static void clear_hdc_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + clear_hdc_work.work); + + chg->is_hdc = 0; +} + static int smblib_create_votables(struct smb_charger *chg) { int rc = 0; @@ -2194,6 +2228,7 @@ int smblib_init(struct smb_charger *chg) INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work); INIT_DELAYED_WORK(&chg->pl_taper_work, smblib_pl_taper_work); INIT_DELAYED_WORK(&chg->step_soc_req_work, step_soc_req_work); + INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work); chg->fake_capacity = -EINVAL; switch (chg->mode) { diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 1b0c221b4764..aeb1eb2c454f 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -145,6 +145,7 @@ struct smb_charger { struct delayed_work ps_change_timeout_work; struct delayed_work pl_taper_work; struct delayed_work step_soc_req_work; + struct delayed_work clear_hdc_work; /* cached status */ int voltage_min_uv; @@ -159,6 +160,7 @@ struct smb_charger { int fake_capacity; bool step_chg_enabled; + bool is_hdc; }; int smblib_read(struct smb_charger *chg, u16 addr, u8 *val); @@ -202,6 +204,7 @@ irqreturn_t smblib_handle_usb_plugin(int irq, void *data); irqreturn_t smblib_handle_usb_source_change(int irq, void *data); irqreturn_t smblib_handle_icl_change(int irq, void *data); irqreturn_t smblib_handle_usb_typec_change(int irq, void *data); +irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data); int smblib_get_prop_input_suspend(struct smb_charger *chg, union power_supply_propval *val); @@ -217,6 +220,8 @@ int smblib_get_prop_batt_health(struct smb_charger *chg, union power_supply_propval *val); int smblib_get_prop_system_temp_level(struct smb_charger *chg, union power_supply_propval *val); +int smblib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val); int smblib_set_prop_input_suspend(struct smb_charger *chg, const union power_supply_propval *val); diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index d49d8606da15..27a5deb1213e 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -150,7 +150,7 @@ static void regulator_lock_supply(struct regulator_dev *rdev) { int i; - for (i = 0; rdev->supply; rdev = rdev_get_supply(rdev), i++) + for (i = 0; rdev; rdev = rdev_get_supply(rdev), i++) mutex_lock_nested(&rdev->mutex, i); } diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index a3bcfb42ca6a..ad4b6ffef36e 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -349,6 +349,28 @@ out: return err; } +static void ufs_qcom_force_mem_config(struct ufs_hba *hba) +{ + struct ufs_clk_info *clki; + + /* + * Configure the behavior of ufs clocks core and peripheral + * memory state when they are turned off. + * This configuration is required to allow retaining + * ICE crypto configuration (including keys) when + * core_clk_ice is turned off, and powering down + * non-ICE RAMs of host controller. + */ + list_for_each_entry(clki, &hba->clk_list_head, list) { + if (!strcmp(clki->name, "core_clk_ice")) + clk_set_flags(clki->clk, CLKFLAG_RETAIN_MEM); + else + clk_set_flags(clki->clk, CLKFLAG_NORETAIN_MEM); + clk_set_flags(clki->clk, CLKFLAG_NORETAIN_PERIPH); + clk_set_flags(clki->clk, CLKFLAG_PERIPH_OFF_CLEAR); + } +} + static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { @@ -357,6 +379,7 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, switch (status) { case PRE_CHANGE: + ufs_qcom_force_mem_config(hba); ufs_qcom_power_up_sequence(hba); /* * The PHY PLL output is the source of tx/rx lane symbol @@ -741,21 +764,29 @@ out: static int ufs_qcom_full_reset(struct ufs_hba *hba) { - struct ufs_clk_info *clki; int ret = -ENOTSUPP; - list_for_each_entry(clki, &hba->clk_list_head, list) { - if (!strcmp(clki->name, "core_clk")) { - ret = clk_reset(clki->clk, CLK_RESET_ASSERT); - if (ret) - goto out; - /* Very small delay, per the documented requirement */ - usleep_range(1, 2); - - ret = clk_reset(clki->clk, CLK_RESET_DEASSERT); - break; - } + if (!hba->core_reset) { + dev_err(hba->dev, "%s: failed, err = %d\n", __func__, + ret); + goto out; + } + + ret = reset_control_assert(hba->core_reset); + if (ret) { + dev_err(hba->dev, "%s: core_reset assert failed, err = %d\n", + __func__, ret); + goto out; } + + /* Very small delay, per the documented requirement */ + usleep_range(1, 2); + + ret = reset_control_deassert(hba->core_reset); + if (ret) + dev_err(hba->dev, "%s: core_reset deassert failed, err = %d\n", + __func__, ret); + out: return ret; } @@ -2382,17 +2413,21 @@ void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, void *priv, static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host) { - if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) + if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) { + ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, + UFS_REG_TEST_BUS_EN, REG_UFS_CFG1); ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1); - else + } else { + ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, 0, REG_UFS_CFG1); ufshcd_rmwl(host->hba, TEST_BUS_EN, 0, REG_UFS_CFG1); + } } static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host) { /* provide a legal default configuration */ - host->testbus.select_major = TSTBUS_UAWM; - host->testbus.select_minor = 1; + host->testbus.select_major = TSTBUS_UNIPRO; + host->testbus.select_minor = 37; } static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host) @@ -2409,7 +2444,7 @@ static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host) * mappings of select_minor, since there is no harm in * configuring a non-existent select_minor */ - if (host->testbus.select_minor > 0x1F) { + if (host->testbus.select_minor > 0xFF) { dev_err(host->hba->dev, "%s: 0x%05X is not a legal testbus option\n", __func__, host->testbus.select_minor); @@ -2478,7 +2513,8 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host) break; case TSTBUS_UNIPRO: reg = UFS_UNIPRO_CFG; - offset = 1; + offset = 20; + mask = 0xFFF; break; /* * No need for a default case, since @@ -2497,6 +2533,11 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host) (u32)host->testbus.select_minor << offset, reg); ufs_qcom_enable_test_bus(host); + /* + * Make sure the test bus configuration is + * committed before returning. + */ + mb(); ufshcd_release(host->hba, false); pm_runtime_put_sync(host->hba->dev); @@ -2508,15 +2549,44 @@ static void ufs_qcom_testbus_read(struct ufs_hba *hba) ufs_qcom_dump_regs(hba, UFS_TEST_BUS, 1, "UFS_TEST_BUS "); } +static void ufs_qcom_print_unipro_testbus(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + u32 *testbus = NULL; + int i, nminor = 256, testbus_len = nminor * sizeof(u32); + + testbus = kmalloc(testbus_len, GFP_KERNEL); + if (!testbus) + return; + + host->testbus.select_major = TSTBUS_UNIPRO; + for (i = 0; i < nminor; i++) { + host->testbus.select_minor = i; + ufs_qcom_testbus_config(host); + testbus[i] = ufshcd_readl(hba, UFS_TEST_BUS); + } + print_hex_dump(KERN_ERR, "UNIPRO_TEST_BUS ", DUMP_PREFIX_OFFSET, + 16, 4, testbus, testbus_len, false); + kfree(testbus); +} + static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba) { struct ufs_qcom_host *host = ufshcd_get_variant(hba); + struct phy *phy = host->generic_phy; ufs_qcom_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16, "HCI Vendor Specific Registers "); + /* sleep a bit intermittently as we are dumping too much data */ ufs_qcom_print_hw_debug_reg_all(hba, NULL, ufs_qcom_dump_regs_wrapper); + usleep_range(1000, 1100); ufs_qcom_testbus_read(hba); + usleep_range(1000, 1100); + ufs_qcom_print_unipro_testbus(hba); + usleep_range(1000, 1100); + ufs_qcom_phy_dbg_register_dump(phy); + usleep_range(1000, 1100); ufs_qcom_ice_print_regs(host); } diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h index ba36d9883a0f..394de8302fd2 100644 --- a/drivers/scsi/ufs/ufs-qcom.h +++ b/drivers/scsi/ufs/ufs-qcom.h @@ -100,6 +100,7 @@ enum { #define QUNIPRO_SEL UFS_BIT(0) #define TEST_BUS_EN BIT(18) #define TEST_BUS_SEL GENMASK(22, 19) +#define UFS_REG_TEST_BUS_EN BIT(30) /* bit definitions for REG_UFS_CFG2 register */ #define UAWM_HW_CGC_EN (1 << 0) diff --git a/drivers/scsi/ufs/ufshcd-pltfrm.c b/drivers/scsi/ufs/ufshcd-pltfrm.c index 19c378c72083..5a9564326099 100644 --- a/drivers/scsi/ufs/ufshcd-pltfrm.c +++ b/drivers/scsi/ufs/ufshcd-pltfrm.c @@ -40,6 +40,22 @@ #include "ufshcd.h" #include "ufshcd-pltfrm.h" +static int ufshcd_parse_reset_info(struct ufs_hba *hba) +{ + int ret = 0; + + hba->core_reset = devm_reset_control_get(hba->dev, + "core_reset"); + if (IS_ERR(hba->core_reset)) { + ret = PTR_ERR(hba->core_reset); + dev_err(hba->dev, "core_reset unavailable,err = %d\n", + ret); + hba->core_reset = NULL; + } + + return ret; +} + static int ufshcd_parse_clock_info(struct ufs_hba *hba) { int ret = 0; @@ -338,6 +354,13 @@ int ufshcd_pltfrm_init(struct platform_device *pdev, goto dealloc_host; } + err = ufshcd_parse_reset_info(hba); + if (err) { + dev_err(&pdev->dev, "%s: reset parse failed %d\n", + __func__, err); + goto dealloc_host; + } + ufshcd_parse_pm_levels(hba); if (!dev->dma_mask) diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index a49b3c7bc4ef..d478767ad3dd 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -3932,8 +3932,12 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd) ret = (status != PWR_OK) ? status : -1; } out: - if (ret) + if (ret) { ufsdbg_set_err_state(hba); + ufshcd_print_host_state(hba); + ufshcd_print_pwr_info(hba); + ufshcd_print_host_regs(hba); + } ufshcd_save_tstamp_of_last_dme_cmd(hba); spin_lock_irqsave(hba->host->host_lock, flags); @@ -4644,8 +4648,12 @@ link_startup: ret = ufshcd_make_hba_operational(hba); out: - if (ret) + if (ret) { dev_err(hba->dev, "link startup failed %d\n", ret); + ufshcd_print_host_state(hba); + ufshcd_print_pwr_info(hba); + ufshcd_print_host_regs(hba); + } return ret; } diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index b79bebb58dcd..a6298f614a0b 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -55,6 +55,7 @@ #include <linux/clk.h> #include <linux/completion.h> #include <linux/regulator/consumer.h> +#include <linux/reset.h> #include "unipro.h" #include <asm/irq.h> @@ -893,6 +894,7 @@ struct ufs_hba { struct rw_semaphore clk_scaling_lock; + struct reset_control *core_reset; /* If set, don't gate device ref_clk during clock gating */ bool no_ref_clk_gating; diff --git a/drivers/slimbus/slim-msm-ngd.c b/drivers/slimbus/slim-msm-ngd.c index fbde0d318584..9af9ce323bc3 100644 --- a/drivers/slimbus/slim-msm-ngd.c +++ b/drivers/slimbus/slim-msm-ngd.c @@ -228,6 +228,16 @@ static int dsp_domr_notify_cb(struct notifier_block *n, unsigned long code, break; case LOCATOR_UP: reg = _cmd; + if (!reg || reg->total_domains != 1) { + SLIM_WARN(dev, "error locating audio-PD\n"); + if (reg) + SLIM_WARN(dev, "audio-PDs matched:%d\n", + reg->total_domains); + + /* Fall back to SSR */ + ngd_reg_ssr(dev); + return NOTIFY_DONE; + } dev->dsp.domr = service_notif_register_notifier( reg->domain_list->name, reg->domain_list->instance_id, @@ -1493,8 +1503,7 @@ static int ngd_slim_rx_msgq_thread(void *data) int retries = 0; u8 wbuf[8]; - set_current_state(TASK_INTERRUPTIBLE); - wait_for_completion(notify); + wait_for_completion_interruptible(notify); txn.dt = SLIM_MSG_DEST_LOGICALADDR; txn.ec = 0; @@ -1555,8 +1564,7 @@ static int ngd_notify_slaves(void *data) } while (!kthread_should_stop()) { - set_current_state(TASK_INTERRUPTIBLE); - wait_for_completion(&dev->qmi.slave_notify); + wait_for_completion_interruptible(&dev->qmi.slave_notify); /* Probe devices for first notification */ if (!i) { i++; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index f884f829b51c..9c27344165be 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -127,6 +127,17 @@ config MSM_SPCOM spcom provides clients/server API, although currently only one client or server is allowed per logical channel. +config MSM_SPSS_UTILS + depends on MSM_PIL + bool "Secure Processor Utilities" + help + spss-utils driver selects Secure Processor firmware file name. + The firmware file name for test or production is selected based + on a test fuse. + Different file name is used for differnt SPSS HW versions, + because the SPSS firmware size is too small to support multiple + HW versions. + config MSM_SMEM_LOGGING depends on MSM_SMEM bool "MSM Shared Memory Logger" diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index fb8437d7a7c3..d9134a558be6 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_MSM_IPC_ROUTER_HSIC_XPRT) += ipc_router_hsic_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_MHI_XPRT) += ipc_router_mhi_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_GLINK_XPRT) += ipc_router_glink_xprt.o obj-$(CONFIG_MSM_SPCOM) += spcom.o +obj-$(CONFIG_MSM_SPSS_UTILS) += spss_utils.o obj-y += qdsp6v2/ obj-$(CONFIG_MSM_SYSTEM_HEALTH_MONITOR) += system_health_monitor_v01.o obj-$(CONFIG_MSM_SYSTEM_HEALTH_MONITOR) += system_health_monitor.o diff --git a/drivers/soc/qcom/glink.c b/drivers/soc/qcom/glink.c index f00570aa5fe8..5612075ba60c 100644 --- a/drivers/soc/qcom/glink.c +++ b/drivers/soc/qcom/glink.c @@ -5376,7 +5376,7 @@ static int glink_scheduler_tx(struct channel_ctx *ctx, struct glink_core_xprt_ctx *xprt_ctx) { unsigned long flags; - struct glink_core_tx_pkt *tx_info; + struct glink_core_tx_pkt *tx_info, *temp_tx_info; size_t txd_len = 0; size_t tx_len = 0; uint32_t num_pkts = 0; @@ -5411,6 +5411,20 @@ static int glink_scheduler_tx(struct channel_ctx *ctx, ctx->lcid, tx_info); } spin_lock_irqsave(&ctx->tx_lists_lock_lhc3, flags); + if (!list_empty(&ctx->tx_active)) { + /* + * Verify if same tx_info still exist in tx_active + * list and is not removed during tx operation. + * It can happen if SSR and tx done both happen + * before tx_lists_lock_lhc3 is taken. + */ + temp_tx_info = list_first_entry(&ctx->tx_active, + struct glink_core_tx_pkt, list_node); + if (temp_tx_info != tx_info) + continue; + } else { + break; + } if (ret == -EAGAIN) { /* * transport unable to send at the moment and will call @@ -5437,6 +5451,7 @@ static int glink_scheduler_tx(struct channel_ctx *ctx, * Break out of the loop so that the scheduler can * continue with the next channel. */ + rwref_put(&tx_info->pkt_ref); break; } else { txd_len += tx_len; @@ -5445,8 +5460,8 @@ static int glink_scheduler_tx(struct channel_ctx *ctx, if (!tx_info->size_remaining) { num_pkts++; list_del_init(&tx_info->list_node); - rwref_put(&tx_info->pkt_ref); } + rwref_put(&tx_info->pkt_ref); } ctx->txd_len += txd_len; @@ -5495,6 +5510,7 @@ static void tx_func(struct kthread_work *work) glink_pm_qos_vote(xprt_ptr); ch_ptr = list_first_entry(&xprt_ptr->prio_bin[prio].tx_ready, struct channel_ctx, tx_ready_list_node); + rwref_get(&ch_ptr->ch_state_lhb2); spin_unlock_irqrestore(&xprt_ptr->tx_ready_lock_lhb3, flags); if (tx_ready_head == NULL || tx_ready_head_prio < prio) { @@ -5506,6 +5522,7 @@ static void tx_func(struct kthread_work *work) GLINK_ERR_XPRT(xprt_ptr, "%s: Unable to send data on this transport.\n", __func__); + rwref_put(&ch_ptr->ch_state_lhb2); break; } transmitted_successfully = false; @@ -5516,6 +5533,7 @@ static void tx_func(struct kthread_work *work) * transport unable to send at the moment and will call * tx_resume() when it can send again. */ + rwref_put(&ch_ptr->ch_state_lhb2); break; } else if (ret < 0) { /* @@ -5528,6 +5546,7 @@ static void tx_func(struct kthread_work *work) GLINK_ERR_XPRT(xprt_ptr, "%s: unrecoverable xprt failure %d\n", __func__, ret); + rwref_put(&ch_ptr->ch_state_lhb2); break; } else if (!ret) { /* @@ -5539,6 +5558,7 @@ static void tx_func(struct kthread_work *work) list_rotate_left(&xprt_ptr->prio_bin[prio].tx_ready); spin_unlock_irqrestore(&xprt_ptr->tx_ready_lock_lhb3, flags); + rwref_put(&ch_ptr->ch_state_lhb2); continue; } @@ -5556,6 +5576,7 @@ static void tx_func(struct kthread_work *work) tx_ready_head = NULL; transmitted_successfully = true; + rwref_put(&ch_ptr->ch_state_lhb2); } glink_pm_qos_unvote(xprt_ptr); GLINK_PERF("%s: worker exiting\n", __func__); diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c index eb55579c19f4..05a9ff4aeb1c 100644 --- a/drivers/soc/qcom/icnss.c +++ b/drivers/soc/qcom/icnss.c @@ -34,6 +34,8 @@ #include <linux/qmi_encdec.h> #include <linux/ipc_logging.h> #include <linux/msm-bus.h> +#include <linux/uaccess.h> +#include <linux/qpnp/qpnp-adc.h> #include <soc/qcom/memory_dump.h> #include <soc/qcom/icnss.h> #include <soc/qcom/msm_qmi_interface.h> @@ -159,6 +161,10 @@ #define ICNSS_SERVICE_LOCATION_CLIENT_NAME "ICNSS-WLAN" #define ICNSS_WLAN_SERVICE_NAME "wlan/fw" +#define ICNSS_THRESHOLD_HIGH 3600000 +#define ICNSS_THRESHOLD_LOW 3450000 +#define ICNSS_THRESHOLD_GUARD 20000 + #define icnss_ipc_log_string(_x...) do { \ if (icnss_ipc_log_context) \ ipc_log_string(icnss_ipc_log_context, _x); \ @@ -359,6 +365,9 @@ struct icnss_stats { uint32_t ini_req; uint32_t ini_resp; uint32_t ini_req_err; + uint32_t vbatt_req; + uint32_t vbatt_resp; + uint32_t vbatt_req_err; }; static struct icnss_priv { @@ -410,6 +419,14 @@ static struct icnss_priv { void *modem_notify_handler; struct notifier_block modem_ssr_nb; struct wakeup_source ws; + uint32_t diag_reg_read_addr; + uint32_t diag_reg_read_mem_type; + uint32_t diag_reg_read_len; + uint8_t *diag_reg_read_buf; + struct qpnp_adc_tm_btm_param vph_monitor_params; + struct qpnp_adc_tm_chip *adc_tm_dev; + struct qpnp_vadc_chip *vadc_dev; + uint64_t vph_pwr; } *penv; static void icnss_hw_write_reg(void *base, u32 offset, u32 val) @@ -559,6 +576,189 @@ static int icnss_driver_event_post(enum icnss_driver_event_type type, return ret; } +static int wlfw_vbatt_send_sync_msg(struct icnss_priv *priv, + uint64_t voltage_uv) +{ + int ret; + struct wlfw_vbatt_req_msg_v01 req; + struct wlfw_vbatt_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + + if (!priv->wlfw_clnt) { + ret = -ENODEV; + goto out; + } + + icnss_pr_dbg("Sending Vbatt message, state: 0x%lx\n", + penv->state); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.voltage_uv = voltage_uv; + + req_desc.max_msg_len = WLFW_VBATT_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_VBATT_REQ_V01; + req_desc.ei_array = wlfw_vbatt_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_VBATT_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_VBATT_RESP_V01; + resp_desc.ei_array = wlfw_vbatt_resp_msg_v01_ei; + + priv->stats.vbatt_req++; + + ret = qmi_send_req_wait(priv->wlfw_clnt, &req_desc, &req, sizeof(req), + &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); + if (ret < 0) { + icnss_pr_err("Send vbatt req failed %d\n", ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI vbatt request failed %d %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + priv->stats.vbatt_resp++; + +out: + priv->stats.vbatt_req_err++; + return ret; +} + +static int icnss_get_phone_power(struct icnss_priv *priv, uint64_t *result_uv) +{ + int ret = 0; + struct qpnp_vadc_result adc_result; + + if (!priv->vadc_dev) { + icnss_pr_err("VADC dev doesn't exists\n"); + ret = -EINVAL; + goto out; + } + + ret = qpnp_vadc_read(penv->vadc_dev, VADC_VPH_PWR, &adc_result); + if (ret) { + icnss_pr_err("Error reading ADC channel %d, ret = %d\n", + VADC_VPH_PWR, ret); + goto out; + } + + icnss_pr_dbg("Phone power read phy=%lld meas=0x%llx\n", + adc_result.physical, adc_result.measurement); + + *result_uv = adc_result.physical; +out: + return ret; +} + +static void icnss_vph_notify(enum qpnp_tm_state state, void *ctx) +{ + struct icnss_priv *priv = ctx; + uint64_t vph_pwr = 0; + uint64_t vph_pwr_prev; + int ret = 0; + bool update = true; + + if (!priv) { + icnss_pr_err("Priv pointer is NULL\n"); + return; + } + + vph_pwr_prev = priv->vph_pwr; + + ret = icnss_get_phone_power(priv, &vph_pwr); + if (ret) + return; + + if (vph_pwr < ICNSS_THRESHOLD_LOW) { + if (vph_pwr_prev < ICNSS_THRESHOLD_LOW) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_HIGH_THR_ENABLE; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_LOW + + ICNSS_THRESHOLD_GUARD; + priv->vph_monitor_params.low_thr = 0; + } else if (vph_pwr > ICNSS_THRESHOLD_HIGH) { + if (vph_pwr_prev > ICNSS_THRESHOLD_HIGH) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_LOW_THR_ENABLE; + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_HIGH - + ICNSS_THRESHOLD_GUARD; + priv->vph_monitor_params.high_thr = 0; + } else { + if (vph_pwr_prev > ICNSS_THRESHOLD_LOW && + vph_pwr_prev < ICNSS_THRESHOLD_HIGH) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_HIGH_LOW_THR_ENABLE; + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_LOW; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_HIGH; + } + + priv->vph_pwr = vph_pwr; + + if (update) + wlfw_vbatt_send_sync_msg(priv, vph_pwr); + + icnss_pr_dbg("set low threshold to %d, high threshold to %d\n", + priv->vph_monitor_params.low_thr, + priv->vph_monitor_params.high_thr); + ret = qpnp_adc_tm_channel_measure(priv->adc_tm_dev, + &priv->vph_monitor_params); + if (ret) + icnss_pr_err("TM channel setup failed %d\n", ret); +} + +static int icnss_setup_vph_monitor(struct icnss_priv *priv) +{ + int ret = 0; + + if (!priv->adc_tm_dev) { + icnss_pr_err("ADC TM handler is NULL\n"); + ret = -EINVAL; + goto out; + } + + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_LOW; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_HIGH; + priv->vph_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + priv->vph_monitor_params.channel = VADC_VPH_PWR; + priv->vph_monitor_params.btm_ctx = priv; + priv->vph_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S; + priv->vph_monitor_params.threshold_notification = &icnss_vph_notify; + icnss_pr_dbg("Set low threshold to %d, high threshold to %d\n", + priv->vph_monitor_params.low_thr, + priv->vph_monitor_params.high_thr); + + ret = qpnp_adc_tm_channel_measure(priv->adc_tm_dev, + &priv->vph_monitor_params); + if (ret) + icnss_pr_err("TM channel setup failed %d\n", ret); +out: + return ret; +} + +static int icnss_init_vph_monitor(struct icnss_priv *priv) +{ + int ret = 0; + + ret = icnss_get_phone_power(priv, &priv->vph_pwr); + if (ret) + goto out; + + wlfw_vbatt_send_sync_msg(priv, priv->vph_pwr); + + ret = icnss_setup_vph_monitor(priv); + if (ret) + goto out; +out: + return ret; +} + + static int icnss_qmi_pin_connect_result_ind(void *msg, unsigned int msg_len) { struct msg_desc ind_desc; @@ -1790,6 +1990,127 @@ out: return ret; } +static int wlfw_athdiag_read_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data) +{ + int ret; + struct wlfw_athdiag_read_req_msg_v01 req; + struct wlfw_athdiag_read_resp_msg_v01 *resp = NULL; + struct msg_desc req_desc, resp_desc; + + if (!priv->wlfw_clnt) { + ret = -ENODEV; + goto out; + } + + icnss_pr_dbg("Diag read: state 0x%lx, offset %x, mem_type %x, data_len %u\n", + priv->state, offset, mem_type, data_len); + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + ret = -ENOMEM; + goto out; + } + memset(&req, 0, sizeof(req)); + + req.offset = offset; + req.mem_type = mem_type; + req.data_len = data_len; + + req_desc.max_msg_len = WLFW_ATHDIAG_READ_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_ATHDIAG_READ_REQ_V01; + req_desc.ei_array = wlfw_athdiag_read_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_ATHDIAG_READ_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_ATHDIAG_READ_RESP_V01; + resp_desc.ei_array = wlfw_athdiag_read_resp_msg_v01_ei; + + ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), + &resp_desc, resp, sizeof(*resp), + WLFW_TIMEOUT_MS); + if (ret < 0) { + icnss_pr_err("send athdiag read req failed %d\n", ret); + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI athdiag read request failed %d %d\n", + resp->resp.result, resp->resp.error); + ret = resp->resp.result; + goto out; + } + + if (!resp->data_valid || resp->data_len <= data_len) { + icnss_pr_err("Athdiag read data is invalid, data_valid = %u, data_len = %u\n", + resp->data_valid, resp->data_len); + ret = -EINVAL; + goto out; + } + + memcpy(data, resp->data, resp->data_len); + +out: + kfree(resp); + return ret; +} + +static int wlfw_athdiag_write_send_sync_msg(struct icnss_priv *priv, + uint32_t offset, uint32_t mem_type, + uint32_t data_len, uint8_t *data) +{ + int ret; + struct wlfw_athdiag_write_req_msg_v01 *req = NULL; + struct wlfw_athdiag_write_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + + if (!priv->wlfw_clnt) { + ret = -ENODEV; + goto out; + } + + icnss_pr_dbg("Diag write: state 0x%lx, offset %x, mem_type %x, data_len %u, data %p\n", + priv->state, offset, mem_type, data_len, data); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out; + } + memset(&resp, 0, sizeof(resp)); + + req->offset = offset; + req->mem_type = mem_type; + req->data_len = data_len; + memcpy(req->data, data, data_len); + + req_desc.max_msg_len = WLFW_ATHDIAG_WRITE_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_ATHDIAG_WRITE_REQ_V01; + req_desc.ei_array = wlfw_athdiag_write_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_ATHDIAG_WRITE_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_ATHDIAG_WRITE_RESP_V01; + resp_desc.ei_array = wlfw_athdiag_write_resp_msg_v01_ei; + + ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, req, sizeof(*req), + &resp_desc, &resp, sizeof(resp), + WLFW_TIMEOUT_MS); + if (ret < 0) { + icnss_pr_err("send athdiag write req failed %d\n", ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI athdiag write request failed %d %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } +out: + kfree(req); + return ret; +} + static void icnss_qmi_wlfw_clnt_notify_work(struct work_struct *work) { int ret; @@ -1920,6 +2241,8 @@ static int icnss_driver_event_server_arrive(void *data) if (ret < 0) goto err_setup_msa; + icnss_init_vph_monitor(penv); + return ret; err_setup_msa: @@ -1941,6 +2264,10 @@ static int icnss_driver_event_server_exit(void *data) icnss_pr_info("QMI Service Disconnected: 0x%lx\n", penv->state); + if (penv->adc_tm_dev) + qpnp_adc_tm_disable_chan_meas(penv->adc_tm_dev, + &penv->vph_monitor_params); + qmi_handle_destroy(penv->wlfw_clnt); clear_bit(ICNSS_WLFW_QMI_CONNECTED, &penv->state); @@ -2690,6 +3017,78 @@ int icnss_set_fw_debug_mode(bool enable_fw_log) } EXPORT_SYMBOL(icnss_set_fw_debug_mode); +int icnss_athdiag_read(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *output) +{ + int ret = 0; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for diag read: dev %p, data %p, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + if (!output || data_len == 0 + || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) { + icnss_pr_err("Invalid parameters for diag read: output %p, data_len %u\n", + output, data_len); + ret = -EINVAL; + goto out; + } + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) { + icnss_pr_err("Invalid state for diag read: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + ret = wlfw_athdiag_read_send_sync_msg(priv, offset, mem_type, + data_len, output); +out: + return ret; +} +EXPORT_SYMBOL(icnss_athdiag_read); + +int icnss_athdiag_write(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *input) +{ + int ret = 0; + struct icnss_priv *priv = dev_get_drvdata(dev); + + if (priv->magic != ICNSS_MAGIC) { + icnss_pr_err("Invalid drvdata for diag write: dev %p, data %p, magic 0x%x\n", + dev, priv, priv->magic); + return -EINVAL; + } + + if (!input || data_len == 0 + || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) { + icnss_pr_err("Invalid parameters for diag write: input %p, data_len %u\n", + input, data_len); + ret = -EINVAL; + goto out; + } + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) { + icnss_pr_err("Invalid state for diag write: 0x%lx\n", + priv->state); + ret = -EINVAL; + goto out; + } + + ret = wlfw_athdiag_write_send_sync_msg(priv, offset, mem_type, + data_len, input); +out: + return ret; +} +EXPORT_SYMBOL(icnss_athdiag_write); + int icnss_wlan_enable(struct icnss_wlan_enable_cfg *config, enum icnss_driver_mode mode, const char *host_version) @@ -3420,6 +3819,9 @@ static int icnss_stats_show(struct seq_file *s, void *data) ICNSS_STATS_DUMP(s, priv, ini_req); ICNSS_STATS_DUMP(s, priv, ini_resp); ICNSS_STATS_DUMP(s, priv, ini_req_err); + ICNSS_STATS_DUMP(s, priv, vbatt_req); + ICNSS_STATS_DUMP(s, priv, vbatt_resp); + ICNSS_STATS_DUMP(s, priv, vbatt_req_err); seq_puts(s, "\n<------------------ PM stats ------------------->\n"); ICNSS_STATS_DUMP(s, priv, pm_suspend); @@ -3457,6 +3859,207 @@ static const struct file_operations icnss_stats_fops = { .llseek = seq_lseek, }; +static int icnss_regwrite_show(struct seq_file *s, void *data) +{ + struct icnss_priv *priv = s->private; + + seq_puts(s, "\nUsage: echo <mem_type> <offset> <reg_val> > <debugfs>/icnss/reg_write\n"); + + if (!test_bit(ICNSS_FW_READY, &priv->state)) + seq_puts(s, "Firmware is not ready yet!, wait for FW READY\n"); + + return 0; +} + +static ssize_t icnss_regwrite_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *sptr, *token; + unsigned int len = 0; + uint32_t reg_offset, mem_type, reg_val; + const char *delim = " "; + int ret = 0; + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) + return -EINVAL; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, &mem_type)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_offset)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, ®_val)) + return -EINVAL; + + ret = wlfw_athdiag_write_send_sync_msg(priv, reg_offset, mem_type, + sizeof(uint32_t), + (uint8_t *)®_val); + if (ret) + return ret; + + return count; +} + +static int icnss_regwrite_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_regwrite_show, inode->i_private); +} + +static const struct file_operations icnss_regwrite_fops = { + .read = seq_read, + .write = icnss_regwrite_write, + .open = icnss_regwrite_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int icnss_regread_show(struct seq_file *s, void *data) +{ + struct icnss_priv *priv = s->private; + + if (!priv->diag_reg_read_buf) { + seq_puts(s, "Usage: echo <mem_type> <offset> <data_len> > <debugfs>/icnss/reg_read\n"); + + if (!test_bit(ICNSS_FW_READY, &priv->state)) + seq_puts(s, "Firmware is not ready yet!, wait for FW READY\n"); + + return 0; + } + + seq_printf(s, "REGREAD: Addr 0x%x Type 0x%x Length 0x%x\n", + priv->diag_reg_read_addr, priv->diag_reg_read_mem_type, + priv->diag_reg_read_len); + + seq_hex_dump(s, "", DUMP_PREFIX_OFFSET, 32, 4, priv->diag_reg_read_buf, + priv->diag_reg_read_len, false); + + priv->diag_reg_read_len = 0; + kfree(priv->diag_reg_read_buf); + priv->diag_reg_read_buf = NULL; + + return 0; +} + +static ssize_t icnss_regread_write(struct file *fp, const char __user *user_buf, + size_t count, loff_t *off) +{ + struct icnss_priv *priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *sptr, *token; + unsigned int len = 0; + uint32_t reg_offset, mem_type; + uint32_t data_len = 0; + uint8_t *reg_buf = NULL; + const char *delim = " "; + int ret = 0; + + if (!test_bit(ICNSS_FW_READY, &priv->state) || + !test_bit(ICNSS_POWER_ON, &priv->state)) + return -EINVAL; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, &mem_type)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_offset)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, &data_len)) + return -EINVAL; + + if (data_len == 0 || + data_len > QMI_WLFW_MAX_DATA_SIZE_V01) + return -EINVAL; + + reg_buf = kzalloc(data_len, GFP_KERNEL); + if (!reg_buf) + return -ENOMEM; + + ret = wlfw_athdiag_read_send_sync_msg(priv, reg_offset, + mem_type, data_len, + reg_buf); + if (ret) { + kfree(reg_buf); + return ret; + } + + priv->diag_reg_read_addr = reg_offset; + priv->diag_reg_read_mem_type = mem_type; + priv->diag_reg_read_len = data_len; + priv->diag_reg_read_buf = reg_buf; + + return count; +} + +static int icnss_regread_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_regread_show, inode->i_private); +} + +static const struct file_operations icnss_regread_fops = { + .read = seq_read, + .write = icnss_regread_write, + .open = icnss_regread_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + static int icnss_debugfs_create(struct icnss_priv *priv) { int ret = 0; @@ -3477,6 +4080,10 @@ static int icnss_debugfs_create(struct icnss_priv *priv) debugfs_create_file("stats", 0644, root_dentry, priv, &icnss_stats_fops); + debugfs_create_file("reg_read", 0600, root_dentry, priv, + &icnss_regread_fops); + debugfs_create_file("reg_write", 0644, root_dentry, priv, + &icnss_regwrite_fops); out: return ret; @@ -3487,6 +4094,44 @@ static void icnss_debugfs_destroy(struct icnss_priv *priv) debugfs_remove_recursive(priv->root_dentry); } +static int icnss_get_vbatt_info(struct icnss_priv *priv) +{ + struct qpnp_adc_tm_chip *adc_tm_dev = NULL; + struct qpnp_vadc_chip *vadc_dev = NULL; + int ret = 0; + + adc_tm_dev = qpnp_get_adc_tm(&priv->pdev->dev, "icnss"); + if (PTR_ERR(adc_tm_dev) == -EPROBE_DEFER) { + icnss_pr_err("adc_tm_dev probe defer\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(adc_tm_dev)) { + ret = PTR_ERR(adc_tm_dev); + icnss_pr_err("Not able to get ADC dev, VBATT monitoring is disabled: %d\n", + ret); + return ret; + } + + vadc_dev = qpnp_get_vadc(&priv->pdev->dev, "icnss"); + if (PTR_ERR(vadc_dev) == -EPROBE_DEFER) { + icnss_pr_err("vadc_dev probe defer\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(vadc_dev)) { + ret = PTR_ERR(vadc_dev); + icnss_pr_err("Not able to get VADC dev, VBATT monitoring is disabled: %d\n", + ret); + return ret; + } + + priv->adc_tm_dev = adc_tm_dev; + priv->vadc_dev = vadc_dev; + + return 0; +} + static int icnss_probe(struct platform_device *pdev) { int ret = 0; @@ -3511,6 +4156,10 @@ static int icnss_probe(struct platform_device *pdev) priv->pdev = pdev; + ret = icnss_get_vbatt_info(priv); + if (ret == -EPROBE_DEFER) + goto out; + memcpy(priv->vreg_info, icnss_vreg_info, sizeof(icnss_vreg_info)); for (i = 0; i < ICNSS_VREG_INFO_SIZE; i++) { ret = icnss_get_vreg_info(dev, &priv->vreg_info[i]); diff --git a/drivers/soc/qcom/memshare/msm_memshare.c b/drivers/soc/qcom/memshare/msm_memshare.c index f44f6f97ecdd..e1e91f56526d 100644 --- a/drivers/soc/qcom/memshare/msm_memshare.c +++ b/drivers/soc/qcom/memshare/msm_memshare.c @@ -203,6 +203,7 @@ static int modem_notifier_cb(struct notifier_block *this, unsigned long code, int dest_vmids[1] = {VMID_HLOS}; int dest_perms[1] = {PERM_READ|PERM_WRITE}; + mutex_lock(&memsh_drv->mem_share); switch (code) { case SUBSYS_BEFORE_SHUTDOWN: @@ -264,6 +265,7 @@ static int modem_notifier_cb(struct notifier_block *this, unsigned long code, break; } + mutex_unlock(&memsh_drv->mem_share); return NOTIFY_DONE; } diff --git a/drivers/soc/qcom/spss_utils.c b/drivers/soc/qcom/spss_utils.c new file mode 100644 index 000000000000..9f799dfd9003 --- /dev/null +++ b/drivers/soc/qcom/spss_utils.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Secure-Processor-SubSystem (SPSS) utilities. + * + * This driver provides utilities for the Secure Processor (SP). + * + * The SP daemon needs to load different SPSS images based on: + * + * 1. Test/Production key used to sign the SPSS image (read fuse). + * 2. SPSS HW version (selected via Device Tree). + * + */ + +#define pr_fmt(fmt) "spss_utils [%s]: " fmt, __func__ + +#include <linux/kernel.h> /* min() */ +#include <linux/module.h> /* MODULE_LICENSE */ +#include <linux/device.h> /* class_create() */ +#include <linux/slab.h> /* kzalloc() */ +#include <linux/fs.h> /* file_operations */ +#include <linux/cdev.h> /* cdev_add() */ +#include <linux/errno.h> /* EINVAL, ETIMEDOUT */ +#include <linux/printk.h> /* pr_err() */ +#include <linux/bitops.h> /* BIT(x) */ +#include <linux/platform_device.h> /* platform_driver_register() */ +#include <linux/of.h> /* of_property_count_strings() */ +#include <linux/io.h> /* ioremap_nocache() */ + +#include <soc/qcom/subsystem_restart.h> + +/* driver name */ +#define DEVICE_NAME "spss-utils" + +static bool is_test_fuse_set; +static const char *test_firmware_name; +static const char *prod_firmware_name; +static const char *firmware_name; +static struct device *spss_dev; + +/*==========================================================================*/ +/* Device Sysfs */ +/*==========================================================================*/ + +static ssize_t firmware_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + + if (!dev || !attr || !buf) { + pr_err("invalid param.\n"); + return -EINVAL; + } + + if (firmware_name == NULL) + ret = snprintf(buf, PAGE_SIZE, "%s\n", "unknown"); + else + ret = snprintf(buf, PAGE_SIZE, "%s\n", firmware_name); + + return ret; +} + +static ssize_t firmware_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + pr_err("set firmware name is not allowed.\n"); + + return -EINVAL; +} + +static DEVICE_ATTR(firmware_name, 0444, + firmware_name_show, firmware_name_store); + +static ssize_t test_fuse_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + + if (!dev || !attr || !buf) { + pr_err("invalid param.\n"); + return -EINVAL; + } + + if (is_test_fuse_set) + ret = snprintf(buf, PAGE_SIZE, "%s\n", "test"); + else + ret = snprintf(buf, PAGE_SIZE, "%s\n", "prod"); + + return ret; +} + +static ssize_t test_fuse_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + pr_err("set test fuse state is not allowed.\n"); + + return -EINVAL; +} + +static DEVICE_ATTR(test_fuse_state, 0444, + test_fuse_state_show, test_fuse_state_store); + +static int spss_create_sysfs(struct device *dev) +{ + int ret; + + ret = device_create_file(dev, &dev_attr_firmware_name); + if (ret < 0) { + pr_err("failed to create sysfs file for firmware_name.\n"); + return ret; + } + + ret = device_create_file(dev, &dev_attr_test_fuse_state); + if (ret < 0) { + pr_err("failed to create sysfs file for test_fuse_state.\n"); + return ret; + } + + return 0; +} + +/*==========================================================================*/ +/* Device Tree */ +/*==========================================================================*/ + +/** + * spss_parse_dt() - Parse Device Tree info. + */ +static int spss_parse_dt(struct device_node *node) +{ + int ret; + u32 spss_fuse_addr = 0; + u32 spss_fuse_bit = 0; + u32 spss_fuse_mask = 0; + void __iomem *spss_fuse_reg = NULL; + u32 val = 0; + + ret = of_property_read_string(node, "qcom,spss-test-firmware-name", + &test_firmware_name); + if (ret < 0) { + pr_err("can't get test fw name.\n"); + return -EFAULT; + } + + ret = of_property_read_string(node, "qcom,spss-prod-firmware-name", + &prod_firmware_name); + if (ret < 0) { + pr_err("can't get prod fw name.\n"); + return -EFAULT; + } + + ret = of_property_read_u32(node, "qcom,spss-fuse-addr", + &spss_fuse_addr); + if (ret < 0) { + pr_err("can't get fuse addr.\n"); + return -EFAULT; + } + + ret = of_property_read_u32(node, "qcom,spss-fuse-bit", + &spss_fuse_bit); + if (ret < 0) { + pr_err("can't get fuse bit.\n"); + return -EFAULT; + } + + spss_fuse_mask = BIT(spss_fuse_bit); + + pr_debug("spss_fuse_addr [0x%x] , spss_fuse_bit [%d] .\n", + (int) spss_fuse_addr, (int) spss_fuse_bit); + + spss_fuse_reg = ioremap_nocache(spss_fuse_addr, sizeof(u32)); + + if (!spss_fuse_reg) { + pr_err("can't map fuse addr.\n"); + return -EFAULT; + } + + val = readl_relaxed(spss_fuse_reg); + + pr_debug("spss fuse register value [0x%x].\n", (int) val); + + if (val & spss_fuse_mask) + is_test_fuse_set = true; + + iounmap(spss_fuse_reg); + + return 0; +} + +/** + * spss_probe() - initialization sequence + */ +static int spss_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device_node *np = NULL; + struct device *dev = NULL; + + if (!pdev) { + pr_err("invalid pdev.\n"); + return -ENODEV; + } + + np = pdev->dev.of_node; + if (!np) { + pr_err("invalid DT node.\n"); + return -EINVAL; + } + + dev = &pdev->dev; + spss_dev = dev; + + if (dev == NULL) { + pr_err("invalid dev.\n"); + return -EINVAL; + } + + platform_set_drvdata(pdev, dev); + + ret = spss_parse_dt(np); + if (ret < 0) { + pr_err("fail to parse device tree.\n"); + return -EFAULT; + } + + if (is_test_fuse_set) + firmware_name = test_firmware_name; + else + firmware_name = prod_firmware_name; + + ret = subsystem_set_fwname("spss", firmware_name); + if (ret < 0) { + pr_err("fail to set fw name.\n"); + return -EFAULT; + } + + spss_create_sysfs(dev); + + pr_info("Initialization completed ok, firmware_name [%s].\n", + firmware_name); + + return 0; +} + +static const struct of_device_id spss_match_table[] = { + { .compatible = "qcom,spss-utils", }, + { }, +}; + +static struct platform_driver spss_driver = { + .probe = spss_probe, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(spss_match_table), + }, +}; + +/*==========================================================================*/ +/* Driver Init/Exit */ +/*==========================================================================*/ +static int __init spss_init(void) +{ + int ret = 0; + + pr_info("spss-utils driver Ver 1.0 12-Sep-2016.\n"); + + ret = platform_driver_register(&spss_driver); + if (ret) + pr_err("register platform driver failed, ret [%d]\n", ret); + + return 0; +} +late_initcall(spss_init); /* start after PIL driver */ + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Secure Processor Utilities"); diff --git a/drivers/usb/gadget/function/f_accessory.c b/drivers/usb/gadget/function/f_accessory.c index 3908bc151c2b..2f6f393ba7c9 100644 --- a/drivers/usb/gadget/function/f_accessory.c +++ b/drivers/usb/gadget/function/f_accessory.c @@ -448,12 +448,19 @@ static void acc_hid_close(struct hid_device *hid) { } +static int acc_hid_raw_request(struct hid_device *hid, unsigned char reportnum, + __u8 *buf, size_t len, unsigned char rtype, int reqtype) +{ + return 0; +} + static struct hid_ll_driver acc_hid_ll_driver = { .parse = acc_hid_parse, .start = acc_hid_start, .stop = acc_hid_stop, .open = acc_hid_open, .close = acc_hid_close, + .raw_request = acc_hid_raw_request, }; static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, diff --git a/drivers/usb/gadget/function/f_audio_source.c b/drivers/usb/gadget/function/f_audio_source.c index 39645be93502..bcd817439dbf 100644 --- a/drivers/usb/gadget/function/f_audio_source.c +++ b/drivers/usb/gadget/function/f_audio_source.c @@ -583,6 +583,11 @@ static void audio_disable(struct usb_function *f) usb_ep_disable(audio->in_ep); } +static void audio_free_func(struct usb_function *f) +{ + /* no-op */ +} + /*-------------------------------------------------------------------------*/ static void audio_build_desc(struct audio_dev *audio) @@ -827,6 +832,7 @@ static struct audio_dev _audio_dev = { .set_alt = audio_set_alt, .setup = audio_setup, .disable = audio_disable, + .free_func = audio_free_func, }, .lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock), .idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs), diff --git a/drivers/usb/gadget/function/f_gsi.c b/drivers/usb/gadget/function/f_gsi.c index 80c9928e948e..92738ae2704d 100644 --- a/drivers/usb/gadget/function/f_gsi.c +++ b/drivers/usb/gadget/function/f_gsi.c @@ -1959,14 +1959,14 @@ static int gsi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (gsi->prot_id == IPA_USB_ECM) gsi->d_port.cdc_filter = DEFAULT_FILTER; - post_event(&gsi->d_port, EVT_CONNECT_IN_PROGRESS); /* * For RNDIS the event is posted from the flow control * handler which is invoked when the host sends the * GEN_CURRENT_PACKET_FILTER message. */ if (gsi->prot_id != IPA_USB_RNDIS) - post_event(&gsi->d_port, EVT_HOST_READY); + post_event(&gsi->d_port, + EVT_CONNECT_IN_PROGRESS); queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w); } diff --git a/drivers/usb/gadget/function/f_mtp.c b/drivers/usb/gadget/function/f_mtp.c index 3b3b1b011407..aa186781ef22 100644 --- a/drivers/usb/gadget/function/f_mtp.c +++ b/drivers/usb/gadget/function/f_mtp.c @@ -158,7 +158,7 @@ static struct usb_interface_descriptor ptp_interface_desc = { .bInterfaceProtocol = 1, }; -static struct usb_endpoint_descriptor mtp_superspeed_in_desc = { +static struct usb_endpoint_descriptor mtp_ss_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, @@ -166,8 +166,8 @@ static struct usb_endpoint_descriptor mtp_superspeed_in_desc = { .wMaxPacketSize = cpu_to_le16(1024), }; -static struct usb_ss_ep_comp_descriptor mtp_superspeed_in_comp_desc = { - .bLength = sizeof(mtp_superspeed_in_comp_desc), +static struct usb_ss_ep_comp_descriptor mtp_ss_in_comp_desc = { + .bLength = sizeof(mtp_ss_in_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 2 values can be tweaked if necessary */ @@ -175,7 +175,8 @@ static struct usb_ss_ep_comp_descriptor mtp_superspeed_in_comp_desc = { /* .bmAttributes = 0, */ }; -static struct usb_endpoint_descriptor mtp_superspeed_out_desc = { + +static struct usb_endpoint_descriptor mtp_ss_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, @@ -183,8 +184,8 @@ static struct usb_endpoint_descriptor mtp_superspeed_out_desc = { .wMaxPacketSize = cpu_to_le16(1024), }; -static struct usb_ss_ep_comp_descriptor mtp_superspeed_out_comp_desc = { - .bLength = sizeof(mtp_superspeed_out_comp_desc), +static struct usb_ss_ep_comp_descriptor mtp_ss_out_comp_desc = { + .bLength = sizeof(mtp_ss_out_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 2 values can be tweaked if necessary */ @@ -231,8 +232,8 @@ static struct usb_endpoint_descriptor mtp_intr_desc = { .bInterval = 6, }; -static struct usb_ss_ep_comp_descriptor mtp_superspeed_intr_comp_desc = { - .bLength = sizeof(mtp_superspeed_intr_comp_desc), +static struct usb_ss_ep_comp_descriptor mtp_intr_ss_comp_desc = { + .bLength = sizeof(mtp_intr_ss_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* the following 3 values can be tweaked if necessary */ @@ -259,12 +260,12 @@ static struct usb_descriptor_header *hs_mtp_descs[] = { static struct usb_descriptor_header *ss_mtp_descs[] = { (struct usb_descriptor_header *) &mtp_interface_desc, - (struct usb_descriptor_header *) &mtp_superspeed_in_desc, - (struct usb_descriptor_header *) &mtp_superspeed_in_comp_desc, - (struct usb_descriptor_header *) &mtp_superspeed_out_desc, - (struct usb_descriptor_header *) &mtp_superspeed_out_comp_desc, + (struct usb_descriptor_header *) &mtp_ss_in_desc, + (struct usb_descriptor_header *) &mtp_ss_in_comp_desc, + (struct usb_descriptor_header *) &mtp_ss_out_desc, + (struct usb_descriptor_header *) &mtp_ss_out_comp_desc, (struct usb_descriptor_header *) &mtp_intr_desc, - (struct usb_descriptor_header *) &mtp_superspeed_intr_comp_desc, + (struct usb_descriptor_header *) &mtp_intr_ss_comp_desc, NULL, }; @@ -286,12 +287,12 @@ static struct usb_descriptor_header *hs_ptp_descs[] = { static struct usb_descriptor_header *ss_ptp_descs[] = { (struct usb_descriptor_header *) &ptp_interface_desc, - (struct usb_descriptor_header *) &mtp_superspeed_in_desc, - (struct usb_descriptor_header *) &mtp_superspeed_in_comp_desc, - (struct usb_descriptor_header *) &mtp_superspeed_out_desc, - (struct usb_descriptor_header *) &mtp_superspeed_out_comp_desc, + (struct usb_descriptor_header *) &mtp_ss_in_desc, + (struct usb_descriptor_header *) &mtp_ss_in_comp_desc, + (struct usb_descriptor_header *) &mtp_ss_out_desc, + (struct usb_descriptor_header *) &mtp_ss_out_comp_desc, (struct usb_descriptor_header *) &mtp_intr_desc, - (struct usb_descriptor_header *) &mtp_superspeed_intr_comp_desc, + (struct usb_descriptor_header *) &mtp_intr_ss_comp_desc, NULL, }; @@ -352,7 +353,7 @@ struct ext_mtp_desc mtp_ext_config_desc = { .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), .bcdVersion = __constant_cpu_to_le16(0x0100), .wIndex = __constant_cpu_to_le16(4), - .bCount = __constant_cpu_to_le16(1), + .bCount = 1, }, .function = { .bFirstInterfaceNumber = 0, @@ -395,6 +396,8 @@ struct mtp_instance { struct usb_function_instance func_inst; const char *name; struct mtp_dev *dev; + char mtp_ext_compat_id[16]; + struct usb_os_desc mtp_os_desc; }; /* temporary variable used between mtp_open() and mtp_gadget_bind() */ @@ -1389,6 +1392,7 @@ mtp_function_bind(struct usb_configuration *c, struct usb_function *f) struct mtp_dev *dev = func_to_mtp(f); int id; int ret; + struct mtp_instance *fi_mtp; dev->cdev = cdev; DBG(cdev, "mtp_function_bind dev: %p\n", dev); @@ -1406,6 +1410,18 @@ mtp_function_bind(struct usb_configuration *c, struct usb_function *f) mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; mtp_interface_desc.iInterface = ret; } + + fi_mtp = container_of(f->fi, struct mtp_instance, func_inst); + + if (cdev->use_os_string) { + f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), + GFP_KERNEL); + if (!f->os_desc_table) + return -ENOMEM; + f->os_desc_n = 1; + f->os_desc_table[0].os_desc = &fi_mtp->mtp_os_desc; + } + /* allocate endpoints */ ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, &mtp_fullspeed_out_desc, &mtp_intr_desc); @@ -1419,18 +1435,32 @@ mtp_function_bind(struct usb_configuration *c, struct usb_function *f) mtp_highspeed_out_desc.bEndpointAddress = mtp_fullspeed_out_desc.bEndpointAddress; } + /* support super speed hardware */ + if (gadget_is_superspeed(c->cdev->gadget)) { + unsigned max_burst; + + /* Calculate bMaxBurst, we know packet size is 1024 */ + max_burst = min_t(unsigned, MTP_BULK_BUFFER_SIZE / 1024, 15); + mtp_ss_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_ss_in_comp_desc.bMaxBurst = max_burst; + mtp_ss_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + mtp_ss_out_comp_desc.bMaxBurst = max_burst; + } /* support super speed hardware */ if (gadget_is_superspeed(c->cdev->gadget)) { - mtp_superspeed_in_desc.bEndpointAddress = + mtp_ss_in_desc.bEndpointAddress = mtp_fullspeed_in_desc.bEndpointAddress; - mtp_superspeed_out_desc.bEndpointAddress = + mtp_ss_out_desc.bEndpointAddress = mtp_fullspeed_out_desc.bEndpointAddress; } DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", - f->name, dev->ep_in->name, dev->ep_out->name); + gadget_is_superspeed(c->cdev->gadget) ? "super" : + (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full"), + f->name, dev->ep_in->name, dev->ep_out->name); return 0; } @@ -1449,6 +1479,8 @@ mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) mtp_request_free(req, dev->ep_intr); dev->state = STATE_OFFLINE; dev->is_ptp = false; + kfree(f->os_desc_table); + f->os_desc_n = 0; } static int mtp_function_set_alt(struct usb_function *f, @@ -1748,6 +1780,7 @@ static void mtp_free_inst(struct usb_function_instance *fi) fi_mtp = to_fi_mtp(fi); kfree(fi_mtp->name); mtp_cleanup(); + kfree(fi_mtp->mtp_os_desc.group.default_groups); kfree(fi_mtp); } @@ -1755,6 +1788,8 @@ struct usb_function_instance *alloc_inst_mtp_ptp(bool mtp_config) { struct mtp_instance *fi_mtp; int ret = 0; + struct usb_os_desc *descs[1]; + char *names[1]; fi_mtp = kzalloc(sizeof(*fi_mtp), GFP_KERNEL); if (!fi_mtp) @@ -1762,6 +1797,13 @@ struct usb_function_instance *alloc_inst_mtp_ptp(bool mtp_config) fi_mtp->func_inst.set_inst_name = mtp_set_inst_name; fi_mtp->func_inst.free_func_inst = mtp_free_inst; + fi_mtp->mtp_os_desc.ext_compat_id = fi_mtp->mtp_ext_compat_id; + INIT_LIST_HEAD(&fi_mtp->mtp_os_desc.ext_prop); + descs[0] = &fi_mtp->mtp_os_desc; + names[0] = "MTP"; + usb_os_desc_prepare_interf_dir(&fi_mtp->func_inst.group, 1, + descs, names, THIS_MODULE); + if (mtp_config) { ret = mtp_setup_configfs(fi_mtp); if (ret) { diff --git a/drivers/video/adf/Kconfig b/drivers/video/adf/Kconfig index 33858b73d8bb..2777db48fae0 100644 --- a/drivers/video/adf/Kconfig +++ b/drivers/video/adf/Kconfig @@ -11,4 +11,4 @@ menuconfig ADF_FBDEV menuconfig ADF_MEMBLOCK depends on ADF depends on HAVE_MEMBLOCK - tristate "Helper for using memblocks as buffers in ADF drivers" + bool "Helper for using memblocks as buffers in ADF drivers" diff --git a/drivers/video/adf/Makefile b/drivers/video/adf/Makefile index 78d0915122f4..cdf34a666dc7 100644 --- a/drivers/video/adf/Makefile +++ b/drivers/video/adf/Makefile @@ -2,13 +2,15 @@ ccflags-y := -Idrivers/staging/android CFLAGS_adf.o := -I$(src) -obj-$(CONFIG_ADF) += adf.o \ +obj-$(CONFIG_ADF) += adf_core.o + +adf_core-y := adf.o \ adf_client.o \ adf_fops.o \ adf_format.o \ adf_sysfs.o -obj-$(CONFIG_COMPAT) += adf_fops32.o +adf_core-$(CONFIG_COMPAT) += adf_fops32.o obj-$(CONFIG_ADF_FBDEV) += adf_fbdev.o diff --git a/fs/Kconfig b/fs/Kconfig index 6ce72d8d1ee1..a5d2dc39ba07 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -199,6 +199,7 @@ if MISC_FILESYSTEMS source "fs/adfs/Kconfig" source "fs/affs/Kconfig" source "fs/ecryptfs/Kconfig" +source "fs/sdcardfs/Kconfig" source "fs/hfs/Kconfig" source "fs/hfsplus/Kconfig" source "fs/befs/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 79f522575cba..3b54070cd629 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -3,7 +3,7 @@ # # 14 Sep 2000, Christoph Hellwig <hch@infradead.org> # Rewritten to use lists instead of if-statements. -# +# obj-y := open.o read_write.o file_table.o super.o \ char_dev.o stat.o exec.o pipe.o namei.o fcntl.o \ @@ -59,7 +59,7 @@ obj-y += devpts/ obj-$(CONFIG_PROFILING) += dcookies.o obj-$(CONFIG_DLM) += dlm/ - + # Do not add any filesystems before this line obj-$(CONFIG_FSCACHE) += fscache/ obj-$(CONFIG_REISERFS_FS) += reiserfs/ @@ -81,6 +81,7 @@ obj-$(CONFIG_ISO9660_FS) += isofs/ obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+ obj-$(CONFIG_HFS_FS) += hfs/ obj-$(CONFIG_ECRYPT_FS) += ecryptfs/ +obj-$(CONFIG_SDCARD_FS) += sdcardfs/ obj-$(CONFIG_VXFS_FS) += freevxfs/ obj-$(CONFIG_NFS_FS) += nfs/ obj-$(CONFIG_EXPORTFS) += exportfs/ diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 3a93755e880f..0c52941dd62c 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -651,7 +651,7 @@ static unsigned long randomize_stack_top(unsigned long stack_top) if ((current->flags & PF_RANDOMIZE) && !(current->personality & ADDR_NO_RANDOMIZE)) { - random_variable = (unsigned long) get_random_int(); + random_variable = get_random_long(); random_variable &= STACK_RND_MASK; random_variable <<= PAGE_SHIFT; } diff --git a/fs/dcache.c b/fs/dcache.c index 18effa378f97..240935d77844 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -3020,6 +3020,7 @@ char *d_absolute_path(const struct path *path, return ERR_PTR(error); return res; } +EXPORT_SYMBOL(d_absolute_path); /* * same as __d_path but appends "(deleted)" for unlinked files. diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig index b46e9fc64196..95a49ef2781a 100644 --- a/fs/ext4/Kconfig +++ b/fs/ext4/Kconfig @@ -117,10 +117,16 @@ config EXT4_ENCRYPTION decrypted pages in the page cache. config EXT4_FS_ENCRYPTION - bool - default y + bool "Ext4 FS Encryption" + default n depends on EXT4_ENCRYPTION +config EXT4_FS_ICE_ENCRYPTION + bool "Ext4 Encryption with ICE support" + default n + depends on EXT4_FS_ENCRYPTION + depends on PFK + config EXT4_DEBUG bool "EXT4 debugging support" depends on EXT4_FS diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile index f52cf54f0cbc..1cabbd9a9229 100644 --- a/fs/ext4/Makefile +++ b/fs/ext4/Makefile @@ -14,3 +14,5 @@ ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o ext4-$(CONFIG_EXT4_FS_ENCRYPTION) += crypto_policy.o crypto.o \ crypto_key.o crypto_fname.o + +ext4-$(CONFIG_EXT4_FS_ICE_ENCRYPTION) += ext4_ice.o diff --git a/fs/ext4/crypto.c b/fs/ext4/crypto.c index 1acac7fd21b2..1bb67391225a 100644 --- a/fs/ext4/crypto.c +++ b/fs/ext4/crypto.c @@ -93,7 +93,8 @@ void ext4_release_crypto_ctx(struct ext4_crypto_ctx *ctx) * Return: An allocated and initialized encryption context on success; error * value or NULL otherwise. */ -struct ext4_crypto_ctx *ext4_get_crypto_ctx(struct inode *inode) +struct ext4_crypto_ctx *ext4_get_crypto_ctx(struct inode *inode, + gfp_t gfp_flags) { struct ext4_crypto_ctx *ctx = NULL; int res = 0; @@ -120,7 +121,7 @@ struct ext4_crypto_ctx *ext4_get_crypto_ctx(struct inode *inode) list_del(&ctx->free_list); spin_unlock_irqrestore(&ext4_crypto_ctx_lock, flags); if (!ctx) { - ctx = kmem_cache_zalloc(ext4_crypto_ctx_cachep, GFP_NOFS); + ctx = kmem_cache_zalloc(ext4_crypto_ctx_cachep, gfp_flags); if (!ctx) { res = -ENOMEM; goto out; @@ -257,7 +258,8 @@ static int ext4_page_crypto(struct inode *inode, ext4_direction_t rw, pgoff_t index, struct page *src_page, - struct page *dest_page) + struct page *dest_page, + gfp_t gfp_flags) { u8 xts_tweak[EXT4_XTS_TWEAK_SIZE]; @@ -309,9 +311,10 @@ static int ext4_page_crypto(struct inode *inode, return 0; } -static struct page *alloc_bounce_page(struct ext4_crypto_ctx *ctx) +static struct page *alloc_bounce_page(struct ext4_crypto_ctx *ctx, + gfp_t gfp_flags) { - ctx->w.bounce_page = mempool_alloc(ext4_bounce_page_pool, GFP_NOWAIT); + ctx->w.bounce_page = mempool_alloc(ext4_bounce_page_pool, gfp_flags); if (ctx->w.bounce_page == NULL) return ERR_PTR(-ENOMEM); ctx->flags |= EXT4_WRITE_PATH_FL; @@ -334,7 +337,8 @@ static struct page *alloc_bounce_page(struct ext4_crypto_ctx *ctx) * error value or NULL. */ struct page *ext4_encrypt(struct inode *inode, - struct page *plaintext_page) + struct page *plaintext_page, + gfp_t gfp_flags) { struct ext4_crypto_ctx *ctx; struct page *ciphertext_page = NULL; @@ -342,17 +346,17 @@ struct page *ext4_encrypt(struct inode *inode, BUG_ON(!PageLocked(plaintext_page)); - ctx = ext4_get_crypto_ctx(inode); + ctx = ext4_get_crypto_ctx(inode, gfp_flags); if (IS_ERR(ctx)) return (struct page *) ctx; /* The encryption operation will require a bounce page. */ - ciphertext_page = alloc_bounce_page(ctx); + ciphertext_page = alloc_bounce_page(ctx, gfp_flags); if (IS_ERR(ciphertext_page)) goto errout; ctx->w.control_page = plaintext_page; err = ext4_page_crypto(inode, EXT4_ENCRYPT, plaintext_page->index, - plaintext_page, ciphertext_page); + plaintext_page, ciphertext_page, gfp_flags); if (err) { ciphertext_page = ERR_PTR(err); errout: @@ -380,8 +384,8 @@ int ext4_decrypt(struct page *page) { BUG_ON(!PageLocked(page)); - return ext4_page_crypto(page->mapping->host, - EXT4_DECRYPT, page->index, page, page); + return ext4_page_crypto(page->mapping->host, EXT4_DECRYPT, + page->index, page, page, GFP_NOFS); } int ext4_encrypted_zeroout(struct inode *inode, struct ext4_extent *ex) @@ -402,11 +406,11 @@ int ext4_encrypted_zeroout(struct inode *inode, struct ext4_extent *ex) BUG_ON(inode->i_sb->s_blocksize != PAGE_CACHE_SIZE); - ctx = ext4_get_crypto_ctx(inode); + ctx = ext4_get_crypto_ctx(inode, GFP_NOFS); if (IS_ERR(ctx)) return PTR_ERR(ctx); - ciphertext_page = alloc_bounce_page(ctx); + ciphertext_page = alloc_bounce_page(ctx, GFP_NOWAIT); if (IS_ERR(ciphertext_page)) { err = PTR_ERR(ciphertext_page); goto errout; @@ -414,11 +418,12 @@ int ext4_encrypted_zeroout(struct inode *inode, struct ext4_extent *ex) while (len--) { err = ext4_page_crypto(inode, EXT4_ENCRYPT, lblk, - ZERO_PAGE(0), ciphertext_page); + ZERO_PAGE(0), ciphertext_page, + GFP_NOFS); if (err) goto errout; - bio = bio_alloc(GFP_KERNEL, 1); + bio = bio_alloc(GFP_NOWAIT, 1); if (!bio) { err = -ENOMEM; goto errout; @@ -453,7 +458,8 @@ errout: bool ext4_valid_contents_enc_mode(uint32_t mode) { - return (mode == EXT4_ENCRYPTION_MODE_AES_256_XTS); + return (mode == EXT4_ENCRYPTION_MODE_AES_256_XTS || + mode == EXT4_ENCRYPTION_MODE_PRIVATE); } /** @@ -477,13 +483,16 @@ uint32_t ext4_validate_encryption_key_size(uint32_t mode, uint32_t size) */ static int ext4_d_revalidate(struct dentry *dentry, unsigned int flags) { - struct inode *dir = d_inode(dentry->d_parent); - struct ext4_crypt_info *ci = EXT4_I(dir)->i_crypt_info; + struct dentry *dir; + struct ext4_crypt_info *ci; int dir_has_key, cached_with_key; - if (!ext4_encrypted_inode(dir)) + dir = dget_parent(dentry); + if (!ext4_encrypted_inode(d_inode(dir))) { + dput(dir); return 0; - + } + ci = EXT4_I(d_inode(dir))->i_crypt_info; if (ci && ci->ci_keyring_key && (ci->ci_keyring_key->flags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED) | @@ -493,6 +502,7 @@ static int ext4_d_revalidate(struct dentry *dentry, unsigned int flags) /* this should eventually be an flag in d_flags */ cached_with_key = dentry->d_fsdata != NULL; dir_has_key = (ci != NULL); + dput(dir); /* * If the dentry was cached without the key, and it is a diff --git a/fs/ext4/crypto_key.c b/fs/ext4/crypto_key.c index 9a16d1e75a49..15342bfff70d 100644 --- a/fs/ext4/crypto_key.c +++ b/fs/ext4/crypto_key.c @@ -15,6 +15,7 @@ #include <uapi/linux/keyctl.h> #include "ext4.h" +#include "ext4_ice.h" #include "xattr.h" static void derive_crypt_complete(struct crypto_async_request *req, int rc) @@ -111,6 +112,12 @@ void ext4_free_encryption_info(struct inode *inode, ext4_free_crypt_info(ci); } +static int ext4_default_data_encryption_mode(void) +{ + return ext4_is_ice_enabled() ? EXT4_ENCRYPTION_MODE_PRIVATE : + EXT4_ENCRYPTION_MODE_AES_256_XTS; +} + int _ext4_get_encryption_info(struct inode *inode) { struct ext4_inode_info *ei = EXT4_I(inode); @@ -124,8 +131,8 @@ int _ext4_get_encryption_info(struct inode *inode) struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct crypto_ablkcipher *ctfm; const char *cipher_str; - char raw_key[EXT4_MAX_KEY_SIZE]; - char mode; + int for_fname = 0; + int mode; int res; if (!ext4_read_workqueue) { @@ -150,7 +157,8 @@ retry: if (res < 0) { if (!DUMMY_ENCRYPTION_ENABLED(sbi)) return res; - ctx.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS; + ctx.contents_encryption_mode = + ext4_default_data_encryption_mode(); ctx.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS; ctx.flags = 0; @@ -169,12 +177,12 @@ retry: crypt_info->ci_keyring_key = NULL; memcpy(crypt_info->ci_master_key, ctx.master_key_descriptor, sizeof(crypt_info->ci_master_key)); - if (S_ISREG(inode->i_mode)) - mode = crypt_info->ci_data_mode; - else if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) - mode = crypt_info->ci_filename_mode; - else + if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) + for_fname = 1; + else if (!S_ISREG(inode->i_mode)) BUG(); + mode = for_fname ? crypt_info->ci_filename_mode : + crypt_info->ci_data_mode; switch (mode) { case EXT4_ENCRYPTION_MODE_AES_256_XTS: cipher_str = "xts(aes)"; @@ -182,6 +190,9 @@ retry: case EXT4_ENCRYPTION_MODE_AES_256_CTS: cipher_str = "cts(cbc(aes))"; break; + case EXT4_ENCRYPTION_MODE_PRIVATE: + cipher_str = "bugon"; + break; default: printk_once(KERN_WARNING "ext4: unsupported key mode %d (ino %u)\n", @@ -190,7 +201,7 @@ retry: goto out; } if (DUMMY_ENCRYPTION_ENABLED(sbi)) { - memset(raw_key, 0x42, EXT4_AES_256_XTS_KEY_SIZE); + memset(crypt_info->ci_raw_key, 0x42, EXT4_AES_256_XTS_KEY_SIZE); goto got_key; } memcpy(full_key_descriptor, EXT4_KEY_DESC_PREFIX, @@ -232,28 +243,36 @@ retry: goto out; } res = ext4_derive_key_aes(ctx.nonce, master_key->raw, - raw_key); + crypt_info->ci_raw_key); up_read(&keyring_key->sem); if (res) goto out; got_key: - ctfm = crypto_alloc_ablkcipher(cipher_str, 0, 0); - if (!ctfm || IS_ERR(ctfm)) { - res = ctfm ? PTR_ERR(ctfm) : -ENOMEM; - printk(KERN_DEBUG - "%s: error %d (inode %u) allocating crypto tfm\n", - __func__, res, (unsigned) inode->i_ino); + if (for_fname || + (crypt_info->ci_data_mode != EXT4_ENCRYPTION_MODE_PRIVATE)) { + ctfm = crypto_alloc_ablkcipher(cipher_str, 0, 0); + if (!ctfm || IS_ERR(ctfm)) { + res = ctfm ? PTR_ERR(ctfm) : -ENOMEM; + pr_debug("%s: error %d (inode %u) allocating crypto tfm\n", + __func__, res, (unsigned) inode->i_ino); + goto out; + } + crypt_info->ci_ctfm = ctfm; + crypto_ablkcipher_clear_flags(ctfm, ~0); + crypto_tfm_set_flags(crypto_ablkcipher_tfm(ctfm), + CRYPTO_TFM_REQ_WEAK_KEY); + res = crypto_ablkcipher_setkey(ctfm, crypt_info->ci_raw_key, + ext4_encryption_key_size(mode)); + if (res) + goto out; + memzero_explicit(crypt_info->ci_raw_key, + sizeof(crypt_info->ci_raw_key)); + } else if (!ext4_is_ice_enabled()) { + pr_warn("%s: ICE support not available\n", + __func__); + res = -EINVAL; goto out; } - crypt_info->ci_ctfm = ctfm; - crypto_ablkcipher_clear_flags(ctfm, ~0); - crypto_tfm_set_flags(crypto_ablkcipher_tfm(ctfm), - CRYPTO_TFM_REQ_WEAK_KEY); - res = crypto_ablkcipher_setkey(ctfm, raw_key, - ext4_encryption_key_size(mode)); - if (res) - goto out; - memzero_explicit(raw_key, sizeof(raw_key)); if (cmpxchg(&ei->i_crypt_info, NULL, crypt_info) != NULL) { ext4_free_crypt_info(crypt_info); goto retry; @@ -263,8 +282,9 @@ got_key: out: if (res == -ENOKEY) res = 0; + memzero_explicit(crypt_info->ci_raw_key, + sizeof(crypt_info->ci_raw_key)); ext4_free_crypt_info(crypt_info); - memzero_explicit(raw_key, sizeof(raw_key)); return res; } diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index c1b4f6ab2148..f287ddfd17f1 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -588,6 +588,7 @@ enum { #define EXT4_ENCRYPTION_MODE_AES_256_GCM 2 #define EXT4_ENCRYPTION_MODE_AES_256_CBC 3 #define EXT4_ENCRYPTION_MODE_AES_256_CTS 4 +#define EXT4_ENCRYPTION_MODE_PRIVATE 127 #include "ext4_crypto.h" @@ -2251,11 +2252,13 @@ extern struct kmem_cache *ext4_crypt_info_cachep; bool ext4_valid_contents_enc_mode(uint32_t mode); uint32_t ext4_validate_encryption_key_size(uint32_t mode, uint32_t size); extern struct workqueue_struct *ext4_read_workqueue; -struct ext4_crypto_ctx *ext4_get_crypto_ctx(struct inode *inode); +struct ext4_crypto_ctx *ext4_get_crypto_ctx(struct inode *inode, + gfp_t gfp_flags); void ext4_release_crypto_ctx(struct ext4_crypto_ctx *ctx); void ext4_restore_control_page(struct page *data_page); struct page *ext4_encrypt(struct inode *inode, - struct page *plaintext_page); + struct page *plaintext_page, + gfp_t gfp_flags); int ext4_decrypt(struct page *page); int ext4_encrypted_zeroout(struct inode *inode, struct ext4_extent *ex); extern const struct dentry_operations ext4_encrypted_d_ops; @@ -2326,6 +2329,19 @@ int _ext4_get_encryption_info(struct inode *inode); #ifdef CONFIG_EXT4_FS_ENCRYPTION int ext4_has_encryption_key(struct inode *inode); +static inline struct ext4_crypt_info *ext4_encryption_info(struct inode *inode) +{ + return EXT4_I(inode)->i_crypt_info; +} + +static inline int ext4_using_hardware_encryption(struct inode *inode) +{ + struct ext4_crypt_info *ci = ext4_encryption_info(inode); + + return S_ISREG(inode->i_mode) && ci && + ci->ci_data_mode == EXT4_ENCRYPTION_MODE_PRIVATE; +} + static inline int ext4_get_encryption_info(struct inode *inode) { struct ext4_crypt_info *ci = EXT4_I(inode)->i_crypt_info; @@ -2339,11 +2355,6 @@ static inline int ext4_get_encryption_info(struct inode *inode) return 0; } -static inline struct ext4_crypt_info *ext4_encryption_info(struct inode *inode) -{ - return EXT4_I(inode)->i_crypt_info; -} - #else static inline int ext4_has_encryption_key(struct inode *inode) { @@ -2357,6 +2368,10 @@ static inline struct ext4_crypt_info *ext4_encryption_info(struct inode *inode) { return NULL; } +static inline int ext4_using_hardware_encryption(struct inode *inode) +{ + return 0; +} #endif diff --git a/fs/ext4/ext4_crypto.h b/fs/ext4/ext4_crypto.h index ac7d4e813796..95cbc9bc1995 100644 --- a/fs/ext4/ext4_crypto.h +++ b/fs/ext4/ext4_crypto.h @@ -12,6 +12,7 @@ #define _EXT4_CRYPTO_H #include <linux/fs.h> +#include <linux/pfk.h> #define EXT4_KEY_DESCRIPTOR_SIZE 8 @@ -61,6 +62,7 @@ struct ext4_encryption_context { #define EXT4_AES_256_CBC_KEY_SIZE 32 #define EXT4_AES_256_CTS_KEY_SIZE 32 #define EXT4_AES_256_XTS_KEY_SIZE 64 +#define EXT4_PRIVATE_KEY_SIZE 64 #define EXT4_MAX_KEY_SIZE 64 #define EXT4_KEY_DESC_PREFIX "ext4:" @@ -80,8 +82,11 @@ struct ext4_crypt_info { struct crypto_ablkcipher *ci_ctfm; struct key *ci_keyring_key; char ci_master_key[EXT4_KEY_DESCRIPTOR_SIZE]; + char ci_raw_key[EXT4_MAX_KEY_SIZE]; }; + + #define EXT4_CTX_REQUIRES_FREE_ENCRYPT_FL 0x00000001 #define EXT4_WRITE_PATH_FL 0x00000002 @@ -114,6 +119,7 @@ static inline int ext4_encryption_key_size(int mode) { switch (mode) { case EXT4_ENCRYPTION_MODE_AES_256_XTS: + case EXT4_ENCRYPTION_MODE_PRIVATE: return EXT4_AES_256_XTS_KEY_SIZE; case EXT4_ENCRYPTION_MODE_AES_256_GCM: return EXT4_AES_256_GCM_KEY_SIZE; diff --git a/fs/ext4/ext4_ice.c b/fs/ext4/ext4_ice.c new file mode 100644 index 000000000000..d85bcb8ea1ba --- /dev/null +++ b/fs/ext4/ext4_ice.c @@ -0,0 +1,109 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ext4_ice.h" +#include "ext4_crypto.h" + + +/* + * Retrieves encryption key from the inode + */ +char *ext4_get_ice_encryption_key(const struct inode *inode) +{ + struct ext4_crypt_info *ci = NULL; + + if (!inode) + return NULL; + + ci = ext4_encryption_info((struct inode *)inode); + if (!ci) + return NULL; + + return &(ci->ci_raw_key[0]); +} + +/* + * Retrieves encryption salt from the inode + */ +char *ext4_get_ice_encryption_salt(const struct inode *inode) +{ + struct ext4_crypt_info *ci = NULL; + + if (!inode) + return NULL; + + ci = ext4_encryption_info((struct inode *)inode); + if (!ci) + return NULL; + + return &(ci->ci_raw_key[ext4_get_ice_encryption_key_size(inode)]); +} + +/* + * returns true if the cipher mode in inode is AES XTS + */ +int ext4_is_aes_xts_cipher(const struct inode *inode) +{ + struct ext4_crypt_info *ci = NULL; + + ci = ext4_encryption_info((struct inode *)inode); + if (!ci) + return 0; + + return (ci->ci_data_mode == EXT4_ENCRYPTION_MODE_PRIVATE); +} + +/* + * returns true if encryption info in both inodes is equal + */ +int ext4_is_ice_encryption_info_equal(const struct inode *inode1, + const struct inode *inode2) +{ + char *key1 = NULL; + char *key2 = NULL; + char *salt1 = NULL; + char *salt2 = NULL; + + if (!inode1 || !inode2) + return 0; + + if (inode1 == inode2) + return 1; + + /* both do not belong to ice, so we don't care, they are equal for us */ + if (!ext4_should_be_processed_by_ice(inode1) && + !ext4_should_be_processed_by_ice(inode2)) + return 1; + + /* one belongs to ice, the other does not -> not equal */ + if (ext4_should_be_processed_by_ice(inode1) ^ + ext4_should_be_processed_by_ice(inode2)) + return 0; + + key1 = ext4_get_ice_encryption_key(inode1); + key2 = ext4_get_ice_encryption_key(inode2); + salt1 = ext4_get_ice_encryption_salt(inode1); + salt2 = ext4_get_ice_encryption_salt(inode2); + + /* key and salt should not be null by this point */ + if (!key1 || !key2 || !salt1 || !salt2 || + (ext4_get_ice_encryption_key_size(inode1) != + ext4_get_ice_encryption_key_size(inode2)) || + (ext4_get_ice_encryption_salt_size(inode1) != + ext4_get_ice_encryption_salt_size(inode2))) + return 0; + + return ((memcmp(key1, key2, + ext4_get_ice_encryption_key_size(inode1)) == 0) && + (memcmp(salt1, salt2, + ext4_get_ice_encryption_salt_size(inode1)) == 0)); +} diff --git a/fs/ext4/ext4_ice.h b/fs/ext4/ext4_ice.h new file mode 100644 index 000000000000..5257edabd6b2 --- /dev/null +++ b/fs/ext4/ext4_ice.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _EXT4_ICE_H +#define _EXT4_ICE_H + +#include "ext4.h" +#include "ext4_crypto.h" + +#ifdef CONFIG_EXT4_FS_ICE_ENCRYPTION +static inline int ext4_should_be_processed_by_ice(const struct inode *inode) +{ + if (!ext4_encrypted_inode((struct inode *)inode)) + return 0; + + return ext4_using_hardware_encryption((struct inode *)inode); +} + +static inline int ext4_is_ice_enabled(void) +{ + return 1; +} + +int ext4_is_aes_xts_cipher(const struct inode *inode); + +char *ext4_get_ice_encryption_key(const struct inode *inode); +char *ext4_get_ice_encryption_salt(const struct inode *inode); + +int ext4_is_ice_encryption_info_equal(const struct inode *inode1, + const struct inode *inode2); + +static inline size_t ext4_get_ice_encryption_key_size( + const struct inode *inode) +{ + return EXT4_AES_256_XTS_KEY_SIZE / 2; +} + +static inline size_t ext4_get_ice_encryption_salt_size( + const struct inode *inode) +{ + return EXT4_AES_256_XTS_KEY_SIZE / 2; +} + +#else +static inline int ext4_should_be_processed_by_ice(const struct inode *inode) +{ + return 0; +} +static inline int ext4_is_ice_enabled(void) +{ + return 0; +} + +static inline char *ext4_get_ice_encryption_key(const struct inode *inode) +{ + return NULL; +} + +static inline char *ext4_get_ice_encryption_salt(const struct inode *inode) +{ + return NULL; +} + +static inline size_t ext4_get_ice_encryption_key_size( + const struct inode *inode) +{ + return 0; +} + +static inline size_t ext4_get_ice_encryption_salt_size( + const struct inode *inode) +{ + return 0; +} + +static inline int ext4_is_xts_cipher(const struct inode *inode) +{ + return 0; +} + +static inline int ext4_is_ice_encryption_info_equal( + const struct inode *inode1, + const struct inode *inode2) +{ + return 0; +} + +static inline int ext4_is_aes_xts_cipher(const struct inode *inode) +{ + return 0; +} + +#endif + +#endif /* _EXT4_ICE_H */ diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 06bda0361e7c..b15e6edb8f2c 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -42,6 +42,7 @@ #include "xattr.h" #include "acl.h" #include "truncate.h" +#include "ext4_ice.h" #include <trace/events/ext4.h> @@ -979,7 +980,8 @@ static int ext4_block_write_begin(struct page *page, loff_t pos, unsigned len, ll_rw_block(READ, 1, &bh); *wait_bh++ = bh; decrypt = ext4_encrypted_inode(inode) && - S_ISREG(inode->i_mode); + S_ISREG(inode->i_mode) && + !ext4_is_ice_enabled(); } } /* @@ -3459,7 +3461,8 @@ static int __ext4_block_zero_page_range(handle_t *handle, if (!buffer_uptodate(bh)) goto unlock; if (S_ISREG(inode->i_mode) && - ext4_encrypted_inode(inode)) { + ext4_encrypted_inode(inode) && + !ext4_using_hardware_encryption(inode)) { /* We expect the key to be set. */ BUG_ON(!ext4_has_encryption_key(inode)); BUG_ON(blocksize != PAGE_CACHE_SIZE); diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index 17fbe3882b8e..1a6a8ca4de3a 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -23,10 +23,12 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/mm.h> +#include <linux/backing-dev.h> #include "ext4_jbd2.h" #include "xattr.h" #include "acl.h" +#include "ext4_ice.h" static struct kmem_cache *io_end_cachep; @@ -485,9 +487,24 @@ int ext4_bio_write_page(struct ext4_io_submit *io, if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode) && nr_to_submit) { - data_page = ext4_encrypt(inode, page); + gfp_t gfp_flags = GFP_NOFS; + + retry_encrypt: + + if (!ext4_using_hardware_encryption(inode)) + data_page = ext4_encrypt(inode, page, gfp_flags); + + if (IS_ERR(data_page)) { ret = PTR_ERR(data_page); + if (ret == ENOMEM && wbc->sync_mode == WB_SYNC_ALL) { + if (io->io_bio) { + ext4_io_submit(io); + congestion_wait(BLK_RW_ASYNC, HZ/50); + } + gfp_flags |= __GFP_NOFAIL; + goto retry_encrypt; + } data_page = NULL; goto out; } diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c index 5dc5e95063de..dd98270d0b21 100644 --- a/fs/ext4/readpage.c +++ b/fs/ext4/readpage.c @@ -45,6 +45,7 @@ #include <linux/cleancache.h> #include "ext4.h" +#include "ext4_ice.h" /* * Call ext4_decrypt on every single page, reusing the encryption @@ -62,12 +63,17 @@ static void completion_pages(struct work_struct *work) bio_for_each_segment_all(bv, bio, i) { struct page *page = bv->bv_page; - int ret = ext4_decrypt(page); - if (ret) { - WARN_ON_ONCE(1); - SetPageError(page); - } else + if (ext4_is_ice_enabled()) { SetPageUptodate(page); + } else { + int ret = ext4_decrypt(page); + + if (ret) { + WARN_ON_ONCE(1); + SetPageError(page); + } else + SetPageUptodate(page); + } unlock_page(page); } ext4_release_crypto_ctx(ctx); @@ -279,7 +285,7 @@ int ext4_mpage_readpages(struct address_space *mapping, if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode)) { - ctx = ext4_get_crypto_ctx(inode); + ctx = ext4_get_crypto_ctx(inode, GFP_NOFS); if (IS_ERR(ctx)) goto set_error_page; } @@ -324,5 +330,6 @@ int ext4_mpage_readpages(struct address_space *mapping, BUG_ON(pages && !list_empty(pages)); if (bio) submit_bio(READ, bio); + return 0; } diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index b8d08d0d0a4d..f72f3b25b3f2 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -702,6 +702,8 @@ SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname, struct fsnotify_group *group; struct inode *inode; struct path path; + struct path alteredpath; + struct path *canonical_path = &path; struct fd f; int ret; unsigned flags = 0; @@ -741,13 +743,22 @@ SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname, if (ret) goto fput_and_out; + /* support stacked filesystems */ + if(path.dentry && path.dentry->d_op) { + if (path.dentry->d_op->d_canonical_path) { + path.dentry->d_op->d_canonical_path(path.dentry, &alteredpath); + canonical_path = &alteredpath; + path_put(&path); + } + } + /* inode held in place by reference to path; group by fget on fd */ - inode = path.dentry->d_inode; + inode = canonical_path->dentry->d_inode; group = f.file->private_data; /* create/update an inode mark */ ret = inotify_update_watch(group, inode, mask); - path_put(&path); + path_put(canonical_path); fput_and_out: fdput(f); return ret; diff --git a/fs/pstore/ram.c b/fs/pstore/ram.c index 2429c804cf78..414041342a99 100644 --- a/fs/pstore/ram.c +++ b/fs/pstore/ram.c @@ -554,7 +554,7 @@ static int ramoops_parse_dt(struct platform_device *pdev, static int ramoops_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct ramoops_platform_data *pdata = platform_get_drvdata(pdev); + struct ramoops_platform_data *pdata = pdev->dev.platform_data; struct ramoops_context *cxt = &oops_cxt; size_t dump_mem_sz; phys_addr_t paddr; @@ -666,7 +666,6 @@ static int ramoops_probe(struct platform_device *pdev) cxt->size, (unsigned long long)cxt->phys_addr, cxt->ecc_info.ecc_size, cxt->ecc_info.block_size); - platform_set_drvdata(pdev, pdata); return 0; fail_buf: diff --git a/fs/sdcardfs/Kconfig b/fs/sdcardfs/Kconfig new file mode 100644 index 000000000000..a1c103316ac7 --- /dev/null +++ b/fs/sdcardfs/Kconfig @@ -0,0 +1,13 @@ +config SDCARD_FS + tristate "sdcard file system" + depends on CONFIGFS_FS + default n + help + Sdcardfs is based on Wrapfs file system. + +config SDCARD_FS_FADV_NOACTIVE + bool "sdcardfs fadvise noactive support" + depends on FADV_NOACTIVE + default y + help + Sdcardfs supports fadvise noactive mode. diff --git a/fs/sdcardfs/Makefile b/fs/sdcardfs/Makefile new file mode 100644 index 000000000000..b84fbb2b45a4 --- /dev/null +++ b/fs/sdcardfs/Makefile @@ -0,0 +1,7 @@ +SDCARDFS_VERSION="0.1" + +EXTRA_CFLAGS += -DSDCARDFS_VERSION=\"$(SDCARDFS_VERSION)\" + +obj-$(CONFIG_SDCARD_FS) += sdcardfs.o + +sdcardfs-y := dentry.o file.o inode.o main.o super.o lookup.o mmap.o packagelist.o derived_perm.o diff --git a/fs/sdcardfs/dentry.c b/fs/sdcardfs/dentry.c new file mode 100644 index 000000000000..ba165ef11e27 --- /dev/null +++ b/fs/sdcardfs/dentry.c @@ -0,0 +1,182 @@ +/* + * fs/sdcardfs/dentry.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" +#include "linux/ctype.h" + +/* + * returns: -ERRNO if error (returned to user) + * 0: tell VFS to invalidate dentry + * 1: dentry is valid + */ +static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) +{ + int err = 1; + struct path parent_lower_path, lower_path; + struct dentry *parent_dentry = NULL; + struct dentry *parent_lower_dentry = NULL; + struct dentry *lower_cur_parent_dentry = NULL; + struct dentry *lower_dentry = NULL; + + if (flags & LOOKUP_RCU) + return -ECHILD; + + spin_lock(&dentry->d_lock); + if (IS_ROOT(dentry)) { + spin_unlock(&dentry->d_lock); + return 1; + } + spin_unlock(&dentry->d_lock); + + /* check uninitialized obb_dentry and + * whether the base obbpath has been changed or not */ + if (is_obbpath_invalid(dentry)) { + d_drop(dentry); + return 0; + } + + parent_dentry = dget_parent(dentry); + sdcardfs_get_lower_path(parent_dentry, &parent_lower_path); + sdcardfs_get_real_lower(dentry, &lower_path); + parent_lower_dentry = parent_lower_path.dentry; + lower_dentry = lower_path.dentry; + lower_cur_parent_dentry = dget_parent(lower_dentry); + + spin_lock(&lower_dentry->d_lock); + if (d_unhashed(lower_dentry)) { + spin_unlock(&lower_dentry->d_lock); + d_drop(dentry); + err = 0; + goto out; + } + spin_unlock(&lower_dentry->d_lock); + + if (parent_lower_dentry != lower_cur_parent_dentry) { + d_drop(dentry); + err = 0; + goto out; + } + + if (dentry < lower_dentry) { + spin_lock(&dentry->d_lock); + spin_lock(&lower_dentry->d_lock); + } else { + spin_lock(&lower_dentry->d_lock); + spin_lock(&dentry->d_lock); + } + + if (dentry->d_name.len != lower_dentry->d_name.len) { + __d_drop(dentry); + err = 0; + } else if (strncasecmp(dentry->d_name.name, lower_dentry->d_name.name, + dentry->d_name.len) != 0) { + __d_drop(dentry); + err = 0; + } + + if (dentry < lower_dentry) { + spin_unlock(&lower_dentry->d_lock); + spin_unlock(&dentry->d_lock); + } else { + spin_unlock(&dentry->d_lock); + spin_unlock(&lower_dentry->d_lock); + } + +out: + dput(parent_dentry); + dput(lower_cur_parent_dentry); + sdcardfs_put_lower_path(parent_dentry, &parent_lower_path); + sdcardfs_put_real_lower(dentry, &lower_path); + return err; +} + +static void sdcardfs_d_release(struct dentry *dentry) +{ + /* release and reset the lower paths */ + if(has_graft_path(dentry)) { + sdcardfs_put_reset_orig_path(dentry); + } + sdcardfs_put_reset_lower_path(dentry); + free_dentry_private_data(dentry); + return; +} + +static int sdcardfs_hash_ci(const struct dentry *dentry, + struct qstr *qstr) +{ + /* + * This function is copy of vfat_hashi. + * FIXME Should we support national language? + * Refer to vfat_hashi() + * struct nls_table *t = MSDOS_SB(dentry->d_sb)->nls_io; + */ + const unsigned char *name; + unsigned int len; + unsigned long hash; + + name = qstr->name; + //len = vfat_striptail_len(qstr); + len = qstr->len; + + hash = init_name_hash(); + while (len--) + //hash = partial_name_hash(nls_tolower(t, *name++), hash); + hash = partial_name_hash(tolower(*name++), hash); + qstr->hash = end_name_hash(hash); + + return 0; +} + +/* + * Case insensitive compare of two vfat names. + */ +static int sdcardfs_cmp_ci(const struct dentry *parent, + const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + /* This function is copy of vfat_cmpi */ + // FIXME Should we support national language? + //struct nls_table *t = MSDOS_SB(parent->d_sb)->nls_io; + //unsigned int alen, blen; + + /* A filename cannot end in '.' or we treat it like it has none */ + /* + alen = vfat_striptail_len(name); + blen = __vfat_striptail_len(len, str); + if (alen == blen) { + if (nls_strnicmp(t, name->name, str, alen) == 0) + return 0; + } + */ + if (name->len == len) { + if (strncasecmp(name->name, str, len) == 0) + return 0; + } + return 1; +} + +const struct dentry_operations sdcardfs_ci_dops = { + .d_revalidate = sdcardfs_d_revalidate, + .d_release = sdcardfs_d_release, + .d_hash = sdcardfs_hash_ci, + .d_compare = sdcardfs_cmp_ci, + .d_canonical_path = sdcardfs_get_real_lower, +}; + diff --git a/fs/sdcardfs/derived_perm.c b/fs/sdcardfs/derived_perm.c new file mode 100644 index 000000000000..128b3e56851f --- /dev/null +++ b/fs/sdcardfs/derived_perm.c @@ -0,0 +1,265 @@ +/* + * fs/sdcardfs/derived_perm.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" + +/* copy derived state from parent inode */ +static void inherit_derived_state(struct inode *parent, struct inode *child) +{ + struct sdcardfs_inode_info *pi = SDCARDFS_I(parent); + struct sdcardfs_inode_info *ci = SDCARDFS_I(child); + + ci->perm = PERM_INHERIT; + ci->userid = pi->userid; + ci->d_uid = pi->d_uid; + ci->under_android = pi->under_android; +} + +/* helper function for derived state */ +void setup_derived_state(struct inode *inode, perm_t perm, + userid_t userid, uid_t uid, bool under_android) +{ + struct sdcardfs_inode_info *info = SDCARDFS_I(inode); + + info->perm = perm; + info->userid = userid; + info->d_uid = uid; + info->under_android = under_android; +} + +/* While renaming, there is a point where we want the path from dentry, but the name from newdentry */ +void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, struct dentry *newdentry) +{ + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + struct sdcardfs_inode_info *info = SDCARDFS_I(dentry->d_inode); + struct sdcardfs_inode_info *parent_info= SDCARDFS_I(parent->d_inode); + appid_t appid; + + /* By default, each inode inherits from its parent. + * the properties are maintained on its private fields + * because the inode attributes will be modified with that of + * its lower inode. + * The derived state will be updated on the last + * stage of each system call by fix_derived_permission(inode). + */ + + inherit_derived_state(parent->d_inode, dentry->d_inode); + + /* Derive custom permissions based on parent and current node */ + switch (parent_info->perm) { + case PERM_INHERIT: + /* Already inherited above */ + break; + case PERM_PRE_ROOT: + /* Legacy internal layout places users at top level */ + info->perm = PERM_ROOT; + info->userid = simple_strtoul(newdentry->d_name.name, NULL, 10); + break; + case PERM_ROOT: + /* Assume masked off by default. */ + if (!strcasecmp(newdentry->d_name.name, "Android")) { + /* App-specific directories inside; let anyone traverse */ + info->perm = PERM_ANDROID; + info->under_android = true; + } + break; + case PERM_ANDROID: + if (!strcasecmp(newdentry->d_name.name, "data")) { + /* App-specific directories inside; let anyone traverse */ + info->perm = PERM_ANDROID_DATA; + } else if (!strcasecmp(newdentry->d_name.name, "obb")) { + /* App-specific directories inside; let anyone traverse */ + info->perm = PERM_ANDROID_OBB; + /* Single OBB directory is always shared */ + } else if (!strcasecmp(newdentry->d_name.name, "media")) { + /* App-specific directories inside; let anyone traverse */ + info->perm = PERM_ANDROID_MEDIA; + } + break; + case PERM_ANDROID_DATA: + case PERM_ANDROID_OBB: + case PERM_ANDROID_MEDIA: + appid = get_appid(sbi->pkgl_id, newdentry->d_name.name); + if (appid != 0) { + info->d_uid = multiuser_get_uid(parent_info->userid, appid); + } + break; + } +} + +void get_derived_permission(struct dentry *parent, struct dentry *dentry) +{ + get_derived_permission_new(parent, dentry, dentry); +} + +void get_derive_permissions_recursive(struct dentry *parent) { + struct dentry *dentry; + list_for_each_entry(dentry, &parent->d_subdirs, d_child) { + if (dentry && dentry->d_inode) { + mutex_lock(&dentry->d_inode->i_mutex); + get_derived_permission(parent, dentry); + fix_derived_permission(dentry->d_inode); + get_derive_permissions_recursive(dentry); + mutex_unlock(&dentry->d_inode->i_mutex); + } + } +} + +/* main function for updating derived permission */ +inline void update_derived_permission_lock(struct dentry *dentry) +{ + struct dentry *parent; + + if(!dentry || !dentry->d_inode) { + printk(KERN_ERR "sdcardfs: %s: invalid dentry\n", __func__); + return; + } + /* FIXME: + * 1. need to check whether the dentry is updated or not + * 2. remove the root dentry update + */ + mutex_lock(&dentry->d_inode->i_mutex); + if(IS_ROOT(dentry)) { + //setup_default_pre_root_state(dentry->d_inode); + } else { + parent = dget_parent(dentry); + if(parent) { + get_derived_permission(parent, dentry); + dput(parent); + } + } + fix_derived_permission(dentry->d_inode); + mutex_unlock(&dentry->d_inode->i_mutex); +} + +int need_graft_path(struct dentry *dentry) +{ + int ret = 0; + struct dentry *parent = dget_parent(dentry); + struct sdcardfs_inode_info *parent_info= SDCARDFS_I(parent->d_inode); + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + + if(parent_info->perm == PERM_ANDROID && + !strcasecmp(dentry->d_name.name, "obb")) { + + /* /Android/obb is the base obbpath of DERIVED_UNIFIED */ + if(!(sbi->options.multiuser == false + && parent_info->userid == 0)) { + ret = 1; + } + } + dput(parent); + return ret; +} + +int is_obbpath_invalid(struct dentry *dent) +{ + int ret = 0; + struct sdcardfs_dentry_info *di = SDCARDFS_D(dent); + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dent->d_sb); + char *path_buf, *obbpath_s; + + /* check the base obbpath has been changed. + * this routine can check an uninitialized obb dentry as well. + * regarding the uninitialized obb, refer to the sdcardfs_mkdir() */ + spin_lock(&di->lock); + if(di->orig_path.dentry) { + if(!di->lower_path.dentry) { + ret = 1; + } else { + path_get(&di->lower_path); + //lower_parent = lock_parent(lower_path->dentry); + + path_buf = kmalloc(PATH_MAX, GFP_ATOMIC); + if(!path_buf) { + ret = 1; + printk(KERN_ERR "sdcardfs: fail to allocate path_buf in %s.\n", __func__); + } else { + obbpath_s = d_path(&di->lower_path, path_buf, PATH_MAX); + if (d_unhashed(di->lower_path.dentry) || + strcasecmp(sbi->obbpath_s, obbpath_s)) { + ret = 1; + } + kfree(path_buf); + } + + //unlock_dir(lower_parent); + path_put(&di->lower_path); + } + } + spin_unlock(&di->lock); + return ret; +} + +int is_base_obbpath(struct dentry *dentry) +{ + int ret = 0; + struct dentry *parent = dget_parent(dentry); + struct sdcardfs_inode_info *parent_info= SDCARDFS_I(parent->d_inode); + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + + spin_lock(&SDCARDFS_D(dentry)->lock); + if (sbi->options.multiuser) { + if(parent_info->perm == PERM_PRE_ROOT && + !strcasecmp(dentry->d_name.name, "obb")) { + ret = 1; + } + } else if (parent_info->perm == PERM_ANDROID && + !strcasecmp(dentry->d_name.name, "obb")) { + ret = 1; + } + spin_unlock(&SDCARDFS_D(dentry)->lock); + return ret; +} + +/* The lower_path will be stored to the dentry's orig_path + * and the base obbpath will be copyed to the lower_path variable. + * if an error returned, there's no change in the lower_path + * returns: -ERRNO if error (0: no error) */ +int setup_obb_dentry(struct dentry *dentry, struct path *lower_path) +{ + int err = 0; + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + struct path obbpath; + + /* A local obb dentry must have its own orig_path to support rmdir + * and mkdir of itself. Usually, we expect that the sbi->obbpath + * is avaiable on this stage. */ + sdcardfs_set_orig_path(dentry, lower_path); + + err = kern_path(sbi->obbpath_s, + LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &obbpath); + + if(!err) { + /* the obbpath base has been found */ + printk(KERN_INFO "sdcardfs: the sbi->obbpath is found\n"); + pathcpy(lower_path, &obbpath); + } else { + /* if the sbi->obbpath is not available, we can optionally + * setup the lower_path with its orig_path. + * but, the current implementation just returns an error + * because the sdcard daemon also regards this case as + * a lookup fail. */ + printk(KERN_INFO "sdcardfs: the sbi->obbpath is not available\n"); + } + return err; +} + + diff --git a/fs/sdcardfs/file.c b/fs/sdcardfs/file.c new file mode 100644 index 000000000000..c249fa982d3c --- /dev/null +++ b/fs/sdcardfs/file.c @@ -0,0 +1,356 @@ +/* + * fs/sdcardfs/file.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" +#ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE +#include <linux/backing-dev.h> +#endif + +static ssize_t sdcardfs_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int err; + struct file *lower_file; + struct dentry *dentry = file->f_path.dentry; +#ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE + struct backing_dev_info *bdi; +#endif + + lower_file = sdcardfs_lower_file(file); + +#ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE + if (file->f_mode & FMODE_NOACTIVE) { + if (!(lower_file->f_mode & FMODE_NOACTIVE)) { + bdi = lower_file->f_mapping->backing_dev_info; + lower_file->f_ra.ra_pages = bdi->ra_pages * 2; + spin_lock(&lower_file->f_lock); + lower_file->f_mode |= FMODE_NOACTIVE; + spin_unlock(&lower_file->f_lock); + } + } +#endif + + err = vfs_read(lower_file, buf, count, ppos); + /* update our inode atime upon a successful lower read */ + if (err >= 0) + fsstack_copy_attr_atime(d_inode(dentry), + file_inode(lower_file)); + + return err; +} + +static ssize_t sdcardfs_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int err; + struct file *lower_file; + struct dentry *dentry = file->f_path.dentry; + + /* check disk space */ + if (!check_min_free_space(dentry, count, 0)) { + printk(KERN_INFO "No minimum free space.\n"); + return -ENOSPC; + } + + lower_file = sdcardfs_lower_file(file); + err = vfs_write(lower_file, buf, count, ppos); + /* update our inode times+sizes upon a successful lower write */ + if (err >= 0) { + fsstack_copy_inode_size(d_inode(dentry), + file_inode(lower_file)); + fsstack_copy_attr_times(d_inode(dentry), + file_inode(lower_file)); + } + + return err; +} + +static int sdcardfs_readdir(struct file *file, struct dir_context *ctx) +{ + int err; + struct file *lower_file = NULL; + struct dentry *dentry = file->f_path.dentry; + + lower_file = sdcardfs_lower_file(file); + + lower_file->f_pos = file->f_pos; + err = iterate_dir(lower_file, ctx); + file->f_pos = lower_file->f_pos; + if (err >= 0) /* copy the atime */ + fsstack_copy_attr_atime(d_inode(dentry), + file_inode(lower_file)); + return err; +} + +static long sdcardfs_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long err = -ENOTTY; + struct file *lower_file; + + lower_file = sdcardfs_lower_file(file); + + /* XXX: use vfs_ioctl if/when VFS exports it */ + if (!lower_file || !lower_file->f_op) + goto out; + if (lower_file->f_op->unlocked_ioctl) + err = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg); + +out: + return err; +} + +#ifdef CONFIG_COMPAT +static long sdcardfs_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long err = -ENOTTY; + struct file *lower_file; + + lower_file = sdcardfs_lower_file(file); + + /* XXX: use vfs_ioctl if/when VFS exports it */ + if (!lower_file || !lower_file->f_op) + goto out; + if (lower_file->f_op->compat_ioctl) + err = lower_file->f_op->compat_ioctl(lower_file, cmd, arg); + +out: + return err; +} +#endif + +static int sdcardfs_mmap(struct file *file, struct vm_area_struct *vma) +{ + int err = 0; + bool willwrite; + struct file *lower_file; + const struct vm_operations_struct *saved_vm_ops = NULL; + + /* this might be deferred to mmap's writepage */ + willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags); + + /* + * File systems which do not implement ->writepage may use + * generic_file_readonly_mmap as their ->mmap op. If you call + * generic_file_readonly_mmap with VM_WRITE, you'd get an -EINVAL. + * But we cannot call the lower ->mmap op, so we can't tell that + * writeable mappings won't work. Therefore, our only choice is to + * check if the lower file system supports the ->writepage, and if + * not, return EINVAL (the same error that + * generic_file_readonly_mmap returns in that case). + */ + lower_file = sdcardfs_lower_file(file); + if (willwrite && !lower_file->f_mapping->a_ops->writepage) { + err = -EINVAL; + printk(KERN_ERR "sdcardfs: lower file system does not " + "support writeable mmap\n"); + goto out; + } + + /* + * find and save lower vm_ops. + * + * XXX: the VFS should have a cleaner way of finding the lower vm_ops + */ + if (!SDCARDFS_F(file)->lower_vm_ops) { + err = lower_file->f_op->mmap(lower_file, vma); + if (err) { + printk(KERN_ERR "sdcardfs: lower mmap failed %d\n", err); + goto out; + } + saved_vm_ops = vma->vm_ops; /* save: came from lower ->mmap */ + err = do_munmap(current->mm, vma->vm_start, + vma->vm_end - vma->vm_start); + if (err) { + printk(KERN_ERR "sdcardfs: do_munmap failed %d\n", err); + goto out; + } + } + + /* + * Next 3 lines are all I need from generic_file_mmap. I definitely + * don't want its test for ->readpage which returns -ENOEXEC. + */ + file_accessed(file); + vma->vm_ops = &sdcardfs_vm_ops; + + file->f_mapping->a_ops = &sdcardfs_aops; /* set our aops */ + if (!SDCARDFS_F(file)->lower_vm_ops) /* save for our ->fault */ + SDCARDFS_F(file)->lower_vm_ops = saved_vm_ops; + +out: + return err; +} + +static int sdcardfs_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct file *lower_file = NULL; + struct path lower_path; + struct dentry *dentry = file->f_path.dentry; + struct dentry *parent = dget_parent(dentry); + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + const struct cred *saved_cred = NULL; + + /* don't open unhashed/deleted files */ + if (d_unhashed(dentry)) { + err = -ENOENT; + goto out_err; + } + + if(!check_caller_access_to_name(parent->d_inode, dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + err = -EACCES; + goto out_err; + } + + /* save current_cred and override it */ + OVERRIDE_CRED(sbi, saved_cred); + + file->private_data = + kzalloc(sizeof(struct sdcardfs_file_info), GFP_KERNEL); + if (!SDCARDFS_F(file)) { + err = -ENOMEM; + goto out_revert_cred; + } + + /* open lower object and link sdcardfs's file struct to lower's */ + sdcardfs_get_lower_path(file->f_path.dentry, &lower_path); + lower_file = dentry_open(&lower_path, file->f_flags, current_cred()); + path_put(&lower_path); + if (IS_ERR(lower_file)) { + err = PTR_ERR(lower_file); + lower_file = sdcardfs_lower_file(file); + if (lower_file) { + sdcardfs_set_lower_file(file, NULL); + fput(lower_file); /* fput calls dput for lower_dentry */ + } + } else { + sdcardfs_set_lower_file(file, lower_file); + } + + if (err) + kfree(SDCARDFS_F(file)); + else { + sdcardfs_copy_and_fix_attrs(inode, sdcardfs_lower_inode(inode)); + } + +out_revert_cred: + REVERT_CRED(saved_cred); +out_err: + dput(parent); + return err; +} + +static int sdcardfs_flush(struct file *file, fl_owner_t id) +{ + int err = 0; + struct file *lower_file = NULL; + + lower_file = sdcardfs_lower_file(file); + if (lower_file && lower_file->f_op && lower_file->f_op->flush) { + filemap_write_and_wait(file->f_mapping); + err = lower_file->f_op->flush(lower_file, id); + } + + return err; +} + +/* release all lower object references & free the file info structure */ +static int sdcardfs_file_release(struct inode *inode, struct file *file) +{ + struct file *lower_file; + + lower_file = sdcardfs_lower_file(file); + if (lower_file) { + sdcardfs_set_lower_file(file, NULL); + fput(lower_file); + } + + kfree(SDCARDFS_F(file)); + return 0; +} + +static int sdcardfs_fsync(struct file *file, loff_t start, loff_t end, + int datasync) +{ + int err; + struct file *lower_file; + struct path lower_path; + struct dentry *dentry = file->f_path.dentry; + + err = __generic_file_fsync(file, start, end, datasync); + if (err) + goto out; + + lower_file = sdcardfs_lower_file(file); + sdcardfs_get_lower_path(dentry, &lower_path); + err = vfs_fsync_range(lower_file, start, end, datasync); + sdcardfs_put_lower_path(dentry, &lower_path); +out: + return err; +} + +static int sdcardfs_fasync(int fd, struct file *file, int flag) +{ + int err = 0; + struct file *lower_file = NULL; + + lower_file = sdcardfs_lower_file(file); + if (lower_file->f_op && lower_file->f_op->fasync) + err = lower_file->f_op->fasync(fd, lower_file, flag); + + return err; +} + +const struct file_operations sdcardfs_main_fops = { + .llseek = generic_file_llseek, + .read = sdcardfs_read, + .write = sdcardfs_write, + .unlocked_ioctl = sdcardfs_unlocked_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = sdcardfs_compat_ioctl, +#endif + .mmap = sdcardfs_mmap, + .open = sdcardfs_open, + .flush = sdcardfs_flush, + .release = sdcardfs_file_release, + .fsync = sdcardfs_fsync, + .fasync = sdcardfs_fasync, +}; + +/* trimmed directory options */ +const struct file_operations sdcardfs_dir_fops = { + .llseek = generic_file_llseek, + .read = generic_read_dir, + .iterate = sdcardfs_readdir, + .unlocked_ioctl = sdcardfs_unlocked_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = sdcardfs_compat_ioctl, +#endif + .open = sdcardfs_open, + .release = sdcardfs_file_release, + .flush = sdcardfs_flush, + .fsync = sdcardfs_fsync, + .fasync = sdcardfs_fasync, +}; diff --git a/fs/sdcardfs/inode.c b/fs/sdcardfs/inode.c new file mode 100644 index 000000000000..2528da0d3ae1 --- /dev/null +++ b/fs/sdcardfs/inode.c @@ -0,0 +1,802 @@ +/* + * fs/sdcardfs/inode.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" + +/* Do not directly use this function. Use OVERRIDE_CRED() instead. */ +const struct cred * override_fsids(struct sdcardfs_sb_info* sbi) +{ + struct cred * cred; + const struct cred * old_cred; + + cred = prepare_creds(); + if (!cred) + return NULL; + + cred->fsuid = make_kuid(&init_user_ns, sbi->options.fs_low_uid); + cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid); + + old_cred = override_creds(cred); + + return old_cred; +} + +/* Do not directly use this function, use REVERT_CRED() instead. */ +void revert_fsids(const struct cred * old_cred) +{ + const struct cred * cur_cred; + + cur_cred = current->cred; + revert_creds(old_cred); + put_cred(cur_cred); +} + +static int sdcardfs_create(struct inode *dir, struct dentry *dentry, + umode_t mode, bool want_excl) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_parent_dentry = NULL; + struct path lower_path; + const struct cred *saved_cred = NULL; + + if(!check_caller_access_to_name(dir, dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + err = -EACCES; + goto out_eacces; + } + + /* save current_cred and override it */ + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred); + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_parent_dentry = lock_parent(lower_dentry); + + /* set last 16bytes of mode field to 0664 */ + mode = (mode & S_IFMT) | 00664; + err = vfs_create(d_inode(lower_parent_dentry), lower_dentry, mode, want_excl); + if (err) + goto out; + + err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path, SDCARDFS_I(dir)->userid); + if (err) + goto out; + fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir)); + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); + +out: + unlock_dir(lower_parent_dentry); + sdcardfs_put_lower_path(dentry, &lower_path); + REVERT_CRED(saved_cred); +out_eacces: + return err; +} + +#if 0 +static int sdcardfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + struct dentry *lower_old_dentry; + struct dentry *lower_new_dentry; + struct dentry *lower_dir_dentry; + u64 file_size_save; + int err; + struct path lower_old_path, lower_new_path; + + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb)); + + file_size_save = i_size_read(d_inode(old_dentry)); + sdcardfs_get_lower_path(old_dentry, &lower_old_path); + sdcardfs_get_lower_path(new_dentry, &lower_new_path); + lower_old_dentry = lower_old_path.dentry; + lower_new_dentry = lower_new_path.dentry; + lower_dir_dentry = lock_parent(lower_new_dentry); + + err = vfs_link(lower_old_dentry, d_inode(lower_dir_dentry), + lower_new_dentry, NULL); + if (err || !d_inode(lower_new_dentry)) + goto out; + + err = sdcardfs_interpose(new_dentry, dir->i_sb, &lower_new_path); + if (err) + goto out; + fsstack_copy_attr_times(dir, d_inode(lower_new_dentry)); + fsstack_copy_inode_size(dir, d_inode(lower_new_dentry)); + set_nlink(d_inode(old_dentry), + sdcardfs_lower_inode(d_inode(old_dentry))->i_nlink); + i_size_write(d_inode(new_dentry), file_size_save); +out: + unlock_dir(lower_dir_dentry); + sdcardfs_put_lower_path(old_dentry, &lower_old_path); + sdcardfs_put_lower_path(new_dentry, &lower_new_path); + REVERT_CRED(); + return err; +} +#endif + +static int sdcardfs_unlink(struct inode *dir, struct dentry *dentry) +{ + int err; + struct dentry *lower_dentry; + struct inode *lower_dir_inode = sdcardfs_lower_inode(dir); + struct dentry *lower_dir_dentry; + struct path lower_path; + const struct cred *saved_cred = NULL; + + if(!check_caller_access_to_name(dir, dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + err = -EACCES; + goto out_eacces; + } + + /* save current_cred and override it */ + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred); + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + dget(lower_dentry); + lower_dir_dentry = lock_parent(lower_dentry); + + err = vfs_unlink(lower_dir_inode, lower_dentry, NULL); + + /* + * Note: unlinking on top of NFS can cause silly-renamed files. + * Trying to delete such files results in EBUSY from NFS + * below. Silly-renamed files will get deleted by NFS later on, so + * we just need to detect them here and treat such EBUSY errors as + * if the upper file was successfully deleted. + */ + if (err == -EBUSY && lower_dentry->d_flags & DCACHE_NFSFS_RENAMED) + err = 0; + if (err) + goto out; + fsstack_copy_attr_times(dir, lower_dir_inode); + fsstack_copy_inode_size(dir, lower_dir_inode); + set_nlink(d_inode(dentry), + sdcardfs_lower_inode(d_inode(dentry))->i_nlink); + d_inode(dentry)->i_ctime = dir->i_ctime; + d_drop(dentry); /* this is needed, else LTP fails (VFS won't do it) */ +out: + unlock_dir(lower_dir_dentry); + dput(lower_dentry); + sdcardfs_put_lower_path(dentry, &lower_path); + REVERT_CRED(saved_cred); +out_eacces: + return err; +} + +#if 0 +static int sdcardfs_symlink(struct inode *dir, struct dentry *dentry, + const char *symname) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_parent_dentry = NULL; + struct path lower_path; + + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb)); + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_parent_dentry = lock_parent(lower_dentry); + + err = vfs_symlink(d_inode(lower_parent_dentry), lower_dentry, symname); + if (err) + goto out; + err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path); + if (err) + goto out; + fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir)); + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); + +out: + unlock_dir(lower_parent_dentry); + sdcardfs_put_lower_path(dentry, &lower_path); + REVERT_CRED(); + return err; +} +#endif + +static int touch(char *abs_path, mode_t mode) { + struct file *filp = filp_open(abs_path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, mode); + if (IS_ERR(filp)) { + if (PTR_ERR(filp) == -EEXIST) { + return 0; + } + else { + printk(KERN_ERR "sdcardfs: failed to open(%s): %ld\n", + abs_path, PTR_ERR(filp)); + return PTR_ERR(filp); + } + } + filp_close(filp, current->files); + return 0; +} + +static int sdcardfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + int err; + int make_nomedia_in_obb = 0; + struct dentry *lower_dentry; + struct dentry *lower_parent_dentry = NULL; + struct path lower_path; + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + const struct cred *saved_cred = NULL; + struct sdcardfs_inode_info *pi = SDCARDFS_I(dir); + char *page_buf; + char *nomedia_dir_name; + char *nomedia_fullpath; + int fullpath_namelen; + int touch_err = 0; + + if(!check_caller_access_to_name(dir, dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + err = -EACCES; + goto out_eacces; + } + + /* save current_cred and override it */ + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred); + + /* check disk space */ + if (!check_min_free_space(dentry, 0, 1)) { + printk(KERN_INFO "sdcardfs: No minimum free space.\n"); + err = -ENOSPC; + goto out_revert; + } + + /* the lower_dentry is negative here */ + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_parent_dentry = lock_parent(lower_dentry); + + /* set last 16bytes of mode field to 0775 */ + mode = (mode & S_IFMT) | 00775; + err = vfs_mkdir(d_inode(lower_parent_dentry), lower_dentry, mode); + + if (err) + goto out; + + /* if it is a local obb dentry, setup it with the base obbpath */ + if(need_graft_path(dentry)) { + + err = setup_obb_dentry(dentry, &lower_path); + if(err) { + /* if the sbi->obbpath is not available, the lower_path won't be + * changed by setup_obb_dentry() but the lower path is saved to + * its orig_path. this dentry will be revalidated later. + * but now, the lower_path should be NULL */ + sdcardfs_put_reset_lower_path(dentry); + + /* the newly created lower path which saved to its orig_path or + * the lower_path is the base obbpath. + * therefore, an additional path_get is required */ + path_get(&lower_path); + } else + make_nomedia_in_obb = 1; + } + + err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path, pi->userid); + if (err) + goto out; + + fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir)); + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); + /* update number of links on parent directory */ + set_nlink(dir, sdcardfs_lower_inode(dir)->i_nlink); + + if ((!sbi->options.multiuser) && (!strcasecmp(dentry->d_name.name, "obb")) + && (pi->perm == PERM_ANDROID) && (pi->userid == 0)) + make_nomedia_in_obb = 1; + + /* When creating /Android/data and /Android/obb, mark them as .nomedia */ + if (make_nomedia_in_obb || + ((pi->perm == PERM_ANDROID) && (!strcasecmp(dentry->d_name.name, "data")))) { + + page_buf = (char *)__get_free_page(GFP_KERNEL); + if (!page_buf) { + printk(KERN_ERR "sdcardfs: failed to allocate page buf\n"); + goto out; + } + + nomedia_dir_name = d_absolute_path(&lower_path, page_buf, PAGE_SIZE); + if (IS_ERR(nomedia_dir_name)) { + free_page((unsigned long)page_buf); + printk(KERN_ERR "sdcardfs: failed to get .nomedia dir name\n"); + goto out; + } + + fullpath_namelen = page_buf + PAGE_SIZE - nomedia_dir_name - 1; + fullpath_namelen += strlen("/.nomedia"); + nomedia_fullpath = kzalloc(fullpath_namelen + 1, GFP_KERNEL); + if (!nomedia_fullpath) { + free_page((unsigned long)page_buf); + printk(KERN_ERR "sdcardfs: failed to allocate .nomedia fullpath buf\n"); + goto out; + } + + strcpy(nomedia_fullpath, nomedia_dir_name); + free_page((unsigned long)page_buf); + strcat(nomedia_fullpath, "/.nomedia"); + touch_err = touch(nomedia_fullpath, 0664); + if (touch_err) { + printk(KERN_ERR "sdcardfs: failed to touch(%s): %d\n", + nomedia_fullpath, touch_err); + kfree(nomedia_fullpath); + goto out; + } + kfree(nomedia_fullpath); + } +out: + unlock_dir(lower_parent_dentry); + sdcardfs_put_lower_path(dentry, &lower_path); +out_revert: + REVERT_CRED(saved_cred); +out_eacces: + return err; +} + +static int sdcardfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct dentry *lower_dentry; + struct dentry *lower_dir_dentry; + int err; + struct path lower_path; + const struct cred *saved_cred = NULL; + + if(!check_caller_access_to_name(dir, dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + err = -EACCES; + goto out_eacces; + } + + /* save current_cred and override it */ + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred); + + /* sdcardfs_get_real_lower(): in case of remove an user's obb dentry + * the dentry on the original path should be deleted. */ + sdcardfs_get_real_lower(dentry, &lower_path); + + lower_dentry = lower_path.dentry; + lower_dir_dentry = lock_parent(lower_dentry); + + err = vfs_rmdir(d_inode(lower_dir_dentry), lower_dentry); + if (err) + goto out; + + d_drop(dentry); /* drop our dentry on success (why not VFS's job?) */ + if (d_inode(dentry)) + clear_nlink(d_inode(dentry)); + fsstack_copy_attr_times(dir, d_inode(lower_dir_dentry)); + fsstack_copy_inode_size(dir, d_inode(lower_dir_dentry)); + set_nlink(dir, d_inode(lower_dir_dentry)->i_nlink); + +out: + unlock_dir(lower_dir_dentry); + sdcardfs_put_real_lower(dentry, &lower_path); + REVERT_CRED(saved_cred); +out_eacces: + return err; +} + +#if 0 +static int sdcardfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, + dev_t dev) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_parent_dentry = NULL; + struct path lower_path; + + OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb)); + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_parent_dentry = lock_parent(lower_dentry); + + err = vfs_mknod(d_inode(lower_parent_dentry), lower_dentry, mode, dev); + if (err) + goto out; + + err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path); + if (err) + goto out; + fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir)); + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); + +out: + unlock_dir(lower_parent_dentry); + sdcardfs_put_lower_path(dentry, &lower_path); + REVERT_CRED(); + return err; +} +#endif + +/* + * The locking rules in sdcardfs_rename are complex. We could use a simpler + * superblock-level name-space lock for renames and copy-ups. + */ +static int sdcardfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + int err = 0; + struct dentry *lower_old_dentry = NULL; + struct dentry *lower_new_dentry = NULL; + struct dentry *lower_old_dir_dentry = NULL; + struct dentry *lower_new_dir_dentry = NULL; + struct dentry *trap = NULL; + struct dentry *new_parent = NULL; + struct path lower_old_path, lower_new_path; + const struct cred *saved_cred = NULL; + + if(!check_caller_access_to_name(old_dir, old_dentry->d_name.name) || + !check_caller_access_to_name(new_dir, new_dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " new_dentry: %s, task:%s\n", + __func__, new_dentry->d_name.name, current->comm); + err = -EACCES; + goto out_eacces; + } + + /* save current_cred and override it */ + OVERRIDE_CRED(SDCARDFS_SB(old_dir->i_sb), saved_cred); + + sdcardfs_get_real_lower(old_dentry, &lower_old_path); + sdcardfs_get_lower_path(new_dentry, &lower_new_path); + lower_old_dentry = lower_old_path.dentry; + lower_new_dentry = lower_new_path.dentry; + lower_old_dir_dentry = dget_parent(lower_old_dentry); + lower_new_dir_dentry = dget_parent(lower_new_dentry); + + trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + /* source should not be ancestor of target */ + if (trap == lower_old_dentry) { + err = -EINVAL; + goto out; + } + /* target should not be ancestor of source */ + if (trap == lower_new_dentry) { + err = -ENOTEMPTY; + goto out; + } + + err = vfs_rename(d_inode(lower_old_dir_dentry), lower_old_dentry, + d_inode(lower_new_dir_dentry), lower_new_dentry, + NULL, 0); + if (err) + goto out; + + /* Copy attrs from lower dir, but i_uid/i_gid */ + sdcardfs_copy_and_fix_attrs(new_dir, d_inode(lower_new_dir_dentry)); + fsstack_copy_inode_size(new_dir, d_inode(lower_new_dir_dentry)); + + if (new_dir != old_dir) { + sdcardfs_copy_and_fix_attrs(old_dir, d_inode(lower_old_dir_dentry)); + fsstack_copy_inode_size(old_dir, d_inode(lower_old_dir_dentry)); + + /* update the derived permission of the old_dentry + * with its new parent + */ + new_parent = dget_parent(new_dentry); + if(new_parent) { + if(d_inode(old_dentry)) { + update_derived_permission_lock(old_dentry); + } + dput(new_parent); + } + } + /* At this point, not all dentry information has been moved, so + * we pass along new_dentry for the name.*/ + mutex_lock(&d_inode(old_dentry)->i_mutex); + get_derived_permission_new(new_dentry->d_parent, old_dentry, new_dentry); + fix_derived_permission(d_inode(old_dentry)); + get_derive_permissions_recursive(old_dentry); + mutex_unlock(&d_inode(old_dentry)->i_mutex); +out: + unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + dput(lower_old_dir_dentry); + dput(lower_new_dir_dentry); + sdcardfs_put_real_lower(old_dentry, &lower_old_path); + sdcardfs_put_lower_path(new_dentry, &lower_new_path); + REVERT_CRED(saved_cred); +out_eacces: + return err; +} + +#if 0 +static int sdcardfs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) +{ + int err; + struct dentry *lower_dentry; + struct path lower_path; + /* XXX readlink does not requires overriding credential */ + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + if (!d_inode(lower_dentry)->i_op || + !d_inode(lower_dentry)->i_op->readlink) { + err = -EINVAL; + goto out; + } + + err = d_inode(lower_dentry)->i_op->readlink(lower_dentry, + buf, bufsiz); + if (err < 0) + goto out; + fsstack_copy_attr_atime(d_inode(dentry), d_inode(lower_dentry)); + +out: + sdcardfs_put_lower_path(dentry, &lower_path); + return err; +} +#endif + +#if 0 +static const char *sdcardfs_follow_link(struct dentry *dentry, void **cookie) +{ + char *buf; + int len = PAGE_SIZE, err; + mm_segment_t old_fs; + + /* This is freed by the put_link method assuming a successful call. */ + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + buf = ERR_PTR(-ENOMEM); + return buf; + } + + /* read the symlink, and then we will follow it */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = sdcardfs_readlink(dentry, buf, len); + set_fs(old_fs); + if (err < 0) { + kfree(buf); + buf = ERR_PTR(err); + } else { + buf[err] = '\0'; + } + return *cookie = buf; +} +#endif + +static int sdcardfs_permission(struct inode *inode, int mask) +{ + int err; + + /* + * Permission check on sdcardfs inode. + * Calling process should have AID_SDCARD_RW permission + */ + err = generic_permission(inode, mask); + + /* XXX + * Original sdcardfs code calls inode_permission(lower_inode,.. ) + * for checking inode permission. But doing such things here seems + * duplicated work, because the functions called after this func, + * such as vfs_create, vfs_unlink, vfs_rename, and etc, + * does exactly same thing, i.e., they calls inode_permission(). + * So we just let they do the things. + * If there are any security hole, just uncomment following if block. + */ +#if 0 + if (!err) { + /* + * Permission check on lower_inode(=EXT4). + * we check it with AID_MEDIA_RW permission + */ + struct inode *lower_inode; + OVERRIDE_CRED(SDCARDFS_SB(inode->sb)); + + lower_inode = sdcardfs_lower_inode(inode); + err = inode_permission(lower_inode, mask); + + REVERT_CRED(); + } +#endif + return err; + +} + +static int sdcardfs_setattr(struct dentry *dentry, struct iattr *ia) +{ + int err; + struct dentry *lower_dentry; + struct inode *inode; + struct inode *lower_inode; + struct path lower_path; + struct iattr lower_ia; + struct dentry *parent; + + inode = d_inode(dentry); + + /* + * Check if user has permission to change inode. We don't check if + * this user can change the lower inode: that should happen when + * calling notify_change on the lower inode. + */ + err = inode_change_ok(inode, ia); + + /* no vfs_XXX operations required, cred overriding will be skipped. wj*/ + if (!err) { + /* check the Android group ID */ + parent = dget_parent(dentry); + if(!check_caller_access_to_name(d_inode(parent), dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + err = -EACCES; + } + dput(parent); + } + + if (err) + goto out_err; + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_inode = sdcardfs_lower_inode(inode); + + /* prepare our own lower struct iattr (with the lower file) */ + memcpy(&lower_ia, ia, sizeof(lower_ia)); + if (ia->ia_valid & ATTR_FILE) + lower_ia.ia_file = sdcardfs_lower_file(ia->ia_file); + + lower_ia.ia_valid &= ~(ATTR_UID | ATTR_GID | ATTR_MODE); + + /* + * If shrinking, first truncate upper level to cancel writing dirty + * pages beyond the new eof; and also if its' maxbytes is more + * limiting (fail with -EFBIG before making any change to the lower + * level). There is no need to vmtruncate the upper level + * afterwards in the other cases: we fsstack_copy_inode_size from + * the lower level. + */ + if (current->mm) + down_write(¤t->mm->mmap_sem); + if (ia->ia_valid & ATTR_SIZE) { + err = inode_newsize_ok(inode, ia->ia_size); + if (err) { + if (current->mm) + up_write(¤t->mm->mmap_sem); + goto out; + } + truncate_setsize(inode, ia->ia_size); + } + + /* + * mode change is for clearing setuid/setgid bits. Allow lower fs + * to interpret this in its own way. + */ + if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) + lower_ia.ia_valid &= ~ATTR_MODE; + + /* notify the (possibly copied-up) lower inode */ + /* + * Note: we use d_inode(lower_dentry), because lower_inode may be + * unlinked (no inode->i_sb and i_ino==0. This happens if someone + * tries to open(), unlink(), then ftruncate() a file. + */ + mutex_lock(&d_inode(lower_dentry)->i_mutex); + err = notify_change(lower_dentry, &lower_ia, /* note: lower_ia */ + NULL); + mutex_unlock(&d_inode(lower_dentry)->i_mutex); + if (current->mm) + up_write(¤t->mm->mmap_sem); + if (err) + goto out; + + /* get attributes from the lower inode and update derived permissions */ + sdcardfs_copy_and_fix_attrs(inode, lower_inode); + + /* + * Not running fsstack_copy_inode_size(inode, lower_inode), because + * VFS should update our inode size, and notify_change on + * lower_inode should update its size. + */ + +out: + sdcardfs_put_lower_path(dentry, &lower_path); +out_err: + return err; +} + +static int sdcardfs_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat) +{ + struct dentry *lower_dentry; + struct inode *inode; + struct inode *lower_inode; + struct path lower_path; + struct dentry *parent; + + parent = dget_parent(dentry); + if(!check_caller_access_to_name(d_inode(parent), dentry->d_name.name)) { + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + dput(parent); + return -EACCES; + } + dput(parent); + + inode = d_inode(dentry); + + sdcardfs_get_lower_path(dentry, &lower_path); + lower_dentry = lower_path.dentry; + lower_inode = sdcardfs_lower_inode(inode); + + + sdcardfs_copy_and_fix_attrs(inode, lower_inode); + fsstack_copy_inode_size(inode, lower_inode); + + + generic_fillattr(inode, stat); + sdcardfs_put_lower_path(dentry, &lower_path); + return 0; +} + +const struct inode_operations sdcardfs_symlink_iops = { + .permission = sdcardfs_permission, + .setattr = sdcardfs_setattr, + /* XXX Following operations are implemented, + * but FUSE(sdcard) or FAT does not support them + * These methods are *NOT* perfectly tested. + .readlink = sdcardfs_readlink, + .follow_link = sdcardfs_follow_link, + .put_link = kfree_put_link, + */ +}; + +const struct inode_operations sdcardfs_dir_iops = { + .create = sdcardfs_create, + .lookup = sdcardfs_lookup, +#if 0 + .permission = sdcardfs_permission, +#endif + .unlink = sdcardfs_unlink, + .mkdir = sdcardfs_mkdir, + .rmdir = sdcardfs_rmdir, + .rename = sdcardfs_rename, + .setattr = sdcardfs_setattr, + .getattr = sdcardfs_getattr, + /* XXX Following operations are implemented, + * but FUSE(sdcard) or FAT does not support them + * These methods are *NOT* perfectly tested. + .symlink = sdcardfs_symlink, + .link = sdcardfs_link, + .mknod = sdcardfs_mknod, + */ +}; + +const struct inode_operations sdcardfs_main_iops = { + .permission = sdcardfs_permission, + .setattr = sdcardfs_setattr, + .getattr = sdcardfs_getattr, +}; diff --git a/fs/sdcardfs/lookup.c b/fs/sdcardfs/lookup.c new file mode 100644 index 000000000000..a01b06a514fd --- /dev/null +++ b/fs/sdcardfs/lookup.c @@ -0,0 +1,384 @@ +/* + * fs/sdcardfs/lookup.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" +#include "linux/delay.h" + +/* The dentry cache is just so we have properly sized dentries */ +static struct kmem_cache *sdcardfs_dentry_cachep; + +int sdcardfs_init_dentry_cache(void) +{ + sdcardfs_dentry_cachep = + kmem_cache_create("sdcardfs_dentry", + sizeof(struct sdcardfs_dentry_info), + 0, SLAB_RECLAIM_ACCOUNT, NULL); + + return sdcardfs_dentry_cachep ? 0 : -ENOMEM; +} + +void sdcardfs_destroy_dentry_cache(void) +{ + if (sdcardfs_dentry_cachep) + kmem_cache_destroy(sdcardfs_dentry_cachep); +} + +void free_dentry_private_data(struct dentry *dentry) +{ + if (!dentry || !dentry->d_fsdata) + return; + kmem_cache_free(sdcardfs_dentry_cachep, dentry->d_fsdata); + dentry->d_fsdata = NULL; +} + +/* allocate new dentry private data */ +int new_dentry_private_data(struct dentry *dentry) +{ + struct sdcardfs_dentry_info *info = SDCARDFS_D(dentry); + + /* use zalloc to init dentry_info.lower_path */ + info = kmem_cache_zalloc(sdcardfs_dentry_cachep, GFP_ATOMIC); + if (!info) + return -ENOMEM; + + spin_lock_init(&info->lock); + dentry->d_fsdata = info; + + return 0; +} + +struct inode_data { + struct inode *lower_inode; + userid_t id; +}; + +static int sdcardfs_inode_test(struct inode *inode, void *candidate_data/*void *candidate_lower_inode*/) +{ + struct inode *current_lower_inode = sdcardfs_lower_inode(inode); + userid_t current_userid = SDCARDFS_I(inode)->userid; + if (current_lower_inode == ((struct inode_data *)candidate_data)->lower_inode && + current_userid == ((struct inode_data *)candidate_data)->id) + return 1; /* found a match */ + else + return 0; /* no match */ +} + +static int sdcardfs_inode_set(struct inode *inode, void *lower_inode) +{ + /* we do actual inode initialization in sdcardfs_iget */ + return 0; +} + +struct inode *sdcardfs_iget(struct super_block *sb, struct inode *lower_inode, userid_t id) +{ + struct sdcardfs_inode_info *info; + struct inode_data data; + struct inode *inode; /* the new inode to return */ + int err; + + data.id = id; + data.lower_inode = lower_inode; + inode = iget5_locked(sb, /* our superblock */ + /* + * hashval: we use inode number, but we can + * also use "(unsigned long)lower_inode" + * instead. + */ + lower_inode->i_ino, /* hashval */ + sdcardfs_inode_test, /* inode comparison function */ + sdcardfs_inode_set, /* inode init function */ + &data); /* data passed to test+set fxns */ + if (!inode) { + err = -EACCES; + iput(lower_inode); + return ERR_PTR(err); + } + /* if found a cached inode, then just return it */ + if (!(inode->i_state & I_NEW)) + return inode; + + /* initialize new inode */ + info = SDCARDFS_I(inode); + + inode->i_ino = lower_inode->i_ino; + if (!igrab(lower_inode)) { + err = -ESTALE; + return ERR_PTR(err); + } + sdcardfs_set_lower_inode(inode, lower_inode); + + inode->i_version++; + + /* use different set of inode ops for symlinks & directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_op = &sdcardfs_dir_iops; + else if (S_ISLNK(lower_inode->i_mode)) + inode->i_op = &sdcardfs_symlink_iops; + else + inode->i_op = &sdcardfs_main_iops; + + /* use different set of file ops for directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_fop = &sdcardfs_dir_fops; + else + inode->i_fop = &sdcardfs_main_fops; + + inode->i_mapping->a_ops = &sdcardfs_aops; + + inode->i_atime.tv_sec = 0; + inode->i_atime.tv_nsec = 0; + inode->i_mtime.tv_sec = 0; + inode->i_mtime.tv_nsec = 0; + inode->i_ctime.tv_sec = 0; + inode->i_ctime.tv_nsec = 0; + + /* properly initialize special inodes */ + if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || + S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) + init_special_inode(inode, lower_inode->i_mode, + lower_inode->i_rdev); + + /* all well, copy inode attributes */ + sdcardfs_copy_and_fix_attrs(inode, lower_inode); + fsstack_copy_inode_size(inode, lower_inode); + + unlock_new_inode(inode); + return inode; +} + +/* + * Connect a sdcardfs inode dentry/inode with several lower ones. This is + * the classic stackable file system "vnode interposition" action. + * + * @dentry: sdcardfs's dentry which interposes on lower one + * @sb: sdcardfs's super_block + * @lower_path: the lower path (caller does path_get/put) + */ +int sdcardfs_interpose(struct dentry *dentry, struct super_block *sb, + struct path *lower_path, userid_t id) +{ + int err = 0; + struct inode *inode; + struct inode *lower_inode; + struct super_block *lower_sb; + + lower_inode = lower_path->dentry->d_inode; + lower_sb = sdcardfs_lower_super(sb); + + /* check that the lower file system didn't cross a mount point */ + if (lower_inode->i_sb != lower_sb) { + err = -EXDEV; + goto out; + } + + /* + * We allocate our new inode below by calling sdcardfs_iget, + * which will initialize some of the new inode's fields + */ + + /* inherit lower inode number for sdcardfs's inode */ + inode = sdcardfs_iget(sb, lower_inode, id); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + + d_add(dentry, inode); + update_derived_permission_lock(dentry); +out: + return err; +} + +/* + * Main driver function for sdcardfs's lookup. + * + * Returns: NULL (ok), ERR_PTR if an error occurred. + * Fills in lower_parent_path with <dentry,mnt> on success. + */ +static struct dentry *__sdcardfs_lookup(struct dentry *dentry, + unsigned int flags, struct path *lower_parent_path, userid_t id) +{ + int err = 0; + struct vfsmount *lower_dir_mnt; + struct dentry *lower_dir_dentry = NULL; + struct dentry *lower_dentry; + const char *name; + struct path lower_path; + struct qstr this; + struct sdcardfs_sb_info *sbi; + + sbi = SDCARDFS_SB(dentry->d_sb); + /* must initialize dentry operations */ + d_set_d_op(dentry, &sdcardfs_ci_dops); + + if (IS_ROOT(dentry)) + goto out; + + name = dentry->d_name.name; + + /* now start the actual lookup procedure */ + lower_dir_dentry = lower_parent_path->dentry; + lower_dir_mnt = lower_parent_path->mnt; + + /* Use vfs_path_lookup to check if the dentry exists or not */ + err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name, 0, + &lower_path); + + /* no error: handle positive dentries */ + if (!err) { + /* check if the dentry is an obb dentry + * if true, the lower_inode must be replaced with + * the inode of the graft path */ + + if(need_graft_path(dentry)) { + + /* setup_obb_dentry() + * The lower_path will be stored to the dentry's orig_path + * and the base obbpath will be copyed to the lower_path variable. + * if an error returned, there's no change in the lower_path + * returns: -ERRNO if error (0: no error) */ + err = setup_obb_dentry(dentry, &lower_path); + + if(err) { + /* if the sbi->obbpath is not available, we can optionally + * setup the lower_path with its orig_path. + * but, the current implementation just returns an error + * because the sdcard daemon also regards this case as + * a lookup fail. */ + printk(KERN_INFO "sdcardfs: base obbpath is not available\n"); + sdcardfs_put_reset_orig_path(dentry); + goto out; + } + } + + sdcardfs_set_lower_path(dentry, &lower_path); + err = sdcardfs_interpose(dentry, dentry->d_sb, &lower_path, id); + if (err) /* path_put underlying path on error */ + sdcardfs_put_reset_lower_path(dentry); + goto out; + } + + /* + * We don't consider ENOENT an error, and we want to return a + * negative dentry. + */ + if (err && err != -ENOENT) + goto out; + + /* instatiate a new negative dentry */ + this.name = name; + this.len = strlen(name); + this.hash = full_name_hash(this.name, this.len); + lower_dentry = d_lookup(lower_dir_dentry, &this); + if (lower_dentry) + goto setup_lower; + + lower_dentry = d_alloc(lower_dir_dentry, &this); + if (!lower_dentry) { + err = -ENOMEM; + goto out; + } + d_add(lower_dentry, NULL); /* instantiate and hash */ + +setup_lower: + lower_path.dentry = lower_dentry; + lower_path.mnt = mntget(lower_dir_mnt); + sdcardfs_set_lower_path(dentry, &lower_path); + + /* + * If the intent is to create a file, then don't return an error, so + * the VFS will continue the process of making this negative dentry + * into a positive one. + */ + if (flags & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET)) + err = 0; + +out: + return ERR_PTR(err); +} + +/* + * On success: + * fills dentry object appropriate values and returns NULL. + * On fail (== error) + * returns error ptr + * + * @dir : Parent inode. It is locked (dir->i_mutex) + * @dentry : Target dentry to lookup. we should set each of fields. + * (dentry->d_name is initialized already) + * @nd : nameidata of parent inode + */ +struct dentry *sdcardfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct dentry *ret = NULL, *parent; + struct path lower_parent_path; + int err = 0; + const struct cred *saved_cred = NULL; + + parent = dget_parent(dentry); + + if(!check_caller_access_to_name(parent->d_inode, dentry->d_name.name)) { + ret = ERR_PTR(-EACCES); + printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n" + " dentry: %s, task:%s\n", + __func__, dentry->d_name.name, current->comm); + goto out_err; + } + + /* save current_cred and override it */ + OVERRIDE_CRED_PTR(SDCARDFS_SB(dir->i_sb), saved_cred); + + sdcardfs_get_lower_path(parent, &lower_parent_path); + + /* allocate dentry private data. We free it in ->d_release */ + err = new_dentry_private_data(dentry); + if (err) { + ret = ERR_PTR(err); + goto out; + } + + ret = __sdcardfs_lookup(dentry, flags, &lower_parent_path, SDCARDFS_I(dir)->userid); + if (IS_ERR(ret)) + { + goto out; + } + if (ret) + dentry = ret; + if (dentry->d_inode) { + fsstack_copy_attr_times(dentry->d_inode, + sdcardfs_lower_inode(dentry->d_inode)); + /* get drived permission */ + mutex_lock(&dentry->d_inode->i_mutex); + get_derived_permission(parent, dentry); + fix_derived_permission(dentry->d_inode); + mutex_unlock(&dentry->d_inode->i_mutex); + } + /* update parent directory's atime */ + fsstack_copy_attr_atime(parent->d_inode, + sdcardfs_lower_inode(parent->d_inode)); + +out: + sdcardfs_put_lower_path(parent, &lower_parent_path); + REVERT_CRED(saved_cred); +out_err: + dput(parent); + return ret; +} diff --git a/fs/sdcardfs/main.c b/fs/sdcardfs/main.c new file mode 100644 index 000000000000..a6522286d731 --- /dev/null +++ b/fs/sdcardfs/main.c @@ -0,0 +1,402 @@ +/* + * fs/sdcardfs/main.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" +#include <linux/module.h> +#include <linux/types.h> +#include <linux/parser.h> + +enum { + Opt_fsuid, + Opt_fsgid, + Opt_gid, + Opt_debug, + Opt_lower_fs, + Opt_mask, + Opt_multiuser, // May need? + Opt_userid, + Opt_reserved_mb, + Opt_err, +}; + +static const match_table_t sdcardfs_tokens = { + {Opt_fsuid, "fsuid=%u"}, + {Opt_fsgid, "fsgid=%u"}, + {Opt_gid, "gid=%u"}, + {Opt_debug, "debug"}, + {Opt_mask, "mask=%u"}, + {Opt_userid, "userid=%d"}, + {Opt_multiuser, "multiuser"}, + {Opt_reserved_mb, "reserved_mb=%u"}, + {Opt_err, NULL} +}; + +static int parse_options(struct super_block *sb, char *options, int silent, + int *debug, struct sdcardfs_mount_options *opts) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + int option; + + /* by default, we use AID_MEDIA_RW as uid, gid */ + opts->fs_low_uid = AID_MEDIA_RW; + opts->fs_low_gid = AID_MEDIA_RW; + opts->mask = 0; + opts->multiuser = false; + opts->fs_user_id = 0; + opts->gid = 0; + /* by default, 0MB is reserved */ + opts->reserved_mb = 0; + + *debug = 0; + + if (!options) + return 0; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + if (!*p) + continue; + + token = match_token(p, sdcardfs_tokens, args); + + switch (token) { + case Opt_debug: + *debug = 1; + break; + case Opt_fsuid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_low_uid = option; + break; + case Opt_fsgid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_low_gid = option; + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return 0; + opts->gid = option; + break; + case Opt_userid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_user_id = option; + break; + case Opt_mask: + if (match_int(&args[0], &option)) + return 0; + opts->mask = option; + break; + case Opt_multiuser: + opts->multiuser = true; + break; + case Opt_reserved_mb: + if (match_int(&args[0], &option)) + return 0; + opts->reserved_mb = option; + break; + /* unknown option */ + default: + if (!silent) { + printk( KERN_ERR "Unrecognized mount option \"%s\" " + "or missing value", p); + } + return -EINVAL; + } + } + + if (*debug) { + printk( KERN_INFO "sdcardfs : options - debug:%d\n", *debug); + printk( KERN_INFO "sdcardfs : options - uid:%d\n", + opts->fs_low_uid); + printk( KERN_INFO "sdcardfs : options - gid:%d\n", + opts->fs_low_gid); + } + + return 0; +} + +#if 0 +/* + * our custom d_alloc_root work-alike + * + * we can't use d_alloc_root if we want to use our own interpose function + * unchanged, so we simply call our own "fake" d_alloc_root + */ +static struct dentry *sdcardfs_d_alloc_root(struct super_block *sb) +{ + struct dentry *ret = NULL; + + if (sb) { + static const struct qstr name = { + .name = "/", + .len = 1 + }; + + ret = d_alloc(NULL, &name); + if (ret) { + d_set_d_op(ret, &sdcardfs_ci_dops); + ret->d_sb = sb; + ret->d_parent = ret; + } + } + return ret; +} +#endif + +DEFINE_MUTEX(sdcardfs_super_list_lock); +LIST_HEAD(sdcardfs_super_list); +EXPORT_SYMBOL_GPL(sdcardfs_super_list_lock); +EXPORT_SYMBOL_GPL(sdcardfs_super_list); + +/* + * There is no need to lock the sdcardfs_super_info's rwsem as there is no + * way anyone can have a reference to the superblock at this point in time. + */ +static int sdcardfs_read_super(struct super_block *sb, const char *dev_name, + void *raw_data, int silent) +{ + int err = 0; + int debug; + struct super_block *lower_sb; + struct path lower_path; + struct sdcardfs_sb_info *sb_info; + struct inode *inode; + + printk(KERN_INFO "sdcardfs version 2.0\n"); + + if (!dev_name) { + printk(KERN_ERR + "sdcardfs: read_super: missing dev_name argument\n"); + err = -EINVAL; + goto out; + } + + printk(KERN_INFO "sdcardfs: dev_name -> %s\n", dev_name); + printk(KERN_INFO "sdcardfs: options -> %s\n", (char *)raw_data); + + /* parse lower path */ + err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, + &lower_path); + if (err) { + printk(KERN_ERR "sdcardfs: error accessing lower directory '%s'\n", dev_name); + goto out; + } + + /* allocate superblock private data */ + sb->s_fs_info = kzalloc(sizeof(struct sdcardfs_sb_info), GFP_KERNEL); + if (!SDCARDFS_SB(sb)) { + printk(KERN_CRIT "sdcardfs: read_super: out of memory\n"); + err = -ENOMEM; + goto out_free; + } + + sb_info = sb->s_fs_info; + /* parse options */ + err = parse_options(sb, raw_data, silent, &debug, &sb_info->options); + if (err) { + printk(KERN_ERR "sdcardfs: invalid options\n"); + goto out_freesbi; + } + + /* set the lower superblock field of upper superblock */ + lower_sb = lower_path.dentry->d_sb; + atomic_inc(&lower_sb->s_active); + sdcardfs_set_lower_super(sb, lower_sb); + + /* inherit maxbytes from lower file system */ + sb->s_maxbytes = lower_sb->s_maxbytes; + + /* + * Our c/m/atime granularity is 1 ns because we may stack on file + * systems whose granularity is as good. + */ + sb->s_time_gran = 1; + + sb->s_magic = SDCARDFS_SUPER_MAGIC; + sb->s_op = &sdcardfs_sops; + + /* get a new inode and allocate our root dentry */ + inode = sdcardfs_iget(sb, lower_path.dentry->d_inode, 0); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out_sput; + } + sb->s_root = d_make_root(inode); + if (!sb->s_root) { + err = -ENOMEM; + goto out_iput; + } + d_set_d_op(sb->s_root, &sdcardfs_ci_dops); + + /* link the upper and lower dentries */ + sb->s_root->d_fsdata = NULL; + err = new_dentry_private_data(sb->s_root); + if (err) + goto out_freeroot; + + /* set the lower dentries for s_root */ + sdcardfs_set_lower_path(sb->s_root, &lower_path); + + /* + * No need to call interpose because we already have a positive + * dentry, which was instantiated by d_make_root. Just need to + * d_rehash it. + */ + d_rehash(sb->s_root); + + /* setup permission policy */ + sb_info->obbpath_s = kzalloc(PATH_MAX, GFP_KERNEL); + mutex_lock(&sdcardfs_super_list_lock); + if(sb_info->options.multiuser) { + setup_derived_state(sb->s_root->d_inode, PERM_PRE_ROOT, sb_info->options.fs_user_id, AID_ROOT, false); + snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name); + /*err = prepare_dir(sb_info->obbpath_s, + sb_info->options.fs_low_uid, + sb_info->options.fs_low_gid, 00755);*/ + } else { + setup_derived_state(sb->s_root->d_inode, PERM_ROOT, sb_info->options.fs_low_uid, AID_ROOT, false); + snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name); + } + fix_derived_permission(sb->s_root->d_inode); + sb_info->sb = sb; + list_add(&sb_info->list, &sdcardfs_super_list); + mutex_unlock(&sdcardfs_super_list_lock); + + if (!silent) + printk(KERN_INFO "sdcardfs: mounted on top of %s type %s\n", + dev_name, lower_sb->s_type->name); + goto out; /* all is well */ + + /* no longer needed: free_dentry_private_data(sb->s_root); */ +out_freeroot: + dput(sb->s_root); +out_iput: + iput(inode); +out_sput: + /* drop refs we took earlier */ + atomic_dec(&lower_sb->s_active); +out_freesbi: + kfree(SDCARDFS_SB(sb)); + sb->s_fs_info = NULL; +out_free: + path_put(&lower_path); + +out: + return err; +} + +/* A feature which supports mount_nodev() with options */ +static struct dentry *mount_nodev_with_options(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, + int (*fill_super)(struct super_block *, const char *, void *, int)) + +{ + int error; + struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL); + + if (IS_ERR(s)) + return ERR_CAST(s); + + s->s_flags = flags; + + error = fill_super(s, dev_name, data, flags & MS_SILENT ? 1 : 0); + if (error) { + deactivate_locked_super(s); + return ERR_PTR(error); + } + s->s_flags |= MS_ACTIVE; + return dget(s->s_root); +} + +struct dentry *sdcardfs_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *raw_data) +{ + /* + * dev_name is a lower_path_name, + * raw_data is a option string. + */ + return mount_nodev_with_options(fs_type, flags, dev_name, + raw_data, sdcardfs_read_super); +} + +void sdcardfs_kill_sb(struct super_block *sb) { + struct sdcardfs_sb_info *sbi; + if (sb->s_magic == SDCARDFS_SUPER_MAGIC) { + sbi = SDCARDFS_SB(sb); + mutex_lock(&sdcardfs_super_list_lock); + list_del(&sbi->list); + mutex_unlock(&sdcardfs_super_list_lock); + } + generic_shutdown_super(sb); +} + +static struct file_system_type sdcardfs_fs_type = { + .owner = THIS_MODULE, + .name = SDCARDFS_NAME, + .mount = sdcardfs_mount, + .kill_sb = sdcardfs_kill_sb, + .fs_flags = 0, +}; + +static int __init init_sdcardfs_fs(void) +{ + int err; + + pr_info("Registering sdcardfs " SDCARDFS_VERSION "\n"); + + err = sdcardfs_init_inode_cache(); + if (err) + goto out; + err = sdcardfs_init_dentry_cache(); + if (err) + goto out; + err = packagelist_init(); + if (err) + goto out; + err = register_filesystem(&sdcardfs_fs_type); +out: + if (err) { + sdcardfs_destroy_inode_cache(); + sdcardfs_destroy_dentry_cache(); + packagelist_exit(); + } + return err; +} + +static void __exit exit_sdcardfs_fs(void) +{ + sdcardfs_destroy_inode_cache(); + sdcardfs_destroy_dentry_cache(); + packagelist_exit(); + unregister_filesystem(&sdcardfs_fs_type); + pr_info("Completed sdcardfs module unload\n"); +} + +MODULE_AUTHOR("Erez Zadok, Filesystems and Storage Lab, Stony Brook University" + " (http://www.fsl.cs.sunysb.edu/)"); +MODULE_DESCRIPTION("Wrapfs " SDCARDFS_VERSION + " (http://wrapfs.filesystems.org/)"); +MODULE_LICENSE("GPL"); + +module_init(init_sdcardfs_fs); +module_exit(exit_sdcardfs_fs); diff --git a/fs/sdcardfs/mmap.c b/fs/sdcardfs/mmap.c new file mode 100644 index 000000000000..e21f64675a80 --- /dev/null +++ b/fs/sdcardfs/mmap.c @@ -0,0 +1,81 @@ +/* + * fs/sdcardfs/mmap.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" + +static int sdcardfs_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + int err; + struct file *file, *lower_file; + const struct vm_operations_struct *lower_vm_ops; + struct vm_area_struct lower_vma; + + memcpy(&lower_vma, vma, sizeof(struct vm_area_struct)); + file = lower_vma.vm_file; + lower_vm_ops = SDCARDFS_F(file)->lower_vm_ops; + BUG_ON(!lower_vm_ops); + + lower_file = sdcardfs_lower_file(file); + /* + * XXX: vm_ops->fault may be called in parallel. Because we have to + * resort to temporarily changing the vma->vm_file to point to the + * lower file, a concurrent invocation of sdcardfs_fault could see a + * different value. In this workaround, we keep a different copy of + * the vma structure in our stack, so we never expose a different + * value of the vma->vm_file called to us, even temporarily. A + * better fix would be to change the calling semantics of ->fault to + * take an explicit file pointer. + */ + lower_vma.vm_file = lower_file; + err = lower_vm_ops->fault(&lower_vma, vmf); + return err; +} + +static ssize_t sdcardfs_direct_IO(struct kiocb *iocb, + struct iov_iter *iter, loff_t pos) +{ + /* + * This function returns zero on purpose in order to support direct IO. + * __dentry_open checks a_ops->direct_IO and returns EINVAL if it is null. + * + * However, this function won't be called by certain file operations + * including generic fs functions. * reads and writes are delivered to + * the lower file systems and the direct IOs will be handled by them. + * + * NOTE: exceptionally, on the recent kernels (since Linux 3.8.x), + * swap_writepage invokes this function directly. + */ + printk(KERN_INFO "%s, operation is not supported\n", __func__); + return 0; +} + +/* + * XXX: the default address_space_ops for sdcardfs is empty. We cannot set + * our inode->i_mapping->a_ops to NULL because too many code paths expect + * the a_ops vector to be non-NULL. + */ +const struct address_space_operations sdcardfs_aops = { + /* empty on purpose */ + .direct_IO = sdcardfs_direct_IO, +}; + +const struct vm_operations_struct sdcardfs_vm_ops = { + .fault = sdcardfs_fault, +}; diff --git a/fs/sdcardfs/multiuser.h b/fs/sdcardfs/multiuser.h new file mode 100644 index 000000000000..923ba101dfa9 --- /dev/null +++ b/fs/sdcardfs/multiuser.h @@ -0,0 +1,37 @@ +/* + * fs/sdcardfs/multiuser.h + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#define MULTIUSER_APP_PER_USER_RANGE 100000 + +typedef uid_t userid_t; +typedef uid_t appid_t; + +static inline userid_t multiuser_get_user_id(uid_t uid) { + return uid / MULTIUSER_APP_PER_USER_RANGE; +} + +static inline appid_t multiuser_get_app_id(uid_t uid) { + return uid % MULTIUSER_APP_PER_USER_RANGE; +} + +static inline uid_t multiuser_get_uid(userid_t userId, appid_t appId) { + return userId * MULTIUSER_APP_PER_USER_RANGE + (appId % MULTIUSER_APP_PER_USER_RANGE); +} + diff --git a/fs/sdcardfs/packagelist.c b/fs/sdcardfs/packagelist.c new file mode 100644 index 000000000000..10f0d6be718b --- /dev/null +++ b/fs/sdcardfs/packagelist.c @@ -0,0 +1,437 @@ +/* + * fs/sdcardfs/packagelist.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" +#include <linux/hashtable.h> +#include <linux/delay.h> + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <linux/configfs.h> + +#define STRING_BUF_SIZE (512) + +struct hashtable_entry { + struct hlist_node hlist; + void *key; + unsigned int value; +}; + +struct sb_list { + struct super_block *sb; + struct list_head list; +}; + +struct packagelist_data { + DECLARE_HASHTABLE(package_to_appid,8); + struct mutex hashtable_lock; + +}; + +static struct packagelist_data *pkgl_data_all; + +static struct kmem_cache *hashtable_entry_cachep; + +static unsigned int str_hash(const char *key) { + int i; + unsigned int h = strlen(key); + char *data = (char *)key; + + for (i = 0; i < strlen(key); i++) { + h = h * 31 + *data; + data++; + } + return h; +} + +appid_t get_appid(void *pkgl_id, const char *app_name) +{ + struct packagelist_data *pkgl_dat = pkgl_data_all; + struct hashtable_entry *hash_cur; + unsigned int hash = str_hash(app_name); + appid_t ret_id; + + mutex_lock(&pkgl_dat->hashtable_lock); + hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) { + if (!strcasecmp(app_name, hash_cur->key)) { + ret_id = (appid_t)hash_cur->value; + mutex_unlock(&pkgl_dat->hashtable_lock); + return ret_id; + } + } + mutex_unlock(&pkgl_dat->hashtable_lock); + return 0; +} + +/* Kernel has already enforced everything we returned through + * derive_permissions_locked(), so this is used to lock down access + * even further, such as enforcing that apps hold sdcard_rw. */ +int check_caller_access_to_name(struct inode *parent_node, const char* name) { + + /* Always block security-sensitive files at root */ + if (parent_node && SDCARDFS_I(parent_node)->perm == PERM_ROOT) { + if (!strcasecmp(name, "autorun.inf") + || !strcasecmp(name, ".android_secure") + || !strcasecmp(name, "android_secure")) { + return 0; + } + } + + /* Root always has access; access for any other UIDs should always + * be controlled through packages.list. */ + if (from_kuid(&init_user_ns, current_fsuid()) == 0) { + return 1; + } + + /* No extra permissions to enforce */ + return 1; +} + +/* This function is used when file opening. The open flags must be + * checked before calling check_caller_access_to_name() */ +int open_flags_to_access_mode(int open_flags) { + if((open_flags & O_ACCMODE) == O_RDONLY) { + return 0; /* R_OK */ + } else if ((open_flags & O_ACCMODE) == O_WRONLY) { + return 1; /* W_OK */ + } else { + /* Probably O_RDRW, but treat as default to be safe */ + return 1; /* R_OK | W_OK */ + } +} + +static int insert_str_to_int_lock(struct packagelist_data *pkgl_dat, char *key, + unsigned int value) +{ + struct hashtable_entry *hash_cur; + struct hashtable_entry *new_entry; + unsigned int hash = str_hash(key); + + hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) { + if (!strcasecmp(key, hash_cur->key)) { + hash_cur->value = value; + return 0; + } + } + new_entry = kmem_cache_alloc(hashtable_entry_cachep, GFP_KERNEL); + if (!new_entry) + return -ENOMEM; + new_entry->key = kstrdup(key, GFP_KERNEL); + new_entry->value = value; + hash_add(pkgl_dat->package_to_appid, &new_entry->hlist, hash); + return 0; +} + +static void fixup_perms(struct super_block *sb) { + if (sb && sb->s_magic == SDCARDFS_SUPER_MAGIC) { + mutex_lock(&sb->s_root->d_inode->i_mutex); + get_derive_permissions_recursive(sb->s_root); + mutex_unlock(&sb->s_root->d_inode->i_mutex); + } +} + +static int insert_str_to_int(struct packagelist_data *pkgl_dat, char *key, + unsigned int value) { + int ret; + struct sdcardfs_sb_info *sbinfo; + mutex_lock(&sdcardfs_super_list_lock); + mutex_lock(&pkgl_dat->hashtable_lock); + ret = insert_str_to_int_lock(pkgl_dat, key, value); + mutex_unlock(&pkgl_dat->hashtable_lock); + + list_for_each_entry(sbinfo, &sdcardfs_super_list, list) { + if (sbinfo) { + fixup_perms(sbinfo->sb); + } + } + mutex_unlock(&sdcardfs_super_list_lock); + return ret; +} + +static void remove_str_to_int_lock(struct hashtable_entry *h_entry) { + kfree(h_entry->key); + hash_del(&h_entry->hlist); + kmem_cache_free(hashtable_entry_cachep, h_entry); +} + +static void remove_str_to_int(struct packagelist_data *pkgl_dat, const char *key) +{ + struct sdcardfs_sb_info *sbinfo; + struct hashtable_entry *hash_cur; + unsigned int hash = str_hash(key); + mutex_lock(&sdcardfs_super_list_lock); + mutex_lock(&pkgl_dat->hashtable_lock); + hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) { + if (!strcasecmp(key, hash_cur->key)) { + remove_str_to_int_lock(hash_cur); + break; + } + } + mutex_unlock(&pkgl_dat->hashtable_lock); + list_for_each_entry(sbinfo, &sdcardfs_super_list, list) { + if (sbinfo) { + fixup_perms(sbinfo->sb); + } + } + mutex_unlock(&sdcardfs_super_list_lock); + return; +} + +static void remove_all_hashentrys(struct packagelist_data *pkgl_dat) +{ + struct hashtable_entry *hash_cur; + struct hlist_node *h_t; + int i; + mutex_lock(&pkgl_dat->hashtable_lock); + hash_for_each_safe(pkgl_dat->package_to_appid, i, h_t, hash_cur, hlist) + remove_str_to_int_lock(hash_cur); + mutex_unlock(&pkgl_dat->hashtable_lock); + hash_init(pkgl_dat->package_to_appid); +} + +static struct packagelist_data * packagelist_create(void) +{ + struct packagelist_data *pkgl_dat; + + pkgl_dat = kmalloc(sizeof(*pkgl_dat), GFP_KERNEL | __GFP_ZERO); + if (!pkgl_dat) { + printk(KERN_ERR "sdcardfs: Failed to create hash\n"); + return ERR_PTR(-ENOMEM); + } + + mutex_init(&pkgl_dat->hashtable_lock); + hash_init(pkgl_dat->package_to_appid); + + return pkgl_dat; +} + +static void packagelist_destroy(struct packagelist_data *pkgl_dat) +{ + remove_all_hashentrys(pkgl_dat); + printk(KERN_INFO "sdcardfs: destroyed packagelist pkgld\n"); + kfree(pkgl_dat); +} + +struct package_appid { + struct config_item item; + int add_pid; +}; + +static inline struct package_appid *to_package_appid(struct config_item *item) +{ + return item ? container_of(item, struct package_appid, item) : NULL; +} + +static ssize_t package_appid_attr_show(struct config_item *item, + char *page) +{ + ssize_t count; + count = sprintf(page, "%d\n", get_appid(pkgl_data_all, item->ci_name)); + return count; +} + +static ssize_t package_appid_attr_store(struct config_item *item, + const char *page, size_t count) +{ + struct package_appid *package_appid = to_package_appid(item); + unsigned long tmp; + char *p = (char *) page; + int ret; + + tmp = simple_strtoul(p, &p, 10); + if (!p || (*p && (*p != '\n'))) + return -EINVAL; + + if (tmp > INT_MAX) + return -ERANGE; + ret = insert_str_to_int(pkgl_data_all, item->ci_name, (unsigned int)tmp); + package_appid->add_pid = tmp; + if (ret) + return ret; + + return count; +} + +static struct configfs_attribute package_appid_attr_add_pid = { + .ca_owner = THIS_MODULE, + .ca_name = "appid", + .ca_mode = S_IRUGO | S_IWUGO, + .show = package_appid_attr_show, + .store = package_appid_attr_store, +}; + +static struct configfs_attribute *package_appid_attrs[] = { + &package_appid_attr_add_pid, + NULL, +}; + +static void package_appid_release(struct config_item *item) +{ + printk(KERN_INFO "sdcardfs: removing %s\n", item->ci_dentry->d_name.name); + /* item->ci_name is freed already, so we rely on the dentry */ + remove_str_to_int(pkgl_data_all, item->ci_dentry->d_name.name); + kfree(to_package_appid(item)); +} + +static struct configfs_item_operations package_appid_item_ops = { + .release = package_appid_release, +}; + +static struct config_item_type package_appid_type = { + .ct_item_ops = &package_appid_item_ops, + .ct_attrs = package_appid_attrs, + .ct_owner = THIS_MODULE, +}; + + +struct sdcardfs_packages { + struct config_group group; +}; + +static inline struct sdcardfs_packages *to_sdcardfs_packages(struct config_item *item) +{ + return item ? container_of(to_config_group(item), struct sdcardfs_packages, group) : NULL; +} + +static struct config_item *sdcardfs_packages_make_item(struct config_group *group, const char *name) +{ + struct package_appid *package_appid; + + package_appid = kzalloc(sizeof(struct package_appid), GFP_KERNEL); + if (!package_appid) + return ERR_PTR(-ENOMEM); + + config_item_init_type_name(&package_appid->item, name, + &package_appid_type); + + package_appid->add_pid = 0; + + return &package_appid->item; +} + +static ssize_t packages_attr_show(struct config_item *item, + char *page) +{ + struct hashtable_entry *hash_cur; + struct hlist_node *h_t; + int i; + int count = 0; + mutex_lock(&pkgl_data_all->hashtable_lock); + hash_for_each_safe(pkgl_data_all->package_to_appid, i, h_t, hash_cur, hlist) + count += snprintf(page + count, PAGE_SIZE - count, "%s %d\n", (char *)hash_cur->key, hash_cur->value); + mutex_unlock(&pkgl_data_all->hashtable_lock); + + + return count; +} + +static struct configfs_attribute sdcardfs_packages_attr_description = { + .ca_owner = THIS_MODULE, + .ca_name = "packages_gid.list", + .ca_mode = S_IRUGO, + .show = packages_attr_show, +}; + +static struct configfs_attribute *sdcardfs_packages_attrs[] = { + &sdcardfs_packages_attr_description, + NULL, +}; + +static void sdcardfs_packages_release(struct config_item *item) +{ + + printk(KERN_INFO "sdcardfs: destroyed something?\n"); + kfree(to_sdcardfs_packages(item)); +} + +static struct configfs_item_operations sdcardfs_packages_item_ops = { + .release = sdcardfs_packages_release, +}; + +/* + * Note that, since no extra work is required on ->drop_item(), + * no ->drop_item() is provided. + */ +static struct configfs_group_operations sdcardfs_packages_group_ops = { + .make_item = sdcardfs_packages_make_item, +}; + +static struct config_item_type sdcardfs_packages_type = { + .ct_item_ops = &sdcardfs_packages_item_ops, + .ct_group_ops = &sdcardfs_packages_group_ops, + .ct_attrs = sdcardfs_packages_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem sdcardfs_packages_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "sdcardfs", + .ci_type = &sdcardfs_packages_type, + }, + }, +}; + +static int configfs_sdcardfs_init(void) +{ + int ret; + struct configfs_subsystem *subsys = &sdcardfs_packages_subsys; + + config_group_init(&subsys->su_group); + mutex_init(&subsys->su_mutex); + ret = configfs_register_subsystem(subsys); + if (ret) { + printk(KERN_ERR "Error %d while registering subsystem %s\n", + ret, + subsys->su_group.cg_item.ci_namebuf); + } + return ret; +} + +static void configfs_sdcardfs_exit(void) +{ + configfs_unregister_subsystem(&sdcardfs_packages_subsys); +} + +int packagelist_init(void) +{ + hashtable_entry_cachep = + kmem_cache_create("packagelist_hashtable_entry", + sizeof(struct hashtable_entry), 0, 0, NULL); + if (!hashtable_entry_cachep) { + printk(KERN_ERR "sdcardfs: failed creating pkgl_hashtable entry slab cache\n"); + return -ENOMEM; + } + + pkgl_data_all = packagelist_create(); + configfs_sdcardfs_init(); + return 0; +} + +void packagelist_exit(void) +{ + configfs_sdcardfs_exit(); + packagelist_destroy(pkgl_data_all); + if (hashtable_entry_cachep) + kmem_cache_destroy(hashtable_entry_cachep); +} diff --git a/fs/sdcardfs/sdcardfs.h b/fs/sdcardfs/sdcardfs.h new file mode 100644 index 000000000000..f111f898b630 --- /dev/null +++ b/fs/sdcardfs/sdcardfs.h @@ -0,0 +1,530 @@ +/* + * fs/sdcardfs/sdcardfs.h + * + * The sdcardfs v2.0 + * This file system replaces the sdcard daemon on Android + * On version 2.0, some of the daemon functions have been ported + * to support the multi-user concepts of Android 4.4 + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#ifndef _SDCARDFS_H_ +#define _SDCARDFS_H_ + +#include <linux/dcache.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/seq_file.h> +#include <linux/statfs.h> +#include <linux/fs_stack.h> +#include <linux/magic.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/security.h> +#include <linux/string.h> +#include <linux/list.h> +#include "multiuser.h" + +/* the file system name */ +#define SDCARDFS_NAME "sdcardfs" + +/* sdcardfs root inode number */ +#define SDCARDFS_ROOT_INO 1 + +/* useful for tracking code reachability */ +#define UDBG printk(KERN_DEFAULT "DBG:%s:%s:%d\n", __FILE__, __func__, __LINE__) + +#define SDCARDFS_DIRENT_SIZE 256 + +/* temporary static uid settings for development */ +#define AID_ROOT 0 /* uid for accessing /mnt/sdcard & extSdcard */ +#define AID_MEDIA_RW 1023 /* internal media storage write access */ + +#define AID_SDCARD_RW 1015 /* external storage write access */ +#define AID_SDCARD_R 1028 /* external storage read access */ +#define AID_SDCARD_PICS 1033 /* external storage photos access */ +#define AID_SDCARD_AV 1034 /* external storage audio/video access */ +#define AID_SDCARD_ALL 1035 /* access all users external storage */ + +#define AID_PACKAGE_INFO 1027 + +#define fix_derived_permission(x) \ + do { \ + (x)->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(x)->d_uid); \ + (x)->i_gid = make_kgid(&init_user_ns, get_gid(SDCARDFS_I(x))); \ + (x)->i_mode = ((x)->i_mode & S_IFMT) | get_mode(SDCARDFS_I(x));\ + } while (0) + + +/* OVERRIDE_CRED() and REVERT_CRED() + * OVERRID_CRED() + * backup original task->cred + * and modifies task->cred->fsuid/fsgid to specified value. + * REVERT_CRED() + * restore original task->cred->fsuid/fsgid. + * These two macro should be used in pair, and OVERRIDE_CRED() should be + * placed at the beginning of a function, right after variable declaration. + */ +#define OVERRIDE_CRED(sdcardfs_sbi, saved_cred) \ + saved_cred = override_fsids(sdcardfs_sbi); \ + if (!saved_cred) { return -ENOMEM; } + +#define OVERRIDE_CRED_PTR(sdcardfs_sbi, saved_cred) \ + saved_cred = override_fsids(sdcardfs_sbi); \ + if (!saved_cred) { return ERR_PTR(-ENOMEM); } + +#define REVERT_CRED(saved_cred) revert_fsids(saved_cred) + +#define DEBUG_CRED() \ + printk("KAKJAGI: %s:%d fsuid %d fsgid %d\n", \ + __FUNCTION__, __LINE__, \ + (int)current->cred->fsuid, \ + (int)current->cred->fsgid); + +/* Android 5.0 support */ + +/* Permission mode for a specific node. Controls how file permissions + * are derived for children nodes. */ +typedef enum { + /* Nothing special; this node should just inherit from its parent. */ + PERM_INHERIT, + /* This node is one level above a normal root; used for legacy layouts + * which use the first level to represent user_id. */ + PERM_PRE_ROOT, + /* This node is "/" */ + PERM_ROOT, + /* This node is "/Android" */ + PERM_ANDROID, + /* This node is "/Android/data" */ + PERM_ANDROID_DATA, + /* This node is "/Android/obb" */ + PERM_ANDROID_OBB, + /* This node is "/Android/media" */ + PERM_ANDROID_MEDIA, +} perm_t; + +struct sdcardfs_sb_info; +struct sdcardfs_mount_options; + +/* Do not directly use this function. Use OVERRIDE_CRED() instead. */ +const struct cred * override_fsids(struct sdcardfs_sb_info* sbi); +/* Do not directly use this function, use REVERT_CRED() instead. */ +void revert_fsids(const struct cred * old_cred); + +/* operations vectors defined in specific files */ +extern const struct file_operations sdcardfs_main_fops; +extern const struct file_operations sdcardfs_dir_fops; +extern const struct inode_operations sdcardfs_main_iops; +extern const struct inode_operations sdcardfs_dir_iops; +extern const struct inode_operations sdcardfs_symlink_iops; +extern const struct super_operations sdcardfs_sops; +extern const struct dentry_operations sdcardfs_ci_dops; +extern const struct address_space_operations sdcardfs_aops, sdcardfs_dummy_aops; +extern const struct vm_operations_struct sdcardfs_vm_ops; + +extern int sdcardfs_init_inode_cache(void); +extern void sdcardfs_destroy_inode_cache(void); +extern int sdcardfs_init_dentry_cache(void); +extern void sdcardfs_destroy_dentry_cache(void); +extern int new_dentry_private_data(struct dentry *dentry); +extern void free_dentry_private_data(struct dentry *dentry); +extern struct dentry *sdcardfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags); +extern struct inode *sdcardfs_iget(struct super_block *sb, + struct inode *lower_inode, userid_t id); +extern int sdcardfs_interpose(struct dentry *dentry, struct super_block *sb, + struct path *lower_path, userid_t id); + +/* file private data */ +struct sdcardfs_file_info { + struct file *lower_file; + const struct vm_operations_struct *lower_vm_ops; +}; + +/* sdcardfs inode data in memory */ +struct sdcardfs_inode_info { + struct inode *lower_inode; + /* state derived based on current position in hierachy */ + perm_t perm; + userid_t userid; + uid_t d_uid; + bool under_android; + + struct inode vfs_inode; +}; + + +/* sdcardfs dentry data in memory */ +struct sdcardfs_dentry_info { + spinlock_t lock; /* protects lower_path */ + struct path lower_path; + struct path orig_path; +}; + +struct sdcardfs_mount_options { + uid_t fs_low_uid; + gid_t fs_low_gid; + userid_t fs_user_id; + gid_t gid; + mode_t mask; + bool multiuser; + unsigned int reserved_mb; +}; + +/* sdcardfs super-block data in memory */ +struct sdcardfs_sb_info { + struct super_block *sb; + struct super_block *lower_sb; + /* derived perm policy : some of options have been added + * to sdcardfs_mount_options (Android 4.4 support) */ + struct sdcardfs_mount_options options; + spinlock_t lock; /* protects obbpath */ + char *obbpath_s; + struct path obbpath; + void *pkgl_id; + struct list_head list; +}; + +/* + * inode to private data + * + * Since we use containers and the struct inode is _inside_ the + * sdcardfs_inode_info structure, SDCARDFS_I will always (given a non-NULL + * inode pointer), return a valid non-NULL pointer. + */ +static inline struct sdcardfs_inode_info *SDCARDFS_I(const struct inode *inode) +{ + return container_of(inode, struct sdcardfs_inode_info, vfs_inode); +} + +/* dentry to private data */ +#define SDCARDFS_D(dent) ((struct sdcardfs_dentry_info *)(dent)->d_fsdata) + +/* superblock to private data */ +#define SDCARDFS_SB(super) ((struct sdcardfs_sb_info *)(super)->s_fs_info) + +/* file to private Data */ +#define SDCARDFS_F(file) ((struct sdcardfs_file_info *)((file)->private_data)) + +/* file to lower file */ +static inline struct file *sdcardfs_lower_file(const struct file *f) +{ + return SDCARDFS_F(f)->lower_file; +} + +static inline void sdcardfs_set_lower_file(struct file *f, struct file *val) +{ + SDCARDFS_F(f)->lower_file = val; +} + +/* inode to lower inode. */ +static inline struct inode *sdcardfs_lower_inode(const struct inode *i) +{ + return SDCARDFS_I(i)->lower_inode; +} + +static inline void sdcardfs_set_lower_inode(struct inode *i, struct inode *val) +{ + SDCARDFS_I(i)->lower_inode = val; +} + +/* superblock to lower superblock */ +static inline struct super_block *sdcardfs_lower_super( + const struct super_block *sb) +{ + return SDCARDFS_SB(sb)->lower_sb; +} + +static inline void sdcardfs_set_lower_super(struct super_block *sb, + struct super_block *val) +{ + SDCARDFS_SB(sb)->lower_sb = val; +} + +/* path based (dentry/mnt) macros */ +static inline void pathcpy(struct path *dst, const struct path *src) +{ + dst->dentry = src->dentry; + dst->mnt = src->mnt; +} + +/* sdcardfs_get_pname functions calls path_get() + * therefore, the caller must call "proper" path_put functions + */ +#define SDCARDFS_DENT_FUNC(pname) \ +static inline void sdcardfs_get_##pname(const struct dentry *dent, \ + struct path *pname) \ +{ \ + spin_lock(&SDCARDFS_D(dent)->lock); \ + pathcpy(pname, &SDCARDFS_D(dent)->pname); \ + path_get(pname); \ + spin_unlock(&SDCARDFS_D(dent)->lock); \ + return; \ +} \ +static inline void sdcardfs_put_##pname(const struct dentry *dent, \ + struct path *pname) \ +{ \ + path_put(pname); \ + return; \ +} \ +static inline void sdcardfs_set_##pname(const struct dentry *dent, \ + struct path *pname) \ +{ \ + spin_lock(&SDCARDFS_D(dent)->lock); \ + pathcpy(&SDCARDFS_D(dent)->pname, pname); \ + spin_unlock(&SDCARDFS_D(dent)->lock); \ + return; \ +} \ +static inline void sdcardfs_reset_##pname(const struct dentry *dent) \ +{ \ + spin_lock(&SDCARDFS_D(dent)->lock); \ + SDCARDFS_D(dent)->pname.dentry = NULL; \ + SDCARDFS_D(dent)->pname.mnt = NULL; \ + spin_unlock(&SDCARDFS_D(dent)->lock); \ + return; \ +} \ +static inline void sdcardfs_put_reset_##pname(const struct dentry *dent) \ +{ \ + struct path pname; \ + spin_lock(&SDCARDFS_D(dent)->lock); \ + if(SDCARDFS_D(dent)->pname.dentry) { \ + pathcpy(&pname, &SDCARDFS_D(dent)->pname); \ + SDCARDFS_D(dent)->pname.dentry = NULL; \ + SDCARDFS_D(dent)->pname.mnt = NULL; \ + spin_unlock(&SDCARDFS_D(dent)->lock); \ + path_put(&pname); \ + } else \ + spin_unlock(&SDCARDFS_D(dent)->lock); \ + return; \ +} + +SDCARDFS_DENT_FUNC(lower_path) +SDCARDFS_DENT_FUNC(orig_path) + +static inline int get_gid(struct sdcardfs_inode_info *info) { + struct sdcardfs_sb_info *sb_info = SDCARDFS_SB(info->vfs_inode.i_sb); + if (sb_info->options.gid == AID_SDCARD_RW) { + /* As an optimization, certain trusted system components only run + * as owner but operate across all users. Since we're now handing + * out the sdcard_rw GID only to trusted apps, we're okay relaxing + * the user boundary enforcement for the default view. The UIDs + * assigned to app directories are still multiuser aware. */ + return AID_SDCARD_RW; + } else { + return multiuser_get_uid(info->userid, sb_info->options.gid); + } +} +static inline int get_mode(struct sdcardfs_inode_info *info) { + int owner_mode; + int filtered_mode; + struct sdcardfs_sb_info *sb_info = SDCARDFS_SB(info->vfs_inode.i_sb); + int visible_mode = 0775 & ~sb_info->options.mask; + + if (info->perm == PERM_PRE_ROOT) { + /* Top of multi-user view should always be visible to ensure + * secondary users can traverse inside. */ + visible_mode = 0711; + } else if (info->under_android) { + /* Block "other" access to Android directories, since only apps + * belonging to a specific user should be in there; we still + * leave +x open for the default view. */ + if (sb_info->options.gid == AID_SDCARD_RW) { + visible_mode = visible_mode & ~0006; + } else { + visible_mode = visible_mode & ~0007; + } + } + owner_mode = info->lower_inode->i_mode & 0700; + filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6)); + return filtered_mode; +} + +static inline int has_graft_path(const struct dentry *dent) +{ + int ret = 0; + + spin_lock(&SDCARDFS_D(dent)->lock); + if (SDCARDFS_D(dent)->orig_path.dentry != NULL) + ret = 1; + spin_unlock(&SDCARDFS_D(dent)->lock); + + return ret; +} + +static inline void sdcardfs_get_real_lower(const struct dentry *dent, + struct path *real_lower) +{ + /* in case of a local obb dentry + * the orig_path should be returned + */ + if(has_graft_path(dent)) + sdcardfs_get_orig_path(dent, real_lower); + else + sdcardfs_get_lower_path(dent, real_lower); +} + +static inline void sdcardfs_put_real_lower(const struct dentry *dent, + struct path *real_lower) +{ + if(has_graft_path(dent)) + sdcardfs_put_orig_path(dent, real_lower); + else + sdcardfs_put_lower_path(dent, real_lower); +} + +extern struct mutex sdcardfs_super_list_lock; +extern struct list_head sdcardfs_super_list; + +/* for packagelist.c */ +extern appid_t get_appid(void *pkgl_id, const char *app_name); +extern int check_caller_access_to_name(struct inode *parent_node, const char* name); +extern int open_flags_to_access_mode(int open_flags); +extern int packagelist_init(void); +extern void packagelist_exit(void); + +/* for derived_perm.c */ +extern void setup_derived_state(struct inode *inode, perm_t perm, + userid_t userid, uid_t uid, bool under_android); +extern void get_derived_permission(struct dentry *parent, struct dentry *dentry); +extern void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, struct dentry *newdentry); +extern void get_derive_permissions_recursive(struct dentry *parent); + +extern void update_derived_permission_lock(struct dentry *dentry); +extern int need_graft_path(struct dentry *dentry); +extern int is_base_obbpath(struct dentry *dentry); +extern int is_obbpath_invalid(struct dentry *dentry); +extern int setup_obb_dentry(struct dentry *dentry, struct path *lower_path); + +/* locking helpers */ +static inline struct dentry *lock_parent(struct dentry *dentry) +{ + struct dentry *dir = dget_parent(dentry); + mutex_lock_nested(&d_inode(dir)->i_mutex, I_MUTEX_PARENT); + return dir; +} + +static inline void unlock_dir(struct dentry *dir) +{ + mutex_unlock(&d_inode(dir)->i_mutex); + dput(dir); +} + +static inline int prepare_dir(const char *path_s, uid_t uid, gid_t gid, mode_t mode) +{ + int err; + struct dentry *dent; + struct iattr attrs; + struct path parent; + + dent = kern_path_locked(path_s, &parent); + if (IS_ERR(dent)) { + err = PTR_ERR(dent); + if (err == -EEXIST) + err = 0; + goto out_unlock; + } + + err = vfs_mkdir(d_inode(parent.dentry), dent, mode); + if (err) { + if (err == -EEXIST) + err = 0; + goto out_dput; + } + + attrs.ia_uid = make_kuid(&init_user_ns, uid); + attrs.ia_gid = make_kgid(&init_user_ns, gid); + attrs.ia_valid = ATTR_UID | ATTR_GID; + mutex_lock(&d_inode(dent)->i_mutex); + notify_change(dent, &attrs, NULL); + mutex_unlock(&d_inode(dent)->i_mutex); + +out_dput: + dput(dent); + +out_unlock: + /* parent dentry locked by lookup_create */ + mutex_unlock(&d_inode(parent.dentry)->i_mutex); + path_put(&parent); + return err; +} + +/* + * Return 1, if a disk has enough free space, otherwise 0. + * We assume that any files can not be overwritten. + */ +static inline int check_min_free_space(struct dentry *dentry, size_t size, int dir) +{ + int err; + struct path lower_path; + struct kstatfs statfs; + u64 avail; + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + + if (sbi->options.reserved_mb) { + /* Get fs stat of lower filesystem. */ + sdcardfs_get_lower_path(dentry, &lower_path); + err = vfs_statfs(&lower_path, &statfs); + sdcardfs_put_lower_path(dentry, &lower_path); + + if (unlikely(err)) + return 0; + + /* Invalid statfs informations. */ + if (unlikely(statfs.f_bsize == 0)) + return 0; + + /* if you are checking directory, set size to f_bsize. */ + if (unlikely(dir)) + size = statfs.f_bsize; + + /* available size */ + avail = statfs.f_bavail * statfs.f_bsize; + + /* not enough space */ + if ((u64)size > avail) + return 0; + + /* enough space */ + if ((avail - size) > (sbi->options.reserved_mb * 1024 * 1024)) + return 1; + + return 0; + } else + return 1; +} + +/* Copies attrs and maintains sdcardfs managed attrs */ +static inline void sdcardfs_copy_and_fix_attrs(struct inode *dest, const struct inode *src) +{ + dest->i_mode = (src->i_mode & S_IFMT) | get_mode(SDCARDFS_I(dest)); + dest->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(dest)->d_uid); + dest->i_gid = make_kgid(&init_user_ns, get_gid(SDCARDFS_I(dest))); + dest->i_rdev = src->i_rdev; + dest->i_atime = src->i_atime; + dest->i_mtime = src->i_mtime; + dest->i_ctime = src->i_ctime; + dest->i_blkbits = src->i_blkbits; + dest->i_flags = src->i_flags; + set_nlink(dest, src->i_nlink); +} +#endif /* not _SDCARDFS_H_ */ diff --git a/fs/sdcardfs/super.c b/fs/sdcardfs/super.c new file mode 100644 index 000000000000..1d6490128c99 --- /dev/null +++ b/fs/sdcardfs/super.c @@ -0,0 +1,222 @@ +/* + * fs/sdcardfs/super.c + * + * Copyright (c) 2013 Samsung Electronics Co. Ltd + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, + * Sunghwan Yun, Sungjong Seo + * + * This program has been developed as a stackable file system based on + * the WrapFS which written by + * + * Copyright (c) 1998-2011 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2011 Stony Brook University + * Copyright (c) 2003-2011 The Research Foundation of SUNY + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#include "sdcardfs.h" + +/* + * The inode cache is used with alloc_inode for both our inode info and the + * vfs inode. + */ +static struct kmem_cache *sdcardfs_inode_cachep; + +/* final actions when unmounting a file system */ +static void sdcardfs_put_super(struct super_block *sb) +{ + struct sdcardfs_sb_info *spd; + struct super_block *s; + + spd = SDCARDFS_SB(sb); + if (!spd) + return; + + if(spd->obbpath_s) { + kfree(spd->obbpath_s); + path_put(&spd->obbpath); + } + + /* decrement lower super references */ + s = sdcardfs_lower_super(sb); + sdcardfs_set_lower_super(sb, NULL); + atomic_dec(&s->s_active); + + kfree(spd); + sb->s_fs_info = NULL; +} + +static int sdcardfs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + int err; + struct path lower_path; + u32 min_blocks; + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); + + sdcardfs_get_lower_path(dentry, &lower_path); + err = vfs_statfs(&lower_path, buf); + sdcardfs_put_lower_path(dentry, &lower_path); + + if (sbi->options.reserved_mb) { + /* Invalid statfs informations. */ + if (buf->f_bsize == 0) { + printk(KERN_ERR "Returned block size is zero.\n"); + return -EINVAL; + } + + min_blocks = ((sbi->options.reserved_mb * 1024 * 1024)/buf->f_bsize); + buf->f_blocks -= min_blocks; + + if (buf->f_bavail > min_blocks) + buf->f_bavail -= min_blocks; + else + buf->f_bavail = 0; + + /* Make reserved blocks invisiable to media storage */ + buf->f_bfree = buf->f_bavail; + } + + /* set return buf to our f/s to avoid confusing user-level utils */ + buf->f_type = SDCARDFS_SUPER_MAGIC; + + return err; +} + +/* + * @flags: numeric mount options + * @options: mount options string + */ +static int sdcardfs_remount_fs(struct super_block *sb, int *flags, char *options) +{ + int err = 0; + + /* + * The VFS will take care of "ro" and "rw" flags among others. We + * can safely accept a few flags (RDONLY, MANDLOCK), and honor + * SILENT, but anything else left over is an error. + */ + if ((*flags & ~(MS_RDONLY | MS_MANDLOCK | MS_SILENT)) != 0) { + printk(KERN_ERR + "sdcardfs: remount flags 0x%x unsupported\n", *flags); + err = -EINVAL; + } + + return err; +} + +/* + * Called by iput() when the inode reference count reached zero + * and the inode is not hashed anywhere. Used to clear anything + * that needs to be, before the inode is completely destroyed and put + * on the inode free list. + */ +static void sdcardfs_evict_inode(struct inode *inode) +{ + struct inode *lower_inode; + + truncate_inode_pages(&inode->i_data, 0); + clear_inode(inode); + /* + * Decrement a reference to a lower_inode, which was incremented + * by our read_inode when it was created initially. + */ + lower_inode = sdcardfs_lower_inode(inode); + sdcardfs_set_lower_inode(inode, NULL); + iput(lower_inode); +} + +static struct inode *sdcardfs_alloc_inode(struct super_block *sb) +{ + struct sdcardfs_inode_info *i; + + i = kmem_cache_alloc(sdcardfs_inode_cachep, GFP_KERNEL); + if (!i) + return NULL; + + /* memset everything up to the inode to 0 */ + memset(i, 0, offsetof(struct sdcardfs_inode_info, vfs_inode)); + + i->vfs_inode.i_version = 1; + return &i->vfs_inode; +} + +static void sdcardfs_destroy_inode(struct inode *inode) +{ + kmem_cache_free(sdcardfs_inode_cachep, SDCARDFS_I(inode)); +} + +/* sdcardfs inode cache constructor */ +static void init_once(void *obj) +{ + struct sdcardfs_inode_info *i = obj; + + inode_init_once(&i->vfs_inode); +} + +int sdcardfs_init_inode_cache(void) +{ + int err = 0; + + sdcardfs_inode_cachep = + kmem_cache_create("sdcardfs_inode_cache", + sizeof(struct sdcardfs_inode_info), 0, + SLAB_RECLAIM_ACCOUNT, init_once); + if (!sdcardfs_inode_cachep) + err = -ENOMEM; + return err; +} + +/* sdcardfs inode cache destructor */ +void sdcardfs_destroy_inode_cache(void) +{ + if (sdcardfs_inode_cachep) + kmem_cache_destroy(sdcardfs_inode_cachep); +} + +/* + * Used only in nfs, to kill any pending RPC tasks, so that subsequent + * code can actually succeed and won't leave tasks that need handling. + */ +static void sdcardfs_umount_begin(struct super_block *sb) +{ + struct super_block *lower_sb; + + lower_sb = sdcardfs_lower_super(sb); + if (lower_sb && lower_sb->s_op && lower_sb->s_op->umount_begin) + lower_sb->s_op->umount_begin(lower_sb); +} + +static int sdcardfs_show_options(struct seq_file *m, struct dentry *root) +{ + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(root->d_sb); + struct sdcardfs_mount_options *opts = &sbi->options; + + if (opts->fs_low_uid != 0) + seq_printf(m, ",uid=%u", opts->fs_low_uid); + if (opts->fs_low_gid != 0) + seq_printf(m, ",gid=%u", opts->fs_low_gid); + + if (opts->multiuser) + seq_printf(m, ",multiuser"); + + if (opts->reserved_mb != 0) + seq_printf(m, ",reserved=%uMB", opts->reserved_mb); + + return 0; +}; + +const struct super_operations sdcardfs_sops = { + .put_super = sdcardfs_put_super, + .statfs = sdcardfs_statfs, + .remount_fs = sdcardfs_remount_fs, + .evict_inode = sdcardfs_evict_inode, + .umount_begin = sdcardfs_umount_begin, + .show_options = sdcardfs_show_options, + .alloc_inode = sdcardfs_alloc_inode, + .destroy_inode = sdcardfs_destroy_inode, + .drop_inode = generic_delete_inode, +}; diff --git a/include/dt-bindings/clock/qcom,gcc-msm8996.h b/include/dt-bindings/clock/qcom,gcc-msm8996.h index 3819485dead3..f66264a2beb4 100644 --- a/include/dt-bindings/clock/qcom,gcc-msm8996.h +++ b/include/dt-bindings/clock/qcom,gcc-msm8996.h @@ -233,6 +233,18 @@ #define GCC_PCIE_CLKREF_CLK 216 #define GCC_RX2_USB2_CLKREF_CLK 217 #define GCC_RX1_USB2_CLKREF_CLK 218 +#define GCC_AGGRE0_NOC_QOSGEN_EXTREF_CLK 219 +#define GCC_HLOS1_VOTE_LPASS_CORE_SMMU_CLK 220 +#define GCC_HLOS1_VOTE_LPASS_ADSP_SMMU_CLK 221 +#define GCC_EDP_CLKREF_CLK 222 +#define GCC_MSS_CFG_AHB_CLK 223 +#define GCC_MSS_Q6_BIMC_AXI_CLK 224 +#define GCC_MSS_SNOC_AXI_CLK 225 +#define GCC_MSS_MNOC_BIMC_AXI_CLK 226 +#define GCC_DCC_AHB_ALK 227 +#define GCC_AGGRE0_NOC_MPU_CFG_AHB_CLK 228 +#define GCC_MMSS_GPLL0_DIV_CLK 229 +#define GPLL0_OUT_MSSCC 230 #define GCC_SYSTEM_NOC_BCR 0 #define GCC_CONFIG_NOC_BCR 1 @@ -340,15 +352,4 @@ #define GCC_PCIE_PHY_NOCSR_COM_PHY_BCR 103 #define GCC_PCIE_PHY_COM_BCR 104 -/* Indexes for GDSCs */ -#define AGGRE0_NOC_GDSC 0 -#define HLOS1_VOTE_AGGRE0_NOC_GDSC 1 -#define HLOS1_VOTE_LPASS_ADSP_GDSC 2 -#define HLOS1_VOTE_LPASS_CORE_GDSC 3 -#define USB30_GDSC 4 -#define PCIE0_GDSC 5 -#define PCIE1_GDSC 6 -#define PCIE2_GDSC 7 -#define UFS_GDSC 8 - #endif diff --git a/include/linux/dcache.h b/include/linux/dcache.h index f513dd855cb2..e4221f7c5b53 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -161,6 +161,7 @@ struct dentry_operations { struct vfsmount *(*d_automount)(struct path *); int (*d_manage)(struct dentry *, bool); struct inode *(*d_select_inode)(struct dentry *, unsigned); + void (*d_canonical_path)(const struct dentry *, struct path *); struct dentry *(*d_real)(struct dentry *, struct inode *); } ____cacheline_aligned; diff --git a/include/linux/ecryptfs.h b/include/linux/ecryptfs.h index 33d35ea4f937..489b99e37128 100644 --- a/include/linux/ecryptfs.h +++ b/include/linux/ecryptfs.h @@ -130,7 +130,7 @@ struct ecryptfs_events { size_t (*get_salt_key_size_cb)(const void *ecrytpfs_data); }; - +#ifdef CONFIG_ECRYPT_FS int ecryptfs_register_to_events(const struct ecryptfs_events *ops); int ecryptfs_unregister_from_events(int user_handle); @@ -151,4 +151,55 @@ bool ecryptfs_is_page_in_metadata(const void *ecrytpfs_data, pgoff_t offset); bool ecryptfs_is_data_equal(const void *ecrytpfs_data1, const void *ecrytpfs_data2); +#else +static inline int ecryptfs_register_to_events( + const struct ecryptfs_events *ops) +{ + return 1; /* dummy handle */ +} + +static int ecryptfs_unregister_from_events(int user_handle) +{ + return 0; +} + +static inline const unsigned char *ecryptfs_get_key(const void *ecrytpfs_data) +{ + return NULL; +} + +static inline size_t ecryptfs_get_key_size(const void *ecrytpfs_data) +{ + return 0; +} + +static inline const unsigned char *ecryptfs_get_salt(const void *ecrytpfs_data) +{ + return NULL; +} + +static inline size_t ecryptfs_get_salt_size(const void *ecrytpfs_data) +{ + return 0; +} + +static inline bool ecryptfs_cipher_match(const void *ecrytpfs_data, + const unsigned char *cipher, size_t cipher_size) +{ + return false; +} + +bool ecryptfs_is_page_in_metadata(const void *ecrytpfs_data, pgoff_t offset) +{ + return false; +} + +bool ecryptfs_is_data_equal(const void *ecrytpfs_data1, + const void *ecrytpfs_data2) +{ + return false; +} + +#endif /* CONFIG_ECRYPT_FS */ + #endif /* _LINUX_ECRYPTFS_H */ diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 1919b06f28f4..cf4832db2b29 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1443,7 +1443,6 @@ union security_list_options { int (*file_receive)(struct file *file); int (*file_open)(struct file *file, const struct cred *cred); int (*file_close)(struct file *file); - bool (*allow_merge_bio)(struct bio *bio1, struct bio *bio2); int (*task_create)(unsigned long clone_flags); void (*task_free)(struct task_struct *task); @@ -1708,7 +1707,6 @@ struct security_hook_heads { struct list_head file_receive; struct list_head file_open; struct list_head file_close; - struct list_head allow_merge_bio; struct list_head task_create; struct list_head task_free; struct list_head cred_alloc_blank; diff --git a/include/linux/mfd/wcd9xxx/core.h b/include/linux/mfd/wcd9xxx/core.h index f595275e9d42..11e8d89c337b 100644 --- a/include/linux/mfd/wcd9xxx/core.h +++ b/include/linux/mfd/wcd9xxx/core.h @@ -53,12 +53,52 @@ #define TASHA_VERSION_1_0 0 #define TASHA_VERSION_1_1 1 #define TASHA_VERSION_2_0 2 -#define TASHA_IS_1_0(ver) \ - ((ver == TASHA_VERSION_1_0) ? 1 : 0) -#define TASHA_IS_1_1(ver) \ - ((ver == TASHA_VERSION_1_1) ? 1 : 0) -#define TASHA_IS_2_0(ver) \ - ((ver == TASHA_VERSION_2_0) ? 1 : 0) + +#define TASHA_IS_1_0(wcd) \ + ((wcd->type == WCD9335 || wcd->type == WCD9326) ? \ + ((wcd->version == TASHA_VERSION_1_0) ? 1 : 0) : 0) + +#define TASHA_IS_1_1(wcd) \ + ((wcd->type == WCD9335 || wcd->type == WCD9326) ? \ + ((wcd->version == TASHA_VERSION_1_1) ? 1 : 0) : 0) + +#define TASHA_IS_2_0(wcd) \ + ((wcd->type == WCD9335 || wcd->type == WCD9326) ? \ + ((wcd->version == TASHA_VERSION_2_0) ? 1 : 0) : 0) + +/* + * As fine version info cannot be retrieved before tavil probe. + * Define three coarse versions for possible future use before tavil probe. + */ +#define TAVIL_VERSION_1_0 0 +#define TAVIL_VERSION_1_1 1 +#define TAVIL_VERSION_WCD9340_1_0 2 +#define TAVIL_VERSION_WCD9341_1_0 3 +#define TAVIL_VERSION_WCD9340_1_1 4 +#define TAVIL_VERSION_WCD9341_1_1 5 + +#define TAVIL_IS_1_0(wcd) \ + ((wcd->type == WCD934X) ? \ + ((wcd->version == TAVIL_VERSION_1_0 || \ + wcd->version == TAVIL_VERSION_WCD9340_1_0 || \ + wcd->version == TAVIL_VERSION_WCD9341_1_0) ? 1 : 0) : 0) +#define TAVIL_IS_1_1(wcd) \ + ((wcd->type == WCD934X) ? \ + ((wcd->version == TAVIL_VERSION_1_1 || \ + wcd->version == TAVIL_VERSION_WCD9340_1_1 || \ + wcd->version == TAVIL_VERSION_WCD9341_1_1) ? 1 : 0) : 0) +#define TAVIL_IS_WCD9340_1_0(wcd) \ + ((wcd->type == WCD934X) ? \ + ((wcd->version == TAVIL_VERSION_WCD9340_1_0) ? 1 : 0) : 0) +#define TAVIL_IS_WCD9341_1_0(wcd) \ + ((wcd->type == WCD934X) ? \ + ((wcd->version == TAVIL_VERSION_WCD9341_1_0) ? 1 : 0) : 0) +#define TAVIL_IS_WCD9340_1_1(wcd) \ + ((wcd->type == WCD934X) ? \ + ((wcd->version == TAVIL_VERSION_WCD9340_1_1) ? 1 : 0) : 0) +#define TAVIL_IS_WCD9341_1_1(wcd) \ + ((wcd->type == WCD934X) ? \ + ((wcd->version == TAVIL_VERSION_WCD9341_1_1) ? 1 : 0) : 0) #define IS_CODEC_TYPE(wcd, wcdtype) \ ((wcd->type == wcdtype) ? true : false) diff --git a/include/linux/namei.h b/include/linux/namei.h index d8c6334cd150..d53c25453aca 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -75,6 +75,8 @@ extern struct dentry *user_path_create(int, const char __user *, struct path *, extern void done_path_create(struct path *, struct dentry *); extern struct dentry *kern_path_locked(const char *, struct path *); extern int kern_path_mountpoint(int, const char *, struct path *, unsigned int); +extern int vfs_path_lookup(struct dentry *, struct vfsmount *, + const char *, unsigned int, struct path *); extern struct dentry *lookup_one_len(const char *, struct dentry *, int); diff --git a/include/linux/pfk.h b/include/linux/pfk.h index a7e8ecbea8f5..2fc64442b8ee 100644 --- a/include/linux/pfk.h +++ b/include/linux/pfk.h @@ -23,7 +23,7 @@ int pfk_load_key_start(const struct bio *bio, struct ice_crypto_setting *ice_setting, bool *is_pfe, bool); int pfk_load_key_end(const struct bio *bio, bool *is_pfe); int pfk_remove_key(const unsigned char *key, size_t key_size); -bool pfk_allow_merge_bio(struct bio *bio1, struct bio *bio2); +bool pfk_allow_merge_bio(const struct bio *bio1, const struct bio *bio2); #else static inline int pfk_load_key_start(const struct bio *bio, @@ -48,10 +48,6 @@ static inline bool pfk_allow_merge_bio(const struct bio *bio1, return true; } -static inline void pfk_remove_all_keys(void) -{ -} - #endif /* CONFIG_PFK */ #endif /* PFK_H */ diff --git a/include/linux/pft.h b/include/linux/pft.h index f2173b89a2a0..818383c73476 100644 --- a/include/linux/pft.h +++ b/include/linux/pft.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -55,7 +55,8 @@ static inline int pft_get_key_index(struct bio *bio, u32 *key_index, bool *is_encrypted, bool *is_inplace) { return -ENODEV; } -static inline bool pft_allow_merge_bio(struct bio *bio1, struct bio *bio2) +static inline bool pft_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2) { return true; } static inline int pft_file_permission(struct file *file, int mask) diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h index 7945fea14d77..25e7a5f183ec 100644 --- a/include/linux/phy/phy-qcom-ufs.h +++ b/include/linux/phy/phy-qcom-ufs.h @@ -58,5 +58,6 @@ void ufs_qcom_phy_save_controller_version(struct phy *phy, u8 major, u16 minor, u16 step); const char *ufs_qcom_phy_name(struct phy *phy); int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable); +void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy); #endif /* PHY_QCOM_UFS_H_ */ diff --git a/include/linux/random.h b/include/linux/random.h index a75840c1aa71..9c29122037f9 100644 --- a/include/linux/random.h +++ b/include/linux/random.h @@ -34,6 +34,7 @@ extern const struct file_operations random_fops, urandom_fops; #endif unsigned int get_random_int(void); +unsigned long get_random_long(void); unsigned long randomize_range(unsigned long start, unsigned long end, unsigned long len); u32 prandom_u32(void); diff --git a/include/linux/security.h b/include/linux/security.h index 3de0302aecf2..e3b5efc0eb4b 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -293,7 +293,6 @@ int security_file_send_sigiotask(struct task_struct *tsk, int security_file_receive(struct file *file); int security_file_open(struct file *file, const struct cred *cred); int security_file_close(struct file *file); -bool security_allow_merge_bio(struct bio *bio1, struct bio *bio2); int security_task_create(unsigned long clone_flags); void security_task_free(struct task_struct *task); int security_cred_alloc_blank(struct cred *cred, gfp_t gfp); @@ -826,11 +825,6 @@ static inline int security_file_close(struct file *file) return 0; } -static inline int security_allow_merge_bio(struct bio *bio1, struct bio *bio2) -{ - return true; -} - static inline int security_task_create(unsigned long clone_flags) { return 0; diff --git a/include/linux/socket.h b/include/linux/socket.h index 18a8337c8959..5bf59c8493b7 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -47,10 +47,6 @@ struct linger { struct msghdr { void *msg_name; /* ptr to socket address structure */ int msg_namelen; /* size of socket address structure */ -#if defined(CONFIG_PPPOLAC) || defined(CONFIG_PPPOPNS) - struct iovec *msg_iov; /* scatter/gather array */ - __kernel_size_t msg_iovlen; /* # elements in msg_iov */ -#endif struct iov_iter msg_iter; /* data */ void *msg_control; /* ancillary data */ __kernel_size_t msg_controllen; /* ancillary data buffer length */ diff --git a/include/linux/wakeup_reason.h b/include/linux/wakeup_reason.h index ad8b76936c7f..d84d8c301546 100644 --- a/include/linux/wakeup_reason.h +++ b/include/linux/wakeup_reason.h @@ -21,7 +21,12 @@ #define MAX_SUSPEND_ABORT_LEN 256 void log_wakeup_reason(int irq); -void log_suspend_abort_reason(const char *fmt, ...); int check_wakeup_reason(int irq); +#ifdef CONFIG_SUSPEND +void log_suspend_abort_reason(const char *fmt, ...); +#else +static inline void log_suspend_abort_reason(const char *fmt, ...) { } +#endif + #endif /* _LINUX_WAKEUP_REASON_H */ diff --git a/include/net/rmnet_config.h b/include/net/rmnet_config.h new file mode 100644 index 000000000000..0e6282b05483 --- /dev/null +++ b/include/net/rmnet_config.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * RMNET Data config definition + * + */ + +#ifndef _RMNET_CONFIG_H_ +#define _RMNET_CONFIG_H_ + +#include <linux/skbuff.h> + +struct rmnet_phys_ep_conf_s { + void (*recycle)(struct sk_buff *); /* Destruct function */ + void *config; +}; + +#endif /* _RMNET_CONFIG_H_ */ diff --git a/include/soc/qcom/icnss.h b/include/soc/qcom/icnss.h index 778ad8f702db..efc5425cf17d 100644 --- a/include/soc/qcom/icnss.h +++ b/include/soc/qcom/icnss.h @@ -107,6 +107,12 @@ extern int icnss_ce_request_irq(unsigned int ce_id, unsigned long flags, const char *name, void *ctx); extern int icnss_get_ce_id(int irq); extern int icnss_set_fw_debug_mode(bool enable_fw_log); +extern int icnss_athdiag_read(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *output); +extern int icnss_athdiag_write(struct device *dev, uint32_t offset, + uint32_t mem_type, uint32_t data_len, + uint8_t *input); extern int icnss_get_irq(int ce_id); extern int icnss_power_on(struct device *dev); extern int icnss_power_off(struct device *dev); diff --git a/include/trace/events/power.h b/include/trace/events/power.h index 44228e7238cc..2df03af6f328 100644 --- a/include/trace/events/power.h +++ b/include/trace/events/power.h @@ -135,7 +135,7 @@ TRACE_EVENT(cpu_frequency_limits, TP_fast_assign( __entry->min_freq = min_freq; - __entry->max_freq = min_freq; + __entry->max_freq = max_freq; __entry->cpu_id = cpu_id; ), diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index accb036bbc9c..cfb5c406f344 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -52,6 +52,8 @@ #define REISER2FS_SUPER_MAGIC_STRING "ReIsEr2Fs" #define REISER2FS_JR_SUPER_MAGIC_STRING "ReIsEr3Fs" +#define SDCARDFS_SUPER_MAGIC 0xb550ca10 + #define SMB_SUPER_MAGIC 0x517B #define CGROUP_SUPER_MAGIC 0x27e0eb diff --git a/include/uapi/linux/mfd/wcd9xxx/wcd9xxx_registers.h b/include/uapi/linux/mfd/wcd9xxx/wcd9xxx_registers.h index a9fe10d8cd6e..7902cfbafad8 100644 --- a/include/uapi/linux/mfd/wcd9xxx/wcd9xxx_registers.h +++ b/include/uapi/linux/mfd/wcd9xxx/wcd9xxx_registers.h @@ -352,4 +352,10 @@ #define WCD9XXX_CDC_RX2_RX_VOL_MIX_CTL (0xB70) #define WCD9XXX_CDC_RX2_RX_PATH_SEC1 (0xB72) +/* Class-H registers for codecs from and above WCD934X */ +#define WCD9XXX_HPH_CNP_WG_CTL (0x06cc) +#define WCD9XXX_FLYBACK_VNEG_CTRL_4 (0x06a8) +#define WCD9XXX_HPH_NEW_INT_PA_MISC2 (0x0738) +#define WCD9XXX_RX_BIAS_HPH_LOWPOWER (0x06bf) +#define WCD9XXX_HPH_PA_CTL1 (0x06d1) #endif diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 84c480946fb2..6d6f63be1f9b 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -1,6 +1,7 @@ config SUSPEND bool "Suspend to RAM and standby" depends on ARCH_SUSPEND_POSSIBLE + select RTC_LIB default y ---help--- Allow the system to enter sleep states in which main memory is diff --git a/kernel/watchdog.c b/kernel/watchdog.c index 9472691c1eb0..029da92fb712 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -121,7 +121,7 @@ static unsigned long soft_lockup_nmi_warn; unsigned int __read_mostly hardlockup_panic = CONFIG_BOOTPARAM_HARDLOCKUP_PANIC_VALUE; #ifdef CONFIG_HARDLOCKUP_DETECTOR_NMI -static unsigned long hardlockup_allcpu_dumped; +static unsigned long __maybe_unused hardlockup_allcpu_dumped; #endif /* * We may not want to enable hard lockup detection by default in all cases, diff --git a/mm/mmap.c b/mm/mmap.c index 6c561acdca92..a089cca8d79a 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -2634,6 +2634,7 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len) return 0; } +EXPORT_SYMBOL(do_munmap); int vm_munmap(unsigned long start, size_t len) { diff --git a/net/rmnet_data/rmnet_data_config.c b/net/rmnet_data/rmnet_data_config.c index c49393924e26..ebce455b645d 100644 --- a/net/rmnet_data/rmnet_data_config.c +++ b/net/rmnet_data/rmnet_data_config.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -21,6 +21,7 @@ #include <linux/skbuff.h> #include <linux/spinlock.h> #include <linux/rmnet_data.h> +#include <net/rmnet_config.h> #include "rmnet_data_config.h" #include "rmnet_data_handlers.h" #include "rmnet_data_vnd.h" @@ -141,14 +142,22 @@ static inline int _rmnet_is_physical_endpoint_associated(struct net_device *dev) * - pointer to configuration if successful * - 0 (null) if device is not associated */ -static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config - (struct net_device *dev) +struct rmnet_phys_ep_config *_rmnet_get_phys_ep_config + (struct net_device *dev) { - if (_rmnet_is_physical_endpoint_associated(dev)) - return (struct rmnet_phys_ep_conf_s *) - rcu_dereference(dev->rx_handler_data); - else + struct rmnet_phys_ep_conf_s *_rmnet_phys_ep_config; + + if (_rmnet_is_physical_endpoint_associated(dev)) { + _rmnet_phys_ep_config = (struct rmnet_phys_ep_conf_s *) + rcu_dereference(dev->rx_handler_data); + if (_rmnet_phys_ep_config && _rmnet_phys_ep_config->config) + return (struct rmnet_phys_ep_config *) + _rmnet_phys_ep_config->config; + else + return 0; + } else { return 0; + } } /** @@ -165,7 +174,7 @@ static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config struct rmnet_logical_ep_conf_s *_rmnet_get_logical_ep(struct net_device *dev, int config_id) { - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; struct rmnet_logical_ep_conf_s *epconfig_l; if (rmnet_vnd_is_vnd(dev)) @@ -400,7 +409,7 @@ static void _rmnet_netlink_get_link_egress_data_format struct rmnet_nl_msg_s *resp_rmnet) { struct net_device *dev; - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; _RMNET_NETLINK_NULL_CHECKS(); resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; @@ -432,7 +441,7 @@ static void _rmnet_netlink_get_link_ingress_data_format struct rmnet_nl_msg_s *resp_rmnet) { struct net_device *dev; - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; _RMNET_NETLINK_NULL_CHECKS(); resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; @@ -725,7 +734,7 @@ int rmnet_set_ingress_data_format(struct net_device *dev, uint32_t ingress_data_format, uint8_t tail_spacing) { - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; ASSERT_RTNL(); LOGL("(%s,0x%08X);", dev->name, ingress_data_format); @@ -762,7 +771,7 @@ int rmnet_set_egress_data_format(struct net_device *dev, uint16_t agg_size, uint16_t agg_count) { - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; ASSERT_RTNL(); LOGL("(%s,0x%08X, %d, %d);", @@ -800,7 +809,9 @@ int rmnet_set_egress_data_format(struct net_device *dev, int rmnet_associate_network_device(struct net_device *dev) { struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *conf; int rc; + ASSERT_RTNL(); LOGL("(%s);\n", dev->name); @@ -819,19 +830,25 @@ int rmnet_associate_network_device(struct net_device *dev) } config = kmalloc(sizeof(*config), GFP_ATOMIC); + conf = kmalloc(sizeof(*conf), GFP_ATOMIC); - if (!config) + if (!config || !conf) return RMNET_CONFIG_NOMEM; memset(config, 0, sizeof(struct rmnet_phys_ep_conf_s)); - config->dev = dev; - spin_lock_init(&config->agg_lock); + memset(conf, 0, sizeof(struct rmnet_phys_ep_config)); + + config->config = conf; + conf->dev = dev; + spin_lock_init(&conf->agg_lock); + config->recycle = kfree_skb; rc = netdev_rx_handler_register(dev, rmnet_rx_handler, config); if (rc) { LOGM("netdev_rx_handler_register returns %d", rc); kfree(config); + kfree(conf); return RMNET_CONFIG_DEVICE_IN_USE; } diff --git a/net/rmnet_data/rmnet_data_config.h b/net/rmnet_data/rmnet_data_config.h index a76bcef79e6a..78329c38b364 100644 --- a/net/rmnet_data/rmnet_data_config.h +++ b/net/rmnet_data/rmnet_data_config.h @@ -17,6 +17,7 @@ #include <linux/types.h> #include <linux/time.h> #include <linux/spinlock.h> +#include <net/rmnet_config.h> #ifndef _RMNET_DATA_CONFIG_H_ #define _RMNET_DATA_CONFIG_H_ @@ -62,7 +63,7 @@ struct rmnet_logical_ep_conf_s { * @agg_time: Wall clock time when aggregated frame was created * @agg_last: Last time the aggregation routing was invoked */ -struct rmnet_phys_ep_conf_s { +struct rmnet_phys_ep_config { struct net_device *dev; struct rmnet_logical_ep_conf_s local_ep; struct rmnet_logical_ep_conf_s muxed_ep[RMNET_DATA_MAX_LOGICAL_EP]; @@ -123,4 +124,7 @@ int rmnet_create_vnd(int id); int rmnet_create_vnd_prefix(int id, const char *name); int rmnet_free_vnd(int id); +struct rmnet_phys_ep_config *_rmnet_get_phys_ep_config + (struct net_device *dev); + #endif /* _RMNET_DATA_CONFIG_H_ */ diff --git a/net/rmnet_data/rmnet_data_handlers.c b/net/rmnet_data/rmnet_data_handlers.c index 9d04b2f8ddd9..185b609e637f 100644 --- a/net/rmnet_data/rmnet_data_handlers.c +++ b/net/rmnet_data/rmnet_data_handlers.c @@ -22,6 +22,7 @@ #include <linux/netdev_features.h> #include <linux/ip.h> #include <linux/ipv6.h> +#include <net/rmnet_config.h> #include "rmnet_data_private.h" #include "rmnet_data_config.h" #include "rmnet_data_vnd.h" @@ -321,7 +322,7 @@ static rx_handler_result_t __rmnet_deliver_skb(struct sk_buff *skb, * - RX_HANDLER_PASS if packet should be passed up the stack by caller */ static rx_handler_result_t rmnet_ingress_deliver_packet(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config) + struct rmnet_phys_ep_config *config) { if (!config) { LOGD("%s", "NULL physical EP provided"); @@ -356,7 +357,7 @@ static rx_handler_result_t rmnet_ingress_deliver_packet(struct sk_buff *skb, * - result of __rmnet_deliver_skb() for all other cases */ static rx_handler_result_t _rmnet_map_ingress_handler(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config) + struct rmnet_phys_ep_config *config) { struct rmnet_logical_ep_conf_s *ep; uint8_t mux_id; @@ -440,7 +441,7 @@ static rx_handler_result_t _rmnet_map_ingress_handler(struct sk_buff *skb, * - result of _rmnet_map_ingress_handler() for all other cases */ static rx_handler_result_t rmnet_map_ingress_handler(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config) + struct rmnet_phys_ep_config *config) { struct sk_buff *skbn; int rc, co = 0; @@ -480,7 +481,7 @@ static rx_handler_result_t rmnet_map_ingress_handler(struct sk_buff *skb, * - 1 on failure */ static int rmnet_map_egress_handler(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config, + struct rmnet_phys_ep_config *config, struct rmnet_logical_ep_conf_s *ep, struct net_device *orig_dev) { @@ -563,7 +564,7 @@ static int rmnet_map_egress_handler(struct sk_buff *skb, */ rx_handler_result_t rmnet_ingress_handler(struct sk_buff *skb) { - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; struct net_device *dev; int rc; @@ -574,8 +575,7 @@ rx_handler_result_t rmnet_ingress_handler(struct sk_buff *skb) trace_rmnet_ingress_handler(skb); rmnet_print_packet(skb, dev->name, 'r'); - config = (struct rmnet_phys_ep_conf_s *) - rcu_dereference(skb->dev->rx_handler_data); + config = _rmnet_get_phys_ep_config(skb->dev); if (!config) { LOGD("%s is not associated with rmnet_data", skb->dev->name); @@ -654,14 +654,13 @@ rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb) void rmnet_egress_handler(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep) { - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; struct net_device *orig_dev; int rc; orig_dev = skb->dev; skb->dev = ep->egress_dev; - config = (struct rmnet_phys_ep_conf_s *) - rcu_dereference(skb->dev->rx_handler_data); + config = _rmnet_get_phys_ep_config(skb->dev); if (!config) { LOGD("%s is not associated with rmnet_data", skb->dev->name); diff --git a/net/rmnet_data/rmnet_data_stats.c b/net/rmnet_data/rmnet_data_stats.c index 5fefda55c72c..20f1628242c7 100644 --- a/net/rmnet_data/rmnet_data_stats.c +++ b/net/rmnet_data/rmnet_data_stats.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2014, 2016 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -21,6 +21,7 @@ #include <linux/skbuff.h> #include <linux/spinlock.h> #include <linux/netdevice.h> +#include <net/rmnet_config.h> #include "rmnet_data_private.h" #include "rmnet_data_stats.h" #include "rmnet_data_config.h" @@ -73,8 +74,16 @@ void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason) skb_free[reason]++; spin_unlock_irqrestore(&rmnet_skb_free_lock, flags); - if (skb) - kfree_skb(skb); + if (likely(skb)) { + struct rmnet_phys_ep_conf_s *config; + + config = (struct rmnet_phys_ep_conf_s *)rcu_dereference + (skb->dev->rx_handler_data); + if (likely(config)) + config->recycle(skb); + else + kfree_skb(skb); + } } void rmnet_stats_queue_xmit(int rc, unsigned int reason) diff --git a/net/rmnet_data/rmnet_map.h b/net/rmnet_data/rmnet_map.h index 0a6ad908b827..71abca122dd6 100644 --- a/net/rmnet_data/rmnet_map.h +++ b/net/rmnet_data/rmnet_map.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -13,6 +13,7 @@ #include <linux/types.h> #include <linux/spinlock.h> +#include <net/rmnet_config.h> #ifndef _RMNET_MAP_H_ #define _RMNET_MAP_H_ @@ -133,14 +134,14 @@ enum rmnet_map_agg_state_e { uint8_t rmnet_map_demultiplex(struct sk_buff *skb); struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config); + struct rmnet_phys_ep_config *config); struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb, int hdrlen, int pad); rx_handler_result_t rmnet_map_command(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config); + struct rmnet_phys_ep_config *config); void rmnet_map_aggregate(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config); + struct rmnet_phys_ep_config *config); int rmnet_map_checksum_downlink_packet(struct sk_buff *skb); int rmnet_map_checksum_uplink_packet(struct sk_buff *skb, diff --git a/net/rmnet_data/rmnet_map_command.c b/net/rmnet_data/rmnet_map_command.c index 4bcfa10db486..055d5f402957 100644 --- a/net/rmnet_data/rmnet_map_command.c +++ b/net/rmnet_data/rmnet_map_command.c @@ -18,6 +18,7 @@ #include <linux/rmnet_data.h> #include <linux/net_map.h> #include <net/pkt_sched.h> +#include <net/rmnet_config.h> #include "rmnet_data_config.h" #include "rmnet_map.h" #include "rmnet_data_private.h" @@ -44,7 +45,7 @@ MODULE_PARM_DESC(rmnet_map_command_stats, "MAP command statistics"); * - RMNET_MAP_COMMAND_ACK on success */ static uint8_t rmnet_map_do_flow_control(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config, + struct rmnet_phys_ep_config *config, int enable) { struct rmnet_map_control_command_s *cmd; @@ -116,7 +117,7 @@ static uint8_t rmnet_map_do_flow_control(struct sk_buff *skb, */ static void rmnet_map_send_ack(struct sk_buff *skb, unsigned char type, - struct rmnet_phys_ep_conf_s *config) + struct rmnet_phys_ep_config *config) { struct rmnet_map_control_command_s *cmd; int xmit_status; @@ -162,7 +163,7 @@ static void rmnet_map_send_ack(struct sk_buff *skb, * - RX_HANDLER_CONSUMED. Command frames are always consumed. */ rx_handler_result_t rmnet_map_command(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config) + struct rmnet_phys_ep_config *config) { struct rmnet_map_control_command_s *cmd; unsigned char command_name; diff --git a/net/rmnet_data/rmnet_map_data.c b/net/rmnet_data/rmnet_map_data.c index 68caf44c1f92..beff8332c731 100644 --- a/net/rmnet_data/rmnet_map_data.c +++ b/net/rmnet_data/rmnet_map_data.c @@ -31,6 +31,7 @@ #include <net/ip.h> #include <net/checksum.h> #include <net/ip6_checksum.h> +#include <net/rmnet_config.h> #include "rmnet_data_config.h" #include "rmnet_map.h" #include "rmnet_data_private.h" @@ -52,7 +53,7 @@ MODULE_PARM_DESC(agg_bypass_time, "Skip agg when apart spaced more than this"); struct agg_work { struct delayed_work work; - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; }; #define RMNET_MAP_DEAGGR_SPACING 64 @@ -131,7 +132,7 @@ done: * - 0 (null) if no more aggregated packets */ struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config) + struct rmnet_phys_ep_config *config) { struct sk_buff *skbn; struct rmnet_map_header_s *maph; @@ -185,7 +186,7 @@ struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, static void rmnet_map_flush_packet_queue(struct work_struct *work) { struct agg_work *real_work; - struct rmnet_phys_ep_conf_s *config; + struct rmnet_phys_ep_config *config; unsigned long flags; struct sk_buff *skb; int rc, agg_count = 0; @@ -233,7 +234,7 @@ static void rmnet_map_flush_packet_queue(struct work_struct *work) * the argument SKB and should not be further processed by any other function. */ void rmnet_map_aggregate(struct sk_buff *skb, - struct rmnet_phys_ep_conf_s *config) { + struct rmnet_phys_ep_config *config) { uint8_t *dest_buff; struct agg_work *work; unsigned long flags; diff --git a/security/pfe/Kconfig b/security/pfe/Kconfig index b951861bfc84..207be713f800 100644 --- a/security/pfe/Kconfig +++ b/security/pfe/Kconfig @@ -15,7 +15,6 @@ config PFT config PFK bool "Per-File-Key driver" depends on SECURITY - depends on ECRYPT_FS default n help This driver is used for storing eCryptfs information diff --git a/security/pfe/Makefile b/security/pfe/Makefile index 983eedb86170..f7badf74c73f 100644 --- a/security/pfe/Makefile +++ b/security/pfe/Makefile @@ -3,6 +3,7 @@ # ccflags-y += -Isecurity/selinux -Isecurity/selinux/include -Ifs/ecryptfs +ccflags-y += -Ifs/ext4 obj-$(CONFIG_PFT) += pft.o -obj-$(CONFIG_PFK) += pfk.o pfk_kc.o pfk_ice.o +obj-$(CONFIG_PFK) += pfk.o pfk_kc.o pfk_ice.o pfk_ext4.o pfk_ecryptfs.o diff --git a/security/pfe/pfk.c b/security/pfe/pfk.c index 5cfb3758d3cf..7e38c9fbf171 100644 --- a/security/pfe/pfk.c +++ b/security/pfe/pfk.c @@ -14,24 +14,27 @@ /* * Per-File-Key (PFK). * - * This driver is used for storing eCryptfs information (mainly file - * encryption key) in file node as part of eCryptfs hardware enhanced solution - * provided by Qualcomm Technologies, Inc. + * This driver is responsible for overall management of various + * Per File Encryption variants that work on top of or as part of different + * file systems. * - * The information is stored in node when file is first opened (eCryptfs - * will fire a callback notifying PFK about this event) and will be later - * accessed by Block Device Driver to actually load the key to encryption hw. + * The driver has the following purpose : + * 1) Define priorities between PFE's if more than one is enabled + * 2) Extract key information from inode + * 3) Load and manage various keys in ICE HW engine + * 4) It should be invoked from various layers in FS/BLOCK/STORAGE DRIVER + * that need to take decision on HW encryption management of the data + * Some examples: + * BLOCK LAYER: when it takes decision on whether 2 chunks can be united + * to one encryption / decryption request sent to the HW * - * PFK exposes API's for loading and removing keys from encryption hw - * and also API to determine whether 2 adjacent blocks can be agregated by - * Block Layer in one request to encryption hw. - * PFK is only supposed to be used by eCryptfs, except the below. + * UFS DRIVER: when it need to configure ICE HW with a particular key slot + * to be used for encryption / decryption + * + * PFE variants can differ on particular way of storing the cryptographic info + * inside inode, actions to be taken upon file operations, etc., but the common + * properties are described above * - * Please note, the only API that uses EXPORT_SYMBOL() is pfk_remove_key, - * this is intentionally, as it is the only API that is intended to be used - * by any kernel module, including dynamically loaded ones. All other API's, - * as mentioned above are only supposed to be used by eCryptfs which is - * a static module. */ @@ -45,7 +48,6 @@ #include <linux/printk.h> #include <linux/bio.h> #include <linux/security.h> -#include <linux/lsm_hooks.h> #include <crypto/ice.h> #include <linux/pfk.h> @@ -55,87 +57,120 @@ #include "objsec.h" #include "ecryptfs_kernel.h" #include "pfk_ice.h" +#include "pfk_ext4.h" +#include "pfk_ecryptfs.h" +#include "pfk_internal.h" +#include "ext4.h" -static DEFINE_MUTEX(pfk_lock); static bool pfk_ready; -static int g_events_handle; /* might be replaced by a table when more than one cipher is supported */ -#define PFK_SUPPORTED_CIPHER "aes_xts" #define PFK_SUPPORTED_KEY_SIZE 32 #define PFK_SUPPORTED_SALT_SIZE 32 +/* Various PFE types and function tables to support each one of them */ +enum pfe_type {ECRYPTFS_PFE, EXT4_CRYPT_PFE, INVALID_PFE}; -/** - * inode_to_filename() - get the filename from inode pointer. - * @inode: inode pointer - * - * it is used for debug prints. - * - * Return: filename string or "unknown". - */ -static char *inode_to_filename(struct inode *inode) -{ - struct dentry *dentry = NULL; - char *filename = NULL; +typedef int (*pfk_parse_inode_type)(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe); - if (hlist_empty(&inode->i_dentry)) - return "unknown"; +typedef bool (*pfk_allow_merge_bio_type)(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2); - dentry = hlist_entry(inode->i_dentry.first, struct dentry, d_u.d_alias); - filename = dentry->d_iname; +static const pfk_parse_inode_type pfk_parse_inode_ftable[] = { + /* ECRYPTFS_PFE */ &pfk_ecryptfs_parse_inode, + /* EXT4_CRYPT_PFE */ &pfk_ext4_parse_inode, +}; - return filename; +static const pfk_allow_merge_bio_type pfk_allow_merge_bio_ftable[] = { + /* ECRYPTFS_PFE */ &pfk_ecryptfs_allow_merge_bio, + /* EXT4_CRYPT_PFE */ &pfk_ext4_allow_merge_bio, +}; + +static void __exit pfk_exit(void) +{ + pfk_ready = false; + pfk_ext4_deinit(); + pfk_ecryptfs_deinit(); + pfk_kc_deinit(); } -static int pfk_inode_alloc_security(struct inode *inode) +static int __init pfk_init(void) { - struct inode_security_struct *i_sec = NULL; - if (inode == NULL) - return -EINVAL; + int ret = 0; - i_sec = kzalloc(sizeof(*i_sec), GFP_KERNEL); + ret = pfk_ecryptfs_init(); + if (ret != 0) + goto fail; + + ret = pfk_ext4_init(); + if (ret != 0) { + pfk_ecryptfs_deinit(); + goto fail; + } - if (i_sec == NULL) - return -ENOMEM; + ret = pfk_kc_init(); + if (ret != 0) { + pr_err("could init pfk key cache, error %d\n", ret); + pfk_ext4_deinit(); + pfk_ecryptfs_deinit(); + goto fail; + } - inode->i_security = i_sec; + pfk_ready = true; + pr_info("Driver initialized successfully\n"); return 0; + +fail: + pr_err("Failed to init driver\n"); + return -ENODEV; } -static void pfk_inode_free_security(struct inode *inode) +/* + * If more than one type is supported simultaneously, this function will also + * set the priority between them + */ +static enum pfe_type pfk_get_pfe_type(const struct inode *inode) { - if (inode == NULL) - return; + if (!inode) + return INVALID_PFE; - kzfree(inode->i_security); -} + if (pfk_is_ecryptfs_type(inode)) + return ECRYPTFS_PFE; -static struct security_hook_list pfk_hooks[] = { - LSM_HOOK_INIT(inode_alloc_security, pfk_inode_alloc_security), - LSM_HOOK_INIT(inode_free_security, pfk_inode_free_security), - LSM_HOOK_INIT(allow_merge_bio, pfk_allow_merge_bio), -}; + if (pfk_is_ext4_type(inode)) + return EXT4_CRYPT_PFE; -static int __init pfk_lsm_init(void) + return INVALID_PFE; +} + +/** + * inode_to_filename() - get the filename from inode pointer. + * @inode: inode pointer + * + * it is used for debug prints. + * + * Return: filename string or "unknown". + */ +char *inode_to_filename(const struct inode *inode) { - /* Check if PFK is the chosen lsm via security_module_enable() */ - if (security_module_enable("pfk")) { - security_add_hooks(pfk_hooks, ARRAY_SIZE(pfk_hooks)); - pr_debug("pfk is the chosen lsm, registered successfully !\n"); - } else { - pr_debug("pfk is not the chosen lsm.\n"); - if (!selinux_is_enabled()) { - pr_err("se linux is not enabled.\n"); - return -ENODEV; - } + struct dentry *dentry = NULL; + char *filename = NULL; - } + if (hlist_empty(&inode->i_dentry)) + return "unknown"; - return 0; + dentry = hlist_entry(inode->i_dentry.first, struct dentry, d_u.d_alias); + filename = dentry->d_iname; + + return filename; } /** @@ -149,37 +184,6 @@ static inline bool pfk_is_ready(void) } /** - * pfk_get_page_index() - get the inode from a bio. - * @bio: Pointer to BIO structure. - * - * Walk the bio struct links to get the inode. - * Please note, that in general bio may consist of several pages from - * several files, but in our case we always assume that all pages come - * from the same file, since our logic ensures it. That is why we only - * walk through the first page to look for inode. - * - * Return: pointer to the inode struct if successful, or NULL otherwise. - * - */ -static int pfk_get_page_index(const struct bio *bio, pgoff_t *page_index) -{ - if (!bio || !page_index) - return -EINVAL; - - if (!bio_has_data((struct bio *)bio)) - return -EINVAL; - - if (!bio->bi_io_vec) - return -EINVAL; - if (!bio->bi_io_vec->bv_page) - return -EINVAL; - - *page_index = bio->bi_io_vec->bv_page->index; - - return 0; -} - -/** * pfk_bio_get_inode() - get the inode from a bio. * @bio: Pointer to BIO structure. * @@ -196,10 +200,8 @@ static struct inode *pfk_bio_get_inode(const struct bio *bio) { if (!bio) return NULL; - if (!bio_has_data((struct bio *)bio)) return NULL; - if (!bio->bi_io_vec) return NULL; if (!bio->bi_io_vec->bv_page) @@ -225,96 +227,13 @@ static struct inode *pfk_bio_get_inode(const struct bio *bio) } /** - * pfk_get_ecryptfs_data() - retrieves ecryptfs data stored inside node - * @inode: inode - * - * Return the data or NULL if there isn't any or in case of error - * Should be invoked under lock - */ -static void *pfk_get_ecryptfs_data(const struct inode *inode) -{ - struct inode_security_struct *isec = NULL; - - if (!inode) - return NULL; - - isec = inode->i_security; - - if (!isec) { - pr_debug("i_security is NULL, could be irrelevant file\n"); - return NULL; - } - - return isec->pfk_data; -} - -/** - * pfk_set_ecryptfs_data() - stores ecryptfs data inside node - * @inode: inode to update - * @data: data to put inside the node - * - * Returns 0 in case of success, error otherwise - * Should be invoked under lock - */ -static int pfk_set_ecryptfs_data(struct inode *inode, void *ecryptfs_data) -{ - struct inode_security_struct *isec = NULL; - - if (!inode) - return -EINVAL; - - isec = inode->i_security; - - if (!isec) { - pr_err("i_security is NULL, not ready yet\n"); - return -EINVAL; - } - - isec->pfk_data = ecryptfs_data; - - return 0; -} - - -/** - * pfk_parse_cipher() - parse cipher from ecryptfs to enum - * @ecryptfs_data: ecrypfs data - * @algo: pointer to store the output enum (can be null) - * - * return 0 in case of success, error otherwise (i.e not supported cipher) - */ -static int pfk_parse_cipher(const void *ecryptfs_data, - enum ice_cryto_algo_mode *algo) -{ - /* - * currently only AES XTS algo is supported - * in the future, table with supported ciphers might - * be introduced - */ - - if (!ecryptfs_data) - return -EINVAL; - - if (!ecryptfs_cipher_match(ecryptfs_data, - PFK_SUPPORTED_CIPHER, sizeof(PFK_SUPPORTED_CIPHER))) { - pr_debug("ecryptfs alghoritm is not supported by pfk\n"); - return -EINVAL; - } - - if (algo) - *algo = ICE_CRYPTO_ALGO_MODE_AES_XTS; - - return 0; -} - -/** * pfk_key_size_to_key_type() - translate key size to key size enum * @key_size: key size in bytes * @key_size_type: pointer to store the output enum (can be null) * * return 0 in case of success, error otherwise (i.e not supported key size) */ -static int pfk_key_size_to_key_type(size_t key_size, +int pfk_key_size_to_key_type(size_t key_size, enum ice_crpto_key_size *key_size_type) { /* @@ -334,100 +253,38 @@ static int pfk_key_size_to_key_type(size_t key_size, return 0; } -static int pfk_bio_to_key(const struct bio *bio, unsigned char const **key, - size_t *key_size, unsigned char const **salt, size_t *salt_size, - bool *is_pfe, bool start) +/* + * Retrieves filesystem type from inode's superblock + */ +bool pfe_is_inode_filesystem_type(const struct inode *inode, + const char *fs_type) { - struct inode *inode = NULL; - int ret = 0; - void *ecryptfs_data = NULL; - pgoff_t offset; - bool is_metadata = false; - - /* - * only a few errors below can indicate that - * this function was not invoked within PFE context, - * otherwise we will consider it PFE - */ - *is_pfe = true; - - - if (!bio) - return -EINVAL; - - if (!key || !salt || !key_size || !salt_size) - return -EINVAL; - - inode = pfk_bio_get_inode(bio); - if (!inode) { - *is_pfe = false; - return -EINVAL; - } - - ecryptfs_data = pfk_get_ecryptfs_data(inode); - if (!ecryptfs_data) { - *is_pfe = false; - return -EPERM; - } - - pr_debug("loading key for file %s, start %d\n", - inode_to_filename(inode), start); - - ret = pfk_get_page_index(bio, &offset); - if (ret != 0) { - pr_err("could not get page index from bio, probably bug %d\n", - ret); - return -EINVAL; - } - - is_metadata = ecryptfs_is_page_in_metadata(ecryptfs_data, offset); - if (is_metadata == true) { - pr_debug("ecryptfs metadata, bypassing ICE\n"); - *is_pfe = false; - return -EPERM; - } - - *key = ecryptfs_get_key(ecryptfs_data); - if (!key) { - pr_err("could not parse key from ecryptfs\n"); - return -EINVAL; - } - - *key_size = ecryptfs_get_key_size(ecryptfs_data); - if (!(*key_size)) { - pr_err("could not parse key size from ecryptfs\n"); - return -EINVAL; - } + if (!inode || !fs_type) + return false; - *salt = ecryptfs_get_salt(ecryptfs_data); - if (!salt) { - pr_err("could not parse salt from ecryptfs\n"); - return -EINVAL; - } + if (!inode->i_sb) + return false; - *salt_size = ecryptfs_get_salt_size(ecryptfs_data); - if (!(*salt_size)) { - pr_err("could not parse salt size from ecryptfs\n"); - return -EINVAL; - } + if (!inode->i_sb->s_type) + return false; - return 0; + return (strcmp(inode->i_sb->s_type->name, fs_type) == 0); } + /** * pfk_load_key_start() - loads PFE encryption key to the ICE - * Can also be invoked from non - * PFE context, than it is not - * relevant and is_pfe flag is - * set to true + * Can also be invoked from non + * PFE context, in this case it + * is not relevant and is_pfe + * flag is set to false + * * @bio: Pointer to the BIO structure * @ice_setting: Pointer to ice setting structure that will be filled with * ice configuration values, including the index to which the key was loaded - * @is_pfe: Pointer to is_pfe flag, which will be true if function was invoked - * from PFE context + * @is_pfe: will be false if inode is not relevant to PFE, in such a case + * it should be treated as non PFE by the block layer * - * Via bio gets access to ecryptfs key stored in auxiliary structure inside - * inode and loads it to encryption hw. * Returns the index where the key is stored in encryption hw and additional * information that will be used later for configuration of the encryption hw. * @@ -439,15 +296,12 @@ int pfk_load_key_start(const struct bio *bio, bool async) { int ret = 0; - const unsigned char *key = NULL; - const unsigned char *salt = NULL; - size_t key_size = 0; - size_t salt_size = 0; - enum ice_cryto_algo_mode algo_mode = 0; + struct pfk_key_info key_info = {0}; + enum ice_cryto_algo_mode algo_mode = ICE_CRYPTO_ALGO_MODE_AES_XTS; enum ice_crpto_key_size key_size_type = 0; - void *ecryptfs_data = NULL; u32 key_index = 0; struct inode *inode = NULL; + enum pfe_type which_pfe = INVALID_PFE; if (!is_pfe) { pr_err("is_pfe is NULL\n"); @@ -469,35 +323,32 @@ int pfk_load_key_start(const struct bio *bio, return -EINVAL; } - ret = pfk_bio_to_key(bio, &key, &key_size, &salt, &salt_size, is_pfe, - true); - if (ret != 0) - return ret; - inode = pfk_bio_get_inode(bio); if (!inode) { *is_pfe = false; return -EINVAL; } - ecryptfs_data = pfk_get_ecryptfs_data(inode); - if (!ecryptfs_data) { + which_pfe = pfk_get_pfe_type(inode); + if (which_pfe == INVALID_PFE) { *is_pfe = false; return -EPERM; } - ret = pfk_parse_cipher(ecryptfs_data, &algo_mode); - if (ret != 0) { - pr_err("not supported cipher\n"); + pr_debug("parsing file %s with PFE %d\n", + inode_to_filename(inode), which_pfe); + + ret = (*(pfk_parse_inode_ftable[which_pfe])) + (bio, inode, &key_info, &algo_mode, is_pfe); + if (ret != 0) return ret; - } - ret = pfk_key_size_to_key_type(key_size, &key_size_type); + ret = pfk_key_size_to_key_type(key_info.key_size, &key_size_type); if (ret != 0) return ret; - ret = pfk_kc_load_key_start(key, key_size, salt, salt_size, &key_index, - async); + ret = pfk_kc_load_key_start(key_info.key, key_info.key_size, + key_info.salt, key_info.salt_size, &key_index, async); if (ret) { if (ret != -EBUSY && ret != -EAGAIN) pr_err("start: could not load key into pfk key cache, error %d\n", @@ -512,30 +363,29 @@ int pfk_load_key_start(const struct bio *bio, ice_setting->key_mode = ICE_CRYPTO_USE_LUT_SW_KEY; ice_setting->key_index = key_index; + pr_debug("loaded key for file %s key_index %d\n", + inode_to_filename(inode), key_index); + return 0; } /** * pfk_load_key_end() - marks the PFE key as no longer used by ICE - * Can also be invoked from non - * PFE context, than it is not - * relevant and is_pfe flag is - * set to true + * Can also be invoked from non + * PFE context, in this case it is not + * relevant and is_pfe flag is + * set to false + * * @bio: Pointer to the BIO structure * @is_pfe: Pointer to is_pfe flag, which will be true if function was invoked * from PFE context - * - * Via bio gets access to ecryptfs key stored in auxiliary structure inside - * inode and loads it to encryption hw. - * */ int pfk_load_key_end(const struct bio *bio, bool *is_pfe) { int ret = 0; - const unsigned char *key = NULL; - const unsigned char *salt = NULL; - size_t key_size = 0; - size_t salt_size = 0; + struct pfk_key_info key_info = {0}; + enum pfe_type which_pfe = INVALID_PFE; + struct inode *inode = NULL; if (!is_pfe) { pr_err("is_pfe is NULL\n"); @@ -551,42 +401,31 @@ int pfk_load_key_end(const struct bio *bio, bool *is_pfe) if (!pfk_is_ready()) return -ENODEV; - ret = pfk_bio_to_key(bio, &key, &key_size, &salt, &salt_size, is_pfe, - false); - if (ret != 0) - return ret; - - pfk_kc_load_key_end(key, key_size, salt, salt_size); - - return 0; -} + inode = pfk_bio_get_inode(bio); + if (!inode) { + *is_pfe = false; + return -EINVAL; + } -/** - * pfk_remove_key() - removes key from hw - * @key: pointer to the key - * @key_size: key size - * - * Will be used by external clients to remove a particular key for security - * reasons. - * The only API that can be used by dynamically loaded modules, - * see explanations above at the beginning of this file. - * The key is removed securely (by memsetting the previous value) - */ -int pfk_remove_key(const unsigned char *key, size_t key_size) -{ - int ret = 0; + which_pfe = pfk_get_pfe_type(inode); + if (which_pfe == INVALID_PFE) { + *is_pfe = false; + return -EPERM; + } - if (!pfk_is_ready()) - return -ENODEV; + ret = (*(pfk_parse_inode_ftable[which_pfe])) + (bio, inode, &key_info, NULL, is_pfe); + if (ret != 0) + return ret; - if (!key) - return -EINVAL; + pfk_kc_load_key_end(key_info.key, key_info.key_size, + key_info.salt, key_info.salt_size); - ret = pfk_kc_remove_key(key, key_size); + pr_debug("finished using key for file %s\n", + inode_to_filename(inode)); - return ret; + return 0; } -EXPORT_SYMBOL(pfk_remove_key); /** * pfk_allow_merge_bio() - Check if 2 BIOs can be merged. @@ -603,252 +442,39 @@ EXPORT_SYMBOL(pfk_remove_key); * Return: true if the BIOs allowed to be merged, false * otherwise. */ -bool pfk_allow_merge_bio(struct bio *bio1, struct bio *bio2) +bool pfk_allow_merge_bio(const struct bio *bio1, const struct bio *bio2) { - int ret; - void *ecryptfs_data1 = NULL; - void *ecryptfs_data2 = NULL; - pgoff_t offset1, offset2; - bool res = false; + struct inode *inode1 = NULL; + struct inode *inode2 = NULL; + enum pfe_type which_pfe1 = INVALID_PFE; + enum pfe_type which_pfe2 = INVALID_PFE; - /* if there is no pfk, don't disallow merging blocks */ if (!pfk_is_ready()) - return true; + return false; if (!bio1 || !bio2) return false; - ecryptfs_data1 = pfk_get_ecryptfs_data(pfk_bio_get_inode(bio1)); - ecryptfs_data2 = pfk_get_ecryptfs_data(pfk_bio_get_inode(bio2)); - - /* - * if we have 2 different encrypted files or 1 encrypted and 1 regular, - * merge is forbidden - */ - if (!ecryptfs_is_data_equal(ecryptfs_data1, ecryptfs_data2)) { - res = false; - goto end; - } - - /* - * if both are equall in their NULLINNESS, we have 2 unencrypted files, - * allow merge - */ - if (!ecryptfs_data1) { - res = true; - goto end; - } - - /* - * at this point both bio's are in the same file which is probably - * encrypted, last thing to check is header vs data - * We are assuming that we are not working in O_DIRECT mode, - * since it is not currently supported by eCryptfs - */ - ret = pfk_get_page_index(bio1, &offset1); - if (ret != 0) { - pr_err("could not get page index from bio1, probably bug %d\n", - ret); - res = false; - goto end; - } - - ret = pfk_get_page_index(bio2, &offset2); - if (ret != 0) { - pr_err("could not get page index from bio2, bug %d\n", ret); - res = false; - goto end; - } - - res = (ecryptfs_is_page_in_metadata(ecryptfs_data1, offset1) == - ecryptfs_is_page_in_metadata(ecryptfs_data2, offset2)); - - /* fall through */ - -end: - - return res; -} - -/** - * pfk_open_cb() - callback function for file open event - * @inode: file inode - * @data: data provided by eCryptfs - * - * Will be invoked from eCryptfs in case of file open event - */ -static void pfk_open_cb(struct inode *inode, void *ecryptfs_data) -{ - size_t key_size; - - if (!pfk_is_ready()) - return; - - if (!inode) { - pr_err("inode is null\n"); - return; - } - - key_size = ecryptfs_get_key_size(ecryptfs_data); - if (!(key_size)) { - pr_err("could not parse key size from ecryptfs\n"); - return; - } - - if (0 != pfk_parse_cipher(ecryptfs_data, NULL)) { - pr_debug("open_cb: not supported cipher\n"); - return; - } - - - if (0 != pfk_key_size_to_key_type(key_size, NULL)) - return; - - mutex_lock(&pfk_lock); - pfk_set_ecryptfs_data(inode, ecryptfs_data); - mutex_unlock(&pfk_lock); -} - -/** - * pfk_release_cb() - callback function for file release event - * @inode: file inode - * - * Will be invoked from eCryptfs in case of file release event - */ -static void pfk_release_cb(struct inode *inode) -{ - const unsigned char *key = NULL; - const unsigned char *salt = NULL; - size_t key_size = 0; - size_t salt_size = 0; - void *data = NULL; - - if (!pfk_is_ready()) - return; - - if (!inode) { - pr_err("inode is null\n"); - return; - } - - data = pfk_get_ecryptfs_data(inode); - if (!data) { - pr_debug("could not get ecryptfs data from inode\n"); - return; - } - - key = ecryptfs_get_key(data); - if (!key) { - pr_err("could not parse key from ecryptfs\n"); - return; - } - - key_size = ecryptfs_get_key_size(data); - if (!(key_size)) { - pr_err("could not parse key size from ecryptfs\n"); - return; - } - - salt = ecryptfs_get_salt(data); - if (!salt) { - pr_err("could not parse salt from ecryptfs\n"); - return; - } - - salt_size = ecryptfs_get_salt_size(data); - if (!salt_size) { - pr_err("could not parse salt size from ecryptfs\n"); - return; - } + if (bio1 == bio2) + return true; - pfk_kc_remove_key_with_salt(key, key_size, salt, salt_size); + inode1 = pfk_bio_get_inode(bio1); + inode2 = pfk_bio_get_inode(bio2); - mutex_lock(&pfk_lock); - pfk_set_ecryptfs_data(inode, NULL); - mutex_unlock(&pfk_lock); -} + which_pfe1 = pfk_get_pfe_type(inode1); + which_pfe2 = pfk_get_pfe_type(inode2); -static bool pfk_is_cipher_supported_cb(const void *ecryptfs_data) -{ - if (!pfk_is_ready()) + /* nodes with different encryption, do not merge */ + if (which_pfe1 != which_pfe2) return false; - if (!ecryptfs_data) - return false; - - return (pfk_parse_cipher(ecryptfs_data, NULL)) == 0; -} - -static bool pfk_is_hw_crypt_cb(void) -{ - if (!pfk_is_ready()) - return false; - - return true; -} - -static size_t pfk_get_salt_key_size_cb(const void *ecryptfs_data) -{ - if (!pfk_is_ready()) - return 0; - - if (!pfk_is_cipher_supported_cb(ecryptfs_data)) - return 0; - - return PFK_SUPPORTED_SALT_SIZE; -} - - -static void __exit pfk_exit(void) -{ - pfk_ready = false; - ecryptfs_unregister_from_events(g_events_handle); - pfk_kc_deinit(); -} - -static int __init pfk_init(void) -{ - - int ret = 0; - struct ecryptfs_events events = {0}; - - events.open_cb = pfk_open_cb; - events.release_cb = pfk_release_cb; - events.is_cipher_supported_cb = pfk_is_cipher_supported_cb; - events.is_hw_crypt_cb = pfk_is_hw_crypt_cb; - events.get_salt_key_size_cb = pfk_get_salt_key_size_cb; - - g_events_handle = ecryptfs_register_to_events(&events); - if (0 == g_events_handle) { - pr_err("could not register with eCryptfs, error %d\n", ret); - goto fail; - } - - ret = pfk_kc_init(); - if (ret != 0) { - pr_err("could init pfk key cache, error %d\n", ret); - ecryptfs_unregister_from_events(g_events_handle); - goto fail; - } - - ret = pfk_lsm_init(); - if (ret != 0) { - pr_debug("neither pfk nor se-linux sec modules are enabled\n"); - pr_debug("not an error, just don't enable pfk\n"); - pfk_kc_deinit(); - ecryptfs_unregister_from_events(g_events_handle); - return 0; - } - - pfk_ready = true; - pr_info("Driver initialized successfully\n"); - - return 0; + /* both nodes do not have encryption, allow merge */ + if (which_pfe1 == INVALID_PFE) + return true; -fail: - pr_err("Failed to init driver\n"); - return -ENODEV; + return (*(pfk_allow_merge_bio_ftable[which_pfe1]))(bio1, bio2, + inode1, inode2); } module_init(pfk_init); diff --git a/security/pfe/pfk_ecryptfs.c b/security/pfe/pfk_ecryptfs.c new file mode 100644 index 000000000000..1d6a2eeaf6fc --- /dev/null +++ b/security/pfe/pfk_ecryptfs.c @@ -0,0 +1,630 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Per-File-Key (PFK) - eCryptfs. + * + * This driver is used for storing eCryptfs information (mainly file + * encryption key) in file node as part of eCryptfs hardware enhanced solution + * provided by Qualcomm Technologies, Inc. + * + * The information is stored in node when file is first opened (eCryptfs + * will fire a callback notifying PFK about this event) and will be later + * accessed by Block Device Driver to actually load the key to encryption hw. + * + * PFK exposes API's for loading and removing keys from encryption hw + * and also API to determine whether 2 adjacent blocks can be agregated by + * Block Layer in one request to encryption hw. + * PFK is only supposed to be used by eCryptfs, except the below. + * + */ + + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ +#define pr_fmt(fmt) "pfk_ecryptfs [%s]: " fmt, __func__ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/printk.h> +#include <linux/bio.h> +#include <linux/security.h> +#include <linux/lsm_hooks.h> +#include <crypto/ice.h> + +#include <linux/pfk.h> +#include <linux/ecryptfs.h> + +#include "pfk_ecryptfs.h" +#include "pfk_kc.h" +#include "objsec.h" +#include "ecryptfs_kernel.h" +#include "pfk_ice.h" + +static DEFINE_MUTEX(pfk_ecryptfs_lock); +static bool pfk_ecryptfs_ready; +static int g_events_handle; + + +/* might be replaced by a table when more than one cipher is supported */ +#define PFK_SUPPORTED_CIPHER "aes_xts" +#define PFK_SUPPORTED_SALT_SIZE 32 + +static void *pfk_ecryptfs_get_data(const struct inode *inode); +static void pfk_ecryptfs_open_cb(struct inode *inode, void *ecryptfs_data); +static void pfk_ecryptfs_release_cb(struct inode *inode); +static bool pfk_ecryptfs_is_cipher_supported_cb(const void *ecryptfs_data); +static size_t pfk_ecryptfs_get_salt_key_size_cb(const void *ecryptfs_data); +static bool pfk_ecryptfs_is_hw_crypt_cb(void); + + +/** + * pfk_is_ecryptfs_type() - return true if inode belongs to ICE ecryptfs PFE + * @inode: inode pointer + */ +bool pfk_is_ecryptfs_type(const struct inode *inode) +{ + void *ecryptfs_data = NULL; + + /* + * the actual filesystem of an inode is still ext4, eCryptfs never + * reaches bio + */ + if (!pfe_is_inode_filesystem_type(inode, "ext4")) + return false; + + ecryptfs_data = pfk_ecryptfs_get_data(inode); + + if (!ecryptfs_data) + return false; + + return true; +} + +static int pfk_ecryptfs_inode_alloc_security(struct inode *inode) +{ + struct inode_security_struct *i_sec = NULL; + + if (inode == NULL) + return -EINVAL; + + i_sec = kzalloc(sizeof(*i_sec), GFP_KERNEL); + + if (i_sec == NULL) + return -ENOMEM; + + inode->i_security = i_sec; + + return 0; +} + +static void pfk_ecryptfs_inode_free_security(struct inode *inode) +{ + if (inode == NULL) + return; + + kzfree(inode->i_security); +} + +static struct security_hook_list pfk_ecryptfs_hooks[] = { + LSM_HOOK_INIT(inode_alloc_security, pfk_ecryptfs_inode_alloc_security), + LSM_HOOK_INIT(inode_free_security, pfk_ecryptfs_inode_free_security), +}; + +/* + * pfk_ecryptfs_lsm_init() - makes sure either se-linux or pfk_ecryptfs are + * registered as security module. + * + * This is required because ecryptfs uses a field inside security struct in + * inode to store its info + */ +static int __init pfk_ecryptfs_lsm_init(void) +{ + /* Check if PFK is the chosen lsm via security_module_enable() */ + if (security_module_enable("pfk_ecryptfs")) { + security_add_hooks(pfk_ecryptfs_hooks, + ARRAY_SIZE(pfk_ecryptfs_hooks)); + pr_debug("pfk_ecryptfs is the chosen lsm, registered successfully !\n"); + } else { + pr_debug("pfk_ecryptfs is not the chosen lsm.\n"); + if (!selinux_is_enabled()) { + pr_err("se linux is not enabled.\n"); + return -ENODEV; + } + } + + return 0; +} + +/* + * pfk_ecryptfs_deinit() - Deinit function, should be invoked by upper PFK layer + */ +void __exit pfk_ecryptfs_deinit(void) +{ + pfk_ecryptfs_ready = false; + ecryptfs_unregister_from_events(g_events_handle); +} + +/* + * pfk_ecryptfs_init() - Init function, should be invoked by upper PFK layer + */ +int __init pfk_ecryptfs_init(void) +{ + int ret = 0; + struct ecryptfs_events events = {0}; + + events.open_cb = pfk_ecryptfs_open_cb; + events.release_cb = pfk_ecryptfs_release_cb; + events.is_cipher_supported_cb = pfk_ecryptfs_is_cipher_supported_cb; + events.is_hw_crypt_cb = pfk_ecryptfs_is_hw_crypt_cb; + events.get_salt_key_size_cb = pfk_ecryptfs_get_salt_key_size_cb; + + g_events_handle = ecryptfs_register_to_events(&events); + if (g_events_handle == 0) { + pr_err("could not register with eCryptfs, error %d\n", ret); + goto fail; + } + + ret = pfk_ecryptfs_lsm_init(); + if (ret != 0) { + pr_debug("neither pfk nor se-linux sec modules are enabled\n"); + pr_debug("not an error, just don't enable PFK ecryptfs\n"); + ecryptfs_unregister_from_events(g_events_handle); + return 0; + } + + pfk_ecryptfs_ready = true; + pr_info("PFK ecryptfs inited successfully\n"); + + return 0; + +fail: + pr_err("Failed to init PFK ecryptfs\n"); + return -ENODEV; +} + +/** + * pfk_ecryptfs_is_ready() - driver is initialized and ready. + * + * Return: true if the driver is ready. + */ +static inline bool pfk_ecryptfs_is_ready(void) +{ + return pfk_ecryptfs_ready; +} + +/** + * pfk_ecryptfs_get_page_index() - get the inode from a bio. + * @bio: Pointer to BIO structure. + * + * Walk the bio struct links to get the inode. + * Please note, that in general bio may consist of several pages from + * several files, but in our case we always assume that all pages come + * from the same file, since our logic ensures it. That is why we only + * walk through the first page to look for inode. + * + * Return: pointer to the inode struct if successful, or NULL otherwise. + * + */ +static int pfk_ecryptfs_get_page_index(const struct bio *bio, + pgoff_t *page_index) +{ + if (!bio || !page_index) + return -EINVAL; + if (!bio_has_data((struct bio *)bio)) + return -EINVAL; + if (!bio->bi_io_vec) + return -EINVAL; + if (!bio->bi_io_vec->bv_page) + return -EINVAL; + + *page_index = bio->bi_io_vec->bv_page->index; + + return 0; +} + +/** + * pfk_ecryptfs_get_data() - retrieves ecryptfs data stored inside node + * @inode: inode + * + * Return the data or NULL if there isn't any or in case of error + * Should be invoked under lock + */ +static void *pfk_ecryptfs_get_data(const struct inode *inode) +{ + struct inode_security_struct *isec = NULL; + + if (!inode) + return NULL; + + isec = inode->i_security; + + if (!isec) { + pr_debug("i_security is NULL, could be irrelevant file\n"); + return NULL; + } + + return isec->pfk_data; +} + +/** + * pfk_ecryptfs_set_data() - stores ecryptfs data inside node + * @inode: inode to update + * @data: data to put inside the node + * + * Returns 0 in case of success, error otherwise + * Should be invoked under lock + */ +static int pfk_ecryptfs_set_data(struct inode *inode, void *ecryptfs_data) +{ + struct inode_security_struct *isec = NULL; + + if (!inode) + return -EINVAL; + + isec = inode->i_security; + + if (!isec) { + pr_err("i_security is NULL, not ready yet\n"); + return -EINVAL; + } + + isec->pfk_data = ecryptfs_data; + + return 0; +} + + +/** + * pfk_ecryptfs_parse_cipher() - parse cipher from ecryptfs to enum + * @ecryptfs_data: ecrypfs data + * @algo: pointer to store the output enum (can be null) + * + * return 0 in case of success, error otherwise (i.e not supported cipher) + */ +static int pfk_ecryptfs_parse_cipher(const void *ecryptfs_data, + enum ice_cryto_algo_mode *algo) +{ + /* + * currently only AES XTS algo is supported + * in the future, table with supported ciphers might + * be introduced + */ + + if (!ecryptfs_data) + return -EINVAL; + + if (!ecryptfs_cipher_match(ecryptfs_data, + PFK_SUPPORTED_CIPHER, sizeof(PFK_SUPPORTED_CIPHER))) { + pr_debug("ecryptfs alghoritm is not supported by pfk\n"); + return -EINVAL; + } + + if (algo) + *algo = ICE_CRYPTO_ALGO_MODE_AES_XTS; + + return 0; +} + +/* + * pfk_ecryptfs_parse_inode() - parses key and algo information from inode + * + * Should be invoked by upper pfk layer + * @bio: bio + * @inode: inode to be parsed + * @key_info: out, key and salt information to be stored + * @algo: out, algorithm to be stored (can be null) + * @is_pfe: out, will be false if inode is not relevant to PFE, in such a case + * it should be treated as non PFE by the block layer + */ +int pfk_ecryptfs_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe) +{ + int ret = 0; + void *ecryptfs_data = NULL; + pgoff_t offset; + bool is_metadata = false; + + if (!is_pfe) + return -EINVAL; + + /* + * only a few errors below can indicate that + * this function was not invoked within PFE context, + * otherwise we will consider it PFE + */ + *is_pfe = true; + + if (!pfk_ecryptfs_is_ready()) + return -ENODEV; + + if (!inode) + return -EINVAL; + + if (!key_info) + return -EINVAL; + + ecryptfs_data = pfk_ecryptfs_get_data(inode); + if (!ecryptfs_data) { + pr_err("internal error, no ecryptfs data\n"); + return -EINVAL; + } + + ret = pfk_ecryptfs_get_page_index(bio, &offset); + if (ret != 0) { + pr_err("could not get page index from bio, probably bug %d\n", + ret); + return -EINVAL; + } + + is_metadata = ecryptfs_is_page_in_metadata(ecryptfs_data, offset); + if (is_metadata == true) { + pr_debug("ecryptfs metadata, bypassing ICE\n"); + *is_pfe = false; + return -EPERM; + } + + key_info->key = ecryptfs_get_key(ecryptfs_data); + if (!key_info->key) { + pr_err("could not parse key from ecryptfs\n"); + return -EINVAL; + } + + key_info->key_size = ecryptfs_get_key_size(ecryptfs_data); + if (!key_info->key_size) { + pr_err("could not parse key size from ecryptfs\n"); + return -EINVAL; + } + + key_info->salt = ecryptfs_get_salt(ecryptfs_data); + if (!key_info->salt) { + pr_err("could not parse salt from ecryptfs\n"); + return -EINVAL; + } + + key_info->salt_size = ecryptfs_get_salt_size(ecryptfs_data); + if (!key_info->salt_size) { + pr_err("could not parse salt size from ecryptfs\n"); + return -EINVAL; + } + + ret = pfk_ecryptfs_parse_cipher(ecryptfs_data, algo); + if (ret != 0) { + pr_err("not supported cipher\n"); + return ret; + } + + return 0; +} + +/** + * pfk_ecryptfs_allow_merge_bio() - Check if 2 bios can be merged. + * + * Should be invoked by upper pfk layer + * + * @bio1: Pointer to first BIO structure. + * @bio2: Pointer to second BIO structure. + * @inode1: Pointer to inode from first bio + * @inode2: Pointer to inode from second bio + * + * Prevent merging of BIOs from encrypted and non-encrypted + * files, or files encrypted with different key. + * Also prevent non encrypted and encrypted data from the same file + * to be merged (ecryptfs header if stored inside file should be non + * encrypted) + * + * Return: true if the BIOs allowed to be merged, false + * otherwise. + */ +bool pfk_ecryptfs_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2) +{ + int ret; + void *ecryptfs_data1 = NULL; + void *ecryptfs_data2 = NULL; + pgoff_t offset1, offset2; + + /* if there is no ecryptfs pfk, don't disallow merging blocks */ + if (!pfk_ecryptfs_is_ready()) + return true; + + if (!inode1 || !inode2) + return false; + + ecryptfs_data1 = pfk_ecryptfs_get_data(inode1); + ecryptfs_data2 = pfk_ecryptfs_get_data(inode2); + + if (!ecryptfs_data1 || !ecryptfs_data2) { + pr_err("internal error, ecryptfs data should not be null"); + return false; + } + + /* + * if we have 2 different encrypted files merge is not allowed + */ + if (!ecryptfs_is_data_equal(ecryptfs_data1, ecryptfs_data2)) + return false; + + /* + * at this point both bio's are in the same file which is probably + * encrypted, last thing to check is header vs data + * We are assuming that we are not working in O_DIRECT mode, + * since it is not currently supported by eCryptfs + */ + ret = pfk_ecryptfs_get_page_index(bio1, &offset1); + if (ret != 0) { + pr_err("could not get page index from bio1, probably bug %d\n", + ret); + return false; + } + + ret = pfk_ecryptfs_get_page_index(bio2, &offset2); + if (ret != 0) { + pr_err("could not get page index from bio2, bug %d\n", ret); + return false; + } + + return (ecryptfs_is_page_in_metadata(ecryptfs_data1, offset1) == + ecryptfs_is_page_in_metadata(ecryptfs_data2, offset2)); +} + +/** + * pfk_ecryptfs_open_cb() - callback function for file open event + * @inode: file inode + * @data: data provided by eCryptfs + * + * Will be invoked from eCryptfs in case of file open event + */ +static void pfk_ecryptfs_open_cb(struct inode *inode, void *ecryptfs_data) +{ + size_t key_size; + + if (!pfk_ecryptfs_is_ready()) + return; + + if (!inode) { + pr_err("inode is null\n"); + return; + } + + key_size = ecryptfs_get_key_size(ecryptfs_data); + if (!(key_size)) { + pr_err("could not parse key size from ecryptfs\n"); + return; + } + + if (pfk_ecryptfs_parse_cipher(ecryptfs_data, NULL) != 0) { + pr_debug("open_cb: not supported cipher\n"); + return; + } + + if (pfk_key_size_to_key_type(key_size, NULL) != 0) + return; + + mutex_lock(&pfk_ecryptfs_lock); + pfk_ecryptfs_set_data(inode, ecryptfs_data); + mutex_unlock(&pfk_ecryptfs_lock); +} + +/** + * pfk_ecryptfs_release_cb() - callback function for file release event + * @inode: file inode + * + * Will be invoked from eCryptfs in case of file release event + */ +static void pfk_ecryptfs_release_cb(struct inode *inode) +{ + const unsigned char *key = NULL; + const unsigned char *salt = NULL; + size_t key_size = 0; + size_t salt_size = 0; + void *data = NULL; + + if (!pfk_ecryptfs_is_ready()) + return; + + if (!inode) { + pr_err("inode is null\n"); + return; + } + + data = pfk_ecryptfs_get_data(inode); + if (!data) { + pr_debug("could not get ecryptfs data from inode\n"); + return; + } + + key = ecryptfs_get_key(data); + if (!key) { + pr_err("could not parse key from ecryptfs\n"); + return; + } + + key_size = ecryptfs_get_key_size(data); + if (!(key_size)) { + pr_err("could not parse key size from ecryptfs\n"); + return; + } + + salt = ecryptfs_get_salt(data); + if (!salt) { + pr_err("could not parse salt from ecryptfs\n"); + return; + } + + salt_size = ecryptfs_get_salt_size(data); + if (!salt_size) { + pr_err("could not parse salt size from ecryptfs\n"); + return; + } + + pfk_kc_remove_key_with_salt(key, key_size, salt, salt_size); + + mutex_lock(&pfk_ecryptfs_lock); + pfk_ecryptfs_set_data(inode, NULL); + mutex_unlock(&pfk_ecryptfs_lock); +} + +/* + * pfk_ecryptfs_is_cipher_supported_cb() - callback function to determine + * whether a particular cipher (stored in ecryptfs_data) is cupported by pfk + * + * Ecryptfs should invoke this callback whenever it needs to determine whether + * pfk supports the particular cipher mode + * + * @ecryptfs_data: ecryptfs data + */ +static bool pfk_ecryptfs_is_cipher_supported_cb(const void *ecryptfs_data) +{ + if (!pfk_ecryptfs_is_ready()) + return false; + + if (!ecryptfs_data) + return false; + + return (pfk_ecryptfs_parse_cipher(ecryptfs_data, NULL)) == 0; +} + +/* + * pfk_ecryptfs_is_hw_crypt_cb() - callback function that implements a query + * by ecryptfs whether PFK supports HW encryption + */ +static bool pfk_ecryptfs_is_hw_crypt_cb(void) +{ + if (!pfk_ecryptfs_is_ready()) + return false; + + return true; +} + +/* + * pfk_ecryptfs_get_salt_key_size_cb() - callback function to determine + * what is the salt size supported by PFK + * + * @ecryptfs_data: ecryptfs data + */ +static size_t pfk_ecryptfs_get_salt_key_size_cb(const void *ecryptfs_data) +{ + if (!pfk_ecryptfs_is_ready()) + return 0; + + if (!pfk_ecryptfs_is_cipher_supported_cb(ecryptfs_data)) + return 0; + + return PFK_SUPPORTED_SALT_SIZE; +} diff --git a/security/pfe/pfk_ecryptfs.h b/security/pfe/pfk_ecryptfs.h new file mode 100644 index 000000000000..1d9942c4c09b --- /dev/null +++ b/security/pfe/pfk_ecryptfs.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _PFK_ECRYPTFS_H_ +#define _PFK_ECRYPTFS_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <crypto/ice.h> +#include "pfk_internal.h" + + +bool pfk_is_ecryptfs_type(const struct inode *inode); + +int pfk_ecryptfs_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe); + +bool pfk_ecryptfs_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2); + +int __init pfk_ecryptfs_init(void); + +void __exit pfk_ecryptfs_deinit(void); + +#endif /* _PFK_ECRYPTFS_H_ */ diff --git a/security/pfe/pfk_ext4.c b/security/pfe/pfk_ext4.c new file mode 100644 index 000000000000..07e2c7ab829c --- /dev/null +++ b/security/pfe/pfk_ext4.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Per-File-Key (PFK) - EXT4 + * + * This driver is used for working with EXT4 crypt extension + * + * The key information is stored in node by EXT4 when file is first opened + * and will be later accessed by Block Device Driver to actually load the key + * to encryption hw. + * + * PFK exposes API's for loading and removing keys from encryption hw + * and also API to determine whether 2 adjacent blocks can be agregated by + * Block Layer in one request to encryption hw. + * + */ + + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ +#define pr_fmt(fmt) "pfk_ext4 [%s]: " fmt, __func__ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/printk.h> + +#include "ext4_ice.h" +#include "pfk_ext4.h" + +static bool pfk_ext4_ready; + +/* + * pfk_ext4_deinit() - Deinit function, should be invoked by upper PFK layer + */ +void __exit pfk_ext4_deinit(void) +{ + pfk_ext4_ready = false; +} + +/* + * pfk_ecryptfs_init() - Init function, should be invoked by upper PFK layer + */ +int __init pfk_ext4_init(void) +{ + pfk_ext4_ready = true; + pr_info("PFK EXT4 inited successfully\n"); + + return 0; +} + +/** + * pfk_ecryptfs_is_ready() - driver is initialized and ready. + * + * Return: true if the driver is ready. + */ +static inline bool pfk_ext4_is_ready(void) +{ + return pfk_ext4_ready; +} + +/** + * pfk_ext4_dump_inode() - dumps all interesting info about inode to the screen + * + * + */ +/* + * static void pfk_ext4_dump_inode(const struct inode* inode) + * { + * struct ext4_crypt_info *ci = ext4_encryption_info((struct inode*)inode); + * + * pr_debug("dumping inode with address 0x%p\n", inode); + * pr_debug("S_ISREG is %d\n", S_ISREG(inode->i_mode)); + * pr_debug("EXT4_INODE_ENCRYPT flag is %d\n", + * ext4_test_inode_flag((struct inode*)inode, EXT4_INODE_ENCRYPT)); + * if (ci) { + * pr_debug("crypt_info address 0x%p\n", ci); + * pr_debug("ci->ci_data_mode %d\n", ci->ci_data_mode); + * } else { + * pr_debug("crypt_info is NULL\n"); + * } + * } +*/ + +/** + * pfk_is_ext4_type() - return true if inode belongs to ICE EXT4 PFE + * @inode: inode pointer + */ +bool pfk_is_ext4_type(const struct inode *inode) +{ + if (!pfe_is_inode_filesystem_type(inode, "ext4")) + return false; + + return ext4_should_be_processed_by_ice(inode); +} + +/** + * pfk_ext4_parse_cipher() - parse cipher from inode to enum + * @inode: inode + * @algo: pointer to store the output enum (can be null) + * + * return 0 in case of success, error otherwise (i.e not supported cipher) + */ +static int pfk_ext4_parse_cipher(const struct inode *inode, + enum ice_cryto_algo_mode *algo) +{ + /* + * currently only AES XTS algo is supported + * in the future, table with supported ciphers might + * be introduced + */ + + if (!inode) + return -EINVAL; + + if (!ext4_is_aes_xts_cipher(inode)) { + pr_err("ext4 alghoritm is not supported by pfk\n"); + return -EINVAL; + } + + if (algo) + *algo = ICE_CRYPTO_ALGO_MODE_AES_XTS; + + return 0; +} + + +int pfk_ext4_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe) +{ + int ret = 0; + + if (!is_pfe) + return -EINVAL; + + /* + * only a few errors below can indicate that + * this function was not invoked within PFE context, + * otherwise we will consider it PFE + */ + *is_pfe = true; + + if (!pfk_ext4_is_ready()) + return -ENODEV; + + if (!inode) + return -EINVAL; + + if (!key_info) + return -EINVAL; + + key_info->key = ext4_get_ice_encryption_key(inode); + if (!key_info->key) { + pr_err("could not parse key from ext4\n"); + return -EINVAL; + } + + key_info->key_size = ext4_get_ice_encryption_key_size(inode); + if (!key_info->key_size) { + pr_err("could not parse key size from ext4\n"); + return -EINVAL; + } + + key_info->salt = ext4_get_ice_encryption_salt(inode); + if (!key_info->salt) { + pr_err("could not parse salt from ext4\n"); + return -EINVAL; + } + + key_info->salt_size = ext4_get_ice_encryption_salt_size(inode); + if (!key_info->salt_size) { + pr_err("could not parse salt size from ext4\n"); + return -EINVAL; + } + + ret = pfk_ext4_parse_cipher(inode, algo); + if (ret != 0) { + pr_err("not supported cipher\n"); + return ret; + } + + return 0; +} + +bool pfk_ext4_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2) +{ + /* if there is no ext4 pfk, don't disallow merging blocks */ + if (!pfk_ext4_is_ready()) + return true; + + if (!inode1 || !inode2) + return false; + + return ext4_is_ice_encryption_info_equal(inode1, inode2); +} + diff --git a/security/pfe/pfk_ext4.h b/security/pfe/pfk_ext4.h new file mode 100644 index 000000000000..6b367b428e50 --- /dev/null +++ b/security/pfe/pfk_ext4.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _PFK_EXT4_H_ +#define _PFK_EXT4_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <crypto/ice.h> +#include "pfk_internal.h" + +bool pfk_is_ext4_type(const struct inode *inode); + +int pfk_ext4_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe); + +bool pfk_ext4_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2); + +int __init pfk_ext4_init(void); + +void __exit pfk_ext4_deinit(void); + +#endif /* _PFK_EXT4_H_ */ diff --git a/security/pfe/pfk_ice.h b/security/pfe/pfk_ice.h index 1d6339a575be..fe3f2ad3950c 100644 --- a/security/pfe/pfk_ice.h +++ b/security/pfe/pfk_ice.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -22,7 +22,6 @@ #include <linux/types.h> - int pfk_ice_init(void); int pfk_ice_deinit(void); diff --git a/security/pfe/pfk_internal.h b/security/pfe/pfk_internal.h new file mode 100644 index 000000000000..abdd0b325b39 --- /dev/null +++ b/security/pfe/pfk_internal.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _PFK_INTERNAL_H_ +#define _PFK_INTERNAL_H_ + +#include <linux/types.h> +#include <crypto/ice.h> + +struct pfk_key_info { + const unsigned char *key; + const unsigned char *salt; + size_t key_size; + size_t salt_size; +}; + +int pfk_key_size_to_key_type(size_t key_size, + enum ice_crpto_key_size *key_size_type); + +bool pfe_is_inode_filesystem_type(const struct inode *inode, + const char *fs_type); + +char *inode_to_filename(const struct inode *inode); + +#endif /* _PFK_INTERNAL_H_ */ diff --git a/security/pfe/pft.c b/security/pfe/pft.c index e4bbba7b6295..74433c4fe0ff 100644 --- a/security/pfe/pft.c +++ b/security/pfe/pft.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -746,7 +746,7 @@ EXPORT_SYMBOL(pft_get_key_index); * Return: true if the BIOs allowed to be merged, false * otherwise. */ -bool pft_allow_merge_bio(struct bio *bio1, struct bio *bio2) +bool pft_allow_merge_bio(const struct bio *bio1, const struct bio *bio2) { u32 key_index1 = 0, key_index2 = 0; bool is_encrypted1 = false, is_encrypted2 = false; diff --git a/security/security.c b/security/security.c index 81a555c14a35..7d1de1f6299f 100644 --- a/security/security.c +++ b/security/security.c @@ -857,11 +857,6 @@ int security_file_close(struct file *file) return call_int_hook(file_close, 0, file); } -bool security_allow_merge_bio(struct bio *bio1, struct bio *bio2) -{ - return call_int_hook(allow_merge_bio, 1, bio1, bio2); -} - int security_task_create(unsigned long clone_flags) { return call_int_hook(task_create, 0, clone_flags); @@ -1693,7 +1688,6 @@ struct security_hook_heads security_hook_heads = { .file_receive = LIST_HEAD_INIT(security_hook_heads.file_receive), .file_open = LIST_HEAD_INIT(security_hook_heads.file_open), .file_close = LIST_HEAD_INIT(security_hook_heads.file_close), - .allow_merge_bio = LIST_HEAD_INIT(security_hook_heads.allow_merge_bio), .task_create = LIST_HEAD_INIT(security_hook_heads.task_create), .task_free = LIST_HEAD_INIT(security_hook_heads.task_free), .cred_alloc_blank = diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 33d1262704ec..89fea87feafb 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3585,12 +3585,6 @@ static int selinux_file_close(struct file *file) return pft_file_close(file); } -static bool selinux_allow_merge_bio(struct bio *bio1, struct bio *bio2) -{ - return pft_allow_merge_bio(bio1, bio2) && - pfk_allow_merge_bio(bio1, bio2); -} - /* task security operations */ static int selinux_task_create(unsigned long clone_flags) @@ -6000,7 +5994,6 @@ static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(file_open, selinux_file_open), LSM_HOOK_INIT(file_close, selinux_file_close), - LSM_HOOK_INIT(allow_merge_bio, selinux_allow_merge_bio), LSM_HOOK_INIT(task_create, selinux_task_create), LSM_HOOK_INIT(cred_alloc_blank, selinux_cred_alloc_blank), diff --git a/sound/soc/codecs/audio-ext-clk.c b/sound/soc/codecs/audio-ext-clk.c index c422267dbf2c..34b1d9481457 100755..100644 --- a/sound/soc/codecs/audio-ext-clk.c +++ b/sound/soc/codecs/audio-ext-clk.c @@ -193,7 +193,13 @@ static int audio_get_pinctrl(struct platform_device *pdev) int ret; pnctrl_info = &audio_ap_clk2.pnctrl_info; - pnctrl_info->pinctrl = NULL; + + if (pnctrl_info->pinctrl) { + dev_dbg(&pdev->dev, "%s: already requested before\n", + __func__); + return -EINVAL; + } + pinctrl = devm_pinctrl_get(&pdev->dev); if (IS_ERR_OR_NULL(pinctrl)) { dev_dbg(&pdev->dev, "%s: Unable to get pinctrl handle\n", diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 158b9ae08642..a4d92fdd3e7c 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -793,7 +793,7 @@ struct tasha_priv { struct wcd_swr_ctrl_platform_data swr_plat_data; /* Port values for Rx and Tx codec_dai */ - unsigned int rx_port_value; + unsigned int rx_port_value[TASHA_RX_MAX]; unsigned int tx_port_value; unsigned int vi_feed_value; @@ -1114,7 +1114,7 @@ static void tasha_cdc_sido_ccl_enable(struct tasha_priv *tasha, bool ccl_flag) if (!codec) return; - if (!TASHA_IS_2_0(tasha->wcd9xxx->version)) { + if (!TASHA_IS_2_0(tasha->wcd9xxx)) { dev_dbg(codec->dev, "%s: tasha version < 2p0, return\n", __func__); return; @@ -1139,7 +1139,7 @@ static void tasha_cdc_sido_ccl_enable(struct tasha_priv *tasha, bool ccl_flag) static bool tasha_cdc_is_svs_enabled(struct tasha_priv *tasha) { - if (TASHA_IS_2_0(tasha->wcd9xxx->version) && + if (TASHA_IS_2_0(tasha->wcd9xxx) && svs_scaling_enabled) return true; @@ -1269,7 +1269,7 @@ int tasha_enable_efuse_sensing(struct snd_soc_codec *codec) tasha_cdc_mclk_enable(codec, true, false); - if (!TASHA_IS_2_0(priv->wcd9xxx->version)) + if (!TASHA_IS_2_0(priv->wcd9xxx)) snd_soc_update_bits(codec, WCD9335_CHIP_TIER_CTRL_EFUSE_CTL, 0x1E, 0x02); snd_soc_update_bits(codec, WCD9335_CHIP_TIER_CTRL_EFUSE_CTL, @@ -1282,7 +1282,7 @@ int tasha_enable_efuse_sensing(struct snd_soc_codec *codec) if (!(snd_soc_read(codec, WCD9335_CHIP_TIER_CTRL_EFUSE_STATUS) & 0x01)) WARN(1, "%s: Efuse sense is not complete\n", __func__); - if (TASHA_IS_2_0(priv->wcd9xxx->version)) { + if (TASHA_IS_2_0(priv->wcd9xxx)) { if (!(snd_soc_read(codec, WCD9335_CHIP_TIER_CTRL_EFUSE_VAL_OUT0) & 0x40)) snd_soc_update_bits(codec, WCD9335_HPH_R_ATEST, @@ -1502,7 +1502,7 @@ static void tasha_mbhc_hph_l_pull_up_control(struct snd_soc_codec *codec, dev_dbg(codec->dev, "%s: HS pull up current:%d\n", __func__, pull_up_cur); - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) snd_soc_update_bits(codec, WCD9335_MBHC_PLUG_DETECT_CTL, 0xC0, pull_up_cur << 6); else @@ -1980,7 +1980,7 @@ static void tasha_wcd_mbhc_calc_impedance(struct wcd_mbhc *mbhc, uint32_t *zl, }; s16 *d1 = NULL; - if (!TASHA_IS_2_0(wcd9xxx->version)) { + if (!TASHA_IS_2_0(wcd9xxx)) { dev_dbg(codec->dev, "%s: Z-det is not supported for this codec version\n", __func__); *zl = 0; @@ -2168,13 +2168,13 @@ static void tasha_mbhc_hph_pull_down_ctrl(struct snd_soc_codec *codec, if (enable) { snd_soc_update_bits(codec, WCD9335_HPH_PA_CTL2, 0x40, 0x40); - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) snd_soc_update_bits(codec, WCD9335_HPH_PA_CTL2, 0x10, 0x10); } else { snd_soc_update_bits(codec, WCD9335_HPH_PA_CTL2, 0x40, 0x00); - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) snd_soc_update_bits(codec, WCD9335_HPH_PA_CTL2, 0x10, 0x00); } @@ -2276,6 +2276,7 @@ static int tasha_put_anc_func(struct snd_kcontrol *kcontrol, snd_soc_dapm_enable_pin(dapm, "ANC HPHL"); snd_soc_dapm_enable_pin(dapm, "ANC EAR PA"); snd_soc_dapm_enable_pin(dapm, "ANC EAR"); + snd_soc_dapm_enable_pin(dapm, "ANC SPK1 PA"); snd_soc_dapm_disable_pin(dapm, "LINEOUT2"); snd_soc_dapm_disable_pin(dapm, "LINEOUT2 PA"); snd_soc_dapm_disable_pin(dapm, "LINEOUT1"); @@ -2297,6 +2298,7 @@ static int tasha_put_anc_func(struct snd_kcontrol *kcontrol, snd_soc_dapm_disable_pin(dapm, "ANC HPHL PA"); snd_soc_dapm_disable_pin(dapm, "ANC EAR PA"); snd_soc_dapm_disable_pin(dapm, "ANC EAR"); + snd_soc_dapm_disable_pin(dapm, "ANC SPK1 PA"); snd_soc_dapm_enable_pin(dapm, "LINEOUT2"); snd_soc_dapm_enable_pin(dapm, "LINEOUT2 PA"); snd_soc_dapm_enable_pin(dapm, "LINEOUT1"); @@ -2597,7 +2599,8 @@ static int slim_rx_mux_get(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_soc_dapm_to_codec(widget->dapm); struct tasha_priv *tasha_p = snd_soc_codec_get_drvdata(codec); - ucontrol->value.enumerated.item[0] = tasha_p->rx_port_value; + ucontrol->value.enumerated.item[0] = + tasha_p->rx_port_value[widget->shift]; return 0; } @@ -2616,25 +2619,27 @@ static int slim_rx_mux_put(struct snd_kcontrol *kcontrol, struct wcd9xxx *core = dev_get_drvdata(codec->dev->parent); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; struct snd_soc_dapm_update *update = NULL; + unsigned int rx_port_value; u32 port_id = widget->shift; + tasha_p->rx_port_value[port_id] = ucontrol->value.enumerated.item[0]; + rx_port_value = tasha_p->rx_port_value[port_id]; + pr_debug("%s: wname %s cname %s value %u shift %d item %ld\n", __func__, - widget->name, ucontrol->id.name, tasha_p->rx_port_value, + widget->name, ucontrol->id.name, rx_port_value, widget->shift, ucontrol->value.integer.value[0]); - tasha_p->rx_port_value = ucontrol->value.enumerated.item[0]; - mutex_lock(&tasha_p->codec_mutex); if (tasha_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) { - if (tasha_p->rx_port_value > 2) { + if (rx_port_value > 2) { dev_err(codec->dev, "%s: invalid AIF for I2C mode\n", __func__); goto err; } } /* value need to match the Virtual port and AIF number */ - switch (tasha_p->rx_port_value) { + switch (rx_port_value) { case 0: list_del_init(&core->rx_chs[port_id].list); break; @@ -2694,13 +2699,13 @@ static int slim_rx_mux_put(struct snd_kcontrol *kcontrol, &tasha_p->dai[AIF_MIX1_PB].wcd9xxx_ch_list); break; default: - pr_err("Unknown AIF %d\n", tasha_p->rx_port_value); + pr_err("Unknown AIF %d\n", rx_port_value); goto err; } rtn: mutex_unlock(&tasha_p->codec_mutex); snd_soc_dapm_mux_update_power(widget->dapm, kcontrol, - tasha_p->rx_port_value, e, update); + rx_port_value, e, update); return 0; err: @@ -2835,23 +2840,19 @@ static const struct snd_kcontrol_new aif4_mad_mixer[] = { }; static const struct snd_kcontrol_new rx_int1_spline_mix_switch[] = { - SOC_DAPM_SINGLE("HPHL Switch", SND_SOC_NOPM, 0, 1, 0), - SOC_DAPM_SINGLE("HPHL Native Switch", SND_SOC_NOPM, 0, 1, 0) + SOC_DAPM_SINGLE("HPHL Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new rx_int2_spline_mix_switch[] = { - SOC_DAPM_SINGLE("HPHR Switch", SND_SOC_NOPM, 0, 1, 0), - SOC_DAPM_SINGLE("HPHR Native Switch", SND_SOC_NOPM, 0, 1, 0) + SOC_DAPM_SINGLE("HPHR Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new rx_int3_spline_mix_switch[] = { - SOC_DAPM_SINGLE("LO1 Switch", SND_SOC_NOPM, 0, 1, 0), - SOC_DAPM_SINGLE("LO1 Native Switch", SND_SOC_NOPM, 0, 1, 0) + SOC_DAPM_SINGLE("LO1 Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new rx_int4_spline_mix_switch[] = { - SOC_DAPM_SINGLE("LO2 Switch", SND_SOC_NOPM, 0, 1, 0), - SOC_DAPM_SINGLE("LO2 Native Switch", SND_SOC_NOPM, 0, 1, 0) + SOC_DAPM_SINGLE("LO2 Switch", SND_SOC_NOPM, 0, 1, 0) }; static const struct snd_kcontrol_new rx_int5_spline_mix_switch[] = { @@ -3664,7 +3665,7 @@ static int tasha_codec_enable_rx_bias(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMU: tasha->rx_bias_count++; if (tasha->rx_bias_count == 1) { - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) tasha_codec_init_flyback(codec); snd_soc_update_bits(codec, WCD9335_ANA_RX_SUPPLIES, 0x01, 0x01); @@ -3823,7 +3824,8 @@ static int tasha_codec_enable_anc(struct snd_soc_dapm_widget *w, i = 0; anc_cal_size = anc_writes_size; - if (!strcmp(w->name, "RX INT0 DAC")) + if (!strcmp(w->name, "RX INT0 DAC") || + !strcmp(w->name, "ANC SPK1 PA")) tasha_realign_anc_coeff(codec, WCD9335_CDC_ANC0_IIR_COEFF_1_CTL, WCD9335_CDC_ANC0_IIR_COEFF_2_CTL); @@ -3872,8 +3874,9 @@ static int tasha_codec_enable_anc(struct snd_soc_dapm_widget *w, break; case SND_SOC_DAPM_POST_PMD: if (!strcmp(w->name, "ANC HPHL PA") || - !strcmp(w->name, "ANC EAR PA") || - !strcmp(w->name, "ANC LINEOUT1 PA")) { + !strcmp(w->name, "ANC EAR PA") || + !strcmp(w->name, "ANC SPK1 PA") || + !strcmp(w->name, "ANC LINEOUT1 PA")) { snd_soc_update_bits(codec, WCD9335_CDC_ANC0_MODE_1_CTL, 0x30, 0x00); msleep(50); @@ -3886,7 +3889,7 @@ static int tasha_codec_enable_anc(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WCD9335_CDC_ANC0_CLK_RESET_CTL, 0x38, 0x00); } else if (!strcmp(w->name, "ANC HPHR PA") || - !strcmp(w->name, "ANC LINEOUT2 PA")) { + !strcmp(w->name, "ANC LINEOUT2 PA")) { snd_soc_update_bits(codec, WCD9335_CDC_ANC1_MODE_1_CTL, 0x30, 0x00); msleep(50); @@ -3930,7 +3933,7 @@ static void tasha_codec_hph_post_pa_config(struct tasha_priv *tasha, { u8 scale_val = 0; - if (!TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (!TASHA_IS_2_0(tasha->wcd9xxx)) return; switch (event) { @@ -4264,6 +4267,35 @@ static int tasha_codec_enable_lineout_pa(struct snd_soc_dapm_widget *w, return ret; } +static int tasha_codec_enable_spk_anc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct tasha_priv *tasha = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "%s %s %d %d\n", __func__, w->name, event, + tasha->anc_func); + + if (!tasha->anc_func) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = tasha_codec_enable_anc(w, kcontrol, event); + snd_soc_update_bits(codec, WCD9335_CDC_RX7_RX_PATH_CFG0, + 0x10, 0x10); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, WCD9335_CDC_RX7_RX_PATH_CFG0, + 0x10, 0x00); + ret = tasha_codec_enable_anc(w, kcontrol, event); + break; + } + return ret; +} + static int tasha_codec_enable_ear_pa(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -4415,7 +4447,7 @@ static void tasha_codec_hph_mode_config(struct snd_soc_codec *codec, { struct tasha_priv *tasha = snd_soc_codec_get_drvdata(codec); - if (!TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (!TASHA_IS_2_0(tasha->wcd9xxx)) return; switch (mode) { @@ -4479,14 +4511,14 @@ static int tasha_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, /* 1000us required as per HW requirement */ usleep_range(1000, 1100); if ((hph_mode == CLS_H_LP) && - (TASHA_IS_1_1(wcd9xxx->version))) { + (TASHA_IS_1_1(wcd9xxx))) { snd_soc_update_bits(codec, WCD9335_HPH_L_DAC_CTL, 0x03, 0x03); } break; case SND_SOC_DAPM_PRE_PMD: if ((hph_mode == CLS_H_LP) && - (TASHA_IS_1_1(wcd9xxx->version))) { + (TASHA_IS_1_1(wcd9xxx))) { snd_soc_update_bits(codec, WCD9335_HPH_L_DAC_CTL, 0x03, 0x00); } @@ -4569,14 +4601,14 @@ static int tasha_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, /* 1000us required as per HW requirement */ usleep_range(1000, 1100); if ((hph_mode == CLS_H_LP) && - (TASHA_IS_1_1(wcd9xxx->version))) { + (TASHA_IS_1_1(wcd9xxx))) { snd_soc_update_bits(codec, WCD9335_HPH_L_DAC_CTL, 0x03, 0x03); } break; case SND_SOC_DAPM_PRE_PMD: if ((hph_mode == CLS_H_LP) && - (TASHA_IS_1_1(wcd9xxx->version))) { + (TASHA_IS_1_1(wcd9xxx))) { snd_soc_update_bits(codec, WCD9335_HPH_L_DAC_CTL, 0x03, 0x00); } @@ -4801,7 +4833,7 @@ static void tasha_codec_hd2_control(struct snd_soc_codec *codec, u16 hd2_scale_reg; u16 hd2_enable_reg = 0; - if (!TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (!TASHA_IS_2_0(tasha->wcd9xxx)) return; if (prim_int_reg == WCD9335_CDC_RX1_RX_PATH_CTL) { @@ -6819,7 +6851,9 @@ static const struct snd_soc_dapm_route audio_map[] = { {"SPL SRC0 MUX", "SRC_IN_HPHL", "RX INT1_1 MIX1"}, {"RX INT1 SPLINE MIX", NULL, "RX INT1_1 MIX1"}, {"RX INT1 SPLINE MIX", "HPHL Switch", "SPL SRC0 MUX"}, - {"RX INT1 SPLINE MIX", "HPHL Native Switch", "RX INT1 NATIVE SUPPLY"}, + {"RX INT1_1 NATIVE MUX", "ON", "RX INT1_1 MIX1"}, + {"RX INT1 SPLINE MIX", NULL, "RX INT1_1 NATIVE MUX"}, + {"RX INT1_1 NATIVE MUX", NULL, "RX INT1 NATIVE SUPPLY"}, {"RX INT1 SEC MIX", NULL, "RX INT1 SPLINE MIX"}, {"RX INT1 MIX2", NULL, "RX INT1 SEC MIX"}, {"RX INT1 MIX2", NULL, "RX INT1 MIX2 INP"}, @@ -6833,7 +6867,9 @@ static const struct snd_soc_dapm_route audio_map[] = { {"SPL SRC1 MUX", "SRC_IN_HPHR", "RX INT2_1 MIX1"}, {"RX INT2 SPLINE MIX", NULL, "RX INT2_1 MIX1"}, {"RX INT2 SPLINE MIX", "HPHR Switch", "SPL SRC1 MUX"}, - {"RX INT2 SPLINE MIX", "HPHR Native Switch", "RX INT2 NATIVE SUPPLY"}, + {"RX INT2_1 NATIVE MUX", "ON", "RX INT2_1 MIX1"}, + {"RX INT2 SPLINE MIX", NULL, "RX INT2_1 NATIVE MUX"}, + {"RX INT2_1 NATIVE MUX", NULL, "RX INT2 NATIVE SUPPLY"}, {"RX INT2 SEC MIX", NULL, "RX INT2 SPLINE MIX"}, {"RX INT2 MIX2", NULL, "RX INT2 SEC MIX"}, {"RX INT2 MIX2", NULL, "RX INT2 MIX2 INP"}, @@ -6847,7 +6883,9 @@ static const struct snd_soc_dapm_route audio_map[] = { {"SPL SRC0 MUX", "SRC_IN_LO1", "RX INT3_1 MIX1"}, {"RX INT3 SPLINE MIX", NULL, "RX INT3_1 MIX1"}, {"RX INT3 SPLINE MIX", "LO1 Switch", "SPL SRC0 MUX"}, - {"RX INT3 SPLINE MIX", "LO1 Native Switch", "RX INT3 NATIVE SUPPLY"}, + {"RX INT3_1 NATIVE MUX", "ON", "RX INT3_1 MIX1"}, + {"RX INT3 SPLINE MIX", NULL, "RX INT3_1 NATIVE MUX"}, + {"RX INT3_1 NATIVE MUX", NULL, "RX INT3 NATIVE SUPPLY"}, {"RX INT3 SEC MIX", NULL, "RX INT3 SPLINE MIX"}, {"RX INT3 MIX2", NULL, "RX INT3 SEC MIX"}, {"RX INT3 MIX2", NULL, "RX INT3 MIX2 INP"}, @@ -6860,7 +6898,9 @@ static const struct snd_soc_dapm_route audio_map[] = { {"SPL SRC1 MUX", "SRC_IN_LO2", "RX INT4_1 MIX1"}, {"RX INT4 SPLINE MIX", NULL, "RX INT4_1 MIX1"}, {"RX INT4 SPLINE MIX", "LO2 Switch", "SPL SRC1 MUX"}, - {"RX INT4 SPLINE MIX", "LO2 Native Switch", "RX INT4 NATIVE SUPPLY"}, + {"RX INT4_1 NATIVE MUX", "ON", "RX INT4_1 MIX1"}, + {"RX INT4 SPLINE MIX", NULL, "RX INT4_1 NATIVE MUX"}, + {"RX INT4_1 NATIVE MUX", NULL, "RX INT4 NATIVE SUPPLY"}, {"RX INT4 SEC MIX", NULL, "RX INT4 SPLINE MIX"}, {"RX INT4 MIX2", NULL, "RX INT4 SEC MIX"}, {"RX INT4 MIX2", NULL, "RX INT4 MIX2 INP"}, @@ -6916,6 +6956,10 @@ static const struct snd_soc_dapm_route audio_map[] = { {"RX INT7 CHAIN", NULL, "RX_BIAS"}, {"SPK1 OUT", NULL, "RX INT7 CHAIN"}, + {"ANC SPKR PA Enable", "Switch", "RX INT7 CHAIN"}, + {"ANC SPK1 PA", NULL, "ANC SPKR PA Enable"}, + {"SPK1 OUT", NULL, "ANC SPK1 PA"}, + {"SPL SRC3 MUX", "SRC_IN_SPKRR", "RX INT8_1 MIX1"}, {"RX INT8 SPLINE MIX", NULL, "RX INT8_1 MIX1"}, {"RX INT8 SPLINE MIX", "SPKRR Switch", "SPL SRC3 MUX"}, @@ -6948,6 +6992,10 @@ static const struct snd_soc_dapm_route audio_map[] = { {"ANC EAR Enable", "Switch", "ADC MUX11"}, {"RX INT0 MIX2", NULL, "ANC EAR Enable"}, + {"ANC OUT EAR SPKR Enable", "Switch", "ADC MUX10"}, + {"ANC OUT EAR SPKR Enable", "Switch", "ADC MUX11"}, + {"RX INT7 MIX2", NULL, "ANC OUT EAR SPKR Enable"}, + {"ANC LINEOUT1 Enable", "Switch", "ADC MUX10"}, {"ANC LINEOUT1 Enable", "Switch", "ADC MUX11"}, {"RX INT3 MIX2", NULL, "ANC LINEOUT1 Enable"}, @@ -8000,7 +8048,7 @@ static void wcd_vbat_adc_out_config(struct wcd_vbat *vbat, if (!vbat->adc_config) { tasha_cdc_mclk_enable(codec, true, false); - if (TASHA_IS_2_0(wcd9xxx->version)) + if (TASHA_IS_2_0(wcd9xxx)) wcd_vbat_adc_out_config_2_0(vbat, codec); else wcd_vbat_adc_out_config_1_x(vbat, codec); @@ -9056,6 +9104,10 @@ static const char * const anc1_fb_mux_text[] = { "ZERO", "ANC_IN_HPHR", "ANC_IN_LO2" }; +static const char * const native_mux_text[] = { + "OFF", "ON", +}; + static const struct soc_enum spl_src0_mux_chain_enum = SOC_ENUM_SINGLE(WCD9335_CDC_RX_INP_MUX_SPLINE_SRC_CFG0, 0, 3, spl_src0_mux_text); @@ -9108,6 +9160,22 @@ static const struct soc_enum rx_int8_2_mux_chain_enum = SOC_ENUM_SINGLE(WCD9335_CDC_RX_INP_MUX_RX_INT8_CFG1, 0, 9, rx_int_mix_mux_text); +static const struct soc_enum int1_1_native_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(native_mux_text), + native_mux_text); + +static const struct soc_enum int2_1_native_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(native_mux_text), + native_mux_text); + +static const struct soc_enum int3_1_native_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(native_mux_text), + native_mux_text); + +static const struct soc_enum int4_1_native_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(native_mux_text), + native_mux_text); + static const struct soc_enum rx_int0_1_mix_inp0_chain_enum = SOC_ENUM_SINGLE(WCD9335_CDC_RX_INP_MUX_RX_INT0_CFG0, 0, 13, rx_prim_mix_text); @@ -9640,6 +9708,18 @@ static const struct snd_kcontrol_new rx_int7_2_mux = static const struct snd_kcontrol_new rx_int8_2_mux = SOC_DAPM_ENUM("RX INT8_2 MUX Mux", rx_int8_2_mux_chain_enum); +static const struct snd_kcontrol_new int1_1_native_mux = + SOC_DAPM_ENUM("RX INT1_1 NATIVE MUX Mux", int1_1_native_enum); + +static const struct snd_kcontrol_new int2_1_native_mux = + SOC_DAPM_ENUM("RX INT2_1 NATIVE MUX Mux", int2_1_native_enum); + +static const struct snd_kcontrol_new int3_1_native_mux = + SOC_DAPM_ENUM("RX INT3_1 NATIVE MUX Mux", int3_1_native_enum); + +static const struct snd_kcontrol_new int4_1_native_mux = + SOC_DAPM_ENUM("RX INT4_1 NATIVE MUX Mux", int4_1_native_enum); + static const struct snd_kcontrol_new rx_int0_1_mix_inp0_mux = SOC_DAPM_ENUM("RX INT0_1 MIX1 INP0 Mux", rx_int0_1_mix_inp0_chain_enum); @@ -10020,12 +10100,18 @@ static const struct snd_kcontrol_new anc_hphr_switch = static const struct snd_kcontrol_new anc_ear_switch = SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); +static const struct snd_kcontrol_new anc_ear_spkr_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); + static const struct snd_kcontrol_new anc_lineout1_switch = SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); static const struct snd_kcontrol_new anc_lineout2_switch = SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); +static const struct snd_kcontrol_new anc_spkr_pa_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); + static const struct snd_kcontrol_new adc_us_mux0_switch = SOC_DAPM_SINGLE("US_Switch", SND_SOC_NOPM, 0, 1, 0); @@ -10645,6 +10731,14 @@ static const struct snd_soc_dapm_widget tasha_dapm_widgets[] = { tasha_codec_configure_cpe_input, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("RX INT1_1 NATIVE MUX", SND_SOC_NOPM, 0, 0, + &int1_1_native_mux), + SND_SOC_DAPM_MUX("RX INT2_1 NATIVE MUX", SND_SOC_NOPM, 0, 0, + &int2_1_native_mux), + SND_SOC_DAPM_MUX("RX INT3_1 NATIVE MUX", SND_SOC_NOPM, 0, 0, + &int3_1_native_mux), + SND_SOC_DAPM_MUX("RX INT4_1 NATIVE MUX", SND_SOC_NOPM, 0, 0, + &int4_1_native_mux), SND_SOC_DAPM_MUX("RX MIX TX0 MUX", SND_SOC_NOPM, 0, 0, &rx_mix_tx0_mux), SND_SOC_DAPM_MUX("RX MIX TX1 MUX", SND_SOC_NOPM, 0, 0, @@ -10791,6 +10885,9 @@ static const struct snd_soc_dapm_widget tasha_dapm_widgets[] = { tasha_codec_enable_lineout_pa, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("ANC SPK1 PA", SND_SOC_NOPM, 0, 0, NULL, 0, + tasha_codec_enable_spk_anc, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUTPUT("HPHL"), SND_SOC_DAPM_OUTPUT("HPHR"), @@ -10852,10 +10949,14 @@ static const struct snd_soc_dapm_widget tasha_dapm_widgets[] = { &anc_hphr_switch), SND_SOC_DAPM_SWITCH("ANC EAR Enable", SND_SOC_NOPM, 0, 0, &anc_ear_switch), + SND_SOC_DAPM_SWITCH("ANC OUT EAR SPKR Enable", SND_SOC_NOPM, 0, 0, + &anc_ear_spkr_switch), SND_SOC_DAPM_SWITCH("ANC LINEOUT1 Enable", SND_SOC_NOPM, 0, 0, &anc_lineout1_switch), SND_SOC_DAPM_SWITCH("ANC LINEOUT2 Enable", SND_SOC_NOPM, 0, 0, &anc_lineout2_switch), + SND_SOC_DAPM_SWITCH("ANC SPKR PA Enable", SND_SOC_NOPM, 0, 0, + &anc_spkr_pa_switch), }; static int tasha_get_channel_map(struct snd_soc_dai *dai, @@ -10945,7 +11046,7 @@ static int tasha_set_channel_map(struct snd_soc_dai *dai, /* Reserve TX12/TX13 for MAD data channel */ dai_data = &tasha->dai[AIF4_MAD_TX]; if (dai_data) { - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) list_add_tail(&core->tx_chs[TASHA_TX13].list, &dai_data->wcd9xxx_ch_list); else @@ -11896,9 +11997,9 @@ static ssize_t tasha_codec_version_read(struct snd_info_entry *entry, wcd9xxx = tasha->wcd9xxx; if (wcd9xxx->codec_type->id_major == TASHA_MAJOR) { - if (TASHA_IS_1_0(wcd9xxx->version)) + if (TASHA_IS_1_0(wcd9xxx)) len = snprintf(buffer, sizeof(buffer), "WCD9335_1_0\n"); - else if (TASHA_IS_1_1(wcd9xxx->version)) + else if (TASHA_IS_1_1(wcd9xxx)) len = snprintf(buffer, sizeof(buffer), "WCD9335_1_1\n"); else snprintf(buffer, sizeof(buffer), "VER_UNDEFINED\n"); @@ -12240,7 +12341,7 @@ static void tasha_update_reg_reset_values(struct snd_soc_codec *codec) u32 i; struct wcd9xxx *tasha_core = dev_get_drvdata(codec->dev->parent); - if (TASHA_IS_1_1(tasha_core->version)) { + if (TASHA_IS_1_1(tasha_core)) { for (i = 0; i < ARRAY_SIZE(tasha_reg_update_reset_val_1_1); i++) snd_soc_write(codec, @@ -12260,27 +12361,27 @@ static void tasha_codec_init_reg(struct snd_soc_codec *codec) tasha_codec_reg_init_common_val[i].mask, tasha_codec_reg_init_common_val[i].val); - if (TASHA_IS_1_1(wcd9xxx->version) || - TASHA_IS_1_0(wcd9xxx->version)) + if (TASHA_IS_1_1(wcd9xxx) || + TASHA_IS_1_0(wcd9xxx)) for (i = 0; i < ARRAY_SIZE(tasha_codec_reg_init_1_x_val); i++) snd_soc_update_bits(codec, tasha_codec_reg_init_1_x_val[i].reg, tasha_codec_reg_init_1_x_val[i].mask, tasha_codec_reg_init_1_x_val[i].val); - if (TASHA_IS_1_1(wcd9xxx->version)) { + if (TASHA_IS_1_1(wcd9xxx)) { for (i = 0; i < ARRAY_SIZE(tasha_codec_reg_init_val_1_1); i++) snd_soc_update_bits(codec, tasha_codec_reg_init_val_1_1[i].reg, tasha_codec_reg_init_val_1_1[i].mask, tasha_codec_reg_init_val_1_1[i].val); - } else if (TASHA_IS_1_0(wcd9xxx->version)) { + } else if (TASHA_IS_1_0(wcd9xxx)) { for (i = 0; i < ARRAY_SIZE(tasha_codec_reg_init_val_1_0); i++) snd_soc_update_bits(codec, tasha_codec_reg_init_val_1_0[i].reg, tasha_codec_reg_init_val_1_0[i].mask, tasha_codec_reg_init_val_1_0[i].val); - } else if (TASHA_IS_2_0(wcd9xxx->version)) { + } else if (TASHA_IS_2_0(wcd9xxx)) { for (i = 0; i < ARRAY_SIZE(tasha_codec_reg_init_val_2_0); i++) snd_soc_update_bits(codec, tasha_codec_reg_init_val_2_0[i].reg, @@ -12767,7 +12868,7 @@ static int tasha_codec_cpe_fll_enable(struct snd_soc_codec *codec, } } - if (TASHA_IS_1_0(wcd9xxx->version)) { + if (TASHA_IS_1_0(wcd9xxx)) { tasha_cdc_mclk_enable(codec, true, false); clk_sel_reg_val = 0x02; } @@ -12806,7 +12907,7 @@ static int tasha_codec_cpe_fll_enable(struct snd_soc_codec *codec, snd_soc_update_bits(codec, WCD9335_CPE_FLL_USER_CTL_0, 0x01, 0x00); - if (TASHA_IS_1_0(wcd9xxx->version)) + if (TASHA_IS_1_0(wcd9xxx)) tasha_cdc_mclk_enable(codec, false, false); /* @@ -12935,7 +13036,7 @@ static int tasha_cpe_err_irq_control(struct snd_soc_codec *codec, struct tasha_priv *tasha = snd_soc_codec_get_drvdata(codec); u8 irq_bits; - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) irq_bits = 0xFF; else irq_bits = 0x3F; @@ -13248,7 +13349,7 @@ static int tasha_codec_probe(struct snd_soc_codec *codec) } /* Initialize MBHC module */ - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) { + if (TASHA_IS_2_0(tasha->wcd9xxx)) { wcd_mbhc_registers[WCD_MBHC_FSM_STATUS].reg = WCD9335_MBHC_FSM_STATUS; wcd_mbhc_registers[WCD_MBHC_FSM_STATUS].mask = 0x01; @@ -13362,6 +13463,7 @@ static int tasha_codec_probe(struct snd_soc_codec *codec) snd_soc_dapm_disable_pin(dapm, "ANC HPHR PA"); snd_soc_dapm_disable_pin(dapm, "ANC EAR PA"); snd_soc_dapm_disable_pin(dapm, "ANC EAR"); + snd_soc_dapm_disable_pin(dapm, "ANC SPK1 PA"); mutex_unlock(&tasha->codec_mutex); snd_soc_dapm_sync(dapm); @@ -13633,7 +13735,7 @@ static int tasha_swrm_clock(void *handle, bool enable) if (enable) { tasha->swr_clk_users++; if (tasha->swr_clk_users == 1) { - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) regmap_update_bits( tasha->wcd9xxx->regmap, WCD9335_TEST_DEBUG_NPL_DLY_TEST_1, @@ -13650,7 +13752,7 @@ static int tasha_swrm_clock(void *handle, bool enable) WCD9335_CDC_CLK_RST_CTRL_SWR_CONTROL, 0x01, 0x00); __tasha_cdc_mclk_enable(tasha, false); - if (TASHA_IS_2_0(tasha->wcd9xxx->version)) + if (TASHA_IS_2_0(tasha->wcd9xxx)) regmap_update_bits( tasha->wcd9xxx->regmap, WCD9335_TEST_DEBUG_NPL_DLY_TEST_1, diff --git a/sound/soc/codecs/wcd934x/wcd934x-routing.h b/sound/soc/codecs/wcd934x/wcd934x-routing.h index 8b20805de6f9..ac3031ffe615 100644 --- a/sound/soc/codecs/wcd934x/wcd934x-routing.h +++ b/sound/soc/codecs/wcd934x/wcd934x-routing.h @@ -312,6 +312,43 @@ const struct snd_soc_dapm_route tavil_audio_map[] = { {"ADC MUX13", "DMIC", "DMIC MUX13"}, {"ADC MUX13", "AMIC", "AMIC MUX13"}, + {"ADC MUX0", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX0", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX0", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX0", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX1", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX1", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX1", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX1", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX2", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX2", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX2", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX2", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX3", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX3", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX3", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX3", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX4", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX4", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX4", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX4", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX5", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX5", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX5", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX5", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX6", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX6", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX6", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX6", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX7", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX7", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX7", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX7", "ANC_FB_TUNE2", "ADC MUX13"}, + {"ADC MUX8", "ANC_FB_TUNE1", "ADC MUX10"}, + {"ADC MUX8", "ANC_FB_TUNE1", "ADC MUX11"}, + {"ADC MUX8", "ANC_FB_TUNE2", "ADC MUX12"}, + {"ADC MUX8", "ANC_FB_TUNE2", "ADC MUX13"}, + {"DMIC MUX0", "DMIC0", "DMIC0"}, {"DMIC MUX0", "DMIC1", "DMIC1"}, {"DMIC MUX0", "DMIC2", "DMIC2"}, @@ -860,6 +897,29 @@ const struct snd_soc_dapm_route tavil_audio_map[] = { {"RX INT8 CHAIN", NULL, "RX_BIAS"}, {"SPK2 OUT", NULL, "RX INT8 CHAIN"}, + /* ANC Routing */ + {"ANC0 FB MUX", "ANC_IN_EAR", "RX INT0 MIX2"}, + {"ANC0 FB MUX", "ANC_IN_HPHL", "RX INT1 MIX2"}, + {"ANC0 FB MUX", "ANC_IN_LO1", "RX INT3 MIX2"}, + {"ANC0 FB MUX", "ANC_IN_EAR_SPKR", "RX INT7 MIX2"}, + {"ANC1 FB MUX", "ANC_IN_HPHR", "RX INT2 MIX2"}, + {"ANC1 FB MUX", "ANC_IN_LO2", "RX INT4 MIX2"}, + + {"ANC OUT EAR Enable", "Switch", "ADC MUX10"}, + {"ANC OUT EAR Enable", "Switch", "ADC MUX11"}, + {"RX INT0 MIX2", NULL, "ANC OUT EAR Enable"}, + + {"ANC EAR PA", NULL, "RX INT0 DAC"}, + {"ANC EAR", NULL, "ANC EAR PA"}, + + {"ANC OUT EAR SPKR Enable", "Switch", "ADC MUX10"}, + {"ANC OUT EAR SPKR Enable", "Switch", "ADC MUX11"}, + {"RX INT7 MIX2", NULL, "ANC OUT EAR SPKR Enable"}, + + {"ANC SPKR PA Enable", "Switch", "RX INT7 CHAIN"}, + {"ANC SPK1 PA", NULL, "ANC SPKR PA Enable"}, + {"SPK1 OUT", NULL, "ANC SPK1 PA"}, + /* * SRC0, SRC1 inputs to Sidetone RX Mixer * on RX0, RX1, RX2, RX3, RX4 and RX7 chains diff --git a/sound/soc/codecs/wcd934x/wcd934x.c b/sound/soc/codecs/wcd934x/wcd934x.c index 84e5c09494c6..28914ed3f937 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.c +++ b/sound/soc/codecs/wcd934x/wcd934x.c @@ -66,6 +66,15 @@ #define WCD934X_FORMATS_S16_LE (SNDRV_PCM_FMTBIT_S16_LE) +/* Macros for packing register writes into a U32 */ +#define WCD934X_PACKED_REG_SIZE sizeof(u32) +#define WCD934X_CODEC_UNPACK_ENTRY(packed, reg, mask, val) \ + do { \ + ((reg) = ((packed >> 16) & (0xffff))); \ + ((mask) = ((packed >> 8) & (0xff))); \ + ((val) = ((packed) & (0xff))); \ + } while (0) + #define STRING(name) #name #define WCD_DAPM_ENUM(name, reg, offset, text) \ static SOC_ENUM_SINGLE_DECL(name##_enum, reg, offset, text); \ @@ -91,6 +100,7 @@ static const struct snd_kcontrol_new name##_mux = \ #define WCD934X_MCLK_CLK_12P288MHZ 12288000 #define WCD934X_MCLK_CLK_9P6MHZ 9600000 +#define WCD934X_INTERP_MUX_NUM_INPUTS 3 #define WCD934X_NUM_INTERPOLATORS 9 #define WCD934X_NUM_DECIMATORS 9 #define WCD934X_RX_PATH_CTL_OFFSET 20 @@ -129,6 +139,8 @@ static const struct snd_kcontrol_new name##_mux = \ #define WCD934X_MAD_AUDIO_FIRMWARE_PATH "wcd934x/wcd934x_mad_audio.bin" +#define TAVIL_VERSION_ENTRY_SIZE 17 + enum { VI_SENSE_1, VI_SENSE_2, @@ -177,6 +189,16 @@ enum { INTn_2_INP_SEL_PROXIMITY, }; +enum { + INTERP_MAIN_PATH, + INTERP_MIX_PATH, +}; + +struct tavil_idle_detect_config { + u8 hph_idle_thr; + u8 hph_idle_detect_en; +}; + static const struct intr_data wcd934x_intr_table[] = { {WCD9XXX_IRQ_SLIMBUS, false}, {WCD934X_IRQ_MBHC_SW_DET, true}, @@ -378,6 +400,24 @@ static struct afe_param_cdc_reg_cfg audio_reg_cfg[] = { (WCD934X_REGISTER_START_OFFSET + WCD934X_SB_PGD_PORT_RX_BASE), SB_PGD_PORT_RX_ENABLE_N, 0x1, WCD934X_REG_BITS, 0x1 }, + { + 1, + (WCD934X_REGISTER_START_OFFSET + + WCD934X_CDC_ANC0_IIR_ADAPT_CTL), + AANC_FF_GAIN_ADAPTIVE, 0x4, WCD934X_REG_BITS, 0 + }, + { + 1, + (WCD934X_REGISTER_START_OFFSET + + WCD934X_CDC_ANC0_IIR_ADAPT_CTL), + AANC_FFGAIN_ADAPTIVE_EN, 0x8, WCD934X_REG_BITS, 0 + }, + { + 1, + (WCD934X_REGISTER_START_OFFSET + + WCD934X_CDC_ANC0_FF_A_GAIN_CTL), + AANC_GAIN_CONTROL, 0xFF, WCD934X_REG_BITS, 0 + }, }; static struct afe_param_cdc_reg_cfg_data tavil_audio_reg_cfg = { @@ -385,6 +425,11 @@ static struct afe_param_cdc_reg_cfg_data tavil_audio_reg_cfg = { .reg_data = audio_reg_cfg, }; +static struct afe_param_id_cdc_aanc_version tavil_cdc_aanc_version = { + .cdc_aanc_minor_version = AFE_API_VERSION_CDC_AANC_VERSION, + .aanc_hw_version = AANC_HW_BLOCK_VERSION_2, +}; + static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0); static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1); static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1); @@ -451,8 +496,14 @@ struct tavil_priv { s32 micb_ref[TAVIL_MAX_MICBIAS]; s32 pullup_ref[TAVIL_MAX_MICBIAS]; + /* ANC related */ + u32 anc_slot; + bool anc_func; + /* compander */ int comp_enabled[COMPANDER_MAX]; + int ear_spkr_gain; + /* class h specific data */ struct wcd_clsh_cdc_data clsh_d; /* Tavil Interpolator Mode Select for EAR, HPH_L and HPH_R */ @@ -461,6 +512,9 @@ struct tavil_priv { /* Mad switch reference count */ int mad_switch_cnt; + /* track tavil interface type */ + u8 intf_type; + /* to track the status */ unsigned long status_mask; @@ -494,6 +548,10 @@ struct tavil_priv { /* cal info for codec */ struct fw_info *fw_data; + /* Entry for version info */ + struct snd_info_entry *entry; + struct snd_info_entry *version_entry; + /* SVS voting related */ struct mutex svs_mutex; int svs_ref_cnt; @@ -504,6 +562,7 @@ struct tavil_priv { /* Main path clock users count */ int main_clk_users[WCD934X_NUM_INTERPOLATORS]; struct tavil_dsd_config *dsd_config; + struct tavil_idle_detect_config idle_det_cfg; }; static const struct tavil_reg_mask_val tavil_spkr_default[] = { @@ -537,7 +596,7 @@ int wcd934x_get_codec_info(struct wcd9xxx *wcd9xxx, { u16 id_minor, id_major; struct regmap *wcd_regmap; - int rc, version = 0; + int rc, version = -1; if (!wcd9xxx || !wcd_type) return -EINVAL; @@ -561,8 +620,22 @@ int wcd934x_get_codec_info(struct wcd9xxx *wcd9xxx, dev_info(wcd9xxx->dev, "%s: wcd9xxx chip id major 0x%x, minor 0x%x\n", __func__, id_major, id_minor); - /* Version detection */ - version = 1.0; + if (id_major != TAVIL_MAJOR) + goto version_unknown; + + /* + * As fine version info cannot be retrieved before tavil probe. + * Assign coarse versions for possible future use before tavil probe. + */ + if (id_minor == cpu_to_le16(0)) + version = TAVIL_VERSION_1_0; + else if (id_minor == cpu_to_le16(0x01)) + version = TAVIL_VERSION_1_1; + +version_unknown: + if (version < 0) + dev_err(wcd9xxx->dev, "%s: wcd934x version unknown\n", + __func__); /* Fill codec type info */ wcd_type->id_major = id_major; @@ -720,7 +793,7 @@ void *tavil_get_afe_config(struct snd_soc_codec *codec, case AFE_SLIMBUS_SLAVE_PORT_CONFIG: return &tavil_slimbus_slave_port_cfg; case AFE_AANC_VERSION: - return NULL; + return &tavil_cdc_aanc_version; case AFE_CDC_REGISTER_PAGE_CONFIG: return &tavil_cdc_reg_page_cfg; default: @@ -731,6 +804,37 @@ void *tavil_get_afe_config(struct snd_soc_codec *codec, } EXPORT_SYMBOL(tavil_get_afe_config); +static bool is_tavil_playback_dai(int dai_id) +{ + if ((dai_id == AIF1_PB) || (dai_id == AIF2_PB) || + (dai_id == AIF3_PB) || (dai_id == AIF4_PB)) + return true; + + return false; +} + +static int tavil_find_playback_dai_id_for_port(int port_id, + struct tavil_priv *tavil) +{ + struct wcd9xxx_codec_dai_data *dai; + struct wcd9xxx_ch *ch; + int i, slv_port_id; + + for (i = AIF1_PB; i < NUM_CODEC_DAIS; i++) { + if (!is_tavil_playback_dai(i)) + continue; + + dai = &tavil->dai[i]; + list_for_each_entry(ch, &dai->wcd9xxx_ch_list, list) { + slv_port_id = wcd9xxx_get_slave_port(ch->ch_num); + if ((slv_port_id > 0) && (slv_port_id == port_id)) + return i; + } + } + + return -EINVAL; +} + static void tavil_vote_svs(struct tavil_priv *tavil, bool vote) { struct wcd9xxx *wcd9xxx; @@ -761,6 +865,212 @@ done: mutex_unlock(&tavil->svs_mutex); } +static int tavil_get_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tavil->anc_slot; + return 0; +} + +static int tavil_put_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + tavil->anc_slot = ucontrol->value.integer.value[0]; + return 0; +} + +static int tavil_get_anc_func(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = (tavil->anc_func == true ? 1 : 0); + return 0; +} + +static int tavil_put_anc_func(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + mutex_lock(&tavil->codec_mutex); + tavil->anc_func = (!ucontrol->value.integer.value[0] ? false : true); + dev_dbg(codec->dev, "%s: anc_func %x", __func__, tavil->anc_func); + + if (tavil->anc_func == true) { + snd_soc_dapm_enable_pin(dapm, "ANC EAR PA"); + snd_soc_dapm_enable_pin(dapm, "ANC EAR"); + snd_soc_dapm_enable_pin(dapm, "ANC SPK1 PA"); + snd_soc_dapm_disable_pin(dapm, "EAR PA"); + snd_soc_dapm_disable_pin(dapm, "EAR"); + } else { + snd_soc_dapm_disable_pin(dapm, "ANC EAR PA"); + snd_soc_dapm_disable_pin(dapm, "ANC EAR"); + snd_soc_dapm_disable_pin(dapm, "ANC SPK1 PA"); + snd_soc_dapm_enable_pin(dapm, "EAR PA"); + snd_soc_dapm_enable_pin(dapm, "EAR"); + } + mutex_unlock(&tavil->codec_mutex); + + snd_soc_dapm_sync(dapm); + return 0; +} + +static int tavil_codec_enable_anc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + const char *filename; + const struct firmware *fw; + int i; + int ret = 0; + int num_anc_slots; + struct wcd9xxx_anc_header *anc_head; + struct firmware_cal *hwdep_cal = NULL; + u32 anc_writes_size = 0; + u32 anc_cal_size = 0; + int anc_size_remaining; + u32 *anc_ptr; + u16 reg; + u8 mask, val; + size_t cal_size; + const void *data; + + if (!tavil->anc_func) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hwdep_cal = wcdcal_get_fw_cal(tavil->fw_data, WCD9XXX_ANC_CAL); + if (hwdep_cal) { + data = hwdep_cal->data; + cal_size = hwdep_cal->size; + dev_dbg(codec->dev, "%s: using hwdep calibration, cal_size %zd", + __func__, cal_size); + } else { + filename = "WCD934X/WCD934X_anc.bin"; + ret = request_firmware(&fw, filename, codec->dev); + if (IS_ERR_VALUE(ret)) { + dev_err(codec->dev, "%s: Failed to acquire ANC data: %d\n", + __func__, ret); + return ret; + } + if (!fw) { + dev_err(codec->dev, "%s: Failed to get anc fw\n", + __func__); + return -ENODEV; + } + data = fw->data; + cal_size = fw->size; + dev_dbg(codec->dev, "%s: using request_firmware calibration\n", + __func__); + } + if (cal_size < sizeof(struct wcd9xxx_anc_header)) { + dev_err(codec->dev, "%s: Invalid cal_size %zd\n", + __func__, cal_size); + ret = -EINVAL; + goto err; + } + /* First number is the number of register writes */ + anc_head = (struct wcd9xxx_anc_header *)(data); + anc_ptr = (u32 *)(data + sizeof(struct wcd9xxx_anc_header)); + anc_size_remaining = cal_size - + sizeof(struct wcd9xxx_anc_header); + num_anc_slots = anc_head->num_anc_slots; + + if (tavil->anc_slot >= num_anc_slots) { + dev_err(codec->dev, "%s: Invalid ANC slot selected\n", + __func__); + ret = -EINVAL; + goto err; + } + for (i = 0; i < num_anc_slots; i++) { + if (anc_size_remaining < WCD934X_PACKED_REG_SIZE) { + dev_err(codec->dev, "%s: Invalid register format\n", + __func__); + ret = -EINVAL; + goto err; + } + anc_writes_size = (u32)(*anc_ptr); + anc_size_remaining -= sizeof(u32); + anc_ptr += 1; + + if ((anc_writes_size * WCD934X_PACKED_REG_SIZE) > + anc_size_remaining) { + dev_err(codec->dev, "%s: Invalid register format\n", + __func__); + ret = -EINVAL; + goto err; + } + + if (tavil->anc_slot == i) + break; + + anc_size_remaining -= (anc_writes_size * + WCD934X_PACKED_REG_SIZE); + anc_ptr += anc_writes_size; + } + if (i == num_anc_slots) { + dev_err(codec->dev, "%s: Selected ANC slot not present\n", + __func__); + ret = -EINVAL; + goto err; + } + + anc_cal_size = anc_writes_size; + for (i = 0; i < anc_writes_size; i++) { + WCD934X_CODEC_UNPACK_ENTRY(anc_ptr[i], reg, mask, val); + snd_soc_write(codec, reg, (val & mask)); + } + + if (!hwdep_cal) + release_firmware(fw); + break; + case SND_SOC_DAPM_POST_PMU: + /* Remove ANC Rx from reset */ + snd_soc_update_bits(codec, WCD934X_CDC_ANC0_CLK_RESET_CTL, + 0x08, 0x00); + snd_soc_update_bits(codec, WCD934X_CDC_ANC1_CLK_RESET_CTL, + 0x08, 0x00); + break; + case SND_SOC_DAPM_POST_PMD: + if (!strcmp(w->name, "ANC EAR PA") || + !strcmp(w->name, "ANC SPK1 PA")) { + snd_soc_update_bits(codec, WCD934X_CDC_ANC0_MODE_1_CTL, + 0x30, 0x00); + msleep(50); + snd_soc_update_bits(codec, WCD934X_CDC_ANC0_MODE_1_CTL, + 0x01, 0x00); + snd_soc_update_bits(codec, + WCD934X_CDC_ANC0_CLK_RESET_CTL, + 0x38, 0x38); + snd_soc_update_bits(codec, + WCD934X_CDC_ANC0_CLK_RESET_CTL, + 0x07, 0x00); + snd_soc_update_bits(codec, + WCD934X_CDC_ANC0_CLK_RESET_CTL, + 0x38, 0x00); + } + break; + } + + return 0; +err: + if (!hwdep_cal) + release_firmware(fw); + return ret; +} + static int tavil_vi_feed_mixer_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1353,10 +1663,40 @@ static int tavil_codec_enable_rx_bias(struct snd_soc_dapm_widget *w, return 0; } +static int tavil_codec_enable_spkr_anc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + if (!tavil->anc_func) + return 0; + + dev_dbg(codec->dev, "%s: w: %s event: %d anc: %d\n", __func__, + w->name, event, tavil->anc_func); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = tavil_codec_enable_anc(w, kcontrol, event); + snd_soc_update_bits(codec, WCD934X_CDC_RX7_RX_PATH_CFG0, + 0x10, 0x10); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, WCD934X_CDC_RX7_RX_PATH_CFG0, + 0x10, 0x00); + ret = tavil_codec_enable_anc(w, kcontrol, event); + break; + } + return ret; +} + static int tavil_codec_enable_ear_pa(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + int ret = 0; struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event); @@ -1377,11 +1717,43 @@ static int tavil_codec_enable_ear_pa(struct snd_soc_dapm_widget *w, WCD934X_CDC_RX0_RX_PATH_MIX_CTL, 0x10, 0x00); break; - default: + case SND_SOC_DAPM_POST_PMD: + /* + * 5ms sleep is required after PA is disabled as per + * HW requirement + */ + usleep_range(5000, 5500); + + if (!(strcmp(w->name, "ANC EAR PA"))) { + ret = tavil_codec_enable_anc(w, kcontrol, event); + snd_soc_update_bits(codec, WCD934X_CDC_RX0_RX_PATH_CFG0, + 0x10, 0x00); + } break; }; - return 0; + return ret; +} + +static void tavil_codec_override(struct snd_soc_codec *codec, int mode, + int event) +{ + if (mode == CLS_AB || mode == CLS_AB_HIFI) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!(snd_soc_read(codec, + WCD934X_CDC_RX2_RX_PATH_CTL) & 0x10) && + (!(snd_soc_read(codec, + WCD934X_CDC_RX1_RX_PATH_CTL) & 0x10))) + snd_soc_update_bits(codec, + WCD9XXX_A_ANA_RX_SUPPLIES, 0x02, 0x02); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, + WCD9XXX_A_ANA_RX_SUPPLIES, 0x02, 0x00); + break; + } + } } static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, @@ -1396,6 +1768,8 @@ static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, WCD934X_HPH_REFBUFF_LP_CTL, + 0x06, (0x03 << 1)); set_bit(HPH_PA_DELAY, &tavil->status_mask); break; case SND_SOC_DAPM_POST_PMU: @@ -1410,6 +1784,9 @@ static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, /* Remove mute */ snd_soc_update_bits(codec, WCD934X_CDC_RX2_RX_PATH_CTL, 0x10, 0x00); + /* Enable GM3 boost */ + snd_soc_update_bits(codec, WCD934X_HPH_CNP_WG_CTL, + 0x80, 0x80); /* Enable AutoChop timer at the end of power up */ snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x02); @@ -1427,6 +1804,7 @@ static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2, 0x04, 0x00); } + tavil_codec_override(codec, tavil->hph_mode, event); break; case SND_SOC_DAPM_PRE_PMD: /* Enable DSD Mute before PA disable */ @@ -1436,6 +1814,9 @@ static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, 0x04, 0x04); break; case SND_SOC_DAPM_POST_PMD: + tavil_codec_override(codec, tavil->hph_mode, event); + snd_soc_update_bits(codec, WCD934X_HPH_REFBUFF_LP_CTL, + 0x06, 0x0); /* 5ms sleep is required after PA disable */ usleep_range(5000, 5100); break; @@ -1456,6 +1837,8 @@ static int tavil_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, WCD934X_HPH_REFBUFF_LP_CTL, + 0x06, (0x03 << 1)); set_bit(HPH_PA_DELAY, &tavil->status_mask); break; case SND_SOC_DAPM_POST_PMU: @@ -1470,6 +1853,12 @@ static int tavil_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, /* Remove Mute on primary path */ snd_soc_update_bits(codec, WCD934X_CDC_RX1_RX_PATH_CTL, 0x10, 0x00); + /* Enable GM3 boost */ + snd_soc_update_bits(codec, WCD934X_HPH_CNP_WG_CTL, + 0x80, 0x80); + /* Enable AutoChop timer at the end of power up */ + snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_HPH_TIMER1, + 0x02, 0x02); /* Remove mix path mute if it is enabled */ if ((snd_soc_read(codec, WCD934X_CDC_RX1_RX_PATH_MIX_CTL)) & 0x10) @@ -1484,6 +1873,7 @@ static int tavil_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2, 0x04, 0x00); } + tavil_codec_override(codec, tavil->hph_mode, event); break; case SND_SOC_DAPM_PRE_PMD: /* Enable DSD Mute before PA disable */ @@ -1493,6 +1883,9 @@ static int tavil_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, 0x04, 0x04); break; case SND_SOC_DAPM_POST_PMD: + tavil_codec_override(codec, tavil->hph_mode, event); + snd_soc_update_bits(codec, WCD934X_HPH_REFBUFF_LP_CTL, + 0x06, 0x0); /* 5ms sleep is required after PA disable */ usleep_range(5000, 5100); break; @@ -1550,6 +1943,7 @@ static int tavil_codec_ear_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + int ret = 0; struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); @@ -1560,10 +1954,17 @@ static int tavil_codec_ear_dac_event(struct snd_soc_dapm_widget *w, /* Disable AutoChop timer during power up */ snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x00); + + if (tavil->anc_func) + ret = tavil_codec_enable_anc(w, kcontrol, event); + wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_PRE_DAC, WCD_CLSH_STATE_EAR, CLS_H_NORMAL); + if (tavil->anc_func) + snd_soc_update_bits(codec, WCD934X_CDC_RX0_RX_PATH_CFG0, + 0x10, 0x10); break; case SND_SOC_DAPM_POST_PMD: wcd_clsh_fsm(codec, &tavil->clsh_d, @@ -1575,7 +1976,7 @@ static int tavil_codec_ear_dac_event(struct snd_soc_dapm_widget *w, break; }; - return 0; + return ret; } static int tavil_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, @@ -1602,9 +2003,17 @@ static int tavil_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, __func__, hph_mode); return -EINVAL; } + if ((hph_mode != CLS_H_LP) && (hph_mode != CLS_H_ULP)) + /* Ripple freq control enable */ + snd_soc_update_bits(codec, + WCD934X_SIDO_NEW_VOUT_D_FREQ2, + 0x01, 0x01); /* Disable AutoChop timer during power up */ snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_HPH_TIMER1, - 0x02, 0x00); + 0x02, 0x00); + /* Set RDAC gain */ + snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_RDAC_GAIN_CTL, + 0xF0, 0x40); if (dsd_conf && (snd_soc_read(codec, WCD934X_CDC_DSD1_PATH_CTL) & 0x01)) @@ -1613,8 +2022,7 @@ static int tavil_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_PRE_DAC, WCD_CLSH_STATE_HPHR, - ((hph_mode == CLS_H_LOHIFI) ? - CLS_H_HIFI : hph_mode)); + hph_mode); break; case SND_SOC_DAPM_POST_PMD: /* 1000us required as per HW requirement */ @@ -1622,8 +2030,15 @@ static int tavil_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_POST_PA, WCD_CLSH_STATE_HPHR, - ((hph_mode == CLS_H_LOHIFI) ? - CLS_H_HIFI : hph_mode)); + hph_mode); + if ((hph_mode != CLS_H_LP) && (hph_mode != CLS_H_ULP)) + /* Ripple freq control disable */ + snd_soc_update_bits(codec, + WCD934X_SIDO_NEW_VOUT_D_FREQ2, + 0x01, 0x0); + /* Re-set RDAC gain */ + snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_RDAC_GAIN_CTL, + 0xF0, 0x0); break; default: break; @@ -1657,6 +2072,17 @@ static int tavil_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, __func__, hph_mode); return -EINVAL; } + if ((hph_mode != CLS_H_LP) && (hph_mode != CLS_H_ULP)) + /* Ripple freq control enable */ + snd_soc_update_bits(codec, + WCD934X_SIDO_NEW_VOUT_D_FREQ2, + 0x01, 0x01); + /* Disable AutoChop timer during power up */ + snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_HPH_TIMER1, + 0x02, 0x00); + /* Set RDAC gain */ + snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_RDAC_GAIN_CTL, + 0xF0, 0x40); if (dsd_conf && (snd_soc_read(codec, WCD934X_CDC_DSD0_PATH_CTL) & 0x01)) hph_mode = CLS_H_HIFI; @@ -1664,8 +2090,7 @@ static int tavil_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_PRE_DAC, WCD_CLSH_STATE_HPHL, - ((hph_mode == CLS_H_LOHIFI) ? - CLS_H_HIFI : hph_mode)); + hph_mode); break; case SND_SOC_DAPM_POST_PMD: /* 1000us required as per HW requirement */ @@ -1673,8 +2098,15 @@ static int tavil_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_POST_PA, WCD_CLSH_STATE_HPHL, - ((hph_mode == CLS_H_LOHIFI) ? - CLS_H_HIFI : hph_mode)); + hph_mode); + if ((hph_mode != CLS_H_LP) && (hph_mode != CLS_H_ULP)) + /* Ripple freq control disable */ + snd_soc_update_bits(codec, + WCD934X_SIDO_NEW_VOUT_D_FREQ2, + 0x01, 0x0); + /* Re-set RDAC gain */ + snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_RDAC_GAIN_CTL, + 0xF0, 0x0); break; default: break; @@ -2207,6 +2639,46 @@ static int tavil_enable_native_supply(struct snd_soc_dapm_widget *w, return 0; } +static void tavil_codec_hphdelay_lutbypass(struct snd_soc_codec *codec, + u16 interp_idx, int event) +{ + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + u8 hph_dly_mask; + u16 hph_lut_bypass_reg = 0; + u16 hph_comp_ctrl7 = 0; + + + switch (interp_idx) { + case INTERP_HPHL: + hph_dly_mask = 1; + hph_lut_bypass_reg = WCD934X_CDC_TOP_HPHL_COMP_LUT; + hph_comp_ctrl7 = WCD934X_CDC_COMPANDER1_CTL7; + break; + case INTERP_HPHR: + hph_dly_mask = 2; + hph_lut_bypass_reg = WCD934X_CDC_TOP_HPHR_COMP_LUT; + hph_comp_ctrl7 = WCD934X_CDC_COMPANDER2_CTL7; + break; + default: + break; + } + + if (hph_lut_bypass_reg && SND_SOC_DAPM_EVENT_ON(event)) { + snd_soc_update_bits(codec, WCD934X_CDC_CLSH_TEST0, + hph_dly_mask, 0x0); + snd_soc_update_bits(codec, hph_lut_bypass_reg, 0x80, 0x80); + if (tavil->hph_mode == CLS_H_ULP) + snd_soc_update_bits(codec, hph_comp_ctrl7, 0x20, 0x20); + } + + if (hph_lut_bypass_reg && SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_update_bits(codec, WCD934X_CDC_CLSH_TEST0, + hph_dly_mask, hph_dly_mask); + snd_soc_update_bits(codec, hph_lut_bypass_reg, 0x80, 0x00); + snd_soc_update_bits(codec, hph_comp_ctrl7, 0x20, 0x0); + } +} + static void tavil_codec_hd2_control(struct snd_soc_codec *codec, u16 interp_idx, int event) { @@ -2225,18 +2697,68 @@ static void tavil_codec_hd2_control(struct snd_soc_codec *codec, } if (hd2_enable_reg && SND_SOC_DAPM_EVENT_ON(event)) { - snd_soc_update_bits(codec, hd2_scale_reg, 0x3C, 0x10); - snd_soc_update_bits(codec, hd2_scale_reg, 0x03, 0x01); + snd_soc_update_bits(codec, hd2_scale_reg, 0x3C, 0x14); snd_soc_update_bits(codec, hd2_enable_reg, 0x04, 0x04); } if (hd2_enable_reg && SND_SOC_DAPM_EVENT_OFF(event)) { snd_soc_update_bits(codec, hd2_enable_reg, 0x04, 0x00); - snd_soc_update_bits(codec, hd2_scale_reg, 0x03, 0x00); snd_soc_update_bits(codec, hd2_scale_reg, 0x3C, 0x00); } } +static int tavil_codec_config_ear_spkr_gain(struct snd_soc_codec *codec, + int event, int gain_reg) +{ + int comp_gain_offset, val; + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + switch (tavil->swr.spkr_mode) { + /* Compander gain in SPKR_MODE1 case is 12 dB */ + case WCD934X_SPKR_MODE_1: + comp_gain_offset = -12; + break; + /* Default case compander gain is 15 dB */ + default: + comp_gain_offset = -15; + break; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Apply ear spkr gain only if compander is enabled */ + if (tavil->comp_enabled[COMPANDER_7] && + (gain_reg == WCD934X_CDC_RX7_RX_VOL_CTL || + gain_reg == WCD934X_CDC_RX7_RX_VOL_MIX_CTL) && + (tavil->ear_spkr_gain != 0)) { + /* For example, val is -8(-12+5-1) for 4dB of gain */ + val = comp_gain_offset + tavil->ear_spkr_gain - 1; + snd_soc_write(codec, gain_reg, val); + + dev_dbg(codec->dev, "%s: RX7 Volume %d dB\n", + __func__, val); + } + break; + case SND_SOC_DAPM_POST_PMD: + /* + * Reset RX7 volume to 0 dB if compander is enabled and + * ear_spkr_gain is non-zero. + */ + if (tavil->comp_enabled[COMPANDER_7] && + (gain_reg == WCD934X_CDC_RX7_RX_VOL_CTL || + gain_reg == WCD934X_CDC_RX7_RX_VOL_MIX_CTL) && + (tavil->ear_spkr_gain != 0)) { + snd_soc_write(codec, gain_reg, 0x0); + + dev_dbg(codec->dev, "%s: Reset RX7 Volume to 0 dB\n", + __func__); + } + break; + } + + return 0; +} + static int tavil_config_compander(struct snd_soc_codec *codec, int interp_n, int event) { @@ -2267,7 +2789,9 @@ static int tavil_config_compander(struct snd_soc_codec *codec, int interp_n, } if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_update_bits(codec, rx_path_cfg0_reg, 0x02, 0x00); snd_soc_update_bits(codec, comp_ctl0_reg, 0x04, 0x04); + snd_soc_update_bits(codec, comp_ctl0_reg, 0x02, 0x02); snd_soc_update_bits(codec, comp_ctl0_reg, 0x02, 0x00); snd_soc_update_bits(codec, comp_ctl0_reg, 0x01, 0x00); snd_soc_update_bits(codec, comp_ctl0_reg, 0x04, 0x00); @@ -2276,6 +2800,36 @@ static int tavil_config_compander(struct snd_soc_codec *codec, int interp_n, return 0; } +static void tavil_codec_idle_detect_control(struct snd_soc_codec *codec, + int interp, int event) +{ + int reg = 0, mask, val; + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + if (!tavil->idle_det_cfg.hph_idle_detect_en) + return; + + if (interp == INTERP_HPHL) { + reg = WCD934X_CDC_RX_IDLE_DET_PATH_CTL; + mask = 0x01; + val = 0x01; + } + if (interp == INTERP_HPHR) { + reg = WCD934X_CDC_RX_IDLE_DET_PATH_CTL; + mask = 0x02; + val = 0x02; + } + + if (reg && SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_update_bits(codec, reg, mask, val); + + if (reg && SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_update_bits(codec, reg, mask, 0x00); + tavil->idle_det_cfg.hph_idle_thr = 0; + snd_soc_write(codec, WCD934X_CDC_RX_IDLE_DET_CFG3, 0x0); + } +} + /** * tavil_codec_enable_interp_clk - Enable main path Interpolator * clock. @@ -2305,7 +2859,11 @@ int tavil_codec_enable_interp_clk(struct snd_soc_codec *codec, snd_soc_update_bits(codec, main_reg, 0x10, 0x10); /* Clk enable */ snd_soc_update_bits(codec, main_reg, 0x20, 0x20); + tavil_codec_idle_detect_control(codec, interp_idx, + event); tavil_codec_hd2_control(codec, interp_idx, event); + tavil_codec_hphdelay_lutbypass(codec, interp_idx, + event); tavil_config_compander(codec, interp_idx, event); } tavil->main_clk_users[interp_idx]++; @@ -2316,7 +2874,11 @@ int tavil_codec_enable_interp_clk(struct snd_soc_codec *codec, if (tavil->main_clk_users[interp_idx] <= 0) { tavil->main_clk_users[interp_idx] = 0; tavil_config_compander(codec, interp_idx, event); + tavil_codec_hphdelay_lutbypass(codec, interp_idx, + event); tavil_codec_hd2_control(codec, interp_idx, event); + tavil_codec_idle_detect_control(codec, interp_idx, + event); /* Clk Disable */ snd_soc_update_bits(codec, main_reg, 0x20, 0x00); /* Reset enable and disable */ @@ -2334,6 +2896,110 @@ int tavil_codec_enable_interp_clk(struct snd_soc_codec *codec, } EXPORT_SYMBOL(tavil_codec_enable_interp_clk); +static int tavil_codec_set_idle_detect_thr(struct snd_soc_codec *codec, + int interp, int path_type) +{ + int port_id[4] = { 0, 0, 0, 0 }; + int *port_ptr, num_ports; + int bit_width = 0, i; + int mux_reg, mux_reg_val; + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + int dai_id, idle_thr; + + if ((interp != INTERP_HPHL) && (interp != INTERP_HPHR)) + return 0; + + if (!tavil->idle_det_cfg.hph_idle_detect_en) + return 0; + + port_ptr = &port_id[0]; + num_ports = 0; + + /* + * Read interpolator MUX input registers and find + * which slimbus port is connected and store the port + * numbers in port_id array. + */ + if (path_type == INTERP_MIX_PATH) { + mux_reg = WCD934X_CDC_RX_INP_MUX_RX_INT1_CFG1 + + 2 * (interp - 1); + mux_reg_val = snd_soc_read(codec, mux_reg) & 0x0f; + + if ((mux_reg_val >= INTn_2_INP_SEL_RX0) && + (mux_reg_val < INTn_2_INP_SEL_PROXIMITY)) { + *port_ptr++ = mux_reg_val + + WCD934X_RX_PORT_START_NUMBER - 1; + num_ports++; + } + } + + if (path_type == INTERP_MAIN_PATH) { + mux_reg = WCD934X_CDC_RX_INP_MUX_RX_INT1_CFG0 + + 2 * (interp - 1); + mux_reg_val = snd_soc_read(codec, mux_reg) & 0x0f; + i = WCD934X_INTERP_MUX_NUM_INPUTS; + + while (i) { + if ((mux_reg_val >= INTn_1_INP_SEL_RX0) && + (mux_reg_val <= INTn_1_INP_SEL_RX7)) { + *port_ptr++ = mux_reg_val + + WCD934X_RX_PORT_START_NUMBER - + INTn_1_INP_SEL_RX0; + num_ports++; + } + mux_reg_val = (snd_soc_read(codec, mux_reg) & + 0xf0) >> 4; + mux_reg += 1; + i--; + } + } + + dev_dbg(codec->dev, "%s: num_ports: %d, ports[%d %d %d %d]\n", + __func__, num_ports, port_id[0], port_id[1], + port_id[2], port_id[3]); + + i = 0; + while (num_ports) { + dai_id = tavil_find_playback_dai_id_for_port(port_id[i++], + tavil); + + if ((dai_id >= 0) && (dai_id < NUM_CODEC_DAIS)) { + dev_dbg(codec->dev, "%s: dai_id: %d bit_width: %d\n", + __func__, dai_id, + tavil->dai[dai_id].bit_width); + + if (tavil->dai[dai_id].bit_width > bit_width) + bit_width = tavil->dai[dai_id].bit_width; + } + + num_ports--; + } + + switch (bit_width) { + case 16: + idle_thr = 0xff; /* F16 */ + break; + case 24: + case 32: + idle_thr = 0x03; /* F22 */ + break; + default: + idle_thr = 0x00; + break; + } + + dev_dbg(codec->dev, "%s: (new) idle_thr: %d, (cur) idle_thr: %d\n", + __func__, idle_thr, tavil->idle_det_cfg.hph_idle_thr); + + if ((tavil->idle_det_cfg.hph_idle_thr == 0) || + (idle_thr < tavil->idle_det_cfg.hph_idle_thr)) { + snd_soc_write(codec, WCD934X_CDC_RX_IDLE_DET_CFG3, idle_thr); + tavil->idle_det_cfg.hph_idle_thr = idle_thr; + } + + return 0; +} + static int tavil_codec_enable_mix_path(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -2361,6 +3027,8 @@ static int tavil_codec_enable_mix_path(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: + tavil_codec_set_idle_detect_thr(codec, w->shift, + INTERP_MIX_PATH); tavil_codec_enable_interp_clk(codec, event, w->shift); /* Clk enable */ snd_soc_update_bits(codec, mix_reg, 0x20, 0x20); @@ -2387,6 +3055,7 @@ static int tavil_codec_enable_mix_path(struct snd_soc_dapm_widget *w, val = snd_soc_read(codec, gain_reg); val += offset_val; snd_soc_write(codec, gain_reg, val); + tavil_codec_config_ear_spkr_gain(codec, event, gain_reg); break; case SND_SOC_DAPM_POST_PMD: /* Clk Disable */ @@ -2417,6 +3086,7 @@ static int tavil_codec_enable_mix_path(struct snd_soc_dapm_widget *w, val += offset_val; snd_soc_write(codec, gain_reg, val); } + tavil_codec_config_ear_spkr_gain(codec, event, gain_reg); break; }; dev_dbg(codec->dev, "%s event %d name %s\n", __func__, event, w->name); @@ -2474,6 +3144,8 @@ static int tavil_codec_enable_main_path(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: + tavil_codec_set_idle_detect_thr(codec, w->shift, + INTERP_MAIN_PATH); tavil_codec_enable_interp_clk(codec, event, w->shift); break; case SND_SOC_DAPM_POST_PMU: @@ -2499,6 +3171,7 @@ static int tavil_codec_enable_main_path(struct snd_soc_dapm_widget *w, val = snd_soc_read(codec, gain_reg); val += offset_val; snd_soc_write(codec, gain_reg, val); + tavil_codec_config_ear_spkr_gain(codec, event, gain_reg); break; case SND_SOC_DAPM_POST_PMD: tavil_codec_enable_interp_clk(codec, event, w->shift); @@ -2524,6 +3197,7 @@ static int tavil_codec_enable_main_path(struct snd_soc_dapm_widget *w, val += offset_val; snd_soc_write(codec, gain_reg, val); } + tavil_codec_config_ear_spkr_gain(codec, event, gain_reg); break; }; @@ -2898,6 +3572,9 @@ static int tavil_codec_enable_dec(struct snd_soc_dapm_widget *w, break; case SND_SOC_DAPM_POST_PMD: snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x10, 0x00); + snd_soc_update_bits(codec, dec_cfg_reg, + WCD934X_DEC_PWR_LVL_MASK, + WCD934X_DEC_PWR_LVL_DF); break; }; out: @@ -3349,6 +4026,58 @@ static int __tavil_codec_enable_micbias(struct snd_soc_dapm_widget *w, return 0; } +/* + * tavil_codec_enable_standalone_micbias - enable micbias standalone + * @codec: pointer to codec instance + * @micb_num: number of micbias to be enabled + * @enable: true to enable micbias or false to disable + * + * This function is used to enable micbias (1, 2, 3 or 4) during + * standalone independent of whether TX use-case is running or not + * + * Return: error code in case of failure or 0 for success + */ +int tavil_codec_enable_standalone_micbias(struct snd_soc_codec *codec, + int micb_num, + bool enable) +{ + const char * const micb_names[] = { + DAPM_MICBIAS1_STANDALONE, DAPM_MICBIAS2_STANDALONE, + DAPM_MICBIAS3_STANDALONE, DAPM_MICBIAS4_STANDALONE + }; + int micb_index = micb_num - 1; + int rc; + + if (!codec) { + pr_err("%s: Codec memory is NULL\n", __func__); + return -EINVAL; + } + + if ((micb_index < 0) || (micb_index > TAVIL_MAX_MICBIAS - 1)) { + dev_err(codec->dev, "%s: Invalid micbias index, micb_ind:%d\n", + __func__, micb_index); + return -EINVAL; + } + + if (enable) + rc = snd_soc_dapm_force_enable_pin_unlocked( + snd_soc_codec_get_dapm(codec), + micb_names[micb_index]); + else + rc = snd_soc_dapm_disable_pin_unlocked( + snd_soc_codec_get_dapm(codec), + micb_names[micb_index]); + + if (!rc) + snd_soc_dapm_sync(snd_soc_codec_get_dapm(codec)); + else + dev_err(codec->dev, "%s: micbias%d force %s pin failed\n", + __func__, micb_num, (enable ? "enable" : "disable")); + + return rc; +} +EXPORT_SYMBOL(tavil_codec_enable_standalone_micbias); + static int tavil_codec_force_enable_micbias(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -3630,37 +4359,49 @@ static int tavil_compander_put(struct snd_kcontrol *kcontrol, return 0; } +static int tavil_hph_idle_detect_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + int val = 0; + + if (tavil) + val = tavil->idle_det_cfg.hph_idle_detect_en; + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int tavil_hph_idle_detect_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + if (tavil) + tavil->idle_det_cfg.hph_idle_detect_en = + ucontrol->value.integer.value[0]; + + return 0; +} + static int tavil_dmic_pin_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); - u16 ctl_reg; + u16 dmic_pin; u8 reg_val, pinctl_position; pinctl_position = ((struct soc_multi_mixer_control *) kcontrol->private_value)->shift; - switch (pinctl_position >> 3) { - case 0: - ctl_reg = WCD934X_TEST_DEBUG_PIN_CTL_OE_0; - break; - case 1: - ctl_reg = WCD934X_TEST_DEBUG_PIN_CTL_OE_1; - break; - case 2: - ctl_reg = WCD934X_TEST_DEBUG_PIN_CTL_OE_2; - break; - case 3: - ctl_reg = WCD934X_TEST_DEBUG_PIN_CTL_OE_3; - break; - default: - dev_err(codec->dev, "%s: Invalid pinctl position = %d\n", - __func__, pinctl_position); - return -EINVAL; - } - reg_val = snd_soc_read(codec, ctl_reg); - reg_val = (reg_val >> (pinctl_position & 0x07)) & 0x1; - ucontrol->value.integer.value[0] = reg_val; + dmic_pin = pinctl_position & 0x07; + reg_val = snd_soc_read(codec, + WCD934X_TLMM_DMIC1_CLK_PINCFG + dmic_pin - 1); + + ucontrol->value.integer.value[0] = !!reg_val; return 0; } @@ -3669,10 +4410,11 @@ static int tavil_dmic_pin_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); - u16 ctl_reg, cfg_reg; + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + u16 ctl_reg, cfg_reg, dmic_pin; u8 ctl_val, cfg_val, pinctl_position, pinctl_mode, mask; - /* 1- high or low; 0- high Z */ + /* 0- high or low; 1- high Z */ pinctl_mode = ucontrol->value.integer.value[0]; pinctl_position = ((struct soc_multi_mixer_control *) kcontrol->private_value)->shift; @@ -3696,16 +4438,20 @@ static int tavil_dmic_pin_mode_put(struct snd_kcontrol *kcontrol, return -EINVAL; } - ctl_val = pinctl_mode << (pinctl_position & 0x07); + ctl_val = ~(pinctl_mode << (pinctl_position & 0x07)); mask = 1 << (pinctl_position & 0x07); snd_soc_update_bits(codec, ctl_reg, mask, ctl_val); - cfg_reg = WCD934X_TLMM_BIST_MODE_PINCFG + pinctl_position; - if (!pinctl_mode) - cfg_val = 0x4; - else + dmic_pin = pinctl_position & 0x07; + cfg_reg = WCD934X_TLMM_DMIC1_CLK_PINCFG + dmic_pin - 1; + if (pinctl_mode) { + if (tavil->intf_type == WCD9XXX_INTERFACE_TYPE_SLIMBUS) + cfg_val = 0x6; + else + cfg_val = 0xD; + } else cfg_val = 0; - snd_soc_update_bits(codec, cfg_reg, 0x07, cfg_val); + snd_soc_update_bits(codec, cfg_reg, 0x1F, cfg_val); dev_dbg(codec->dev, "%s: reg=0x%x mask=0x%x val=%d reg=0x%x val=%d\n", __func__, ctl_reg, mask, ctl_val, cfg_reg, cfg_val); @@ -3919,6 +4665,76 @@ static int tavil_ear_pa_gain_put(struct snd_kcontrol *kcontrol, return 0; } +static int tavil_ear_spkr_pa_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tavil->ear_spkr_gain; + + dev_dbg(codec->dev, "%s: ucontrol->value.integer.value[0] = %ld\n", + __func__, ucontrol->value.integer.value[0]); + + return 0; +} + +static int tavil_ear_spkr_pa_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + tavil->ear_spkr_gain = ucontrol->value.integer.value[0]; + + dev_dbg(codec->dev, "%s: gain = %d\n", __func__, tavil->ear_spkr_gain); + + return 0; +} + +static int tavil_rx_hph_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tavil->hph_mode; + return 0; +} + +static int tavil_rx_hph_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + u32 mode_val; + + mode_val = ucontrol->value.enumerated.item[0]; + + dev_dbg(codec->dev, "%s: mode: %d\n", __func__, mode_val); + + if (mode_val == 0) { + dev_warn(codec->dev, "%s:Invalid HPH Mode, default to Cls-H LOHiFi\n", + __func__); + mode_val = CLS_H_LOHIFI; + } + tavil->hph_mode = mode_val; + return 0; +} + +static const char * const rx_hph_mode_mux_text[] = { + "CLS_H_INVALID", "CLS_H_HIFI", "CLS_H_LP", "CLS_AB", "CLS_H_LOHIFI", + "CLS_H_ULP", "CLS_AB_HIFI", +}; + +static const struct soc_enum rx_hph_mode_mux_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(rx_hph_mode_mux_text), + rx_hph_mode_mux_text); + +static const char *const tavil_anc_func_text[] = {"OFF", "ON"}; +static const struct soc_enum tavil_anc_func_enum = + SOC_ENUM_SINGLE_EXT(2, tavil_anc_func_text); + /* Cutoff frequency for high pass filter */ static const char * const cf_text[] = { "CF_NEG_3DB_4HZ", "CF_NEG_3DB_75HZ", "CF_NEG_3DB_150HZ" @@ -3933,13 +4749,25 @@ static const char * const amic_pwr_lvl_text[] = { "LOW_PWR", "DEFAULT", "HIGH_PERF" }; +static const char * const hph_idle_detect_text[] = { + "OFF", "ON" +}; + static const char * const tavil_ear_pa_gain_text[] = { "G_6_DB", "G_4P5_DB", "G_3_DB", "G_1P5_DB", "G_0_DB", "G_M2P5_DB", "UNDEFINED", "G_M12_DB" }; +static const char * const tavil_ear_spkr_pa_gain_text[] = { + "G_DEFAULT", "G_0_DB", "G_1_DB", "G_2_DB", "G_3_DB", + "G_4_DB", "G_5_DB", "G_6_DB" +}; + static SOC_ENUM_SINGLE_EXT_DECL(tavil_ear_pa_gain_enum, tavil_ear_pa_gain_text); +static SOC_ENUM_SINGLE_EXT_DECL(tavil_ear_spkr_pa_gain_enum, + tavil_ear_spkr_pa_gain_text); static SOC_ENUM_SINGLE_EXT_DECL(amic_pwr_lvl_enum, amic_pwr_lvl_text); +static SOC_ENUM_SINGLE_EXT_DECL(hph_idle_detect_enum, hph_idle_detect_text); static SOC_ENUM_SINGLE_DECL(cf_dec0_enum, WCD934X_CDC_TX0_TX_PATH_CFG0, 5, cf_text); static SOC_ENUM_SINGLE_DECL(cf_dec1_enum, WCD934X_CDC_TX1_TX_PATH_CFG0, 5, @@ -3990,6 +4818,8 @@ static SOC_ENUM_SINGLE_DECL(cf_int8_2_enum, WCD934X_CDC_RX8_RX_PATH_MIX_CFG, 2, static const struct snd_kcontrol_new tavil_snd_controls[] = { SOC_ENUM_EXT("EAR PA Gain", tavil_ear_pa_gain_enum, tavil_ear_pa_gain_get, tavil_ear_pa_gain_put), + SOC_ENUM_EXT("EAR SPKR PA Gain", tavil_ear_spkr_pa_gain_enum, + tavil_ear_spkr_pa_gain_get, tavil_ear_spkr_pa_gain_put), SOC_SINGLE_TLV("HPHL Volume", WCD934X_HPH_L_EN, 0, 20, 1, line_gain), SOC_SINGLE_TLV("HPHR Volume", WCD934X_HPH_R_EN, 0, 20, 1, line_gain), SOC_SINGLE_TLV("LINEOUT1 Volume", WCD934X_DIFF_LO_LO1_COMPANDER, @@ -4070,6 +4900,11 @@ static const struct snd_kcontrol_new tavil_snd_controls[] = { WCD934X_CDC_SIDETONE_IIR1_IIR_GAIN_B4_CTL, 0, -84, 40, digital_gain), + SOC_SINGLE_EXT("ANC Slot", SND_SOC_NOPM, 0, 100, 0, tavil_get_anc_slot, + tavil_put_anc_slot), + SOC_ENUM_EXT("ANC Function", tavil_anc_func_enum, tavil_get_anc_func, + tavil_put_anc_func), + SOC_ENUM("TX0 HPF cut off", cf_dec0_enum), SOC_ENUM("TX1 HPF cut off", cf_dec1_enum), SOC_ENUM("TX2 HPF cut off", cf_dec2_enum), @@ -4095,6 +4930,9 @@ static const struct snd_kcontrol_new tavil_snd_controls[] = { SOC_ENUM("RX INT8_1 HPF cut off", cf_int8_1_enum), SOC_ENUM("RX INT8_2 HPF cut off", cf_int8_2_enum), + SOC_ENUM_EXT("RX HPH Mode", rx_hph_mode_mux_enum, + tavil_rx_hph_mode_get, tavil_rx_hph_mode_put), + SOC_SINGLE_EXT("IIR0 Enable Band1", IIR0, BAND1, 1, 0, tavil_iir_enable_audio_mixer_get, tavil_iir_enable_audio_mixer_put), @@ -4160,6 +4998,9 @@ static const struct snd_kcontrol_new tavil_snd_controls[] = { SOC_SINGLE_EXT("COMP8 Switch", SND_SOC_NOPM, COMPANDER_8, 1, 0, tavil_compander_get, tavil_compander_put), + SOC_ENUM_EXT("HPH Idle Detect", hph_idle_detect_enum, + tavil_hph_idle_detect_get, tavil_hph_idle_detect_put), + SOC_ENUM_EXT("MAD Input", tavil_conn_mad_enum, tavil_mad_input_get, tavil_mad_input_put), @@ -4435,6 +5276,15 @@ static const char * const amic4_5_sel_text[] = { "AMIC4", "AMIC5" }; +static const char * const anc0_fb_mux_text[] = { + "ZERO", "ANC_IN_HPHL", "ANC_IN_EAR", "ANC_IN_EAR_SPKR", + "ANC_IN_LO1" +}; + +static const char * const anc1_fb_mux_text[] = { + "ZERO", "ANC_IN_HPHR", "ANC_IN_LO2" +}; + static const char * const rx_echo_mux_text[] = { "ZERO", "RX_MIX0", "RX_MIX1", "RX_MIX2", "RX_MIX3", "RX_MIX4", "RX_MIX5", "RX_MIX6", "RX_MIX7", "RX_MIX8" @@ -4886,6 +5736,18 @@ WCD_DAPM_ENUM(int4_2_native, SND_SOC_NOPM, 0, native_mux_text); WCD_DAPM_ENUM(int7_2_native, SND_SOC_NOPM, 0, native_mux_text); WCD_DAPM_ENUM(int8_2_native, SND_SOC_NOPM, 0, native_mux_text); +WCD_DAPM_ENUM(anc0_fb, WCD934X_CDC_RX_INP_MUX_ANC_CFG0, 0, anc0_fb_mux_text); +WCD_DAPM_ENUM(anc1_fb, WCD934X_CDC_RX_INP_MUX_ANC_CFG0, 3, anc1_fb_mux_text); + +static const struct snd_kcontrol_new anc_ear_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); + +static const struct snd_kcontrol_new anc_ear_spkr_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); + +static const struct snd_kcontrol_new anc_spkr_pa_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); + static const struct snd_kcontrol_new mad_cpe1_switch = SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); @@ -5256,6 +6118,9 @@ static const struct snd_soc_dapm_widget tavil_dapm_widgets[] = { WCD_DAPM_MUX("AMIC4_5 SEL", 0, tx_amic4_5), + WCD_DAPM_MUX("ANC0 FB MUX", 0, anc0_fb), + WCD_DAPM_MUX("ANC1 FB MUX", 0, anc1_fb), + SND_SOC_DAPM_INPUT("AMIC1"), SND_SOC_DAPM_INPUT("AMIC2"), SND_SOC_DAPM_INPUT("AMIC3"), @@ -5487,6 +6352,12 @@ static const struct snd_soc_dapm_widget tavil_dapm_widgets[] = { SND_SOC_DAPM_PGA_E("LINEOUT2 PA", WCD934X_ANA_LO_1_2, 6, 0, NULL, 0, tavil_codec_enable_lineout_pa, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("ANC EAR PA", WCD934X_ANA_EAR, 7, 0, NULL, 0, + tavil_codec_enable_ear_pa, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("ANC SPK1 PA", SND_SOC_NOPM, 0, 0, NULL, 0, + tavil_codec_enable_spkr_anc, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUTPUT("EAR"), SND_SOC_DAPM_OUTPUT("HPHL"), @@ -5495,6 +6366,14 @@ static const struct snd_soc_dapm_widget tavil_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("LINEOUT2"), SND_SOC_DAPM_OUTPUT("SPK1 OUT"), SND_SOC_DAPM_OUTPUT("SPK2 OUT"), + SND_SOC_DAPM_OUTPUT("ANC EAR"), + + SND_SOC_DAPM_SWITCH("ANC OUT EAR Enable", SND_SOC_NOPM, 0, 0, + &anc_ear_switch), + SND_SOC_DAPM_SWITCH("ANC OUT EAR SPKR Enable", SND_SOC_NOPM, 0, 0, + &anc_ear_spkr_switch), + SND_SOC_DAPM_SWITCH("ANC SPKR PA Enable", SND_SOC_NOPM, 0, 0, + &anc_spkr_pa_switch), SND_SOC_DAPM_SUPPLY("RX_BIAS", SND_SOC_NOPM, 0, 0, tavil_codec_enable_rx_bias, @@ -6222,6 +7101,104 @@ static int __tavil_cdc_mclk_enable(struct tavil_priv *tavil, return ret; } +static ssize_t tavil_codec_version_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, + char __user *buf, size_t count, + loff_t pos) +{ + struct tavil_priv *tavil; + struct wcd9xxx *wcd9xxx; + char buffer[TAVIL_VERSION_ENTRY_SIZE]; + int len = 0; + + tavil = (struct tavil_priv *) entry->private_data; + if (!tavil) { + pr_err("%s: tavil priv is null\n", __func__); + return -EINVAL; + } + + wcd9xxx = tavil->wcd9xxx; + + switch (wcd9xxx->version) { + case TAVIL_VERSION_WCD9340_1_0: + len = snprintf(buffer, sizeof(buffer), "WCD9340_1_0\n"); + break; + case TAVIL_VERSION_WCD9341_1_0: + len = snprintf(buffer, sizeof(buffer), "WCD9341_1_0\n"); + break; + case TAVIL_VERSION_WCD9340_1_1: + len = snprintf(buffer, sizeof(buffer), "WCD9340_1_1\n"); + break; + case TAVIL_VERSION_WCD9341_1_1: + len = snprintf(buffer, sizeof(buffer), "WCD9341_1_1\n"); + break; + default: + len = snprintf(buffer, sizeof(buffer), "VER_UNDEFINED\n"); + } + + return simple_read_from_buffer(buf, count, &pos, buffer, len); +} + +static struct snd_info_entry_ops tavil_codec_info_ops = { + .read = tavil_codec_version_read, +}; + +/* + * tavil_codec_info_create_codec_entry - creates wcd934x module + * @codec_root: The parent directory + * @codec: Codec instance + * + * Creates wcd934x module and version entry under the given + * parent directory. + * + * Return: 0 on success or negative error code on failure. + */ +int tavil_codec_info_create_codec_entry(struct snd_info_entry *codec_root, + struct snd_soc_codec *codec) +{ + struct snd_info_entry *version_entry; + struct tavil_priv *tavil; + struct snd_soc_card *card; + + if (!codec_root || !codec) + return -EINVAL; + + tavil = snd_soc_codec_get_drvdata(codec); + card = codec->component.card; + tavil->entry = snd_register_module_info(codec_root->module, + "tavil", + codec_root); + if (!tavil->entry) { + dev_dbg(codec->dev, "%s: failed to create wcd934x entry\n", + __func__); + return -ENOMEM; + } + + version_entry = snd_info_create_card_entry(card->snd_card, + "version", + tavil->entry); + if (!version_entry) { + dev_dbg(codec->dev, "%s: failed to create wcd934x version entry\n", + __func__); + return -ENOMEM; + } + + version_entry->private_data = tavil; + version_entry->size = TAVIL_VERSION_ENTRY_SIZE; + version_entry->content = SNDRV_INFO_CONTENT_DATA; + version_entry->c.ops = &tavil_codec_info_ops; + + if (snd_info_register(version_entry) < 0) { + snd_info_free_entry(version_entry); + return -ENOMEM; + } + tavil->version_entry = version_entry; + + return 0; +} +EXPORT_SYMBOL(tavil_codec_info_create_codec_entry); + /** * tavil_cdc_mclk_enable - Enable/disable codec mclk * @@ -6327,7 +7304,6 @@ static const struct tavil_reg_mask_val tavil_codec_reg_defaults[] = { {WCD934X_BIAS_VBG_FINE_ADJ, 0xFF, 0x75}, {WCD934X_CODEC_CPR_SVS_CX_VDD, 0xFF, 0x7C}, /* value in svs mode */ {WCD934X_CODEC_CPR_SVS2_CX_VDD, 0xFF, 0x58}, /* value in svs2 mode */ - {WCD934X_SIDO_NEW_VOUT_D_FREQ2, 0x01, 0x01}, {WCD934X_CDC_RX0_RX_PATH_DSMDEM_CTL, 0x01, 0x01}, {WCD934X_CDC_RX1_RX_PATH_DSMDEM_CTL, 0x01, 0x01}, {WCD934X_CDC_RX2_RX_PATH_DSMDEM_CTL, 0x01, 0x01}, @@ -6343,6 +7319,15 @@ static const struct tavil_reg_mask_val tavil_codec_reg_defaults[] = { {WCD934X_CDC_BOOST1_BOOST_CFG1, 0x3F, 0x12}, {WCD934X_CDC_BOOST1_BOOST_CFG2, 0x1C, 0x08}, {WCD934X_CPE_SS_CPARMAD_BUFRDY_INT_PERIOD, 0x1F, 0x09}, + {WCD934X_CDC_TX0_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX1_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX2_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX3_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX4_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX5_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX6_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX7_TX_PATH_CFG1, 0x01, 0x00}, + {WCD934X_CDC_TX8_TX_PATH_CFG1, 0x01, 0x00}, }; static const struct tavil_reg_mask_val tavil_codec_reg_init_common_val[] = { @@ -6608,6 +7593,7 @@ static int tavil_handle_pdata(struct tavil_priv *tavil, { struct snd_soc_codec *codec = tavil->codec; u8 mad_dmic_ctl_val; + u8 anc_ctl_value; u32 def_dmic_rate, dmic_clk_drv; int vout_ctl_1, vout_ctl_2, vout_ctl_3, vout_ctl_4; int rc = 0; @@ -6713,6 +7699,20 @@ static int tavil_handle_pdata(struct tavil_priv *tavil, snd_soc_update_bits(codec, WCD934X_CPE_SS_DMIC2_CTL, 0x0E, mad_dmic_ctl_val << 1); + if (dmic_clk_drv == WCD934X_DMIC_CLK_DIV_2) + anc_ctl_value = WCD934X_ANC_DMIC_X2_FULL_RATE; + else + anc_ctl_value = WCD934X_ANC_DMIC_X2_HALF_RATE; + + snd_soc_update_bits(codec, WCD934X_CDC_ANC0_MODE_2_CTL, + 0x40, anc_ctl_value << 6); + snd_soc_update_bits(codec, WCD934X_CDC_ANC0_MODE_2_CTL, + 0x20, anc_ctl_value << 5); + snd_soc_update_bits(codec, WCD934X_CDC_ANC1_MODE_2_CTL, + 0x40, anc_ctl_value << 6); + snd_soc_update_bits(codec, WCD934X_CDC_ANC1_MODE_2_CTL, + 0x20, anc_ctl_value << 5); + done: return rc; } @@ -6821,6 +7821,7 @@ static int tavil_soc_codec_probe(struct snd_soc_codec *codec) dev_info(codec->dev, "%s()\n", __func__); tavil = snd_soc_codec_get_drvdata(codec); + tavil->intf_type = wcd9xxx_get_intf_type(); /* Resource Manager post Init */ ret = wcd_resmgr_post_init(tavil->resmgr, NULL, codec); @@ -6831,8 +7832,8 @@ static int tavil_soc_codec_probe(struct snd_soc_codec *codec) } /* Class-H Init */ wcd_clsh_init(&tavil->clsh_d); - /* Default HPH Mode to Class-H HiFi */ - tavil->hph_mode = CLS_H_HIFI; + /* Default HPH Mode to Class-H Low HiFi */ + tavil->hph_mode = CLS_H_LOHIFI; tavil->fw_data = devm_kzalloc(codec->dev, sizeof(*(tavil->fw_data)), GFP_KERNEL); @@ -6926,6 +7927,12 @@ static int tavil_soc_codec_probe(struct snd_soc_codec *codec) if (IS_ERR_OR_NULL(tavil->dsd_config)) dev_dbg(tavil->dev, "%s: DSD init failed\n", __func__); + mutex_lock(&tavil->codec_mutex); + snd_soc_dapm_disable_pin(dapm, "ANC EAR PA"); + snd_soc_dapm_disable_pin(dapm, "ANC EAR"); + snd_soc_dapm_enable_pin(dapm, "ANC SPK1 PA"); + mutex_unlock(&tavil->codec_mutex); + snd_soc_dapm_sync(dapm); tavil_wdsp_initialize(codec); @@ -7433,6 +8440,45 @@ static int __tavil_enable_efuse_sensing(struct tavil_priv *tavil) return rc; } +static void ___tavil_get_codec_fine_version(struct tavil_priv *tavil) +{ + int val1, val2, version; + struct regmap *regmap; + u16 id_minor; + u32 version_mask = 0; + + regmap = tavil->wcd9xxx->regmap; + version = tavil->wcd9xxx->version; + id_minor = tavil->wcd9xxx->codec_type->id_minor; + + regmap_read(regmap, WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT14, &val1); + regmap_read(regmap, WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT15, &val2); + + dev_dbg(tavil->dev, "%s: chip version :0x%x 0x:%x\n", + __func__, val1, val2); + + version_mask |= (!!((u8)val1 & 0x80)) << DSD_DISABLED_MASK; + version_mask |= (!!((u8)val2 & 0x01)) << SLNQ_DISABLED_MASK; + + switch (version_mask) { + case DSD_DISABLED | SLNQ_DISABLED: + if (id_minor == cpu_to_le16(0)) + version = TAVIL_VERSION_WCD9340_1_0; + else if (id_minor == cpu_to_le16(0x01)) + version = TAVIL_VERSION_WCD9340_1_1; + break; + case SLNQ_DISABLED: + if (id_minor == cpu_to_le16(0)) + version = TAVIL_VERSION_WCD9341_1_0; + else if (id_minor == cpu_to_le16(0x01)) + version = TAVIL_VERSION_WCD9341_1_1; + break; + } + + tavil->wcd9xxx->version = version; + tavil->wcd9xxx->codec_type->version = version; +} + /* * tavil_get_wcd_dsp_cntl: Get the reference to wcd_dsp_cntl * @dev: Device pointer for codec device @@ -7462,7 +8508,6 @@ static int tavil_probe(struct platform_device *pdev) struct tavil_priv *tavil; struct clk *wcd_ext_clk; struct wcd9xxx_resmgr_v2 *resmgr; - int val1, val2, val3, val4; tavil = devm_kzalloc(&pdev->dev, sizeof(struct tavil_priv), GFP_KERNEL); @@ -7507,6 +8552,7 @@ static int tavil_probe(struct platform_device *pdev) tavil->swr.plat_data.bulk_write = tavil_swrm_bulk_write; tavil->swr.plat_data.clk = tavil_swrm_clock; tavil->swr.plat_data.handle_irq = tavil_swrm_handle_irq; + tavil->swr.spkr_gain_offset = WCD934X_RX_GAIN_OFFSET_0_DB; /* Register for Clock */ wcd_ext_clk = clk_get(tavil->wcd9xxx->dev, "wcd_clk"); @@ -7530,17 +8576,7 @@ static int tavil_probe(struct platform_device *pdev) 0x03, 0x01); tavil_update_reg_defaults(tavil); __tavil_enable_efuse_sensing(tavil); - - regmap_read(tavil->wcd9xxx->regmap, - WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT0, &val1); - regmap_read(tavil->wcd9xxx->regmap, - WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT14, &val2); - regmap_read(tavil->wcd9xxx->regmap, - WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT15, &val3); - regmap_read(tavil->wcd9xxx->regmap, - WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT9, &val4); - dev_dbg(&pdev->dev, "%s: chip version :0x%x 0x:%x 0x:%x 0x:%x\n", - __func__, val1, val2, val3, val4); + ___tavil_get_codec_fine_version(tavil); /* Register with soc framework */ ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tavil, diff --git a/sound/soc/codecs/wcd934x/wcd934x.h b/sound/soc/codecs/wcd934x/wcd934x.h index 9cffa168c298..1f5fda88c4f9 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.h +++ b/sound/soc/codecs/wcd934x/wcd934x.h @@ -32,6 +32,9 @@ #define WCD934X_DMIC_CLK_DIV_16 0x5 #define WCD934X_DMIC_CLK_DRIVE_DEFAULT 0x02 +#define WCD934X_ANC_DMIC_X2_FULL_RATE 1 +#define WCD934X_ANC_DMIC_X2_HALF_RATE 0 + #define TAVIL_MAX_MICBIAS 4 #define TAVIL_NUM_INTERPOLATORS 9 #define MAX_ON_DEMAND_SUPPLY_NAME_LENGTH 64 @@ -39,6 +42,13 @@ /* Convert from vout ctl to micbias voltage in mV */ #define WCD_VOUT_CTL_TO_MICB(v) (1000 + v * 50) +/* Feature masks to distinguish codec version */ +#define DSD_DISABLED_MASK 0 +#define SLNQ_DISABLED_MASK 1 + +#define DSD_DISABLED (1 << DSD_DISABLED_MASK) +#define SLNQ_DISABLED (1 << SLNQ_DISABLED_MASK) + /* Number of input and output Slimbus port */ enum { WCD934X_RX0 = 0, @@ -180,4 +190,6 @@ extern struct wcd934x_mbhc *tavil_soc_get_mbhc(struct snd_soc_codec *codec); extern int tavil_codec_enable_interp_clk(struct snd_soc_codec *codec, int event, int intp_idx); extern struct tavil_dsd_config *tavil_get_dsd_config(struct snd_soc_codec *); +extern int tavil_codec_info_create_codec_entry(struct snd_info_entry *, + struct snd_soc_codec *); #endif diff --git a/sound/soc/codecs/wcd9xxx-common-v2.c b/sound/soc/codecs/wcd9xxx-common-v2.c index f1b147aafd84..63872bbf540c 100644 --- a/sound/soc/codecs/wcd9xxx-common-v2.c +++ b/sound/soc/codecs/wcd9xxx-common-v2.c @@ -237,10 +237,16 @@ static const char *mode_to_str(int mode) return "CLS_H_NORMAL"; case CLS_H_HIFI: return "CLS_H_HIFI"; + case CLS_H_LOHIFI: + return "CLS_H_LOHIFI"; case CLS_H_LP: return "CLS_H_LP"; + case CLS_H_ULP: + return "CLS_H_ULP"; case CLS_AB: return "CLS_AB"; + case CLS_AB_HIFI: + return "CLS_AB_HIFI"; default: return "CLS_H_INVALID"; }; @@ -332,7 +338,8 @@ static inline void wcd_clsh_set_int_mode(struct wcd_clsh_cdc_data *clsh_d, static inline void wcd_clsh_set_buck_mode(struct snd_soc_codec *codec, int mode) { - if (mode == CLS_H_HIFI) + if (mode == CLS_H_HIFI || mode == CLS_H_LOHIFI || + mode == CLS_AB_HIFI || mode == CLS_AB) snd_soc_update_bits(codec, WCD9XXX_A_ANA_RX_SUPPLIES, 0x08, 0x08); /* set to HIFI */ else @@ -343,7 +350,8 @@ static inline void wcd_clsh_set_buck_mode(struct snd_soc_codec *codec, static inline void wcd_clsh_set_flyback_mode(struct snd_soc_codec *codec, int mode) { - if (mode == CLS_H_HIFI) + if (mode == CLS_H_HIFI || mode == CLS_H_LOHIFI || + mode == CLS_AB_HIFI || mode == CLS_AB) snd_soc_update_bits(codec, WCD9XXX_A_ANA_RX_SUPPLIES, 0x04, 0x04); /* set to HIFI */ else @@ -351,6 +359,55 @@ static inline void wcd_clsh_set_flyback_mode(struct snd_soc_codec *codec, 0x04, 0x00); /* set to Default */ } +static inline void wcd_clsh_gm3_boost_disable(struct snd_soc_codec *codec, + int mode) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + + if (!IS_CODEC_TYPE(wcd9xxx, WCD934X)) + return; + + if (mode == CLS_H_HIFI || mode == CLS_H_LOHIFI || + mode == CLS_AB_HIFI || mode == CLS_AB) { + snd_soc_update_bits(codec, WCD9XXX_HPH_CNP_WG_CTL, + 0x80, 0x0); /* disable GM3 Boost */ + snd_soc_update_bits(codec, WCD9XXX_FLYBACK_VNEG_CTRL_4, + 0xF0, 0x80); + } else { + snd_soc_update_bits(codec, WCD9XXX_HPH_CNP_WG_CTL, + 0x80, 0x80); /* set to Default */ + snd_soc_update_bits(codec, WCD9XXX_FLYBACK_VNEG_CTRL_4, + 0xF0, 0x70); + } +} + + +static inline void wcd_clsh_force_iq_ctl(struct snd_soc_codec *codec, + int mode) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + + if (!IS_CODEC_TYPE(wcd9xxx, WCD934X)) + return; + + if (mode == CLS_H_LOHIFI || mode == CLS_AB) { + snd_soc_update_bits(codec, WCD9XXX_HPH_NEW_INT_PA_MISC2, + 0x20, 0x20); + snd_soc_update_bits(codec, WCD9XXX_RX_BIAS_HPH_LOWPOWER, + 0xF0, 0xC0); + snd_soc_update_bits(codec, WCD9XXX_HPH_PA_CTL1, + 0x0E, 0x02); + } else { + + snd_soc_update_bits(codec, WCD9XXX_HPH_NEW_INT_PA_MISC2, + 0x20, 0x0); + snd_soc_update_bits(codec, WCD9XXX_RX_BIAS_HPH_LOWPOWER, + 0xF0, 0x80); + snd_soc_update_bits(codec, WCD9XXX_HPH_PA_CTL1, + 0x0E, 0x06); + } +} + static void wcd_clsh_buck_ctrl(struct snd_soc_codec *codec, struct wcd_clsh_cdc_data *clsh_d, int mode, @@ -386,7 +443,7 @@ static void wcd_clsh_flyback_ctrl(struct snd_soc_codec *codec, (1 << 6), (enable << 6)); /* 100usec delay is needed as per HW requirement */ usleep_range(100, 110); - if (enable && (TASHA_IS_1_1(wcd9xxx->version))) { + if (enable && (TASHA_IS_1_1(wcd9xxx))) { wcd_clsh_set_flyback_mode(codec, CLS_H_HIFI); snd_soc_update_bits(codec, WCD9XXX_FLYBACK_EN, 0x60, 0x40); @@ -427,7 +484,7 @@ static void wcd_clsh_set_gain_path(struct snd_soc_codec *codec, u8 val = 0; struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); - if (!TASHA_IS_2_0(wcd9xxx->version)) + if (!TASHA_IS_2_0(wcd9xxx)) return; switch (mode) { @@ -470,11 +527,26 @@ static void wcd_clsh_set_hph_mode(struct snd_soc_codec *codec, gain = DAC_GAIN_0DB; ipeak = DELTA_I_50MA; break; + case CLS_AB_HIFI: + val = 0x08; + break; case CLS_H_HIFI: val = 0x08; gain = DAC_GAIN_M0P2DB; ipeak = DELTA_I_50MA; break; + case CLS_H_LOHIFI: + val = 0x00; + if ((IS_CODEC_TYPE(wcd9xxx, WCD9335)) || + (IS_CODEC_TYPE(wcd9xxx, WCD9326))) { + val = 0x08; + gain = DAC_GAIN_M0P2DB; + ipeak = DELTA_I_50MA; + } + break; + case CLS_H_ULP: + val = 0x0C; + break; case CLS_H_LP: val = 0x04; ipeak = DELTA_I_30MA; @@ -483,8 +555,16 @@ static void wcd_clsh_set_hph_mode(struct snd_soc_codec *codec, return; }; + /* + * For tavil set mode to Lower_power for + * CLS_H_LOHIFI and CLS_AB + */ + if ((IS_CODEC_TYPE(wcd9xxx, WCD934X)) && + (mode == CLS_H_LOHIFI || mode == CLS_AB)) + val = 0x04; + snd_soc_update_bits(codec, WCD9XXX_A_ANA_HPH, 0x0C, val); - if (TASHA_IS_2_0(wcd9xxx->version)) { + if (TASHA_IS_2_0(wcd9xxx)) { snd_soc_update_bits(codec, WCD9XXX_CLASSH_CTRL_VCL_2, 0x30, (res_val << 4)); if (mode != CLS_H_LP) @@ -515,7 +595,7 @@ static void wcd_clsh_set_flyback_current(struct snd_soc_codec *codec, int mode) { struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); - if (!TASHA_IS_2_0(wcd9xxx->version)) + if (!TASHA_IS_2_0(wcd9xxx)) return; snd_soc_update_bits(codec, WCD9XXX_RX_BIAS_FLYB_BUFF, 0x0F, 0x0A); @@ -538,7 +618,7 @@ static void wcd_clsh_state_lo(struct snd_soc_codec *codec, dev_dbg(codec->dev, "%s: mode: %s, %s\n", __func__, mode_to_str(mode), is_enable ? "enable" : "disable"); - if (mode != CLS_AB) { + if (mode != CLS_AB && mode != CLS_AB_HIFI) { dev_err(codec->dev, "%s: LO cannot be in this mode: %d\n", __func__, mode); return; @@ -586,7 +666,8 @@ static void wcd_clsh_state_hph_ear(struct snd_soc_codec *codec, WCD_CLSH_STATE_HPHR); else return; - if (hph_mode != CLS_AB && !is_native_44_1_active(codec)) + if (hph_mode != CLS_AB && hph_mode != CLS_AB_HIFI + && !is_native_44_1_active(codec)) snd_soc_update_bits(codec, WCD9XXX_A_CDC_RX0_RX_PATH_CFG0, 0x40, 0x40); @@ -795,6 +876,7 @@ static void wcd_clsh_state_hph_lo(struct snd_soc_codec *codec, hph_mode); if ((hph_mode == CLS_AB) || + (hph_mode == CLS_AB_HIFI) || (hph_mode == CLS_NONE)) goto end; @@ -843,7 +925,7 @@ static void wcd_clsh_state_hph_st(struct snd_soc_codec *codec, dev_dbg(codec->dev, "%s: mode: %s, %s\n", __func__, mode_to_str(mode), is_enable ? "enable" : "disable"); - if (mode == CLS_AB) + if (mode == CLS_AB || mode == CLS_AB_HIFI) return; if (is_enable) { @@ -881,7 +963,7 @@ static void wcd_clsh_state_hph_r(struct snd_soc_codec *codec, } if (is_enable) { - if (mode != CLS_AB) { + if (mode != CLS_AB && mode != CLS_AB_HIFI) { wcd_enable_clsh_block(codec, clsh_d, true); /* * These K1 values depend on the Headphone Impedance @@ -897,6 +979,8 @@ static void wcd_clsh_state_hph_r(struct snd_soc_codec *codec, } wcd_clsh_set_buck_regulator_mode(codec, mode); wcd_clsh_set_flyback_mode(codec, mode); + wcd_clsh_gm3_boost_disable(codec, mode); + wcd_clsh_force_iq_ctl(codec, mode); wcd_clsh_flyback_ctrl(codec, clsh_d, mode, true); wcd_clsh_set_flyback_current(codec, mode); wcd_clsh_set_buck_mode(codec, mode); @@ -906,7 +990,7 @@ static void wcd_clsh_state_hph_r(struct snd_soc_codec *codec, } else { wcd_clsh_set_hph_mode(codec, CLS_H_NORMAL); - if (mode != CLS_AB) { + if (mode != CLS_AB && mode != CLS_AB_HIFI) { snd_soc_update_bits(codec, WCD9XXX_A_CDC_RX2_RX_PATH_CFG0, 0x40, 0x00); @@ -915,6 +999,8 @@ static void wcd_clsh_state_hph_r(struct snd_soc_codec *codec, /* buck and flyback set to default mode and disable */ wcd_clsh_buck_ctrl(codec, clsh_d, CLS_H_NORMAL, false); wcd_clsh_flyback_ctrl(codec, clsh_d, CLS_H_NORMAL, false); + wcd_clsh_force_iq_ctl(codec, CLS_H_NORMAL); + wcd_clsh_gm3_boost_disable(codec, CLS_H_NORMAL); wcd_clsh_set_flyback_mode(codec, CLS_H_NORMAL); wcd_clsh_set_buck_mode(codec, CLS_H_NORMAL); wcd_clsh_set_buck_regulator_mode(codec, CLS_H_NORMAL); @@ -935,7 +1021,7 @@ static void wcd_clsh_state_hph_l(struct snd_soc_codec *codec, } if (is_enable) { - if (mode != CLS_AB) { + if (mode != CLS_AB && mode != CLS_AB_HIFI) { wcd_enable_clsh_block(codec, clsh_d, true); /* * These K1 values depend on the Headphone Impedance @@ -951,6 +1037,8 @@ static void wcd_clsh_state_hph_l(struct snd_soc_codec *codec, } wcd_clsh_set_buck_regulator_mode(codec, mode); wcd_clsh_set_flyback_mode(codec, mode); + wcd_clsh_gm3_boost_disable(codec, mode); + wcd_clsh_force_iq_ctl(codec, mode); wcd_clsh_flyback_ctrl(codec, clsh_d, mode, true); wcd_clsh_set_flyback_current(codec, mode); wcd_clsh_set_buck_mode(codec, mode); @@ -960,7 +1048,7 @@ static void wcd_clsh_state_hph_l(struct snd_soc_codec *codec, } else { wcd_clsh_set_hph_mode(codec, CLS_H_NORMAL); - if (mode != CLS_AB) { + if (mode != CLS_AB && mode != CLS_AB_HIFI) { snd_soc_update_bits(codec, WCD9XXX_A_CDC_RX1_RX_PATH_CFG0, 0x40, 0x00); @@ -969,6 +1057,8 @@ static void wcd_clsh_state_hph_l(struct snd_soc_codec *codec, /* set buck and flyback to Default Mode */ wcd_clsh_buck_ctrl(codec, clsh_d, CLS_H_NORMAL, false); wcd_clsh_flyback_ctrl(codec, clsh_d, CLS_H_NORMAL, false); + wcd_clsh_force_iq_ctl(codec, CLS_H_NORMAL); + wcd_clsh_gm3_boost_disable(codec, CLS_H_NORMAL); wcd_clsh_set_flyback_mode(codec, CLS_H_NORMAL); wcd_clsh_set_buck_mode(codec, CLS_H_NORMAL); wcd_clsh_set_buck_regulator_mode(codec, CLS_H_NORMAL); diff --git a/sound/soc/codecs/wcd9xxx-common-v2.h b/sound/soc/codecs/wcd9xxx-common-v2.h index d3c52e73a7da..ee7e587b3f24 100644 --- a/sound/soc/codecs/wcd9xxx-common-v2.h +++ b/sound/soc/codecs/wcd9xxx-common-v2.h @@ -61,8 +61,10 @@ enum { CLS_H_NORMAL = 0, /* Class-H Default */ CLS_H_HIFI, /* Class-H HiFi */ CLS_H_LP, /* Class-H Low Power */ - CLS_AB, /* Class-AB */ + CLS_AB, /* Class-AB Low HIFI*/ CLS_H_LOHIFI, /* LoHIFI */ + CLS_H_ULP, /* Ultra Low power */ + CLS_AB_HIFI, /* Class-AB */ CLS_NONE, /* None of the above modes */ }; diff --git a/sound/soc/codecs/wcd_cpe_core.c b/sound/soc/codecs/wcd_cpe_core.c index 0b4bd3c15127..e9f167fa643b 100644 --- a/sound/soc/codecs/wcd_cpe_core.c +++ b/sound/soc/codecs/wcd_cpe_core.c @@ -2051,6 +2051,7 @@ struct wcd_cpe_core *wcd_cpe_init(const char *img_fname, goto schedule_dload_work; } + arch_setup_dma_ops(core->dev, 0, 0, NULL, 0); core->cpe_dump_v_addr = dma_alloc_coherent(core->dev, core->hw_info.dram_size, &core->cpe_dump_addr, diff --git a/sound/soc/msm/msmcobalt.c b/sound/soc/msm/msmcobalt.c index 523cd1ce4140..63367ce07f5a 100644 --- a/sound/soc/msm/msmcobalt.c +++ b/sound/soc/msm/msmcobalt.c @@ -191,7 +191,9 @@ static char const *slim_sample_rate_text[] = {"KHZ_8", "KHZ_16", "KHZ_88P2", "KHZ_96", "KHZ_176P4", "KHZ_192", "KHZ_352P8", "KHZ_384"}; static char const *bt_sample_rate_text[] = {"KHZ_8", "KHZ_16", "KHZ_48"}; -static const char *const usb_ch_text[] = {"One", "Two"}; +static const char *const usb_ch_text[] = {"One", "Two", "Three", "Four", + "Five", "Six", "Seven", + "Eight"}; static char const *ch_text[] = {"Two", "Three", "Four", "Five", "Six", "Seven", "Eight"}; static char const *usb_sample_rate_text[] = {"KHZ_8", "KHZ_11P025", @@ -1866,6 +1868,17 @@ static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd) tavil_set_spkr_gain_offset(rtd->codec, RX_GAIN_OFFSET_M1P5_DB); } + card = rtd->card->snd_card; + entry = snd_register_module_info(card->module, "codecs", + card->proc_root); + if (!entry) { + pr_debug("%s: Cannot create codecs module entry\n", + __func__); + pdata->codec_root = NULL; + goto done; + } + pdata->codec_root = entry; + tavil_codec_info_create_codec_entry(pdata->codec_root, codec); } else { if (rtd_aux && rtd_aux->component) if (!strcmp(rtd_aux->component->name, WSA8810_NAME_1) || @@ -1886,6 +1899,7 @@ static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd) pdata->codec_root = entry; tasha_codec_info_create_codec_entry(pdata->codec_root, codec); } +done: codec_reg_done = true; return 0; diff --git a/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c index a898532643ae..5fa3b6fb0885 100644 --- a/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c @@ -2502,7 +2502,7 @@ static struct snd_soc_dai_driver msm_dai_q6_usb_rx_dai = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rate_max = 192000, .rate_min = 8000, }, @@ -2523,7 +2523,7 @@ static struct snd_soc_dai_driver msm_dai_q6_usb_tx_dai = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rate_max = 192000, .rate_min = 8000, }, diff --git a/sound/usb/card.c b/sound/usb/card.c index 524688e4c144..1f6c247f773a 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -673,6 +673,9 @@ static void usb_audio_disconnect(struct usb_interface *intf) card = chip->card; + if (chip->disconnect_cb) + chip->disconnect_cb(chip); + mutex_lock(®ister_mutex); if (atomic_inc_return(&chip->shutdown) == 1) { struct snd_usb_stream *as; @@ -707,8 +710,6 @@ static void usb_audio_disconnect(struct usb_interface *intf) if (chip->num_interfaces <= 0) { usb_chip[chip->index] = NULL; mutex_unlock(®ister_mutex); - if (chip->disconnect_cb) - chip->disconnect_cb(chip); snd_card_free_when_closed(card); } else { mutex_unlock(®ister_mutex); diff --git a/sound/usb/usb_audio_qmi_svc.c b/sound/usb/usb_audio_qmi_svc.c index fa0206cc14c6..8337d11bad12 100644 --- a/sound/usb/usb_audio_qmi_svc.c +++ b/sound/usb/usb_audio_qmi_svc.c @@ -134,6 +134,12 @@ static struct msg_desc uaudio_stream_resp_desc = { .ei_array = qmi_uaudio_stream_resp_msg_v01_ei, }; +static struct msg_desc uaudio_stream_ind_desc = { + .max_msg_len = QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, + .msg_id = QMI_UADUIO_STREAM_IND_V01, + .ei_array = qmi_uaudio_stream_ind_msg_v01_ei, +}; + enum mem_type { MEM_EVENT_RING, MEM_DCBA, @@ -643,29 +649,47 @@ void uaudio_disconnect_cb(struct snd_usb_audio *chip) int ret, if_idx; struct uaudio_dev *dev; int card_num = chip->card_num; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; pr_debug("%s: for card# %d\n", __func__, card_num); - mutex_lock(&chip->dev_lock); if (card_num >= SNDRV_CARDS) { pr_err("%s: invalid card number\n", __func__); - goto done; + return; } + mutex_lock(&chip->dev_lock); dev = &uadev[card_num]; + + /* clean up */ + if (!dev->udev) { + pr_debug("%s: no clean up required\n", __func__); + goto done; + } + if (atomic_read(&dev->in_use)) { + mutex_unlock(&chip->dev_lock); + + pr_debug("%s: sending qmi indication disconnect\n", __func__); + disconnect_ind.dev_event = USB_AUDIO_DEV_DISCONNECT_V01; + disconnect_ind.slot_id = dev->udev->slot_id; + ret = qmi_send_ind(svc->uaudio_svc_hdl, svc->curr_conn, + &uaudio_stream_ind_desc, &disconnect_ind, + sizeof(disconnect_ind)); + if (ret < 0) { + pr_err("%s: qmi send failed wiht err: %d\n", + __func__, ret); + return; + } + ret = wait_event_interruptible(dev->disconnect_wq, !atomic_read(&dev->in_use)); if (ret < 0) { pr_debug("%s: failed with ret %d\n", __func__, ret); - goto done; + return; } - } - - /* clean up */ - if (!dev->udev) { - pr_debug("%s: no clean up required\n", __func__); - goto done; + mutex_lock(&chip->dev_lock); } /* free xfer buffer and unmap xfer ring and buf per interface */ |
