diff --git a/.mailmap b/.mailmap index 7d6fb567ba5e..4cdb7986fa43 100644 --- a/.mailmap +++ b/.mailmap @@ -208,6 +208,7 @@ Colin Ian King Corey Minyard Damian Hobson-Garcia Dan Carpenter +Dan Williams Daniel Borkmann Daniel Borkmann Daniel Borkmann @@ -427,6 +428,7 @@ John Stultz Jonas Gorski +Jonathan Cameron Jordan Crouse diff --git a/CREDITS b/CREDITS index dcf7b271782a..bee8609d889f 100644 --- a/CREDITS +++ b/CREDITS @@ -71,11 +71,6 @@ D: dosfs, LILO, some fd features, ATM, various other hacks here and there S: Buenos Aires S: Argentina -NTFS FILESYSTEM -N: Anton Altaparmakov -E: anton@tuxera.com -D: NTFS filesystem - N: Tim Alpaerts E: tim_alpaerts@toyota-motor-europe.com D: 802.2 class II logical link control layer, @@ -85,8 +80,8 @@ S: B-2610 Wilrijk-Antwerpen S: Belgium N: Anton Altaparmakov -E: aia21@cantab.net -W: http://www-stu.christs.cam.ac.uk/~aia21/ +E: anton@tuxera.com +W: http://www.tuxera.com/ D: Author of new NTFS driver, various other kernel hacks. S: Christ's College S: Cambridge CB2 3BU diff --git a/Documentation/ABI/testing/sysfs-bus-cxl b/Documentation/ABI/testing/sysfs-bus-cxl index c80a1b5a03db..16a9b3d2e2c0 100644 --- a/Documentation/ABI/testing/sysfs-bus-cxl +++ b/Documentation/ABI/testing/sysfs-bus-cxl @@ -508,6 +508,19 @@ Description: (RO) The size of extended linear cache, if there is an extended linear cache. Otherwise the attribute will not be visible. + +What: /sys/bus/cxl/devices/regionZ/locked +Date: Mar, 2026 +KernelVersion: v7.1 +Contact: linux-cxl@vger.kernel.org +Description: + (RO) The CXL driver has the capability to lock a region based on + a BIOS or platform dependent configuration. Regions created as + locked are never permitted to be destroyed. Resets to participating + decoders will not result in a region destroy and will not free the + decoder resources. + + What: /sys/bus/cxl/devices/regionZ/mode Date: January, 2023 KernelVersion: v6.3 diff --git a/Documentation/devicetree/bindings/i2c/i2c-iop3xx.txt b/Documentation/devicetree/bindings/i2c/i2c-iop3xx.txt deleted file mode 100644 index dcc8390e0d24..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-iop3xx.txt +++ /dev/null @@ -1,20 +0,0 @@ -i2c Controller on XScale platforms such as IOP3xx and IXP4xx - -Required properties: -- compatible : Must be one of - "intel,iop3xx-i2c" - "intel,ixp4xx-i2c"; -- reg -- #address-cells = <1>; -- #size-cells = <0>; - -Optional properties: -- Child nodes conforming to i2c bus binding - -Example: - -i2c@c8011000 { - compatible = "intel,ixp4xx-i2c"; - reg = <0xc8011000 0x18>; - interrupts = <33 IRQ_TYPE_LEVEL_LOW>; -}; diff --git a/Documentation/devicetree/bindings/i2c/intel,ixp4xx-i2c.yaml b/Documentation/devicetree/bindings/i2c/intel,ixp4xx-i2c.yaml new file mode 100644 index 000000000000..15ef510f6fd8 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/intel,ixp4xx-i2c.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/intel,ixp4xx-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: I2c Controller on XScale platforms such as IOP3xx and IXP4xx + +maintainers: + - Andi Shyti + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + enum: + - intel,iop3xx-i2c + - intel,ixp4xx-i2c + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + + i2c@c8011000 { + compatible = "intel,ixp4xx-i2c"; + reg = <0xc8011000 0x18>; + interrupts = <33 IRQ_TYPE_LEVEL_LOW>; + }; diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml index 399a09409e07..7c497a358e1d 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml @@ -27,6 +27,7 @@ properties: - items: - enum: - qcom,kaanapali-cci + - qcom,milos-cci - qcom,qcm2290-cci - qcom,qcs8300-cci - qcom,sa8775p-cci @@ -34,6 +35,7 @@ properties: - qcom,sc8280xp-cci - qcom,sdm670-cci - qcom,sdm845-cci + - qcom,sm6150-cci - qcom,sm6350-cci - qcom,sm8250-cci - qcom,sm8450-cci @@ -251,6 +253,7 @@ allOf: contains: enum: - qcom,sa8775p-cci + - qcom,sm6150-cci - qcom,sm8550-cci - qcom,sm8650-cci - qcom,x1e80100-cci @@ -265,6 +268,23 @@ allOf: - const: cpas_ahb - const: cci + - if: + properties: + compatible: + contains: + enum: + - qcom,milos-cci + then: + properties: + clocks: + minItems: 3 + maxItems: 3 + clock-names: + items: + - const: soc_ahb + - const: cpas_ahb + - const: cci + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml index f9a449fee2b0..5873cfdc5b3e 100644 --- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml @@ -15,6 +15,8 @@ description: assigned to either I2C controller. RTL9310 SoCs have equal capabilities but support 12 common SDA lines which can be assigned to either I2C controller. + RTL9607C SoCs have equal capabilities but each controller only supports 1 + SCL/SDA line. properties: compatible: @@ -34,6 +36,7 @@ properties: - enum: - realtek,rtl9301-i2c - realtek,rtl9310-i2c + - realtek,rtl9607-i2c reg: items: @@ -51,6 +54,9 @@ properties: The SCL line number of this I2C controller. enum: [ 0, 1 ] + clocks: + maxItems: 1 + patternProperties: '^i2c@[0-9ab]$': $ref: /schemas/i2c/i2c-controller.yaml @@ -81,6 +87,15 @@ allOf: then: patternProperties: '^i2c@[89ab]$': false + - if: + properties: + compatible: + contains: + const: realtek,rtl9607-i2c + then: + required: + - realtek,scl + - clocks required: - compatible diff --git a/Documentation/devicetree/bindings/i2c/renesas,riic.yaml b/Documentation/devicetree/bindings/i2c/renesas,riic.yaml index 6876eade431b..ae1f71eadc66 100644 --- a/Documentation/devicetree/bindings/i2c/renesas,riic.yaml +++ b/Documentation/devicetree/bindings/i2c/renesas,riic.yaml @@ -25,6 +25,7 @@ properties: - items: - enum: - renesas,riic-r9a08g045 # RZ/G3S + - renesas,riic-r9a08g046 # RZ/G3L - renesas,riic-r9a09g047 # RZ/G3E - renesas,riic-r9a09g056 # RZ/V2N - const: renesas,riic-r9a09g057 # RZ/V2H(P) diff --git a/Documentation/devicetree/bindings/i2c/snps,designware-i2c.yaml b/Documentation/devicetree/bindings/i2c/snps,designware-i2c.yaml index 082fdc2e69ea..467bdcbb8538 100644 --- a/Documentation/devicetree/bindings/i2c/snps,designware-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/snps,designware-i2c.yaml @@ -32,8 +32,6 @@ properties: - const: renesas,r9a06g032-i2c # RZ/N1D - const: renesas,rzn1-i2c # RZ/N1 - const: snps,designware-i2c - - description: Baikal-T1 SoC System I2C controller - const: baikal,bt1-sys-i2c - description: Mobileye EyeQ DesignWare I2C controller items: - enum: diff --git a/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml b/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml index 5896fb120501..8c04c675b25e 100644 --- a/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml @@ -14,7 +14,11 @@ allOf: properties: compatible: - const: spacemit,k1-i2c + oneOf: + - items: + - const: spacemit,k3-i2c + - const: spacemit,k1-i2c + - const: spacemit,k1-i2c reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml b/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml index 0badb2e978c7..adb684e3207c 100644 --- a/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml +++ b/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml @@ -101,7 +101,7 @@ required: unevaluatedProperties: false allOf: - - $ref: nand-controller.yaml + - $ref: nand-controller-legacy.yaml - if: properties: diff --git a/Documentation/devicetree/bindings/mtd/mxc-nand.yaml b/Documentation/devicetree/bindings/mtd/mxc-nand.yaml index bd8f7b683953..fbaff7d3eda8 100644 --- a/Documentation/devicetree/bindings/mtd/mxc-nand.yaml +++ b/Documentation/devicetree/bindings/mtd/mxc-nand.yaml @@ -10,22 +10,43 @@ maintainers: - Uwe Kleine-König allOf: - - $ref: nand-controller.yaml + - $ref: nand-controller-legacy.yaml properties: compatible: oneOf: - - const: fsl,imx27-nand + - enum: + - fsl,imx25-nand + - fsl,imx27-nand + - fsl,imx51-nand + - fsl,imx53-nand + - items: + - enum: + - fsl,imx35-nand + - const: fsl,imx25-nand - items: - enum: - fsl,imx31-nand - const: fsl,imx27-nand reg: - maxItems: 1 + minItems: 1 + items: + - description: IP register space + - description: Nand flash internal buffer space interrupts: maxItems: 1 + clocks: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + items: + - const: rx-tx + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/mtd/nand-chip.yaml b/Documentation/devicetree/bindings/mtd/nand-chip.yaml index 609d4a4ddd80..8800d1d07266 100644 --- a/Documentation/devicetree/bindings/mtd/nand-chip.yaml +++ b/Documentation/devicetree/bindings/mtd/nand-chip.yaml @@ -11,6 +11,7 @@ maintainers: allOf: - $ref: mtd.yaml# + - $ref: nand-property.yaml description: | This file covers the generic description of a NAND chip. It implies that the @@ -22,51 +23,6 @@ properties: description: Contains the chip-select IDs. - nand-ecc-engine: - description: | - A phandle on the hardware ECC engine if any. There are - basically three possibilities: - 1/ The ECC engine is part of the NAND controller, in this - case the phandle should reference the parent node. - 2/ The ECC engine is part of the NAND part (on-die), in this - case the phandle should reference the node itself. - 3/ The ECC engine is external, in this case the phandle should - reference the specific ECC engine node. - $ref: /schemas/types.yaml#/definitions/phandle - - nand-use-soft-ecc-engine: - description: Use a software ECC engine. - type: boolean - - nand-no-ecc-engine: - description: Do not use any ECC correction. - type: boolean - - nand-ecc-algo: - description: - Desired ECC algorithm. - $ref: /schemas/types.yaml#/definitions/string - enum: [hamming, bch, rs] - - nand-ecc-strength: - description: - Maximum number of bits that can be corrected per ECC step. - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 1 - - nand-ecc-step-size: - description: - Number of data bytes covered by a single ECC step. - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 1 - - secure-regions: - description: - Regions in the NAND chip which are protected using a secure element - like Trustzone. This property contains the start address and size of - the secure regions present. - $ref: /schemas/types.yaml#/definitions/uint64-matrix - required: - reg diff --git a/Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml b/Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml new file mode 100644 index 000000000000..d6e612413df1 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/nand-controller-legacy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NAND Controller Common Properties + +maintainers: + - Miquel Raynal + - Richard Weinberger + +description: > + The NAND controller should be represented with its own DT node, and + all NAND chips attached to this controller should be defined as + children nodes of the NAND controller. This representation should be + enforced even for simple controllers supporting only one chip. + + This is only for legacy nand controller, new controller should use + nand-controller.yaml + +properties: + + "#address-cells": + const: 1 + + "#size-cells": + enum: [0, 1] + + ranges: true + + cs-gpios: + description: + Array of chip-select available to the controller. The first + entries are a 1:1 mapping of the available chip-select on the + NAND controller (even if they are not used). As many additional + chip-select as needed may follow and should be phandles of GPIO + lines. 'reg' entries of the NAND chip subnodes become indexes of + this array when this property is present. + minItems: 1 + maxItems: 8 + + partitions: + type: object + + required: + - compatible + +patternProperties: + "^nand@[a-f0-9]$": + type: object + $ref: raw-nand-chip.yaml# + + "^partition@[0-9a-f]+$": + type: object + $ref: /schemas/mtd/partitions/partition.yaml#/$defs/partition-node + deprecated: true + +allOf: + - $ref: raw-nand-property.yaml# + - $ref: nand-property.yaml# + +# This is a generic file other binding inherit from and extend +additionalProperties: true + diff --git a/Documentation/devicetree/bindings/mtd/nand-controller.yaml b/Documentation/devicetree/bindings/mtd/nand-controller.yaml index 28167c0cf271..0aa61d5fa50b 100644 --- a/Documentation/devicetree/bindings/mtd/nand-controller.yaml +++ b/Documentation/devicetree/bindings/mtd/nand-controller.yaml @@ -16,6 +16,8 @@ description: | children nodes of the NAND controller. This representation should be enforced even for simple controllers supporting only one chip. +select: false + properties: $nodename: pattern: "^nand-controller(@.*)?" diff --git a/Documentation/devicetree/bindings/mtd/nand-property.yaml b/Documentation/devicetree/bindings/mtd/nand-property.yaml new file mode 100644 index 000000000000..55488a4b1548 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/nand-property.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/nand-property.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NAND Chip Common Properties + +maintainers: + - Miquel Raynal + +description: | + This file covers the generic properties of a NAND chip. It implies that the + bus interface should not be taken into account: both raw NAND devices and + SPI-NAND devices are concerned by this description. + +properties: + nand-ecc-engine: + description: | + A phandle on the hardware ECC engine if any. There are + basically three possibilities: + 1/ The ECC engine is part of the NAND controller, in this + case the phandle should reference the parent node. + 2/ The ECC engine is part of the NAND part (on-die), in this + case the phandle should reference the node itself. + 3/ The ECC engine is external, in this case the phandle should + reference the specific ECC engine node. + $ref: /schemas/types.yaml#/definitions/phandle + + nand-use-soft-ecc-engine: + description: Use a software ECC engine. + type: boolean + + nand-no-ecc-engine: + description: Do not use any ECC correction. + type: boolean + + nand-ecc-algo: + description: + Desired ECC algorithm. + $ref: /schemas/types.yaml#/definitions/string + enum: [hamming, bch, rs] + + nand-ecc-strength: + description: + Maximum number of bits that can be corrected per ECC step. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + + nand-ecc-step-size: + description: + Number of data bytes covered by a single ECC step. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + + secure-regions: + description: + Regions in the NAND chip which are protected using a secure element + like Trustzone. This property contains the start address and size of + the secure regions present. + $ref: /schemas/types.yaml#/definitions/uint64-matrix + +# This file can be referenced by more specific devices (like spi-nands) +additionalProperties: true diff --git a/Documentation/devicetree/bindings/mtd/partitions/partition.yaml b/Documentation/devicetree/bindings/mtd/partitions/partition.yaml index 2397d97ecac5..eaeac2f2ea94 100644 --- a/Documentation/devicetree/bindings/mtd/partitions/partition.yaml +++ b/Documentation/devicetree/bindings/mtd/partitions/partition.yaml @@ -57,6 +57,15 @@ properties: user space from type: boolean + part-concat-next: + description: List of phandles to MTD partitions that need be concatenated + with the current partition. + $ref: /schemas/types.yaml#/definitions/phandle-array + minItems: 1 + maxItems: 16 + items: + maxItems: 1 + align: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 2 @@ -180,4 +189,15 @@ examples: reg = <0x200000 0x100000>; align = <0x4000>; }; + + part0: partition@400000 { + part-concat-next = <&part1>; + label = "part0_0"; + reg = <0x400000 0x100000>; + }; + + part1: partition@800000 { + label = "part0_1"; + reg = <0x800000 0x800000>; + }; }; diff --git a/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml b/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml index 092448d7bfc5..792de3e3c6ee 100644 --- a/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml +++ b/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml @@ -11,6 +11,7 @@ maintainers: allOf: - $ref: nand-chip.yaml# + - $ref: raw-nand-property.yaml# description: | The ECC strength and ECC step size properties define the user @@ -31,79 +32,6 @@ properties: description: Contains the chip-select IDs. - nand-ecc-placement: - description: - Location of the ECC bytes. This location is unknown by default - but can be explicitly set to "oob", if all ECC bytes are - known to be stored in the OOB area, or "interleaved" if ECC - bytes will be interleaved with regular data in the main area. - $ref: /schemas/types.yaml#/definitions/string - enum: [ oob, interleaved ] - deprecated: true - - nand-ecc-mode: - description: - Legacy ECC configuration mixing the ECC engine choice and - configuration. - $ref: /schemas/types.yaml#/definitions/string - enum: [none, soft, soft_bch, hw, hw_syndrome, on-die] - deprecated: true - - nand-bus-width: - description: - Bus width to the NAND chip - $ref: /schemas/types.yaml#/definitions/uint32 - enum: [8, 16] - default: 8 - - nand-on-flash-bbt: - description: - With this property, the OS will search the device for a Bad - Block Table (BBT). If not found, it will create one, reserve - a few blocks at the end of the device to store it and update - it as the device ages. Otherwise, the out-of-band area of a - few pages of all the blocks will be scanned at boot time to - find Bad Block Markers (BBM). These markers will help to - build a volatile BBT in RAM. - $ref: /schemas/types.yaml#/definitions/flag - - nand-ecc-maximize: - description: - Whether or not the ECC strength should be maximized. The - maximum ECC strength is both controller and chip - dependent. The ECC engine has to select the ECC config - providing the best strength and taking the OOB area size - constraint into account. This is particularly useful when - only the in-band area is used by the upper layers, and you - want to make your NAND as reliable as possible. - $ref: /schemas/types.yaml#/definitions/flag - - nand-is-boot-medium: - description: - Whether or not the NAND chip is a boot medium. Drivers might - use this information to select ECC algorithms supported by - the boot ROM or similar restrictions. - $ref: /schemas/types.yaml#/definitions/flag - - nand-rb: - description: - Contains the native Ready/Busy IDs. - $ref: /schemas/types.yaml#/definitions/uint32-array - - rb-gpios: - description: - Contains one or more GPIO descriptor (the numper of descriptor - depends on the number of R/B pins exposed by the flash) for the - Ready/Busy pins. Active state refers to the NAND ready state and - should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted. - - wp-gpios: - description: - Contains one GPIO descriptor for the Write Protect pin. - Active state refers to the NAND Write Protect state and should be - set to GPIOD_ACTIVE_LOW unless the signal is inverted. - maxItems: 1 - required: - reg diff --git a/Documentation/devicetree/bindings/mtd/raw-nand-property.yaml b/Documentation/devicetree/bindings/mtd/raw-nand-property.yaml new file mode 100644 index 000000000000..f853b72426c4 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/raw-nand-property.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/raw-nand-property.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Raw NAND Chip Common Properties + +maintainers: + - Miquel Raynal + +description: | + The ECC strength and ECC step size properties define the user + desires in terms of correction capability of a controller. Together, + they request the ECC engine to correct {strength} bit errors per + {size} bytes for a particular raw NAND chip. + + The interpretation of these parameters is implementation-defined, so + not all implementations must support all possible + combinations. However, implementations are encouraged to further + specify the value(s) they support. + +properties: + nand-ecc-placement: + description: + Location of the ECC bytes. This location is unknown by default + but can be explicitly set to "oob", if all ECC bytes are + known to be stored in the OOB area, or "interleaved" if ECC + bytes will be interleaved with regular data in the main area. + $ref: /schemas/types.yaml#/definitions/string + enum: [ oob, interleaved ] + deprecated: true + + nand-ecc-mode: + description: + Legacy ECC configuration mixing the ECC engine choice and + configuration. + $ref: /schemas/types.yaml#/definitions/string + enum: [none, soft, soft_bch, hw, hw_syndrome, on-die] + deprecated: true + + nand-bus-width: + description: + Bus width to the NAND chip + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [8, 16] + default: 8 + + nand-on-flash-bbt: + description: + With this property, the OS will search the device for a Bad + Block Table (BBT). If not found, it will create one, reserve + a few blocks at the end of the device to store it and update + it as the device ages. Otherwise, the out-of-band area of a + few pages of all the blocks will be scanned at boot time to + find Bad Block Markers (BBM). These markers will help to + build a volatile BBT in RAM. + $ref: /schemas/types.yaml#/definitions/flag + + nand-ecc-maximize: + description: + Whether or not the ECC strength should be maximized. The + maximum ECC strength is both controller and chip + dependent. The ECC engine has to select the ECC config + providing the best strength and taking the OOB area size + constraint into account. This is particularly useful when + only the in-band area is used by the upper layers, and you + want to make your NAND as reliable as possible. + $ref: /schemas/types.yaml#/definitions/flag + + nand-is-boot-medium: + description: + Whether or not the NAND chip is a boot medium. Drivers might + use this information to select ECC algorithms supported by + the boot ROM or similar restrictions. + $ref: /schemas/types.yaml#/definitions/flag + + nand-rb: + description: + Contains the native Ready/Busy IDs. + $ref: /schemas/types.yaml#/definitions/uint32-array + + rb-gpios: + description: + Contains one or more GPIO descriptor (the numper of descriptor + depends on the number of R/B pins exposed by the flash) for the + Ready/Busy pins. Active state refers to the NAND ready state and + should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted. + + wp-gpios: + description: + Contains one GPIO descriptor for the Write Protect pin. + Active state refers to the NAND Write Protect state and should be + set to GPIOD_ACTIVE_LOW unless the signal is inverted. + maxItems: 1 + +# This is a generic file other binding inherit from and extend +additionalProperties: true diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst index 6cbc3e0292ae..fc7254d01a2b 100644 --- a/Documentation/filesystems/index.rst +++ b/Documentation/filesystems/index.rst @@ -99,6 +99,7 @@ Documentation for filesystem implementations. isofs nilfs2 nfs/index + ntfs ntfs3 ocfs2 ocfs2-online-filecheck diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems/ntfs.rst new file mode 100644 index 000000000000..5c96b04a4d7a --- /dev/null +++ b/Documentation/filesystems/ntfs.rst @@ -0,0 +1,159 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================================= +The Linux NTFS filesystem driver +================================= + + +.. Table of contents + + - Overview + - Utilities support + - Supported mount options + + +Overview +======== + +NTFS is a Linux kernel filesystem driver that provides full read and write +support for NTFS volumes. It is designed for high performance, modern +kernel infrastructure (iomap, folio), and stable long-term maintenance. + + +Utilities support +================= + +The NTFS utilities project, called ntfsprogs-plus, provides mkfs.ntfs, +fsck.ntfs, and other related tools (e.g., ntfsinfo, ntfsclone, etc.) for +creating, checking, and managing NTFS volumes. These utilities can be used +for filesystem testing with xfstests as well as for recovering corrupted +NTFS devices. + +The project is available at: + + https://github.com/ntfsprogs-plus/ntfsprogs-plus + + +Supported mount options +======================= + +The NTFS driver supports the following mount options: + +======================= ==================================================== +iocharset=name Character set to use for converting between + the encoding is used for user visible filename and + 16 bit Unicode characters. + +nls=name Deprecated option. Still supported but please use + iocharset=name in the future. + +uid= +gid= +umask= Provide default owner, group, and access mode mask. + These options work as documented in mount(8). By + default, the files/directories are owned by root + and he/she has read and write permissions, as well + as browse permission for directories. No one else + has any access permissions. I.e. the mode on all + files is by default rw------- and + for directories rwx------, a consequence of + the default fmask=0177 and dmask=0077. + Using a umask of zero will grant all permissions to + everyone, i.e. all files and directories will have + mode rwxrwxrwx. + +fmask= +dmask= Instead of specifying umask which applies both to + files and directories, fmask applies only to files + and dmask only to directories. + +showmeta= +show_sys_files= If show_sys_files is specified, show the system + files in directory listings. Otherwise the default + behaviour is to hide the system files. + Note that even when show_sys_files is specified, + "$MFT" will not be visible due to bugs/mis-features + in glibc. Further, note that irrespective of + show_sys_files, all files are accessible by name, + i.e. you can always do "ls -l \$UpCase" for example + to specifically show the system file containing + the Unicode upcase table. + +case_sensitive= If case_sensitive is specified, treat all filenames + as case sensitive and create file names in + the POSIX namespace (default behavior). Note, + the Linux NTFS driver will never create short + filenames and will remove them on rename/delete of + the corresponding long file name. Note that files + remain accessible via their short file name, if it + exists. + +nocase= If nocase is specified, treat filenames + case-insensitively. + +disable_sparse= If disable_sparse is specified, creation of sparse + regions, i.e. holes, inside files is disabled for + the volume (for the duration of this mount only). + By default, creation of sparse regions is enabled, + which is consistent with the behaviour of + traditional Unix filesystems. + +errors=opt Specify NTFS behavior on critical errors: panic, + remount the partition in read-only mode or + continue without doing anything (default behavior). + +mft_zone_multiplier= Set the MFT zone multiplier for the volume (this + setting is not persistent across mounts and can be + changed from mount to mount but cannot be changed + on remount). Values of 1 to 4 are allowed, 1 being + the default. The MFT zone multiplier determines + how much space is reserved for the MFT on the + volume. If all other space is used up, then the + MFT zone will be shrunk dynamically, so this has no + impact on the amount of free space. However, it + can have an impact on performance by affecting + fragmentation of the MFT. In general use the + default. If you have a lot of small files then use + a higher value. The values have the following + meaning: + + ===== ================================= + Value MFT zone size (% of volume size) + ===== ================================= + 1 12.5% + 2 25% + 3 37.5% + 4 50% + ===== ================================= + + Note this option is irrelevant for read-only mount. + +preallocated_size= Set preallocated size to optimize runlist merge + overhead with small chunck size.(64KB size by + default) + +acl= Enable POSIX ACL support. When specified, POSIX + ACLs stored in extended attributes are enforced. + Default is off. Requires kernel config + NTFS_FS_POSIX_ACL enabled. + +sys_immutable= Make NTFS system files (e.g. $MFT, $LogFile, + $Bitmap, $UpCase, etc.) immutable to user initiated + modifications for extra safety. Default is off. + +nohidden= Hide files and directories marked with the Windows + "hidden" attribute. By default hidden items are + shown. + +hide_dot_files= Hide names beginning with a dot ("."). By default + dot files are shown. When enabled, files and + directories created with a leading '.' will be + hidden from directory listings. + +windows_names= Refuse creation/rename of files with characters or + reserved device names disallowed on Windows (e.g. + CON, NUL, AUX, COM1, LPT1, etc.). Default is off. +discard= Issue block device discard for clusters freed on + file deletion/truncation to inform underlying + storage. +======================= ==================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 911fa2254442..dc372dbfe1dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4055,7 +4055,7 @@ S: Maintained F: crypto/rsa* ASYNCHRONOUS TRANSFERS/TRANSFORMS (IOAT) API -R: Dan Williams +R: Dan Williams S: Odd fixes W: http://sourceforge.net/projects/xscaleiop F: Documentation/crypto/async-tx-api.rst @@ -6422,12 +6422,12 @@ F: include/linux/compiler_attributes.h COMPUTE EXPRESS LINK (CXL) M: Davidlohr Bueso -M: Jonathan Cameron +M: Jonathan Cameron M: Dave Jiang M: Alison Schofield M: Vishal Verma M: Ira Weiny -M: Dan Williams +M: Dan Williams L: linux-cxl@vger.kernel.org S: Maintained F: Documentation/driver-api/cxl @@ -6438,7 +6438,7 @@ F: include/uapi/linux/cxl_mem.h F: tools/testing/cxl/ COMPUTE EXPRESS LINK PMU (CPMU) -M: Jonathan Cameron +M: Jonathan Cameron L: linux-cxl@vger.kernel.org S: Maintained F: Documentation/admin-guide/perf/cxl.rst @@ -7295,7 +7295,7 @@ S: Maintained F: scripts/dev-needs.sh DEVICE DIRECT ACCESS (DAX) -M: Dan Williams +M: Dan Williams M: Vishal Verma M: Dave Jiang L: nvdimm@lists.linux.dev @@ -9852,7 +9852,7 @@ F: include/linux/fcntl.h F: include/uapi/linux/fcntl.h FILESYSTEM DIRECT ACCESS (DAX) -M: Dan Williams +M: Dan Williams R: Matthew Wilcox R: Jan Kara L: linux-fsdevel@vger.kernel.org @@ -10597,7 +10597,7 @@ FWCTL SUBSYSTEM M: Dave Jiang M: Jason Gunthorpe M: Saeed Mahameed -R: Jonathan Cameron +R: Jonathan Cameron S: Maintained F: Documentation/userspace-api/fwctl/ F: drivers/fwctl/ @@ -12946,7 +12946,7 @@ F: drivers/platform/x86/intel/hid.c INTEL I/OAT DMA DRIVER M: Dave Jiang -R: Dan Williams +R: Dan Williams L: dmaengine@vger.kernel.org S: Supported Q: https://patchwork.kernel.org/project/linux-dmaengine/list/ @@ -14665,7 +14665,7 @@ K: libie LIBNVDIMM BTT: BLOCK TRANSLATION TABLE M: Vishal Verma -M: Dan Williams +M: Dan Williams M: Dave Jiang L: nvdimm@lists.linux.dev S: Supported @@ -14674,7 +14674,7 @@ P: Documentation/nvdimm/maintainer-entry-profile.rst F: drivers/nvdimm/btt* LIBNVDIMM PMEM: PERSISTENT MEMORY DRIVER -M: Dan Williams +M: Dan Williams M: Vishal Verma M: Dave Jiang L: nvdimm@lists.linux.dev @@ -14692,7 +14692,7 @@ F: Documentation/devicetree/bindings/pmem/pmem-region.yaml F: drivers/nvdimm/of_pmem.c LIBNVDIMM: NON-VOLATILE MEMORY DEVICE SUBSYSTEM -M: Dan Williams +M: Dan Williams M: Vishal Verma M: Dave Jiang M: Ira Weiny @@ -19005,6 +19005,15 @@ W: https://github.com/davejiang/linux/wiki T: git https://github.com/davejiang/linux.git F: drivers/ntb/hw/intel/ +NTFS FILESYSTEM +M: Namjae Jeon +M: Hyunchul Lee +L: linux-fsdevel@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git +F: Documentation/filesystems/ntfs.rst +F: fs/ntfs/ + NTFS3 FILESYSTEM M: Konstantin Komarov L: ntfs3@lists.linux.dev @@ -25369,7 +25378,7 @@ F: drivers/staging/ STANDALONE CACHE CONTROLLER DRIVERS M: Conor Dooley -M: Jonathan Cameron +M: Jonathan Cameron S: Maintained T: git https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git/ F: Documentation/devicetree/bindings/cache/ @@ -27096,7 +27105,7 @@ S: Maintained F: Documentation/devicetree/bindings/trigger-source/* TRUSTED EXECUTION ENVIRONMENT SECURITY MANAGER (TSM) -M: Dan Williams +M: Dan Williams L: linux-coco@lists.linux.dev S: Maintained F: Documentation/ABI/testing/configfs-tsm-report diff --git a/arch/arc/net/bpf_jit_core.c b/arch/arc/net/bpf_jit_core.c index 1421eeced0f5..639a2736f029 100644 --- a/arch/arc/net/bpf_jit_core.c +++ b/arch/arc/net/bpf_jit_core.c @@ -79,7 +79,6 @@ struct arc_jit_data { * The JIT pertinent context that is used by different functions. * * prog: The current eBPF program being handled. - * orig_prog: The original eBPF program before any possible change. * jit: The JIT buffer and its length. * bpf_header: The JITed program header. "jit.buf" points inside it. * emit: If set, opcodes are written to memory; else, a dry-run. @@ -94,12 +93,10 @@ struct arc_jit_data { * need_extra_pass: A forecast if an "extra_pass" will occur. * is_extra_pass: Indicates if the current pass is an extra pass. * user_bpf_prog: True, if VM opcodes come from a real program. - * blinded: True if "constant blinding" step returned a new "prog". * success: Indicates if the whole JIT went OK. */ struct jit_context { struct bpf_prog *prog; - struct bpf_prog *orig_prog; struct jit_buffer jit; struct bpf_binary_header *bpf_header; bool emit; @@ -114,7 +111,6 @@ struct jit_context { bool need_extra_pass; bool is_extra_pass; bool user_bpf_prog; - bool blinded; bool success; }; @@ -161,13 +157,7 @@ static int jit_ctx_init(struct jit_context *ctx, struct bpf_prog *prog) { memset(ctx, 0, sizeof(*ctx)); - ctx->orig_prog = prog; - - /* If constant blinding was requested but failed, scram. */ - ctx->prog = bpf_jit_blind_constants(prog); - if (IS_ERR(ctx->prog)) - return PTR_ERR(ctx->prog); - ctx->blinded = (ctx->prog != ctx->orig_prog); + ctx->prog = prog; /* If the verifier doesn't zero-extend, then we have to do it. */ ctx->do_zext = !ctx->prog->aux->verifier_zext; @@ -214,14 +204,6 @@ static inline void maybe_free(struct jit_context *ctx, void **mem) */ static void jit_ctx_cleanup(struct jit_context *ctx) { - if (ctx->blinded) { - /* if all went well, release the orig_prog. */ - if (ctx->success) - bpf_jit_prog_release_other(ctx->prog, ctx->orig_prog); - else - bpf_jit_prog_release_other(ctx->orig_prog, ctx->prog); - } - maybe_free(ctx, (void **)&ctx->bpf2insn); maybe_free(ctx, (void **)&ctx->jit_data); @@ -229,12 +211,19 @@ static void jit_ctx_cleanup(struct jit_context *ctx) ctx->bpf2insn_valid = false; /* Freeing "bpf_header" is enough. "jit.buf" is a sub-array of it. */ - if (!ctx->success && ctx->bpf_header) { - bpf_jit_binary_free(ctx->bpf_header); - ctx->bpf_header = NULL; - ctx->jit.buf = NULL; - ctx->jit.index = 0; - ctx->jit.len = 0; + if (!ctx->success) { + if (ctx->bpf_header) { + bpf_jit_binary_free(ctx->bpf_header); + ctx->bpf_header = NULL; + ctx->jit.buf = NULL; + ctx->jit.index = 0; + ctx->jit.len = 0; + } + if (ctx->is_extra_pass) { + ctx->prog->bpf_func = NULL; + ctx->prog->jited = 0; + ctx->prog->jited_len = 0; + } } ctx->emit = false; @@ -1411,7 +1400,7 @@ static struct bpf_prog *do_extra_pass(struct bpf_prog *prog) * (re)locations involved that their addresses are not known * during the first run. */ -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { vm_dump(prog); diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c index deeb8f292454..9ede81afbc50 100644 --- a/arch/arm/net/bpf_jit_32.c +++ b/arch/arm/net/bpf_jit_32.c @@ -1852,6 +1852,9 @@ exit: { u64 val = (u32)imm | (u64)insn[1].imm << 32; + if (insn->src_reg == BPF_PSEUDO_FUNC) + goto notyet; + emit_a32_mov_i64(dst, val, ctx); return 1; @@ -2055,6 +2058,9 @@ go_jmp: const s8 *r5 = bpf2a32[BPF_REG_5]; const u32 func = (u32)__bpf_call_base + (u32)imm; + if (insn->src_reg == BPF_PSEUDO_CALL) + goto notyet; + emit_a32_mov_r64(true, r0, r1, ctx); emit_a32_mov_r64(true, r1, r2, ctx); emit_push_r64(r5, ctx); @@ -2142,11 +2148,9 @@ bool bpf_jit_needs_zext(void) return true; } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { - struct bpf_prog *tmp, *orig_prog = prog; struct bpf_binary_header *header; - bool tmp_blinded = false; struct jit_ctx ctx; unsigned int tmp_idx; unsigned int image_size; @@ -2156,20 +2160,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * the interpreter. */ if (!prog->jit_requested) - return orig_prog; - - /* If constant blinding was enabled and we failed during blinding - * then we must fall back to the interpreter. Otherwise, we save - * the new JITed code. - */ - tmp = bpf_jit_blind_constants(prog); - - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; memset(&ctx, 0, sizeof(ctx)); ctx.prog = prog; @@ -2179,10 +2170,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * we must fall back to the interpreter */ ctx.offsets = kcalloc(prog->len, sizeof(int), GFP_KERNEL); - if (ctx.offsets == NULL) { - prog = orig_prog; - goto out; - } + if (ctx.offsets == NULL) + return prog; /* 1) fake pass to find in the length of the JITed code, * to compute ctx->offsets and other context variables @@ -2194,10 +2183,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * being successful in the second pass, so just fall back * to the interpreter. */ - if (build_body(&ctx)) { - prog = orig_prog; + if (build_body(&ctx)) goto out_off; - } tmp_idx = ctx.idx; build_prologue(&ctx); @@ -2213,10 +2200,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx.idx += ctx.imm_count; if (ctx.imm_count) { ctx.imms = kcalloc(ctx.imm_count, sizeof(u32), GFP_KERNEL); - if (ctx.imms == NULL) { - prog = orig_prog; + if (ctx.imms == NULL) goto out_off; - } } #else /* there's nothing about the epilogue on ARMv7 */ @@ -2238,10 +2223,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) /* Not able to allocate memory for the structure then * we must fall back to the interpretation */ - if (header == NULL) { - prog = orig_prog; + if (header == NULL) goto out_imms; - } /* 2.) Actual pass to generate final JIT code */ ctx.target = (u32 *) image_ptr; @@ -2278,16 +2261,12 @@ out_imms: #endif out_off: kfree(ctx.offsets); -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); + return prog; out_free: image_ptr = NULL; bpf_jit_binary_free(header); - prog = orig_prog; goto out_imms; } diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c index cc5b40917d0d..37ce75f7f1f0 100644 --- a/arch/arm64/lib/insn.c +++ b/arch/arm64/lib/insn.c @@ -338,6 +338,8 @@ u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr, long offset; offset = label_imm_common(pc, addr, SZ_1M); + if (offset >= SZ_1M) + return AARCH64_BREAK_FAULT; insn = aarch64_insn_get_bcond_value(); diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c index c932a6c175a4..9cc1c644002b 100644 --- a/arch/arm64/mm/init.c +++ b/arch/arm64/mm/init.c @@ -418,9 +418,6 @@ void free_initmem(void) WARN_ON(!IS_ALIGNED((unsigned long)lm_init_begin, PAGE_SIZE)); WARN_ON(!IS_ALIGNED((unsigned long)lm_init_end, PAGE_SIZE)); - /* Delete __init region from memblock.reserved. */ - memblock_free(lm_init_begin, lm_init_end - lm_init_begin); - free_reserved_area(lm_init_begin, lm_init_end, POISON_FREE_INITMEM, "unused kernel"); /* diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index adf84962d579..0816c40fc7af 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -35,8 +34,8 @@ #define ARENA_VM_START (MAX_BPF_JIT_REG + 5) #define check_imm(bits, imm) do { \ - if ((((imm) > 0) && ((imm) >> (bits))) || \ - (((imm) < 0) && (~(imm) >> (bits)))) { \ + if ((((imm) > 0) && ((imm) >> ((bits) - 1))) || \ + (((imm) < 0) && (~(imm) >> ((bits) - 1)))) { \ pr_info("[%2d] imm=%d(0x%x) out of range\n", \ i, imm, imm); \ return -EINVAL; \ @@ -1198,8 +1197,8 @@ static int add_exception_handler(const struct bpf_insn *insn, * >0 - successfully JITed a 16-byte eBPF instruction. * <0 - failed to JIT. */ -static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, - bool extra_pass) +static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn *insn, + struct jit_ctx *ctx, bool extra_pass) { const u8 code = insn->code; u8 dst = bpf2a64[insn->dst_reg]; @@ -1224,6 +1223,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, int ret; bool sign_extend; + if (bpf_insn_is_indirect_target(env, ctx->prog, i)) + emit_bti(A64_BTI_J, ctx); + switch (code) { /* dst = src */ case BPF_ALU | BPF_MOV | BPF_X: @@ -1899,7 +1901,7 @@ emit_cond_jmp: return 0; } -static int build_body(struct jit_ctx *ctx, bool extra_pass) +static int build_body(struct bpf_verifier_env *env, struct jit_ctx *ctx, bool extra_pass) { const struct bpf_prog *prog = ctx->prog; int i; @@ -1918,7 +1920,7 @@ static int build_body(struct jit_ctx *ctx, bool extra_pass) int ret; ctx->offset[i] = ctx->idx; - ret = build_insn(insn, ctx, extra_pass); + ret = build_insn(env, insn, ctx, extra_pass); if (ret > 0) { i++; ctx->offset[i] = ctx->idx; @@ -1961,11 +1963,6 @@ static int validate_ctx(struct jit_ctx *ctx) return 0; } -static inline void bpf_flush_icache(void *start, void *end) -{ - flush_icache_range((unsigned long)start, (unsigned long)end); -} - static void priv_stack_init_guard(void __percpu *priv_stack_ptr, int alloc_size) { int cpu, underflow_idx = (alloc_size - PRIV_STACK_GUARD_SZ) >> 3; @@ -2006,17 +2003,15 @@ struct arm64_jit_data { struct jit_ctx ctx; }; -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { int image_size, prog_size, extable_size, extable_align, extable_offset; - struct bpf_prog *tmp, *orig_prog = prog; struct bpf_binary_header *header; struct bpf_binary_header *ro_header = NULL; struct arm64_jit_data *jit_data; void __percpu *priv_stack_ptr = NULL; bool was_classic = bpf_prog_was_classic(prog); int priv_stack_alloc_sz; - bool tmp_blinded = false; bool extra_pass = false; struct jit_ctx ctx; u8 *image_ptr; @@ -2025,26 +2020,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) int exentry_idx; if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - /* If blinding was requested and we failed during blinding, - * we must fall back to the interpreter. - */ - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - prog = orig_prog; - goto out; - } + if (!jit_data) + return prog; prog->aux->jit_data = jit_data; } priv_stack_ptr = prog->aux->priv_stack_ptr; @@ -2056,10 +2038,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) priv_stack_alloc_sz = round_up(prog->aux->stack_depth, 16) + 2 * PRIV_STACK_GUARD_SZ; priv_stack_ptr = __alloc_percpu_gfp(priv_stack_alloc_sz, 16, GFP_KERNEL); - if (!priv_stack_ptr) { - prog = orig_prog; + if (!priv_stack_ptr) goto out_priv_stack; - } priv_stack_init_guard(priv_stack_ptr, priv_stack_alloc_sz); prog->aux->priv_stack_ptr = priv_stack_ptr; @@ -2079,10 +2059,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx.prog = prog; ctx.offset = kvzalloc_objs(int, prog->len + 1); - if (ctx.offset == NULL) { - prog = orig_prog; + if (ctx.offset == NULL) goto out_off; - } ctx.user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena); ctx.arena_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena); @@ -2095,15 +2073,11 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * BPF line info needs ctx->offset[i] to be the offset of * instruction[i] in jited image, so build prologue first. */ - if (build_prologue(&ctx, was_classic)) { - prog = orig_prog; + if (build_prologue(&ctx, was_classic)) goto out_off; - } - if (build_body(&ctx, extra_pass)) { - prog = orig_prog; + if (build_body(env, &ctx, extra_pass)) goto out_off; - } ctx.epilogue_offset = ctx.idx; build_epilogue(&ctx, was_classic); @@ -2121,10 +2095,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ro_header = bpf_jit_binary_pack_alloc(image_size, &ro_image_ptr, sizeof(u64), &header, &image_ptr, jit_fill_hole); - if (!ro_header) { - prog = orig_prog; + if (!ro_header) goto out_off; - } /* Pass 2: Determine jited position and result for each instruction */ @@ -2152,10 +2124,8 @@ skip_init_ctx: /* Dont write body instructions to memory for now */ ctx.write = false; - if (build_body(&ctx, extra_pass)) { - prog = orig_prog; + if (build_body(env, &ctx, extra_pass)) goto out_free_hdr; - } ctx.epilogue_offset = ctx.idx; ctx.exentry_idx = exentry_idx; @@ -2163,20 +2133,16 @@ skip_init_ctx: ctx.write = true; /* Pass 3: Adjust jump offset and write final image */ - if (build_body(&ctx, extra_pass) || - WARN_ON_ONCE(ctx.idx != ctx.epilogue_offset)) { - prog = orig_prog; + if (build_body(env, &ctx, extra_pass) || + WARN_ON_ONCE(ctx.idx != ctx.epilogue_offset)) goto out_free_hdr; - } build_epilogue(&ctx, was_classic); build_plt(&ctx); /* Extra pass to validate JITed code. */ - if (validate_ctx(&ctx)) { - prog = orig_prog; + if (validate_ctx(&ctx)) goto out_free_hdr; - } /* update the real prog size */ prog_size = sizeof(u32) * ctx.idx; @@ -2193,23 +2159,14 @@ skip_init_ctx: if (extra_pass && ctx.idx > jit_data->ctx.idx) { pr_err_once("multi-func JIT bug %d > %d\n", ctx.idx, jit_data->ctx.idx); - prog->bpf_func = NULL; - prog->jited = 0; - prog->jited_len = 0; goto out_free_hdr; } if (WARN_ON(bpf_jit_binary_pack_finalize(ro_header, header))) { - /* ro_header has been freed */ + /* ro_header and header has been freed */ ro_header = NULL; - prog = orig_prog; - goto out_off; + header = NULL; + goto out_free_hdr; } - /* - * The instructions have now been copied to the ROX region from - * where they will execute. Now the data cache has to be cleaned to - * the PoU and the I-cache has to be invalidated for the VAs. - */ - bpf_flush_icache(ro_header, ctx.ro_image + ctx.idx); } else { jit_data->ctx = ctx; jit_data->ro_image = ro_image_ptr; @@ -2245,13 +2202,15 @@ out_priv_stack: kfree(jit_data); prog->aux->jit_data = NULL; } -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); + return prog; out_free_hdr: + if (extra_pass) { + prog->bpf_func = NULL; + prog->jited = 0; + prog->jited_len = 0; + } if (header) { bpf_arch_text_copy(&ro_header->size, &header->size, sizeof(header->size)); diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 9cb796e16379..5149ce4cef7e 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1920,45 +1920,28 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, return ret < 0 ? ret : ret * LOONGARCH_INSN_SIZE; } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { - bool tmp_blinded = false, extra_pass = false; + bool extra_pass = false; u8 *image_ptr, *ro_image_ptr; int image_size, prog_size, extable_size; struct jit_ctx ctx; struct jit_data *jit_data; struct bpf_binary_header *header; struct bpf_binary_header *ro_header; - struct bpf_prog *tmp, *orig_prog = prog; /* * If BPF JIT was not enabled then we must fall back to * the interpreter. */ if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - /* - * If blinding was requested and we failed during blinding, - * we must fall back to the interpreter. Otherwise, we save - * the new JITed code. - */ - if (IS_ERR(tmp)) - return orig_prog; - - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - prog = orig_prog; - goto out; - } + if (!jit_data) + return prog; prog->aux->jit_data = jit_data; } if (jit_data->ctx.offset) { @@ -1978,17 +1961,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx.user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena); ctx.offset = kvcalloc(prog->len + 1, sizeof(u32), GFP_KERNEL); - if (ctx.offset == NULL) { - prog = orig_prog; + if (ctx.offset == NULL) goto out_offset; - } /* 1. Initial fake pass to compute ctx->idx and set ctx->flags */ build_prologue(&ctx); - if (build_body(&ctx, extra_pass)) { - prog = orig_prog; + if (build_body(&ctx, extra_pass)) goto out_offset; - } ctx.epilogue_offset = ctx.idx; build_epilogue(&ctx); @@ -2004,10 +1983,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) /* Now we know the size of the structure to make */ ro_header = bpf_jit_binary_pack_alloc(image_size, &ro_image_ptr, sizeof(u32), &header, &image_ptr, jit_fill_hole); - if (!ro_header) { - prog = orig_prog; + if (!ro_header) goto out_offset; - } /* 2. Now, the actual pass to generate final JIT code */ /* @@ -2027,17 +2004,13 @@ skip_init_ctx: ctx.num_exentries = 0; build_prologue(&ctx); - if (build_body(&ctx, extra_pass)) { - prog = orig_prog; + if (build_body(&ctx, extra_pass)) goto out_free; - } build_epilogue(&ctx); /* 3. Extra pass to validate JITed code */ - if (validate_ctx(&ctx)) { - prog = orig_prog; + if (validate_ctx(&ctx)) goto out_free; - } /* And we're done */ if (bpf_jit_enable > 1) @@ -2050,9 +2023,9 @@ skip_init_ctx: goto out_free; } if (WARN_ON(bpf_jit_binary_pack_finalize(ro_header, header))) { - /* ro_header has been freed */ + /* ro_header and header have been freed */ ro_header = NULL; - prog = orig_prog; + header = NULL; goto out_free; } /* @@ -2084,13 +2057,15 @@ out_offset: prog->aux->jit_data = NULL; } -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? tmp : orig_prog); - return prog; out_free: + if (extra_pass) { + prog->bpf_func = NULL; + prog->jited = 0; + prog->jited_len = 0; + } + if (header) { bpf_arch_text_copy(&ro_header->size, &header->size, sizeof(header->size)); bpf_jit_binary_pack_free(ro_header, header); diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index e355dfca4400..6ee4abe6a1f7 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -909,12 +909,10 @@ bool bpf_jit_needs_zext(void) return true; } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { - struct bpf_prog *tmp, *orig_prog = prog; struct bpf_binary_header *header = NULL; struct jit_context ctx; - bool tmp_blinded = false; unsigned int tmp_idx; unsigned int image_size; u8 *image_ptr; @@ -925,19 +923,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * the interpreter. */ if (!prog->jit_requested) - return orig_prog; - /* - * If constant blinding was enabled and we failed during blinding - * then we must fall back to the interpreter. Otherwise, we save - * the new JITed code. - */ - tmp = bpf_jit_blind_constants(prog); - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; memset(&ctx, 0, sizeof(ctx)); ctx.program = prog; @@ -1025,14 +1011,10 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) prog->jited_len = image_size; out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); kfree(ctx.descriptors); return prog; out_err: - prog = orig_prog; if (header) bpf_jit_binary_free(header); goto out; diff --git a/arch/parisc/net/bpf_jit_core.c b/arch/parisc/net/bpf_jit_core.c index a5eb6b51e27a..172770132440 100644 --- a/arch/parisc/net/bpf_jit_core.c +++ b/arch/parisc/net/bpf_jit_core.c @@ -41,33 +41,22 @@ bool bpf_jit_needs_zext(void) return true; } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { unsigned int prog_size = 0, extable_size = 0; - bool tmp_blinded = false, extra_pass = false; - struct bpf_prog *tmp, *orig_prog = prog; + bool extra_pass = false; int pass = 0, prev_ninsns = 0, prologue_len, i; struct hppa_jit_data *jit_data; struct hppa_jit_context *ctx; if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - prog = orig_prog; - goto out; - } + if (!jit_data) + return prog; prog->aux->jit_data = jit_data; } @@ -81,10 +70,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx->prog = prog; ctx->offset = kzalloc_objs(int, prog->len); - if (!ctx->offset) { - prog = orig_prog; - goto out_offset; - } + if (!ctx->offset) + goto out_err; for (i = 0; i < prog->len; i++) { prev_ninsns += 20; ctx->offset[i] = prev_ninsns; @@ -93,10 +80,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) for (i = 0; i < NR_JIT_ITERATIONS; i++) { pass++; ctx->ninsns = 0; - if (build_body(ctx, extra_pass, ctx->offset)) { - prog = orig_prog; - goto out_offset; - } + if (build_body(ctx, extra_pass, ctx->offset)) + goto out_err; ctx->body_len = ctx->ninsns; bpf_jit_build_prologue(ctx); ctx->prologue_len = ctx->ninsns - ctx->body_len; @@ -116,10 +101,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) &jit_data->image, sizeof(long), bpf_fill_ill_insns); - if (!jit_data->header) { - prog = orig_prog; - goto out_offset; - } + if (!jit_data->header) + goto out_err; ctx->insns = (u32 *)jit_data->image; /* @@ -134,8 +117,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) pr_err("bpf-jit: image did not converge in <%d passes!\n", i); if (jit_data->header) bpf_jit_binary_free(jit_data->header); - prog = orig_prog; - goto out_offset; + goto out_err; } if (extable_size) @@ -148,8 +130,7 @@ skip_init_ctx: bpf_jit_build_prologue(ctx); if (build_body(ctx, extra_pass, NULL)) { bpf_jit_binary_free(jit_data->header); - prog = orig_prog; - goto out_offset; + goto out_err; } bpf_jit_build_epilogue(ctx); @@ -160,20 +141,19 @@ skip_init_ctx: { extern int machine_restart(char *); machine_restart(""); } } + if (!prog->is_func || extra_pass) { + if (bpf_jit_binary_lock_ro(jit_data->header)) { + bpf_jit_binary_free(jit_data->header); + goto out_err; + } + bpf_flush_icache(jit_data->header, ctx->insns + ctx->ninsns); + } + prog->bpf_func = (void *)ctx->insns; prog->jited = 1; prog->jited_len = prog_size; - bpf_flush_icache(jit_data->header, ctx->insns + ctx->ninsns); - if (!prog->is_func || extra_pass) { - if (bpf_jit_binary_lock_ro(jit_data->header)) { - bpf_jit_binary_free(jit_data->header); - prog->bpf_func = NULL; - prog->jited = 0; - prog->jited_len = 0; - goto out_offset; - } prologue_len = ctx->epilogue_offset - ctx->body_len; for (i = 0; i < prog->len; i++) ctx->offset[i] += prologue_len; @@ -183,14 +163,19 @@ out_offset: kfree(jit_data); prog->aux->jit_data = NULL; } -out: + if (HPPA_JIT_REBOOT) { extern int machine_restart(char *); machine_restart(""); } - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); return prog; + +out_err: + if (extra_pass) { + prog->bpf_func = NULL; + prog->jited = 0; + prog->jited_len = 0; + } + goto out_offset; } u64 hppa_div64(u64 div, u64 divisor) diff --git a/arch/powerpc/kernel/fadump.c b/arch/powerpc/kernel/fadump.c index 4ebc333dd786..501d43bf18f3 100644 --- a/arch/powerpc/kernel/fadump.c +++ b/arch/powerpc/kernel/fadump.c @@ -775,24 +775,12 @@ void __init fadump_update_elfcore_header(char *bufp) static void *__init fadump_alloc_buffer(unsigned long size) { - unsigned long count, i; - struct page *page; - void *vaddr; - - vaddr = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO); - if (!vaddr) - return NULL; - - count = PAGE_ALIGN(size) / PAGE_SIZE; - page = virt_to_page(vaddr); - for (i = 0; i < count; i++) - mark_page_reserved(page + i); - return vaddr; + return alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO); } static void fadump_free_buffer(unsigned long vaddr, unsigned long size) { - free_reserved_area((void *)vaddr, (void *)(vaddr + size), -1, NULL); + free_pages_exact((void *)vaddr, size); } s32 __init fadump_setup_cpu_notes_buf(u32 num_cpus) diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c index 50103b3794fb..53ab97ad6074 100644 --- a/arch/powerpc/net/bpf_jit_comp.c +++ b/arch/powerpc/net/bpf_jit_comp.c @@ -162,7 +162,7 @@ static void priv_stack_check_guard(void __percpu *priv_stack_ptr, int alloc_size } } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *fp) { u32 proglen; u32 alloclen; @@ -177,9 +177,6 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) void __percpu *priv_stack_ptr = NULL; struct bpf_binary_header *fhdr = NULL; struct bpf_binary_header *hdr = NULL; - struct bpf_prog *org_fp = fp; - struct bpf_prog *tmp_fp = NULL; - bool bpf_blinded = false; bool extra_pass = false; u8 *fimage = NULL; u32 *fcode_base = NULL; @@ -187,24 +184,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) u32 fixup_len; if (!fp->jit_requested) - return org_fp; - - tmp_fp = bpf_jit_blind_constants(org_fp); - if (IS_ERR(tmp_fp)) - return org_fp; - - if (tmp_fp != org_fp) { - bpf_blinded = true; - fp = tmp_fp; - } + return fp; jit_data = fp->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - fp = org_fp; - goto out; - } + if (!jit_data) + return fp; fp->aux->jit_data = jit_data; } @@ -219,10 +205,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) priv_stack_alloc_size = round_up(fp->aux->stack_depth, 16) + 2 * PRIV_STACK_GUARD_SZ; priv_stack_ptr = __alloc_percpu_gfp(priv_stack_alloc_size, 16, GFP_KERNEL); - if (!priv_stack_ptr) { - fp = org_fp; + if (!priv_stack_ptr) goto out_priv_stack; - } priv_stack_init_guard(priv_stack_ptr, priv_stack_alloc_size); fp->aux->priv_stack_ptr = priv_stack_ptr; @@ -249,10 +233,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) } addrs = kcalloc(flen + 1, sizeof(*addrs), GFP_KERNEL); - if (addrs == NULL) { - fp = org_fp; - goto out_addrs; - } + if (addrs == NULL) + goto out_err; memset(&cgctx, 0, sizeof(struct codegen_context)); bpf_jit_init_reg_mapping(&cgctx); @@ -279,11 +261,9 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) } /* Scouting faux-generate pass 0 */ - if (bpf_jit_build_body(fp, NULL, NULL, &cgctx, addrs, 0, false)) { + if (bpf_jit_build_body(fp, NULL, NULL, &cgctx, addrs, 0, false)) /* We hit something illegal or unsupported. */ - fp = org_fp; - goto out_addrs; - } + goto out_err; /* * If we have seen a tail call, we need a second pass. @@ -294,10 +274,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) */ if (cgctx.seen & SEEN_TAILCALL || !is_offset_in_branch_range((long)cgctx.idx * 4)) { cgctx.idx = 0; - if (bpf_jit_build_body(fp, NULL, NULL, &cgctx, addrs, 0, false)) { - fp = org_fp; - goto out_addrs; - } + if (bpf_jit_build_body(fp, NULL, NULL, &cgctx, addrs, 0, false)) + goto out_err; } bpf_jit_realloc_regs(&cgctx); @@ -318,10 +296,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) fhdr = bpf_jit_binary_pack_alloc(alloclen, &fimage, 4, &hdr, &image, bpf_jit_fill_ill_insns); - if (!fhdr) { - fp = org_fp; - goto out_addrs; - } + if (!fhdr) + goto out_err; if (extable_len) fp->aux->extable = (void *)fimage + FUNCTION_DESCR_SIZE + proglen + fixup_len; @@ -340,8 +316,7 @@ skip_init_ctx: extra_pass)) { bpf_arch_text_copy(&fhdr->size, &hdr->size, sizeof(hdr->size)); bpf_jit_binary_pack_free(fhdr, hdr); - fp = org_fp; - goto out_addrs; + goto out_err; } bpf_jit_build_epilogue(code_base, &cgctx); @@ -363,15 +338,16 @@ skip_init_ctx: ((u64 *)image)[1] = local_paca->kernel_toc; #endif + if (!fp->is_func || extra_pass) { + if (bpf_jit_binary_pack_finalize(fhdr, hdr)) + goto out_err; + } + fp->bpf_func = (void *)fimage; fp->jited = 1; fp->jited_len = cgctx.idx * 4 + FUNCTION_DESCR_SIZE; if (!fp->is_func || extra_pass) { - if (bpf_jit_binary_pack_finalize(fhdr, hdr)) { - fp = org_fp; - goto out_addrs; - } bpf_prog_fill_jited_linfo(fp, addrs); /* * On ABI V1, executable code starts after the function @@ -398,11 +374,15 @@ out_priv_stack: jit_data->hdr = hdr; } -out: - if (bpf_blinded) - bpf_jit_prog_release_other(fp, fp == org_fp ? tmp_fp : org_fp); - return fp; + +out_err: + if (extra_pass) { + fp->bpf_func = NULL; + fp->jited = 0; + fp->jited_len = 0; + } + goto out_addrs; } /* diff --git a/arch/powerpc/platforms/powernv/opal-core.c b/arch/powerpc/platforms/powernv/opal-core.c index e76e462f55f6..32662d30d70f 100644 --- a/arch/powerpc/platforms/powernv/opal-core.c +++ b/arch/powerpc/platforms/powernv/opal-core.c @@ -303,7 +303,6 @@ static int __init create_opalcore(void) struct device_node *dn; struct opalcore *new; loff_t opalcore_off; - struct page *page; Elf64_Phdr *phdr; Elf64_Ehdr *elf; int i, ret; @@ -328,11 +327,6 @@ static int __init create_opalcore(void) oc_conf->opalcorebuf_sz = 0; return -ENOMEM; } - count = oc_conf->opalcorebuf_sz / PAGE_SIZE; - page = virt_to_page(oc_conf->opalcorebuf); - for (i = 0; i < count; i++) - mark_page_reserved(page + i); - pr_debug("opalcorebuf = 0x%llx\n", (u64)oc_conf->opalcorebuf); /* Read OPAL related device-tree entries */ @@ -437,10 +431,7 @@ static void opalcore_cleanup(void) /* free the buffer used for setting up OPAL core */ if (oc_conf->opalcorebuf) { - void *end = (void *)((u64)oc_conf->opalcorebuf + - oc_conf->opalcorebuf_sz); - - free_reserved_area(oc_conf->opalcorebuf, end, -1, NULL); + free_pages_exact(oc_conf->opalcorebuf, oc_conf->opalcorebuf_sz); oc_conf->opalcorebuf = NULL; oc_conf->opalcorebuf_sz = 0; } diff --git a/arch/riscv/net/bpf_jit.h b/arch/riscv/net/bpf_jit.h index 632ced07bca4..da0271790244 100644 --- a/arch/riscv/net/bpf_jit.h +++ b/arch/riscv/net/bpf_jit.h @@ -11,7 +11,6 @@ #include #include -#include /* verify runtime detection extension status */ #define rv_ext_enabled(ext) \ @@ -105,11 +104,6 @@ static inline void bpf_fill_ill_insns(void *area, unsigned int size) memset(area, 0, size); } -static inline void bpf_flush_icache(void *start, void *end) -{ - flush_icache_range((unsigned long)start, (unsigned long)end); -} - /* Emit a 4-byte riscv instruction. */ static inline void emit(const u32 insn, struct rv_jit_context *ctx) { diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c index b3581e926436..4365d07aaf54 100644 --- a/arch/riscv/net/bpf_jit_core.c +++ b/arch/riscv/net/bpf_jit_core.c @@ -41,32 +41,22 @@ bool bpf_jit_needs_zext(void) return true; } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { unsigned int prog_size = 0, extable_size = 0; - bool tmp_blinded = false, extra_pass = false; - struct bpf_prog *tmp, *orig_prog = prog; + bool extra_pass = false; int pass = 0, prev_ninsns = 0, i; struct rv_jit_data *jit_data; struct rv_jit_context *ctx; if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); if (!jit_data) { - prog = orig_prog; - goto out; + return prog; } prog->aux->jit_data = jit_data; } @@ -83,15 +73,11 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx->user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena); ctx->prog = prog; ctx->offset = kzalloc_objs(int, prog->len); - if (!ctx->offset) { - prog = orig_prog; + if (!ctx->offset) goto out_offset; - } - if (build_body(ctx, extra_pass, NULL)) { - prog = orig_prog; + if (build_body(ctx, extra_pass, NULL)) goto out_offset; - } for (i = 0; i < prog->len; i++) { prev_ninsns += 32; @@ -105,10 +91,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) bpf_jit_build_prologue(ctx, bpf_is_subprog(prog)); ctx->prologue_len = ctx->ninsns; - if (build_body(ctx, extra_pass, ctx->offset)) { - prog = orig_prog; + if (build_body(ctx, extra_pass, ctx->offset)) goto out_offset; - } ctx->epilogue_offset = ctx->ninsns; bpf_jit_build_epilogue(ctx); @@ -126,10 +110,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) &jit_data->ro_image, sizeof(u32), &jit_data->header, &jit_data->image, bpf_fill_ill_insns); - if (!jit_data->ro_header) { - prog = orig_prog; + if (!jit_data->ro_header) goto out_offset; - } /* * Use the image(RW) for writing the JITed instructions. But also save @@ -150,7 +132,6 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) if (i == NR_JIT_ITERATIONS) { pr_err("bpf-jit: image did not converge in <%d passes!\n", i); - prog = orig_prog; goto out_free_hdr; } @@ -163,33 +144,27 @@ skip_init_ctx: ctx->nexentries = 0; bpf_jit_build_prologue(ctx, bpf_is_subprog(prog)); - if (build_body(ctx, extra_pass, NULL)) { - prog = orig_prog; + if (build_body(ctx, extra_pass, NULL)) goto out_free_hdr; - } bpf_jit_build_epilogue(ctx); if (bpf_jit_enable > 1) bpf_jit_dump(prog->len, prog_size, pass, ctx->insns); + if (!prog->is_func || extra_pass) { + if (WARN_ON(bpf_jit_binary_pack_finalize(jit_data->ro_header, jit_data->header))) { + /* ro_header has been freed */ + jit_data->ro_header = NULL; + jit_data->header = NULL; + goto out_free_hdr; + } + } + prog->bpf_func = (void *)ctx->ro_insns + cfi_get_offset(); prog->jited = 1; prog->jited_len = prog_size - cfi_get_offset(); if (!prog->is_func || extra_pass) { - if (WARN_ON(bpf_jit_binary_pack_finalize(jit_data->ro_header, jit_data->header))) { - /* ro_header has been freed */ - jit_data->ro_header = NULL; - prog = orig_prog; - goto out_offset; - } - /* - * The instructions have now been copied to the ROX region from - * where they will execute. - * Write any modified data cache blocks out to memory and - * invalidate the corresponding blocks in the instruction cache. - */ - bpf_flush_icache(jit_data->ro_header, ctx->ro_insns + ctx->ninsns); for (i = 0; i < prog->len; i++) ctx->offset[i] = ninsns_rvoff(ctx->offset[i]); bpf_prog_fill_jited_linfo(prog, ctx->offset); @@ -198,14 +173,15 @@ out_offset: kfree(jit_data); prog->aux->jit_data = NULL; } -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); return prog; out_free_hdr: + if (extra_pass) { + prog->bpf_func = NULL; + prog->jited = 0; + prog->jited_len = 0; + } if (jit_data->header) { bpf_arch_text_copy(&jit_data->ro_header->size, &jit_data->header->size, sizeof(jit_data->header->size)); diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c index d08d159b6319..94128fe6be23 100644 --- a/arch/s390/net/bpf_jit_comp.c +++ b/arch/s390/net/bpf_jit_comp.c @@ -2312,38 +2312,22 @@ static struct bpf_binary_header *bpf_jit_alloc(struct bpf_jit *jit, /* * Compile eBPF program "fp" */ -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *fp) { - struct bpf_prog *tmp, *orig_fp = fp; struct bpf_binary_header *header; struct s390_jit_data *jit_data; - bool tmp_blinded = false; bool extra_pass = false; struct bpf_jit jit; int pass; if (!fp->jit_requested) - return orig_fp; - - tmp = bpf_jit_blind_constants(fp); - /* - * If blinding was requested and we failed during blinding, - * we must fall back to the interpreter. - */ - if (IS_ERR(tmp)) - return orig_fp; - if (tmp != fp) { - tmp_blinded = true; - fp = tmp; - } + return fp; jit_data = fp->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - fp = orig_fp; - goto out; - } + if (!jit_data) + return fp; fp->aux->jit_data = jit_data; } if (jit_data->ctx.addrs) { @@ -2356,34 +2340,27 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp) memset(&jit, 0, sizeof(jit)); jit.addrs = kvcalloc(fp->len + 1, sizeof(*jit.addrs), GFP_KERNEL); - if (jit.addrs == NULL) { - fp = orig_fp; - goto free_addrs; - } + if (jit.addrs == NULL) + goto out_err; /* * Three initial passes: * - 1/2: Determine clobbered registers * - 3: Calculate program size and addrs array */ for (pass = 1; pass <= 3; pass++) { - if (bpf_jit_prog(&jit, fp, extra_pass)) { - fp = orig_fp; - goto free_addrs; - } + if (bpf_jit_prog(&jit, fp, extra_pass)) + goto out_err; } /* * Final pass: Allocate and generate program */ header = bpf_jit_alloc(&jit, fp); - if (!header) { - fp = orig_fp; - goto free_addrs; - } + if (!header) + goto out_err; skip_init_ctx: if (bpf_jit_prog(&jit, fp, extra_pass)) { bpf_jit_binary_free(header); - fp = orig_fp; - goto free_addrs; + goto out_err; } if (bpf_jit_enable > 1) { bpf_jit_dump(fp->len, jit.size, pass, jit.prg_buf); @@ -2392,8 +2369,7 @@ skip_init_ctx: if (!fp->is_func || extra_pass) { if (bpf_jit_binary_lock_ro(header)) { bpf_jit_binary_free(header); - fp = orig_fp; - goto free_addrs; + goto out_err; } } else { jit_data->header = header; @@ -2411,11 +2387,16 @@ free_addrs: kfree(jit_data); fp->aux->jit_data = NULL; } -out: - if (tmp_blinded) - bpf_jit_prog_release_other(fp, fp == orig_fp ? - tmp : orig_fp); + return fp; + +out_err: + if (extra_pass) { + fp->bpf_func = NULL; + fp->jited = 0; + fp->jited_len = 0; + } + goto free_addrs; } bool bpf_jit_supports_kfunc_call(void) diff --git a/arch/sparc/kernel/mdesc.c b/arch/sparc/kernel/mdesc.c index 30f171b7b00c..ecd6c8ae49c7 100644 --- a/arch/sparc/kernel/mdesc.c +++ b/arch/sparc/kernel/mdesc.c @@ -183,14 +183,12 @@ static struct mdesc_handle * __init mdesc_memblock_alloc(unsigned int mdesc_size static void __init mdesc_memblock_free(struct mdesc_handle *hp) { unsigned int alloc_size; - unsigned long start; BUG_ON(refcount_read(&hp->refcnt) != 0); BUG_ON(!list_empty(&hp->list)); alloc_size = PAGE_ALIGN(hp->handle_size); - start = __pa(hp); - memblock_free_late(start, alloc_size); + memblock_free(hp, alloc_size); } static struct mdesc_mem_ops memblock_mdesc_ops = { diff --git a/arch/sparc/net/bpf_jit_comp_64.c b/arch/sparc/net/bpf_jit_comp_64.c index b23d1c645ae5..2fa0e9375127 100644 --- a/arch/sparc/net/bpf_jit_comp_64.c +++ b/arch/sparc/net/bpf_jit_comp_64.c @@ -1477,39 +1477,24 @@ struct sparc64_jit_data { struct jit_ctx ctx; }; -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { - struct bpf_prog *tmp, *orig_prog = prog; struct sparc64_jit_data *jit_data; struct bpf_binary_header *header; u32 prev_image_size, image_size; - bool tmp_blinded = false; bool extra_pass = false; struct jit_ctx ctx; u8 *image_ptr; int pass, i; if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - /* If blinding was requested and we failed during blinding, - * we must fall back to the interpreter. - */ - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - prog = orig_prog; - goto out; - } + if (!jit_data) + return prog; prog->aux->jit_data = jit_data; } if (jit_data->ctx.offset) { @@ -1527,10 +1512,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx.prog = prog; ctx.offset = kmalloc_array(prog->len, sizeof(unsigned int), GFP_KERNEL); - if (ctx.offset == NULL) { - prog = orig_prog; - goto out_off; - } + if (ctx.offset == NULL) + goto out_err; /* Longest sequence emitted is for bswap32, 12 instructions. Pre-cook * the offset array so that we converge faster. @@ -1543,10 +1526,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx.idx = 0; build_prologue(&ctx); - if (build_body(&ctx)) { - prog = orig_prog; - goto out_off; - } + if (build_body(&ctx)) + goto out_err; build_epilogue(&ctx); if (bpf_jit_enable > 1) @@ -1569,10 +1550,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) image_size = sizeof(u32) * ctx.idx; header = bpf_jit_binary_alloc(image_size, &image_ptr, sizeof(u32), jit_fill_hole); - if (header == NULL) { - prog = orig_prog; - goto out_off; - } + if (header == NULL) + goto out_err; ctx.image = (u32 *)image_ptr; skip_init_ctx: @@ -1582,8 +1561,7 @@ skip_init_ctx: if (build_body(&ctx)) { bpf_jit_binary_free(header); - prog = orig_prog; - goto out_off; + goto out_err; } build_epilogue(&ctx); @@ -1592,8 +1570,7 @@ skip_init_ctx: pr_err("bpf_jit: Failed to converge, prev_size=%u size=%d\n", prev_image_size, ctx.idx * 4); bpf_jit_binary_free(header); - prog = orig_prog; - goto out_off; + goto out_err; } if (bpf_jit_enable > 1) @@ -1604,8 +1581,7 @@ skip_init_ctx: if (!prog->is_func || extra_pass) { if (bpf_jit_binary_lock_ro(header)) { bpf_jit_binary_free(header); - prog = orig_prog; - goto out_off; + goto out_err; } } else { jit_data->ctx = ctx; @@ -1624,9 +1600,14 @@ out_off: kfree(jit_data); prog->aux->jit_data = NULL; } -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); + return prog; + +out_err: + if (extra_pass) { + prog->bpf_func = NULL; + prog->jited = 0; + prog->jited_len = 0; + } + goto out_off; } diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index e87da25d1236..62936a3bde19 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -2448,12 +2448,6 @@ void __init alternative_instructions(void) __smp_locks, __smp_locks_end, _text, _etext); } - - if (!uniproc_patched || num_possible_cpus() == 1) { - free_init_pages("SMP alternatives", - (unsigned long)__smp_locks, - (unsigned long)__smp_locks_end); - } #endif restart_nmi(); @@ -2462,6 +2456,24 @@ void __init alternative_instructions(void) alt_reloc_selftest(); } +#ifdef CONFIG_SMP +/* + * With CONFIG_DEFERRED_STRUCT_PAGE_INIT enabled we can free_init_pages() only + * after the deferred initialization of the memory map is complete. + */ +static int __init free_smp_locks(void) +{ + if (!uniproc_patched || num_possible_cpus() == 1) { + free_init_pages("SMP alternatives", + (unsigned long)__smp_locks, + (unsigned long)__smp_locks_end); + } + + return 0; +} +arch_initcall(free_smp_locks); +#endif + /** * text_poke_early - Update instructions on a live kernel at boot time * @addr: address to modify diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index eebcc9db1a1b..46882ce79c3a 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -426,7 +426,7 @@ int __init ima_free_kexec_buffer(void) if (!ima_kexec_buffer_size) return -ENOENT; - memblock_free_late(ima_kexec_buffer_phys, + memblock_phys_free(ima_kexec_buffer_phys, ima_kexec_buffer_size); ima_kexec_buffer_phys = 0; diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index e9b78040d703..ea9e707e8abf 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -58,8 +58,8 @@ static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len) #define EMIT_ENDBR() EMIT(gen_endbr(), 4) #define EMIT_ENDBR_POISON() EMIT(gen_endbr_poison(), 4) #else -#define EMIT_ENDBR() -#define EMIT_ENDBR_POISON() +#define EMIT_ENDBR() do { } while (0) +#define EMIT_ENDBR_POISON() do { } while (0) #endif static bool is_imm8(int value) @@ -1649,8 +1649,8 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip, return 0; } -static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image, - int oldproglen, struct jit_context *ctx, bool jmp_padding) +static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image, + u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding) { bool tail_call_reachable = bpf_prog->aux->tail_call_reachable; struct bpf_insn *insn = bpf_prog->insnsi; @@ -1663,7 +1663,7 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image void __percpu *priv_stack_ptr; int i, excnt = 0; int ilen, proglen = 0; - u8 *prog = temp; + u8 *ip, *prog = temp; u32 stack_depth; int err; @@ -1734,6 +1734,11 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image dst_reg = X86_REG_R9; } + if (bpf_insn_is_indirect_target(env, bpf_prog, i - 1)) + EMIT_ENDBR(); + + ip = image + addrs[i - 1] + (prog - temp); + switch (insn->code) { /* ALU */ case BPF_ALU | BPF_ADD | BPF_X: @@ -2440,8 +2445,6 @@ populate_extable: /* call */ case BPF_JMP | BPF_CALL: { - u8 *ip = image + addrs[i - 1]; - func = (u8 *) __bpf_call_base + imm32; if (src_reg == BPF_PSEUDO_CALL && tail_call_reachable) { LOAD_TAIL_CALL_CNT_PTR(stack_depth); @@ -2465,7 +2468,8 @@ populate_extable: if (imm32) emit_bpf_tail_call_direct(bpf_prog, &bpf_prog->aux->poke_tab[imm32 - 1], - &prog, image + addrs[i - 1], + &prog, + ip, callee_regs_used, stack_depth, ctx); @@ -2474,7 +2478,7 @@ populate_extable: &prog, callee_regs_used, stack_depth, - image + addrs[i - 1], + ip, ctx); break; @@ -2639,7 +2643,7 @@ emit_cond_jmp: /* Convert BPF opcode to x86 */ break; case BPF_JMP | BPF_JA | BPF_X: - emit_indirect_jump(&prog, insn->dst_reg, image + addrs[i - 1]); + emit_indirect_jump(&prog, insn->dst_reg, ip); break; case BPF_JMP | BPF_JA: case BPF_JMP32 | BPF_JA: @@ -2729,8 +2733,6 @@ emit_jmp: ctx->cleanup_addr = proglen; if (bpf_prog_was_classic(bpf_prog) && !ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN)) { - u8 *ip = image + addrs[i - 1]; - if (emit_spectre_bhb_barrier(&prog, ip, bpf_prog)) return -EINVAL; } @@ -3713,17 +3715,15 @@ struct x64_jit_data { #define MAX_PASSES 20 #define PADDING_PASSES (MAX_PASSES - 5) -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { struct bpf_binary_header *rw_header = NULL; struct bpf_binary_header *header = NULL; - struct bpf_prog *tmp, *orig_prog = prog; void __percpu *priv_stack_ptr = NULL; struct x64_jit_data *jit_data; int priv_stack_alloc_sz; int proglen, oldproglen = 0; struct jit_context ctx = {}; - bool tmp_blinded = false; bool extra_pass = false; bool padding = false; u8 *rw_image = NULL; @@ -3733,27 +3733,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) int i; if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - /* - * If blinding was requested and we failed during blinding, - * we must fall back to the interpreter. - */ - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc_obj(*jit_data); - if (!jit_data) { - prog = orig_prog; - goto out; - } + if (!jit_data) + return prog; prog->aux->jit_data = jit_data; } priv_stack_ptr = prog->aux->priv_stack_ptr; @@ -3765,10 +3751,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) priv_stack_alloc_sz = round_up(prog->aux->stack_depth, 8) + 2 * PRIV_STACK_GUARD_SZ; priv_stack_ptr = __alloc_percpu_gfp(priv_stack_alloc_sz, 8, GFP_KERNEL); - if (!priv_stack_ptr) { - prog = orig_prog; + if (!priv_stack_ptr) goto out_priv_stack; - } priv_stack_init_guard(priv_stack_ptr, priv_stack_alloc_sz); prog->aux->priv_stack_ptr = priv_stack_ptr; @@ -3786,10 +3770,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) goto skip_init_addrs; } addrs = kvmalloc_objs(*addrs, prog->len + 1); - if (!addrs) { - prog = orig_prog; + if (!addrs) goto out_addrs; - } /* * Before first pass, make a rough estimation of addrs[] @@ -3811,7 +3793,7 @@ skip_init_addrs: for (pass = 0; pass < MAX_PASSES || image; pass++) { if (!padding && pass >= PADDING_PASSES) padding = true; - proglen = do_jit(prog, addrs, image, rw_image, oldproglen, &ctx, padding); + proglen = do_jit(env, prog, addrs, image, rw_image, oldproglen, &ctx, padding); if (proglen <= 0) { out_image: image = NULL; @@ -3820,8 +3802,6 @@ out_image: sizeof(rw_header->size)); bpf_jit_binary_pack_free(header, rw_header); } - /* Fall back to interpreter mode */ - prog = orig_prog; if (extra_pass) { prog->bpf_func = NULL; prog->jited = 0; @@ -3852,10 +3832,8 @@ out_image: header = bpf_jit_binary_pack_alloc(roundup(proglen, align) + extable_size, &image, align, &rw_header, &rw_image, jit_fill_hole); - if (!header) { - prog = orig_prog; + if (!header) goto out_addrs; - } prog->aux->extable = (void *) image + roundup(proglen, align); } oldproglen = proglen; @@ -3908,8 +3886,6 @@ out_image: prog->bpf_func = (void *)image + cfi_get_offset(); prog->jited = 1; prog->jited_len = proglen - cfi_get_offset(); - } else { - prog = orig_prog; } if (!image || !prog->is_func || extra_pass) { @@ -3925,10 +3901,7 @@ out_priv_stack: kfree(jit_data); prog->aux->jit_data = NULL; } -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); + return prog; } diff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c index dda423025c3d..852baf2e4db4 100644 --- a/arch/x86/net/bpf_jit_comp32.c +++ b/arch/x86/net/bpf_jit_comp32.c @@ -2518,38 +2518,22 @@ bool bpf_jit_needs_zext(void) return true; } -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { struct bpf_binary_header *header = NULL; - struct bpf_prog *tmp, *orig_prog = prog; int proglen, oldproglen = 0; struct jit_context ctx = {}; - bool tmp_blinded = false; u8 *image = NULL; int *addrs; int pass; int i; if (!prog->jit_requested) - return orig_prog; - - tmp = bpf_jit_blind_constants(prog); - /* - * If blinding was requested and we failed during blinding, - * we must fall back to the interpreter. - */ - if (IS_ERR(tmp)) - return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } + return prog; addrs = kmalloc_objs(*addrs, prog->len); - if (!addrs) { - prog = orig_prog; - goto out; - } + if (!addrs) + return prog; /* * Before first pass, make a rough estimation of addrs[] @@ -2574,7 +2558,6 @@ out_image: image = NULL; if (header) bpf_jit_binary_free(header); - prog = orig_prog; goto out_addrs; } if (image) { @@ -2588,10 +2571,8 @@ out_image: if (proglen == oldproglen) { header = bpf_jit_binary_alloc(proglen, &image, 1, jit_fill_hole); - if (!header) { - prog = orig_prog; + if (!header) goto out_addrs; - } } oldproglen = proglen; cond_resched(); @@ -2604,16 +2585,10 @@ out_image: prog->bpf_func = (void *)image; prog->jited = 1; prog->jited_len = proglen; - } else { - prog = orig_prog; } out_addrs: kfree(addrs); -out: - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); return prog; } diff --git a/arch/x86/platform/efi/memmap.c b/arch/x86/platform/efi/memmap.c index 023697c88910..697a9a26a005 100644 --- a/arch/x86/platform/efi/memmap.c +++ b/arch/x86/platform/efi/memmap.c @@ -34,10 +34,7 @@ static void __init __efi_memmap_free(u64 phys, unsigned long size, unsigned long flags) { if (flags & EFI_MEMMAP_MEMBLOCK) { - if (slab_is_available()) - memblock_free_late(phys, size); - else - memblock_phys_free(phys, size); + memblock_phys_free(phys, size); } else if (flags & EFI_MEMMAP_SLAB) { struct page *p = pfn_to_page(PHYS_PFN(phys)); unsigned int order = get_order(size); diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c index 79f0818131e8..df24ffc6105d 100644 --- a/arch/x86/platform/efi/quirks.c +++ b/arch/x86/platform/efi/quirks.c @@ -372,7 +372,7 @@ void __init efi_reserve_boot_services(void) * doesn't make sense as far as the firmware is * concerned, but it does provide us with a way to tag * those regions that must not be paired with - * memblock_free_late(). + * memblock_phys_free(). */ md->attribute |= EFI_MEMORY_RUNTIME; } diff --git a/drivers/acpi/numa/srat.c b/drivers/acpi/numa/srat.c index aa87ee1583a4..62d4a8df0b8c 100644 --- a/drivers/acpi/numa/srat.c +++ b/drivers/acpi/numa/srat.c @@ -654,8 +654,11 @@ int __init acpi_numa_init(void) } last_real_pxm = fake_pxm; fake_pxm++; - acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, acpi_parse_cfmws, - &fake_pxm); + + /* No need to expand numa nodes if CXL is disabled */ + if (IS_ENABLED(CONFIG_CXL_ACPI)) + acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, acpi_parse_cfmws, + &fake_pxm); if (cnt < 0) return cnt; diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 92bed266d07c..669f76000197 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -187,6 +187,16 @@ config SSIF_IPMI_BMC The driver implements the BMC side of the SMBus system interface (SSIF). +config SSIF_IPMI_BMC_KUNIT_TEST + bool "KUnit tests for SSIF IPMI BMC driver" if !KUNIT_ALL_TESTS + depends on KUNIT=y + depends on SSIF_IPMI_BMC + default KUNIT_ALL_TESTS + help + This option builds unit tests that exercise the SSIF BMC state + machine, including request handling, response transmission, + and error paths such as aborted or truncated transfers. + config IPMB_DEVICE_INTERFACE tristate 'IPMB Interface handler' depends on I2C diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index c41f51c82edd..869ac87a4b6a 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -987,7 +987,7 @@ static int deliver_response(struct ipmi_smi *intf, struct ipmi_recv_msg *msg) mutex_lock(&intf->user_msgs_mutex); list_add_tail(&msg->link, &intf->user_msgs); mutex_unlock(&intf->user_msgs_mutex); - queue_work(system_wq, &intf->smi_work); + queue_work(system_percpu_wq, &intf->smi_work); } return rv; @@ -4977,7 +4977,7 @@ void ipmi_smi_msg_received(struct ipmi_smi *intf, if (run_to_completion) smi_work(&intf->smi_work); else - queue_work(system_wq, &intf->smi_work); + queue_work(system_percpu_wq, &intf->smi_work); } EXPORT_SYMBOL(ipmi_smi_msg_received); @@ -4987,7 +4987,7 @@ void ipmi_smi_watchdog_pretimeout(struct ipmi_smi *intf) return; atomic_set(&intf->watchdog_pretimeouts_to_deliver, 1); - queue_work(system_wq, &intf->smi_work); + queue_work(system_percpu_wq, &intf->smi_work); } EXPORT_SYMBOL(ipmi_smi_watchdog_pretimeout); @@ -5162,7 +5162,7 @@ static bool ipmi_timeout_handler(struct ipmi_smi *intf, flags); } - queue_work(system_wq, &intf->smi_work); + queue_work(system_percpu_wq, &intf->smi_work); return need_timer; } @@ -5218,7 +5218,7 @@ static void ipmi_timeout(struct timer_list *unused) if (atomic_read(&stop_operation)) return; - queue_work(system_wq, &ipmi_timer_work); + queue_work(system_percpu_wq, &ipmi_timer_work); } static void need_waiter(struct ipmi_smi *intf) diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c index 37a5cb5c53f1..b49500a1bd36 100644 --- a/drivers/char/ipmi/ipmi_ssif.c +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -1268,8 +1268,10 @@ static void shutdown_ssif(void *send_info) ssif_info->stopping = true; timer_delete_sync(&ssif_info->watch_timer); timer_delete_sync(&ssif_info->retry_timer); - if (ssif_info->thread) + if (ssif_info->thread) { kthread_stop(ssif_info->thread); + ssif_info->thread = NULL; + } } static void ssif_remove(struct i2c_client *client) @@ -1658,6 +1660,7 @@ static int ssif_probe(struct i2c_client *client) int len = 0; int i; u8 slave_addr = 0; + unsigned int thread_num; struct ssif_addr_info *addr_info = NULL; mutex_lock(&ssif_infos_mutex); @@ -1876,22 +1879,17 @@ static int ssif_probe(struct i2c_client *client) ssif_info->handlers.request_events = request_events; ssif_info->handlers.set_need_watch = ssif_set_need_watch; - { - unsigned int thread_num; - - thread_num = ((i2c_adapter_id(ssif_info->client->adapter) - << 8) | - ssif_info->client->addr); - init_completion(&ssif_info->wake_thread); - ssif_info->thread = kthread_run(ipmi_ssif_thread, ssif_info, - "kssif%4.4x", thread_num); - if (IS_ERR(ssif_info->thread)) { - rv = PTR_ERR(ssif_info->thread); - dev_notice(&ssif_info->client->dev, - "Could not start kernel thread: error %d\n", - rv); - goto out; - } + thread_num = ((i2c_adapter_id(ssif_info->client->adapter) << 8) | + ssif_info->client->addr); + init_completion(&ssif_info->wake_thread); + ssif_info->thread = kthread_run(ipmi_ssif_thread, ssif_info, + "kssif%4.4x", thread_num); + if (IS_ERR(ssif_info->thread)) { + rv = PTR_ERR(ssif_info->thread); + dev_notice(&ssif_info->client->dev, + "Could not start kernel thread: error %d\n", + rv); + goto out; } dev_set_drvdata(&ssif_info->client->dev, ssif_info); @@ -1916,6 +1914,15 @@ static int ssif_probe(struct i2c_client *client) out: if (rv) { + /* + * If ipmi_register_smi() starts the interface, it will + * call shutdown and that will free the thread and set + * it to NULL. Otherwise it must be freed here. + */ + if (ssif_info->thread) { + kthread_stop(ssif_info->thread); + ssif_info->thread = NULL; + } if (addr_info) addr_info->client = NULL; diff --git a/drivers/char/ipmi/ssif_bmc.c b/drivers/char/ipmi/ssif_bmc.c index 7a52e3ea49ed..1df0e9284ad9 100644 --- a/drivers/char/ipmi/ssif_bmc.c +++ b/drivers/char/ipmi/ssif_bmc.c @@ -18,6 +18,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_SSIF_IPMI_BMC_KUNIT_TEST) +#include +#endif #define DEVICE_NAME "ipmi-ssif-host" @@ -163,6 +166,8 @@ static ssize_t ssif_bmc_read(struct file *file, char __user *buf, size_t count, spin_unlock_irqrestore(&ssif_bmc->lock, flags); ret = copy_to_user(buf, &msg, count); + if (ret > 0) + ret = -EFAULT; } return (ret < 0) ? ret : count; @@ -456,6 +461,15 @@ static bool supported_write_cmd(u8 cmd) return false; } +static bool supported_write_start_cmd(u8 cmd) +{ + if (cmd == SSIF_IPMI_SINGLEPART_WRITE || + cmd == SSIF_IPMI_MULTIPART_WRITE_START) + return true; + + return false; +} + /* Process the IPMI response that will be read by master */ static void handle_read_processed(struct ssif_bmc_ctx *ssif_bmc, u8 *val) { @@ -558,7 +572,7 @@ static void process_request_part(struct ssif_bmc_ctx *ssif_bmc) len = ssif_bmc->request.len + part->length; /* Do the bound check here, not allow the request len exceed 254 bytes */ if (len > IPMI_SSIF_PAYLOAD_MAX) { - dev_warn(&ssif_bmc->client->dev, + dev_dbg(&ssif_bmc->client->dev, "Warn: Request exceeded 254 bytes, aborting"); /* Request too long, aborting */ ssif_bmc->aborting = true; @@ -604,7 +618,7 @@ static void on_read_requested_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) ssif_bmc->state == SSIF_START || ssif_bmc->state == SSIF_REQ_RECVING || ssif_bmc->state == SSIF_RES_SENDING) { - dev_warn(&ssif_bmc->client->dev, + dev_dbg(&ssif_bmc->client->dev, "Warn: %s unexpected READ REQUESTED in state=%s\n", __func__, state_to_string(ssif_bmc->state)); ssif_bmc->state = SSIF_ABORTING; @@ -613,7 +627,7 @@ static void on_read_requested_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) } else if (ssif_bmc->state == SSIF_SMBUS_CMD) { if (!supported_read_cmd(ssif_bmc->part_buf.smbus_cmd)) { - dev_warn(&ssif_bmc->client->dev, "Warn: Unknown SMBus read command=0x%x", + dev_dbg(&ssif_bmc->client->dev, "Warn: Unknown SMBus read command=0x%x", ssif_bmc->part_buf.smbus_cmd); ssif_bmc->aborting = true; } @@ -648,7 +662,7 @@ static void on_read_processed_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) ssif_bmc->state == SSIF_START || ssif_bmc->state == SSIF_REQ_RECVING || ssif_bmc->state == SSIF_SMBUS_CMD) { - dev_warn(&ssif_bmc->client->dev, + dev_dbg(&ssif_bmc->client->dev, "Warn: %s unexpected READ PROCESSED in state=%s\n", __func__, state_to_string(ssif_bmc->state)); ssif_bmc->state = SSIF_ABORTING; @@ -673,7 +687,7 @@ static void on_write_requested_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) } else if (ssif_bmc->state == SSIF_START || ssif_bmc->state == SSIF_REQ_RECVING || ssif_bmc->state == SSIF_RES_SENDING) { - dev_warn(&ssif_bmc->client->dev, + dev_dbg(&ssif_bmc->client->dev, "Warn: %s unexpected WRITE REQUEST in state=%s\n", __func__, state_to_string(ssif_bmc->state)); ssif_bmc->state = SSIF_ABORTING; @@ -688,7 +702,7 @@ static void on_write_received_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) { if (ssif_bmc->state == SSIF_READY || ssif_bmc->state == SSIF_RES_SENDING) { - dev_warn(&ssif_bmc->client->dev, + dev_dbg(&ssif_bmc->client->dev, "Warn: %s unexpected WRITE RECEIVED in state=%s\n", __func__, state_to_string(ssif_bmc->state)); ssif_bmc->state = SSIF_ABORTING; @@ -698,7 +712,7 @@ static void on_write_received_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) } else if (ssif_bmc->state == SSIF_SMBUS_CMD) { if (!supported_write_cmd(ssif_bmc->part_buf.smbus_cmd)) { - dev_warn(&ssif_bmc->client->dev, "Warn: Unknown SMBus write command=0x%x", + dev_dbg(&ssif_bmc->client->dev, "Warn: Unknown SMBus write command=0x%x", ssif_bmc->part_buf.smbus_cmd); ssif_bmc->aborting = true; } @@ -707,6 +721,11 @@ static void on_write_received_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) ssif_bmc->state = SSIF_ABORTING; else ssif_bmc->state = SSIF_REQ_RECVING; + } else if (ssif_bmc->state == SSIF_ABORTING) { + if (supported_write_start_cmd(*val)) { + ssif_bmc->state = SSIF_SMBUS_CMD; + ssif_bmc->aborting = false; + } } /* This is response sending state */ @@ -722,7 +741,7 @@ static void on_stop_event(struct ssif_bmc_ctx *ssif_bmc, u8 *val) ssif_bmc->state == SSIF_START || ssif_bmc->state == SSIF_SMBUS_CMD || ssif_bmc->state == SSIF_ABORTING) { - dev_warn(&ssif_bmc->client->dev, + dev_dbg(&ssif_bmc->client->dev, "Warn: %s unexpected SLAVE STOP in state=%s\n", __func__, state_to_string(ssif_bmc->state)); ssif_bmc->state = SSIF_READY; @@ -789,7 +808,7 @@ static int ssif_bmc_cb(struct i2c_client *client, enum i2c_slave_event event, u8 break; default: - dev_warn(&ssif_bmc->client->dev, "Warn: Unknown i2c slave event\n"); + dev_dbg(&ssif_bmc->client->dev, "Warn: Unknown i2c slave event\n"); break; } @@ -843,6 +862,7 @@ static void ssif_bmc_remove(struct i2c_client *client) { struct ssif_bmc_ctx *ssif_bmc = i2c_get_clientdata(client); + timer_delete_sync(&ssif_bmc->response_timer); i2c_slave_unregister(client); misc_deregister(&ssif_bmc->miscdev); } @@ -869,6 +889,373 @@ static struct i2c_driver ssif_bmc_driver = { .id_table = ssif_bmc_id, }; +#if IS_ENABLED(CONFIG_SSIF_IPMI_BMC_KUNIT_TEST) +struct ssif_bmc_test_ctx { + struct ssif_bmc_ctx ssif_bmc; + struct i2c_client client; + struct i2c_adapter adapter; + struct i2c_algorithm algo; +}; + +static int ssif_bmc_test_init(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx; + + test_ctx = kunit_kzalloc(test, sizeof(*test_ctx), GFP_KERNEL); + if (!test_ctx) + return -ENOMEM; + + test_ctx->adapter.algo = &test_ctx->algo; + test_ctx->client.addr = 0x20; + test_ctx->client.adapter = &test_ctx->adapter; + + spin_lock_init(&test_ctx->ssif_bmc.lock); + init_waitqueue_head(&test_ctx->ssif_bmc.wait_queue); + test_ctx->ssif_bmc.client = &test_ctx->client; + i2c_set_clientdata(&test_ctx->client, &test_ctx->ssif_bmc); + + test->priv = test_ctx; + + return 0; +} + +static void ssif_bmc_test_exit(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + + if (test_ctx->ssif_bmc.response_timer_inited) + timer_delete_sync(&test_ctx->ssif_bmc.response_timer); +} + +static int ssif_bmc_test_run_event_val(struct ssif_bmc_test_ctx *test_ctx, + enum i2c_slave_event event, + u8 *value) +{ + return ssif_bmc_cb(&test_ctx->client, event, value); +} + +static int ssif_bmc_test_run_event(struct ssif_bmc_test_ctx *test_ctx, + enum i2c_slave_event event, u8 value) +{ + return ssif_bmc_test_run_event_val(test_ctx, event, &value); +} + +static void ssif_bmc_test_singlepart_req(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)); + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE); + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 2); + + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0xaa); + + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0x55); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), -EBUSY); + + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_READY); + KUNIT_EXPECT_TRUE(test, ssif_bmc->request_available); + KUNIT_EXPECT_TRUE(test, ssif_bmc->busy); + KUNIT_EXPECT_FALSE(test, ssif_bmc->aborting); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.len, 2); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.payload[0], 0xaa); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.payload[1], 0x55); + KUNIT_EXPECT_TRUE(test, ssif_bmc->response_timer_inited); +} + +static void ssif_bmc_test_restart_write_without_stop(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 2), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0xde), 0); + + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_REQ_RECVING); + + /* Write transaction, without stop, and new request coming */ + ssif_bmc_test_singlepart_req(test); +} + + +static void ssif_bmc_test_restart_after_invalid_command(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0xff), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 1), 0); + + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_ABORTING); + KUNIT_EXPECT_TRUE(test, ssif_bmc->aborting); + + /* After An Invalid Command, expect could handle new request */ + ssif_bmc_test_singlepart_req(test); +} + +static void ssif_bmc_test_singlepart_read_response_completion(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + u8 value; + + ssif_bmc->state = SSIF_SMBUS_CMD; + ssif_bmc->part_buf.smbus_cmd = SSIF_IPMI_SINGLEPART_READ; + ssif_bmc->response.len = 2; + ssif_bmc->response.payload[0] = 0x11; + ssif_bmc->response.payload[1] = 0x22; + ssif_bmc->response_in_progress = true; + ssif_bmc->is_singlepart_read = true; + ssif_bmc->pec_support = true; + + value = 0; + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_REQUESTED, + &value), 0); + KUNIT_EXPECT_EQ(test, value, 2); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_RES_SENDING); + + value = 0; + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_PROCESSED, + &value), 0); + KUNIT_EXPECT_EQ(test, value, 0x11); + + value = 0; + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_PROCESSED, + &value), 0); + KUNIT_EXPECT_EQ(test, value, 0x22); + + value = 0; + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_PROCESSED, + &value), 0); + KUNIT_EXPECT_EQ(test, value, ssif_bmc->part_buf.pec); + + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_READY); + KUNIT_EXPECT_FALSE(test, ssif_bmc->response_in_progress); + KUNIT_EXPECT_EQ(test, ssif_bmc->response.len, 0); +} + +static void ssif_bmc_test_stop_during_start_discards_partial_request(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_START); + + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_READY); + KUNIT_EXPECT_FALSE(test, ssif_bmc->request_available); + KUNIT_EXPECT_EQ(test, ssif_bmc->msg_idx, 0); + + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 1), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0x77), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), -EBUSY); + + KUNIT_EXPECT_TRUE(test, ssif_bmc->request_available); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.len, 1); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.payload[0], 0x77); +} + +static void ssif_bmc_test_read_interrupts_partial_write(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + u8 value = 0xff; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 2), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0xab), 0); + + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_REQ_RECVING); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_REQUESTED, + &value), 0); + KUNIT_EXPECT_EQ(test, value, 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_ABORTING); + + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_READY); + KUNIT_EXPECT_FALSE(test, ssif_bmc->request_available); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.len, 0); + + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 1), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0xcd), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), -EBUSY); + + KUNIT_EXPECT_TRUE(test, ssif_bmc->request_available); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.len, 1); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.payload[0], 0xcd); +} + +static void ssif_bmc_test_write_interrupts_response_send(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + u8 value = 0; + + ssif_bmc->state = SSIF_SMBUS_CMD; + ssif_bmc->part_buf.smbus_cmd = SSIF_IPMI_SINGLEPART_READ; + ssif_bmc->response.len = 1; + ssif_bmc->response.payload[0] = 0x66; + ssif_bmc->response_in_progress = true; + ssif_bmc->is_singlepart_read = true; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_REQUESTED, + &value), 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_RES_SENDING); + + /* READ_REQUESTED transaction */ + ssif_bmc_test_singlepart_req(test); +} + +static void ssif_bmc_test_write_interrupts_response_sending(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + u8 value = 0; + + ssif_bmc->state = SSIF_SMBUS_CMD; + ssif_bmc->part_buf.smbus_cmd = SSIF_IPMI_SINGLEPART_READ; + ssif_bmc->response.len = 1; + ssif_bmc->response.payload[0] = 0x66; + ssif_bmc->response_in_progress = true; + ssif_bmc->is_singlepart_read = true; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_REQUESTED, + &value), 0); + KUNIT_EXPECT_EQ(test, ssif_bmc->state, SSIF_RES_SENDING); + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event_val(test_ctx, I2C_SLAVE_READ_PROCESSED, + &value), 0); + KUNIT_EXPECT_EQ(test, value, 0x66); + + /* READ_REQUESTED transaction */ + ssif_bmc_test_singlepart_req(test); +} + +static void ssif_bmc_test_timeout_interrupt_allows_retry(struct kunit *test) +{ + struct ssif_bmc_test_ctx *test_ctx = test->priv; + struct ssif_bmc_ctx *ssif_bmc = &test_ctx->ssif_bmc; + + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 1), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0x21), 0); + KUNIT_ASSERT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), -EBUSY); + + KUNIT_ASSERT_TRUE(test, timer_pending(&ssif_bmc->response_timer)); + timer_delete_sync(&ssif_bmc->response_timer); + response_timeout(&ssif_bmc->response_timer); + + KUNIT_EXPECT_FALSE(test, ssif_bmc->busy); + KUNIT_EXPECT_TRUE(test, ssif_bmc->aborting); + KUNIT_EXPECT_FALSE(test, ssif_bmc->response_timer_inited); + + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_REQUESTED, + GET_8BIT_ADDR(test_ctx->client.addr)), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, + SSIF_IPMI_SINGLEPART_WRITE), 0); + KUNIT_EXPECT_FALSE(test, ssif_bmc->aborting); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 1), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_WRITE_RECEIVED, 0x22), 0); + KUNIT_EXPECT_EQ(test, + ssif_bmc_test_run_event(test_ctx, I2C_SLAVE_STOP, 0), -EBUSY); + + KUNIT_EXPECT_TRUE(test, ssif_bmc->request_available); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.len, 1); + KUNIT_EXPECT_EQ(test, ssif_bmc->request.payload[0], 0x22); +} + +static struct kunit_case ssif_bmc_test_cases[] = { + KUNIT_CASE(ssif_bmc_test_singlepart_req), + KUNIT_CASE(ssif_bmc_test_restart_write_without_stop), + KUNIT_CASE(ssif_bmc_test_restart_after_invalid_command), + KUNIT_CASE(ssif_bmc_test_singlepart_read_response_completion), + KUNIT_CASE(ssif_bmc_test_stop_during_start_discards_partial_request), + KUNIT_CASE(ssif_bmc_test_read_interrupts_partial_write), + KUNIT_CASE(ssif_bmc_test_write_interrupts_response_send), + KUNIT_CASE(ssif_bmc_test_write_interrupts_response_sending), + KUNIT_CASE(ssif_bmc_test_timeout_interrupt_allows_retry), + {} +}; + +static struct kunit_suite ssif_bmc_test_suite = { + .name = "ssif_bmc_test", + .init = ssif_bmc_test_init, + .exit = ssif_bmc_test_exit, + .test_cases = ssif_bmc_test_cases, +}; + +kunit_test_suite(ssif_bmc_test_suite); +#endif + module_i2c_driver(ssif_bmc_driver); MODULE_AUTHOR("Quan Nguyen "); diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile index a639a9499972..ce7213818d3c 100644 --- a/drivers/cxl/core/Makefile +++ b/drivers/cxl/core/Makefile @@ -15,7 +15,7 @@ cxl_core-y += hdm.o cxl_core-y += pmu.o cxl_core-y += cdat.o cxl_core-$(CONFIG_TRACING) += trace.o -cxl_core-$(CONFIG_CXL_REGION) += region.o +cxl_core-$(CONFIG_CXL_REGION) += region.o region_pmem.o region_dax.o cxl_core-$(CONFIG_CXL_MCE) += mce.o cxl_core-$(CONFIG_CXL_FEATURES) += features.o cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index 5b0570df0fd9..82ca3a476708 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -50,6 +50,8 @@ int cxl_get_poison_by_endpoint(struct cxl_port *port); struct cxl_region *cxl_dpa_to_region(const struct cxl_memdev *cxlmd, u64 dpa); u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd, u64 dpa); +int devm_cxl_add_dax_region(struct cxl_region *cxlr); +int devm_cxl_add_pmem_region(struct cxl_region *cxlr); #else static inline u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, @@ -224,4 +226,6 @@ int cxl_set_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, u16 *return_code); #endif +resource_size_t cxl_rcd_component_reg_phys(struct device *dev, + struct cxl_dport *dport); #endif /* __CXL_CORE_H__ */ diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c index cb5d5a047a9d..0c80b76a5f9b 100644 --- a/drivers/cxl/core/hdm.c +++ b/drivers/cxl/core/hdm.c @@ -170,7 +170,7 @@ static struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port, } parse_hdm_decoder_caps(cxlhdm); - if (cxlhdm->decoder_count == 0) { + if (cxlhdm->decoder_count < 0) { dev_err(dev, "Spec violation. Caps invalid\n"); return ERR_PTR(-ENXIO); } diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 12386d912705..7c6c5b7450a5 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -893,7 +893,7 @@ out: } EXPORT_SYMBOL_NS_GPL(cxl_enumerate_cmds, "CXL"); -void cxl_event_trace_record(const struct cxl_memdev *cxlmd, +void cxl_event_trace_record(struct cxl_memdev *cxlmd, enum cxl_event_log_type type, enum cxl_event_type event_type, const uuid_t *uuid, union cxl_event *evt) @@ -920,6 +920,7 @@ void cxl_event_trace_record(const struct cxl_memdev *cxlmd, * translations. Take topology mutation locks and lookup * { HPA, REGION } from { DPA, MEMDEV } in the event record. */ + guard(device)(&cxlmd->dev); guard(rwsem_read)(&cxl_rwsem.region); guard(rwsem_read)(&cxl_rwsem.dpa); @@ -968,7 +969,7 @@ void cxl_event_trace_record(const struct cxl_memdev *cxlmd, } EXPORT_SYMBOL_NS_GPL(cxl_event_trace_record, "CXL"); -static void __cxl_event_trace_record(const struct cxl_memdev *cxlmd, +static void __cxl_event_trace_record(struct cxl_memdev *cxlmd, enum cxl_event_log_type type, struct cxl_event_record_raw *record) { @@ -1521,23 +1522,21 @@ int cxl_mailbox_init(struct cxl_mailbox *cxl_mbox, struct device *host) } EXPORT_SYMBOL_NS_GPL(cxl_mailbox_init, "CXL"); -struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev) +struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev, u64 serial, + u16 dvsec) { struct cxl_memdev_state *mds; int rc; - mds = devm_kzalloc(dev, sizeof(*mds), GFP_KERNEL); + mds = devm_cxl_dev_state_create(dev, CXL_DEVTYPE_CLASSMEM, serial, + dvsec, struct cxl_memdev_state, cxlds, + true); if (!mds) { dev_err(dev, "No memory available\n"); return ERR_PTR(-ENOMEM); } mutex_init(&mds->event.log_lock); - mds->cxlds.dev = dev; - mds->cxlds.reg_map.host = dev; - mds->cxlds.cxl_mbox.host = dev; - mds->cxlds.reg_map.resource = CXL_RESOURCE_NONE; - mds->cxlds.type = CXL_DEVTYPE_CLASSMEM; rc = devm_cxl_register_mce_notifier(dev, &mds->mce_notifier); if (rc == -EOPNOTSUPP) diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index 273c22118d3d..80e65690eb77 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -204,6 +204,9 @@ bool cxl_memdev_has_poison_cmd(struct cxl_memdev *cxlmd, { struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds); + if (!mds) + return 0; + return test_bit(cmd, mds->poison.enabled_cmds); } @@ -656,6 +659,30 @@ static void detach_memdev(struct work_struct *work) static struct lock_class_key cxl_memdev_key; +struct cxl_dev_state *_devm_cxl_dev_state_create(struct device *dev, + enum cxl_devtype type, + u64 serial, u16 dvsec, + size_t size, bool has_mbox) +{ + struct cxl_dev_state *cxlds = devm_kzalloc(dev, size, GFP_KERNEL); + + if (!cxlds) + return NULL; + + cxlds->dev = dev; + cxlds->type = type; + cxlds->serial = serial; + cxlds->cxl_dvsec = dvsec; + cxlds->reg_map.host = dev; + cxlds->reg_map.resource = CXL_RESOURCE_NONE; + + if (has_mbox) + cxlds->cxl_mbox.host = dev; + + return cxlds; +} +EXPORT_SYMBOL_NS_GPL(_devm_cxl_dev_state_create, "CXL"); + static struct cxl_memdev *cxl_memdev_alloc(struct cxl_dev_state *cxlds, const struct file_operations *fops, const struct cxl_memdev_attach *attach) diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c index f96ce884a213..d1f487b3d809 100644 --- a/drivers/cxl/core/pci.c +++ b/drivers/cxl/core/pci.c @@ -696,6 +696,63 @@ bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port) } EXPORT_SYMBOL_NS_GPL(cxl_endpoint_decoder_reset_detected, "CXL"); +static int cxl_rcrb_get_comp_regs(struct pci_dev *pdev, + struct cxl_register_map *map, + struct cxl_dport *dport) +{ + resource_size_t component_reg_phys; + + *map = (struct cxl_register_map) { + .host = &pdev->dev, + .resource = CXL_RESOURCE_NONE, + }; + + component_reg_phys = cxl_rcd_component_reg_phys(&pdev->dev, dport); + if (component_reg_phys == CXL_RESOURCE_NONE) + return -ENXIO; + + map->resource = component_reg_phys; + map->reg_type = CXL_REGLOC_RBI_COMPONENT; + map->max_size = CXL_COMPONENT_REG_BLOCK_SIZE; + + return 0; +} + +int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, + struct cxl_register_map *map) +{ + int rc; + + rc = cxl_find_regblock(pdev, type, map); + + /* + * If the Register Locator DVSEC does not exist, check if it + * is an RCH and try to extract the Component Registers from + * an RCRB. + */ + if (rc && type == CXL_REGLOC_RBI_COMPONENT && is_cxl_restricted(pdev)) { + struct cxl_dport *dport; + struct cxl_port *port __free(put_cxl_port) = + cxl_pci_find_port(pdev, &dport); + if (!port) + return -EPROBE_DEFER; + + rc = cxl_rcrb_get_comp_regs(pdev, map, dport); + if (rc) + return rc; + + rc = cxl_dport_map_rcd_linkcap(pdev, dport); + if (rc) + return rc; + + } else if (rc) { + return rc; + } + + return cxl_setup_regs(map); +} +EXPORT_SYMBOL_NS_GPL(cxl_pci_setup_regs, "CXL"); + int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c) { int speed, bw; diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c index c37ae0b28bbb..e50dc716d4e8 100644 --- a/drivers/cxl/core/region.c +++ b/drivers/cxl/core/region.c @@ -485,22 +485,14 @@ static ssize_t interleave_ways_show(struct device *dev, static const struct attribute_group *get_cxl_region_target_group(void); -static ssize_t interleave_ways_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) +static int set_interleave_ways(struct cxl_region *cxlr, int val) { - struct cxl_region *cxlr = to_cxl_region(dev); struct cxl_root_decoder *cxlrd = cxlr->cxlrd; struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld; struct cxl_region_params *p = &cxlr->params; - unsigned int val, save; - int rc; + int save, rc; u8 iw; - rc = kstrtouint(buf, 0, &val); - if (rc) - return rc; - rc = ways_to_eiw(val, &iw); if (rc) return rc; @@ -515,9 +507,7 @@ static ssize_t interleave_ways_store(struct device *dev, return -EINVAL; } - ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region); - if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem))) - return rc; + lockdep_assert_held_write(&cxl_rwsem.region); if (p->state >= CXL_CONFIG_INTERLEAVE_ACTIVE) return -EBUSY; @@ -525,10 +515,31 @@ static ssize_t interleave_ways_store(struct device *dev, save = p->interleave_ways; p->interleave_ways = val; rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_target_group()); - if (rc) { + if (rc) p->interleave_ways = save; + + return rc; +} + +static ssize_t interleave_ways_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct cxl_region *cxlr = to_cxl_region(dev); + int val; + int rc; + + rc = kstrtoint(buf, 0, &val); + if (rc) + return rc; + + ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region); + if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem))) + return rc; + + rc = set_interleave_ways(cxlr, val); + if (rc) return rc; - } return len; } @@ -548,21 +559,14 @@ static ssize_t interleave_granularity_show(struct device *dev, return sysfs_emit(buf, "%d\n", p->interleave_granularity); } -static ssize_t interleave_granularity_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) +static int set_interleave_granularity(struct cxl_region *cxlr, int val) { - struct cxl_region *cxlr = to_cxl_region(dev); struct cxl_root_decoder *cxlrd = cxlr->cxlrd; struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld; struct cxl_region_params *p = &cxlr->params; - int rc, val; + int rc; u16 ig; - rc = kstrtoint(buf, 0, &val); - if (rc) - return rc; - rc = granularity_to_eig(val, &ig); if (rc) return rc; @@ -578,14 +582,33 @@ static ssize_t interleave_granularity_store(struct device *dev, if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity) return -EINVAL; - ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region); - if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem))) - return rc; + lockdep_assert_held_write(&cxl_rwsem.region); if (p->state >= CXL_CONFIG_INTERLEAVE_ACTIVE) return -EBUSY; p->interleave_granularity = val; + return 0; +} + +static ssize_t interleave_granularity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct cxl_region *cxlr = to_cxl_region(dev); + int rc, val; + + rc = kstrtoint(buf, 0, &val); + if (rc) + return rc; + + ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region); + if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem))) + return rc; + + rc = set_interleave_granularity(cxlr, val); + if (rc) + return rc; return len; } @@ -767,6 +790,22 @@ static ssize_t extended_linear_cache_size_show(struct device *dev, } static DEVICE_ATTR_RO(extended_linear_cache_size); +static ssize_t locked_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cxl_region *cxlr = to_cxl_region(dev); + int rc; + + ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region); + if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem))) + return rc; + + rc = test_bit(CXL_REGION_F_LOCK, &cxlr->flags); + return sysfs_emit(buf, "%d\n", rc); +} +static DEVICE_ATTR_RO(locked); + static struct attribute *cxl_region_attrs[] = { &dev_attr_uuid.attr, &dev_attr_commit.attr, @@ -776,6 +815,7 @@ static struct attribute *cxl_region_attrs[] = { &dev_attr_size.attr, &dev_attr_mode.attr, &dev_attr_extended_linear_cache_size.attr, + &dev_attr_locked.attr, NULL, }; @@ -1063,6 +1103,14 @@ static int cxl_rr_ep_add(struct cxl_region_ref *cxl_rr, if (!cxld->region) { cxld->region = cxlr; + + /* + * Now that cxld->region is set the intermediate staging state + * can be cleared. + */ + if (cxld == &cxled->cxld && + cxled->state == CXL_DECODER_STATE_AUTO_STAGED) + cxled->state = CXL_DECODER_STATE_AUTO; get_device(&cxlr->dev); } @@ -1804,6 +1852,7 @@ static int cxl_region_attach_auto(struct cxl_region *cxlr, pos = p->nr_targets; p->targets[pos] = cxled; cxled->pos = pos; + cxled->state = CXL_DECODER_STATE_AUTO_STAGED; p->nr_targets++; return 0; @@ -2153,6 +2202,47 @@ static int cxl_region_attach(struct cxl_region *cxlr, return 0; } +static int cxl_region_by_target(struct device *dev, const void *data) +{ + const struct cxl_endpoint_decoder *cxled = data; + struct cxl_region_params *p; + struct cxl_region *cxlr; + + if (!is_cxl_region(dev)) + return 0; + + cxlr = to_cxl_region(dev); + p = &cxlr->params; + return p->targets[cxled->pos] == cxled; +} + +/* + * When an auto-region fails to assemble the decoder may be listed as a target, + * but not fully attached. + */ +static void cxl_cancel_auto_attach(struct cxl_endpoint_decoder *cxled) +{ + struct cxl_region_params *p; + struct cxl_region *cxlr; + int pos = cxled->pos; + + if (cxled->state != CXL_DECODER_STATE_AUTO_STAGED) + return; + + struct device *dev __free(put_device) = + bus_find_device(&cxl_bus_type, NULL, cxled, cxl_region_by_target); + if (!dev) + return; + + cxlr = to_cxl_region(dev); + p = &cxlr->params; + + p->nr_targets--; + cxled->state = CXL_DECODER_STATE_AUTO; + cxled->pos = -1; + p->targets[pos] = NULL; +} + static struct cxl_region * __cxl_decoder_detach(struct cxl_region *cxlr, struct cxl_endpoint_decoder *cxled, int pos, @@ -2176,8 +2266,10 @@ __cxl_decoder_detach(struct cxl_region *cxlr, cxled = p->targets[pos]; } else { cxlr = cxled->cxld.region; - if (!cxlr) + if (!cxlr) { + cxl_cancel_auto_attach(cxled); return NULL; + } p = &cxlr->params; } @@ -2650,7 +2742,8 @@ static ssize_t create_ram_region_show(struct device *dev, } static struct cxl_region *__create_region(struct cxl_root_decoder *cxlrd, - enum cxl_partition_mode mode, int id) + enum cxl_partition_mode mode, int id, + enum cxl_decoder_type target_type) { int rc; @@ -2672,7 +2765,7 @@ static struct cxl_region *__create_region(struct cxl_root_decoder *cxlrd, return ERR_PTR(-EBUSY); } - return devm_cxl_add_region(cxlrd, id, mode, CXL_DECODER_HOSTONLYMEM); + return devm_cxl_add_region(cxlrd, id, mode, target_type); } static ssize_t create_region_store(struct device *dev, const char *buf, @@ -2686,7 +2779,7 @@ static ssize_t create_region_store(struct device *dev, const char *buf, if (rc != 1) return -EINVAL; - cxlr = __create_region(cxlrd, mode, id); + cxlr = __create_region(cxlrd, mode, id, CXL_DECODER_HOSTONLYMEM); if (IS_ERR(cxlr)) return PTR_ERR(cxlr); @@ -2757,46 +2850,6 @@ static ssize_t delete_region_store(struct device *dev, } DEVICE_ATTR_WO(delete_region); -static void cxl_pmem_region_release(struct device *dev) -{ - struct cxl_pmem_region *cxlr_pmem = to_cxl_pmem_region(dev); - int i; - - for (i = 0; i < cxlr_pmem->nr_mappings; i++) { - struct cxl_memdev *cxlmd = cxlr_pmem->mapping[i].cxlmd; - - put_device(&cxlmd->dev); - } - - kfree(cxlr_pmem); -} - -static const struct attribute_group *cxl_pmem_region_attribute_groups[] = { - &cxl_base_attribute_group, - NULL, -}; - -const struct device_type cxl_pmem_region_type = { - .name = "cxl_pmem_region", - .release = cxl_pmem_region_release, - .groups = cxl_pmem_region_attribute_groups, -}; - -bool is_cxl_pmem_region(struct device *dev) -{ - return dev->type == &cxl_pmem_region_type; -} -EXPORT_SYMBOL_NS_GPL(is_cxl_pmem_region, "CXL"); - -struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev) -{ - if (dev_WARN_ONCE(dev, !is_cxl_pmem_region(dev), - "not a cxl_pmem_region device\n")) - return NULL; - return container_of(dev, struct cxl_pmem_region, dev); -} -EXPORT_SYMBOL_NS_GPL(to_cxl_pmem_region, "CXL"); - struct cxl_poison_context { struct cxl_port *port; int part; @@ -2950,13 +3003,15 @@ static int __cxl_dpa_to_region(struct device *dev, void *arg) struct cxl_region *cxl_dpa_to_region(const struct cxl_memdev *cxlmd, u64 dpa) { struct cxl_dpa_to_region_context ctx; - struct cxl_port *port; + struct cxl_port *port = cxlmd->endpoint; + + if (!cxlmd->dev.driver) + return NULL; ctx = (struct cxl_dpa_to_region_context) { .dpa = dpa, }; - port = cxlmd->endpoint; - if (port && is_cxl_endpoint(port) && cxl_num_decoders_committed(port)) + if (cxl_num_decoders_committed(port)) device_for_each_child(&port->dev, &ctx, __cxl_dpa_to_region); return ctx.cxlr; @@ -3450,249 +3505,6 @@ static int region_offset_to_dpa_result(struct cxl_region *cxlr, u64 offset, return -ENXIO; } -static struct lock_class_key cxl_pmem_region_key; - -static int cxl_pmem_region_alloc(struct cxl_region *cxlr) -{ - struct cxl_region_params *p = &cxlr->params; - struct cxl_nvdimm_bridge *cxl_nvb; - struct device *dev; - int i; - - guard(rwsem_read)(&cxl_rwsem.region); - if (p->state != CXL_CONFIG_COMMIT) - return -ENXIO; - - struct cxl_pmem_region *cxlr_pmem __free(kfree) = - kzalloc_flex(*cxlr_pmem, mapping, p->nr_targets); - if (!cxlr_pmem) - return -ENOMEM; - - cxlr_pmem->hpa_range.start = p->res->start; - cxlr_pmem->hpa_range.end = p->res->end; - - /* Snapshot the region configuration underneath the cxl_rwsem.region */ - cxlr_pmem->nr_mappings = p->nr_targets; - for (i = 0; i < p->nr_targets; i++) { - struct cxl_endpoint_decoder *cxled = p->targets[i]; - struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); - struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i]; - - /* - * Regions never span CXL root devices, so by definition the - * bridge for one device is the same for all. - */ - if (i == 0) { - cxl_nvb = cxl_find_nvdimm_bridge(cxlmd->endpoint); - if (!cxl_nvb) - return -ENODEV; - cxlr->cxl_nvb = cxl_nvb; - } - m->cxlmd = cxlmd; - get_device(&cxlmd->dev); - m->start = cxled->dpa_res->start; - m->size = resource_size(cxled->dpa_res); - m->position = i; - } - - dev = &cxlr_pmem->dev; - device_initialize(dev); - lockdep_set_class(&dev->mutex, &cxl_pmem_region_key); - device_set_pm_not_required(dev); - dev->parent = &cxlr->dev; - dev->bus = &cxl_bus_type; - dev->type = &cxl_pmem_region_type; - cxlr_pmem->cxlr = cxlr; - cxlr->cxlr_pmem = no_free_ptr(cxlr_pmem); - - return 0; -} - -static void cxl_dax_region_release(struct device *dev) -{ - struct cxl_dax_region *cxlr_dax = to_cxl_dax_region(dev); - - kfree(cxlr_dax); -} - -static const struct attribute_group *cxl_dax_region_attribute_groups[] = { - &cxl_base_attribute_group, - NULL, -}; - -const struct device_type cxl_dax_region_type = { - .name = "cxl_dax_region", - .release = cxl_dax_region_release, - .groups = cxl_dax_region_attribute_groups, -}; - -static bool is_cxl_dax_region(struct device *dev) -{ - return dev->type == &cxl_dax_region_type; -} - -struct cxl_dax_region *to_cxl_dax_region(struct device *dev) -{ - if (dev_WARN_ONCE(dev, !is_cxl_dax_region(dev), - "not a cxl_dax_region device\n")) - return NULL; - return container_of(dev, struct cxl_dax_region, dev); -} -EXPORT_SYMBOL_NS_GPL(to_cxl_dax_region, "CXL"); - -static struct lock_class_key cxl_dax_region_key; - -static struct cxl_dax_region *cxl_dax_region_alloc(struct cxl_region *cxlr) -{ - struct cxl_region_params *p = &cxlr->params; - struct cxl_dax_region *cxlr_dax; - struct device *dev; - - guard(rwsem_read)(&cxl_rwsem.region); - if (p->state != CXL_CONFIG_COMMIT) - return ERR_PTR(-ENXIO); - - cxlr_dax = kzalloc_obj(*cxlr_dax); - if (!cxlr_dax) - return ERR_PTR(-ENOMEM); - - cxlr_dax->hpa_range.start = p->res->start; - cxlr_dax->hpa_range.end = p->res->end; - - dev = &cxlr_dax->dev; - cxlr_dax->cxlr = cxlr; - device_initialize(dev); - lockdep_set_class(&dev->mutex, &cxl_dax_region_key); - device_set_pm_not_required(dev); - dev->parent = &cxlr->dev; - dev->bus = &cxl_bus_type; - dev->type = &cxl_dax_region_type; - - return cxlr_dax; -} - -static void cxlr_pmem_unregister(void *_cxlr_pmem) -{ - struct cxl_pmem_region *cxlr_pmem = _cxlr_pmem; - struct cxl_region *cxlr = cxlr_pmem->cxlr; - struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb; - - /* - * Either the bridge is in ->remove() context under the device_lock(), - * or cxlr_release_nvdimm() is cancelling the bridge's release action - * for @cxlr_pmem and doing it itself (while manually holding the bridge - * lock). - */ - device_lock_assert(&cxl_nvb->dev); - cxlr->cxlr_pmem = NULL; - cxlr_pmem->cxlr = NULL; - device_unregister(&cxlr_pmem->dev); -} - -static void cxlr_release_nvdimm(void *_cxlr) -{ - struct cxl_region *cxlr = _cxlr; - struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb; - - scoped_guard(device, &cxl_nvb->dev) { - if (cxlr->cxlr_pmem) - devm_release_action(&cxl_nvb->dev, cxlr_pmem_unregister, - cxlr->cxlr_pmem); - } - cxlr->cxl_nvb = NULL; - put_device(&cxl_nvb->dev); -} - -/** - * devm_cxl_add_pmem_region() - add a cxl_region-to-nd_region bridge - * @cxlr: parent CXL region for this pmem region bridge device - * - * Return: 0 on success negative error code on failure. - */ -static int devm_cxl_add_pmem_region(struct cxl_region *cxlr) -{ - struct cxl_pmem_region *cxlr_pmem; - struct cxl_nvdimm_bridge *cxl_nvb; - struct device *dev; - int rc; - - rc = cxl_pmem_region_alloc(cxlr); - if (rc) - return rc; - cxlr_pmem = cxlr->cxlr_pmem; - cxl_nvb = cxlr->cxl_nvb; - - dev = &cxlr_pmem->dev; - rc = dev_set_name(dev, "pmem_region%d", cxlr->id); - if (rc) - goto err; - - rc = device_add(dev); - if (rc) - goto err; - - dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent), - dev_name(dev)); - - scoped_guard(device, &cxl_nvb->dev) { - if (cxl_nvb->dev.driver) - rc = devm_add_action_or_reset(&cxl_nvb->dev, - cxlr_pmem_unregister, - cxlr_pmem); - else - rc = -ENXIO; - } - - if (rc) - goto err_bridge; - - /* @cxlr carries a reference on @cxl_nvb until cxlr_release_nvdimm */ - return devm_add_action_or_reset(&cxlr->dev, cxlr_release_nvdimm, cxlr); - -err: - put_device(dev); -err_bridge: - put_device(&cxl_nvb->dev); - cxlr->cxl_nvb = NULL; - return rc; -} - -static void cxlr_dax_unregister(void *_cxlr_dax) -{ - struct cxl_dax_region *cxlr_dax = _cxlr_dax; - - device_unregister(&cxlr_dax->dev); -} - -static int devm_cxl_add_dax_region(struct cxl_region *cxlr) -{ - struct cxl_dax_region *cxlr_dax; - struct device *dev; - int rc; - - cxlr_dax = cxl_dax_region_alloc(cxlr); - if (IS_ERR(cxlr_dax)) - return PTR_ERR(cxlr_dax); - - dev = &cxlr_dax->dev; - rc = dev_set_name(dev, "dax_region%d", cxlr->id); - if (rc) - goto err; - - rc = device_add(dev); - if (rc) - goto err; - - dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent), - dev_name(dev)); - - return devm_add_action_or_reset(&cxlr->dev, cxlr_dax_unregister, - cxlr_dax); -err: - put_device(dev); - return rc; -} - static int match_root_decoder(struct device *dev, const void *data) { const struct range *r1, *r2 = data; @@ -3904,7 +3716,8 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd, do { cxlr = __create_region(cxlrd, cxlds->part[part].mode, - atomic_read(&cxlrd->region_id)); + atomic_read(&cxlrd->region_id), + cxled->cxld.target_type); } while (IS_ERR(cxlr) && PTR_ERR(cxlr) == -EBUSY); if (IS_ERR(cxlr)) { @@ -4175,6 +3988,36 @@ static int cxl_region_setup_poison(struct cxl_region *cxlr) return devm_add_action_or_reset(dev, remove_debugfs, dentry); } +static int region_contains_resource(struct device *dev, const void *data) +{ + const struct resource *res = data; + struct cxl_region *cxlr; + struct cxl_region_params *p; + + if (!is_cxl_region(dev)) + return 0; + + cxlr = to_cxl_region(dev); + p = &cxlr->params; + + if (p->state != CXL_CONFIG_COMMIT) + return 0; + + if (!p->res) + return 0; + + return resource_contains(p->res, res) ? 1 : 0; +} + +bool cxl_region_contains_resource(const struct resource *res) +{ + guard(rwsem_read)(&cxl_rwsem.region); + struct device *dev __free(put_device) = bus_find_device( + &cxl_bus_type, NULL, res, region_contains_resource); + return !!dev; +} +EXPORT_SYMBOL_FOR_MODULES(cxl_region_contains_resource, "dax_hmem"); + static int cxl_region_can_probe(struct cxl_region *cxlr) { struct cxl_region_params *p = &cxlr->params; diff --git a/drivers/cxl/core/region_dax.c b/drivers/cxl/core/region_dax.c new file mode 100644 index 000000000000..de04f78f6ad8 --- /dev/null +++ b/drivers/cxl/core/region_dax.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2022 Intel Corporation. All rights reserved. + * Copyright(c) 2026 Meta Technologies Inc. All rights reserved. + */ +#include +#include +#include +#include +#include "core.h" + +static void cxl_dax_region_release(struct device *dev) +{ + struct cxl_dax_region *cxlr_dax = to_cxl_dax_region(dev); + + kfree(cxlr_dax); +} + +static const struct attribute_group *cxl_dax_region_attribute_groups[] = { + &cxl_base_attribute_group, + NULL +}; + +const struct device_type cxl_dax_region_type = { + .name = "cxl_dax_region", + .release = cxl_dax_region_release, + .groups = cxl_dax_region_attribute_groups, +}; + +static bool is_cxl_dax_region(struct device *dev) +{ + return dev->type == &cxl_dax_region_type; +} + +struct cxl_dax_region *to_cxl_dax_region(struct device *dev) +{ + if (dev_WARN_ONCE(dev, !is_cxl_dax_region(dev), + "not a cxl_dax_region device\n")) + return NULL; + return container_of(dev, struct cxl_dax_region, dev); +} +EXPORT_SYMBOL_NS_GPL(to_cxl_dax_region, "CXL"); + +static struct lock_class_key cxl_dax_region_key; + +static struct cxl_dax_region *cxl_dax_region_alloc(struct cxl_region *cxlr) +{ + struct cxl_region_params *p = &cxlr->params; + struct cxl_dax_region *cxlr_dax; + struct device *dev; + + guard(rwsem_read)(&cxl_rwsem.region); + if (p->state != CXL_CONFIG_COMMIT) + return ERR_PTR(-ENXIO); + + cxlr_dax = kzalloc_obj(*cxlr_dax); + if (!cxlr_dax) + return ERR_PTR(-ENOMEM); + + cxlr_dax->hpa_range.start = p->res->start; + cxlr_dax->hpa_range.end = p->res->end; + + dev = &cxlr_dax->dev; + cxlr_dax->cxlr = cxlr; + device_initialize(dev); + lockdep_set_class(&dev->mutex, &cxl_dax_region_key); + device_set_pm_not_required(dev); + dev->parent = &cxlr->dev; + dev->bus = &cxl_bus_type; + dev->type = &cxl_dax_region_type; + + return cxlr_dax; +} + +static void cxlr_dax_unregister(void *_cxlr_dax) +{ + struct cxl_dax_region *cxlr_dax = _cxlr_dax; + + device_unregister(&cxlr_dax->dev); +} + +int devm_cxl_add_dax_region(struct cxl_region *cxlr) +{ + struct device *dev; + int rc; + + struct cxl_dax_region *cxlr_dax __free(put_cxl_dax_region) = + cxl_dax_region_alloc(cxlr); + if (IS_ERR(cxlr_dax)) + return PTR_ERR(cxlr_dax); + + dev = &cxlr_dax->dev; + rc = dev_set_name(dev, "dax_region%d", cxlr->id); + if (rc) + return rc; + + rc = device_add(dev); + if (rc) + return rc; + + dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent), + dev_name(dev)); + + return devm_add_action_or_reset(&cxlr->dev, cxlr_dax_unregister, + no_free_ptr(cxlr_dax)); +} diff --git a/drivers/cxl/core/region_pmem.c b/drivers/cxl/core/region_pmem.c new file mode 100644 index 000000000000..23d97e3d78b6 --- /dev/null +++ b/drivers/cxl/core/region_pmem.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2022 Intel Corporation. All rights reserved. */ +#include +#include +#include +#include +#include "core.h" + +static void cxl_pmem_region_release(struct device *dev) +{ + struct cxl_pmem_region *cxlr_pmem = to_cxl_pmem_region(dev); + int i; + + for (i = 0; i < cxlr_pmem->nr_mappings; i++) { + struct cxl_memdev *cxlmd = cxlr_pmem->mapping[i].cxlmd; + + put_device(&cxlmd->dev); + } + + kfree(cxlr_pmem); +} + +static const struct attribute_group *cxl_pmem_region_attribute_groups[] = { + &cxl_base_attribute_group, + NULL +}; + +const struct device_type cxl_pmem_region_type = { + .name = "cxl_pmem_region", + .release = cxl_pmem_region_release, + .groups = cxl_pmem_region_attribute_groups, +}; + +bool is_cxl_pmem_region(struct device *dev) +{ + return dev->type == &cxl_pmem_region_type; +} +EXPORT_SYMBOL_NS_GPL(is_cxl_pmem_region, "CXL"); + +struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev) +{ + if (dev_WARN_ONCE(dev, !is_cxl_pmem_region(dev), + "not a cxl_pmem_region device\n")) + return NULL; + return container_of(dev, struct cxl_pmem_region, dev); +} +EXPORT_SYMBOL_NS_GPL(to_cxl_pmem_region, "CXL"); + +static struct lock_class_key cxl_pmem_region_key; + +static int cxl_pmem_region_alloc(struct cxl_region *cxlr) +{ + struct cxl_region_params *p = &cxlr->params; + struct cxl_nvdimm_bridge *cxl_nvb; + struct device *dev; + int i; + + guard(rwsem_read)(&cxl_rwsem.region); + if (p->state != CXL_CONFIG_COMMIT) + return -ENXIO; + + struct cxl_pmem_region *cxlr_pmem __free(kfree) = + kzalloc_flex(*cxlr_pmem, mapping, p->nr_targets); + if (!cxlr_pmem) + return -ENOMEM; + + cxlr_pmem->hpa_range.start = p->res->start; + cxlr_pmem->hpa_range.end = p->res->end; + + /* Snapshot the region configuration underneath the cxl_rwsem.region */ + cxlr_pmem->nr_mappings = p->nr_targets; + for (i = 0; i < p->nr_targets; i++) { + struct cxl_endpoint_decoder *cxled = p->targets[i]; + struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); + struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i]; + + /* + * Regions never span CXL root devices, so by definition the + * bridge for one device is the same for all. + */ + if (i == 0) { + cxl_nvb = cxl_find_nvdimm_bridge(cxlmd->endpoint); + if (!cxl_nvb) + return -ENODEV; + cxlr->cxl_nvb = cxl_nvb; + } + m->cxlmd = cxlmd; + get_device(&cxlmd->dev); + m->start = cxled->dpa_res->start; + m->size = resource_size(cxled->dpa_res); + m->position = i; + } + + dev = &cxlr_pmem->dev; + device_initialize(dev); + lockdep_set_class(&dev->mutex, &cxl_pmem_region_key); + device_set_pm_not_required(dev); + dev->parent = &cxlr->dev; + dev->bus = &cxl_bus_type; + dev->type = &cxl_pmem_region_type; + cxlr_pmem->cxlr = cxlr; + cxlr->cxlr_pmem = no_free_ptr(cxlr_pmem); + + return 0; +} + +static void cxlr_pmem_unregister(void *_cxlr_pmem) +{ + struct cxl_pmem_region *cxlr_pmem = _cxlr_pmem; + struct cxl_region *cxlr = cxlr_pmem->cxlr; + struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb; + + /* + * Either the bridge is in ->remove() context under the device_lock(), + * or cxlr_release_nvdimm() is cancelling the bridge's release action + * for @cxlr_pmem and doing it itself (while manually holding the bridge + * lock). + */ + device_lock_assert(&cxl_nvb->dev); + cxlr->cxlr_pmem = NULL; + cxlr_pmem->cxlr = NULL; + device_unregister(&cxlr_pmem->dev); +} + +static void cxlr_release_nvdimm(void *_cxlr) +{ + struct cxl_region *cxlr = _cxlr; + struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb; + + scoped_guard(device, &cxl_nvb->dev) { + if (cxlr->cxlr_pmem) + devm_release_action(&cxl_nvb->dev, cxlr_pmem_unregister, + cxlr->cxlr_pmem); + } + cxlr->cxl_nvb = NULL; + put_device(&cxl_nvb->dev); +} + +/** + * devm_cxl_add_pmem_region() - add a cxl_region-to-nd_region bridge + * @cxlr: parent CXL region for this pmem region bridge device + * + * Return: 0 on success negative error code on failure. + */ +int devm_cxl_add_pmem_region(struct cxl_region *cxlr) +{ + struct cxl_pmem_region *cxlr_pmem; + struct cxl_nvdimm_bridge *cxl_nvb; + struct device *dev; + int rc; + + rc = cxl_pmem_region_alloc(cxlr); + if (rc) + return rc; + cxlr_pmem = cxlr->cxlr_pmem; + cxl_nvb = cxlr->cxl_nvb; + + dev = &cxlr_pmem->dev; + rc = dev_set_name(dev, "pmem_region%d", cxlr->id); + if (rc) + goto err; + + rc = device_add(dev); + if (rc) + goto err; + + dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent), + dev_name(dev)); + + scoped_guard(device, &cxl_nvb->dev) { + if (cxl_nvb->dev.driver) + rc = devm_add_action_or_reset(&cxl_nvb->dev, + cxlr_pmem_unregister, + cxlr_pmem); + else + rc = -ENXIO; + } + + if (rc) + goto err_bridge; + + /* @cxlr carries a reference on @cxl_nvb until cxlr_release_nvdimm */ + return devm_add_action_or_reset(&cxlr->dev, cxlr_release_nvdimm, cxlr); + +err: + put_device(dev); +err_bridge: + put_device(&cxl_nvb->dev); + cxlr->cxl_nvb = NULL; + return rc; +} diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c index a010b3214342..93710cf4f0a6 100644 --- a/drivers/cxl/core/regs.c +++ b/drivers/cxl/core/regs.c @@ -641,4 +641,3 @@ resource_size_t cxl_rcd_component_reg_phys(struct device *dev, return CXL_RESOURCE_NONE; return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM); } -EXPORT_SYMBOL_NS_GPL(cxl_rcd_component_reg_phys, "CXL"); diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h index 9b947286eb9b..1297594beaec 100644 --- a/drivers/cxl/cxl.h +++ b/drivers/cxl/cxl.h @@ -12,6 +12,7 @@ #include #include #include +#include extern const struct nvdimm_security_ops *cxl_security_ops; @@ -77,7 +78,16 @@ static inline int cxl_hdm_decoder_count(u32 cap_hdr) { int val = FIELD_GET(CXL_HDM_DECODER_COUNT_MASK, cap_hdr); - return val ? val * 2 : 1; + switch (val) { + case 0: + return 1; + case 1 ... 8: + return val * 2; + case 9 ... 12: + return (val - 4) * 4; + default: + return -ENXIO; + } } /* Encode defined in CXL 2.0 8.2.5.12.7 HDM Decoder Control Register */ @@ -201,97 +211,6 @@ static inline int ways_to_eiw(unsigned int ways, u8 *eiw) #define CXLDEV_MBOX_BG_CMD_COMMAND_VENDOR_MASK GENMASK_ULL(63, 48) #define CXLDEV_MBOX_PAYLOAD_OFFSET 0x20 -/* - * Using struct_group() allows for per register-block-type helper routines, - * without requiring block-type agnostic code to include the prefix. - */ -struct cxl_regs { - /* - * Common set of CXL Component register block base pointers - * @hdm_decoder: CXL 2.0 8.2.5.12 CXL HDM Decoder Capability Structure - * @ras: CXL 2.0 8.2.5.9 CXL RAS Capability Structure - */ - struct_group_tagged(cxl_component_regs, component, - void __iomem *hdm_decoder; - void __iomem *ras; - ); - /* - * Common set of CXL Device register block base pointers - * @status: CXL 2.0 8.2.8.3 Device Status Registers - * @mbox: CXL 2.0 8.2.8.4 Mailbox Registers - * @memdev: CXL 2.0 8.2.8.5 Memory Device Registers - */ - struct_group_tagged(cxl_device_regs, device_regs, - void __iomem *status, *mbox, *memdev; - ); - - struct_group_tagged(cxl_pmu_regs, pmu_regs, - void __iomem *pmu; - ); - - /* - * RCH downstream port specific RAS register - * @aer: CXL 3.0 8.2.1.1 RCH Downstream Port RCRB - */ - struct_group_tagged(cxl_rch_regs, rch_regs, - void __iomem *dport_aer; - ); - - /* - * RCD upstream port specific PCIe cap register - * @pcie_cap: CXL 3.0 8.2.1.2 RCD Upstream Port RCRB - */ - struct_group_tagged(cxl_rcd_regs, rcd_regs, - void __iomem *rcd_pcie_cap; - ); -}; - -struct cxl_reg_map { - bool valid; - int id; - unsigned long offset; - unsigned long size; -}; - -struct cxl_component_reg_map { - struct cxl_reg_map hdm_decoder; - struct cxl_reg_map ras; -}; - -struct cxl_device_reg_map { - struct cxl_reg_map status; - struct cxl_reg_map mbox; - struct cxl_reg_map memdev; -}; - -struct cxl_pmu_reg_map { - struct cxl_reg_map pmu; -}; - -/** - * struct cxl_register_map - DVSEC harvested register block mapping parameters - * @host: device for devm operations and logging - * @base: virtual base of the register-block-BAR + @block_offset - * @resource: physical resource base of the register block - * @max_size: maximum mapping size to perform register search - * @reg_type: see enum cxl_regloc_type - * @component_map: cxl_reg_map for component registers - * @device_map: cxl_reg_maps for device registers - * @pmu_map: cxl_reg_maps for CXL Performance Monitoring Units - */ -struct cxl_register_map { - struct device *host; - void __iomem *base; - resource_size_t resource; - resource_size_t max_size; - u8 reg_type; - union { - struct cxl_component_reg_map component_map; - struct cxl_device_reg_map device_map; - struct cxl_pmu_reg_map pmu_map; - }; -}; - void cxl_probe_component_regs(struct device *dev, void __iomem *base, struct cxl_component_reg_map *map); void cxl_probe_device_regs(struct device *dev, void __iomem *base, @@ -312,8 +231,6 @@ int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type, struct cxl_register_map *map); int cxl_setup_regs(struct cxl_register_map *map); struct cxl_dport; -resource_size_t cxl_rcd_component_reg_phys(struct device *dev, - struct cxl_dport *dport); int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport); #define CXL_RESOURCE_NONE ((resource_size_t) -1) @@ -333,6 +250,7 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport); #define CXL_DECODER_F_LOCK BIT(4) #define CXL_DECODER_F_ENABLE BIT(5) #define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6) +#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK) enum cxl_decoder_type { CXL_DECODER_DEVMEM = 2, @@ -378,12 +296,14 @@ struct cxl_decoder { }; /* - * Track whether this decoder is reserved for region autodiscovery, or - * free for userspace provisioning. + * Track whether this decoder is free for userspace provisioning, reserved for + * region autodiscovery, whether it is started connecting (awaiting other + * peers), or has completed auto assembly. */ enum cxl_decoder_state { CXL_DECODER_STATE_MANUAL, CXL_DECODER_STATE_AUTO, + CXL_DECODER_STATE_AUTO_STAGED, }; /** @@ -497,11 +417,6 @@ struct cxl_region_params { resource_size_t cache_size; }; -enum cxl_partition_mode { - CXL_PARTMODE_RAM, - CXL_PARTMODE_PMEM, -}; - /* * Indicate whether this region has been assembled by autodetection or * userspace assembly. Prevent endpoint decoders outside of automatic @@ -808,6 +723,7 @@ DEFINE_FREE(put_cxl_root, struct cxl_root *, if (_T) put_device(&_T->port.dev)) DEFINE_FREE(put_cxl_port, struct cxl_port *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->dev)) DEFINE_FREE(put_cxl_root_decoder, struct cxl_root_decoder *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->cxlsd.cxld.dev)) DEFINE_FREE(put_cxl_region, struct cxl_region *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->dev)) +DEFINE_FREE(put_cxl_dax_region, struct cxl_dax_region *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->dev)) int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd); void cxl_bus_rescan(void); @@ -939,6 +855,7 @@ struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev); int cxl_add_to_region(struct cxl_endpoint_decoder *cxled); struct cxl_dax_region *to_cxl_dax_region(struct device *dev); u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint, u64 spa); +bool cxl_region_contains_resource(const struct resource *res); #else static inline bool is_cxl_pmem_region(struct device *dev) { @@ -961,6 +878,10 @@ static inline u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint, { return 0; } +static inline bool cxl_region_contains_resource(const struct resource *res) +{ + return false; +} #endif void cxl_endpoint_parse_cdat(struct cxl_port *port); diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index e21d744d639b..776c50d1db51 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -113,8 +113,6 @@ int devm_cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled, resource_size_t base, resource_size_t len, resource_size_t skipped); -#define CXL_NR_PARTITIONS_MAX 2 - struct cxl_dpa_info { u64 size; struct cxl_dpa_part_info { @@ -373,87 +371,6 @@ struct cxl_security_state { struct kernfs_node *sanitize_node; }; -/* - * enum cxl_devtype - delineate type-2 from a generic type-3 device - * @CXL_DEVTYPE_DEVMEM - Vendor specific CXL Type-2 device implementing HDM-D or - * HDM-DB, no requirement that this device implements a - * mailbox, or other memory-device-standard manageability - * flows. - * @CXL_DEVTYPE_CLASSMEM - Common class definition of a CXL Type-3 device with - * HDM-H and class-mandatory memory device registers - */ -enum cxl_devtype { - CXL_DEVTYPE_DEVMEM, - CXL_DEVTYPE_CLASSMEM, -}; - -/** - * struct cxl_dpa_perf - DPA performance property entry - * @dpa_range: range for DPA address - * @coord: QoS performance data (i.e. latency, bandwidth) - * @cdat_coord: raw QoS performance data from CDAT - * @qos_class: QoS Class cookies - */ -struct cxl_dpa_perf { - struct range dpa_range; - struct access_coordinate coord[ACCESS_COORDINATE_MAX]; - struct access_coordinate cdat_coord[ACCESS_COORDINATE_MAX]; - int qos_class; -}; - -/** - * struct cxl_dpa_partition - DPA partition descriptor - * @res: shortcut to the partition in the DPA resource tree (cxlds->dpa_res) - * @perf: performance attributes of the partition from CDAT - * @mode: operation mode for the DPA capacity, e.g. ram, pmem, dynamic... - */ -struct cxl_dpa_partition { - struct resource res; - struct cxl_dpa_perf perf; - enum cxl_partition_mode mode; -}; - -/** - * struct cxl_dev_state - The driver device state - * - * cxl_dev_state represents the CXL driver/device state. It provides an - * interface to mailbox commands as well as some cached data about the device. - * Currently only memory devices are represented. - * - * @dev: The device associated with this CXL state - * @cxlmd: The device representing the CXL.mem capabilities of @dev - * @reg_map: component and ras register mapping parameters - * @regs: Class device "Device" registers - * @cxl_dvsec: Offset to the PCIe device DVSEC - * @rcd: operating in RCD mode (CXL 3.0 9.11.8 CXL Devices Attached to an RCH) - * @media_ready: Indicate whether the device media is usable - * @dpa_res: Overall DPA resource tree for the device - * @part: DPA partition array - * @nr_partitions: Number of DPA partitions - * @serial: PCIe Device Serial Number - * @type: Generic Memory Class device or Vendor Specific Memory device - * @cxl_mbox: CXL mailbox context - * @cxlfs: CXL features context - */ -struct cxl_dev_state { - struct device *dev; - struct cxl_memdev *cxlmd; - struct cxl_register_map reg_map; - struct cxl_device_regs regs; - int cxl_dvsec; - bool rcd; - bool media_ready; - struct resource dpa_res; - struct cxl_dpa_partition part[CXL_NR_PARTITIONS_MAX]; - unsigned int nr_partitions; - u64 serial; - enum cxl_devtype type; - struct cxl_mailbox cxl_mbox; -#ifdef CONFIG_CXL_FEATURES - struct cxl_features_state *cxlfs; -#endif -}; - static inline resource_size_t cxl_pmem_size(struct cxl_dev_state *cxlds) { /* @@ -858,13 +775,14 @@ int cxl_dev_state_identify(struct cxl_memdev_state *mds); int cxl_await_media_ready(struct cxl_dev_state *cxlds); int cxl_enumerate_cmds(struct cxl_memdev_state *mds); int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info); -struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev); +struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev, u64 serial, + u16 dvsec); void set_exclusive_cxl_commands(struct cxl_memdev_state *mds, unsigned long *cmds); void clear_exclusive_cxl_commands(struct cxl_memdev_state *mds, unsigned long *cmds); void cxl_mem_get_event_records(struct cxl_memdev_state *mds, u32 status); -void cxl_event_trace_record(const struct cxl_memdev *cxlmd, +void cxl_event_trace_record(struct cxl_memdev *cxlmd, enum cxl_event_log_type type, enum cxl_event_type event_type, const uuid_t *uuid, union cxl_event *evt); @@ -923,7 +841,7 @@ int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd); */ struct cxl_hdm { struct cxl_component_regs regs; - unsigned int decoder_count; + int decoder_count; unsigned int target_count; unsigned int interleave_mask; unsigned long iw_cap_mask; diff --git a/drivers/cxl/cxlpci.h b/drivers/cxl/cxlpci.h index 0cf64218aa16..b826eb53cf7b 100644 --- a/drivers/cxl/cxlpci.h +++ b/drivers/cxl/cxlpci.h @@ -74,6 +74,17 @@ static inline bool cxl_pci_flit_256(struct pci_dev *pdev) return lnksta2 & PCI_EXP_LNKSTA2_FLIT; } +/* + * Assume that the caller has already validated that @pdev has CXL + * capabilities, any RCiEP with CXL capabilities is treated as a + * Restricted CXL Device (RCD) and finds upstream port and endpoint + * registers in a Root Complex Register Block (RCRB). + */ +static inline bool is_cxl_restricted(struct pci_dev *pdev) +{ + return pci_pcie_type(pdev) == PCI_EXP_TYPE_RC_END; +} + struct cxl_dev_state; void read_cdat_data(struct cxl_port *port); @@ -101,4 +112,6 @@ static inline void devm_cxl_port_ras_setup(struct cxl_port *port) } #endif +int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, + struct cxl_register_map *map); #endif /* __CXL_PCI_H__ */ diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index fbb300a01830..bace662dc988 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -465,76 +465,6 @@ static int cxl_pci_setup_mailbox(struct cxl_memdev_state *mds, bool irq_avail) return 0; } -/* - * Assume that any RCIEP that emits the CXL memory expander class code - * is an RCD - */ -static bool is_cxl_restricted(struct pci_dev *pdev) -{ - return pci_pcie_type(pdev) == PCI_EXP_TYPE_RC_END; -} - -static int cxl_rcrb_get_comp_regs(struct pci_dev *pdev, - struct cxl_register_map *map, - struct cxl_dport *dport) -{ - resource_size_t component_reg_phys; - - *map = (struct cxl_register_map) { - .host = &pdev->dev, - .resource = CXL_RESOURCE_NONE, - }; - - struct cxl_port *port __free(put_cxl_port) = - cxl_pci_find_port(pdev, &dport); - if (!port) - return -EPROBE_DEFER; - - component_reg_phys = cxl_rcd_component_reg_phys(&pdev->dev, dport); - if (component_reg_phys == CXL_RESOURCE_NONE) - return -ENXIO; - - map->resource = component_reg_phys; - map->reg_type = CXL_REGLOC_RBI_COMPONENT; - map->max_size = CXL_COMPONENT_REG_BLOCK_SIZE; - - return 0; -} - -static int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, - struct cxl_register_map *map) -{ - int rc; - - rc = cxl_find_regblock(pdev, type, map); - - /* - * If the Register Locator DVSEC does not exist, check if it - * is an RCH and try to extract the Component Registers from - * an RCRB. - */ - if (rc && type == CXL_REGLOC_RBI_COMPONENT && is_cxl_restricted(pdev)) { - struct cxl_dport *dport; - struct cxl_port *port __free(put_cxl_port) = - cxl_pci_find_port(pdev, &dport); - if (!port) - return -EPROBE_DEFER; - - rc = cxl_rcrb_get_comp_regs(pdev, map, dport); - if (rc) - return rc; - - rc = cxl_dport_map_rcd_linkcap(pdev, dport); - if (rc) - return rc; - - } else if (rc) { - return rc; - } - - return cxl_setup_regs(map); -} - static void free_event_buf(void *buf) { kvfree(buf); @@ -865,25 +795,25 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) int rc, pmu_count; unsigned int i; bool irq_avail; + u16 dvsec; rc = pcim_enable_device(pdev); if (rc) return rc; pci_set_master(pdev); - mds = cxl_memdev_state_create(&pdev->dev); + dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL, + PCI_DVSEC_CXL_DEVICE); + if (!dvsec) + pci_warn(pdev, "Device DVSEC not present, skip CXL.mem init\n"); + + mds = cxl_memdev_state_create(&pdev->dev, pci_get_dsn(pdev), dvsec); if (IS_ERR(mds)) return PTR_ERR(mds); cxlds = &mds->cxlds; pci_set_drvdata(pdev, cxlds); cxlds->rcd = is_cxl_restricted(pdev); - cxlds->serial = pci_get_dsn(pdev); - cxlds->cxl_dvsec = pci_find_dvsec_capability( - pdev, PCI_VENDOR_ID_CXL, PCI_DVSEC_CXL_DEVICE); - if (!cxlds->cxl_dvsec) - dev_warn(&pdev->dev, - "Device DVSEC not present, skip CXL.mem init\n"); rc = cxl_pci_setup_regs(pdev, CXL_REGLOC_RBI_MEMDEV, &map); if (rc) @@ -1030,6 +960,19 @@ static void cxl_error_resume(struct pci_dev *pdev) dev->driver ? "successful" : "failed"); } +static int cxl_endpoint_decoder_clear_reset_flags(struct device *dev, void *data) +{ + struct cxl_endpoint_decoder *cxled; + + if (!is_endpoint_decoder(dev)) + return 0; + + cxled = to_cxl_endpoint_decoder(dev); + cxled->cxld.flags &= ~CXL_DECODER_F_RESET_MASK; + + return 0; +} + static void cxl_reset_done(struct pci_dev *pdev) { struct cxl_dev_state *cxlds = pci_get_drvdata(pdev); @@ -1043,8 +986,14 @@ static void cxl_reset_done(struct pci_dev *pdev) * that no longer exists. */ guard(device)(&cxlmd->dev); + if (!cxlmd->dev.driver) + return; + if (cxlmd->endpoint && cxl_endpoint_decoder_reset_detected(cxlmd->endpoint)) { + device_for_each_child(&cxlmd->endpoint->dev, NULL, + cxl_endpoint_decoder_clear_reset_flags); + dev_crit(dev, "SBR happened without memory regions removal.\n"); dev_crit(dev, "System may be unstable if regions hosted system memory.\n"); add_taint(TAINT_USER, LOCKDEP_STILL_OK); diff --git a/drivers/dax/Kconfig b/drivers/dax/Kconfig index d656e4c0eb84..504f7f735ef5 100644 --- a/drivers/dax/Kconfig +++ b/drivers/dax/Kconfig @@ -32,6 +32,9 @@ config DEV_DAX_HMEM depends on EFI_SOFT_RESERVE select NUMA_KEEP_MEMINFO if NUMA_MEMBLKS default DEV_DAX + depends on CXL_ACPI || !CXL_ACPI + depends on CXL_PCI || !CXL_PCI + depends on CXL_BUS || !CXL_BUS help EFI 2.8 platforms, and others, may advertise 'specific purpose' memory. For example, a high bandwidth memory pool. The @@ -48,6 +51,7 @@ config DEV_DAX_CXL tristate "CXL DAX: direct access to CXL RAM regions" depends on CXL_BUS && CXL_REGION && DEV_DAX default CXL_REGION && DEV_DAX + depends on DEV_DAX_HMEM || !DEV_DAX_HMEM help CXL RAM regions are either mapped by platform-firmware and published in the initial system-memory map as "System RAM", mapped diff --git a/drivers/dax/Makefile b/drivers/dax/Makefile index 5ed5c39857c8..70e996bf1526 100644 --- a/drivers/dax/Makefile +++ b/drivers/dax/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +obj-y += hmem/ obj-$(CONFIG_DAX) += dax.o obj-$(CONFIG_DEV_DAX) += device_dax.o obj-$(CONFIG_DEV_DAX_KMEM) += kmem.o @@ -10,5 +11,3 @@ dax-y += bus.o device_dax-y := device.o dax_pmem-y := pmem.o dax_cxl-y := cxl.o - -obj-y += hmem/ diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c index c94c09622516..68437c05e21d 100644 --- a/drivers/dax/bus.c +++ b/drivers/dax/bus.c @@ -10,6 +10,7 @@ #include "dax-private.h" #include "bus.h" +static struct resource dax_regions = DEFINE_RES_MEM_NAMED(0, -1, "DAX Regions"); static DEFINE_MUTEX(dax_bus_lock); /* @@ -627,6 +628,7 @@ static void dax_region_unregister(void *region) sysfs_remove_groups(&dax_region->dev->kobj, dax_region_attribute_groups); + release_resource(&dax_region->res); dax_region_put(dax_region); } @@ -635,6 +637,7 @@ struct dax_region *alloc_dax_region(struct device *parent, int region_id, unsigned long flags) { struct dax_region *dax_region; + int rc; /* * The DAX core assumes that it can store its private data in @@ -667,14 +670,25 @@ struct dax_region *alloc_dax_region(struct device *parent, int region_id, .flags = IORESOURCE_MEM | flags, }; - if (sysfs_create_groups(&parent->kobj, dax_region_attribute_groups)) { - kfree(dax_region); - return NULL; + rc = request_resource(&dax_regions, &dax_region->res); + if (rc) { + dev_dbg(parent, "dax_region resource conflict for %pR\n", + &dax_region->res); + goto err_res; } + if (sysfs_create_groups(&parent->kobj, dax_region_attribute_groups)) + goto err_sysfs; + if (devm_add_action_or_reset(parent, dax_region_unregister, dax_region)) return NULL; return dax_region; + +err_sysfs: + release_resource(&dax_region->res); +err_res: + dax_region_put(dax_region); + return NULL; } EXPORT_SYMBOL_GPL(alloc_dax_region); diff --git a/drivers/dax/bus.h b/drivers/dax/bus.h index cbbf64443098..7b1a83f1ce1f 100644 --- a/drivers/dax/bus.h +++ b/drivers/dax/bus.h @@ -3,7 +3,9 @@ #ifndef __DAX_BUS_H__ #define __DAX_BUS_H__ #include +#include #include +#include struct dev_dax; struct resource; @@ -49,6 +51,24 @@ void dax_driver_unregister(struct dax_device_driver *dax_drv); void kill_dev_dax(struct dev_dax *dev_dax); bool static_dev_dax(struct dev_dax *dev_dax); +struct hmem_platform_device { + struct platform_device pdev; + struct work_struct work; + bool did_probe; +}; + +static inline struct hmem_platform_device * +to_hmem_platform_device(struct platform_device *pdev) +{ + return container_of(pdev, struct hmem_platform_device, pdev); +} + +#if IS_ENABLED(CONFIG_DEV_DAX_HMEM) +void dax_hmem_flush_work(void); +#else +static inline void dax_hmem_flush_work(void) { } +#endif + #define MODULE_ALIAS_DAX_DEVICE(type) \ MODULE_ALIAS("dax:t" __stringify(type) "*") #define DAX_DEVICE_MODALIAS_FMT "dax:t%d" diff --git a/drivers/dax/cxl.c b/drivers/dax/cxl.c index 13cd94d32ff7..3ab39b77843d 100644 --- a/drivers/dax/cxl.c +++ b/drivers/dax/cxl.c @@ -38,10 +38,36 @@ static struct cxl_driver cxl_dax_region_driver = { .id = CXL_DEVICE_DAX_REGION, .drv = { .suppress_bind_attrs = true, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; -module_cxl_driver(cxl_dax_region_driver); +static void cxl_dax_region_driver_register(struct work_struct *work) +{ + dax_hmem_flush_work(); + cxl_driver_register(&cxl_dax_region_driver); +} + +static DECLARE_WORK(cxl_dax_region_driver_work, cxl_dax_region_driver_register); + +static int __init cxl_dax_region_init(void) +{ + /* + * Need to resolve a race with dax_hmem wanting to drive regions + * instead of CXL + */ + queue_work(system_long_wq, &cxl_dax_region_driver_work); + return 0; +} +module_init(cxl_dax_region_init); + +static void __exit cxl_dax_region_exit(void) +{ + flush_work(&cxl_dax_region_driver_work); + cxl_driver_unregister(&cxl_dax_region_driver); +} +module_exit(cxl_dax_region_exit); + MODULE_ALIAS_CXL(CXL_DEVICE_DAX_REGION); MODULE_DESCRIPTION("CXL DAX: direct access to CXL regions"); MODULE_LICENSE("GPL"); diff --git a/drivers/dax/hmem/device.c b/drivers/dax/hmem/device.c index 56e3cbd181b5..d70359b4307b 100644 --- a/drivers/dax/hmem/device.c +++ b/drivers/dax/hmem/device.c @@ -4,6 +4,7 @@ #include #include #include +#include "../bus.h" static bool nohmem; module_param_named(disable, nohmem, bool, 0444); @@ -33,9 +34,21 @@ int walk_hmem_resources(struct device *host, walk_hmem_fn fn) } EXPORT_SYMBOL_GPL(walk_hmem_resources); +static void hmem_work(struct work_struct *work) +{ + /* place holder until dax_hmem driver attaches */ +} + +static struct hmem_platform_device hmem_platform = { + .pdev = { + .name = "hmem_platform", + .id = 0, + }, + .work = __WORK_INITIALIZER(hmem_platform.work, hmem_work), +}; + static void __hmem_register_resource(int target_nid, struct resource *res) { - struct platform_device *pdev; struct resource *new; int rc; @@ -51,17 +64,13 @@ static void __hmem_register_resource(int target_nid, struct resource *res) if (platform_initialized) return; - pdev = platform_device_alloc("hmem_platform", 0); - if (!pdev) { + rc = platform_device_register(&hmem_platform.pdev); + if (rc) { pr_err_once("failed to register device-dax hmem_platform device\n"); return; } - rc = platform_device_add(pdev); - if (rc) - platform_device_put(pdev); - else - platform_initialized = true; + platform_initialized = true; } void hmem_register_resource(int target_nid, struct resource *res) diff --git a/drivers/dax/hmem/hmem.c b/drivers/dax/hmem/hmem.c index 1cf7c2a0ee1c..af21f66bf872 100644 --- a/drivers/dax/hmem/hmem.c +++ b/drivers/dax/hmem/hmem.c @@ -3,6 +3,7 @@ #include #include #include +#include "../../cxl/cxl.h" #include "../bus.h" static bool region_idle; @@ -58,21 +59,22 @@ static void release_hmem(void *pdev) platform_device_unregister(pdev); } -static int hmem_register_device(struct device *host, int target_nid, - const struct resource *res) +static struct workqueue_struct *dax_hmem_wq; + +void dax_hmem_flush_work(void) +{ + flush_workqueue(dax_hmem_wq); +} +EXPORT_SYMBOL_FOR_MODULES(dax_hmem_flush_work, "dax_cxl"); + +static int __hmem_register_device(struct device *host, int target_nid, + const struct resource *res) { struct platform_device *pdev; struct memregion_info info; long id; int rc; - if (IS_ENABLED(CONFIG_CXL_REGION) && - region_intersects(res->start, resource_size(res), IORESOURCE_MEM, - IORES_DESC_CXL) != REGION_DISJOINT) { - dev_dbg(host, "deferring range to CXL: %pr\n", res); - return 0; - } - rc = region_intersects_soft_reserve(res->start, resource_size(res)); if (rc != REGION_INTERSECTS) return 0; @@ -94,6 +96,7 @@ static int hmem_register_device(struct device *host, int target_nid, return -ENOMEM; } + pdev->dev.parent = host; pdev->dev.numa_node = numa_map_to_online_node(target_nid); info = (struct memregion_info) { .target_node = target_nid, @@ -123,8 +126,74 @@ out_put: return rc; } +static int hmem_register_cxl_device(struct device *host, int target_nid, + const struct resource *res) +{ + if (region_intersects(res->start, resource_size(res), IORESOURCE_MEM, + IORES_DESC_CXL) == REGION_DISJOINT) + return 0; + + if (cxl_region_contains_resource(res)) { + dev_dbg(host, "CXL claims resource, dropping: %pr\n", res); + return 0; + } + + dev_dbg(host, "CXL did not claim resource, registering: %pr\n", res); + return __hmem_register_device(host, target_nid, res); +} + +static void process_defer_work(struct work_struct *w) +{ + struct hmem_platform_device *hpdev = container_of(w, typeof(*hpdev), work); + struct device *dev = &hpdev->pdev.dev; + + /* Relies on cxl_acpi and cxl_pci having had a chance to load */ + wait_for_device_probe(); + + guard(device)(dev); + if (!dev->driver) + goto out; + + if (!hpdev->did_probe) { + hpdev->did_probe = true; + walk_hmem_resources(dev, hmem_register_cxl_device); + } +out: + put_device(dev); +} + +static int hmem_register_device(struct device *host, int target_nid, + const struct resource *res) +{ + struct platform_device *pdev = to_platform_device(host); + struct hmem_platform_device *hpdev = to_hmem_platform_device(pdev); + + if (IS_ENABLED(CONFIG_DEV_DAX_CXL) && + region_intersects(res->start, resource_size(res), IORESOURCE_MEM, + IORES_DESC_CXL) != REGION_DISJOINT) { + if (!hpdev->did_probe) { + dev_dbg(host, "await CXL initial probe: %pr\n", res); + hpdev->work.func = process_defer_work; + get_device(host); + if (!queue_work(dax_hmem_wq, &hpdev->work)) + put_device(host); + return 0; + } + dev_dbg(host, "deferring range to CXL: %pr\n", res); + return 0; + } + + return __hmem_register_device(host, target_nid, res); +} + static int dax_hmem_platform_probe(struct platform_device *pdev) { + struct hmem_platform_device *hpdev = to_hmem_platform_device(pdev); + + /* queue is only flushed on module unload, fail rebind with pending work */ + if (work_pending(&hpdev->work)) + return -EBUSY; + return walk_hmem_resources(&pdev->dev, hmem_register_device); } @@ -139,13 +208,34 @@ static __init int dax_hmem_init(void) { int rc; + /* + * Ensure that cxl_acpi and cxl_pci have a chance to kick off + * CXL topology discovery at least once before scanning the + * iomem resource tree for IORES_DESC_CXL resources. + */ + if (IS_ENABLED(CONFIG_DEV_DAX_CXL)) { + request_module("cxl_acpi"); + request_module("cxl_pci"); + } + + dax_hmem_wq = alloc_ordered_workqueue("dax_hmem_wq", 0); + if (!dax_hmem_wq) + return -ENOMEM; + rc = platform_driver_register(&dax_hmem_platform_driver); if (rc) - return rc; + goto err_platform_driver; rc = platform_driver_register(&dax_hmem_driver); if (rc) - platform_driver_unregister(&dax_hmem_platform_driver); + goto err_driver; + + return 0; + +err_driver: + platform_driver_unregister(&dax_hmem_platform_driver); +err_platform_driver: + destroy_workqueue(dax_hmem_wq); return rc; } @@ -154,18 +244,12 @@ static __exit void dax_hmem_exit(void) { platform_driver_unregister(&dax_hmem_driver); platform_driver_unregister(&dax_hmem_platform_driver); + destroy_workqueue(dax_hmem_wq); } module_init(dax_hmem_init); module_exit(dax_hmem_exit); -/* Allow for CXL to define its own dax regions */ -#if IS_ENABLED(CONFIG_CXL_REGION) -#if IS_MODULE(CONFIG_CXL_ACPI) -MODULE_SOFTDEP("pre: cxl_acpi"); -#endif -#endif - MODULE_ALIAS("platform:hmem*"); MODULE_ALIAS("platform:hmem_platform*"); MODULE_DESCRIPTION("HMEM DAX: direct access to 'specific purpose' memory"); diff --git a/drivers/firmware/efi/apple-properties.c b/drivers/firmware/efi/apple-properties.c index 13ac28754c03..2e525e17fba7 100644 --- a/drivers/firmware/efi/apple-properties.c +++ b/drivers/firmware/efi/apple-properties.c @@ -226,7 +226,7 @@ static int __init map_properties(void) */ data->len = 0; memunmap(data); - memblock_free_late(pa_data + sizeof(*data), data_len); + memblock_phys_free(pa_data + sizeof(*data), data_len); return ret; } diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 7cb6b9b864a7..8c935f867a37 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1211,8 +1211,7 @@ config I2C_SYNQUACER config I2C_TEGRA tristate "NVIDIA Tegra internal I2C controller" - depends on ARCH_TEGRA || (COMPILE_TEST && (ARC || ARM || ARM64 || M68K || RISCV || SUPERH || SPARC)) - # COMPILE_TEST needs architectures with readsX()/writesX() primitives + depends on ARCH_TEGRA || COMPILE_TEST depends on PINCTRL # ARCH_TEGRA implies PINCTRL, but the COMPILE_TEST side doesn't. help diff --git a/drivers/i2c/busses/i2c-cp2615.c b/drivers/i2c/busses/i2c-cp2615.c index 8212875700e1..951de6249834 100644 --- a/drivers/i2c/busses/i2c-cp2615.c +++ b/drivers/i2c/busses/i2c-cp2615.c @@ -270,8 +270,7 @@ static struct i2c_adapter_quirks cp2615_i2c_quirks = { .max_comb_2nd_msg_len = MAX_I2C_SIZE }; -static void -cp2615_i2c_remove(struct usb_interface *usbif) +static void cp2615_i2c_disconnect(struct usb_interface *usbif) { struct i2c_adapter *adap = usb_get_intfdata(usbif); @@ -328,7 +327,7 @@ MODULE_DEVICE_TABLE(usb, id_table); static struct usb_driver cp2615_i2c_driver = { .name = "i2c-cp2615", .probe = cp2615_i2c_probe, - .disconnect = cp2615_i2c_remove, + .disconnect = cp2615_i2c_disconnect, .id_table = id_table, }; diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 426ffec06e22..3351c4a9ef11 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -268,6 +268,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "AMDI0510", 0 }, { "APMC0D0F", 0 }, { "FUJI200B", 0 }, + { "GOOG5000", 0 }, { "HISI02A1", 0 }, { "HISI02A2", 0 }, { "HISI02A3", 0 }, diff --git a/drivers/i2c/busses/i2c-diolan-u2c.c b/drivers/i2c/busses/i2c-diolan-u2c.c index 077b093ba834..d502f6e4732f 100644 --- a/drivers/i2c/busses/i2c-diolan-u2c.c +++ b/drivers/i2c/busses/i2c-diolan-u2c.c @@ -427,12 +427,6 @@ static const struct usb_device_id diolan_u2c_table[] = { MODULE_DEVICE_TABLE(usb, diolan_u2c_table); -static void diolan_u2c_free(struct i2c_diolan_u2c *dev) -{ - usb_put_dev(dev->usb_dev); - kfree(dev); -} - static int diolan_u2c_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -453,7 +447,7 @@ static int diolan_u2c_probe(struct usb_interface *interface, dev->ep_out = hostif->endpoint[0].desc.bEndpointAddress; dev->ep_in = hostif->endpoint[1].desc.bEndpointAddress; - dev->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + dev->usb_dev = interface_to_usbdev(interface); dev->interface = interface; /* save our data pointer in this interface device */ @@ -488,7 +482,7 @@ static int diolan_u2c_probe(struct usb_interface *interface, error_free: usb_set_intfdata(interface, NULL); - diolan_u2c_free(dev); + kfree(dev); error: return ret; } @@ -499,7 +493,7 @@ static void diolan_u2c_disconnect(struct usb_interface *interface) i2c_del_adapter(&dev->adapter); usb_set_intfdata(interface, NULL); - diolan_u2c_free(dev); + kfree(dev); dev_dbg(&interface->dev, "disconnected\n"); } diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 6e93da576bbd..9152cf436bea 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -98,6 +98,10 @@ #define SPACEMIT_BUS_RESET_CLK_CNT_MAX 9 +#define SPACEMIT_WAIT_TIMEOUT 1000 /* ms */ +#define SPACEMIT_POLL_TIMEOUT 1000 /* us */ +#define SPACEMIT_POLL_INTERVAL 30 /* us */ + enum spacemit_i2c_state { SPACEMIT_STATE_IDLE, SPACEMIT_STATE_START, @@ -126,6 +130,7 @@ struct spacemit_i2c_dev { enum spacemit_i2c_state state; bool read; + bool use_pio; struct completion complete; u32 status; }; @@ -172,6 +177,14 @@ static int spacemit_i2c_handle_err(struct spacemit_i2c_dev *i2c) return i2c->status & SPACEMIT_SR_ACKNAK ? -ENXIO : -EIO; } +static inline void spacemit_i2c_delay(struct spacemit_i2c_dev *i2c, unsigned int us) +{ + if (i2c->use_pio) + udelay(us); + else + fsleep(us); +} + static void spacemit_i2c_conditionally_reset_bus(struct spacemit_i2c_dev *i2c) { u32 status; @@ -183,7 +196,8 @@ static void spacemit_i2c_conditionally_reset_bus(struct spacemit_i2c_dev *i2c) return; spacemit_i2c_reset(i2c); - usleep_range(10, 20); + + spacemit_i2c_delay(i2c, 10); for (clk_cnt = 0; clk_cnt < SPACEMIT_BUS_RESET_CLK_CNT_MAX; clk_cnt++) { status = readl(i2c->base + SPACEMIT_IBMR); @@ -212,9 +226,15 @@ static int spacemit_i2c_wait_bus_idle(struct spacemit_i2c_dev *i2c) if (!(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB))) return 0; - ret = readl_poll_timeout(i2c->base + SPACEMIT_ISR, - val, !(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB)), - 1500, SPACEMIT_I2C_BUS_BUSY_TIMEOUT); + if (i2c->use_pio) + ret = readl_poll_timeout_atomic(i2c->base + SPACEMIT_ISR, + val, !(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB)), + 1500, SPACEMIT_I2C_BUS_BUSY_TIMEOUT); + else + ret = readl_poll_timeout(i2c->base + SPACEMIT_ISR, + val, !(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB)), + 1500, SPACEMIT_I2C_BUS_BUSY_TIMEOUT); + if (ret) spacemit_i2c_reset(i2c); @@ -226,7 +246,7 @@ static void spacemit_i2c_check_bus_release(struct spacemit_i2c_dev *i2c) /* in case bus is not released after transfer completes */ if (readl(i2c->base + SPACEMIT_ISR) & SPACEMIT_SR_EBB) { spacemit_i2c_conditionally_reset_bus(i2c); - usleep_range(90, 150); + spacemit_i2c_delay(i2c, 90); } } @@ -238,25 +258,33 @@ spacemit_i2c_clear_int_status(struct spacemit_i2c_dev *i2c, u32 mask) static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) { - u32 val; + u32 val = 0; - /* - * Unmask interrupt bits for all xfer mode: - * bus error, arbitration loss detected. - * For transaction complete signal, we use master stop - * interrupt, so we don't need to unmask SPACEMIT_CR_TXDONEIE. - */ - val = SPACEMIT_CR_BEIE | SPACEMIT_CR_ALDIE; + if (!i2c->use_pio) { + /* + * Enable interrupt bits for all xfer mode: + * bus error, arbitration loss detected. + */ + val |= SPACEMIT_CR_BEIE | SPACEMIT_CR_ALDIE; - /* - * Unmask interrupt bits for interrupt xfer mode: - * When IDBR receives a byte, an interrupt is triggered. - * - * For the tx empty interrupt, it will be enabled in the - * i2c_start function. - * Otherwise, it will cause an erroneous empty interrupt before i2c_start. - */ - val |= SPACEMIT_CR_DRFIE; + /* + * Unmask interrupt bits for interrupt xfer mode: + * When IDBR receives a byte, an interrupt is triggered. + * + * For the tx empty interrupt, it will be enabled in the + * i2c_start(). + * We don't want a TX empty interrupt until we start + * a transfer in i2c_start(). + */ + val |= SPACEMIT_CR_DRFIE; + + /* + * Enable master stop interrupt bit. + * For transaction complete signal, we use master stop + * interrupt, so we don't need to unmask SPACEMIT_CR_TXDONEIE. + */ + val |= SPACEMIT_CR_MSDIE; + } if (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ) val |= SPACEMIT_CR_MODE_FAST; @@ -268,7 +296,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) val |= SPACEMIT_CR_SCLE; /* enable master stop detected */ - val |= SPACEMIT_CR_MSDE | SPACEMIT_CR_MSDIE; + val |= SPACEMIT_CR_MSDE; writel(val, i2c->base + SPACEMIT_ICR); @@ -301,41 +329,15 @@ static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c) /* send start pulse */ val = readl(i2c->base + SPACEMIT_ICR); val &= ~SPACEMIT_CR_STOP; - val |= SPACEMIT_CR_START | SPACEMIT_CR_TB | SPACEMIT_CR_DTEIE; + val |= SPACEMIT_CR_START | SPACEMIT_CR_TB; + + /* Enable the TX empty interrupt */ + if (!i2c->use_pio) + val |= SPACEMIT_CR_DTEIE; + writel(val, i2c->base + SPACEMIT_ICR); } -static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c) -{ - unsigned long time_left; - struct i2c_msg *msg; - - for (i2c->msg_idx = 0; i2c->msg_idx < i2c->msg_num; i2c->msg_idx++) { - msg = &i2c->msgs[i2c->msg_idx]; - i2c->msg_buf = msg->buf; - i2c->unprocessed = msg->len; - i2c->status = 0; - - reinit_completion(&i2c->complete); - - spacemit_i2c_start(i2c); - - time_left = wait_for_completion_timeout(&i2c->complete, - i2c->adapt.timeout); - if (!time_left) { - dev_err(i2c->dev, "msg completion timeout\n"); - spacemit_i2c_conditionally_reset_bus(i2c); - spacemit_i2c_reset(i2c); - return -ETIMEDOUT; - } - - if (i2c->status & SPACEMIT_SR_ERR) - return spacemit_i2c_handle_err(i2c); - } - - return 0; -} - static bool spacemit_i2c_is_last_msg(struct spacemit_i2c_dev *i2c) { if (i2c->msg_idx != i2c->msg_num - 1) @@ -347,8 +349,23 @@ static bool spacemit_i2c_is_last_msg(struct spacemit_i2c_dev *i2c) return !i2c->unprocessed; } +static inline void spacemit_i2c_complete(struct spacemit_i2c_dev *i2c) +{ + /* SPACEMIT_STATE_IDLE avoids triggering the next byte */ + i2c->state = SPACEMIT_STATE_IDLE; + + if (i2c->use_pio) + return; + + complete(&i2c->complete); +} + static void spacemit_i2c_handle_write(struct spacemit_i2c_dev *i2c) { + /* If there's no space in the IDBR, we're done */ + if (!(i2c->status & SPACEMIT_SR_ITE)) + return; + /* if transfer completes, SPACEMIT_ISR will handle it */ if (i2c->status & SPACEMIT_SR_MSD) return; @@ -359,16 +376,19 @@ static void spacemit_i2c_handle_write(struct spacemit_i2c_dev *i2c) return; } - /* SPACEMIT_STATE_IDLE avoids trigger next byte */ - i2c->state = SPACEMIT_STATE_IDLE; - complete(&i2c->complete); + spacemit_i2c_complete(i2c); } static void spacemit_i2c_handle_read(struct spacemit_i2c_dev *i2c) { + /* If there's nothing in the IDBR, we're done */ + if (!(i2c->status & SPACEMIT_SR_IRF)) + return; + if (i2c->unprocessed) { *i2c->msg_buf++ = readl(i2c->base + SPACEMIT_IDBR); i2c->unprocessed--; + return; } /* if transfer completes, SPACEMIT_ISR will handle it */ @@ -379,9 +399,7 @@ static void spacemit_i2c_handle_read(struct spacemit_i2c_dev *i2c) if (i2c->unprocessed) return; - /* SPACEMIT_STATE_IDLE avoids trigger next byte */ - i2c->state = SPACEMIT_STATE_IDLE; - complete(&i2c->complete); + spacemit_i2c_complete(i2c); } static void spacemit_i2c_handle_start(struct spacemit_i2c_dev *i2c) @@ -415,29 +433,16 @@ static void spacemit_i2c_err_check(struct spacemit_i2c_dev *i2c) spacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK); - i2c->state = SPACEMIT_STATE_IDLE; - complete(&i2c->complete); + spacemit_i2c_complete(i2c); } -static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) +static void spacemit_i2c_handle_state(struct spacemit_i2c_dev *i2c) { - struct spacemit_i2c_dev *i2c = devid; - u32 status, val; - - status = readl(i2c->base + SPACEMIT_ISR); - if (!status) - return IRQ_HANDLED; - - i2c->status = status; - - spacemit_i2c_clear_int_status(i2c, status); + u32 val; if (i2c->status & SPACEMIT_SR_ERR) goto err_out; - val = readl(i2c->base + SPACEMIT_ICR); - val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | SPACEMIT_CR_STOP | SPACEMIT_CR_START); - switch (i2c->state) { case SPACEMIT_STATE_START: spacemit_i2c_handle_start(i2c); @@ -453,7 +458,12 @@ static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) } if (i2c->state != SPACEMIT_STATE_IDLE) { - val |= SPACEMIT_CR_TB | SPACEMIT_CR_ALDIE; + val = readl(i2c->base + SPACEMIT_ICR); + val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | + SPACEMIT_CR_STOP | SPACEMIT_CR_START); + val |= SPACEMIT_CR_TB; + if (!i2c->use_pio) + val |= SPACEMIT_CR_ALDIE; if (spacemit_i2c_is_last_msg(i2c)) { /* trigger next byte with stop */ @@ -467,6 +477,133 @@ static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) err_out: spacemit_i2c_err_check(i2c); +} + +/* + * In PIO mode, this function is used as a replacement for + * wait_for_completion_timeout(), whose return value indicates + * the remaining time. + * + * We do not have a meaningful remaining-time value here, so + * return a non-zero value on success to indicate "not timed out". + * Returning 1 ensures callers treating the return value as + * time_left will not incorrectly report a timeout. + */ +static int spacemit_i2c_wait_pio_xfer(struct spacemit_i2c_dev *i2c) +{ + u32 mask, msec = jiffies_to_msecs(i2c->adapt.timeout); + ktime_t timeout = ktime_add_ms(ktime_get(), msec); + int ret; + + mask = SPACEMIT_SR_IRF | SPACEMIT_SR_ITE; + + do { + i2c->status = readl(i2c->base + SPACEMIT_ISR); + + spacemit_i2c_clear_int_status(i2c, i2c->status); + + if (i2c->status & mask) + spacemit_i2c_handle_state(i2c); + else + udelay(SPACEMIT_POLL_INTERVAL); + } while (i2c->unprocessed && ktime_compare(ktime_get(), timeout) < 0); + + if (i2c->unprocessed) + return 0; + + if (i2c->read) + return 1; + + /* + * If this is the last byte to write of the current message, + * we have to wait here. Otherwise, control will proceed directly + * to start(), which would overwrite the current data. + */ + ret = readl_poll_timeout_atomic(i2c->base + SPACEMIT_ISR, + i2c->status, i2c->status & SPACEMIT_SR_ITE, + SPACEMIT_POLL_INTERVAL, SPACEMIT_POLL_TIMEOUT); + if (ret) + return 0; + + /* + * For writes: in interrupt mode, an ITE (write-empty) interrupt is triggered + * after the last byte, and the MSD-related handling takes place there. + * In PIO mode, however, we need to explicitly call err_check() to emulate this + * step, otherwise the next transfer will fail. + */ + if (i2c->msg_idx == i2c->msg_num - 1) { + mask = SPACEMIT_SR_MSD | SPACEMIT_SR_ERR; + /* + * In some cases, MSD may not arrive immediately; + * wait here to handle that. + */ + ret = readl_poll_timeout_atomic(i2c->base + SPACEMIT_ISR, + i2c->status, i2c->status & mask, + SPACEMIT_POLL_INTERVAL, SPACEMIT_POLL_TIMEOUT); + if (ret) + return 0; + + spacemit_i2c_err_check(i2c); + } + + return 1; +} + +static int spacemit_i2c_wait_xfer_complete(struct spacemit_i2c_dev *i2c) +{ + if (i2c->use_pio) + return spacemit_i2c_wait_pio_xfer(i2c); + + return wait_for_completion_timeout(&i2c->complete, + i2c->adapt.timeout); +} + +static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c) +{ + unsigned long time_left; + struct i2c_msg *msg; + + for (i2c->msg_idx = 0; i2c->msg_idx < i2c->msg_num; i2c->msg_idx++) { + msg = &i2c->msgs[i2c->msg_idx]; + i2c->msg_buf = msg->buf; + i2c->unprocessed = msg->len; + i2c->status = 0; + + reinit_completion(&i2c->complete); + + spacemit_i2c_start(i2c); + + time_left = spacemit_i2c_wait_xfer_complete(i2c); + + if (!time_left) { + dev_err(i2c->dev, "msg completion timeout\n"); + spacemit_i2c_conditionally_reset_bus(i2c); + spacemit_i2c_reset(i2c); + return -ETIMEDOUT; + } + + if (i2c->status & SPACEMIT_SR_ERR) + return spacemit_i2c_handle_err(i2c); + } + + return 0; +} + +static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) +{ + struct spacemit_i2c_dev *i2c = devid; + u32 status; + + status = readl(i2c->base + SPACEMIT_ISR); + if (!status) + return IRQ_HANDLED; + + i2c->status = status; + + spacemit_i2c_clear_int_status(i2c, status); + + spacemit_i2c_handle_state(i2c); + return IRQ_HANDLED; } @@ -475,6 +612,11 @@ static void spacemit_i2c_calc_timeout(struct spacemit_i2c_dev *i2c) unsigned long timeout; int idx = 0, cnt = 0; + if (i2c->use_pio) { + i2c->adapt.timeout = msecs_to_jiffies(SPACEMIT_WAIT_TIMEOUT); + return; + } + for (; idx < i2c->msg_num; idx++) cnt += (i2c->msgs + idx)->len + 1; @@ -487,11 +629,14 @@ static void spacemit_i2c_calc_timeout(struct spacemit_i2c_dev *i2c) i2c->adapt.timeout = usecs_to_jiffies(timeout + USEC_PER_SEC / 10) / i2c->msg_num; } -static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num) +static inline int +spacemit_i2c_xfer_common(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num, bool use_pio) { struct spacemit_i2c_dev *i2c = i2c_get_adapdata(adapt); int ret; + i2c->use_pio = use_pio; + i2c->msgs = msgs; i2c->msg_num = num; @@ -519,6 +664,16 @@ static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg *msgs, in return ret < 0 ? ret : num; } +static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num) +{ + return spacemit_i2c_xfer_common(adapt, msgs, num, false); +} + +static int spacemit_i2c_pio_xfer_atomic(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num) +{ + return spacemit_i2c_xfer_common(adapt, msgs, num, true); +} + static u32 spacemit_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); @@ -526,6 +681,7 @@ static u32 spacemit_i2c_func(struct i2c_adapter *adap) static const struct i2c_algorithm spacemit_i2c_algo = { .xfer = spacemit_i2c_xfer, + .xfer_atomic = spacemit_i2c_pio_xfer_atomic, .functionality = spacemit_i2c_func, }; diff --git a/drivers/i2c/busses/i2c-npcm7xx.c b/drivers/i2c/busses/i2c-npcm7xx.c index 8b7e15240fb0..f667a873b81e 100644 --- a/drivers/i2c/busses/i2c-npcm7xx.c +++ b/drivers/i2c/busses/i2c-npcm7xx.c @@ -1384,7 +1384,7 @@ static irqreturn_t npcm_i2c_int_slave_handler(struct npcm_i2c *bus) */ bus->operation = I2C_NO_OPER; bus->own_slave_addr = 0xFF; - i2c_slave_event(bus->slave, I2C_SLAVE_STOP, 0); + i2c_slave_event(bus->slave, I2C_SLAVE_STOP, NULL); iowrite8(NPCM_I2CST_SLVSTP, bus->reg + NPCM_I2CST); if (bus->fifo_use) { npcm_i2c_clear_fifo_int(bus); diff --git a/drivers/i2c/busses/i2c-ocores.c b/drivers/i2c/busses/i2c-ocores.c index 0f67e57cdeff..df6ebf32d6e8 100644 --- a/drivers/i2c/busses/i2c-ocores.c +++ b/drivers/i2c/busses/i2c-ocores.c @@ -24,6 +24,7 @@ #include #include #include +#include #include /* @@ -258,7 +259,7 @@ static void ocores_process_timeout(struct ocores_i2c *i2c) * @reg: register to query * @mask: bitmask to apply on register value * @val: expected result - * @timeout: timeout in jiffies + * @timeout_us: timeout in microseconds * * Timeout is necessary to avoid to stay here forever when the chip * does not answer correctly. @@ -267,21 +268,14 @@ static void ocores_process_timeout(struct ocores_i2c *i2c) */ static int ocores_wait(struct ocores_i2c *i2c, int reg, u8 mask, u8 val, - const unsigned long timeout) + unsigned long timeout_us) { - unsigned long j; + u8 status; - j = jiffies + timeout; - while (1) { - u8 status = oc_getreg(i2c, reg); - - if ((status & mask) == val) - break; - - if (time_after(jiffies, j)) - return -ETIMEDOUT; - } - return 0; + return read_poll_timeout_atomic(oc_getreg, status, + (status & mask) == val, + 0, timeout_us, false, + i2c, reg); } /** @@ -314,7 +308,7 @@ static int ocores_poll_wait(struct ocores_i2c *i2c) * once we are here we expect to get the expected result immediately * so if after 1ms we timeout then something is broken. */ - err = ocores_wait(i2c, OCI2C_STATUS, mask, 0, msecs_to_jiffies(1)); + err = ocores_wait(i2c, OCI2C_STATUS, mask, 0, 1000); if (err) dev_warn(i2c->adap.dev.parent, "%s: STATUS timeout, bit 0x%x did not clear in 1ms\n", diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index 884055df1560..f3ccfbbc4bea 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -71,9 +71,6 @@ #define NUM_MASTERS 2 #define NUM_QUEUES 2 -/* Max number of resources + 1 for a NULL terminator */ -#define CCI_RES_MAX 6 - #define CCI_I2C_SET_PARAM 1 #define CCI_I2C_REPORT 8 #define CCI_I2C_WRITE 9 diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index a4acb78fafb6..a482a4c60744 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -625,8 +625,8 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], { struct gpi_i2c_config *peripheral; unsigned int flags; - void *dma_buf; - dma_addr_t addr; + void *dma_buf = NULL; + dma_addr_t addr = 0; enum dma_data_direction map_dirn; enum dma_transfer_direction dma_dirn; struct dma_async_tx_descriptor *desc; @@ -639,6 +639,16 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], gi2c_gpi_xfer = &gi2c->i2c_multi_desc_config; msg_idx = gi2c_gpi_xfer->msg_idx_cnt; + /* + * Skip TX DMA mapping for a read message (I2C_M_RD) to avoid + * programming an extra TX DMA TRE that would cause an unintended + * write cycle on the I2C bus before the actual read operation. + */ + if (op == I2C_WRITE && msgs[msg_idx].flags & I2C_M_RD) { + peripheral->multi_msg = true; + goto skip_tx_dma_map; + } + dma_buf = i2c_get_dma_safe_msg_buf(&msgs[msg_idx], 1); if (!dma_buf) { ret = -ENOMEM; @@ -658,6 +668,7 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], goto out; } +skip_tx_dma_map: if (gi2c->is_tx_multi_desc_xfer) { flags = DMA_CTRL_ACK; @@ -740,9 +751,12 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], return 0; err_config: - dma_unmap_single(gi2c->se.dev->parent, addr, - msgs[msg_idx].len, map_dirn); - i2c_put_dma_safe_msg_buf(dma_buf, &msgs[msg_idx], false); + /* Avoid DMA unmap as the write operation skipped DMA mapping */ + if (dma_buf) { + dma_unmap_single(gi2c->se.dev->parent, addr, + msgs[msg_idx].len, map_dirn); + i2c_put_dma_safe_msg_buf(dma_buf, &msgs[msg_idx], false); + } out: gi2c->err = ret; diff --git a/drivers/i2c/busses/i2c-robotfuzz-osif.c b/drivers/i2c/busses/i2c-robotfuzz-osif.c index e0a76fb5bc31..412fa8e37f69 100644 --- a/drivers/i2c/busses/i2c-robotfuzz-osif.c +++ b/drivers/i2c/busses/i2c-robotfuzz-osif.c @@ -141,7 +141,7 @@ static int osif_probe(struct usb_interface *interface, if (!priv) return -ENOMEM; - priv->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + priv->usb_dev = interface_to_usbdev(interface); priv->interface = interface; usb_set_intfdata(interface, priv); @@ -163,7 +163,6 @@ static int osif_probe(struct usb_interface *interface, NULL, 0); if (ret) { dev_err(&interface->dev, "failure sending bit rate"); - usb_put_dev(priv->usb_dev); return ret; } @@ -184,7 +183,6 @@ static void osif_disconnect(struct usb_interface *interface) i2c_del_adapter(&(priv->adapter)); usb_set_intfdata(interface, NULL); - usb_put_dev(priv->usb_dev); } static struct usb_driver osif_driver = { diff --git a/drivers/i2c/busses/i2c-rtl9300.c b/drivers/i2c/busses/i2c-rtl9300.c index 672cb978066d..8cedffbb2964 100644 --- a/drivers/i2c/busses/i2c-rtl9300.c +++ b/drivers/i2c/busses/i2c-rtl9300.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include #include @@ -11,10 +12,16 @@ #include enum rtl9300_bus_freq { - RTL9300_I2C_STD_FREQ, - RTL9300_I2C_FAST_FREQ, + RTL9300_I2C_STD_FREQ, // 100kHz + RTL9300_I2C_FAST_FREQ, // 400kHz + RTL9300_I2C_SUPER_FAST_FREQ, // 2.5MHz + RTL9300_I2C_SLOW_FREQ, // 50kHz }; +#define RTL9300_I2C_MAX_SUPER_FAST_FREQ 2500000 +#define RTL9300_I2C_MAX_SLOW_FREQ 50000 + + struct rtl9300_i2c; struct rtl9300_i2c_chan { @@ -22,6 +29,7 @@ struct rtl9300_i2c_chan { struct rtl9300_i2c *i2c; enum rtl9300_bus_freq bus_freq; u8 sda_num; + u32 clk_div; }; enum rtl9300_i2c_reg_scope { @@ -47,6 +55,9 @@ enum rtl9300_i2c_reg_fields { F_SCL_SEL, F_SDA_OUT_SEL, F_SDA_SEL, + F_BUSY, + F_CLK_DIV, + F_EXT_SCK_5MS, /* keep last */ F_NUM_FIELDS @@ -55,12 +66,22 @@ enum rtl9300_i2c_reg_fields { struct rtl9300_i2c_drv_data { struct rtl9300_i2c_reg_field field_desc[F_NUM_FIELDS]; int (*select_scl)(struct rtl9300_i2c *i2c, u8 scl); - u32 data_reg; + int (*config_chan)(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan); + void (*config_clock)(u32 clock_freq, struct rtl9300_i2c_chan *chan); + int (*misc_init)(struct rtl9300_i2c *i2c); + u32 rd_reg; + u32 wd_reg; u8 max_nchan; + u8 max_data_len; + u8 reg_addr_8bit_len; }; #define RTL9300_I2C_MUX_NCHAN 8 #define RTL9310_I2C_MUX_NCHAN 12 +#define RTL9607_I2C_MUX_NCHAN 1 + +#define RTL9300_I2C_MAX_DATA_LEN 16 +#define RTL9607_I2C_MAX_DATA_LEN 4 struct rtl9300_i2c { struct regmap *regmap; @@ -68,10 +89,12 @@ struct rtl9300_i2c { struct rtl9300_i2c_chan chans[RTL9310_I2C_MUX_NCHAN]; struct regmap_field *fields[F_NUM_FIELDS]; u32 reg_base; - u32 data_reg; + u32 rd_reg; + u32 wd_reg; u8 scl_num; u8 sda_num; struct mutex lock; + struct clk *clk; }; DEFINE_GUARD(rtl9300_i2c, struct rtl9300_i2c *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock)) @@ -99,6 +122,7 @@ struct rtl9300_i2c_xfer { #define RTL9300_I2C_MST_DATA_WORD2 0x10 #define RTL9300_I2C_MST_DATA_WORD3 0x14 #define RTL9300_I2C_MST_GLB_CTRL 0x384 +#define RTL9300_REG_ADDR_8BIT_LEN 1 #define RTL9310_I2C_MST_IF_CTRL 0x1004 #define RTL9310_I2C_MST_IF_SEL 0x1008 @@ -106,6 +130,14 @@ struct rtl9300_i2c_xfer { #define RTL9310_I2C_MST_MEMADDR_CTRL 0x4 #define RTL9310_I2C_MST_DATA_CTRL 0x8 +#define RTL9607_I2C_CONFIG 0x22f50 +#define RTL9607_IO_MODE_EN 0x23014 +#define RTL9607_I2C_IND_WD 0x0 +#define RTL9607_I2C_IND_ADR 0x8 +#define RTL9607_I2C_IND_CMD 0x10 +#define RTL9607_I2C_IND_RD 0x18 +#define RTL9607_REG_ADDR_8BIT_LEN 0 + static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len) { int ret; @@ -157,6 +189,58 @@ static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_c return 0; } +static int rtl9607_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan) +{ + const struct rtl9300_i2c_drv_data *drv_data; + int ret; + + if (i2c->sda_num == chan->sda_num) + return 0; + + ret = regmap_field_write(i2c->fields[F_CLK_DIV], chan->clk_div); + if (ret) + return ret; + + drv_data = device_get_match_data(i2c->dev); + ret = drv_data->select_scl(i2c, i2c->scl_num); + if (ret) + return ret; + + i2c->sda_num = chan->sda_num; + return 0; +} + +static void rtl9300_i2c_config_clock(u32 clock_freq, struct rtl9300_i2c_chan *chan) +{ + struct rtl9300_i2c *i2c = chan->i2c; + + switch (clock_freq) { + case I2C_MAX_STANDARD_MODE_FREQ: + chan->bus_freq = RTL9300_I2C_STD_FREQ; + break; + case I2C_MAX_FAST_MODE_FREQ: + chan->bus_freq = RTL9300_I2C_FAST_FREQ; + break; + case RTL9300_I2C_MAX_SUPER_FAST_FREQ: + chan->bus_freq = RTL9300_I2C_SUPER_FAST_FREQ; + break; + case RTL9300_I2C_MAX_SLOW_FREQ: + chan->bus_freq = RTL9300_I2C_SLOW_FREQ; + break; + default: + dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n", + chan->sda_num, clock_freq); + break; + } +} + +static void rtl9607_i2c_config_clock(u32 clock_freq, struct rtl9300_i2c_chan *chan) +{ + struct rtl9300_i2c *i2c = chan->i2c; + + chan->clk_div = clk_get_rate(i2c->clk) / clock_freq - 1; +} + static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len) { u32 vals[4] = {}; @@ -165,7 +249,7 @@ static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len) if (len > 16) return -EIO; - ret = regmap_bulk_read(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals)); + ret = regmap_bulk_read(i2c->regmap, i2c->rd_reg, vals, ARRAY_SIZE(vals)); if (ret) return ret; @@ -192,19 +276,21 @@ static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, u8 len) vals[reg] |= buf[i] << shift; } - return regmap_bulk_write(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals)); + return regmap_bulk_write(i2c->regmap, i2c->wd_reg, vals, ARRAY_SIZE(vals)); } static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data) { - return regmap_write(i2c->regmap, i2c->data_reg, data); + return regmap_write(i2c->regmap, i2c->wd_reg, data); } static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer) { + const struct rtl9300_i2c_drv_data *drv_data; int ret; - if (xfer->data_len < 1 || xfer->data_len > 16) + drv_data = device_get_match_data(i2c->dev); + if (xfer->data_len < 1 || xfer->data_len > drv_data->max_data_len) return -EINVAL; ret = regmap_field_write(i2c->fields[F_DEV_ADDR], xfer->dev_addr); @@ -249,7 +335,7 @@ static int rtl9300_i2c_do_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer if (ret) return ret; - ret = regmap_field_read_poll_timeout(i2c->fields[F_I2C_TRIG], val, !val, 100, 100000); + ret = regmap_field_read_poll_timeout(i2c->fields[F_BUSY], val, !val, 100, 100000); if (ret) return ret; @@ -262,14 +348,14 @@ static int rtl9300_i2c_do_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer if (!xfer->write) { switch (xfer->type) { case RTL9300_I2C_XFER_BYTE: - ret = regmap_read(i2c->regmap, i2c->data_reg, &val); + ret = regmap_read(i2c->regmap, i2c->rd_reg, &val); if (ret) return ret; *xfer->data = val & 0xff; break; case RTL9300_I2C_XFER_WORD: - ret = regmap_read(i2c->regmap, i2c->data_reg, &val); + ret = regmap_read(i2c->regmap, i2c->rd_reg, &val); if (ret) return ret; @@ -291,6 +377,7 @@ static int rtl9300_i2c_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned s union i2c_smbus_data *data) { struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap); + const struct rtl9300_i2c_drv_data *drv_data; struct rtl9300_i2c *i2c = chan->i2c; struct rtl9300_i2c_xfer xfer = {0}; int ret; @@ -300,14 +387,15 @@ static int rtl9300_i2c_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned s guard(rtl9300_i2c)(i2c); - ret = rtl9300_i2c_config_chan(i2c, chan); + drv_data = device_get_match_data(i2c->dev); + ret = drv_data->config_chan(i2c, chan); if (ret) return ret; xfer.dev_addr = addr & 0x7f; xfer.write = (read_write == I2C_SMBUS_WRITE); xfer.reg_addr = command; - xfer.reg_addr_len = 1; + xfer.reg_addr_len = drv_data->reg_addr_8bit_len; switch (size) { case I2C_SMBUS_BYTE: @@ -367,6 +455,17 @@ static struct i2c_adapter_quirks rtl9300_i2c_quirks = { .max_write_len = 16, }; +static int rtl9300_i2c_init(struct rtl9300_i2c *i2c) +{ + /* only use standard read format */ + return regmap_field_write(i2c->fields[F_RD_MODE], 0); +} + +static int rtl9607_i2c_init(struct rtl9300_i2c *i2c) +{ + return regmap_field_write(i2c->fields[F_EXT_SCK_5MS], 1); +} + static int rtl9300_i2c_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -402,7 +501,8 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) if (device_get_child_node_count(dev) > drv_data->max_nchan) return dev_err_probe(dev, -EINVAL, "Too many channels\n"); - i2c->data_reg = i2c->reg_base + drv_data->data_reg; + i2c->rd_reg = i2c->reg_base + drv_data->rd_reg; + i2c->wd_reg = i2c->reg_base + drv_data->wd_reg; for (i = 0; i < F_NUM_FIELDS; i++) { fields[i] = drv_data->field_desc[i].field; if (drv_data->field_desc[i].scope == REG_SCOPE_MASTER) @@ -413,6 +513,10 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) if (ret) return ret; + i2c->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(i2c->clk)) + return dev_err_probe(dev, PTR_ERR(i2c->clk), "Failed to enable i2c clock\n"); + i = 0; for_each_child_of_node_scoped(dev->of_node, child) { struct rtl9300_i2c_chan *chan = &i2c->chans[i]; @@ -426,21 +530,11 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) if (ret) clock_freq = I2C_MAX_STANDARD_MODE_FREQ; - switch (clock_freq) { - case I2C_MAX_STANDARD_MODE_FREQ: - chan->bus_freq = RTL9300_I2C_STD_FREQ; - break; - case I2C_MAX_FAST_MODE_FREQ: - chan->bus_freq = RTL9300_I2C_FAST_FREQ; - break; - default: - dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n", - sda_num, clock_freq); - break; - } - chan->sda_num = sda_num; chan->i2c = i2c; + + drv_data->config_clock(clock_freq, chan); + adap = &i2c->chans[i].adap; adap->owner = THIS_MODULE; adap->algo = &rtl9300_i2c_algo; @@ -458,8 +552,7 @@ static int rtl9300_i2c_probe(struct platform_device *pdev) } i2c->sda_num = 0xff; - /* only use standard read format */ - ret = regmap_field_write(i2c->fields[F_RD_MODE], 0); + ret = drv_data->misc_init(i2c); if (ret) return ret; @@ -485,10 +578,17 @@ static const struct rtl9300_i2c_drv_data rtl9300_i2c_drv_data = { [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 2, 3), [F_SCL_FREQ] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 0, 1), [F_SDA_SEL] = GLB_REG_FIELD(RTL9300_I2C_MST_GLB_CTRL, 0, 7), + [F_BUSY] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 0, 0), }, .select_scl = rtl9300_i2c_select_scl, - .data_reg = RTL9300_I2C_MST_DATA_WORD0, + .config_chan = rtl9300_i2c_config_chan, + .config_clock = rtl9300_i2c_config_clock, + .misc_init = rtl9300_i2c_init, + .rd_reg = RTL9300_I2C_MST_DATA_WORD0, + .wd_reg = RTL9300_I2C_MST_DATA_WORD0, .max_nchan = RTL9300_I2C_MUX_NCHAN, + .max_data_len = RTL9300_I2C_MAX_DATA_LEN, + .reg_addr_8bit_len = RTL9300_REG_ADDR_8BIT_LEN, }; static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = { @@ -505,10 +605,42 @@ static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = { [F_I2C_FAIL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 1, 1), [F_I2C_TRIG] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0), [F_MEM_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_MEMADDR_CTRL, 0, 23), + [F_BUSY] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0), }, .select_scl = rtl9310_i2c_select_scl, - .data_reg = RTL9310_I2C_MST_DATA_CTRL, + .config_chan = rtl9300_i2c_config_chan, + .config_clock = rtl9300_i2c_config_clock, + .misc_init = rtl9300_i2c_init, + .rd_reg = RTL9310_I2C_MST_DATA_CTRL, + .wd_reg = RTL9310_I2C_MST_DATA_CTRL, .max_nchan = RTL9310_I2C_MUX_NCHAN, + .max_data_len = RTL9300_I2C_MAX_DATA_LEN, + .reg_addr_8bit_len = RTL9300_REG_ADDR_8BIT_LEN, +}; + +static const struct rtl9300_i2c_drv_data rtl9607_i2c_drv_data = { + .field_desc = { + [F_SCL_SEL] = GLB_REG_FIELD(RTL9607_IO_MODE_EN, 13, 14), + [F_EXT_SCK_5MS] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 26, 26), + [F_DEV_ADDR] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 14, 20), + [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 12, 13), + [F_DATA_WIDTH] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 10, 11), + [F_CLK_DIV] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 0, 9), + [F_I2C_FAIL] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 3, 3), + [F_BUSY] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 2, 2), + [F_RWOP] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 1, 1), + [F_I2C_TRIG] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 0, 0), + [F_MEM_ADDR] = MST_REG_FIELD(RTL9607_I2C_IND_ADR, 0, 31), + }, + .select_scl = rtl9310_i2c_select_scl, + .config_chan = rtl9607_i2c_config_chan, + .config_clock = rtl9607_i2c_config_clock, + .misc_init = rtl9607_i2c_init, + .rd_reg = RTL9607_I2C_IND_RD, + .wd_reg = RTL9607_I2C_IND_WD, + .max_nchan = RTL9607_I2C_MUX_NCHAN, + .max_data_len = RTL9607_I2C_MAX_DATA_LEN, + .reg_addr_8bit_len = RTL9607_REG_ADDR_8BIT_LEN, }; static const struct of_device_id i2c_rtl9300_dt_ids[] = { @@ -520,6 +652,7 @@ static const struct of_device_id i2c_rtl9300_dt_ids[] = { { .compatible = "realtek,rtl9311-i2c", .data = (void *) &rtl9310_i2c_drv_data }, { .compatible = "realtek,rtl9312-i2c", .data = (void *) &rtl9310_i2c_drv_data }, { .compatible = "realtek,rtl9313-i2c", .data = (void *) &rtl9310_i2c_drv_data }, + { .compatible = "realtek,rtl9607-i2c", .data = (void *) &rtl9607_i2c_drv_data }, {} }; MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids); diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c index 8138f5ef40f0..15e14a6fe6dc 100644 --- a/drivers/i2c/busses/i2c-s3c2410.c +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -503,8 +503,13 @@ static void i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) i2c->msg->buf[i2c->msg_ptr++] = byte; /* Add actual length to read for smbus block read */ - if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1) + if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1) { + if (byte == 0 || byte > I2C_SMBUS_BLOCK_MAX) { + s3c24xx_i2c_stop(i2c, -EPROTO); + break; + } i2c->msg->len += byte; + } prepare_read: if (is_msglast(i2c)) { /* last byte of buffer */ diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 4eaeb395d5db..9fd5ade774a0 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -30,38 +30,29 @@ #define BYTES_PER_FIFO_WORD 4 -#define I2C_CNFG 0x000 #define I2C_CNFG_DEBOUNCE_CNT GENMASK(14, 12) #define I2C_CNFG_PACKET_MODE_EN BIT(10) #define I2C_CNFG_NEW_MASTER_FSM BIT(11) #define I2C_CNFG_MULTI_MASTER_MODE BIT(17) -#define I2C_STATUS 0x01c -#define I2C_SL_CNFG 0x020 + #define I2C_SL_CNFG_NACK BIT(1) #define I2C_SL_CNFG_NEWSL BIT(2) -#define I2C_SL_ADDR1 0x02c -#define I2C_SL_ADDR2 0x030 -#define I2C_TLOW_SEXT 0x034 -#define I2C_TX_FIFO 0x050 -#define I2C_RX_FIFO 0x054 -#define I2C_PACKET_TRANSFER_STATUS 0x058 -#define I2C_FIFO_CONTROL 0x05c + #define I2C_FIFO_CONTROL_TX_FLUSH BIT(1) #define I2C_FIFO_CONTROL_RX_FLUSH BIT(0) #define I2C_FIFO_CONTROL_TX_TRIG(x) (((x) - 1) << 5) #define I2C_FIFO_CONTROL_RX_TRIG(x) (((x) - 1) << 2) -#define I2C_FIFO_STATUS 0x060 + #define I2C_FIFO_STATUS_TX GENMASK(7, 4) #define I2C_FIFO_STATUS_RX GENMASK(3, 0) -#define I2C_INT_MASK 0x064 -#define I2C_INT_STATUS 0x068 + #define I2C_INT_BUS_CLR_DONE BIT(11) #define I2C_INT_PACKET_XFER_COMPLETE BIT(7) #define I2C_INT_NO_ACK BIT(3) #define I2C_INT_ARBITRATION_LOST BIT(2) #define I2C_INT_TX_FIFO_DATA_REQ BIT(1) #define I2C_INT_RX_FIFO_DATA_REQ BIT(0) -#define I2C_CLK_DIVISOR 0x06c + #define I2C_CLK_DIVISOR_STD_FAST_MODE GENMASK(31, 16) #define I2C_CLK_DIVISOR_HSMODE GENMASK(15, 0) @@ -94,50 +85,38 @@ #define I2C_HEADER_CONTINUE_XFER BIT(15) #define I2C_HEADER_SLAVE_ADDR_SHIFT 1 -#define I2C_BUS_CLEAR_CNFG 0x084 #define I2C_BC_SCLK_THRESHOLD GENMASK(23, 16) #define I2C_BC_STOP_COND BIT(2) #define I2C_BC_TERMINATE BIT(1) #define I2C_BC_ENABLE BIT(0) -#define I2C_BUS_CLEAR_STATUS 0x088 + #define I2C_BC_STATUS BIT(0) -#define I2C_CONFIG_LOAD 0x08c #define I2C_MSTR_CONFIG_LOAD BIT(0) -#define I2C_CLKEN_OVERRIDE 0x090 #define I2C_MST_CORE_CLKEN_OVR BIT(0) -#define I2C_INTERFACE_TIMING_0 0x094 -#define I2C_INTERFACE_TIMING_THIGH GENMASK(13, 8) -#define I2C_INTERFACE_TIMING_TLOW GENMASK(5, 0) -#define I2C_INTERFACE_TIMING_1 0x098 -#define I2C_INTERFACE_TIMING_TBUF GENMASK(29, 24) -#define I2C_INTERFACE_TIMING_TSU_STO GENMASK(21, 16) -#define I2C_INTERFACE_TIMING_THD_STA GENMASK(13, 8) -#define I2C_INTERFACE_TIMING_TSU_STA GENMASK(5, 0) +#define I2C_INTERFACE_TIMING_THIGH GENMASK(13, 8) +#define I2C_INTERFACE_TIMING_TLOW GENMASK(5, 0) +#define I2C_INTERFACE_TIMING_TBUF GENMASK(29, 24) +#define I2C_INTERFACE_TIMING_TSU_STO GENMASK(21, 16) +#define I2C_INTERFACE_TIMING_THD_STA GENMASK(13, 8) +#define I2C_INTERFACE_TIMING_TSU_STA GENMASK(5, 0) -#define I2C_HS_INTERFACE_TIMING_0 0x09c -#define I2C_HS_INTERFACE_TIMING_THIGH GENMASK(13, 8) -#define I2C_HS_INTERFACE_TIMING_TLOW GENMASK(5, 0) -#define I2C_HS_INTERFACE_TIMING_1 0x0a0 -#define I2C_HS_INTERFACE_TIMING_TSU_STO GENMASK(21, 16) -#define I2C_HS_INTERFACE_TIMING_THD_STA GENMASK(13, 8) -#define I2C_HS_INTERFACE_TIMING_TSU_STA GENMASK(5, 0) +#define I2C_HS_INTERFACE_TIMING_THIGH GENMASK(13, 8) +#define I2C_HS_INTERFACE_TIMING_TLOW GENMASK(5, 0) +#define I2C_HS_INTERFACE_TIMING_TSU_STO GENMASK(21, 16) +#define I2C_HS_INTERFACE_TIMING_THD_STA GENMASK(13, 8) +#define I2C_HS_INTERFACE_TIMING_TSU_STA GENMASK(5, 0) -#define I2C_MST_FIFO_CONTROL 0x0b4 #define I2C_MST_FIFO_CONTROL_RX_FLUSH BIT(0) #define I2C_MST_FIFO_CONTROL_TX_FLUSH BIT(1) #define I2C_MST_FIFO_CONTROL_RX_TRIG(x) (((x) - 1) << 4) #define I2C_MST_FIFO_CONTROL_TX_TRIG(x) (((x) - 1) << 16) -#define I2C_MST_FIFO_STATUS 0x0b8 #define I2C_MST_FIFO_STATUS_TX GENMASK(23, 16) #define I2C_MST_FIFO_STATUS_RX GENMASK(7, 0) -#define I2C_MASTER_RESET_CNTRL 0x0a8 - -#define I2C_SW_MUTEX 0x0ec #define I2C_SW_MUTEX_REQUEST GENMASK(3, 0) #define I2C_SW_MUTEX_GRANT GENMASK(7, 4) #define I2C_SW_MUTEX_ID_CCPLEX 9 @@ -159,6 +138,171 @@ */ #define I2C_PIO_MODE_PREFERRED_LEN 32 +struct tegra_i2c_regs { + unsigned int cnfg; + unsigned int status; + unsigned int sl_cnfg; + unsigned int sl_addr1; + unsigned int sl_addr2; + unsigned int tlow_sext; + unsigned int tx_fifo; + unsigned int rx_fifo; + unsigned int packet_transfer_status; + unsigned int fifo_control; + unsigned int fifo_status; + unsigned int int_mask; + unsigned int int_status; + unsigned int clk_divisor; + unsigned int bus_clear_cnfg; + unsigned int bus_clear_status; + unsigned int config_load; + unsigned int clken_override; + unsigned int interface_timing_0; + unsigned int interface_timing_1; + unsigned int hs_interface_timing_0; + unsigned int hs_interface_timing_1; + unsigned int master_reset_cntrl; + unsigned int mst_fifo_control; + unsigned int mst_fifo_status; + unsigned int sw_mutex; +}; + +static const struct tegra_i2c_regs tegra20_i2c_regs = { + .cnfg = 0x000, + .status = 0x01c, + .sl_cnfg = 0x020, + .sl_addr1 = 0x02c, + .sl_addr2 = 0x030, + .tx_fifo = 0x050, + .rx_fifo = 0x054, + .packet_transfer_status = 0x058, + .fifo_control = 0x05c, + .fifo_status = 0x060, + .int_mask = 0x064, + .int_status = 0x068, + .clk_divisor = 0x06c, + .bus_clear_cnfg = 0x084, + .bus_clear_status = 0x088, + .config_load = 0x08c, + .clken_override = 0x090, + .interface_timing_0 = 0x094, + .interface_timing_1 = 0x098, + .hs_interface_timing_0 = 0x09c, + .hs_interface_timing_1 = 0x0a0, + .master_reset_cntrl = 0x0a8, + .mst_fifo_control = 0x0b4, + .mst_fifo_status = 0x0b8, +}; + +#if IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) +static const struct tegra_i2c_regs tegra20_dvc_i2c_regs = { + .cnfg = 0x040, + .status = 0x05c, + .tx_fifo = 0x060, + .rx_fifo = 0x064, + .packet_transfer_status = 0x068, + .fifo_control = 0x06c, + .fifo_status = 0x070, + .int_mask = 0x074, + .int_status = 0x078, + .clk_divisor = 0x07c, + .bus_clear_cnfg = 0x094, + .bus_clear_status = 0x098, + .config_load = 0x09c, + .clken_override = 0x0a0, + .interface_timing_0 = 0x0a4, + .interface_timing_1 = 0x0a8, + .hs_interface_timing_0 = 0x0ac, + .hs_interface_timing_1 = 0x0b0, + .master_reset_cntrl = 0x0b8, + .mst_fifo_control = 0x0c4, + .mst_fifo_status = 0x0c8, +}; +#endif + +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) +static const struct tegra_i2c_regs tegra210_vi_i2c_regs = { + .cnfg = 0x0c00, + .status = 0x0c70, + .tlow_sext = 0x0cd0, + .tx_fifo = 0x0d40, + .rx_fifo = 0x0d50, + .packet_transfer_status = 0x0d60, + .fifo_control = 0x0d70, + .fifo_status = 0x0d80, + .int_mask = 0x0d90, + .int_status = 0x0da0, + .clk_divisor = 0x0db0, + .bus_clear_cnfg = 0x0e10, + .bus_clear_status = 0x0e20, + .config_load = 0x0e30, + .clken_override = 0x0e40, + .interface_timing_0 = 0x0e50, + .interface_timing_1 = 0x0e60, + .hs_interface_timing_0 = 0x0e70, + .hs_interface_timing_1 = 0x0e80, + .master_reset_cntrl = 0x0ea0, + .mst_fifo_control = 0x0ed0, + .mst_fifo_status = 0x0ee0, +}; +#endif + +static const struct tegra_i2c_regs tegra264_i2c_regs = { + .cnfg = 0x000, + .status = 0x01c, + .sl_cnfg = 0x020, + .sl_addr1 = 0x02c, + .sl_addr2 = 0x030, + .tx_fifo = 0x050, + .rx_fifo = 0x054, + .packet_transfer_status = 0x058, + .fifo_control = 0x05c, + .fifo_status = 0x060, + .int_mask = 0x064, + .int_status = 0x068, + .clk_divisor = 0x06c, + .bus_clear_cnfg = 0x084, + .bus_clear_status = 0x088, + .config_load = 0x08c, + .clken_override = 0x090, + .interface_timing_0 = 0x094, + .interface_timing_1 = 0x098, + .hs_interface_timing_0 = 0x09c, + .hs_interface_timing_1 = 0x0a0, + .master_reset_cntrl = 0x0a8, + .mst_fifo_control = 0x0b4, + .mst_fifo_status = 0x0b8, + .sw_mutex = 0x0ec, +}; + +static const struct tegra_i2c_regs tegra410_i2c_regs = { + .cnfg = 0x000, + .status = 0x01c, + .sl_cnfg = 0x020, + .sl_addr1 = 0x02c, + .sl_addr2 = 0x030, + .tx_fifo = 0x054, + .rx_fifo = 0x058, + .packet_transfer_status = 0x05c, + .fifo_control = 0x060, + .fifo_status = 0x064, + .int_mask = 0x068, + .int_status = 0x06c, + .clk_divisor = 0x070, + .bus_clear_cnfg = 0x088, + .bus_clear_status = 0x08c, + .config_load = 0x090, + .clken_override = 0x094, + .interface_timing_0 = 0x098, + .interface_timing_1 = 0x09c, + .hs_interface_timing_0 = 0x0a0, + .hs_interface_timing_1 = 0x0a4, + .master_reset_cntrl = 0x0ac, + .mst_fifo_control = 0x0b8, + .mst_fifo_status = 0x0bc, + .sw_mutex = 0x0f0, +}; + /* * msg_end_type: The bus control which needs to be sent at end of transfer. * @MSG_END_STOP: Send stop pulse. @@ -171,6 +315,18 @@ enum msg_end_type { MSG_END_CONTINUE, }; +/* + * tegra_i2c_variant: Identifies the variant of I2C controller. + * @TEGRA_I2C_VARIANT_DEFAULT: Identifies the default I2C controller. + * @TEGRA_I2C_VARIANT_DVC: Identifies the DVC I2C controller, has a different register layout. + * @TEGRA_I2C_VARIANT_VI: Identifies the VI I2C controller, has a different register layout. + */ +enum tegra_i2c_variant { + TEGRA_I2C_VARIANT_DEFAULT, + TEGRA_I2C_VARIANT_DVC, + TEGRA_I2C_VARIANT_VI, +}; + /** * struct tegra_i2c_hw_feature : per hardware generation features * @has_continue_xfer_support: continue-transfer supported @@ -223,6 +379,8 @@ enum msg_end_type { * timing settings. * @enable_hs_mode_support: Enable support for high speed (HS) mode transfers. * @has_mutex: Has mutex register for mutual exclusion with other firmwares or VMs. + * @variant: This represents the I2C controller variant. + * @regs: Register offsets for the specific SoC variant. */ struct tegra_i2c_hw_feature { bool has_continue_xfer_support; @@ -254,6 +412,8 @@ struct tegra_i2c_hw_feature { bool has_interface_timing_reg; bool enable_hs_mode_support; bool has_mutex; + enum tegra_i2c_variant variant; + const struct tegra_i2c_regs *regs; }; /** @@ -268,8 +428,6 @@ struct tegra_i2c_hw_feature { * @base_phys: physical base address of the I2C controller * @cont_id: I2C controller ID, used for packet header * @irq: IRQ number of transfer complete interrupt - * @is_dvc: identifies the DVC I2C controller, has a different register layout - * @is_vi: identifies the VI I2C controller, has a different register layout * @msg_complete: transfer completion notifier * @msg_buf_remaining: size of unsent data in the message buffer * @msg_len: length of message in current transfer @@ -321,12 +479,12 @@ struct tegra_i2c_dev { bool atomic_mode; bool dma_mode; bool msg_read; - bool is_dvc; - bool is_vi; }; -#define IS_DVC(dev) (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && (dev)->is_dvc) -#define IS_VI(dev) (IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) && (dev)->is_vi) +#define IS_DVC(dev) (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && \ + (dev)->hw->variant == TEGRA_I2C_VARIANT_DVC) +#define IS_VI(dev) (IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) && \ + (dev)->hw->variant == TEGRA_I2C_VARIANT_VI) static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned int reg) @@ -339,40 +497,26 @@ static u32 dvc_readl(struct tegra_i2c_dev *i2c_dev, unsigned int reg) return readl_relaxed(i2c_dev->base + reg); } -/* - * If necessary, i2c_writel() and i2c_readl() will offset the register - * in order to talk to the I2C block inside the DVC block. - */ -static u32 tegra_i2c_reg_addr(struct tegra_i2c_dev *i2c_dev, unsigned int reg) -{ - if (IS_DVC(i2c_dev)) - reg += (reg >= I2C_TX_FIFO) ? 0x10 : 0x40; - else if (IS_VI(i2c_dev)) - reg = 0xc00 + (reg << 2); - - return reg; -} - static void i2c_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned int reg) { - writel_relaxed(val, i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg)); + writel_relaxed(val, i2c_dev->base + reg); /* read back register to make sure that register writes completed */ - if (reg != I2C_TX_FIFO) - readl_relaxed(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg)); + if (reg != i2c_dev->hw->regs->tx_fifo) + readl_relaxed(i2c_dev->base + reg); else if (IS_VI(i2c_dev)) - readl_relaxed(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, I2C_INT_STATUS)); + readl_relaxed(i2c_dev->base + i2c_dev->hw->regs->int_status); } static u32 i2c_readl(struct tegra_i2c_dev *i2c_dev, unsigned int reg) { - return readl_relaxed(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg)); + return readl_relaxed(i2c_dev->base + reg); } static void i2c_writesl(struct tegra_i2c_dev *i2c_dev, void *data, unsigned int reg, unsigned int len) { - writesl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len); + writesl(i2c_dev->base + reg, data, len); } static void i2c_writesl_vi(struct tegra_i2c_dev *i2c_dev, void *data, @@ -393,12 +537,12 @@ static void i2c_writesl_vi(struct tegra_i2c_dev *i2c_dev, void *data, static void i2c_readsl(struct tegra_i2c_dev *i2c_dev, void *data, unsigned int reg, unsigned int len) { - readsl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len); + readsl(i2c_dev->base + reg, data, len); } static bool tegra_i2c_mutex_acquired(struct tegra_i2c_dev *i2c_dev) { - unsigned int reg = tegra_i2c_reg_addr(i2c_dev, I2C_SW_MUTEX); + unsigned int reg = i2c_dev->hw->regs->sw_mutex; u32 val, id; val = readl(i2c_dev->base + reg); @@ -409,7 +553,7 @@ static bool tegra_i2c_mutex_acquired(struct tegra_i2c_dev *i2c_dev) static bool tegra_i2c_mutex_trylock(struct tegra_i2c_dev *i2c_dev) { - unsigned int reg = tegra_i2c_reg_addr(i2c_dev, I2C_SW_MUTEX); + unsigned int reg = i2c_dev->hw->regs->sw_mutex; u32 val, id; val = readl(i2c_dev->base + reg); @@ -447,7 +591,7 @@ static int tegra_i2c_mutex_lock(struct tegra_i2c_dev *i2c_dev) static int tegra_i2c_mutex_unlock(struct tegra_i2c_dev *i2c_dev) { - unsigned int reg = tegra_i2c_reg_addr(i2c_dev, I2C_SW_MUTEX); + unsigned int reg = i2c_dev->hw->regs->sw_mutex; u32 val, id; if (!i2c_dev->hw->has_mutex) @@ -470,16 +614,16 @@ static void tegra_i2c_mask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) { u32 int_mask; - int_mask = i2c_readl(i2c_dev, I2C_INT_MASK) & ~mask; - i2c_writel(i2c_dev, int_mask, I2C_INT_MASK); + int_mask = i2c_readl(i2c_dev, i2c_dev->hw->regs->int_mask) & ~mask; + i2c_writel(i2c_dev, int_mask, i2c_dev->hw->regs->int_mask); } static void tegra_i2c_unmask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) { u32 int_mask; - int_mask = i2c_readl(i2c_dev, I2C_INT_MASK) | mask; - i2c_writel(i2c_dev, int_mask, I2C_INT_MASK); + int_mask = i2c_readl(i2c_dev, i2c_dev->hw->regs->int_mask) | mask; + i2c_writel(i2c_dev, int_mask, i2c_dev->hw->regs->int_mask); } static void tegra_i2c_dma_complete(void *args) @@ -623,34 +767,34 @@ static void tegra_i2c_vi_init(struct tegra_i2c_dev *i2c_dev) value = FIELD_PREP(I2C_INTERFACE_TIMING_THIGH, 2) | FIELD_PREP(I2C_INTERFACE_TIMING_TLOW, 4); - i2c_writel(i2c_dev, value, I2C_INTERFACE_TIMING_0); + i2c_writel(i2c_dev, value, i2c_dev->hw->regs->interface_timing_0); value = FIELD_PREP(I2C_INTERFACE_TIMING_TBUF, 4) | FIELD_PREP(I2C_INTERFACE_TIMING_TSU_STO, 7) | FIELD_PREP(I2C_INTERFACE_TIMING_THD_STA, 4) | FIELD_PREP(I2C_INTERFACE_TIMING_TSU_STA, 4); - i2c_writel(i2c_dev, value, I2C_INTERFACE_TIMING_1); + i2c_writel(i2c_dev, value, i2c_dev->hw->regs->interface_timing_1); value = FIELD_PREP(I2C_HS_INTERFACE_TIMING_THIGH, 3) | FIELD_PREP(I2C_HS_INTERFACE_TIMING_TLOW, 8); - i2c_writel(i2c_dev, value, I2C_HS_INTERFACE_TIMING_0); + i2c_writel(i2c_dev, value, i2c_dev->hw->regs->hs_interface_timing_0); value = FIELD_PREP(I2C_HS_INTERFACE_TIMING_TSU_STO, 11) | FIELD_PREP(I2C_HS_INTERFACE_TIMING_THD_STA, 11) | FIELD_PREP(I2C_HS_INTERFACE_TIMING_TSU_STA, 11); - i2c_writel(i2c_dev, value, I2C_HS_INTERFACE_TIMING_1); + i2c_writel(i2c_dev, value, i2c_dev->hw->regs->hs_interface_timing_1); value = FIELD_PREP(I2C_BC_SCLK_THRESHOLD, 9) | I2C_BC_STOP_COND; - i2c_writel(i2c_dev, value, I2C_BUS_CLEAR_CNFG); + i2c_writel(i2c_dev, value, i2c_dev->hw->regs->bus_clear_cnfg); - i2c_writel(i2c_dev, 0x0, I2C_TLOW_SEXT); + i2c_writel(i2c_dev, 0x0, i2c_dev->hw->regs->tlow_sext); } static int tegra_i2c_poll_register(struct tegra_i2c_dev *i2c_dev, u32 reg, u32 mask, u32 delay_us, u32 timeout_us) { - void __iomem *addr = i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg); + void __iomem *addr = i2c_dev->base + reg; u32 val; if (!i2c_dev->atomic_mode) @@ -669,11 +813,11 @@ static int tegra_i2c_flush_fifos(struct tegra_i2c_dev *i2c_dev) if (i2c_dev->hw->has_mst_fifo) { mask = I2C_MST_FIFO_CONTROL_TX_FLUSH | I2C_MST_FIFO_CONTROL_RX_FLUSH; - offset = I2C_MST_FIFO_CONTROL; + offset = i2c_dev->hw->regs->mst_fifo_control; } else { mask = I2C_FIFO_CONTROL_TX_FLUSH | I2C_FIFO_CONTROL_RX_FLUSH; - offset = I2C_FIFO_CONTROL; + offset = i2c_dev->hw->regs->fifo_control; } val = i2c_readl(i2c_dev, offset); @@ -696,9 +840,9 @@ static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev) if (!i2c_dev->hw->has_config_load_reg) return 0; - i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD); + i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, i2c_dev->hw->regs->config_load); - err = tegra_i2c_poll_register(i2c_dev, I2C_CONFIG_LOAD, 0xffffffff, + err = tegra_i2c_poll_register(i2c_dev, i2c_dev->hw->regs->config_load, 0xffffffff, 1000, I2C_CONFIG_LOAD_TIMEOUT); if (err) { dev_err(i2c_dev->dev, "failed to load config\n"); @@ -719,10 +863,10 @@ static int tegra_i2c_master_reset(struct tegra_i2c_dev *i2c_dev) * SW needs to wait for 2us after assertion and de-assertion of this soft * reset. */ - i2c_writel(i2c_dev, 0x1, I2C_MASTER_RESET_CNTRL); + i2c_writel(i2c_dev, 0x1, i2c_dev->hw->regs->master_reset_cntrl); fsleep(2); - i2c_writel(i2c_dev, 0x0, I2C_MASTER_RESET_CNTRL); + i2c_writel(i2c_dev, 0x0, i2c_dev->hw->regs->master_reset_cntrl); fsleep(2); return 0; @@ -764,8 +908,8 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (i2c_dev->hw->has_multi_master_mode) val |= I2C_CNFG_MULTI_MASTER_MODE; - i2c_writel(i2c_dev, val, I2C_CNFG); - i2c_writel(i2c_dev, 0, I2C_INT_MASK); + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->cnfg); + i2c_writel(i2c_dev, 0, i2c_dev->hw->regs->int_mask); if (IS_VI(i2c_dev)) tegra_i2c_vi_init(i2c_dev); @@ -810,12 +954,12 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) clk_divisor = FIELD_PREP(I2C_CLK_DIVISOR_HSMODE, i2c_dev->hw->clk_divisor_hs_mode) | FIELD_PREP(I2C_CLK_DIVISOR_STD_FAST_MODE, non_hs_mode); - i2c_writel(i2c_dev, clk_divisor, I2C_CLK_DIVISOR); + i2c_writel(i2c_dev, clk_divisor, i2c_dev->hw->regs->clk_divisor); if (i2c_dev->hw->has_interface_timing_reg) { val = FIELD_PREP(I2C_INTERFACE_TIMING_THIGH, thigh) | FIELD_PREP(I2C_INTERFACE_TIMING_TLOW, tlow); - i2c_writel(i2c_dev, val, I2C_INTERFACE_TIMING_0); + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->interface_timing_0); } /* @@ -823,7 +967,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) * Otherwise, preserve the chip default values. */ if (i2c_dev->hw->has_interface_timing_reg && tsu_thd) - i2c_writel(i2c_dev, tsu_thd, I2C_INTERFACE_TIMING_1); + i2c_writel(i2c_dev, tsu_thd, i2c_dev->hw->regs->interface_timing_1); /* Write HS mode registers. These will get used only for HS mode*/ if (i2c_dev->hw->enable_hs_mode_support) { @@ -833,8 +977,8 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) val = FIELD_PREP(I2C_HS_INTERFACE_TIMING_THIGH, thigh) | FIELD_PREP(I2C_HS_INTERFACE_TIMING_TLOW, tlow); - i2c_writel(i2c_dev, val, I2C_HS_INTERFACE_TIMING_0); - i2c_writel(i2c_dev, tsu_thd, I2C_HS_INTERFACE_TIMING_1); + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->hs_interface_timing_0); + i2c_writel(i2c_dev, tsu_thd, i2c_dev->hw->regs->hs_interface_timing_1); } clk_multiplier = (tlow + thigh + 2) * (non_hs_mode + 1); @@ -847,12 +991,12 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) } if (!IS_DVC(i2c_dev) && !IS_VI(i2c_dev)) { - u32 sl_cfg = i2c_readl(i2c_dev, I2C_SL_CNFG); + u32 sl_cfg = i2c_readl(i2c_dev, i2c_dev->hw->regs->sl_cnfg); sl_cfg |= I2C_SL_CNFG_NACK | I2C_SL_CNFG_NEWSL; - i2c_writel(i2c_dev, sl_cfg, I2C_SL_CNFG); - i2c_writel(i2c_dev, 0xfc, I2C_SL_ADDR1); - i2c_writel(i2c_dev, 0x00, I2C_SL_ADDR2); + i2c_writel(i2c_dev, sl_cfg, i2c_dev->hw->regs->sl_cnfg); + i2c_writel(i2c_dev, 0xfc, i2c_dev->hw->regs->sl_addr1); + i2c_writel(i2c_dev, 0x00, i2c_dev->hw->regs->sl_addr2); } err = tegra_i2c_flush_fifos(i2c_dev); @@ -860,7 +1004,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) return err; if (i2c_dev->multimaster_mode && i2c_dev->hw->has_slcg_override_reg) - i2c_writel(i2c_dev, I2C_MST_CORE_CLKEN_OVR, I2C_CLKEN_OVERRIDE); + i2c_writel(i2c_dev, I2C_MST_CORE_CLKEN_OVR, i2c_dev->hw->regs->clken_override); err = tegra_i2c_wait_for_config_load(i2c_dev); if (err) @@ -881,9 +1025,9 @@ static int tegra_i2c_disable_packet_mode(struct tegra_i2c_dev *i2c_dev) */ udelay(DIV_ROUND_UP(2 * 1000000, i2c_dev->timings.bus_freq_hz)); - cnfg = i2c_readl(i2c_dev, I2C_CNFG); + cnfg = i2c_readl(i2c_dev, i2c_dev->hw->regs->cnfg); if (cnfg & I2C_CNFG_PACKET_MODE_EN) - i2c_writel(i2c_dev, cnfg & ~I2C_CNFG_PACKET_MODE_EN, I2C_CNFG); + i2c_writel(i2c_dev, cnfg & ~I2C_CNFG_PACKET_MODE_EN, i2c_dev->hw->regs->cnfg); return tegra_i2c_wait_for_config_load(i2c_dev); } @@ -903,10 +1047,10 @@ static int tegra_i2c_empty_rx_fifo(struct tegra_i2c_dev *i2c_dev) return -EINVAL; if (i2c_dev->hw->has_mst_fifo) { - val = i2c_readl(i2c_dev, I2C_MST_FIFO_STATUS); + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->mst_fifo_status); rx_fifo_avail = FIELD_GET(I2C_MST_FIFO_STATUS_RX, val); } else { - val = i2c_readl(i2c_dev, I2C_FIFO_STATUS); + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->fifo_status); rx_fifo_avail = FIELD_GET(I2C_FIFO_STATUS_RX, val); } @@ -915,7 +1059,7 @@ static int tegra_i2c_empty_rx_fifo(struct tegra_i2c_dev *i2c_dev) if (words_to_transfer > rx_fifo_avail) words_to_transfer = rx_fifo_avail; - i2c_readsl(i2c_dev, buf, I2C_RX_FIFO, words_to_transfer); + i2c_readsl(i2c_dev, buf, i2c_dev->hw->regs->rx_fifo, words_to_transfer); buf += words_to_transfer * BYTES_PER_FIFO_WORD; buf_remaining -= words_to_transfer * BYTES_PER_FIFO_WORD; @@ -931,7 +1075,7 @@ static int tegra_i2c_empty_rx_fifo(struct tegra_i2c_dev *i2c_dev) * when (words_to_transfer was > rx_fifo_avail) earlier * in this function. */ - val = i2c_readl(i2c_dev, I2C_RX_FIFO); + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->rx_fifo); val = cpu_to_le32(val); memcpy(buf, &val, buf_remaining); buf_remaining = 0; @@ -956,10 +1100,10 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) u32 val; if (i2c_dev->hw->has_mst_fifo) { - val = i2c_readl(i2c_dev, I2C_MST_FIFO_STATUS); + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->mst_fifo_status); tx_fifo_avail = FIELD_GET(I2C_MST_FIFO_STATUS_TX, val); } else { - val = i2c_readl(i2c_dev, I2C_FIFO_STATUS); + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->fifo_status); tx_fifo_avail = FIELD_GET(I2C_FIFO_STATUS_TX, val); } @@ -990,9 +1134,9 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) i2c_dev->msg_buf = buf + words_to_transfer * BYTES_PER_FIFO_WORD; if (IS_VI(i2c_dev)) - i2c_writesl_vi(i2c_dev, buf, I2C_TX_FIFO, words_to_transfer); + i2c_writesl_vi(i2c_dev, buf, i2c_dev->hw->regs->tx_fifo, words_to_transfer); else - i2c_writesl(i2c_dev, buf, I2C_TX_FIFO, words_to_transfer); + i2c_writesl(i2c_dev, buf, i2c_dev->hw->regs->tx_fifo, words_to_transfer); buf += words_to_transfer * BYTES_PER_FIFO_WORD; } @@ -1014,7 +1158,7 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) i2c_dev->msg_buf_remaining = 0; i2c_dev->msg_buf = NULL; - i2c_writel(i2c_dev, val, I2C_TX_FIFO); + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->tx_fifo); } return 0; @@ -1026,13 +1170,13 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) struct tegra_i2c_dev *i2c_dev = dev_id; u32 status; - status = i2c_readl(i2c_dev, I2C_INT_STATUS); + status = i2c_readl(i2c_dev, i2c_dev->hw->regs->int_status); if (status == 0) { dev_warn(i2c_dev->dev, "IRQ status 0 %08x %08x %08x\n", - i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS), - i2c_readl(i2c_dev, I2C_STATUS), - i2c_readl(i2c_dev, I2C_CNFG)); + i2c_readl(i2c_dev, i2c_dev->hw->regs->packet_transfer_status), + i2c_readl(i2c_dev, i2c_dev->hw->regs->status), + i2c_readl(i2c_dev, i2c_dev->hw->regs->cnfg)); i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT; goto err; } @@ -1075,7 +1219,7 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) } } - i2c_writel(i2c_dev, status, I2C_INT_STATUS); + i2c_writel(i2c_dev, status, i2c_dev->hw->regs->int_status); if (IS_DVC(i2c_dev)) dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); @@ -1113,7 +1257,7 @@ err: if (i2c_dev->hw->supports_bus_clear) tegra_i2c_mask_irq(i2c_dev, I2C_INT_BUS_CLR_DONE); - i2c_writel(i2c_dev, status, I2C_INT_STATUS); + i2c_writel(i2c_dev, status, i2c_dev->hw->regs->int_status); if (IS_DVC(i2c_dev)) dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); @@ -1136,9 +1280,9 @@ static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev, int err; if (i2c_dev->hw->has_mst_fifo) - reg = I2C_MST_FIFO_CONTROL; + reg = i2c_dev->hw->regs->mst_fifo_control; else - reg = I2C_FIFO_CONTROL; + reg = i2c_dev->hw->regs->fifo_control; if (i2c_dev->dma_mode) { if (len & 0xF) @@ -1149,7 +1293,7 @@ static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev, dma_burst = 8; if (i2c_dev->msg_read) { - reg_offset = tegra_i2c_reg_addr(i2c_dev, I2C_RX_FIFO); + reg_offset = i2c_dev->hw->regs->rx_fifo; slv_config.src_addr = i2c_dev->base_phys + reg_offset; slv_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; @@ -1160,7 +1304,7 @@ static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev, else val = I2C_FIFO_CONTROL_RX_TRIG(dma_burst); } else { - reg_offset = tegra_i2c_reg_addr(i2c_dev, I2C_TX_FIFO); + reg_offset = i2c_dev->hw->regs->tx_fifo; slv_config.dst_addr = i2c_dev->base_phys + reg_offset; slv_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; @@ -1203,7 +1347,7 @@ static unsigned long tegra_i2c_poll_completion(struct tegra_i2c_dev *i2c_dev, ktime_t ktimeout = ktime_add_ms(ktime, timeout_ms); do { - u32 status = i2c_readl(i2c_dev, I2C_INT_STATUS); + u32 status = i2c_readl(i2c_dev, i2c_dev->hw->regs->int_status); if (status) tegra_i2c_isr(i2c_dev->irq, i2c_dev); @@ -1262,14 +1406,14 @@ static int tegra_i2c_issue_bus_clear(struct i2c_adapter *adap) val = FIELD_PREP(I2C_BC_SCLK_THRESHOLD, 9) | I2C_BC_STOP_COND | I2C_BC_TERMINATE; - i2c_writel(i2c_dev, val, I2C_BUS_CLEAR_CNFG); + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->bus_clear_cnfg); err = tegra_i2c_wait_for_config_load(i2c_dev); if (err) return err; val |= I2C_BC_ENABLE; - i2c_writel(i2c_dev, val, I2C_BUS_CLEAR_CNFG); + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->bus_clear_cnfg); tegra_i2c_unmask_irq(i2c_dev, I2C_INT_BUS_CLR_DONE); time_left = tegra_i2c_wait_completion(i2c_dev, &i2c_dev->msg_complete, 50); @@ -1280,7 +1424,7 @@ static int tegra_i2c_issue_bus_clear(struct i2c_adapter *adap) return -ETIMEDOUT; } - val = i2c_readl(i2c_dev, I2C_BUS_CLEAR_STATUS); + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->bus_clear_status); if (!(val & I2C_BC_STATUS)) { dev_err(i2c_dev->dev, "un-recovered arbitration lost\n"); return -EIO; @@ -1305,14 +1449,14 @@ static void tegra_i2c_push_packet_header(struct tegra_i2c_dev *i2c_dev, if (i2c_dev->dma_mode && !i2c_dev->msg_read) *dma_buf++ = packet_header; else - i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO); + i2c_writel(i2c_dev, packet_header, i2c_dev->hw->regs->tx_fifo); packet_header = i2c_dev->msg_len - 1; if (i2c_dev->dma_mode && !i2c_dev->msg_read) *dma_buf++ = packet_header; else - i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO); + i2c_writel(i2c_dev, packet_header, i2c_dev->hw->regs->tx_fifo); packet_header = I2C_HEADER_IE_ENABLE; @@ -1340,7 +1484,7 @@ static void tegra_i2c_push_packet_header(struct tegra_i2c_dev *i2c_dev, if (i2c_dev->dma_mode && !i2c_dev->msg_read) *dma_buf++ = packet_header; else - i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO); + i2c_writel(i2c_dev, packet_header, i2c_dev->hw->regs->tx_fifo); } static int tegra_i2c_error_recover(struct tegra_i2c_dev *i2c_dev, @@ -1461,7 +1605,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev, tegra_i2c_unmask_irq(i2c_dev, int_mask); dev_dbg(i2c_dev->dev, "unmasked IRQ: %02x\n", - i2c_readl(i2c_dev, I2C_INT_MASK)); + i2c_readl(i2c_dev, i2c_dev->hw->regs->int_mask)); if (i2c_dev->dma_mode) { time_left = tegra_i2c_wait_completion(i2c_dev, @@ -1635,8 +1779,44 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) +static const struct tegra_i2c_hw_feature tegra20_dvc_i2c_hw = { + .has_continue_xfer_support = false, + .has_per_pkt_xfer_complete_irq = false, + .clk_divisor_hs_mode = 3, + .clk_divisor_std_mode = 0, + .clk_divisor_fast_mode = 0, + .clk_divisor_fast_plus_mode = 0, + .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, + .has_mst_fifo = false, + .has_mst_reset = false, + .quirks = &tegra_i2c_quirks, + .supports_bus_clear = false, + .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0x0, + .setup_hold_time_fast_mode = 0x0, + .setup_hold_time_fastplus_mode = 0x0, + .setup_hold_time_hs_mode = 0x0, + .has_interface_timing_reg = false, + .enable_hs_mode_support = false, + .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DVC, + .regs = &tegra20_dvc_i2c_regs, +}; +#endif + static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .has_continue_xfer_support = true, .has_per_pkt_xfer_complete_irq = false, @@ -1665,6 +1845,8 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { @@ -1695,6 +1877,8 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .has_interface_timing_reg = false, .enable_hs_mode_support = false, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { @@ -1725,6 +1909,8 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { @@ -1755,8 +1941,44 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) +static const struct tegra_i2c_hw_feature tegra210_vi_i2c_hw = { + .has_continue_xfer_support = true, + .has_per_pkt_xfer_complete_irq = true, + .clk_divisor_hs_mode = 1, + .clk_divisor_std_mode = 0x19, + .clk_divisor_fast_mode = 0x19, + .clk_divisor_fast_plus_mode = 0x10, + .has_config_load_reg = true, + .has_multi_master_mode = false, + .has_slcg_override_reg = true, + .has_mst_fifo = false, + .has_mst_reset = false, + .quirks = &tegra_i2c_quirks, + .supports_bus_clear = true, + .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_mode = 0x4, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x4, + .thigh_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0, + .setup_hold_time_fast_mode = 0, + .setup_hold_time_fastplus_mode = 0, + .setup_hold_time_hs_mode = 0, + .has_interface_timing_reg = true, + .enable_hs_mode_support = false, + .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_VI, + .regs = &tegra210_vi_i2c_regs, +}; +#endif + static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .has_continue_xfer_support = true, .has_per_pkt_xfer_complete_irq = true, @@ -1785,6 +2007,8 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = false, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { @@ -1817,6 +2041,8 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = false, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra20_i2c_regs, }; static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { @@ -1849,6 +2075,8 @@ static const struct tegra_i2c_hw_feature tegra256_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = true, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra264_i2c_regs, }; static const struct tegra_i2c_hw_feature tegra264_i2c_hw = { @@ -1881,6 +2109,42 @@ static const struct tegra_i2c_hw_feature tegra264_i2c_hw = { .has_interface_timing_reg = true, .enable_hs_mode_support = true, .has_mutex = true, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra264_i2c_regs, +}; + +static const struct tegra_i2c_hw_feature tegra410_i2c_hw = { + .has_continue_xfer_support = true, + .has_per_pkt_xfer_complete_irq = true, + .clk_divisor_hs_mode = 1, + .clk_divisor_std_mode = 0x3f, + .clk_divisor_fast_mode = 0x2c, + .clk_divisor_fast_plus_mode = 0x11, + .has_config_load_reg = true, + .has_multi_master_mode = true, + .has_slcg_override_reg = true, + .has_mst_fifo = true, + .has_mst_reset = true, + .quirks = &tegra194_i2c_quirks, + .supports_bus_clear = true, + .has_apb_dma = false, + .tlow_std_mode = 0x8, + .thigh_std_mode = 0x7, + .tlow_fast_mode = 0x2, + .thigh_fast_mode = 0x2, + .tlow_fastplus_mode = 0x2, + .thigh_fastplus_mode = 0x2, + .tlow_hs_mode = 0x8, + .thigh_hs_mode = 0x6, + .setup_hold_time_std_mode = 0x08080808, + .setup_hold_time_fast_mode = 0x02020202, + .setup_hold_time_fastplus_mode = 0x02020202, + .setup_hold_time_hs_mode = 0x0b0b0b, + .has_interface_timing_reg = true, + .enable_hs_mode_support = true, + .has_mutex = true, + .variant = TEGRA_I2C_VARIANT_DEFAULT, + .regs = &tegra410_i2c_regs, }; static const struct of_device_id tegra_i2c_of_match[] = { @@ -1889,7 +2153,7 @@ static const struct of_device_id tegra_i2c_of_match[] = { { .compatible = "nvidia,tegra194-i2c", .data = &tegra194_i2c_hw, }, { .compatible = "nvidia,tegra186-i2c", .data = &tegra186_i2c_hw, }, #if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) - { .compatible = "nvidia,tegra210-i2c-vi", .data = &tegra210_i2c_hw, }, + { .compatible = "nvidia,tegra210-i2c-vi", .data = &tegra210_vi_i2c_hw, }, #endif { .compatible = "nvidia,tegra210-i2c", .data = &tegra210_i2c_hw, }, { .compatible = "nvidia,tegra124-i2c", .data = &tegra124_i2c_hw, }, @@ -1897,7 +2161,7 @@ static const struct of_device_id tegra_i2c_of_match[] = { { .compatible = "nvidia,tegra30-i2c", .data = &tegra30_i2c_hw, }, { .compatible = "nvidia,tegra20-i2c", .data = &tegra20_i2c_hw, }, #if IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) - { .compatible = "nvidia,tegra20-i2c-dvc", .data = &tegra20_i2c_hw, }, + { .compatible = "nvidia,tegra20-i2c-dvc", .data = &tegra20_dvc_i2c_hw, }, #endif {}, }; @@ -1905,21 +2169,12 @@ MODULE_DEVICE_TABLE(of, tegra_i2c_of_match); static void tegra_i2c_parse_dt(struct tegra_i2c_dev *i2c_dev) { - struct device_node *np = i2c_dev->dev->of_node; bool multi_mode; i2c_parse_fw_timings(i2c_dev->dev, &i2c_dev->timings, true); multi_mode = device_property_read_bool(i2c_dev->dev, "multi-master"); i2c_dev->multimaster_mode = multi_mode; - - if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) && - of_device_is_compatible(np, "nvidia,tegra20-i2c-dvc")) - i2c_dev->is_dvc = true; - - if (IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) && - of_device_is_compatible(np, "nvidia,tegra210-i2c-vi")) - i2c_dev->is_vi = true; } static int tegra_i2c_init_clocks(struct tegra_i2c_dev *i2c_dev) @@ -2205,6 +2460,7 @@ static const struct acpi_device_id tegra_i2c_acpi_match[] = { {.id = "NVDA0101", .driver_data = (kernel_ulong_t)&tegra210_i2c_hw}, {.id = "NVDA0201", .driver_data = (kernel_ulong_t)&tegra186_i2c_hw}, {.id = "NVDA0301", .driver_data = (kernel_ulong_t)&tegra194_i2c_hw}, + {.id = "NVDA2017", .driver_data = (kernel_ulong_t)&tegra410_i2c_hw}, { } }; MODULE_DEVICE_TABLE(acpi, tegra_i2c_acpi_match); diff --git a/drivers/i2c/busses/i2c-tiny-usb.c b/drivers/i2c/busses/i2c-tiny-usb.c index 9ef495f88ef2..88d66593d9fc 100644 --- a/drivers/i2c/busses/i2c-tiny-usb.c +++ b/drivers/i2c/busses/i2c-tiny-usb.c @@ -213,12 +213,6 @@ static int usb_write(struct i2c_adapter *adapter, int cmd, return ret; } -static void i2c_tiny_usb_free(struct i2c_tiny_usb *dev) -{ - usb_put_dev(dev->usb_dev); - kfree(dev); -} - static int i2c_tiny_usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -237,7 +231,7 @@ static int i2c_tiny_usb_probe(struct usb_interface *interface, if (!dev) goto error; - dev->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + dev->usb_dev = interface_to_usbdev(interface); dev->interface = interface; /* save our data pointer in this interface device */ @@ -277,8 +271,7 @@ static int i2c_tiny_usb_probe(struct usb_interface *interface, return 0; error: - if (dev) - i2c_tiny_usb_free(dev); + kfree(dev); return retval; } @@ -289,7 +282,7 @@ static void i2c_tiny_usb_disconnect(struct usb_interface *interface) i2c_del_adapter(&dev->adapter); usb_set_intfdata(interface, NULL); - i2c_tiny_usb_free(dev); + kfree(dev); dev_dbg(&interface->dev, "disconnected\n"); } diff --git a/drivers/i2c/busses/i2c-usbio.c b/drivers/i2c/busses/i2c-usbio.c index e7799abf6787..259754e5fd05 100644 --- a/drivers/i2c/busses/i2c-usbio.c +++ b/drivers/i2c/busses/i2c-usbio.c @@ -29,6 +29,7 @@ static const struct acpi_device_id usbio_i2c_acpi_hids[] = { { "INTC10B6" }, /* LNL */ { "INTC10D2" }, /* MTL-CVF */ { "INTC10E3" }, /* PTL */ + { "INTC1118" }, /* NVL */ { } }; diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index 28015d77599d..3e7735e1dae0 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -1408,7 +1407,6 @@ static const struct i2c_adapter xiic_adapter = { .algo = &xiic_algorithm, }; -#if defined(CONFIG_OF) static const struct xiic_version_data xiic_2_00 = { .quirks = DYNAMIC_MODE_READ_BROKEN_BIT, }; @@ -1419,28 +1417,26 @@ static const struct of_device_id xiic_of_match[] = { {}, }; MODULE_DEVICE_TABLE(of, xiic_of_match); -#endif static int xiic_i2c_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct fwnode_handle *fwnode = dev_fwnode(dev); struct xiic_i2c *i2c; struct xiic_i2c_platform_data *pdata; - const struct of_device_id *match; + const struct xiic_version_data *data; struct resource *res; int ret, irq; u8 i; u32 sr; - i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); if (!i2c) return -ENOMEM; - match = of_match_node(xiic_of_match, pdev->dev.of_node); - if (match && match->data) { - const struct xiic_version_data *data = match->data; - + data = device_get_match_data(dev); + if (data) i2c->quirks = data->quirks; - } i2c->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(i2c->base)) @@ -1450,50 +1446,52 @@ static int xiic_i2c_probe(struct platform_device *pdev) if (irq < 0) return irq; - pdata = dev_get_platdata(&pdev->dev); + pdata = dev_get_platdata(dev); /* hook up driver to tree */ platform_set_drvdata(pdev, i2c); i2c->adap = xiic_adapter; + i2c->adap.nr = pdev->id; i2c_set_adapdata(&i2c->adap, i2c); i2c->adap.dev.parent = &pdev->dev; - i2c->adap.dev.of_node = pdev->dev.of_node; + device_set_node(&i2c->adap.dev, fwnode); snprintf(i2c->adap.name, sizeof(i2c->adap.name), DRIVER_NAME " %s", pdev->name); - mutex_init(&i2c->lock); + ret = devm_mutex_init(dev, &i2c->lock); + if (ret) + return ret; + spin_lock_init(&i2c->atomic_lock); - i2c->clk = devm_clk_get_enabled(&pdev->dev, NULL); - if (IS_ERR(i2c->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(i2c->clk), - "failed to enable input clock.\n"); + if (is_of_node(fwnode)) { + i2c->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(i2c->clk)) + return dev_err_probe(dev, PTR_ERR(i2c->clk), + "failed to enable input clock.\n"); + } - i2c->dev = &pdev->dev; - pm_runtime_set_autosuspend_delay(i2c->dev, XIIC_PM_TIMEOUT); - pm_runtime_use_autosuspend(i2c->dev); - pm_runtime_set_active(i2c->dev); - pm_runtime_enable(i2c->dev); + i2c->dev = dev; + + pm_runtime_set_autosuspend_delay(dev, XIIC_PM_TIMEOUT); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; /* SCL frequency configuration */ i2c->input_clk = clk_get_rate(i2c->clk); - ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", - &i2c->i2c_clk); + ret = device_property_read_u32(dev, "clock-frequency", &i2c->i2c_clk); /* If clock-frequency not specified in DT, do not configure in SW */ if (ret || i2c->i2c_clk > I2C_MAX_FAST_MODE_PLUS_FREQ) i2c->i2c_clk = 0; - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - xiic_process, IRQF_ONESHOT, - pdev->name, i2c); + ret = devm_request_threaded_irq(dev, irq, NULL, xiic_process, + IRQF_ONESHOT, pdev->name, i2c); + if (ret) + return ret; - if (ret < 0) { - dev_err_probe(&pdev->dev, ret, "Cannot claim IRQ\n"); - goto err_pm_disable; - } - - i2c->singlemaster = - of_property_read_bool(pdev->dev.of_node, "single-master"); + i2c->singlemaster = device_property_read_bool(dev, "single-master"); /* * Detect endianness @@ -1508,16 +1506,14 @@ static int xiic_i2c_probe(struct platform_device *pdev) i2c->endianness = BIG; ret = xiic_reinit(i2c); - if (ret < 0) { - dev_err_probe(&pdev->dev, ret, "Cannot xiic_reinit\n"); - goto err_pm_disable; - } + if (ret) + return dev_err_probe(dev, ret, "Cannot xiic_reinit\n"); /* add i2c adapter to i2c tree */ - ret = i2c_add_adapter(&i2c->adap); + ret = i2c_add_numbered_adapter(&i2c->adap); if (ret) { xiic_deinit(i2c); - goto err_pm_disable; + return ret; } if (pdata) { @@ -1526,38 +1522,29 @@ static int xiic_i2c_probe(struct platform_device *pdev) i2c_new_client_device(&i2c->adap, pdata->devices + i); } - dev_dbg(&pdev->dev, "mmio %08lx irq %d scl clock frequency %d\n", - (unsigned long)res->start, irq, i2c->i2c_clk); + dev_dbg(dev, "mmio %pR irq %d scl clock frequency %d\n", + res, irq, i2c->i2c_clk); return 0; - -err_pm_disable: - pm_runtime_disable(&pdev->dev); - pm_runtime_set_suspended(&pdev->dev); - - return ret; } static void xiic_i2c_remove(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct xiic_i2c *i2c = platform_get_drvdata(pdev); int ret; /* remove adapter & data */ i2c_del_adapter(&i2c->adap); - ret = pm_runtime_get_sync(i2c->dev); - + ret = pm_runtime_get_sync(dev); if (ret < 0) - dev_warn(&pdev->dev, "Failed to activate device for removal (%pe)\n", + dev_warn(dev, "Failed to activate device for removal (%pe)\n", ERR_PTR(ret)); else xiic_deinit(i2c); - pm_runtime_put_sync(i2c->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_set_suspended(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_put_sync(dev); } static const struct dev_pm_ops xiic_dev_pm_ops = { diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c index f9fcb4793aaf..e6d2af659d81 100644 --- a/drivers/i2c/i2c-atr.c +++ b/drivers/i2c/i2c-atr.c @@ -49,8 +49,8 @@ struct i2c_atr_alias_pair { * @shared: Indicates if this alias pool is shared by multiple channels * * @lock: Lock protecting @aliases and @use_mask - * @aliases: Array of aliases, must hold exactly @size elements * @use_mask: Mask of used aliases + * @aliases: Array of aliases, must hold exactly @size elements */ struct i2c_atr_alias_pool { size_t size; @@ -58,8 +58,8 @@ struct i2c_atr_alias_pool { /* Protects aliases and use_mask */ spinlock_t lock; - u16 *aliases; unsigned long *use_mask; + u16 aliases[] __counted_by(size); }; /** @@ -137,22 +137,16 @@ static struct i2c_atr_alias_pool *i2c_atr_alloc_alias_pool(size_t num_aliases, b struct i2c_atr_alias_pool *alias_pool; int ret; - alias_pool = kzalloc_obj(*alias_pool); + alias_pool = kzalloc_flex(*alias_pool, aliases, num_aliases); if (!alias_pool) return ERR_PTR(-ENOMEM); alias_pool->size = num_aliases; - alias_pool->aliases = kcalloc(num_aliases, sizeof(*alias_pool->aliases), GFP_KERNEL); - if (!alias_pool->aliases) { - ret = -ENOMEM; - goto err_free_alias_pool; - } - alias_pool->use_mask = bitmap_zalloc(num_aliases, GFP_KERNEL); if (!alias_pool->use_mask) { ret = -ENOMEM; - goto err_free_aliases; + goto err_free_alias_pool; } alias_pool->shared = shared; @@ -161,8 +155,6 @@ static struct i2c_atr_alias_pool *i2c_atr_alloc_alias_pool(size_t num_aliases, b return alias_pool; -err_free_aliases: - kfree(alias_pool->aliases); err_free_alias_pool: kfree(alias_pool); return ERR_PTR(ret); @@ -171,7 +163,6 @@ err_free_alias_pool: static void i2c_atr_free_alias_pool(struct i2c_atr_alias_pool *alias_pool) { bitmap_free(alias_pool->use_mask); - kfree(alias_pool->aliases); kfree(alias_pool); } diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 796a2eccbef0..0421c6208de7 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -206,6 +206,15 @@ config MTD_PARTITIONED_MASTER the parent of the partition device be the master device, rather than what lies behind the master. +config MTD_VIRT_CONCAT + bool "Virtual concatenated MTD devices" + depends on MTD_PARTITIONED_MASTER + help + The driver enables the creation of virtual MTD device by + concatenating multiple physical MTD devices into a single + entity. This allows for the creation of partitions larger than + the individual physical chips, extending across chip boundaries. + source "drivers/mtd/chips/Kconfig" source "drivers/mtd/maps/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 593d0593a038..7b6dd53e8150 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -6,6 +6,7 @@ # Core functionality. obj-$(CONFIG_MTD) += mtd.o mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o +mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o obj-y += parsers/ diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c index 33050a2a80f7..603fd0efc2ea 100644 --- a/drivers/mtd/devices/docg3.c +++ b/drivers/mtd/devices/docg3.c @@ -2049,7 +2049,6 @@ err_probe: static void docg3_release(struct platform_device *pdev) { struct docg3_cascade *cascade = platform_get_drvdata(pdev); - struct docg3 *docg3 = cascade->floors[0]->priv; int floor; doc_unregister_sysfs(pdev, cascade); @@ -2057,7 +2056,7 @@ static void docg3_release(struct platform_device *pdev) if (cascade->floors[floor]) doc_release_device(cascade->floors[floor]); - bch_free(docg3->cascade->bch); + bch_free(cascade->bch); } #ifdef CONFIG_OF diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 8a8b19874e23..99d5ff9a1fbe 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -75,17 +75,6 @@ config MTD_PHYSMAP_OF physically into the CPU's memory. The mapping description here is taken from OF device tree. -config MTD_PHYSMAP_BT1_ROM - bool "Baikal-T1 Boot ROMs OF-based physical memory map handling" - depends on MTD_PHYSMAP_OF - depends on MIPS_BAIKAL_T1 || COMPILE_TEST - select MTD_COMPLEX_MAPPINGS - select MULTIPLEXER - select MUX_MMIO - help - This provides some extra DT physmap parsing for the Baikal-T1 - platforms, some detection and setting up ROMs-specific accessors. - config MTD_PHYSMAP_VERSATILE bool "ARM Versatile OF-based physical memory map handling" depends on MTD_PHYSMAP_OF diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 019f1e92cc41..51af1d2ebd52 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o obj-$(CONFIG_MTD_PHYSMAP) += physmap.o physmap-y := physmap-core.o -physmap-$(CONFIG_MTD_PHYSMAP_BT1_ROM) += physmap-bt1-rom.o physmap-$(CONFIG_MTD_PHYSMAP_VERSATILE) += physmap-versatile.o physmap-$(CONFIG_MTD_PHYSMAP_GEMINI) += physmap-gemini.o physmap-$(CONFIG_MTD_PHYSMAP_IXP4XX) += physmap-ixp4xx.o diff --git a/drivers/mtd/maps/physmap-bt1-rom.c b/drivers/mtd/maps/physmap-bt1-rom.c deleted file mode 100644 index 60dccc48f99e..000000000000 --- a/drivers/mtd/maps/physmap-bt1-rom.c +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * - * Baikal-T1 Physically Mapped Internal ROM driver - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "physmap-bt1-rom.h" - -/* - * Baikal-T1 SoC ROMs are only accessible by the dword-aligned instructions. - * We have to take this into account when implementing the data read-methods. - * Note there is no need in bothering with endianness, since both Baikal-T1 - * CPU and MMIO are LE. - */ -static map_word __xipram bt1_rom_map_read(struct map_info *map, - unsigned long ofs) -{ - void __iomem *src = map->virt + ofs; - unsigned int shift; - map_word ret; - u32 data; - - /* Read data within offset dword. */ - shift = (uintptr_t)src & 0x3; - data = readl_relaxed(src - shift); - if (!shift) { - ret.x[0] = data; - return ret; - } - ret.x[0] = data >> (shift * BITS_PER_BYTE); - - /* Read data from the next dword. */ - shift = 4 - shift; - if (ofs + shift >= map->size) - return ret; - - data = readl_relaxed(src + shift); - ret.x[0] |= data << (shift * BITS_PER_BYTE); - - return ret; -} - -static void __xipram bt1_rom_map_copy_from(struct map_info *map, - void *to, unsigned long from, - ssize_t len) -{ - void __iomem *src = map->virt + from; - unsigned int shift, chunk; - u32 data; - - if (len <= 0 || from >= map->size) - return; - - /* Make sure we don't go over the map limit. */ - len = min_t(ssize_t, map->size - from, len); - - /* - * Since requested data size can be pretty big we have to implement - * the copy procedure as optimal as possible. That's why it's split - * up into the next three stages: unaligned head, aligned body, - * unaligned tail. - */ - shift = (uintptr_t)src & 0x3; - if (shift) { - chunk = min_t(ssize_t, 4 - shift, len); - data = readl_relaxed(src - shift); - memcpy(to, (char *)&data + shift, chunk); - src += chunk; - to += chunk; - len -= chunk; - } - - while (len >= 4) { - data = readl_relaxed(src); - memcpy(to, &data, 4); - src += 4; - to += 4; - len -= 4; - } - - if (len) { - data = readl_relaxed(src); - memcpy(to, &data, len); - } -} - -int of_flash_probe_bt1_rom(struct platform_device *pdev, - struct device_node *np, - struct map_info *map) -{ - struct device *dev = &pdev->dev; - - /* It's supposed to be read-only MTD. */ - if (!of_device_is_compatible(np, "mtd-rom")) { - dev_info(dev, "No mtd-rom compatible string\n"); - return 0; - } - - /* Multiplatform guard. */ - if (!of_device_is_compatible(np, "baikal,bt1-int-rom")) - return 0; - - /* Sanity check the device parameters retrieved from DTB. */ - if (map->bankwidth != 4) - dev_warn(dev, "Bank width is supposed to be 32 bits wide\n"); - - map->read = bt1_rom_map_read; - map->copy_from = bt1_rom_map_copy_from; - - return 0; -} diff --git a/drivers/mtd/maps/physmap-bt1-rom.h b/drivers/mtd/maps/physmap-bt1-rom.h deleted file mode 100644 index 6782899598a4..000000000000 --- a/drivers/mtd/maps/physmap-bt1-rom.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#include -#include - -#ifdef CONFIG_MTD_PHYSMAP_BT1_ROM -int of_flash_probe_bt1_rom(struct platform_device *pdev, - struct device_node *np, - struct map_info *map); -#else -static inline -int of_flash_probe_bt1_rom(struct platform_device *pdev, - struct device_node *np, - struct map_info *map) -{ - return 0; -} -#endif diff --git a/drivers/mtd/maps/physmap-core.c b/drivers/mtd/maps/physmap-core.c index 0dcc25b7ff98..dcda7685fc99 100644 --- a/drivers/mtd/maps/physmap-core.c +++ b/drivers/mtd/maps/physmap-core.c @@ -42,7 +42,6 @@ #include #include -#include "physmap-bt1-rom.h" #include "physmap-gemini.h" #include "physmap-ixp4xx.h" #include "physmap-versatile.h" @@ -365,10 +364,6 @@ static int physmap_flash_of_init(struct platform_device *dev) info->maps[i].bankwidth = bankwidth; info->maps[i].device_node = dp; - err = of_flash_probe_bt1_rom(dev, dp, &info->maps[i]); - if (err) - return err; - err = of_flash_probe_gemini(dev, dp, &info->maps[i]); if (err) return err; diff --git a/drivers/mtd/maps/physmap-gemini.c b/drivers/mtd/maps/physmap-gemini.c index 9d3b4bf84a1a..1c34b4ef77ea 100644 --- a/drivers/mtd/maps/physmap-gemini.c +++ b/drivers/mtd/maps/physmap-gemini.c @@ -181,7 +181,7 @@ int of_flash_probe_gemini(struct platform_device *pdev, dev_err(dev, "no enabled pin control state\n"); gf->disabled_state = pinctrl_lookup_state(gf->p, "disabled"); - if (IS_ERR(gf->enabled_state)) { + if (IS_ERR(gf->disabled_state)) { dev_err(dev, "no disabled pin control state\n"); } else { ret = pinctrl_select_state(gf->p, gf->disabled_state); diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c new file mode 100644 index 000000000000..37075ead0f33 --- /dev/null +++ b/drivers/mtd/mtd_virt_concat.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Virtual concat MTD device driver + * + * Copyright (C) 2018 Bernhard Frauendienst + * Author: Bernhard Frauendienst + */ + +#include +#include +#include "mtdcore.h" +#include +#include +#include +#include +#include + +#define CONCAT_PROP "part-concat-next" +#define CONCAT_POSTFIX "concat" +#define MIN_DEV_PER_CONCAT 1 + +static LIST_HEAD(concat_node_list); + +/** + * struct mtd_virt_concat_node - components of a concatenation + * @head: List handle + * @count: Number of nodes + * @nodes: Pointer to the nodes (partitions) to concatenate + * @concat: Concatenation container + */ +struct mtd_virt_concat_node { + struct list_head head; + unsigned int count; + struct mtd_concat *concat; + struct device_node *nodes[] __counted_by(count); +}; + +/** + * mtd_is_part_concat - Check if the device is already part + * of a concatenated device + * @dev: pointer to 'device_node' + * + * Return: true if the device is already part of a concatenation, + * false otherwise. + */ +static bool mtd_is_part_concat(struct device_node *dev) +{ + struct mtd_virt_concat_node *item; + int idx; + + list_for_each_entry(item, &concat_node_list, head) { + for (idx = 0; idx < item->count; idx++) { + if (item->nodes[idx] == dev) + return true; + } + } + return false; +} + +static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat) +{ + int i; + + for (i = 0; i < concat->num_subdev; i++) + put_mtd_device(concat->subdev[i]); +} + +void mtd_virt_concat_destroy_joins(void) +{ + struct mtd_virt_concat_node *item, *tmp; + struct mtd_info *mtd; + + list_for_each_entry_safe(item, tmp, &concat_node_list, head) { + mtd = &item->concat->mtd; + if (item->concat) { + mtd_device_unregister(mtd); + kfree(mtd->name); + mtd_concat_destroy(mtd); + mtd_virt_concat_put_mtd_devices(item->concat); + } + } +} + +/** + * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object + * @mtd: pointer to 'mtd_info' + * + * Return: 0 on success, -error otherwise. + */ +int mtd_virt_concat_destroy(struct mtd_info *mtd) +{ + struct mtd_info *child, *master = mtd_get_master(mtd); + struct mtd_virt_concat_node *item, *tmp; + struct mtd_concat *concat; + int idx, ret = 0; + bool is_mtd_found; + + list_for_each_entry_safe(item, tmp, &concat_node_list, head) { + is_mtd_found = false; + + /* Find the concat item that hold the mtd device */ + for (idx = 0; idx < item->count; idx++) { + if (item->nodes[idx] == mtd->dev.of_node) { + is_mtd_found = true; + break; + } + } + if (!is_mtd_found) + continue; + concat = item->concat; + + /* + * Since this concatenated device is being removed, retrieve + * all MTD devices that are part of it and register them + * individually. + */ + for (idx = 0; idx < concat->num_subdev; idx++) { + child = concat->subdev[idx]; + if (child->dev.of_node != mtd->dev.of_node) { + ret = add_mtd_device(child); + if (ret) + goto out; + } + } + /* Destroy the concat */ + if (concat->mtd.name) { + del_mtd_device(&concat->mtd); + kfree(concat->mtd.name); + mtd_concat_destroy(&concat->mtd); + mtd_virt_concat_put_mtd_devices(item->concat); + } + + for (idx = 0; idx < item->count; idx++) + of_node_put(item->nodes[idx]); + + kfree(item); + } + return 0; +out: + mutex_lock(&master->master.partitions_lock); + list_del(&child->part.node); + mutex_unlock(&master->master.partitions_lock); + kfree(mtd->name); + kfree(mtd); + + return ret; +} + +/** + * mtd_virt_concat_create_item - Create a concat item + * @parts: pointer to 'device_node' + * @count: number of mtd devices that make up + * the concatenated device. + * + * Return: 0 on success, -error otherwise. + */ +static int mtd_virt_concat_create_item(struct device_node *parts, + unsigned int count) +{ + struct mtd_virt_concat_node *item; + struct mtd_concat *concat; + int i; + + for (i = 0; i < (count - 1); i++) { + if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i))) + return 0; + } + + item = kzalloc_flex(*item, nodes, count, GFP_KERNEL); + if (!item) + return -ENOMEM; + + item->count = count; + + /* + * The partition in which "part-concat-next" property + * is defined is the first device in the list of concat + * devices. + */ + item->nodes[0] = parts; + + for (i = 1; i < count; i++) + item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1)); + + concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL); + if (!concat) { + kfree(item); + return -ENOMEM; + } + + item->concat = concat; + + list_add_tail(&item->head, &concat_node_list); + + return 0; +} + +void mtd_virt_concat_destroy_items(void) +{ + struct mtd_virt_concat_node *item, *temp; + int i; + + list_for_each_entry_safe(item, temp, &concat_node_list, head) { + for (i = 0; i < item->count; i++) + of_node_put(item->nodes[i]); + + kfree(item); + } +} + +/** + * mtd_virt_concat_add - Add a mtd device to the concat list + * @mtd: pointer to 'mtd_info' + * + * Return: true on success, false otherwise. + */ +bool mtd_virt_concat_add(struct mtd_info *mtd) +{ + struct mtd_virt_concat_node *item; + struct mtd_concat *concat; + int idx; + + list_for_each_entry(item, &concat_node_list, head) { + concat = item->concat; + for (idx = 0; idx < item->count; idx++) { + if (item->nodes[idx] == mtd->dev.of_node) { + concat->subdev[concat->num_subdev++] = mtd; + return true; + } + } + } + return false; +} + +/** + * mtd_virt_concat_node_create - List all the concatenations found in DT + * + * Return: 0 on success, -error otherwise. + */ +int mtd_virt_concat_node_create(void) +{ + struct device_node *parts = NULL; + int ret = 0, count = 0; + + /* List all the concatenations found in DT */ + do { + parts = of_find_node_with_property(parts, CONCAT_PROP); + if (!of_device_is_available(parts)) + continue; + + if (mtd_is_part_concat(parts)) + continue; + + count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL); + if (count < MIN_DEV_PER_CONCAT) + continue; + + /* + * The partition in which "part-concat-next" property is defined + * is also part of the concat device, so increament count by 1. + */ + count++; + + ret = mtd_virt_concat_create_item(parts, count); + if (ret) { + of_node_put(parts); + goto destroy_items; + } + } while (parts); + + return ret; + +destroy_items: + mtd_virt_concat_destroy_items(); + + return ret; +} + +/** + * mtd_virt_concat_create_join - Create and register the concatenated + * MTD device. + * + * Return: 0 on success, -error otherwise. + */ +int mtd_virt_concat_create_join(void) +{ + struct mtd_virt_concat_node *item; + struct mtd_concat *concat; + struct mtd_info *mtd; + ssize_t name_sz; + int ret, idx; + char *name; + + list_for_each_entry(item, &concat_node_list, head) { + concat = item->concat; + /* + * Check if item->count != concat->num_subdev, it indicates + * that the MTD information for all devices included in the + * concatenation are not handy, concat MTD device can't be + * created hence switch to next concat device. + */ + if (item->count != concat->num_subdev) { + continue; + } else { + /* Calculate the legth of the name of the virtual device */ + for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++) + name_sz += (strlen(concat->subdev[idx]->name) + 1); + name_sz += strlen(CONCAT_POSTFIX); + name = kmalloc(name_sz + 1, GFP_KERNEL); + if (!name) { + mtd_virt_concat_put_mtd_devices(concat); + return -ENOMEM; + } + + ret = 0; + for (idx = 0; idx < concat->num_subdev; idx++) { + ret += sprintf((name + ret), "%s-", + concat->subdev[idx]->name); + } + sprintf((name + ret), CONCAT_POSTFIX); + + if (concat->mtd.name) { + ret = memcmp(concat->mtd.name, name, name_sz); + if (ret == 0) + continue; + } + mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name); + if (!mtd) { + kfree(name); + return -ENXIO; + } + concat->mtd = *mtd; + /* Arbitrary set the first device as parent */ + concat->mtd.dev.parent = concat->subdev[0]->dev.parent; + concat->mtd.dev = concat->subdev[0]->dev; + + /* Add the mtd device */ + ret = add_mtd_device(&concat->mtd); + if (ret) + goto destroy_concat; + } + } + + return 0; + +destroy_concat: + mtd_concat_destroy(mtd); + + return ret; +} diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 9eb5d919d9ba..c97167d51fe2 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -20,18 +20,6 @@ #include -/* - * Our storage structure: - * Subdev points to an array of pointers to struct mtd_info objects - * which is allocated along with this structure - * - */ -struct mtd_concat { - struct mtd_info mtd; - int num_subdev; - struct mtd_info **subdev; -}; - /* * how to calculate the size required for the above structure, * including the pointer array subdev points to: @@ -639,7 +627,6 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c const char *name) { /* name for the new device */ int i; - size_t size; struct mtd_concat *concat; struct mtd_info *subdev_master = NULL; uint32_t max_erasesize, curr_erasesize; @@ -652,15 +639,13 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c printk(KERN_NOTICE "into device \"%s\"\n", name); /* allocate the device structure */ - size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); - concat = kzalloc(size, GFP_KERNEL); + concat = kzalloc_flex(*concat, subdev, num_devs, GFP_KERNEL); if (!concat) { printk ("memory allocation error while creating concatenated device \"%s\"\n", name); return NULL; } - concat->subdev = (struct mtd_info **) (concat + 1); /* * Set up the new "super" device's MTD object structure, check for diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 64808493b4f5..576537774628 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -34,6 +34,7 @@ #include #include +#include #include "mtdcore.h" @@ -1120,6 +1121,12 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, goto out; } + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + ret = mtd_virt_concat_node_create(); + if (ret < 0) + goto out; + } + /* Prefer parsed partitions over driver-provided fallback */ ret = parse_mtd_partitions(mtd, types, parser_data); if (ret == -EPROBE_DEFER) @@ -1137,6 +1144,11 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, if (ret) goto out; + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + ret = mtd_virt_concat_create_join(); + if (ret < 0) + goto out; + } /* * FIXME: some drivers unfortunately call this function more than once. * So we have to check if we've already assigned the reboot notifier. @@ -1186,6 +1198,11 @@ int mtd_device_unregister(struct mtd_info *master) nvmem_unregister(master->otp_user_nvmem); nvmem_unregister(master->otp_factory_nvmem); + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + err = mtd_virt_concat_destroy(master); + if (err) + return err; + } err = del_mtd_partitions(master); if (err) return err; @@ -2621,6 +2638,10 @@ err_reg: static void __exit cleanup_mtd(void) { + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + mtd_virt_concat_destroy_joins(); + mtd_virt_concat_destroy_items(); + } debugfs_remove_recursive(dfs_dir_mtd); cleanup_mtdchar(); if (proc_mtd) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index e016cfbc7224..795a94e6b482 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "mtdcore.h" @@ -409,6 +410,11 @@ int add_mtd_partitions(struct mtd_info *parent, goto err_del_partitions; } + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + if (mtd_virt_concat_add(child)) + continue; + } + mutex_lock(&master->master.partitions_lock); list_add_tail(&child->part.node, &parent->partitions); mutex_unlock(&master->master.partitions_lock); diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c index 0046da37ea3e..7d003fd72027 100644 --- a/drivers/mtd/nand/ecc-realtek.c +++ b/drivers/mtd/nand/ecc-realtek.c @@ -17,10 +17,12 @@ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes * * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there - * are only two known devices in the wild that have NAND flash and make use of this ECC engine - * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only - * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte - * blocks and 64 bytes oob. + * are a few known devices in the wild that make use of this ECC engine + * (Linksys LGS328C, LGS352C & Netlink HG323DAC). To keep compatibility with vendor firmware, + * new modes can only be added when new data layouts have been analyzed. For now allow BCH6 on + * flash with 2048 byte blocks and at least 64 bytes oob. Some vendors make use of + * 128 bytes OOB NAND chips (e.g. Macronix MX35LF1G24AD) but only use BCH6 and thus the first + * 64 bytes of the OOB area. In this case the engine leaves any extra bytes unused. * * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the * Realtek naming conventions for the different structures in the OOB area. @@ -39,7 +41,7 @@ */ #define RTL_ECC_ALLOWED_PAGE_SIZE 2048 -#define RTL_ECC_ALLOWED_OOB_SIZE 64 +#define RTL_ECC_ALLOWED_MIN_OOB_SIZE 64 #define RTL_ECC_ALLOWED_STRENGTH 6 #define RTL_ECC_BLOCK_SIZE 512 @@ -310,10 +312,10 @@ static int rtl_ecc_check_support(struct nand_device *nand) struct mtd_info *mtd = nanddev_to_mtd(nand); struct device *dev = nand->ecc.engine->dev; - if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE || + if (mtd->oobsize < RTL_ECC_ALLOWED_MIN_OOB_SIZE || mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) { - dev_err(dev, "only flash geometry data=%d, oob=%d supported\n", - RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE); + dev_err(dev, "only flash geometry data=%d, oob>=%d supported\n", + RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_MIN_OOB_SIZE); return -EINVAL; } diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c index 65a36d5de742..c4018bc59670 100644 --- a/drivers/mtd/nand/raw/cafe_nand.c +++ b/drivers/mtd/nand/raw/cafe_nand.c @@ -837,9 +837,10 @@ static const struct pci_device_id cafe_nand_tbl[] = { MODULE_DEVICE_TABLE(pci, cafe_nand_tbl); -static int cafe_nand_resume(struct pci_dev *pdev) +static int cafe_nand_resume(struct device *dev) { uint32_t ctrl; + struct pci_dev *pdev = to_pci_dev(dev); struct mtd_info *mtd = pci_get_drvdata(pdev); struct nand_chip *chip = mtd_to_nand(mtd); struct cafe_priv *cafe = nand_get_controller_data(chip); @@ -877,12 +878,14 @@ static int cafe_nand_resume(struct pci_dev *pdev) return 0; } +static DEFINE_SIMPLE_DEV_PM_OPS(cafe_nand_ops, NULL, cafe_nand_resume); + static struct pci_driver cafe_nand_pci_driver = { .name = "CAFÉ NAND", .id_table = cafe_nand_tbl, .probe = cafe_nand_probe, .remove = cafe_nand_remove, - .resume = cafe_nand_resume, + .driver.pm = &cafe_nand_ops, }; module_pci_driver(cafe_nand_pci_driver); diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c index dd88b22a91bd..fad0334f759d 100644 --- a/drivers/mtd/nand/raw/fsl_ifc_nand.c +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -7,6 +7,7 @@ * Author: Dipen Dudhat */ +#include #include #include #include @@ -863,7 +864,14 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) /* Fill in fsl_ifc_mtd structure */ mtd->dev.parent = priv->dev; - nand_set_flash_node(chip, priv->dev->of_node); + + struct device_node *np __free(device_node) = + of_get_next_child_with_prefix(priv->dev->of_node, NULL, "nand"); + + if (np) + nand_set_flash_node(chip, np); + else + nand_set_flash_node(chip, priv->dev->of_node); /* fill in nand_chip structure */ /* set up function call table */ diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index 51f595fbc834..c1f766cb225a 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -5,6 +5,7 @@ * Copyright (C) 2010-2015 Freescale Semiconductor, Inc. * Copyright (C) 2008 Embedded Alley Solutions, Inc. */ +#include #include #include #include @@ -2688,7 +2689,15 @@ static int gpmi_nand_init(struct gpmi_nand_data *this) /* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */ nand_set_controller_data(chip, this); - nand_set_flash_node(chip, this->pdev->dev.of_node); + + struct device_node *np __free(device_node) = + of_get_next_child_with_prefix(this->pdev->dev.of_node, NULL, "nand"); + + if (np) + nand_set_flash_node(chip, np); + else + nand_set_flash_node(chip, this->pdev->dev.of_node); + chip->legacy.block_markbad = gpmi_block_markbad; chip->badblock_pattern = &gpmi_bbt_descr; chip->options |= NAND_NO_SUBPAGE_WRITE; diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c index 8c56b685bf91..4d8b92e7e672 100644 --- a/drivers/mtd/nand/raw/mxc_nand.c +++ b/drivers/mtd/nand/raw/mxc_nand.c @@ -4,6 +4,7 @@ * Copyright 2008 Sascha Hauer, kernel@pengutronix.de */ +#include #include #include #include @@ -1714,7 +1715,14 @@ static int mxcnd_probe(struct platform_device *pdev) this->legacy.chip_delay = 5; nand_set_controller_data(this, host); - nand_set_flash_node(this, pdev->dev.of_node); + + struct device_node *np __free(device_node) = + of_get_next_child_with_prefix(pdev->dev.of_node, NULL, "nand"); + + if (np) + nand_set_flash_node(this, np); + else + nand_set_flash_node(this, pdev->dev.of_node); host->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(host->clk)) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index dfd8361bdd36..d6d3e17ab407 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "internals.h" @@ -4704,16 +4705,16 @@ static void nand_resume(struct mtd_info *mtd) { struct nand_chip *chip = mtd_to_nand(mtd); - mutex_lock(&chip->lock); - if (chip->suspended) { - if (chip->ops.resume) - chip->ops.resume(chip); - chip->suspended = 0; - } else { - pr_err("%s called for a chip which is not in suspended state\n", - __func__); + scoped_guard(mutex, &chip->lock) { + if (chip->suspended) { + if (chip->ops.resume) + chip->ops.resume(chip); + chip->suspended = 0; + } else { + pr_err("%s called for a chip which is not in suspended state\n", + __func__); + } } - mutex_unlock(&chip->lock); wake_up_all(&chip->resume_wq); } diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index e66adfcca7cd..02647565c8ba 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -209,9 +209,8 @@ /* * On A10/A23, this is the size of the NDFC User Data Register, containing the - * mandatory user data bytes following the ECC for each ECC step. + * mandatory user data bytes preceding the ECC for each ECC step. * Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ. - * Those bits are currently unsused, and kept as default value 0xffffffff. * * On H6/H616, this size became configurable, from 0 bytes to 32, via the * USER_DATA_LEN registers. @@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc { * @timing_ctl: TIMING_CTL register value for this NAND chip * @nsels: number of CS lines required by the NAND chip * @sels: array of CS lines descriptions + * @user_data_bytes: array of user data lengths for all ECC steps */ struct sunxi_nand_chip { struct list_head node; @@ -257,6 +257,7 @@ struct sunxi_nand_chip { unsigned long clk_rate; u32 timing_cfg; u32 timing_ctl; + u8 *user_data_bytes; int nsels; struct sunxi_nand_chip_sel sels[] __counted_by(nsels); }; @@ -272,9 +273,11 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand) * * @has_mdma: Use mbus dma mode, otherwise general dma * through MBUS on A23/A33 needs extra configuration. - * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chuncks + * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks * @has_ecc_clk: If the controller needs an ECC clock. * @has_mbus_clk: If the controller needs a mbus clock. + * @legacy_max_strength:If the maximize strength function was off by 2 bytes + * NB: this should not be used in new controllers * @reg_io_data: I/O data register * @reg_ecc_err_cnt: ECC error counter register * @reg_user_data: User data register @@ -292,7 +295,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand) * @nstrengths: Size of @ecc_strengths * @max_ecc_steps: Maximum supported steps for ECC, this is also the * number of user data registers - * @user_data_len_tab: Table of lenghts supported by USER_DATA_LEN register + * @user_data_len_tab: Table of lengths supported by USER_DATA_LEN register * The table index is the value to set in NFC_USER_DATA_LEN * registers, and the corresponding value is the number of * bytes to write @@ -304,6 +307,7 @@ struct sunxi_nfc_caps { bool has_ecc_block_512; bool has_ecc_clk; bool has_mbus_clk; + bool legacy_max_strength; unsigned int reg_io_data; unsigned int reg_ecc_err_cnt; unsigned int reg_user_data; @@ -820,12 +824,50 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf) return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); } -static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob, - int step, bool bbm, int page) +static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step) { - struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + if (!sunxi_nand->user_data_bytes) + return USER_DATA_SZ; - sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)), oob); + return sunxi_nand->user_data_bytes[step]; +} + +static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob, + int step, bool bbm, int page, + unsigned int user_data_sz) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + u32 user_data; + + if (!nfc->caps->reg_user_data_len) { + /* + * For A10, the user data for step n is in the nth + * REG_USER_DATA + */ + user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)); + sunxi_nfc_user_data_to_buf(user_data, oob); + } else { + /* + * For H6 NAND controller, the user data for all steps is + * contained in 32 user data registers, but not at a specific + * offset for each step, they are just concatenated. + */ + unsigned int user_data_off = 0; + unsigned int reg_off; + u8 *ptr = oob; + unsigned int i; + + for (i = 0; i < step; i++) + user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i); + + user_data_off /= 4; + for (i = 0; i < user_data_sz / 4; i++, ptr += 4) { + reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i); + user_data = readl(nfc->regs + reg_off); + sunxi_nfc_user_data_to_buf(user_data, ptr); + } + } /* De-randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) @@ -884,17 +926,46 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand, bool bbm, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); - u8 user_data[USER_DATA_SZ]; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); + u8 *user_data = NULL; /* Randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) { - memcpy(user_data, oob, sizeof(user_data)); + user_data = kmalloc(user_data_sz, GFP_KERNEL); + memcpy(user_data, oob, user_data_sz); sunxi_nfc_randomize_bbm(nand, page, user_data); oob = user_data; } - writel(sunxi_nfc_buf_to_user_data(oob), - nfc->regs + NFC_REG_USER_DATA(nfc, step)); + if (!nfc->caps->reg_user_data_len) { + /* + * For A10, the user data for step n is in the nth + * REG_USER_DATA + */ + writel(sunxi_nfc_buf_to_user_data(oob), + nfc->regs + NFC_REG_USER_DATA(nfc, step)); + } else { + /* + * For H6 NAND controller, the user data for all steps is + * contained in 32 user data registers, but not at a specific + * offset for each step, they are just concatenated. + */ + unsigned int user_data_off = 0; + const u8 *ptr = oob; + unsigned int i; + + for (i = 0; i < step; i++) + user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i); + + user_data_off /= 4; + for (i = 0; i < user_data_sz / 4; i++, ptr += 4) { + writel(sunxi_nfc_buf_to_user_data(ptr), + nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i)); + } + } + + kfree(user_data); } static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand, @@ -915,6 +986,8 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob, bool *erased) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc = &nand->ecc; u32 tmp; @@ -937,7 +1010,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob, memset(data, pattern, ecc->size); if (oob) - memset(oob, pattern, ecc->bytes + USER_DATA_SZ); + memset(oob, pattern, ecc->bytes + user_data_sz); return 0; } @@ -952,14 +1025,19 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, u8 *oob, int oob_off, int *cur_off, unsigned int *max_bitflips, - bool bbm, bool oob_required, int page) + int step, bool oob_required, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc = &nand->ecc; int raw_mode = 0; u32 pattern_found; + bool bbm = !step; bool erased; int ret; + /* From the controller point of view, we are at step 0 */ + const int nfc_step = 0; if (*cur_off != data_off) nand_change_read_column_op(nand, data_off, NULL, 0, false); @@ -973,8 +1051,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, if (ret) return ret; - sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP, @@ -985,15 +1062,14 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, if (ret) return ret; - *cur_off = oob_off + ecc->bytes + USER_DATA_SZ; + *cur_off = oob_off + ecc->bytes + user_data_sz; pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found); pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found); - ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, 0, - readl(nfc->regs + NFC_REG_ECC_ST), - pattern_found, - &erased); + ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, + nfc_step, readl(nfc->regs + NFC_REG_ECC_ST), + pattern_found, &erased); if (erased) return 1; @@ -1010,10 +1086,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, ecc->size); nand_change_read_column_op(nand, oob_off, oob, - ecc->bytes + USER_DATA_SZ, false); + ecc->bytes + user_data_sz, false); ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, - ecc->bytes + USER_DATA_SZ, + ecc->bytes + user_data_sz, NULL, 0, ecc->strength); if (ret >= 0) raw_mode = 1; @@ -1023,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, if (oob_required) { nand_change_read_column_op(nand, oob_off, NULL, 0, false); - sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + USER_DATA_SZ, + sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz, true, page); - sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, 0, - bbm, page); + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step, + bbm, page, user_data_sz); } } @@ -1036,21 +1112,50 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, return raw_mode; } +/* + * Returns the offset of the OOB for each step. + * (it includes the user data before the ECC data.) + */ +static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand, + struct nand_ecc_ctrl *ecc, int step) +{ + int ecc_off = step * ecc->bytes; + int i; + + for (i = 0; i < step; i++) + ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i); + + return ecc_off; +} + +/* + * Returns the offset of the ECC for each step. + * So, it's the same as sunxi_get_oob_offset(), + * but it skips the next user data. + */ +static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand, + struct nand_ecc_ctrl *ecc, int step) +{ + return sunxi_get_oob_offset(sunxi_nand, ecc, step) + + sunxi_nfc_user_data_sz(sunxi_nand, step); +} + static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand, u8 *oob, int *cur_off, bool randomize, int page) { + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; - int offset = ((ecc->bytes + 4) * ecc->steps); + int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); int len = mtd->oobsize - offset; if (len <= 0) return; - if (!cur_off || *cur_off != offset) - nand_change_read_column_op(nand, mtd->writesize, NULL, 0, - false); + if (!cur_off || *cur_off != (offset + mtd->writesize)) + nand_change_read_column_op(nand, mtd->writesize + offset, + NULL, 0, false); if (!randomize) sunxi_nfc_read_buf(nand, oob + offset, len); @@ -1067,6 +1172,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf int nchunks) { bool randomized = nand->options & NAND_NEED_SCRAMBLING; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; @@ -1086,7 +1192,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf sunxi_nfc_hw_ecc_enable(nand); sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); + for (i = 0; i < nchunks; i++) + sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); @@ -1121,7 +1228,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf for (i = 0; i < nchunks; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = buf + data_off; u8 *oob = nand->oob_poi + oob_off; bool erased; @@ -1139,10 +1247,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf /* TODO: use DMA to retrieve OOB */ nand_change_read_column_op(nand, mtd->writesize + oob_off, - oob, ecc->bytes + USER_DATA_SZ, false); + oob, ecc->bytes + user_data_sz, false); - sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, - !i, page); + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i, + page, user_data_sz); } if (erased) @@ -1154,7 +1262,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf if (status & NFC_ECC_ERR_MSK(nfc)) { for (i = 0; i < nchunks; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = buf + data_off; u8 *oob = nand->oob_poi + oob_off; @@ -1174,10 +1283,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf /* TODO: use DMA to retrieve OOB */ nand_change_read_column_op(nand, mtd->writesize + oob_off, - oob, ecc->bytes + USER_DATA_SZ, false); + oob, ecc->bytes + user_data_sz, false); ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, - ecc->bytes + USER_DATA_SZ, + ecc->bytes + user_data_sz, NULL, 0, ecc->strength); if (ret >= 0) @@ -1198,12 +1307,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, const u8 *data, int data_off, const u8 *oob, int oob_off, - int *cur_off, bool bbm, + int *cur_off, int step, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc = &nand->ecc; + bool bbm = !step; int ret; + /* From the controller point of view, we are at step 0 */ + const int nfc_step = 0; if (data_off != *cur_off) nand_change_write_column_op(nand, data_off, NULL, 0, false); @@ -1219,9 +1333,8 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); - sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); - sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0, bbm, page); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step); + sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ACCESS_DIR | NFC_ECC_OP, @@ -1232,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, if (ret) return ret; - *cur_off = oob_off + ecc->bytes + USER_DATA_SZ; + *cur_off = oob_off + ecc->bytes + user_data_sz; return 0; } @@ -1242,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand, int page) { struct mtd_info *mtd = nand_to_mtd(nand); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; - int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps); + int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); int len = mtd->oobsize - offset; if (len <= 0) @@ -1262,6 +1376,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand, static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf, int oob_required, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; unsigned int max_bitflips = 0; @@ -1274,16 +1390,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = 0; i < ecc->steps; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = buf + data_off; u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, &cur_off, &max_bitflips, - !i, oob_required, page); + i, oob_required, page); if (ret < 0) return ret; else if (ret) @@ -1321,6 +1438,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand, u32 data_offs, u32 readlen, u8 *bufpoi, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret, i, cur_off = 0; @@ -1332,17 +1451,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = data_offs / ecc->size; i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = bufpoi + data_off; u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, &max_bitflips, !i, + &cur_off, &max_bitflips, i, false, page); if (ret < 0) return ret; @@ -1377,6 +1497,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand, const uint8_t *buf, int oob_required, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret, i, cur_off = 0; @@ -1387,15 +1509,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = 0; i < ecc->steps; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); const u8 *data = buf + data_off; const u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, !i, page); + &cur_off, i, page); if (ret) return ret; } @@ -1414,6 +1537,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand, const u8 *buf, int oob_required, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret, i, cur_off = 0; @@ -1424,16 +1549,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = data_offs / ecc->size; i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); const u8 *data = buf + data_off; const u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, !i, page); + &cur_off, i, page); if (ret) return ret; } @@ -1449,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; struct scatterlist sg; u32 wait; @@ -1467,10 +1594,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand, sunxi_nfc_reset_user_data_len(nfc); for (i = 0; i < ecc->steps; i++) { - const u8 *oob = nand->oob_poi + (i * (ecc->bytes + USER_DATA_SZ)); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); + const u8 *oob = nand->oob_poi + oob_off; sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, i); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, i); } nand_prog_page_begin_op(nand, page, 0, NULL, 0); @@ -1734,11 +1863,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section, { struct nand_chip *nand = mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc = &nand->ecc; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); if (section >= ecc->steps) return -ERANGE; - oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + 4; + oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section); oobregion->length = ecc->bytes; return 0; @@ -1749,35 +1879,30 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, { struct nand_chip *nand = mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc = &nand->ecc; - - if (section > ecc->steps) - return -ERANGE; - - /* - * The first 2 bytes are used for BB markers, hence we - * only have 2 bytes available in the first user data - * section. - */ - if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { - oobregion->offset = 2; - oobregion->length = 2; - - return 0; - } + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section); /* * The controller does not provide access to OOB bytes * past the end of the ECC data. */ - if (section == ecc->steps && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + if (section >= ecc->steps) return -ERANGE; - oobregion->offset = section * (ecc->bytes + USER_DATA_SZ); + /* + * The first 2 bytes are used for BB markers, hence we + * only have user_data_sz - 2 bytes available in the first user data + * section. + */ + if (section == 0) { + oobregion->offset = 2; + oobregion->length = user_data_sz - 2; - if (section < ecc->steps) - oobregion->length = USER_DATA_SZ; - else - oobregion->length = mtd->oobsize - oobregion->offset; + return 0; + } + + oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section); + oobregion->length = user_data_sz; return 0; } @@ -1787,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = { .free = sunxi_nand_ooblayout_free, }; +static void sunxi_nand_detach_chip(struct nand_chip *nand) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + devm_kfree(nfc->dev, sunxi_nand->user_data_bytes); + sunxi_nand->user_data_bytes = NULL; +} + +static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize, + int ecc_bytes, int nsectors) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + const struct sunxi_nfc_caps *c = nfc->caps; + int remaining_bytes = oobsize - (ecc_bytes * nsectors); + int i, step; + + sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors, + GFP_KERNEL); + if (!sunxi_nand->user_data_bytes) + return -ENOMEM; + + for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) { + for (i = 0; i < c->nuser_data_tab; i++) { + if (c->user_data_len_tab[i] > remaining_bytes) + break; + sunxi_nand->user_data_bytes[step] = c->user_data_len_tab[i]; + } + remaining_bytes -= sunxi_nand->user_data_bytes[step]; + if (sunxi_nand->user_data_bytes[step] == 0) + break; + } + + return 0; +} + static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, struct nand_ecc_ctrl *ecc, struct device_node *np) @@ -1796,20 +1958,50 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, const u8 *strengths = nfc->caps->ecc_strengths; struct mtd_info *mtd = nand_to_mtd(nand); struct nand_device *nanddev = mtd_to_nanddev(mtd); + int total_user_data_sz = 0; int nsectors; + int ecc_mode; int i; if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { - int bytes; + int bytes = mtd->oobsize; ecc->size = 1024; nsectors = mtd->writesize / ecc->size; - /* Reserve 2 bytes for the BBM */ - bytes = (mtd->oobsize - 2) / nsectors; + if (!nfc->caps->reg_user_data_len) { + /* + * If there's a fixed user data length, subtract it before + * computing the max ECC strength + */ - /* 4 non-ECC bytes are added before each ECC bytes section */ - bytes -= USER_DATA_SZ; + for (i = 0; i < nsectors; i++) + total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i); + + /* + * The 2 BBM bytes should not be removed from the grand total, + * because they are part of the USER_DATA_SZ. + * But we can't modify that for older platform since it may + * result in a stronger ECC at the end, and break the + * compatibility. + */ + if (nfc->caps->legacy_max_strength) + bytes -= 2; + + bytes -= total_user_data_sz; + } else { + /* + * remove at least the BBM size before computing the + * max ECC + */ + bytes -= 2; + } + + /* + * Once all user data has been subtracted, the rest can be used + * for ECC bytes + */ + bytes /= nsectors; /* and bytes has to be even. */ if (bytes % 2) @@ -1838,18 +2030,18 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, } /* Add ECC info retrieval from DT */ - for (i = 0; i < nfc->caps->nstrengths; i++) { - if (ecc->strength <= strengths[i]) { + for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) { + if (ecc->strength <= strengths[ecc_mode]) { /* * Update ecc->strength value with the actual strength * that will be used by the ECC engine. */ - ecc->strength = strengths[i]; + ecc->strength = strengths[ecc_mode]; break; } } - if (i >= nfc->caps->nstrengths) { + if (ecc_mode >= nfc->caps->nstrengths) { dev_err(nfc->dev, "unsupported strength\n"); return -ENOTSUPP; } @@ -1862,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, nsectors = mtd->writesize / ecc->size; - if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors)) + /* + * The rationale for variable data length is to prioritize maximum ECC + * strength, and then use the remaining space for user data. + */ + if (nfc->caps->reg_user_data_len) + sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes, + nsectors); + + if (total_user_data_sz == 0) + for (i = 0; i < nsectors; i++) + total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i); + + if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz)) return -EINVAL; ecc->read_oob = sunxi_nfc_hw_ecc_read_oob; @@ -1885,7 +2089,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, ecc->read_oob_raw = nand_read_oob_std; ecc->write_oob_raw = nand_write_oob_std; - sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, i) | NFC_ECC_EXCEPTION | + sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION | NFC_ECC_PIPELINE | NFC_ECC_EN; if (ecc->size == 512) { @@ -2092,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand, static const struct nand_controller_ops sunxi_nand_controller_ops = { .attach_chip = sunxi_nand_attach_chip, + .detach_chip = sunxi_nand_detach_chip, .setup_interface = sunxi_nfc_setup_interface, .exec_op = sunxi_nfc_exec_op, }; @@ -2373,6 +2578,7 @@ static const u8 sunxi_user_data_len_h6[] = { static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = { .has_ecc_block_512 = true, + .legacy_max_strength = true, .reg_io_data = NFC_REG_A10_IO_DATA, .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT, .reg_user_data = NFC_REG_A10_USER_DATA, @@ -2394,6 +2600,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = { static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = { .has_mdma = true, .has_ecc_block_512 = true, + .legacy_max_strength = true, .reg_io_data = NFC_REG_A23_IO_DATA, .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT, .reg_user_data = NFC_REG_A10_USER_DATA, diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 6dfd0dcc8ee7..ad22774096e6 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -337,16 +337,19 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand, if (iface != SSDR) return -EOPNOTSUPP; + /* + * SDR dual and quad I/O operations over 104MHz require the HS bit to + * enable a few more dummy cycles. + */ op = spinand->op_templates->read_cache; if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) hs = false; - else if (op->cmd.buswidth == 1 && op->addr.buswidth == 1 && - op->dummy.buswidth == 1 && op->data.buswidth == 1) + else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1) + hs = false; + else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ) hs = false; - else if (!op->max_freq) - hs = true; else - hs = false; + hs = true; ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4); if (ret) @@ -485,7 +488,7 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants, &write_cache_variants, &update_cache_variants), - 0, + SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&w25n01jw_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)), SPINAND_INFO("W25N01KV", /* 3.3V */ @@ -549,7 +552,7 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants, &write_cache_variants, &update_cache_variants), - 0, + SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)), SPINAND_INFO("W25N02KV", /* 3.3V */ diff --git a/drivers/mtd/parsers/cmdlinepart.c b/drivers/mtd/parsers/cmdlinepart.c index 504e5fa2b45b..4caf1b3804f2 100644 --- a/drivers/mtd/parsers/cmdlinepart.c +++ b/drivers/mtd/parsers/cmdlinepart.c @@ -50,9 +50,9 @@ struct cmdline_mtd_partition { struct cmdline_mtd_partition *next; - char *mtd_id; int num_parts; struct mtd_partition *parts; + char mtd_id[]; }; /* mtdpart_setup() parses into here */ @@ -289,7 +289,6 @@ static int mtdpart_setup_real(char *s) /* enter results */ this_mtd->parts = parts; this_mtd->num_parts = num_parts; - this_mtd->mtd_id = (char*)(this_mtd + 1); strscpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1); /* link into chain */ diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c index 0029bda165bd..262c4221d23f 100644 --- a/drivers/mtd/parsers/ofpart_core.c +++ b/drivers/mtd/parsers/ofpart_core.c @@ -75,7 +75,7 @@ static int parse_fixed_partitions(struct mtd_info *master, dedicated = false; } } else { /* Partition */ - ofpart_node = mtd_node; + ofpart_node = of_node_get(mtd_node); } of_id = of_match_node(parse_ofpart_match_table, ofpart_node); @@ -195,11 +195,11 @@ static int parse_fixed_partitions(struct mtd_info *master, ofpart_fail: pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n", master->name, pp, mtd_node); + of_node_put(pp); ret = -EINVAL; ofpart_none: if (dedicated) of_node_put(ofpart_node); - of_node_put(pp); kfree(parts); return ret; } diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 1eee519c01e5..5dd0b3cb5250 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2393,7 +2393,7 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor, /* convert the dummy cycles to the number of bytes */ op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) * op.dummy.buswidth / 8; - if (spi_nor_protocol_is_dtr(nor->read_proto)) + if (spi_nor_protocol_is_dtr(read->proto)) op.dummy.nbytes *= 2; return spi_nor_spimem_check_read_pp_op(nor, &op); diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 16b382d4f04f..e838c40a2589 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -413,7 +413,7 @@ struct spi_nor_flash_parameter { * number of dummy cycles in read register ops. * @smpt_map_id: called after map ID in SMPT table has been determined for the * case the map ID is wrong and needs to be fixed. - * @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs + * @post_sfdp: called after SFDP has been parsed (is not called for SPI NORs * that do not support RDSFDP). Typically used to tweak various * parameters that could not be extracted by other means (i.e. * when information provided by the SFDP/flash_info tables are diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 88033384a71e..c75b0a1cd567 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -167,6 +167,16 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor) 0, 20, SPINOR_OP_MT_DTR_RD, SNOR_PROTO_8_8_8_DTR); + /* + * Some batches of mt35xu512aba do not contain the OCT DTR command + * information, but do support OCT DTR mode. Add the settings for + * SNOR_CMD_PP_8_8_8_DTR here. This also makes sure the flash can switch + * to OCT DTR mode. + */ + nor->params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR; + spi_nor_set_pp_settings(&nor->params->page_programs[SNOR_CMD_PP_8_8_8_DTR], + SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR); + nor->cmd_ext_type = SPI_NOR_EXT_REPEAT; nor->params->rdsr_dummy = 8; nor->params->rdsr_addr_nbytes = 0; @@ -185,7 +195,7 @@ static const struct spi_nor_fixups mt35xu512aba_fixups = { .post_sfdp = mt35xu512aba_post_sfdp_fixup, }; -static const struct spi_nor_fixups mt35xu01gbba_fixups = { +static const struct spi_nor_fixups mt35_two_die_fixups = { .post_sfdp = mt35xu512aba_post_sfdp_fixup, .late_init = micron_st_nor_two_die_late_init, }; @@ -202,25 +212,16 @@ static const struct flash_info micron_nor_parts[] = { .id = SNOR_ID(0x2c, 0x5b, 0x1b), .mfr_flags = USE_FSR, .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE, - .fixups = &mt35xu01gbba_fixups, + .fixups = &mt35_two_die_fixups, }, { - /* - * The MT35XU02GCBA flash device does not support chip erase, - * according to its datasheet. It supports die erase, which - * means the current driver implementation will likely need to - * be converted to use die erase. Furthermore, similar to the - * MT35XU01GBBA, the SPI_NOR_IO_MODE_EN_VOLATILE flag probably - * needs to be enabled. - * - * TODO: Fix these and test on real hardware. - */ .id = SNOR_ID(0x2c, 0x5b, 0x1c), .name = "mt35xu02g", .sector_size = SZ_128K, .size = SZ_256M, .no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ, .mfr_flags = USE_FSR, - .fixup_flags = SPI_NOR_4B_OPCODES, + .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE, + .fixups = &mt35_two_die_fixups, }, }; diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index 175211fe6a5e..db02c14ba16f 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -203,6 +203,8 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, /* Start write from odd address. */ if (to % 2) { + bool needs_write_enable = (len > 1); + /* write one byte. */ ret = sst_nor_write_data(nor, to, 1, buf); if (ret < 0) @@ -210,6 +212,17 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, to++; actual++; + + /* + * Byte program clears the write enable latch. If more + * data needs to be written using the AAI sequence, + * re-enable writes. + */ + if (needs_write_enable) { + ret = spi_nor_write_enable(nor); + if (ret) + goto out; + } } /* Write out most of the data here. */ diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 9b07f83aeac7..e67a81dbb6bf 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -28,8 +28,10 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) { if (nor->flags & SNOR_F_HAS_SR_TB_BIT6) return SR_TB_BIT6; - else + else if (nor->flags & SNOR_F_HAS_SR_TB) return SR_TB_BIT5; + else + return 0; } static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index fb855fe44733..eaa547d36aad 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -274,6 +274,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x19), .name = "w25q256jw", .size = SZ_32M, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { .id = SNOR_ID(0xef, 0x60, 0x20), @@ -295,6 +296,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x17), .name = "w25q64jvm", .size = SZ_8M, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K, }, { .id = SNOR_ID(0xef, 0x70, 0x18), @@ -337,7 +339,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x19), .name = "w25q256jwm", .size = SZ_32M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { .id = SNOR_ID(0xef, 0x80, 0x20), diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c index fbd253f0d3c5..b6837e299e7f 100644 --- a/drivers/of/kexec.c +++ b/drivers/of/kexec.c @@ -175,7 +175,7 @@ int __init ima_free_kexec_buffer(void) if (ret) return ret; - memblock_free_late(addr, size); + memblock_phys_free(addr, size); return 0; } #endif diff --git a/fs/Kconfig b/fs/Kconfig index fb3d6cf64e78..ffa71c792868 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -153,6 +153,7 @@ menu "DOS/FAT/EXFAT/NT Filesystems" source "fs/fat/Kconfig" source "fs/exfat/Kconfig" +source "fs/ntfs/Kconfig" source "fs/ntfs3/Kconfig" endmenu diff --git a/fs/Makefile b/fs/Makefile index 00ab650cf6d9..6f8eb5bc3452 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_NLS) += nls/ obj-y += unicode/ obj-$(CONFIG_SMBFS) += smb/ obj-$(CONFIG_HPFS_FS) += hpfs/ +obj-$(CONFIG_NTFS_FS) += ntfs/ obj-$(CONFIG_NTFS3_FS) += ntfs3/ obj-$(CONFIG_UFS_FS) += ufs/ obj-$(CONFIG_EFS_FS) += efs/ diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 0cf68f85dfd1..94283a991e5c 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -1082,9 +1081,6 @@ struct ext4_inode_info { spinlock_t i_raw_lock; /* protects updates to the raw inode */ - /* Fast commit wait queue for this inode */ - wait_queue_head_t i_fc_wait; - /* * Protect concurrent accesses on i_fc_lblk_start, i_fc_lblk_len * and inode's EXT4_FC_STATE_COMMITTING state bit. @@ -2976,7 +2972,8 @@ void __ext4_fc_track_unlink(handle_t *handle, struct inode *inode, void __ext4_fc_track_link(handle_t *handle, struct inode *inode, struct dentry *dentry); void ext4_fc_track_unlink(handle_t *handle, struct dentry *dentry); -void ext4_fc_track_link(handle_t *handle, struct dentry *dentry); +void ext4_fc_track_link(handle_t *handle, struct inode *inode, + struct dentry *dentry); void __ext4_fc_track_create(handle_t *handle, struct inode *inode, struct dentry *dentry); void ext4_fc_track_create(handle_t *handle, struct dentry *dentry); @@ -3101,8 +3098,9 @@ extern int ext4_chunk_trans_blocks(struct inode *, int nrblocks); extern int ext4_chunk_trans_extent(struct inode *inode, int nrblocks); extern int ext4_meta_trans_blocks(struct inode *inode, int lblocks, int pextents); -extern int ext4_zero_partial_blocks(handle_t *handle, struct inode *inode, - loff_t lstart, loff_t lend); +extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end); +extern int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, + loff_t length, bool *did_zero); extern vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf); extern qsize_t *ext4_get_reserved_space(struct inode *inode); extern int ext4_get_projid(struct inode *inode, kprojid_t *projid); @@ -3721,7 +3719,7 @@ extern int ext4_handle_dirty_dirblock(handle_t *handle, struct inode *inode, extern int __ext4_unlink(struct inode *dir, const struct qstr *d_name, struct inode *inode, struct dentry *dentry); extern int __ext4_link(struct inode *dir, struct inode *inode, - struct dentry *dentry); + const struct qstr *d_name, struct dentry *dentry); #define S_SHIFT 12 static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = { diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index 5496b2c8e2cd..6b53a3f39fcd 100644 --- a/fs/ext4/extents-test.c +++ b/fs/ext4/extents-test.c @@ -142,10 +142,14 @@ static struct file_system_type ext_fs_type = { static void extents_kunit_exit(struct kunit *test) { - struct super_block *sb = k_ctx.k_ei->vfs_inode.i_sb; - struct ext4_sb_info *sbi = sb->s_fs_info; + struct ext4_sb_info *sbi; + if (!k_ctx.k_ei) + return; + + sbi = k_ctx.k_ei->vfs_inode.i_sb->s_fs_info; ext4_es_unregister_shrinker(sbi); + deactivate_super(sbi->s_sb); kfree(sbi); kfree(k_ctx.k_ei); kfree(k_ctx.k_data); @@ -224,33 +228,37 @@ static int extents_kunit_init(struct kunit *test) (struct kunit_ext_test_param *)(test->param_value); int err; - sb = sget(&ext_fs_type, NULL, ext_set, 0, NULL); - if (IS_ERR(sb)) - return PTR_ERR(sb); - - sb->s_blocksize = 4096; - sb->s_blocksize_bits = 12; - sbi = kzalloc_obj(struct ext4_sb_info); if (sbi == NULL) return -ENOMEM; + sb = sget(&ext_fs_type, NULL, ext_set, 0, NULL); + if (IS_ERR(sb)) { + kfree(sbi); + return PTR_ERR(sb); + } + sbi->s_sb = sb; sb->s_fs_info = sbi; + sb->s_blocksize = 4096; + sb->s_blocksize_bits = 12; + if (!param || !param->disable_zeroout) sbi->s_extent_max_zeroout_kb = 32; - /* setup the mock inode */ - k_ctx.k_ei = kzalloc_obj(struct ext4_inode_info); - if (k_ctx.k_ei == NULL) - return -ENOMEM; - ei = k_ctx.k_ei; - inode = &ei->vfs_inode; - err = ext4_es_register_shrinker(sbi); if (err) - return err; + goto out_deactivate; + + /* setup the mock inode */ + k_ctx.k_ei = kzalloc_obj(struct ext4_inode_info); + if (k_ctx.k_ei == NULL) { + err = -ENOMEM; + goto out; + } + ei = k_ctx.k_ei; + inode = &ei->vfs_inode; ext4_es_init_tree(&ei->i_es_tree); rwlock_init(&ei->i_es_lock); @@ -266,8 +274,10 @@ static int extents_kunit_init(struct kunit *test) inode->i_sb = sb; k_ctx.k_data = kzalloc(EXT_DATA_LEN * 4096, GFP_KERNEL); - if (k_ctx.k_data == NULL) - return -ENOMEM; + if (k_ctx.k_data == NULL) { + err = -ENOMEM; + goto out; + } /* * set the data area to a junk value @@ -309,7 +319,23 @@ static int extents_kunit_init(struct kunit *test) kunit_activate_static_stub(test, ext4_ext_zeroout, ext4_ext_zeroout_stub); kunit_activate_static_stub(test, ext4_issue_zeroout, ext4_issue_zeroout_stub); + up_write(&sb->s_umount); + return 0; + +out: + kfree(k_ctx.k_ei); + k_ctx.k_ei = NULL; + + kfree(k_ctx.k_data); + k_ctx.k_data = NULL; + + ext4_es_unregister_shrinker(sbi); +out_deactivate: + deactivate_locked_super(sb); + kfree(sbi); + + return err; } /* diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 95d404486f1a..125f628e738a 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -4571,30 +4571,30 @@ retry_remove_space: return err; } -static int ext4_alloc_file_blocks(struct file *file, ext4_lblk_t offset, - ext4_lblk_t len, loff_t new_size, - int flags) +static int ext4_alloc_file_blocks(struct file *file, loff_t offset, loff_t len, + loff_t new_size, int flags) { struct inode *inode = file_inode(file); handle_t *handle; int ret = 0, ret2 = 0, ret3 = 0; int retries = 0; int depth = 0; + ext4_lblk_t len_lblk; struct ext4_map_blocks map; unsigned int credits; - loff_t epos, old_size = i_size_read(inode); + loff_t epos = 0, old_size = i_size_read(inode); unsigned int blkbits = inode->i_blkbits; bool alloc_zero = false; BUG_ON(!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)); - map.m_lblk = offset; - map.m_len = len; + map.m_lblk = offset >> blkbits; + map.m_len = len_lblk = EXT4_MAX_BLOCKS(len, offset, blkbits); /* * Don't normalize the request if it can fit in one extent so * that it doesn't get unnecessarily split into multiple * extents. */ - if (len <= EXT_UNWRITTEN_MAX_LEN) + if (len_lblk <= EXT_UNWRITTEN_MAX_LEN) flags |= EXT4_GET_BLOCKS_NO_NORMALIZE; /* @@ -4611,16 +4611,23 @@ static int ext4_alloc_file_blocks(struct file *file, ext4_lblk_t offset, /* * credits to insert 1 extent into extent tree */ - credits = ext4_chunk_trans_blocks(inode, len); + credits = ext4_chunk_trans_blocks(inode, len_lblk); depth = ext_depth(inode); + /* Zero to the end of the block containing i_size */ + if (new_size > old_size) { + ret = ext4_block_zero_eof(inode, old_size, LLONG_MAX); + if (ret) + return ret; + } + retry: - while (len) { + while (len_lblk) { /* * Recalculate credits when extent tree depth changes. */ if (depth != ext_depth(inode)) { - credits = ext4_chunk_trans_blocks(inode, len); + credits = ext4_chunk_trans_blocks(inode, len_lblk); depth = ext_depth(inode); } @@ -4640,50 +4647,60 @@ retry: ext4_journal_stop(handle); break; } + ext4_update_inode_fsync_trans(handle, inode, 1); + ret = ext4_journal_stop(handle); + if (unlikely(ret)) + break; + /* * allow a full retry cycle for any remaining allocations */ retries = 0; - epos = EXT4_LBLK_TO_B(inode, map.m_lblk + ret); - inode_set_ctime_current(inode); - if (new_size) { - if (epos > new_size) - epos = new_size; - if (ext4_update_inode_size(inode, epos) & 0x1) - inode_set_mtime_to_ts(inode, - inode_get_ctime(inode)); - if (epos > old_size) { - pagecache_isize_extended(inode, old_size, epos); - ext4_zero_partial_blocks(handle, inode, - old_size, epos - old_size); - } - } - ret2 = ext4_mark_inode_dirty(handle, inode); - ext4_update_inode_fsync_trans(handle, inode, 1); - ret3 = ext4_journal_stop(handle); - ret2 = ret3 ? ret3 : ret2; - if (unlikely(ret2)) - break; if (alloc_zero && (map.m_flags & (EXT4_MAP_MAPPED | EXT4_MAP_UNWRITTEN))) { - ret2 = ext4_issue_zeroout(inode, map.m_lblk, map.m_pblk, - map.m_len); - if (likely(!ret2)) - ret2 = ext4_convert_unwritten_extents(NULL, + ret = ext4_issue_zeroout(inode, map.m_lblk, map.m_pblk, + map.m_len); + if (likely(!ret)) + ret = ext4_convert_unwritten_extents(NULL, inode, (loff_t)map.m_lblk << blkbits, (loff_t)map.m_len << blkbits); - if (ret2) + if (ret) break; } - map.m_lblk += ret; - map.m_len = len = len - ret; + map.m_lblk += map.m_len; + map.m_len = len_lblk = len_lblk - map.m_len; + epos = EXT4_LBLK_TO_B(inode, map.m_lblk); } + if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries)) goto retry; - return ret > 0 ? ret2 : ret; + if (!epos || !new_size) + return ret; + + /* + * Allocate blocks, update the file size to match the size of the + * already successfully allocated blocks. + */ + if (epos > new_size) + epos = new_size; + + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); + if (IS_ERR(handle)) + return ret ? ret : PTR_ERR(handle); + + ext4_update_inode_size(inode, epos); + ret2 = ext4_mark_inode_dirty(handle, inode); + ext4_update_inode_fsync_trans(handle, inode, 1); + ret3 = ext4_journal_stop(handle); + ret2 = ret3 ? ret3 : ret2; + + if (epos > old_size) + pagecache_isize_extended(inode, old_size, epos); + + return ret ? ret : ret2; } static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len); @@ -4695,12 +4712,11 @@ static long ext4_zero_range(struct file *file, loff_t offset, { struct inode *inode = file_inode(file); handle_t *handle = NULL; - loff_t new_size = 0; + loff_t align_start, align_end, new_size = 0; loff_t end = offset + len; - ext4_lblk_t start_lblk, end_lblk; unsigned int blocksize = i_blocksize(inode); - unsigned int blkbits = inode->i_blkbits; - int ret, flags, credits; + bool partial_zeroed = false; + int ret, flags; trace_ext4_zero_range(inode, offset, len, mode); WARN_ON_ONCE(!inode_is_locked(inode)); @@ -4720,11 +4736,8 @@ static long ext4_zero_range(struct file *file, loff_t offset, flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT; /* Preallocate the range including the unaligned edges */ if (!IS_ALIGNED(offset | end, blocksize)) { - ext4_lblk_t alloc_lblk = offset >> blkbits; - ext4_lblk_t len_lblk = EXT4_MAX_BLOCKS(len, offset, blkbits); - - ret = ext4_alloc_file_blocks(file, alloc_lblk, len_lblk, - new_size, flags); + ret = ext4_alloc_file_blocks(file, offset, len, new_size, + flags); if (ret) return ret; } @@ -4739,18 +4752,17 @@ static long ext4_zero_range(struct file *file, loff_t offset, return ret; /* Zero range excluding the unaligned edges */ - start_lblk = EXT4_B_TO_LBLK(inode, offset); - end_lblk = end >> blkbits; - if (end_lblk > start_lblk) { - ext4_lblk_t zero_blks = end_lblk - start_lblk; - + align_start = round_up(offset, blocksize); + align_end = round_down(end, blocksize); + if (align_end > align_start) { if (mode & FALLOC_FL_WRITE_ZEROES) flags = EXT4_GET_BLOCKS_CREATE_ZERO | EXT4_EX_NOCACHE; else flags |= (EXT4_GET_BLOCKS_CONVERT_UNWRITTEN | EXT4_EX_NOCACHE); - ret = ext4_alloc_file_blocks(file, start_lblk, zero_blks, - new_size, flags); + ret = ext4_alloc_file_blocks(file, align_start, + align_end - align_start, new_size, + flags); if (ret) return ret; } @@ -4758,25 +4770,24 @@ static long ext4_zero_range(struct file *file, loff_t offset, if (IS_ALIGNED(offset | end, blocksize)) return ret; - /* - * In worst case we have to writeout two nonadjacent unwritten - * blocks and update the inode - */ - credits = (2 * ext4_ext_index_trans_blocks(inode, 2)) + 1; - if (ext4_should_journal_data(inode)) - credits += 2; - handle = ext4_journal_start(inode, EXT4_HT_MISC, credits); + /* Zero out partial block at the edges of the range */ + ret = ext4_zero_partial_blocks(inode, offset, len, &partial_zeroed); + if (ret) + return ret; + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) { + ret = filemap_write_and_wait_range(inode->i_mapping, offset, + end - 1); + if (ret) + return ret; + } + + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); if (IS_ERR(handle)) { ret = PTR_ERR(handle); ext4_std_error(inode->i_sb, ret); return ret; } - /* Zero out partial block at the edges of the range */ - ret = ext4_zero_partial_blocks(handle, inode, offset, len); - if (ret) - goto out_handle; - if (new_size) ext4_update_inode_size(inode, new_size); ret = ext4_mark_inode_dirty(handle, inode); @@ -4784,7 +4795,7 @@ static long ext4_zero_range(struct file *file, loff_t offset, goto out_handle; ext4_update_inode_fsync_trans(handle, inode, 1); - if (file->f_flags & O_SYNC) + if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) ext4_handle_sync(handle); out_handle: @@ -4798,15 +4809,11 @@ static long ext4_do_fallocate(struct file *file, loff_t offset, struct inode *inode = file_inode(file); loff_t end = offset + len; loff_t new_size = 0; - ext4_lblk_t start_lblk, len_lblk; int ret; trace_ext4_fallocate_enter(inode, offset, len, mode); WARN_ON_ONCE(!inode_is_locked(inode)); - start_lblk = offset >> inode->i_blkbits; - len_lblk = EXT4_MAX_BLOCKS(len, offset, inode->i_blkbits); - /* We only support preallocation for extent-based files only. */ if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) { ret = -EOPNOTSUPP; @@ -4821,17 +4828,19 @@ static long ext4_do_fallocate(struct file *file, loff_t offset, goto out; } - ret = ext4_alloc_file_blocks(file, start_lblk, len_lblk, new_size, + ret = ext4_alloc_file_blocks(file, offset, len, new_size, EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT); if (ret) goto out; - if (file->f_flags & O_SYNC && EXT4_SB(inode->i_sb)->s_journal) { + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && + EXT4_SB(inode->i_sb)->s_journal) { ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal, EXT4_I(inode)->i_sync_tid); } out: - trace_ext4_fallocate_exit(inode, offset, len_lblk, ret); + trace_ext4_fallocate_exit(inode, offset, + EXT4_MAX_BLOCKS(len, offset, inode->i_blkbits), ret); return ret; } @@ -5598,7 +5607,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len) goto out_handle; ext4_update_inode_fsync_trans(handle, inode, 1); - if (IS_SYNC(inode)) + if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) ext4_handle_sync(handle); out_handle: @@ -5722,7 +5731,7 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len) goto out_handle; ext4_update_inode_fsync_trans(handle, inode, 1); - if (IS_SYNC(inode)) + if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) ext4_handle_sync(handle); out_handle: diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c index 390f25dee2b1..b3c22636251d 100644 --- a/fs/ext4/fast_commit.c +++ b/fs/ext4/fast_commit.c @@ -13,6 +13,7 @@ #include "mballoc.h" #include +#include /* * Ext4 Fast Commits * ----------------- @@ -215,7 +216,6 @@ void ext4_fc_init_inode(struct inode *inode) ext4_clear_inode_state(inode, EXT4_STATE_FC_COMMITTING); INIT_LIST_HEAD(&ei->i_fc_list); INIT_LIST_HEAD(&ei->i_fc_dilist); - init_waitqueue_head(&ei->i_fc_wait); } static bool ext4_fc_disabled(struct super_block *sb) @@ -224,6 +224,12 @@ static bool ext4_fc_disabled(struct super_block *sb) (EXT4_SB(sb)->s_mount_state & EXT4_FC_REPLAY)); } +static bool ext4_fc_eligible(struct super_block *sb) +{ + return !ext4_fc_disabled(sb) && + !(ext4_test_mount_flag(sb, EXT4_MF_FC_INELIGIBLE)); +} + /* * Remove inode from fast commit list. If the inode is being committed * we wait until inode commit is done. @@ -320,7 +326,7 @@ void ext4_fc_mark_ineligible(struct super_block *sb, int reason, handle_t *handl if (ext4_fc_disabled(sb)) return; - if (handle && !IS_ERR(handle)) + if (!IS_ERR_OR_NULL(handle)) tid = handle->h_transaction->t_tid; else { read_lock(&sbi->s_journal->j_state_lock); @@ -473,13 +479,8 @@ void ext4_fc_track_unlink(handle_t *handle, struct dentry *dentry) { struct inode *inode = d_inode(dentry); - if (ext4_fc_disabled(inode->i_sb)) - return; - - if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) - return; - - __ext4_fc_track_unlink(handle, inode, dentry); + if (ext4_fc_eligible(inode->i_sb)) + __ext4_fc_track_unlink(handle, inode, dentry); } void __ext4_fc_track_link(handle_t *handle, @@ -496,17 +497,11 @@ void __ext4_fc_track_link(handle_t *handle, trace_ext4_fc_track_link(handle, inode, dentry, ret); } -void ext4_fc_track_link(handle_t *handle, struct dentry *dentry) +void ext4_fc_track_link(handle_t *handle, struct inode *inode, + struct dentry *dentry) { - struct inode *inode = d_inode(dentry); - - if (ext4_fc_disabled(inode->i_sb)) - return; - - if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) - return; - - __ext4_fc_track_link(handle, inode, dentry); + if (ext4_fc_eligible(inode->i_sb)) + __ext4_fc_track_link(handle, inode, dentry); } void __ext4_fc_track_create(handle_t *handle, struct inode *inode, @@ -527,13 +522,8 @@ void ext4_fc_track_create(handle_t *handle, struct dentry *dentry) { struct inode *inode = d_inode(dentry); - if (ext4_fc_disabled(inode->i_sb)) - return; - - if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) - return; - - __ext4_fc_track_create(handle, inode, dentry); + if (ext4_fc_eligible(inode->i_sb)) + __ext4_fc_track_create(handle, inode, dentry); } /* __track_fn for inode tracking */ @@ -557,16 +547,13 @@ void ext4_fc_track_inode(handle_t *handle, struct inode *inode) if (S_ISDIR(inode->i_mode)) return; - if (ext4_fc_disabled(inode->i_sb)) - return; - if (ext4_should_journal_data(inode)) { ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_INODE_JOURNAL_DATA, handle); return; } - if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) + if (!ext4_fc_eligible(inode->i_sb)) return; /* @@ -644,10 +631,7 @@ void ext4_fc_track_range(handle_t *handle, struct inode *inode, ext4_lblk_t star if (S_ISDIR(inode->i_mode)) return; - if (ext4_fc_disabled(inode->i_sb)) - return; - - if (ext4_test_mount_flag(inode->i_sb, EXT4_MF_FC_INELIGIBLE)) + if (!ext4_fc_eligible(inode->i_sb)) return; if (ext4_has_inline_data(inode)) { @@ -1446,7 +1430,6 @@ static int ext4_fc_replay_link_internal(struct super_block *sb, struct inode *inode) { struct inode *dir = NULL; - struct dentry *dentry_dir = NULL, *dentry_inode = NULL; struct qstr qstr_dname = QSTR_INIT(darg->dname, darg->dname_len); int ret = 0; @@ -1457,21 +1440,7 @@ static int ext4_fc_replay_link_internal(struct super_block *sb, goto out; } - dentry_dir = d_obtain_alias(dir); - if (IS_ERR(dentry_dir)) { - ext4_debug("Failed to obtain dentry"); - dentry_dir = NULL; - goto out; - } - - dentry_inode = d_alloc(dentry_dir, &qstr_dname); - if (!dentry_inode) { - ext4_debug("Inode dentry not created."); - ret = -ENOMEM; - goto out; - } - - ret = __ext4_link(dir, inode, dentry_inode); + ret = __ext4_link(dir, inode, &qstr_dname, NULL); /* * It's possible that link already existed since data blocks * for the dir in question got persisted before we crashed OR @@ -1485,16 +1454,8 @@ static int ext4_fc_replay_link_internal(struct super_block *sb, ret = 0; out: - if (dentry_dir) { - d_drop(dentry_dir); - dput(dentry_dir); - } else if (dir) { + if (dir) iput(dir); - } - if (dentry_inode) { - d_drop(dentry_inode); - dput(dentry_inode); - } return ret; } @@ -1759,8 +1720,7 @@ int ext4_fc_record_regions(struct super_block *sb, int ino, } /* Replay add range tag */ -static int ext4_fc_replay_add_range(struct super_block *sb, - struct ext4_fc_tl_mem *tl, u8 *val) +static int ext4_fc_replay_add_range(struct super_block *sb, u8 *val) { struct ext4_fc_add_range fc_add_ex; struct ext4_extent newex, *ex; @@ -1880,8 +1840,7 @@ out: /* Replay DEL_RANGE tag */ static int -ext4_fc_replay_del_range(struct super_block *sb, - struct ext4_fc_tl_mem *tl, u8 *val) +ext4_fc_replay_del_range(struct super_block *sb, u8 *val) { struct inode *inode; struct ext4_fc_del_range lrange; @@ -2251,13 +2210,13 @@ static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh, ret = ext4_fc_replay_unlink(sb, &tl, val); break; case EXT4_FC_TAG_ADD_RANGE: - ret = ext4_fc_replay_add_range(sb, &tl, val); + ret = ext4_fc_replay_add_range(sb, val); break; case EXT4_FC_TAG_CREAT: ret = ext4_fc_replay_create(sb, &tl, val); break; case EXT4_FC_TAG_DEL_RANGE: - ret = ext4_fc_replay_del_range(sb, &tl, val); + ret = ext4_fc_replay_del_range(sb, val); break; case EXT4_FC_TAG_INODE: ret = ext4_fc_replay_inode(sb, &tl, val); diff --git a/fs/ext4/file.c b/fs/ext4/file.c index 5e02f6cf653e..eb1a323962b1 100644 --- a/fs/ext4/file.c +++ b/fs/ext4/file.c @@ -270,6 +270,8 @@ static ssize_t ext4_generic_write_checks(struct kiocb *iocb, static ssize_t ext4_write_checks(struct kiocb *iocb, struct iov_iter *from) { + struct inode *inode = file_inode(iocb->ki_filp); + loff_t old_size = i_size_read(inode); ssize_t ret, count; count = ext4_generic_write_checks(iocb, from); @@ -279,6 +281,21 @@ static ssize_t ext4_write_checks(struct kiocb *iocb, struct iov_iter *from) ret = file_modified(iocb->ki_filp); if (ret) return ret; + + /* + * If the position is beyond the EOF, it is necessary to zero out the + * partial block that beyond the existing EOF, as it may contains + * stale data written through mmap. + */ + if (iocb->ki_pos > old_size && !ext4_verity_in_progress(inode)) { + if (iocb->ki_flags & IOCB_NOWAIT) + return -EAGAIN; + + ret = ext4_block_zero_eof(inode, old_size, iocb->ki_pos); + if (ret) + return ret; + } + return count; } diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 64dba7679245..c2c2d6ac7f3d 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -1468,10 +1468,9 @@ static int ext4_write_end(const struct kiocb *iocb, folio_unlock(folio); folio_put(folio); - if (old_size < pos && !verity) { + if (old_size < pos && !verity) pagecache_isize_extended(inode, old_size, pos); - ext4_zero_partial_blocks(handle, inode, old_size, pos - old_size); - } + /* * Don't mark the inode dirty under folio lock. First, it unnecessarily * makes the holding time of folio lock longer. Second, it forces lock @@ -1586,10 +1585,8 @@ static int ext4_journalled_write_end(const struct kiocb *iocb, folio_unlock(folio); folio_put(folio); - if (old_size < pos && !verity) { + if (old_size < pos && !verity) pagecache_isize_extended(inode, old_size, pos); - ext4_zero_partial_blocks(handle, inode, old_size, pos - old_size); - } if (size_changed) { ret2 = ext4_mark_inode_dirty(handle, inode); @@ -1759,8 +1756,22 @@ static void mpage_release_unused_pages(struct mpage_da_data *mpd, BUG_ON(!folio_test_locked(folio)); BUG_ON(folio_test_writeback(folio)); if (invalidate) { - if (folio_mapped(folio)) + if (folio_mapped(folio)) { folio_clear_dirty_for_io(folio); + /* + * Unmap folio from page + * tables to prevent + * subsequent accesses through + * stale PTEs. This ensures + * future accesses trigger new + * page faults rather than + * reusing the invalidated + * folio. + */ + unmap_mapping_pages(folio->mapping, + folio->index, + folio_nr_pages(folio), false); + } block_invalidate_folio(folio, 0, folio_size(folio)); folio_clear_uptodate(folio); @@ -3043,17 +3054,23 @@ static int ext4_writepages(struct address_space *mapping, int ext4_normal_submit_inode_data_buffers(struct jbd2_inode *jinode) { + loff_t range_start, range_end; struct writeback_control wbc = { .sync_mode = WB_SYNC_ALL, .nr_to_write = LONG_MAX, - .range_start = jinode->i_dirty_start, - .range_end = jinode->i_dirty_end, }; struct mpage_da_data mpd = { .inode = jinode->i_vfs_inode, .wbc = &wbc, .can_map = 0, }; + + if (!jbd2_jinode_get_dirty_range(jinode, &range_start, &range_end)) + return 0; + + wbc.range_start = range_start; + wbc.range_end = range_end; + return ext4_do_writepages(&mpd); } @@ -3208,7 +3225,7 @@ static int ext4_da_do_write_end(struct address_space *mapping, struct inode *inode = mapping->host; loff_t old_size = inode->i_size; bool disksize_changed = false; - loff_t new_i_size, zero_len = 0; + loff_t new_i_size; handle_t *handle; if (unlikely(!folio_buffers(folio))) { @@ -3252,19 +3269,15 @@ static int ext4_da_do_write_end(struct address_space *mapping, folio_unlock(folio); folio_put(folio); - if (pos > old_size) { + if (pos > old_size) pagecache_isize_extended(inode, old_size, pos); - zero_len = pos - old_size; - } - if (!disksize_changed && !zero_len) + if (!disksize_changed) return copied; - handle = ext4_journal_start(inode, EXT4_HT_INODE, 2); + handle = ext4_journal_start(inode, EXT4_HT_INODE, 1); if (IS_ERR(handle)) return PTR_ERR(handle); - if (zero_len) - ext4_zero_partial_blocks(handle, inode, old_size, zero_len); ext4_mark_inode_dirty(handle, inode); ext4_journal_stop(handle); @@ -4014,12 +4027,11 @@ void ext4_set_aops(struct inode *inode) * ext4_punch_hole, etc) which needs to be properly zeroed out. Otherwise a * racing writeback can come later and flush the stale pagecache to disk. */ -static int __ext4_block_zero_page_range(handle_t *handle, - struct address_space *mapping, loff_t from, loff_t length) +static struct buffer_head *ext4_load_tail_bh(struct inode *inode, loff_t from) { unsigned int offset, blocksize, pos; ext4_lblk_t iblock; - struct inode *inode = mapping->host; + struct address_space *mapping = inode->i_mapping; struct buffer_head *bh; struct folio *folio; int err = 0; @@ -4028,7 +4040,7 @@ static int __ext4_block_zero_page_range(handle_t *handle, FGP_LOCK | FGP_ACCESSED | FGP_CREAT, mapping_gfp_constraint(mapping, ~__GFP_FS)); if (IS_ERR(folio)) - return PTR_ERR(folio); + return ERR_CAST(folio); blocksize = inode->i_sb->s_blocksize; @@ -4080,47 +4092,92 @@ static int __ext4_block_zero_page_range(handle_t *handle, } } } - if (ext4_should_journal_data(inode)) { - BUFFER_TRACE(bh, "get write access"); - err = ext4_journal_get_write_access(handle, inode->i_sb, bh, - EXT4_JTR_NONE); - if (err) - goto unlock; - } - folio_zero_range(folio, offset, length); - BUFFER_TRACE(bh, "zeroed end of block"); - - if (ext4_should_journal_data(inode)) { - err = ext4_dirty_journalled_data(handle, bh); - } else { - mark_buffer_dirty(bh); - /* - * Only the written block requires ordered data to prevent - * exposing stale data. - */ - if (!buffer_unwritten(bh) && !buffer_delay(bh) && - ext4_should_order_data(inode)) - err = ext4_jbd2_inode_add_write(handle, inode, from, - length); - } + return bh; unlock: folio_unlock(folio); folio_put(folio); + return err ? ERR_PTR(err) : NULL; +} + +static int ext4_block_do_zero_range(struct inode *inode, loff_t from, + loff_t length, bool *did_zero, + bool *zero_written) +{ + struct buffer_head *bh; + struct folio *folio; + + bh = ext4_load_tail_bh(inode, from); + if (IS_ERR_OR_NULL(bh)) + return PTR_ERR_OR_ZERO(bh); + + folio = bh->b_folio; + folio_zero_range(folio, offset_in_folio(folio, from), length); + BUFFER_TRACE(bh, "zeroed end of block"); + + mark_buffer_dirty(bh); + if (did_zero) + *did_zero = true; + if (zero_written && !buffer_unwritten(bh) && !buffer_delay(bh)) + *zero_written = true; + + folio_unlock(folio); + folio_put(folio); + return 0; +} + +static int ext4_block_journalled_zero_range(struct inode *inode, loff_t from, + loff_t length, bool *did_zero) +{ + struct buffer_head *bh; + struct folio *folio; + handle_t *handle; + int err; + + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + bh = ext4_load_tail_bh(inode, from); + if (IS_ERR_OR_NULL(bh)) { + err = PTR_ERR_OR_ZERO(bh); + goto out_handle; + } + folio = bh->b_folio; + + BUFFER_TRACE(bh, "get write access"); + err = ext4_journal_get_write_access(handle, inode->i_sb, bh, + EXT4_JTR_NONE); + if (err) + goto out; + + folio_zero_range(folio, offset_in_folio(folio, from), length); + BUFFER_TRACE(bh, "zeroed end of block"); + + err = ext4_dirty_journalled_data(handle, bh); + if (err) + goto out; + + if (did_zero) + *did_zero = true; +out: + folio_unlock(folio); + folio_put(folio); +out_handle: + ext4_journal_stop(handle); return err; } /* - * ext4_block_zero_page_range() zeros out a mapping of length 'length' - * starting from file offset 'from'. The range to be zero'd must - * be contained with in one block. If the specified range exceeds - * the end of the block it will be shortened to end of the block - * that corresponds to 'from' + * Zeros out a mapping of length 'length' starting from file offset + * 'from'. The range to be zero'd must be contained with in one block. + * If the specified range exceeds the end of the block it will be + * shortened to end of the block that corresponds to 'from'. */ -static int ext4_block_zero_page_range(handle_t *handle, - struct address_space *mapping, loff_t from, loff_t length) +static int ext4_block_zero_range(struct inode *inode, + loff_t from, loff_t length, bool *did_zero, + bool *zero_written) { - struct inode *inode = mapping->host; unsigned blocksize = inode->i_sb->s_blocksize; unsigned int max = blocksize - (from & (blocksize - 1)); @@ -4132,40 +4189,73 @@ static int ext4_block_zero_page_range(handle_t *handle, length = max; if (IS_DAX(inode)) { - return dax_zero_range(inode, from, length, NULL, + return dax_zero_range(inode, from, length, did_zero, &ext4_iomap_ops); + } else if (ext4_should_journal_data(inode)) { + return ext4_block_journalled_zero_range(inode, from, length, + did_zero); } - return __ext4_block_zero_page_range(handle, mapping, from, length); + return ext4_block_do_zero_range(inode, from, length, did_zero, + zero_written); } /* - * ext4_block_truncate_page() zeroes out a mapping from file offset `from' - * up to the end of the block which corresponds to `from'. - * This required during truncate. We need to physically zero the tail end - * of that block so it doesn't yield old data if the file is later grown. + * Zero out a mapping from file offset 'from' up to the end of the block + * which corresponds to 'from' or to the given 'end' inside this block. + * This required during truncate up and performing append writes. We need + * to physically zero the tail end of that block so it doesn't yield old + * data if the file is grown. */ -static int ext4_block_truncate_page(handle_t *handle, - struct address_space *mapping, loff_t from) +int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end) { - unsigned length; - unsigned blocksize; - struct inode *inode = mapping->host; + unsigned int blocksize = i_blocksize(inode); + unsigned int offset; + loff_t length = end - from; + bool did_zero = false; + bool zero_written = false; + int err; + offset = from & (blocksize - 1); + if (!offset || from >= end) + return 0; /* If we are processing an encrypted inode during orphan list handling */ if (IS_ENCRYPTED(inode) && !fscrypt_has_encryption_key(inode)) return 0; - blocksize = i_blocksize(inode); - length = blocksize - (from & (blocksize - 1)); + if (length > blocksize - offset) + length = blocksize - offset; - return ext4_block_zero_page_range(handle, mapping, from, length); + err = ext4_block_zero_range(inode, from, length, + &did_zero, &zero_written); + if (err) + return err; + /* + * It's necessary to order zeroed data before update i_disksize when + * truncating up or performing an append write, because there might be + * exposing stale on-disk data which may caused by concurrent post-EOF + * mmap write during folio writeback. + */ + if (ext4_should_order_data(inode) && + did_zero && zero_written && !IS_DAX(inode)) { + handle_t *handle; + + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + err = ext4_jbd2_inode_add_write(handle, inode, from, length); + ext4_journal_stop(handle); + if (err) + return err; + } + + return 0; } -int ext4_zero_partial_blocks(handle_t *handle, struct inode *inode, - loff_t lstart, loff_t length) +int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length, + bool *did_zero) { struct super_block *sb = inode->i_sb; - struct address_space *mapping = inode->i_mapping; unsigned partial_start, partial_end; ext4_fsblk_t start, end; loff_t byte_end = (lstart + length - 1); @@ -4180,22 +4270,21 @@ int ext4_zero_partial_blocks(handle_t *handle, struct inode *inode, /* Handle partial zero within the single block */ if (start == end && (partial_start || (partial_end != sb->s_blocksize - 1))) { - err = ext4_block_zero_page_range(handle, mapping, - lstart, length); + err = ext4_block_zero_range(inode, lstart, length, did_zero, + NULL); return err; } /* Handle partial zero out on the start of the range */ if (partial_start) { - err = ext4_block_zero_page_range(handle, mapping, - lstart, sb->s_blocksize); + err = ext4_block_zero_range(inode, lstart, sb->s_blocksize, + did_zero, NULL); if (err) return err; } /* Handle partial zero out on the end of the range */ if (partial_end != sb->s_blocksize - 1) - err = ext4_block_zero_page_range(handle, mapping, - byte_end - partial_end, - partial_end + 1); + err = ext4_block_zero_range(inode, byte_end - partial_end, + partial_end + 1, did_zero, NULL); return err; } @@ -4344,6 +4433,7 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) loff_t end = offset + length; handle_t *handle; unsigned int credits; + bool partial_zeroed = false; int ret; trace_ext4_punch_hole(inode, offset, length, 0); @@ -4370,17 +4460,6 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) end = max_end; length = end - offset; - /* - * Attach jinode to inode for jbd2 if we do any zeroing of partial - * block. - */ - if (!IS_ALIGNED(offset | end, sb->s_blocksize)) { - ret = ext4_inode_attach_jinode(inode); - if (ret < 0) - return ret; - } - - ret = ext4_update_disksize_before_punch(inode, offset, length); if (ret) return ret; @@ -4390,8 +4469,18 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) if (ret) return ret; + ret = ext4_zero_partial_blocks(inode, offset, length, &partial_zeroed); + if (ret) + return ret; + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) { + ret = filemap_write_and_wait_range(inode->i_mapping, offset, + end - 1); + if (ret) + return ret; + } + if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) - credits = ext4_chunk_trans_extent(inode, 2); + credits = ext4_chunk_trans_extent(inode, 0); else credits = ext4_blocks_for_truncate(inode); handle = ext4_journal_start(inode, EXT4_HT_TRUNCATE, credits); @@ -4401,10 +4490,6 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) return ret; } - ret = ext4_zero_partial_blocks(handle, inode, offset, length); - if (ret) - goto out_handle; - /* If there are blocks to remove, do it */ start_lblk = EXT4_B_TO_LBLK(inode, offset); end_lblk = end >> inode->i_blkbits; @@ -4441,7 +4526,7 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) goto out_handle; ext4_update_inode_fsync_trans(handle, inode, 1); - if (IS_SYNC(inode)) + if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) ext4_handle_sync(handle); out_handle: ext4_journal_stop(handle); @@ -4512,7 +4597,6 @@ int ext4_truncate(struct inode *inode) unsigned int credits; int err = 0, err2; handle_t *handle; - struct address_space *mapping = inode->i_mapping; /* * There is a possibility that we're either freeing the inode @@ -4542,6 +4626,11 @@ int ext4_truncate(struct inode *inode) err = ext4_inode_attach_jinode(inode); if (err) goto out_trace; + + /* Zero to the end of the block containing i_size */ + err = ext4_block_zero_eof(inode, inode->i_size, LLONG_MAX); + if (err) + goto out_trace; } if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) @@ -4555,9 +4644,6 @@ int ext4_truncate(struct inode *inode) goto out_trace; } - if (inode->i_size & (inode->i_sb->s_blocksize - 1)) - ext4_block_truncate_page(handle, mapping, inode->i_size); - /* * We add the inode to the orphan list, so that if this * truncate spans multiple transactions, and we crash, we will @@ -5927,15 +6013,6 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry, goto out_mmap_sem; } - handle = ext4_journal_start(inode, EXT4_HT_INODE, 3); - if (IS_ERR(handle)) { - error = PTR_ERR(handle); - goto out_mmap_sem; - } - if (ext4_handle_valid(handle) && shrink) { - error = ext4_orphan_add(handle, inode); - orphan = 1; - } /* * Update c/mtime and tail zero the EOF folio on * truncate up. ext4_truncate() handles the shrink case @@ -5944,9 +6021,22 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if (!shrink) { inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); - if (oldsize & (inode->i_sb->s_blocksize - 1)) - ext4_block_truncate_page(handle, - inode->i_mapping, oldsize); + if (oldsize & (inode->i_sb->s_blocksize - 1)) { + error = ext4_block_zero_eof(inode, + oldsize, LLONG_MAX); + if (error) + goto out_mmap_sem; + } + } + + handle = ext4_journal_start(inode, EXT4_HT_INODE, 3); + if (IS_ERR(handle)) { + error = PTR_ERR(handle); + goto out_mmap_sem; + } + if (ext4_handle_valid(handle) && shrink) { + error = ext4_orphan_add(handle, inode); + orphan = 1; } if (shrink) diff --git a/fs/ext4/mballoc-test.c b/fs/ext4/mballoc-test.c index c75b91ae0cf0..90ed505fa4b1 100644 --- a/fs/ext4/mballoc-test.c +++ b/fs/ext4/mballoc-test.c @@ -362,7 +362,6 @@ static int mbt_kunit_init(struct kunit *test) return ret; } - test->priv = sb; kunit_activate_static_stub(test, ext4_read_block_bitmap_nowait, ext4_read_block_bitmap_nowait_stub); @@ -383,6 +382,8 @@ static int mbt_kunit_init(struct kunit *test) return -ENOMEM; } + test->priv = sb; + return 0; } @@ -390,6 +391,9 @@ static void mbt_kunit_exit(struct kunit *test) { struct super_block *sb = (struct super_block *)test->priv; + if (!sb) + return; + mbt_mb_release(sb); mbt_ctx_release(sb); mbt_ext4_free_super_block(sb); diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index a44514a3f5fe..ed1bd00e11cd 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -2876,7 +2876,7 @@ ext4_group_t ext4_mb_prefetch(struct super_block *sb, ext4_group_t group, EXT4_MB_GRP_NEED_INIT(grp) && ext4_free_group_clusters(sb, gdp) > 0 ) { bh = ext4_read_block_bitmap_nowait(sb, group, true); - if (bh && !IS_ERR(bh)) { + if (!IS_ERR_OR_NULL(bh)) { if (!buffer_uptodate(bh) && cnt) (*cnt)++; brelse(bh); @@ -4561,22 +4561,16 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac, (req <= (size) || max <= (chunk_size)) /* first, try to predict filesize */ - /* XXX: should this table be tunable? */ start_off = 0; - if (size <= 16 * 1024) { - size = 16 * 1024; - } else if (size <= 32 * 1024) { - size = 32 * 1024; - } else if (size <= 64 * 1024) { - size = 64 * 1024; - } else if (size <= 128 * 1024) { - size = 128 * 1024; - } else if (size <= 256 * 1024) { - size = 256 * 1024; - } else if (size <= 512 * 1024) { - size = 512 * 1024; - } else if (size <= 1024 * 1024) { - size = 1024 * 1024; + if (size <= SZ_1M) { + /* + * For files up to 1MB, round up the preallocation size to + * the next power of two, with a minimum of 16KB. + */ + if (size <= (unsigned long)SZ_16K) + size = SZ_16K; + else + size = roundup_pow_of_two(size); } else if (NRL_CHECK_SIZE(size, 4 * 1024 * 1024, max, 2 * 1024)) { start_off = ((loff_t)ac->ac_o_ex.fe_logical >> (21 - bsbits)) << 21; diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c index ab17c1d3a7b5..3329b7ad5dbd 100644 --- a/fs/ext4/move_extent.c +++ b/fs/ext4/move_extent.c @@ -224,8 +224,8 @@ static int mext_move_begin(struct mext_data *mext, struct folio *folio[2], } /* Adjust the moving length according to the length of shorter folio. */ - move_len = umin(folio_pos(folio[0]) + folio_size(folio[0]) - orig_pos, - folio_pos(folio[1]) + folio_size(folio[1]) - donor_pos); + move_len = umin(folio_next_pos(folio[0]) - orig_pos, + folio_next_pos(folio[1]) - donor_pos); move_len >>= blkbits; if (move_len < mext->orig_map.m_len) mext->orig_map.m_len = move_len; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 503dc9ffd614..4a47fbd8dd30 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -647,7 +647,7 @@ static struct stats dx_show_leaf(struct inode *dir, /* Directory is not encrypted */ (void) ext4fs_dirhash(dir, de->name, de->name_len, &h); - printk("%*.s:(U)%x.%u ", len, + printk("%.*s:(U)%x.%u ", len, name, h.hash, (unsigned) ((char *) de - base)); @@ -683,7 +683,7 @@ static struct stats dx_show_leaf(struct inode *dir, (void) ext4fs_dirhash(dir, de->name, de->name_len, &h); - printk("%*.s:(E)%x.%u ", len, name, + printk("%.*s:(E)%x.%u ", len, name, h.hash, (unsigned) ((char *) de - base)); fscrypt_fname_free_buffer( @@ -694,7 +694,7 @@ static struct stats dx_show_leaf(struct inode *dir, char *name = de->name; (void) ext4fs_dirhash(dir, de->name, de->name_len, &h); - printk("%*.s:%x.%u ", len, name, h.hash, + printk("%.*s:%x.%u ", len, name, h.hash, (unsigned) ((char *) de - base)); #endif } @@ -723,7 +723,7 @@ struct stats dx_show_entries(struct dx_hash_info *hinfo, struct inode *dir, struct stats stats; printk("%s%3u:%03u hash %8x/%8x ",levels?"":" ", i, block, hash, range); bh = ext4_bread(NULL,dir, block, 0); - if (!bh || IS_ERR(bh)) + if (IS_ERR_OR_NULL(bh)) continue; stats = levels? dx_show_entries(hinfo, dir, ((struct dx_node *) bh->b_data)->entries, levels - 1): @@ -2353,10 +2353,10 @@ out_frames: * may not sleep between calling this and putting something into * the entry, as someone else might have used it while you slept. */ -static int ext4_add_entry(handle_t *handle, struct dentry *dentry, +static int __ext4_add_entry(handle_t *handle, struct inode *dir, + const struct qstr *d_name, struct inode *inode) { - struct inode *dir = d_inode(dentry->d_parent); struct buffer_head *bh = NULL; struct ext4_dir_entry_2 *de; struct super_block *sb; @@ -2373,13 +2373,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, sb = dir->i_sb; blocksize = sb->s_blocksize; - if (fscrypt_is_nokey_name(dentry)) - return -ENOKEY; - - if (!generic_ci_validate_strict_name(dir, &dentry->d_name)) + if (!generic_ci_validate_strict_name(dir, d_name)) return -EINVAL; - retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname); + retval = ext4_fname_setup_filename(dir, d_name, 0, &fname); if (retval) return retval; @@ -2460,6 +2457,16 @@ out: return retval; } +static int ext4_add_entry(handle_t *handle, struct dentry *dentry, + struct inode *inode) +{ + struct inode *dir = d_inode(dentry->d_parent); + + if (fscrypt_is_nokey_name(dentry)) + return -ENOKEY; + return __ext4_add_entry(handle, dir, &dentry->d_name, inode); +} + /* * Returns 0 for success, or a negative error value */ @@ -3445,7 +3452,8 @@ out_retry: return err; } -int __ext4_link(struct inode *dir, struct inode *inode, struct dentry *dentry) +int __ext4_link(struct inode *dir, struct inode *inode, + const struct qstr *d_name, struct dentry *dentry) { handle_t *handle; int err, retries = 0; @@ -3461,9 +3469,8 @@ retry: inode_set_ctime_current(inode); ext4_inc_count(inode); - ihold(inode); - err = ext4_add_entry(handle, dentry, inode); + err = __ext4_add_entry(handle, dir, d_name, inode); if (!err) { err = ext4_mark_inode_dirty(handle, inode); /* this can happen only for tmpfile being @@ -3471,11 +3478,10 @@ retry: */ if (inode->i_nlink == 1) ext4_orphan_del(handle, inode); - d_instantiate(dentry, inode); - ext4_fc_track_link(handle, dentry); + if (dentry) + ext4_fc_track_link(handle, inode, dentry); } else { drop_nlink(inode); - iput(inode); } ext4_journal_stop(handle); if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries)) @@ -3504,9 +3510,13 @@ static int ext4_link(struct dentry *old_dentry, err = dquot_initialize(dir); if (err) return err; - return __ext4_link(dir, inode, dentry); + err = __ext4_link(dir, inode, &dentry->d_name, dentry); + if (!err) { + ihold(inode); + d_instantiate(dentry, inode); + } + return err; } - /* * Try to find buffer head where contains the parent block. * It should be the inode block if it is inlined or the 1st block diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 578508eb4f1a..6a77db4d3124 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -521,6 +521,7 @@ static bool ext4_journalled_writepage_needs_redirty(struct jbd2_inode *jinode, { struct buffer_head *bh, *head; struct journal_head *jh; + transaction_t *trans = READ_ONCE(jinode->i_transaction); bh = head = folio_buffers(folio); do { @@ -539,7 +540,7 @@ static bool ext4_journalled_writepage_needs_redirty(struct jbd2_inode *jinode, */ jh = bh2jh(bh); if (buffer_dirty(bh) || - (jh && (jh->b_transaction != jinode->i_transaction || + (jh && (jh->b_transaction != trans || jh->b_next_transaction))) return true; } while ((bh = bh->b_this_page) != head); @@ -550,15 +551,20 @@ static bool ext4_journalled_writepage_needs_redirty(struct jbd2_inode *jinode, static int ext4_journalled_submit_inode_data_buffers(struct jbd2_inode *jinode) { struct address_space *mapping = jinode->i_vfs_inode->i_mapping; + loff_t range_start, range_end; struct writeback_control wbc = { - .sync_mode = WB_SYNC_ALL, + .sync_mode = WB_SYNC_ALL, .nr_to_write = LONG_MAX, - .range_start = jinode->i_dirty_start, - .range_end = jinode->i_dirty_end, - }; + }; struct folio *folio = NULL; int error; + if (!jbd2_jinode_get_dirty_range(jinode, &range_start, &range_end)) + return 0; + + wbc.range_start = range_start; + wbc.range_end = range_end; + /* * writeback_iter() already checks for dirty pages and calls * folio_clear_dirty_for_io(), which we want to write protect the diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index 645240cc0229..b612262719ed 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -92,7 +92,7 @@ static const char *ext4_get_link(struct dentry *dentry, struct inode *inode, if (!dentry) { bh = ext4_getblk(NULL, inode, 0, EXT4_GET_BLOCKS_CACHED_NOWAIT); - if (IS_ERR(bh) || !bh) + if (IS_ERR_OR_NULL(bh)) return ERR_PTR(-ECHILD); if (!ext4_buffer_uptodate(bh)) { brelse(bh); diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index 60aec4712f7f..982a1f831e22 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -226,7 +226,7 @@ check_xattrs(struct inode *inode, struct buffer_head *bh, /* Find the end of the names list */ while (!IS_LAST_ENTRY(e)) { struct ext4_xattr_entry *next = EXT4_XATTR_NEXT(e); - if ((void *)next >= end) { + if ((void *)next + sizeof(u32) > end) { err_str = "e_name out of bounds"; goto errout; } @@ -1165,7 +1165,7 @@ ext4_xattr_inode_dec_ref_all(handle_t *handle, struct inode *parent, { struct inode *ea_inode; struct ext4_xattr_entry *entry; - struct ext4_iloc iloc; + struct ext4_iloc iloc = { .bh = NULL }; bool dirty = false; unsigned int ea_ino; int err; @@ -1260,6 +1260,8 @@ ext4_xattr_inode_dec_ref_all(handle_t *handle, struct inode *parent, ext4_warning_inode(parent, "handle dirty metadata err=%d", err); } + + brelse(iloc.bh); } /* diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index 7203d2d2624d..8cf61e7185c4 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -180,7 +180,13 @@ static int journal_wait_on_commit_record(journal_t *journal, /* Send all the data buffers related to an inode */ int jbd2_submit_inode_data(journal_t *journal, struct jbd2_inode *jinode) { - if (!jinode || !(jinode->i_flags & JI_WRITE_DATA)) + unsigned long flags; + + if (!jinode) + return 0; + + flags = READ_ONCE(jinode->i_flags); + if (!(flags & JI_WRITE_DATA)) return 0; trace_jbd2_submit_inode_data(jinode->i_vfs_inode); @@ -191,12 +197,30 @@ EXPORT_SYMBOL(jbd2_submit_inode_data); int jbd2_wait_inode_data(journal_t *journal, struct jbd2_inode *jinode) { - if (!jinode || !(jinode->i_flags & JI_WAIT_DATA) || - !jinode->i_vfs_inode || !jinode->i_vfs_inode->i_mapping) + struct address_space *mapping; + struct inode *inode; + unsigned long flags; + loff_t start_byte, end_byte; + + if (!jinode) + return 0; + + flags = READ_ONCE(jinode->i_flags); + if (!(flags & JI_WAIT_DATA)) + return 0; + + inode = jinode->i_vfs_inode; + if (!inode) + return 0; + + mapping = inode->i_mapping; + if (!mapping) + return 0; + + if (!jbd2_jinode_get_dirty_range(jinode, &start_byte, &end_byte)) return 0; return filemap_fdatawait_range_keep_errors( - jinode->i_vfs_inode->i_mapping, jinode->i_dirty_start, - jinode->i_dirty_end); + mapping, start_byte, end_byte); } EXPORT_SYMBOL(jbd2_wait_inode_data); @@ -218,7 +242,8 @@ static int journal_submit_data_buffers(journal_t *journal, list_for_each_entry(jinode, &commit_transaction->t_inode_list, i_list) { if (!(jinode->i_flags & JI_WRITE_DATA)) continue; - jinode->i_flags |= JI_COMMIT_RUNNING; + WRITE_ONCE(jinode->i_flags, + jinode->i_flags | JI_COMMIT_RUNNING); spin_unlock(&journal->j_list_lock); /* submit the inode data buffers. */ trace_jbd2_submit_inode_data(jinode->i_vfs_inode); @@ -229,7 +254,8 @@ static int journal_submit_data_buffers(journal_t *journal, } spin_lock(&journal->j_list_lock); J_ASSERT(jinode->i_transaction == commit_transaction); - jinode->i_flags &= ~JI_COMMIT_RUNNING; + WRITE_ONCE(jinode->i_flags, + jinode->i_flags & ~JI_COMMIT_RUNNING); smp_mb(); wake_up_bit(&jinode->i_flags, __JI_COMMIT_RUNNING); } @@ -240,10 +266,13 @@ static int journal_submit_data_buffers(journal_t *journal, int jbd2_journal_finish_inode_data_buffers(struct jbd2_inode *jinode) { struct address_space *mapping = jinode->i_vfs_inode->i_mapping; + loff_t start_byte, end_byte; + + if (!jbd2_jinode_get_dirty_range(jinode, &start_byte, &end_byte)) + return 0; return filemap_fdatawait_range_keep_errors(mapping, - jinode->i_dirty_start, - jinode->i_dirty_end); + start_byte, end_byte); } /* @@ -262,7 +291,7 @@ static int journal_finish_inode_data_buffers(journal_t *journal, list_for_each_entry(jinode, &commit_transaction->t_inode_list, i_list) { if (!(jinode->i_flags & JI_WAIT_DATA)) continue; - jinode->i_flags |= JI_COMMIT_RUNNING; + WRITE_ONCE(jinode->i_flags, jinode->i_flags | JI_COMMIT_RUNNING); spin_unlock(&journal->j_list_lock); /* wait for the inode data buffers writeout. */ if (journal->j_finish_inode_data_buffers) { @@ -272,7 +301,7 @@ static int journal_finish_inode_data_buffers(journal_t *journal, } cond_resched(); spin_lock(&journal->j_list_lock); - jinode->i_flags &= ~JI_COMMIT_RUNNING; + WRITE_ONCE(jinode->i_flags, jinode->i_flags & ~JI_COMMIT_RUNNING); smp_mb(); wake_up_bit(&jinode->i_flags, __JI_COMMIT_RUNNING); } @@ -288,8 +317,8 @@ static int journal_finish_inode_data_buffers(journal_t *journal, &jinode->i_transaction->t_inode_list); } else { jinode->i_transaction = NULL; - jinode->i_dirty_start = 0; - jinode->i_dirty_end = 0; + WRITE_ONCE(jinode->i_dirty_start_page, 0); + WRITE_ONCE(jinode->i_dirty_end_page, 0); } } spin_unlock(&journal->j_list_lock); diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index b60918ed8a99..4f397fcdb13c 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -3018,8 +3018,8 @@ void jbd2_journal_init_jbd_inode(struct jbd2_inode *jinode, struct inode *inode) jinode->i_next_transaction = NULL; jinode->i_vfs_inode = inode; jinode->i_flags = 0; - jinode->i_dirty_start = 0; - jinode->i_dirty_end = 0; + jinode->i_dirty_start_page = 0; + jinode->i_dirty_end_page = 0; INIT_LIST_HEAD(&jinode->i_list); } @@ -3176,4 +3176,3 @@ MODULE_DESCRIPTION("Generic filesystem journal-writing module"); MODULE_LICENSE("GPL"); module_init(journal_init); module_exit(journal_exit); - diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c index 9016ddb82447..e4c2fbd381f1 100644 --- a/fs/jbd2/revoke.c +++ b/fs/jbd2/revoke.c @@ -428,6 +428,7 @@ void jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh) journal_t *journal = handle->h_transaction->t_journal; int need_cancel; struct buffer_head *bh = jh2bh(jh); + struct address_space *bh_mapping = bh->b_folio->mapping; jbd2_debug(4, "journal_head %p, cancelling revoke\n", jh); @@ -464,13 +465,14 @@ void jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh) * buffer_head? If so, we'd better make sure we clear the * revoked status on any hashed alias too, otherwise the revoke * state machine will get very upset later on. */ - if (need_cancel) { + if (need_cancel && !sb_is_blkdev_sb(bh_mapping->host->i_sb)) { struct buffer_head *bh2; + bh2 = __find_get_block_nonatomic(bh->b_bdev, bh->b_blocknr, bh->b_size); if (bh2) { - if (bh2 != bh) - clear_buffer_revoked(bh2); + WARN_ON_ONCE(bh2 == bh); + clear_buffer_revoked(bh2); __brelse(bh2); } } diff --git a/fs/jbd2/transaction.c b/fs/jbd2/transaction.c index a90f9092706c..4885903bbd10 100644 --- a/fs/jbd2/transaction.c +++ b/fs/jbd2/transaction.c @@ -474,7 +474,8 @@ handle_t *jbd2__journal_start(journal_t *journal, int nblocks, int rsv_blocks, return ERR_PTR(-EROFS); if (handle) { - J_ASSERT(handle->h_transaction->t_journal == journal); + if (WARN_ON_ONCE(handle->h_transaction->t_journal != journal)) + return ERR_PTR(-EINVAL); handle->h_ref++; return handle; } @@ -1036,7 +1037,13 @@ repeat: */ if (!jh->b_transaction) { JBUFFER_TRACE(jh, "no transaction"); - J_ASSERT_JH(jh, !jh->b_next_transaction); + if (WARN_ON_ONCE(jh->b_next_transaction)) { + spin_unlock(&jh->b_state_lock); + unlock_buffer(bh); + error = -EINVAL; + jbd2_journal_abort(journal, error); + goto out; + } JBUFFER_TRACE(jh, "file as BJ_Reserved"); /* * Make sure all stores to jh (b_modified, b_frozen_data) are @@ -1069,13 +1076,27 @@ repeat: */ if (jh->b_frozen_data) { JBUFFER_TRACE(jh, "has frozen data"); - J_ASSERT_JH(jh, jh->b_next_transaction == NULL); + if (WARN_ON_ONCE(jh->b_next_transaction)) { + spin_unlock(&jh->b_state_lock); + error = -EINVAL; + jbd2_journal_abort(journal, error); + goto out; + } goto attach_next; } JBUFFER_TRACE(jh, "owned by older transaction"); - J_ASSERT_JH(jh, jh->b_next_transaction == NULL); - J_ASSERT_JH(jh, jh->b_transaction == journal->j_committing_transaction); + if (WARN_ON_ONCE(jh->b_next_transaction || + jh->b_transaction != + journal->j_committing_transaction)) { + pr_err("JBD2: %s: assertion failure: b_next_transaction=%p b_transaction=%p j_committing_transaction=%p\n", + journal->j_devname, jh->b_next_transaction, + jh->b_transaction, journal->j_committing_transaction); + spin_unlock(&jh->b_state_lock); + error = -EINVAL; + jbd2_journal_abort(journal, error); + goto out; + } /* * There is one case we have to be very careful about. If the @@ -1302,7 +1323,12 @@ int jbd2_journal_get_create_access(handle_t *handle, struct buffer_head *bh) goto out; } - J_ASSERT_JH(jh, buffer_locked(jh2bh(jh))); + if (WARN_ON_ONCE(!buffer_locked(jh2bh(jh)))) { + err = -EINVAL; + spin_unlock(&jh->b_state_lock); + jbd2_journal_abort(journal, err); + goto out; + } if (jh->b_transaction == NULL) { /* @@ -1491,7 +1517,7 @@ void jbd2_buffer_abort_trigger(struct journal_head *jh, int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh) { transaction_t *transaction = handle->h_transaction; - journal_t *journal; + journal_t *journal = transaction->t_journal; struct journal_head *jh; int ret = 0; @@ -1515,8 +1541,14 @@ int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh) if (data_race(jh->b_transaction != transaction && jh->b_next_transaction != transaction)) { spin_lock(&jh->b_state_lock); - J_ASSERT_JH(jh, jh->b_transaction == transaction || - jh->b_next_transaction == transaction); + if (WARN_ON_ONCE(jh->b_transaction != transaction && + jh->b_next_transaction != transaction)) { + pr_err("JBD2: %s: assertion failure: b_transaction=%p transaction=%p b_next_transaction=%p\n", + journal->j_devname, jh->b_transaction, + transaction, jh->b_next_transaction); + ret = -EINVAL; + goto out_unlock_bh; + } spin_unlock(&jh->b_state_lock); } if (data_race(jh->b_modified == 1)) { @@ -1524,15 +1556,15 @@ int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh) if (data_race(jh->b_transaction == transaction && jh->b_jlist != BJ_Metadata)) { spin_lock(&jh->b_state_lock); - if (jh->b_transaction == transaction && - jh->b_jlist != BJ_Metadata) - pr_err("JBD2: assertion failure: h_type=%u " - "h_line_no=%u block_no=%llu jlist=%u\n", + if (WARN_ON_ONCE(jh->b_transaction == transaction && + jh->b_jlist != BJ_Metadata)) { + pr_err("JBD2: assertion failure: h_type=%u h_line_no=%u block_no=%llu jlist=%u\n", handle->h_type, handle->h_line_no, (unsigned long long) bh->b_blocknr, jh->b_jlist); - J_ASSERT_JH(jh, jh->b_transaction != transaction || - jh->b_jlist == BJ_Metadata); + ret = -EINVAL; + goto out_unlock_bh; + } spin_unlock(&jh->b_state_lock); } goto out; @@ -1552,8 +1584,6 @@ int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh) goto out_unlock_bh; } - journal = transaction->t_journal; - if (jh->b_modified == 0) { /* * This buffer's got modified and becoming part @@ -1631,7 +1661,10 @@ int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh) } /* That test should have eliminated the following case: */ - J_ASSERT_JH(jh, jh->b_frozen_data == NULL); + if (WARN_ON_ONCE(jh->b_frozen_data)) { + ret = -EINVAL; + goto out_unlock_bh; + } JBUFFER_TRACE(jh, "file as BJ_Metadata"); spin_lock(&journal->j_list_lock); @@ -1670,6 +1703,7 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh) int err = 0; int was_modified = 0; int wait_for_writeback = 0; + int abort_journal = 0; if (is_handle_aborted(handle)) return -EROFS; @@ -1703,7 +1737,11 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh) jh->b_modified = 0; if (jh->b_transaction == transaction) { - J_ASSERT_JH(jh, !jh->b_frozen_data); + if (WARN_ON_ONCE(jh->b_frozen_data)) { + err = -EINVAL; + abort_journal = 1; + goto drop; + } /* If we are forgetting a buffer which is already part * of this transaction, then we can just drop it from @@ -1742,8 +1780,11 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh) } spin_unlock(&journal->j_list_lock); } else if (jh->b_transaction) { - J_ASSERT_JH(jh, (jh->b_transaction == - journal->j_committing_transaction)); + if (WARN_ON_ONCE(jh->b_transaction != journal->j_committing_transaction)) { + err = -EINVAL; + abort_journal = 1; + goto drop; + } /* However, if the buffer is still owned by a prior * (committing) transaction, we can't drop it yet... */ JBUFFER_TRACE(jh, "belongs to older transaction"); @@ -1761,7 +1802,11 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh) jh->b_next_transaction = transaction; spin_unlock(&journal->j_list_lock); } else { - J_ASSERT(jh->b_next_transaction == transaction); + if (WARN_ON_ONCE(jh->b_next_transaction != transaction)) { + err = -EINVAL; + abort_journal = 1; + goto drop; + } /* * only drop a reference if this transaction modified @@ -1807,6 +1852,8 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh) drop: __brelse(bh); spin_unlock(&jh->b_state_lock); + if (abort_journal) + jbd2_journal_abort(journal, err); if (wait_for_writeback) wait_on_buffer(bh); jbd2_journal_put_journal_head(jh); @@ -2131,7 +2178,8 @@ bool jbd2_journal_try_to_free_buffers(journal_t *journal, struct folio *folio) struct buffer_head *bh; bool ret = false; - J_ASSERT(folio_test_locked(folio)); + if (WARN_ON_ONCE(!folio_test_locked(folio))) + return false; head = folio_buffers(folio); bh = head; @@ -2646,6 +2694,9 @@ static int jbd2_journal_file_inode(handle_t *handle, struct jbd2_inode *jinode, { transaction_t *transaction = handle->h_transaction; journal_t *journal; + pgoff_t start_page, end_page; + int err = 0; + int abort_transaction = 0; if (is_handle_aborted(handle)) return -EROFS; @@ -2654,15 +2705,21 @@ static int jbd2_journal_file_inode(handle_t *handle, struct jbd2_inode *jinode, jbd2_debug(4, "Adding inode %llu, tid:%d\n", jinode->i_vfs_inode->i_ino, transaction->t_tid); - spin_lock(&journal->j_list_lock); - jinode->i_flags |= flags; + start_page = (pgoff_t)(start_byte >> PAGE_SHIFT); + end_page = (pgoff_t)(end_byte >> PAGE_SHIFT) + 1; - if (jinode->i_dirty_end) { - jinode->i_dirty_start = min(jinode->i_dirty_start, start_byte); - jinode->i_dirty_end = max(jinode->i_dirty_end, end_byte); + spin_lock(&journal->j_list_lock); + WRITE_ONCE(jinode->i_flags, jinode->i_flags | flags); + + if (jinode->i_dirty_start_page != jinode->i_dirty_end_page) { + WRITE_ONCE(jinode->i_dirty_start_page, + min(jinode->i_dirty_start_page, start_page)); + WRITE_ONCE(jinode->i_dirty_end_page, + max(jinode->i_dirty_end_page, end_page)); } else { - jinode->i_dirty_start = start_byte; - jinode->i_dirty_end = end_byte; + /* Publish a new non-empty range by making end visible first. */ + WRITE_ONCE(jinode->i_dirty_end_page, end_page); + WRITE_ONCE(jinode->i_dirty_start_page, start_page); } /* Is inode already attached where we need it? */ @@ -2680,20 +2737,33 @@ static int jbd2_journal_file_inode(handle_t *handle, struct jbd2_inode *jinode, /* On some different transaction's list - should be * the committing one */ if (jinode->i_transaction) { - J_ASSERT(jinode->i_next_transaction == NULL); - J_ASSERT(jinode->i_transaction == - journal->j_committing_transaction); + if (WARN_ON_ONCE(jinode->i_next_transaction || + jinode->i_transaction != + journal->j_committing_transaction)) { + pr_err("JBD2: %s: assertion failure: i_next_transaction=%p i_transaction=%p j_committing_transaction=%p\n", + journal->j_devname, jinode->i_next_transaction, + jinode->i_transaction, + journal->j_committing_transaction); + err = -EINVAL; + abort_transaction = 1; + goto done; + } jinode->i_next_transaction = transaction; goto done; } /* Not on any transaction list... */ - J_ASSERT(!jinode->i_next_transaction); + if (WARN_ON_ONCE(jinode->i_next_transaction)) { + err = -EINVAL; + abort_transaction = 1; + goto done; + } jinode->i_transaction = transaction; list_add(&jinode->i_list, &transaction->t_inode_list); done: spin_unlock(&journal->j_list_lock); - - return 0; + if (abort_transaction) + jbd2_journal_abort(journal, err); + return err; } int jbd2_journal_inode_ranged_write(handle_t *handle, @@ -2739,7 +2809,7 @@ int jbd2_journal_begin_ordered_truncate(journal_t *journal, int ret = 0; /* This is a quick check to avoid locking if not necessary */ - if (!jinode->i_transaction) + if (!READ_ONCE(jinode->i_transaction)) goto out; /* Locks are here just to force reading of recent values, it is * enough that the transaction was not committing before we started diff --git a/fs/ntfs/Kconfig b/fs/ntfs/Kconfig new file mode 100644 index 000000000000..6a6acde9ba91 --- /dev/null +++ b/fs/ntfs/Kconfig @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NTFS_FS + tristate "NTFS file system support" + select NLS + select FS_IOMAP + help + NTFS is the file system of Microsoft Windows NT, 2000, XP and 2003. + This allows you to mount devices formatted with the ntfs file system. + + To compile this as a module, choose M here: the module will be called + ntfs. + +config NTFS_DEBUG + bool "NTFS debugging support" + depends on NTFS_FS + help + If you are experiencing any problems with the NTFS file system, say + Y here. This will result in additional consistency checks to be + performed by the driver as well as additional debugging messages to + be written to the system log. Note that debugging messages are + disabled by default. To enable them, supply the option debug_msgs=1 + at the kernel command line when booting the kernel or as an option + to insmod when loading the ntfs module. Once the driver is active, + you can enable debugging messages by doing (as root): + echo 1 > /proc/sys/fs/ntfs-debug + Replacing the "1" with "0" would disable debug messages. + + If you leave debugging messages disabled, this results in little + overhead, but enabling debug messages results in very significant + slowdown of the system. + + When reporting bugs, please try to have available a full dump of + debugging messages while the misbehaviour was occurring. + +config NTFS_FS_POSIX_ACL + bool "NTFS POSIX Access Control Lists" + depends on NTFS_FS + select FS_POSIX_ACL + help + POSIX Access Control Lists (ACLs) support additional access rights + for users and groups beyond the standard owner/group/world scheme. + + This option enables ACL support for ntfs, providing functional parity + with ntfs3 drivier. + + NOTE: this is linux only feature. Windows will ignore these ACLs. + + If you don't know what Access Control Lists are, say N. diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile new file mode 100644 index 000000000000..0ce4d9a9388a --- /dev/null +++ b/fs/ntfs/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_NTFS_FS) += ntfs.o + +ntfs-y := aops.o attrib.o collate.o dir.o file.o index.o inode.o \ + mft.o mst.o namei.o runlist.o super.o unistr.o attrlist.o ea.o \ + upcase.o bitmap.o lcnalloc.o logfile.o reparse.o compress.o \ + iomap.o debug.o sysctl.o quota.o object_id.o bdev-io.o + +ccflags-$(CONFIG_NTFS_DEBUG) += -DDEBUG diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c new file mode 100644 index 000000000000..78d1ce41958e --- /dev/null +++ b/fs/ntfs/aops.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel address space operations and page cache handling. + * + * Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include + +#include "attrib.h" +#include "mft.h" +#include "ntfs.h" +#include "debug.h" +#include "iomap.h" + +/* + * ntfs_read_folio - Read data for a folio from the device + * @file: open file to which the folio @folio belongs or NULL + * @folio: page cache folio to fill with data + * + * This function handles reading data into the page cache. It first checks + * for specific ntfs attribute type like encryption and compression. + * + * - If the attribute is encrypted, access is denied (-EACCES) because + * decryption is not supported in this path. + * - If the attribute is non-resident and compressed, the read operation is + * delegated to ntfs_read_compressed_block(). + * - For normal resident or non-resident attribute, it utilizes the generic + * iomap infrastructure via iomap_bio_read_folio() to perform the I/O. + * + * Return: 0 on success, or -errno on error. + */ +static int ntfs_read_folio(struct file *file, struct folio *folio) +{ + struct ntfs_inode *ni = NTFS_I(folio->mapping->host); + + /* + * Only $DATA attributes can be encrypted and only unnamed $DATA + * attributes can be compressed. Index root can have the flags set but + * this means to create compressed/encrypted files, not that the + * attribute is compressed/encrypted. Note we need to check for + * AT_INDEX_ALLOCATION since this is the type of both directory and + * index inodes. + */ + if (ni->type != AT_INDEX_ALLOCATION) { + /* + * EFS-encrypted files are not supported. + * (decryption/encryption is not implemented yet) + */ + if (NInoEncrypted(ni)) { + folio_unlock(folio); + return -EOPNOTSUPP; + } + /* Compressed data streams are handled in compress.c. */ + if (NInoNonResident(ni) && NInoCompressed(ni)) + return ntfs_read_compressed_block(folio); + } + + iomap_bio_read_folio(folio, &ntfs_read_iomap_ops); + return 0; +} + +/* + * ntfs_bmap - map logical file block to physical device block + * @mapping: address space mapping to which the block to be mapped belongs + * @block: logical block to map to its physical device block + * + * For regular, non-resident files (i.e. not compressed and not encrypted), map + * the logical @block belonging to the file described by the address space + * mapping @mapping to its physical device block. + * + * The size of the block is equal to the @s_blocksize field of the super block + * of the mounted file system which is guaranteed to be smaller than or equal + * to the cluster size thus the block is guaranteed to fit entirely inside the + * cluster which means we do not need to care how many contiguous bytes are + * available after the beginning of the block. + * + * Return the physical device block if the mapping succeeded or 0 if the block + * is sparse or there was an error. + * + * Note: This is a problem if someone tries to run bmap() on $Boot system file + * as that really is in block zero but there is nothing we can do. bmap() is + * just broken in that respect (just like it cannot distinguish sparse from + * not available or error). + */ +static sector_t ntfs_bmap(struct address_space *mapping, sector_t block) +{ + s64 ofs, size; + loff_t i_size; + s64 lcn; + unsigned long blocksize, flags; + struct ntfs_inode *ni = NTFS_I(mapping->host); + struct ntfs_volume *vol = ni->vol; + unsigned int delta; + unsigned char blocksize_bits; + + ntfs_debug("Entering for mft_no 0x%llx, logical block 0x%llx.", + ni->mft_no, (unsigned long long)block); + if (ni->type != AT_DATA || !NInoNonResident(ni) || NInoEncrypted(ni) || + NInoMstProtected(ni)) { + ntfs_error(vol->sb, "BMAP does not make sense for %s attributes, returning 0.", + (ni->type != AT_DATA) ? "non-data" : + (!NInoNonResident(ni) ? "resident" : + "encrypted")); + return 0; + } + /* None of these can happen. */ + blocksize = vol->sb->s_blocksize; + blocksize_bits = vol->sb->s_blocksize_bits; + ofs = (s64)block << blocksize_bits; + read_lock_irqsave(&ni->size_lock, flags); + size = ni->initialized_size; + i_size = i_size_read(VFS_I(ni)); + read_unlock_irqrestore(&ni->size_lock, flags); + /* + * If the offset is outside the initialized size or the block straddles + * the initialized size then pretend it is a hole unless the + * initialized size equals the file size. + */ + if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size))) + goto hole; + down_read(&ni->runlist.lock); + lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ntfs_bytes_to_cluster(vol, ofs), + false); + up_read(&ni->runlist.lock); + if (unlikely(lcn < LCN_HOLE)) { + /* + * Step down to an integer to avoid gcc doing a long long + * comparision in the switch when we know @lcn is between + * LCN_HOLE and LCN_EIO (i.e. -1 to -5). + * + * Otherwise older gcc (at least on some architectures) will + * try to use __cmpdi2() which is of course not available in + * the kernel. + */ + switch ((int)lcn) { + case LCN_ENOENT: + /* + * If the offset is out of bounds then pretend it is a + * hole. + */ + goto hole; + case LCN_ENOMEM: + ntfs_error(vol->sb, + "Not enough memory to complete mapping for inode 0x%llx. Returning 0.", + ni->mft_no); + break; + default: + ntfs_error(vol->sb, + "Failed to complete mapping for inode 0x%llx. Run chkdsk. Returning 0.", + ni->mft_no); + break; + } + return 0; + } + if (lcn < 0) { + /* It is a hole. */ +hole: + ntfs_debug("Done (returning hole)."); + return 0; + } + /* + * The block is really allocated and fullfils all our criteria. + * Convert the cluster to units of block size and return the result. + */ + delta = ofs & vol->cluster_size_mask; + if (unlikely(sizeof(block) < sizeof(lcn))) { + block = lcn = (ntfs_cluster_to_bytes(vol, lcn) + delta) >> + blocksize_bits; + /* If the block number was truncated return 0. */ + if (unlikely(block != lcn)) { + ntfs_error(vol->sb, + "Physical block 0x%llx is too large to be returned, returning 0.", + (long long)lcn); + return 0; + } + } else + block = (ntfs_cluster_to_bytes(vol, lcn) + delta) >> + blocksize_bits; + ntfs_debug("Done (returning block 0x%llx).", (unsigned long long)lcn); + return block; +} + +static void ntfs_readahead(struct readahead_control *rac) +{ + struct address_space *mapping = rac->mapping; + struct inode *inode = mapping->host; + struct ntfs_inode *ni = NTFS_I(inode); + + /* + * Resident files are not cached in the page cache, + * and readahead is not implemented for compressed files. + */ + if (!NInoNonResident(ni) || NInoCompressed(ni)) + return; + iomap_bio_readahead(rac, &ntfs_read_iomap_ops); +} + +static int ntfs_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + struct inode *inode = mapping->host; + struct ntfs_inode *ni = NTFS_I(inode); + struct iomap_writepage_ctx wpc = { + .inode = mapping->host, + .wbc = wbc, + .ops = &ntfs_writeback_ops, + }; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (!NInoNonResident(ni)) + return 0; + + /* + * EFS-encrypted files are not supported. + * (decryption/encryption is not implemented yet) + */ + if (NInoEncrypted(ni)) { + ntfs_debug("Encrypted I/O not supported"); + return -EOPNOTSUPP; + } + + return iomap_writepages(&wpc); +} + +static int ntfs_swap_activate(struct swap_info_struct *sis, + struct file *swap_file, sector_t *span) +{ + return iomap_swapfile_activate(sis, swap_file, span, + &ntfs_read_iomap_ops); +} + +const struct address_space_operations ntfs_aops = { + .read_folio = ntfs_read_folio, + .readahead = ntfs_readahead, + .writepages = ntfs_writepages, + .direct_IO = noop_direct_IO, + .dirty_folio = iomap_dirty_folio, + .bmap = ntfs_bmap, + .migrate_folio = filemap_migrate_folio, + .is_partially_uptodate = iomap_is_partially_uptodate, + .error_remove_folio = generic_error_remove_folio, + .release_folio = iomap_release_folio, + .invalidate_folio = iomap_invalidate_folio, + .swap_activate = ntfs_swap_activate, +}; + +const struct address_space_operations ntfs_mft_aops = { + .read_folio = ntfs_read_folio, + .readahead = ntfs_readahead, + .writepages = ntfs_mft_writepages, + .dirty_folio = iomap_dirty_folio, + .bmap = ntfs_bmap, + .migrate_folio = filemap_migrate_folio, + .is_partially_uptodate = iomap_is_partially_uptodate, + .error_remove_folio = generic_error_remove_folio, + .release_folio = iomap_release_folio, + .invalidate_folio = iomap_invalidate_folio, +}; diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c new file mode 100644 index 000000000000..78915c1d5128 --- /dev/null +++ b/fs/ntfs/attrib.c @@ -0,0 +1,5539 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS attribute operations. + * + * Copyright (c) 2001-2012 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2000-2010 Anton Altaparmakov + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2002-2008 Szabolcs Szakacsits + * Copyright (c) 2004-2007 Yura Pakhuchiy + * Copyright (c) 2007-2021 Jean-Pierre Andre + * Copyright (c) 2010 Erik Larsson + */ + +#include +#include + +#include "attrib.h" +#include "attrlist.h" +#include "lcnalloc.h" +#include "debug.h" +#include "mft.h" +#include "ntfs.h" +#include "iomap.h" + +__le16 AT_UNNAMED[] = { cpu_to_le16('\0') }; + +/* + * ntfs_map_runlist_nolock - map (a part of) a runlist of an ntfs inode + * @ni: ntfs inode for which to map (part of) a runlist + * @vcn: map runlist part containing this vcn + * @ctx: active attribute search context if present or NULL if not + * + * Map the part of a runlist containing the @vcn of the ntfs inode @ni. + * + * If @ctx is specified, it is an active search context of @ni and its base mft + * record. This is needed when ntfs_map_runlist_nolock() encounters unmapped + * runlist fragments and allows their mapping. If you do not have the mft + * record mapped, you can specify @ctx as NULL and ntfs_map_runlist_nolock() + * will perform the necessary mapping and unmapping. + * + * Note, ntfs_map_runlist_nolock() saves the state of @ctx on entry and + * restores it before returning. Thus, @ctx will be left pointing to the same + * attribute on return as on entry. However, the actual pointers in @ctx may + * point to different memory locations on return, so you must remember to reset + * any cached pointers from the @ctx, i.e. after the call to + * ntfs_map_runlist_nolock(), you will probably want to do: + * m = ctx->mrec; + * a = ctx->attr; + * Assuming you cache ctx->attr in a variable @a of type struct attr_record * + * and that you cache ctx->mrec in a variable @m of type struct mft_record *. + * + * Return 0 on success and -errno on error. There is one special error code + * which is not an error as such. This is -ENOENT. It means that @vcn is out + * of bounds of the runlist. + * + * Note the runlist can be NULL after this function returns if @vcn is zero and + * the attribute has zero allocated size, i.e. there simply is no runlist. + * + * WARNING: If @ctx is supplied, regardless of whether success or failure is + * returned, you need to check IS_ERR(@ctx->mrec) and if 'true' the @ctx + * is no longer valid, i.e. you need to either call + * ntfs_attr_reinit_search_ctx() or ntfs_attr_put_search_ctx() on it. + * In that case PTR_ERR(@ctx->mrec) will give you the error code for + * why the mapping of the old inode failed. + * + * Locking: - The runlist described by @ni must be locked for writing on entry + * and is locked on return. Note the runlist will be modified. + * - If @ctx is NULL, the base mft record of @ni must not be mapped on + * entry and it will be left unmapped on return. + * - If @ctx is not NULL, the base mft record must be mapped on entry + * and it will be left mapped on return. + */ +int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_search_ctx *ctx) +{ + s64 end_vcn; + unsigned long flags; + struct ntfs_inode *base_ni; + struct mft_record *m; + struct attr_record *a; + struct runlist_element *rl; + struct folio *put_this_folio = NULL; + int err = 0; + bool ctx_is_temporary = false, ctx_needs_reset; + struct ntfs_attr_search_ctx old_ctx = { NULL, }; + size_t new_rl_count; + + ntfs_debug("Mapping runlist part containing vcn 0x%llx.", + (unsigned long long)vcn); + if (!NInoAttr(ni)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + if (!ctx) { + ctx_is_temporary = ctx_needs_reset = true; + m = map_mft_record(base_ni); + if (IS_ERR(m)) + return PTR_ERR(m); + ctx = ntfs_attr_get_search_ctx(base_ni, m); + if (unlikely(!ctx)) { + err = -ENOMEM; + goto err_out; + } + } else { + s64 allocated_size_vcn; + + WARN_ON(IS_ERR(ctx->mrec)); + a = ctx->attr; + if (!a->non_resident) { + err = -EIO; + goto err_out; + } + end_vcn = le64_to_cpu(a->data.non_resident.highest_vcn); + read_lock_irqsave(&ni->size_lock, flags); + allocated_size_vcn = + ntfs_bytes_to_cluster(ni->vol, ni->allocated_size); + read_unlock_irqrestore(&ni->size_lock, flags); + if (!a->data.non_resident.lowest_vcn && end_vcn <= 0) + end_vcn = allocated_size_vcn - 1; + /* + * If we already have the attribute extent containing @vcn in + * @ctx, no need to look it up again. We slightly cheat in + * that if vcn exceeds the allocated size, we will refuse to + * map the runlist below, so there is definitely no need to get + * the right attribute extent. + */ + if (vcn >= allocated_size_vcn || (a->type == ni->type && + a->name_length == ni->name_len && + !memcmp((u8 *)a + le16_to_cpu(a->name_offset), + ni->name, ni->name_len) && + le64_to_cpu(a->data.non_resident.lowest_vcn) + <= vcn && end_vcn >= vcn)) + ctx_needs_reset = false; + else { + /* Save the old search context. */ + old_ctx = *ctx; + /* + * If the currently mapped (extent) inode is not the + * base inode we will unmap it when we reinitialize the + * search context which means we need to get a + * reference to the page containing the mapped mft + * record so we do not accidentally drop changes to the + * mft record when it has not been marked dirty yet. + */ + if (old_ctx.base_ntfs_ino && old_ctx.ntfs_ino != + old_ctx.base_ntfs_ino) { + put_this_folio = old_ctx.ntfs_ino->folio; + folio_get(put_this_folio); + } + /* + * Reinitialize the search context so we can lookup the + * needed attribute extent. + */ + ntfs_attr_reinit_search_ctx(ctx); + ctx_needs_reset = true; + } + } + if (ctx_needs_reset) { + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, vcn, NULL, 0, ctx); + if (unlikely(err)) { + if (err == -ENOENT) + err = -EIO; + goto err_out; + } + WARN_ON(!ctx->attr->non_resident); + } + a = ctx->attr; + /* + * Only decompress the mapping pairs if @vcn is inside it. Otherwise + * we get into problems when we try to map an out of bounds vcn because + * we then try to map the already mapped runlist fragment and + * ntfs_mapping_pairs_decompress() fails. + */ + end_vcn = le64_to_cpu(a->data.non_resident.highest_vcn) + 1; + if (unlikely(vcn && vcn >= end_vcn)) { + err = -ENOENT; + goto err_out; + } + rl = ntfs_mapping_pairs_decompress(ni->vol, a, &ni->runlist, &new_rl_count); + if (IS_ERR(rl)) + err = PTR_ERR(rl); + else { + ni->runlist.rl = rl; + ni->runlist.count = new_rl_count; + } +err_out: + if (ctx_is_temporary) { + if (likely(ctx)) + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); + } else if (ctx_needs_reset) { + /* + * If there is no attribute list, restoring the search context + * is accomplished simply by copying the saved context back over + * the caller supplied context. If there is an attribute list, + * things are more complicated as we need to deal with mapping + * of mft records and resulting potential changes in pointers. + */ + if (NInoAttrList(base_ni)) { + /* + * If the currently mapped (extent) inode is not the + * one we had before, we need to unmap it and map the + * old one. + */ + if (ctx->ntfs_ino != old_ctx.ntfs_ino) { + /* + * If the currently mapped inode is not the + * base inode, unmap it. + */ + if (ctx->base_ntfs_ino && ctx->ntfs_ino != + ctx->base_ntfs_ino) { + unmap_extent_mft_record(ctx->ntfs_ino); + ctx->mrec = ctx->base_mrec; + WARN_ON(!ctx->mrec); + } + /* + * If the old mapped inode is not the base + * inode, map it. + */ + if (old_ctx.base_ntfs_ino && + old_ctx.ntfs_ino != old_ctx.base_ntfs_ino) { +retry_map: + ctx->mrec = map_mft_record(old_ctx.ntfs_ino); + /* + * Something bad has happened. If out + * of memory retry till it succeeds. + * Any other errors are fatal and we + * return the error code in ctx->mrec. + * Let the caller deal with it... We + * just need to fudge things so the + * caller can reinit and/or put the + * search context safely. + */ + if (IS_ERR(ctx->mrec)) { + if (PTR_ERR(ctx->mrec) == -ENOMEM) { + schedule(); + goto retry_map; + } else + old_ctx.ntfs_ino = + old_ctx.base_ntfs_ino; + } + } + } + /* Update the changed pointers in the saved context. */ + if (ctx->mrec != old_ctx.mrec) { + if (!IS_ERR(ctx->mrec)) + old_ctx.attr = (struct attr_record *)( + (u8 *)ctx->mrec + + ((u8 *)old_ctx.attr - + (u8 *)old_ctx.mrec)); + old_ctx.mrec = ctx->mrec; + } + } + /* Restore the search context to the saved one. */ + *ctx = old_ctx; + /* + * We drop the reference on the page we took earlier. In the + * case that IS_ERR(ctx->mrec) is true this means we might lose + * some changes to the mft record that had been made between + * the last time it was marked dirty/written out and now. This + * at this stage is not a problem as the mapping error is fatal + * enough that the mft record cannot be written out anyway and + * the caller is very likely to shutdown the whole inode + * immediately and mark the volume dirty for chkdsk to pick up + * the pieces anyway. + */ + if (put_this_folio) + folio_put(put_this_folio); + } + return err; +} + +/* + * ntfs_map_runlist - map (a part of) a runlist of an ntfs inode + * @ni: ntfs inode for which to map (part of) a runlist + * @vcn: map runlist part containing this vcn + * + * Map the part of a runlist containing the @vcn of the ntfs inode @ni. + * + * Return 0 on success and -errno on error. There is one special error code + * which is not an error as such. This is -ENOENT. It means that @vcn is out + * of bounds of the runlist. + * + * Locking: - The runlist must be unlocked on entry and is unlocked on return. + * - This function takes the runlist lock for writing and may modify + * the runlist. + */ +int ntfs_map_runlist(struct ntfs_inode *ni, s64 vcn) +{ + int err = 0; + + down_write(&ni->runlist.lock); + /* Make sure someone else didn't do the work while we were sleeping. */ + if (likely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) <= + LCN_RL_NOT_MAPPED)) + err = ntfs_map_runlist_nolock(ni, vcn, NULL); + up_write(&ni->runlist.lock); + return err; +} + +struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn) +{ + struct runlist_element *rl = ni->runlist.rl; + int err; + bool is_retry = false; + + if (!rl) { + err = ntfs_attr_map_whole_runlist(ni); + if (err) + return ERR_PTR(-ENOENT); + rl = ni->runlist.rl; + } + +remap_rl: + /* Seek to element containing target vcn. */ + while (rl->length && rl[1].vcn <= vcn) + rl++; + *lcn = ntfs_rl_vcn_to_lcn(rl, vcn); + + if (*lcn <= LCN_RL_NOT_MAPPED && is_retry == false) { + is_retry = true; + if (!ntfs_map_runlist_nolock(ni, vcn, NULL)) { + rl = ni->runlist.rl; + goto remap_rl; + } + } + + return rl; +} + +/* + * ntfs_attr_vcn_to_lcn_nolock - convert a vcn into a lcn given an ntfs inode + * @ni: ntfs inode of the attribute whose runlist to search + * @vcn: vcn to convert + * @write_locked: true if the runlist is locked for writing + * + * Find the virtual cluster number @vcn in the runlist of the ntfs attribute + * described by the ntfs inode @ni and return the corresponding logical cluster + * number (lcn). + * + * If the @vcn is not mapped yet, the attempt is made to map the attribute + * extent containing the @vcn and the vcn to lcn conversion is retried. + * + * If @write_locked is true the caller has locked the runlist for writing and + * if false for reading. + * + * Since lcns must be >= 0, we use negative return codes with special meaning: + * + * Return code Meaning / Description + * ========================================== + * LCN_HOLE Hole / not allocated on disk. + * LCN_ENOENT There is no such vcn in the runlist, i.e. @vcn is out of bounds. + * LCN_ENOMEM Not enough memory to map runlist. + * LCN_EIO Critical error (runlist/file is corrupt, i/o error, etc). + * + * Locking: - The runlist must be locked on entry and is left locked on return. + * - If @write_locked is 'false', i.e. the runlist is locked for reading, + * the lock may be dropped inside the function so you cannot rely on + * the runlist still being the same when this function returns. + */ +s64 ntfs_attr_vcn_to_lcn_nolock(struct ntfs_inode *ni, const s64 vcn, + const bool write_locked) +{ + s64 lcn; + unsigned long flags; + bool is_retry = false; + + ntfs_debug("Entering for i_ino 0x%llx, vcn 0x%llx, %s_locked.", + ni->mft_no, (unsigned long long)vcn, + write_locked ? "write" : "read"); + if (!ni->runlist.rl) { + read_lock_irqsave(&ni->size_lock, flags); + if (!ni->allocated_size) { + read_unlock_irqrestore(&ni->size_lock, flags); + return LCN_ENOENT; + } + read_unlock_irqrestore(&ni->size_lock, flags); + } +retry_remap: + /* Convert vcn to lcn. If that fails map the runlist and retry once. */ + lcn = ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn); + if (likely(lcn >= LCN_HOLE)) { + ntfs_debug("Done, lcn 0x%llx.", (long long)lcn); + return lcn; + } + if (lcn != LCN_RL_NOT_MAPPED) { + if (lcn != LCN_ENOENT) + lcn = LCN_EIO; + } else if (!is_retry) { + int err; + + if (!write_locked) { + up_read(&ni->runlist.lock); + down_write(&ni->runlist.lock); + if (unlikely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) != + LCN_RL_NOT_MAPPED)) { + up_write(&ni->runlist.lock); + down_read(&ni->runlist.lock); + goto retry_remap; + } + } + err = ntfs_map_runlist_nolock(ni, vcn, NULL); + if (!write_locked) { + up_write(&ni->runlist.lock); + down_read(&ni->runlist.lock); + } + if (likely(!err)) { + is_retry = true; + goto retry_remap; + } + if (err == -ENOENT) + lcn = LCN_ENOENT; + else if (err == -ENOMEM) + lcn = LCN_ENOMEM; + else + lcn = LCN_EIO; + } + if (lcn != LCN_ENOENT) + ntfs_error(ni->vol->sb, "Failed with error code %lli.", + (long long)lcn); + return lcn; +} + +struct runlist_element *__ntfs_attr_find_vcn_nolock(struct runlist *runlist, const s64 vcn) +{ + size_t lower_idx, upper_idx, idx; + struct runlist_element *run; + int rh = runlist->rl_hint; + + if (runlist->count <= 1) + return ERR_PTR(-ENOENT); + + if (runlist->count - 1 > rh && runlist->rl[rh].vcn <= vcn) { + if (vcn < runlist->rl[rh].vcn + runlist->rl[rh].length) + return &runlist->rl[rh]; + if (runlist->count - 2 == rh) + return ERR_PTR(-ENOENT); + + lower_idx = rh + 1; + } else { + run = &runlist->rl[0]; + if (vcn < run->vcn) + return ERR_PTR(-ENOENT); + else if (vcn < run->vcn + run->length) { + runlist->rl_hint = 0; + return run; + } + + lower_idx = 1; + } + + run = &runlist->rl[runlist->count - 2]; + if (vcn >= run->vcn && vcn < run->vcn + run->length) { + runlist->rl_hint = runlist->count - 2; + return run; + } + if (vcn >= run->vcn + run->length) + return ERR_PTR(-ENOENT); + + upper_idx = runlist->count - 2; + + while (lower_idx <= upper_idx) { + idx = (lower_idx + upper_idx) >> 1; + run = &runlist->rl[idx]; + + if (vcn < run->vcn) + upper_idx = idx - 1; + else if (vcn >= run->vcn + run->length) + lower_idx = idx + 1; + else { + runlist->rl_hint = idx; + return run; + } + } + + return ERR_PTR(-ENOENT); +} + +/* + * ntfs_attr_find_vcn_nolock - find a vcn in the runlist of an ntfs inode + * @ni: ntfs inode describing the runlist to search + * @vcn: vcn to find + * @ctx: active attribute search context if present or NULL if not + * + * Find the virtual cluster number @vcn in the runlist described by the ntfs + * inode @ni and return the address of the runlist element containing the @vcn. + * + * If the @vcn is not mapped yet, the attempt is made to map the attribute + * extent containing the @vcn and the vcn to lcn conversion is retried. + * + * If @ctx is specified, it is an active search context of @ni and its base mft + * record. This is needed when ntfs_attr_find_vcn_nolock() encounters unmapped + * runlist fragments and allows their mapping. If you do not have the mft + * record mapped, you can specify @ctx as NULL and ntfs_attr_find_vcn_nolock() + * will perform the necessary mapping and unmapping. + * + * Note, ntfs_attr_find_vcn_nolock() saves the state of @ctx on entry and + * restores it before returning. Thus, @ctx will be left pointing to the same + * attribute on return as on entry. However, the actual pointers in @ctx may + * point to different memory locations on return, so you must remember to reset + * any cached pointers from the @ctx, i.e. after the call to + * ntfs_attr_find_vcn_nolock(), you will probably want to do: + * m = ctx->mrec; + * a = ctx->attr; + * Assuming you cache ctx->attr in a variable @a of type attr_record * and that + * you cache ctx->mrec in a variable @m of type struct mft_record *. + * Note you need to distinguish between the lcn of the returned runlist element + * being >= 0 and LCN_HOLE. In the later case you have to return zeroes on + * read and allocate clusters on write. + */ +struct runlist_element *ntfs_attr_find_vcn_nolock(struct ntfs_inode *ni, const s64 vcn, + struct ntfs_attr_search_ctx *ctx) +{ + unsigned long flags; + struct runlist_element *rl; + int err = 0; + bool is_retry = false; + + ntfs_debug("Entering for i_ino 0x%llx, vcn 0x%llx, with%s ctx.", + ni->mft_no, (unsigned long long)vcn, ctx ? "" : "out"); + if (!ni->runlist.rl) { + read_lock_irqsave(&ni->size_lock, flags); + if (!ni->allocated_size) { + read_unlock_irqrestore(&ni->size_lock, flags); + return ERR_PTR(-ENOENT); + } + read_unlock_irqrestore(&ni->size_lock, flags); + } + +retry_remap: + rl = ni->runlist.rl; + if (likely(rl && vcn >= rl[0].vcn)) { + rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn); + if (IS_ERR(rl)) + err = PTR_ERR(rl); + else if (rl->lcn >= LCN_HOLE) + return rl; + else if (rl->lcn <= LCN_ENOENT) + err = -EIO; + } + if (!err && !is_retry) { + /* + * If the search context is invalid we cannot map the unmapped + * region. + */ + if (ctx && IS_ERR(ctx->mrec)) + err = PTR_ERR(ctx->mrec); + else { + /* + * The @vcn is in an unmapped region, map the runlist + * and retry. + */ + err = ntfs_map_runlist_nolock(ni, vcn, ctx); + if (likely(!err)) { + is_retry = true; + goto retry_remap; + } + } + if (err == -EINVAL) + err = -EIO; + } else if (!err) + err = -EIO; + if (err != -ENOENT) + ntfs_error(ni->vol->sb, "Failed with error code %i.", err); + return ERR_PTR(err); +} + +static u32 ntfs_resident_attr_min_value_length(const __le32 type) +{ + switch (type) { + case AT_STANDARD_INFORMATION: + return offsetof(struct standard_information, ver) + + sizeof(((struct standard_information *)0)->ver.v1.reserved12); + case AT_ATTRIBUTE_LIST: + return offsetof(struct attr_list_entry, name); + case AT_FILE_NAME: + return offsetof(struct file_name_attr, file_name); + case AT_OBJECT_ID: + return sizeof(struct guid); + case AT_SECURITY_DESCRIPTOR: + return sizeof(struct security_descriptor_relative); + case AT_VOLUME_INFORMATION: + return sizeof(struct volume_information); + case AT_INDEX_ROOT: + return sizeof(struct index_root); + case AT_REPARSE_POINT: + return offsetof(struct reparse_point, reparse_data); + case AT_EA_INFORMATION: + return sizeof(struct ea_information); + case AT_EA: + return offsetof(struct ea_attr, ea_name) + 1; + default: + return 0; + } +} + +/* + * ntfs_attr_find - find (next) attribute in mft record + * @type: attribute type to find + * @name: attribute name to find (optional, i.e. NULL means don't care) + * @name_len: attribute name length (only needed if @name present) + * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) + * @val: attribute value to find (optional, resident attributes only) + * @val_len: attribute value length + * @ctx: search context with mft record and attribute to search from + * + * You should not need to call this function directly. Use ntfs_attr_lookup() + * instead. + * + * ntfs_attr_find() takes a search context @ctx as parameter and searches the + * mft record specified by @ctx->mrec, beginning at @ctx->attr, for an + * attribute of @type, optionally @name and @val. + * + * If the attribute is found, ntfs_attr_find() returns 0 and @ctx->attr will + * point to the found attribute. + * + * If the attribute is not found, ntfs_attr_find() returns -ENOENT and + * @ctx->attr will point to the attribute before which the attribute being + * searched for would need to be inserted if such an action were to be desired. + * + * On actual error, ntfs_attr_find() returns -EIO. In this case @ctx->attr is + * undefined and in particular do not rely on it not changing. + * + * If @ctx->is_first is 'true', the search begins with @ctx->attr itself. If it + * is 'false', the search begins after @ctx->attr. + * + * If @ic is IGNORE_CASE, the @name comparisson is not case sensitive and + * @ctx->ntfs_ino must be set to the ntfs inode to which the mft record + * @ctx->mrec belongs. This is so we can get at the ntfs volume and hence at + * the upcase table. If @ic is CASE_SENSITIVE, the comparison is case + * sensitive. When @name is present, @name_len is the @name length in Unicode + * characters. + * + * If @name is not present (NULL), we assume that the unnamed attribute is + * being searched for. + * + * Finally, the resident attribute value @val is looked for, if present. If + * @val is not present (NULL), @val_len is ignored. + * + * ntfs_attr_find() only searches the specified mft record and it ignores the + * presence of an attribute list attribute (unless it is the one being searched + * for, obviously). If you need to take attribute lists into consideration, + * use ntfs_attr_lookup() instead (see below). This also means that you cannot + * use ntfs_attr_find() to search for extent records of non-resident + * attributes, as extents with lowest_vcn != 0 are usually described by the + * attribute list attribute only. - Note that it is possible that the first + * extent is only in the attribute list while the last extent is in the base + * mft record, so do not rely on being able to find the first extent in the + * base mft record. + * + * Warning: Never use @val when looking for attribute types which can be + * non-resident as this most likely will result in a crash! + */ +static int ntfs_attr_find(const __le32 type, const __le16 *name, + const u32 name_len, const u32 ic, + const u8 *val, const u32 val_len, struct ntfs_attr_search_ctx *ctx) +{ + struct attr_record *a; + struct ntfs_volume *vol = ctx->ntfs_ino->vol; + __le16 *upcase = vol->upcase; + u32 upcase_len = vol->upcase_len; + unsigned int space; + + /* + * Iterate over attributes in mft record starting at @ctx->attr, or the + * attribute following that, if @ctx->is_first is 'true'. + */ + if (ctx->is_first) { + a = ctx->attr; + ctx->is_first = false; + } else + a = (struct attr_record *)((u8 *)ctx->attr + + le32_to_cpu(ctx->attr->length)); + for (;; a = (struct attr_record *)((u8 *)a + le32_to_cpu(a->length))) { + if ((u8 *)a < (u8 *)ctx->mrec || (u8 *)a > (u8 *)ctx->mrec + + le32_to_cpu(ctx->mrec->bytes_allocated)) + break; + + space = le32_to_cpu(ctx->mrec->bytes_in_use) - ((u8 *)a - (u8 *)ctx->mrec); + if ((space < offsetof(struct attr_record, data.resident.reserved) + 1 || + space < le32_to_cpu(a->length)) && (space < 4 || a->type != AT_END)) + break; + + ctx->attr = a; + if (((type != AT_UNUSED) && (le32_to_cpu(a->type) > le32_to_cpu(type))) || + a->type == AT_END) + return -ENOENT; + if (unlikely(!a->length)) + break; + if (type == AT_UNUSED) + return 0; + if (a->type != type) + continue; + /* + * If @name is present, compare the two names. If @name is + * missing, assume we want an unnamed attribute. + */ + if (!name || name == AT_UNNAMED) { + /* The search failed if the found attribute is named. */ + if (a->name_length) + return -ENOENT; + } else { + if (a->name_length && ((le16_to_cpu(a->name_offset) + + a->name_length * sizeof(__le16)) > + le32_to_cpu(a->length))) { + ntfs_error(vol->sb, "Corrupt attribute name in MFT record %llu\n", + ctx->ntfs_ino->mft_no); + break; + } + + if (!ntfs_are_names_equal(name, name_len, + (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length, ic, upcase, upcase_len)) { + register int rc; + + rc = ntfs_collate_names(name, name_len, + (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length, 1, IGNORE_CASE, + upcase, upcase_len); + /* + * If @name collates before a->name, there is no + * matching attribute. + */ + if (rc == -1) + return -ENOENT; + /* If the strings are not equal, continue search. */ + if (rc) + continue; + rc = ntfs_collate_names(name, name_len, + (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length, 1, CASE_SENSITIVE, + upcase, upcase_len); + if (rc == -1) + return -ENOENT; + if (rc) + continue; + } + } + + /* Validate attribute's value offset/length */ + if (!a->non_resident) { + u32 min_len; + u32 value_length = le32_to_cpu(a->data.resident.value_length); + u16 value_offset = le16_to_cpu(a->data.resident.value_offset); + + if (value_length > le32_to_cpu(a->length) || + value_offset > le32_to_cpu(a->length) - value_length) + break; + + min_len = ntfs_resident_attr_min_value_length(a->type); + if (min_len && value_length < min_len) { + ntfs_error(vol->sb, + "Too small %#x resident attribute value in MFT record %lld\n", + le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no); + break; + } + } else { + u32 min_len; + u16 mp_offset; + + min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + + sizeof(a->data.non_resident.initialized_size); + if (le32_to_cpu(a->length) < min_len) + break; + + mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + if (mp_offset < min_len || + mp_offset > le32_to_cpu(a->length)) + break; + } + + /* + * The names match or @name not present and attribute is + * unnamed. If no @val specified, we have found the attribute + * and are done. + */ + if (!val || a->non_resident) + return 0; + /* @val is present; compare values. */ + else { + u32 value_length = le32_to_cpu(a->data.resident.value_length); + int rc; + + rc = memcmp(val, (u8 *)a + le16_to_cpu( + a->data.resident.value_offset), + min_t(u32, val_len, value_length)); + /* + * If @val collates before the current attribute's + * value, there is no matching attribute. + */ + if (!rc) { + if (val_len == value_length) + return 0; + if (val_len < value_length) + return -ENOENT; + } else if (rc < 0) + return -ENOENT; + } + } + ntfs_error(vol->sb, "mft %#llx, type %#x is corrupt. Run chkdsk.", + (long long)ctx->ntfs_ino->mft_no, le32_to_cpu(type)); + NVolSetErrors(vol); + return -EIO; +} + +void ntfs_attr_name_free(unsigned char **name) +{ + if (*name) { + kfree(*name); + *name = NULL; + } +} + +char *ntfs_attr_name_get(const struct ntfs_volume *vol, const __le16 *uname, + const int uname_len) +{ + unsigned char *name = NULL; + int name_len; + + name_len = ntfs_ucstonls(vol, uname, uname_len, &name, 0); + if (name_len < 0) { + ntfs_error(vol->sb, "ntfs_ucstonls error"); + /* This function when returns -1, memory for name might + * be allocated. So lets free this memory. + */ + ntfs_attr_name_free(&name); + return NULL; + + } else if (name_len > 0) + return name; + + ntfs_attr_name_free(&name); + return NULL; +} + +int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size) +{ + struct inode *attr_vi = NULL; + u8 *al; + struct attr_list_entry *ale; + + if (!al_start || size <= 0) + return -EINVAL; + + attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (IS_ERR(attr_vi)) { + ntfs_error(base_ni->vol->sb, + "Failed to open an inode for Attribute list, mft = %llu", + base_ni->mft_no); + return PTR_ERR(attr_vi); + } + + if (ntfs_inode_attr_pread(attr_vi, 0, size, al_start) != size) { + iput(attr_vi); + ntfs_error(base_ni->vol->sb, + "Failed to read attribute list, mft = %llu", + base_ni->mft_no); + return -EIO; + } + iput(attr_vi); + + for (al = al_start; al < al_start + size; al += le16_to_cpu(ale->length)) { + ale = (struct attr_list_entry *)al; + if (ale->name_offset != sizeof(struct attr_list_entry)) + break; + if (le16_to_cpu(ale->length) <= ale->name_offset + ale->name_length || + al + le16_to_cpu(ale->length) > al_start + size) + break; + if (ale->type == AT_UNUSED) + break; + if (MSEQNO_LE(ale->mft_reference) == 0) + break; + } + if (al != al_start + size) { + ntfs_error(base_ni->vol->sb, "Corrupt attribute list, mft = %llu", + base_ni->mft_no); + return -EIO; + } + return 0; +} + +/* + * ntfs_external_attr_find - find an attribute in the attribute list of an inode + * @type: attribute type to find + * @name: attribute name to find (optional, i.e. NULL means don't care) + * @name_len: attribute name length (only needed if @name present) + * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) + * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only) + * @val: attribute value to find (optional, resident attributes only) + * @val_len: attribute value length + * @ctx: search context with mft record and attribute to search from + * + * You should not need to call this function directly. Use ntfs_attr_lookup() + * instead. + * + * Find an attribute by searching the attribute list for the corresponding + * attribute list entry. Having found the entry, map the mft record if the + * attribute is in a different mft record/inode, ntfs_attr_find() the attribute + * in there and return it. + * + * On first search @ctx->ntfs_ino must be the base mft record and @ctx must + * have been obtained from a call to ntfs_attr_get_search_ctx(). On subsequent + * calls @ctx->ntfs_ino can be any extent inode, too (@ctx->base_ntfs_ino is + * then the base inode). + * + * After finishing with the attribute/mft record you need to call + * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any + * mapped inodes, etc). + * + * If the attribute is found, ntfs_external_attr_find() returns 0 and + * @ctx->attr will point to the found attribute. @ctx->mrec will point to the + * mft record in which @ctx->attr is located and @ctx->al_entry will point to + * the attribute list entry for the attribute. + * + * If the attribute is not found, ntfs_external_attr_find() returns -ENOENT and + * @ctx->attr will point to the attribute in the base mft record before which + * the attribute being searched for would need to be inserted if such an action + * were to be desired. @ctx->mrec will point to the mft record in which + * @ctx->attr is located and @ctx->al_entry will point to the attribute list + * entry of the attribute before which the attribute being searched for would + * need to be inserted if such an action were to be desired. + * + * Thus to insert the not found attribute, one wants to add the attribute to + * @ctx->mrec (the base mft record) and if there is not enough space, the + * attribute should be placed in a newly allocated extent mft record. The + * attribute list entry for the inserted attribute should be inserted in the + * attribute list attribute at @ctx->al_entry. + * + * On actual error, ntfs_external_attr_find() returns -EIO. In this case + * @ctx->attr is undefined and in particular do not rely on it not changing. + */ +static int ntfs_external_attr_find(const __le32 type, + const __le16 *name, const u32 name_len, + const u32 ic, const s64 lowest_vcn, + const u8 *val, const u32 val_len, struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *base_ni = ctx->base_ntfs_ino, *ni = ctx->ntfs_ino; + struct ntfs_volume *vol; + struct attr_list_entry *al_entry, *next_al_entry; + u8 *al_start, *al_end; + struct attr_record *a; + __le16 *al_name; + u32 al_name_len; + u32 attr_len, mft_free_len; + bool is_first_search = false; + int err = 0; + static const char *es = " Unmount and run chkdsk."; + + ntfs_debug("Entering for inode 0x%llx, type 0x%x.", ni->mft_no, type); + if (!base_ni) { + /* First call happens with the base mft record. */ + base_ni = ctx->base_ntfs_ino = ctx->ntfs_ino; + ctx->base_mrec = ctx->mrec; + ctx->mapped_base_mrec = ctx->mapped_mrec; + } + if (ni == base_ni) + ctx->base_attr = ctx->attr; + if (type == AT_END) + goto not_found; + vol = base_ni->vol; + al_start = base_ni->attr_list; + al_end = al_start + base_ni->attr_list_size; + if (!ctx->al_entry) { + ctx->al_entry = (struct attr_list_entry *)al_start; + is_first_search = true; + } + /* + * Iterate over entries in attribute list starting at @ctx->al_entry, + * or the entry following that, if @ctx->is_first is 'true'. + */ + if (ctx->is_first) { + al_entry = ctx->al_entry; + ctx->is_first = false; + /* + * If an enumeration and the first attribute is higher than + * the attribute list itself, need to return the attribute list + * attribute. + */ + if ((type == AT_UNUSED) && is_first_search && + le32_to_cpu(al_entry->type) > + le32_to_cpu(AT_ATTRIBUTE_LIST)) + goto find_attr_list_attr; + } else { + /* Check for small entry */ + if (((al_end - (u8 *)ctx->al_entry) < + (long)offsetof(struct attr_list_entry, name)) || + (le16_to_cpu(ctx->al_entry->length) & 7) || + (le16_to_cpu(ctx->al_entry->length) < offsetof(struct attr_list_entry, name))) + goto corrupt; + + al_entry = (struct attr_list_entry *)((u8 *)ctx->al_entry + + le16_to_cpu(ctx->al_entry->length)); + + if ((u8 *)al_entry == al_end) + goto not_found; + + /* Preliminary check for small entry */ + if ((al_end - (u8 *)al_entry) < + (long)offsetof(struct attr_list_entry, name)) + goto corrupt; + + /* + * If this is an enumeration and the attribute list attribute + * is the next one in the enumeration sequence, just return the + * attribute list attribute from the base mft record as it is + * not listed in the attribute list itself. + */ + if ((type == AT_UNUSED) && le32_to_cpu(ctx->al_entry->type) < + le32_to_cpu(AT_ATTRIBUTE_LIST) && + le32_to_cpu(al_entry->type) > + le32_to_cpu(AT_ATTRIBUTE_LIST)) { +find_attr_list_attr: + + /* Check for bogus calls. */ + if (name || name_len || val || val_len || lowest_vcn) + return -EINVAL; + + /* We want the base record. */ + if (ctx->ntfs_ino != base_ni) + unmap_mft_record(ctx->ntfs_ino); + ctx->ntfs_ino = base_ni; + ctx->mapped_mrec = ctx->mapped_base_mrec; + ctx->mrec = ctx->base_mrec; + ctx->is_first = true; + + /* Sanity checks are performed elsewhere. */ + ctx->attr = (struct attr_record *)((u8 *)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + + /* Find the attribute list attribute. */ + err = ntfs_attr_find(AT_ATTRIBUTE_LIST, NULL, 0, + IGNORE_CASE, NULL, 0, ctx); + + /* + * Setup the search context so the correct + * attribute is returned next time round. + */ + ctx->al_entry = al_entry; + ctx->is_first = true; + + /* Got it. Done. */ + if (!err) + return 0; + + /* Error! If other than not found return it. */ + if (err != -ENOENT) + return err; + + /* Not found?!? Absurd! */ + ntfs_error(ctx->ntfs_ino->vol->sb, "Attribute list wasn't found"); + return -EIO; + } + } + for (;; al_entry = next_al_entry) { + /* Out of bounds check. */ + if ((u8 *)al_entry < base_ni->attr_list || + (u8 *)al_entry > al_end) + break; /* Inode is corrupt. */ + ctx->al_entry = al_entry; + /* Catch the end of the attribute list. */ + if ((u8 *)al_entry == al_end) + goto not_found; + + if ((((u8 *)al_entry + offsetof(struct attr_list_entry, name)) > al_end) || + ((u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end) || + (le16_to_cpu(al_entry->length) & 7) || + (le16_to_cpu(al_entry->length) < + offsetof(struct attr_list_entry, name_length)) || + (al_entry->name_length && ((u8 *)al_entry + al_entry->name_offset + + al_entry->name_length * sizeof(__le16)) > al_end)) + break; /* corrupt */ + + next_al_entry = (struct attr_list_entry *)((u8 *)al_entry + + le16_to_cpu(al_entry->length)); + if (type != AT_UNUSED) { + if (le32_to_cpu(al_entry->type) > le32_to_cpu(type)) + goto not_found; + if (type != al_entry->type) + continue; + } + /* + * If @name is present, compare the two names. If @name is + * missing, assume we want an unnamed attribute. + */ + al_name_len = al_entry->name_length; + al_name = (__le16 *)((u8 *)al_entry + al_entry->name_offset); + + /* + * If !@type we want the attribute represented by this + * attribute list entry. + */ + if (type == AT_UNUSED) + goto is_enumeration; + + if (!name || name == AT_UNNAMED) { + if (al_name_len) + goto not_found; + } else if (!ntfs_are_names_equal(al_name, al_name_len, name, + name_len, ic, vol->upcase, vol->upcase_len)) { + register int rc; + + rc = ntfs_collate_names(name, name_len, al_name, + al_name_len, 1, IGNORE_CASE, + vol->upcase, vol->upcase_len); + /* + * If @name collates before al_name, there is no + * matching attribute. + */ + if (rc == -1) + goto not_found; + /* If the strings are not equal, continue search. */ + if (rc) + continue; + + rc = ntfs_collate_names(name, name_len, al_name, + al_name_len, 1, CASE_SENSITIVE, + vol->upcase, vol->upcase_len); + if (rc == -1) + goto not_found; + if (rc) + continue; + } + /* + * The names match or @name not present and attribute is + * unnamed. Now check @lowest_vcn. Continue search if the + * next attribute list entry still fits @lowest_vcn. Otherwise + * we have reached the right one or the search has failed. + */ + if (lowest_vcn && (u8 *)next_al_entry >= al_start && + (u8 *)next_al_entry + 6 < al_end && + (u8 *)next_al_entry + le16_to_cpu( + next_al_entry->length) <= al_end && + le64_to_cpu(next_al_entry->lowest_vcn) <= + lowest_vcn && + next_al_entry->type == al_entry->type && + next_al_entry->name_length == al_name_len && + ntfs_are_names_equal((__le16 *)((u8 *) + next_al_entry + + next_al_entry->name_offset), + next_al_entry->name_length, + al_name, al_name_len, CASE_SENSITIVE, + vol->upcase, vol->upcase_len)) + continue; + +is_enumeration: + if (MREF_LE(al_entry->mft_reference) == ni->mft_no) { + if (MSEQNO_LE(al_entry->mft_reference) != ni->seq_no) { + ntfs_error(vol->sb, + "Found stale mft reference in attribute list of base inode 0x%llx.%s", + base_ni->mft_no, es); + err = -EIO; + break; + } + } else { /* Mft references do not match. */ + /* If there is a mapped record unmap it first. */ + if (ni != base_ni) + unmap_extent_mft_record(ni); + /* Do we want the base record back? */ + if (MREF_LE(al_entry->mft_reference) == + base_ni->mft_no) { + ni = ctx->ntfs_ino = base_ni; + ctx->mrec = ctx->base_mrec; + ctx->mapped_mrec = ctx->mapped_base_mrec; + } else { + /* We want an extent record. */ + ctx->mrec = map_extent_mft_record(base_ni, + le64_to_cpu( + al_entry->mft_reference), &ni); + if (IS_ERR(ctx->mrec)) { + ntfs_error(vol->sb, + "Failed to map extent mft record 0x%lx of base inode 0x%llx.%s", + MREF_LE(al_entry->mft_reference), + base_ni->mft_no, es); + err = PTR_ERR(ctx->mrec); + if (err == -ENOENT) + err = -EIO; + /* Cause @ctx to be sanitized below. */ + ni = NULL; + break; + } + ctx->ntfs_ino = ni; + ctx->mapped_mrec = true; + + } + } + a = ctx->attr = (struct attr_record *)((u8 *)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + /* + * ctx->vfs_ino, ctx->mrec, and ctx->attr now point to the + * mft record containing the attribute represented by the + * current al_entry. + */ + /* + * We could call into ntfs_attr_find() to find the right + * attribute in this mft record but this would be less + * efficient and not quite accurate as ntfs_attr_find() ignores + * the attribute instance numbers for example which become + * important when one plays with attribute lists. Also, + * because a proper match has been found in the attribute list + * entry above, the comparison can now be optimized. So it is + * worth re-implementing a simplified ntfs_attr_find() here. + */ + /* + * Use a manual loop so we can still use break and continue + * with the same meanings as above. + */ +do_next_attr_loop: + if ((u8 *)a < (u8 *)ctx->mrec || + (u8 *)a >= (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_allocated) || + (u8 *)a >= (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_in_use)) + break; + + mft_free_len = le32_to_cpu(ctx->mrec->bytes_in_use) - + ((u8 *)a - (u8 *)ctx->mrec); + if (mft_free_len >= sizeof(a->type) && a->type == AT_END) + continue; + + attr_len = le32_to_cpu(a->length); + if (!attr_len || + attr_len < offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved) || + attr_len > mft_free_len) + break; + + if (al_entry->instance != a->instance) + goto do_next_attr; + /* + * If the type and/or the name are mismatched between the + * attribute list entry and the attribute record, there is + * corruption so we break and return error EIO. + */ + if (al_entry->type != a->type) + break; + if (a->name_length && ((le16_to_cpu(a->name_offset) + + a->name_length * sizeof(__le16)) > attr_len)) + break; + if (!ntfs_are_names_equal((__le16 *)((u8 *)a + + le16_to_cpu(a->name_offset)), a->name_length, + al_name, al_name_len, CASE_SENSITIVE, + vol->upcase, vol->upcase_len)) + break; + + ctx->attr = a; + + if (a->non_resident) { + u32 min_len; + u16 mp_offset; + + min_len = offsetof(struct attr_record, + data.non_resident.initialized_size) + + sizeof(a->data.non_resident.initialized_size); + + if (le32_to_cpu(a->length) < min_len) + break; + + mp_offset = + le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + if (mp_offset < min_len || mp_offset > attr_len) + break; + } + + /* + * If no @val specified or @val specified and it matches, we + * have found it! + */ + if ((type == AT_UNUSED) || !val) + goto attr_found; + if (!a->non_resident) { + u32 value_length = le32_to_cpu(a->data.resident.value_length); + u16 value_offset = le16_to_cpu(a->data.resident.value_offset); + + if (attr_len < offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved)) + break; + if (value_length > attr_len || value_offset > attr_len - value_length) + break; + + value_length = ntfs_resident_attr_min_value_length(a->type); + if (value_length && le32_to_cpu(a->data.resident.value_length) < + value_length) { + pr_err("Too small resident attribute value in MFT record %lld, type %#x\n", + (long long)ctx->ntfs_ino->mft_no, a->type); + break; + } + if (value_length == val_len && + !memcmp((u8 *)a + value_offset, val, val_len)) { +attr_found: + ntfs_debug("Done, found."); + return 0; + } + } +do_next_attr: + /* Proceed to the next attribute in the current mft record. */ + a = (struct attr_record *)((u8 *)a + attr_len); + goto do_next_attr_loop; + } + +corrupt: + if (ni != base_ni) { + if (ni) + unmap_extent_mft_record(ni); + ctx->ntfs_ino = base_ni; + ctx->mrec = ctx->base_mrec; + ctx->attr = ctx->base_attr; + ctx->mapped_mrec = ctx->mapped_base_mrec; + } + + if (!err) { + u64 mft_no = ctx->al_entry ? MREF_LE(ctx->al_entry->mft_reference) : 0; + u32 type = ctx->al_entry ? le32_to_cpu(ctx->al_entry->type) : 0; + + ntfs_error(vol->sb, + "Base inode 0x%llx contains corrupt attribute, mft %#llx, type %#x. %s", + (long long)base_ni->mft_no, (long long)mft_no, type, + "Unmount and run chkdsk."); + err = -EIO; + } + + if (err != -ENOMEM) + NVolSetErrors(vol); + return err; +not_found: + /* + * If we were looking for AT_END, we reset the search context @ctx and + * use ntfs_attr_find() to seek to the end of the base mft record. + */ + if (type == AT_UNUSED || type == AT_END) { + ntfs_attr_reinit_search_ctx(ctx); + return ntfs_attr_find(AT_END, name, name_len, ic, val, val_len, + ctx); + } + /* + * The attribute was not found. Before we return, we want to ensure + * @ctx->mrec and @ctx->attr indicate the position at which the + * attribute should be inserted in the base mft record. Since we also + * want to preserve @ctx->al_entry we cannot reinitialize the search + * context using ntfs_attr_reinit_search_ctx() as this would set + * @ctx->al_entry to NULL. Thus we do the necessary bits manually (see + * ntfs_attr_init_search_ctx() below). Note, we _only_ preserve + * @ctx->al_entry as the remaining fields (base_*) are identical to + * their non base_ counterparts and we cannot set @ctx->base_attr + * correctly yet as we do not know what @ctx->attr will be set to by + * the call to ntfs_attr_find() below. + */ + if (ni != base_ni) + unmap_extent_mft_record(ni); + ctx->mrec = ctx->base_mrec; + ctx->attr = (struct attr_record *)((u8 *)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + ctx->is_first = true; + ctx->ntfs_ino = base_ni; + ctx->base_ntfs_ino = NULL; + ctx->base_mrec = NULL; + ctx->base_attr = NULL; + ctx->mapped_mrec = ctx->mapped_base_mrec; + /* + * In case there are multiple matches in the base mft record, need to + * keep enumerating until we get an attribute not found response (or + * another error), otherwise we would keep returning the same attribute + * over and over again and all programs using us for enumeration would + * lock up in a tight loop. + */ + do { + err = ntfs_attr_find(type, name, name_len, ic, val, val_len, + ctx); + } while (!err); + ntfs_debug("Done, not found."); + return err; +} + +/* + * ntfs_attr_lookup - find an attribute in an ntfs inode + * @type: attribute type to find + * @name: attribute name to find (optional, i.e. NULL means don't care) + * @name_len: attribute name length (only needed if @name present) + * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) + * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only) + * @val: attribute value to find (optional, resident attributes only) + * @val_len: attribute value length + * @ctx: search context with mft record and attribute to search from + * + * Find an attribute in an ntfs inode. On first search @ctx->ntfs_ino must + * be the base mft record and @ctx must have been obtained from a call to + * ntfs_attr_get_search_ctx(). + * + * This function transparently handles attribute lists and @ctx is used to + * continue searches where they were left off at. + * + * After finishing with the attribute/mft record you need to call + * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any + * mapped inodes, etc). + * + * Return 0 if the search was successful and -errno if not. + * + * When 0, @ctx->attr is the found attribute and it is in mft record + * @ctx->mrec. If an attribute list attribute is present, @ctx->al_entry is + * the attribute list entry of the found attribute. + * + * When -ENOENT, @ctx->attr is the attribute which collates just after the + * attribute being searched for, i.e. if one wants to add the attribute to the + * mft record this is the correct place to insert it into. If an attribute + * list attribute is present, @ctx->al_entry is the attribute list entry which + * collates just after the attribute list entry of the attribute being searched + * for, i.e. if one wants to add the attribute to the mft record this is the + * correct place to insert its attribute list entry into. + */ +int ntfs_attr_lookup(const __le32 type, const __le16 *name, + const u32 name_len, const u32 ic, + const s64 lowest_vcn, const u8 *val, const u32 val_len, + struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *base_ni; + + ntfs_debug("Entering."); + if (ctx->base_ntfs_ino) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + /* Sanity check, just for debugging really. */ + if (!base_ni || !NInoAttrList(base_ni) || type == AT_ATTRIBUTE_LIST) + return ntfs_attr_find(type, name, name_len, ic, val, val_len, + ctx); + return ntfs_external_attr_find(type, name, name_len, ic, lowest_vcn, + val, val_len, ctx); +} + +/** + * ntfs_attr_init_search_ctx - initialize an attribute search context + * @ctx: attribute search context to initialize + * @ni: ntfs inode with which to initialize the search context + * @mrec: mft record with which to initialize the search context + * + * Initialize the attribute search context @ctx with @ni and @mrec. + */ +static bool ntfs_attr_init_search_ctx(struct ntfs_attr_search_ctx *ctx, + struct ntfs_inode *ni, struct mft_record *mrec) +{ + if (!mrec) { + mrec = map_mft_record(ni); + if (IS_ERR(mrec)) + return false; + ctx->mapped_mrec = true; + } else { + ctx->mapped_mrec = false; + } + + ctx->mrec = mrec; + /* Sanity checks are performed elsewhere. */ + ctx->attr = (struct attr_record *)((u8 *)mrec + le16_to_cpu(mrec->attrs_offset)); + ctx->is_first = true; + ctx->ntfs_ino = ni; + ctx->al_entry = NULL; + ctx->base_ntfs_ino = NULL; + ctx->base_mrec = NULL; + ctx->base_attr = NULL; + ctx->mapped_base_mrec = false; + return true; +} + +/* + * ntfs_attr_reinit_search_ctx - reinitialize an attribute search context + * @ctx: attribute search context to reinitialize + * + * Reinitialize the attribute search context @ctx, unmapping an associated + * extent mft record if present, and initialize the search context again. + * + * This is used when a search for a new attribute is being started to reset + * the search context to the beginning. + */ +void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx) +{ + bool mapped_mrec; + + if (likely(!ctx->base_ntfs_ino)) { + /* No attribute list. */ + ctx->is_first = true; + /* Sanity checks are performed elsewhere. */ + ctx->attr = (struct attr_record *)((u8 *)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + /* + * This needs resetting due to ntfs_external_attr_find() which + * can leave it set despite having zeroed ctx->base_ntfs_ino. + */ + ctx->al_entry = NULL; + return; + } /* Attribute list. */ + if (ctx->ntfs_ino != ctx->base_ntfs_ino && ctx->ntfs_ino) + unmap_extent_mft_record(ctx->ntfs_ino); + + mapped_mrec = ctx->mapped_base_mrec; + ntfs_attr_init_search_ctx(ctx, ctx->base_ntfs_ino, ctx->base_mrec); + ctx->mapped_mrec = mapped_mrec; +} + +/* + * ntfs_attr_get_search_ctx - allocate/initialize a new attribute search context + * @ni: ntfs inode with which to initialize the search context + * @mrec: mft record with which to initialize the search context + * + * Allocate a new attribute search context, initialize it with @ni and @mrec, + * and return it. Return NULL if allocation failed. + */ +struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni, + struct mft_record *mrec) +{ + struct ntfs_attr_search_ctx *ctx; + bool init; + + ctx = kmem_cache_alloc(ntfs_attr_ctx_cache, GFP_NOFS); + if (ctx) { + init = ntfs_attr_init_search_ctx(ctx, ni, mrec); + if (init == false) { + kmem_cache_free(ntfs_attr_ctx_cache, ctx); + ctx = NULL; + } + } + + return ctx; +} + +/* + * ntfs_attr_put_search_ctx - release an attribute search context + * @ctx: attribute search context to free + * + * Release the attribute search context @ctx, unmapping an associated extent + * mft record if present. + */ +void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx) +{ + if (ctx->mapped_mrec) + unmap_mft_record(ctx->ntfs_ino); + + if (ctx->mapped_base_mrec && ctx->base_ntfs_ino && + ctx->ntfs_ino != ctx->base_ntfs_ino) + unmap_extent_mft_record(ctx->base_ntfs_ino); + kmem_cache_free(ntfs_attr_ctx_cache, ctx); +} + +/* + * ntfs_attr_find_in_attrdef - find an attribute in the $AttrDef system file + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to find + * + * Search for the attribute definition record corresponding to the attribute + * @type in the $AttrDef system file. + * + * Return the attribute type definition record if found and NULL if not found. + */ +static struct attr_def *ntfs_attr_find_in_attrdef(const struct ntfs_volume *vol, + const __le32 type) +{ + struct attr_def *ad; + + WARN_ON(!type); + for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef < + vol->attrdef_size && ad->type; ++ad) { + /* We have not found it yet, carry on searching. */ + if (likely(le32_to_cpu(ad->type) < le32_to_cpu(type))) + continue; + /* We found the attribute; return it. */ + if (likely(ad->type == type)) + return ad; + /* We have gone too far already. No point in continuing. */ + break; + } + /* Attribute not found. */ + ntfs_debug("Attribute type 0x%x not found in $AttrDef.", + le32_to_cpu(type)); + return NULL; +} + +/* + * ntfs_attr_size_bounds_check - check a size of an attribute type for validity + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to check + * @size: size which to check + * + * Check whether the @size in bytes is valid for an attribute of @type on the + * ntfs volume @vol. This information is obtained from $AttrDef system file. + */ +int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol, const __le32 type, + const s64 size) +{ + struct attr_def *ad; + + if (size < 0) + return -EINVAL; + + /* + * $ATTRIBUTE_LIST has a maximum size of 256kiB, but this is not + * listed in $AttrDef. + */ + if (unlikely(type == AT_ATTRIBUTE_LIST && size > 256 * 1024)) + return -ERANGE; + /* Get the $AttrDef entry for the attribute @type. */ + ad = ntfs_attr_find_in_attrdef(vol, type); + if (unlikely(!ad)) + return -ENOENT; + /* Do the bounds check. */ + if (((le64_to_cpu(ad->min_size) > 0) && + size < le64_to_cpu(ad->min_size)) || + ((le64_to_cpu(ad->max_size) > 0) && size > + le64_to_cpu(ad->max_size))) + return -ERANGE; + return 0; +} + +/* + * ntfs_attr_can_be_non_resident - check if an attribute can be non-resident + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to check + * + * Check whether the attribute of @type on the ntfs volume @vol is allowed to + * be non-resident. This information is obtained from $AttrDef system file. + */ +static int ntfs_attr_can_be_non_resident(const struct ntfs_volume *vol, + const __le32 type) +{ + struct attr_def *ad; + + /* Find the attribute definition record in $AttrDef. */ + ad = ntfs_attr_find_in_attrdef(vol, type); + if (unlikely(!ad)) + return -ENOENT; + /* Check the flags and return the result. */ + if (ad->flags & ATTR_DEF_RESIDENT) + return -EPERM; + return 0; +} + +/* + * ntfs_attr_can_be_resident - check if an attribute can be resident + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to check + * + * Check whether the attribute of @type on the ntfs volume @vol is allowed to + * be resident. This information is derived from our ntfs knowledge and may + * not be completely accurate, especially when user defined attributes are + * present. Basically we allow everything to be resident except for index + * allocation and $EA attributes. + * + * Return 0 if the attribute is allowed to be non-resident and -EPERM if not. + * + * Warning: In the system file $MFT the attribute $Bitmap must be non-resident + * otherwise windows will not boot (blue screen of death)! We cannot + * check for this here as we do not know which inode's $Bitmap is + * being asked about so the caller needs to special case this. + */ +int ntfs_attr_can_be_resident(const struct ntfs_volume *vol, const __le32 type) +{ + if (type == AT_INDEX_ALLOCATION) + return -EPERM; + return 0; +} + +/* + * ntfs_attr_record_resize - resize an attribute record + * @m: mft record containing attribute record + * @a: attribute record to resize + * @new_size: new size in bytes to which to resize the attribute record @a + * + * Resize the attribute record @a, i.e. the resident part of the attribute, in + * the mft record @m to @new_size bytes. + */ +int ntfs_attr_record_resize(struct mft_record *m, struct attr_record *a, u32 new_size) +{ + u32 old_size, alloc_size, attr_size; + + old_size = le32_to_cpu(m->bytes_in_use); + alloc_size = le32_to_cpu(m->bytes_allocated); + attr_size = le32_to_cpu(a->length); + + ntfs_debug("Sizes: old=%u alloc=%u attr=%u new=%u\n", + (unsigned int)old_size, (unsigned int)alloc_size, + (unsigned int)attr_size, (unsigned int)new_size); + + /* Align to 8 bytes if it is not already done. */ + if (new_size & 7) + new_size = (new_size + 7) & ~7; + /* If the actual attribute length has changed, move things around. */ + if (new_size != attr_size) { + u32 new_muse = le32_to_cpu(m->bytes_in_use) - + attr_size + new_size; + /* Not enough space in this mft record. */ + if (new_muse > le32_to_cpu(m->bytes_allocated)) + return -ENOSPC; + + if (a->type == AT_INDEX_ROOT && new_size > attr_size && + new_muse + 120 > alloc_size && old_size + 120 <= alloc_size) { + ntfs_debug("Too big struct index_root (%u > %u)\n", + new_muse, alloc_size); + return -ENOSPC; + } + + /* Move attributes following @a to their new location. */ + memmove((u8 *)a + new_size, (u8 *)a + le32_to_cpu(a->length), + le32_to_cpu(m->bytes_in_use) - ((u8 *)a - + (u8 *)m) - attr_size); + /* Adjust @m to reflect the change in used space. */ + m->bytes_in_use = cpu_to_le32(new_muse); + /* Adjust @a to reflect the new size. */ + if (new_size >= offsetof(struct attr_record, length) + sizeof(a->length)) + a->length = cpu_to_le32(new_size); + } + return 0; +} + +/* + * ntfs_resident_attr_value_resize - resize the value of a resident attribute + * @m: mft record containing attribute record + * @a: attribute record whose value to resize + * @new_size: new size in bytes to which to resize the attribute value of @a + * + * Resize the value of the attribute @a in the mft record @m to @new_size bytes. + * If the value is made bigger, the newly allocated space is cleared. + */ +int ntfs_resident_attr_value_resize(struct mft_record *m, struct attr_record *a, + const u32 new_size) +{ + u32 old_size; + + /* Resize the resident part of the attribute record. */ + if (ntfs_attr_record_resize(m, a, + le16_to_cpu(a->data.resident.value_offset) + new_size)) + return -ENOSPC; + /* + * The resize succeeded! If we made the attribute value bigger, clear + * the area between the old size and @new_size. + */ + old_size = le32_to_cpu(a->data.resident.value_length); + if (new_size > old_size) + memset((u8 *)a + le16_to_cpu(a->data.resident.value_offset) + + old_size, 0, new_size - old_size); + /* Finally update the length of the attribute value. */ + a->data.resident.value_length = cpu_to_le32(new_size); + return 0; +} + +/* + * ntfs_attr_make_non_resident - convert a resident to a non-resident attribute + * @ni: ntfs inode describing the attribute to convert + * @data_size: size of the resident data to copy to the non-resident attribute + * + * Convert the resident ntfs attribute described by the ntfs inode @ni to a + * non-resident one. + * + * @data_size must be equal to the attribute value size. This is needed since + * we need to know the size before we can map the mft record and our callers + * always know it. The reason we cannot simply read the size from the vfs + * inode i_size is that this is not necessarily uptodate. This happens when + * ntfs_attr_make_non_resident() is called in the ->truncate call path(s). + */ +int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size) +{ + s64 new_size; + struct inode *vi = VFS_I(ni); + struct ntfs_volume *vol = ni->vol; + struct ntfs_inode *base_ni; + struct mft_record *m; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + struct folio *folio; + struct runlist_element *rl; + unsigned long flags; + int mp_size, mp_ofs, name_ofs, arec_size, err, err2; + u32 attr_size; + u8 old_res_attr_flags; + + if (NInoNonResident(ni)) { + ntfs_warning(vol->sb, + "Trying to make non-resident attribute non-resident. Aborting...\n"); + return -EINVAL; + } + + /* Check that the attribute is allowed to be non-resident. */ + err = ntfs_attr_can_be_non_resident(vol, ni->type); + if (unlikely(err)) { + if (err == -EPERM) + ntfs_debug("Attribute is not allowed to be non-resident."); + else + ntfs_debug("Attribute not defined on the NTFS volume!"); + return err; + } + + if (NInoEncrypted(ni)) + return -EIO; + + if (!NInoAttr(ni)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + m = map_mft_record(base_ni); + if (IS_ERR(m)) { + err = PTR_ERR(m); + m = NULL; + ctx = NULL; + goto err_out; + } + ctx = ntfs_attr_get_search_ctx(base_ni, m); + if (unlikely(!ctx)) { + err = -ENOMEM; + goto err_out; + } + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + if (err == -ENOENT) + err = -EIO; + goto err_out; + } + m = ctx->mrec; + a = ctx->attr; + + /* + * The size needs to be aligned to a cluster boundary for allocation + * purposes. + */ + new_size = (data_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1); + if (new_size > 0) { + if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) { + /* must allocate full compression blocks */ + new_size = + ((new_size - 1) | + ((1L << (STANDARD_COMPRESSION_UNIT + + vol->cluster_size_bits)) - 1)) + 1; + } + + /* + * Will need folio later and since folio lock nests + * outside all ntfs locks, we need to get the folio now. + */ + folio = __filemap_get_folio(vi->i_mapping, 0, + FGP_CREAT | FGP_LOCK, + mapping_gfp_mask(vi->i_mapping)); + if (IS_ERR(folio)) { + err = -ENOMEM; + goto err_out; + } + + /* Start by allocating clusters to hold the attribute value. */ + rl = ntfs_cluster_alloc(vol, 0, + ntfs_bytes_to_cluster(vol, new_size), + -1, DATA_ZONE, true, false, false); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + ntfs_debug("Failed to allocate cluster%s, error code %i.", + ntfs_bytes_to_cluster(vol, new_size) > 1 ? "s" : "", + err); + goto folio_err_out; + } + } else { + rl = NULL; + folio = NULL; + } + + down_write(&ni->runlist.lock); + /* Determine the size of the mapping pairs array. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0, -1, -1); + if (unlikely(mp_size < 0)) { + err = mp_size; + ntfs_debug("Failed to get size for mapping pairs array, error code %i.\n", err); + goto rl_err_out; + } + + if (NInoNonResident(ni) || a->non_resident) { + err = -EIO; + goto rl_err_out; + } + + /* + * Calculate new offsets for the name and the mapping pairs array. + */ + if (NInoSparse(ni) || NInoCompressed(ni)) + name_ofs = (offsetof(struct attr_record, + data.non_resident.compressed_size) + + sizeof(a->data.non_resident.compressed_size) + + 7) & ~7; + else + name_ofs = (offsetof(struct attr_record, + data.non_resident.compressed_size) + 7) & ~7; + mp_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7; + /* + * Determine the size of the resident part of the now non-resident + * attribute record. + */ + arec_size = (mp_ofs + mp_size + 7) & ~7; + /* + * If the folio is not uptodate bring it uptodate by copying from the + * attribute value. + */ + attr_size = le32_to_cpu(a->data.resident.value_length); + WARN_ON(attr_size != data_size); + if (folio && !folio_test_uptodate(folio)) { + folio_fill_tail(folio, 0, (u8 *)a + + le16_to_cpu(a->data.resident.value_offset), + attr_size); + folio_mark_uptodate(folio); + } + + /* Backup the attribute flag. */ + old_res_attr_flags = a->data.resident.flags; + /* Resize the resident part of the attribute record. */ + err = ntfs_attr_record_resize(m, a, arec_size); + if (unlikely(err)) + goto rl_err_out; + + /* + * Convert the resident part of the attribute record to describe a + * non-resident attribute. + */ + a->non_resident = 1; + /* Move the attribute name if it exists and update the offset. */ + if (a->name_length) + memmove((u8 *)a + name_ofs, (u8 *)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(__le16)); + a->name_offset = cpu_to_le16(name_ofs); + /* Setup the fields specific to non-resident attributes. */ + a->data.non_resident.lowest_vcn = 0; + a->data.non_resident.highest_vcn = + cpu_to_le64(ntfs_bytes_to_cluster(vol, new_size - 1)); + a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs); + memset(&a->data.non_resident.reserved, 0, + sizeof(a->data.non_resident.reserved)); + a->data.non_resident.allocated_size = cpu_to_le64(new_size); + a->data.non_resident.data_size = + a->data.non_resident.initialized_size = + cpu_to_le64(attr_size); + if (NInoSparse(ni) || NInoCompressed(ni)) { + a->data.non_resident.compression_unit = 0; + if (NInoCompressed(ni) || vol->major_ver < 3) + a->data.non_resident.compression_unit = 4; + a->data.non_resident.compressed_size = + a->data.non_resident.allocated_size; + } else + a->data.non_resident.compression_unit = 0; + /* Generate the mapping pairs array into the attribute record. */ + err = ntfs_mapping_pairs_build(vol, (u8 *)a + mp_ofs, + arec_size - mp_ofs, rl, 0, -1, NULL, NULL, NULL); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to build mapping pairs, error code %i.", + err); + goto undo_err_out; + } + + /* Setup the in-memory attribute structure to be non-resident. */ + ni->runlist.rl = rl; + if (rl) { + for (ni->runlist.count = 1; rl->length != 0; rl++) + ni->runlist.count++; + } else + ni->runlist.count = 0; + write_lock_irqsave(&ni->size_lock, flags); + ni->allocated_size = new_size; + if (NInoSparse(ni) || NInoCompressed(ni)) { + ni->itype.compressed.size = ni->allocated_size; + if (a->data.non_resident.compression_unit) { + ni->itype.compressed.block_size = 1U << + (a->data.non_resident.compression_unit + + vol->cluster_size_bits); + ni->itype.compressed.block_size_bits = + ffs(ni->itype.compressed.block_size) - + 1; + ni->itype.compressed.block_clusters = 1U << + a->data.non_resident.compression_unit; + } else { + ni->itype.compressed.block_size = 0; + ni->itype.compressed.block_size_bits = 0; + ni->itype.compressed.block_clusters = 0; + } + vi->i_blocks = ni->itype.compressed.size >> 9; + } else + vi->i_blocks = ni->allocated_size >> 9; + write_unlock_irqrestore(&ni->size_lock, flags); + /* + * This needs to be last since the address space operations ->read_folio + * and ->writepage can run concurrently with us as they are not + * serialized on i_mutex. Note, we are not allowed to fail once we flip + * this switch, which is another reason to do this last. + */ + NInoSetNonResident(ni); + NInoSetFullyMapped(ni); + /* Mark the mft record dirty, so it gets written back. */ + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); + up_write(&ni->runlist.lock); + if (folio) { + iomap_dirty_folio(vi->i_mapping, folio); + folio_unlock(folio); + folio_put(folio); + } + ntfs_debug("Done."); + return 0; +undo_err_out: + /* Convert the attribute back into a resident attribute. */ + a->non_resident = 0; + /* Move the attribute name if it exists and update the offset. */ + name_ofs = (offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved) + 7) & ~7; + if (a->name_length) + memmove((u8 *)a + name_ofs, (u8 *)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(__le16)); + mp_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7; + a->name_offset = cpu_to_le16(name_ofs); + arec_size = (mp_ofs + attr_size + 7) & ~7; + /* Resize the resident part of the attribute record. */ + err2 = ntfs_attr_record_resize(m, a, arec_size); + if (unlikely(err2)) { + /* + * This cannot happen (well if memory corruption is at work it + * could happen in theory), but deal with it as well as we can. + * If the old size is too small, truncate the attribute, + * otherwise simply give it a larger allocated size. + */ + arec_size = le32_to_cpu(a->length); + if ((mp_ofs + attr_size) > arec_size) { + err2 = attr_size; + attr_size = arec_size - mp_ofs; + ntfs_error(vol->sb, + "Failed to undo partial resident to non-resident attribute conversion. Truncating inode 0x%llx, attribute type 0x%x from %i bytes to %i bytes to maintain metadata consistency. THIS MEANS YOU ARE LOSING %i BYTES DATA FROM THIS %s.", + ni->mft_no, + (unsigned int)le32_to_cpu(ni->type), + err2, attr_size, err2 - attr_size, + ((ni->type == AT_DATA) && + !ni->name_len) ? "FILE" : "ATTRIBUTE"); + write_lock_irqsave(&ni->size_lock, flags); + ni->initialized_size = attr_size; + i_size_write(vi, attr_size); + write_unlock_irqrestore(&ni->size_lock, flags); + } + } + /* Setup the fields specific to resident attributes. */ + a->data.resident.value_length = cpu_to_le32(attr_size); + a->data.resident.value_offset = cpu_to_le16(mp_ofs); + a->data.resident.flags = old_res_attr_flags; + memset(&a->data.resident.reserved, 0, + sizeof(a->data.resident.reserved)); + /* Copy the data from folio back to the attribute value. */ + if (folio) + memcpy_from_folio((u8 *)a + mp_ofs, folio, 0, attr_size); + /* Setup the allocated size in the ntfs inode in case it changed. */ + write_lock_irqsave(&ni->size_lock, flags); + ni->allocated_size = arec_size - mp_ofs; + write_unlock_irqrestore(&ni->size_lock, flags); + /* Mark the mft record dirty, so it gets written back. */ + mark_mft_record_dirty(ctx->ntfs_ino); +rl_err_out: + up_write(&ni->runlist.lock); + if (rl) { + if (ntfs_cluster_free_from_rl(vol, rl) < 0) { + ntfs_error(vol->sb, + "Failed to release allocated cluster(s) in error code path. Run chkdsk to recover the lost cluster(s)."); + NVolSetErrors(vol); + } + kvfree(rl); +folio_err_out: + folio_unlock(folio); + folio_put(folio); + } +err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(base_ni); + ni->runlist.rl = NULL; + + if (err == -EINVAL) + err = -EIO; + return err; +} + +/* + * ntfs_attr_set - fill (a part of) an attribute with a byte + * @ni: ntfs inode describing the attribute to fill + * @ofs: offset inside the attribute at which to start to fill + * @cnt: number of bytes to fill + * @val: the unsigned 8-bit value with which to fill the attribute + * + * Fill @cnt bytes of the attribute described by the ntfs inode @ni starting at + * byte offset @ofs inside the attribute with the constant byte @val. + * + * This function is effectively like memset() applied to an ntfs attribute. + * Note thie function actually only operates on the page cache pages belonging + * to the ntfs attribute and it marks them dirty after doing the memset(). + * Thus it relies on the vm dirty page write code paths to cause the modified + * pages to be written to the mft record/disk. + */ +int ntfs_attr_set(struct ntfs_inode *ni, s64 ofs, s64 cnt, const u8 val) +{ + struct address_space *mapping = VFS_I(ni)->i_mapping; + struct folio *folio; + pgoff_t index; + u8 *addr; + unsigned long offset; + size_t attr_len; + int ret = 0; + + index = ofs >> PAGE_SHIFT; + while (cnt) { + folio = read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + ntfs_error(VFS_I(ni)->i_sb, "Failed to read a page %lu for attr %#x: %ld", + index, ni->type, PTR_ERR(folio)); + break; + } + + offset = offset_in_folio(folio, ofs); + attr_len = min_t(size_t, (size_t)cnt, folio_size(folio) - offset); + + folio_lock(folio); + addr = kmap_local_folio(folio, offset); + memset(addr, val, attr_len); + kunmap_local(addr); + + folio_mark_dirty(folio); + folio_unlock(folio); + folio_put(folio); + + ofs += attr_len; + cnt -= attr_len; + index++; + cond_resched(); + } + + return ret; +} + +int ntfs_attr_set_initialized_size(struct ntfs_inode *ni, loff_t new_size) +{ + struct ntfs_attr_search_ctx *ctx; + int err = 0; + + if (!NInoNonResident(ni)) + return -EINVAL; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -ENOMEM; + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (err) + goto out_ctx; + + ctx->attr->data.non_resident.initialized_size = cpu_to_le64(new_size); + ni->initialized_size = new_size; + mark_mft_record_dirty(ctx->ntfs_ino); +out_ctx: + ntfs_attr_put_search_ctx(ctx); + return err; +} + +/* + * ntfs_make_room_for_attr - make room for an attribute inside an mft record + * @m: mft record + * @pos: position at which to make space + * @size: byte size to make available at this position + * + * @pos points to the attribute in front of which we want to make space. + */ +static int ntfs_make_room_for_attr(struct mft_record *m, u8 *pos, u32 size) +{ + u32 biu; + + ntfs_debug("Entering for pos 0x%x, size %u.\n", + (int)(pos - (u8 *)m), (unsigned int) size); + + /* Make size 8-byte alignment. */ + size = (size + 7) & ~7; + + /* Rigorous consistency checks. */ + if (!m || !pos || pos < (u8 *)m) { + pr_err("%s: pos=%p m=%p\n", __func__, pos, m); + return -EINVAL; + } + + /* The -8 is for the attribute terminator. */ + if (pos - (u8 *)m > (int)le32_to_cpu(m->bytes_in_use) - 8) + return -EINVAL; + /* Nothing to do. */ + if (!size) + return 0; + + biu = le32_to_cpu(m->bytes_in_use); + /* Do we have enough space? */ + if (biu + size > le32_to_cpu(m->bytes_allocated) || + pos + size > (u8 *)m + le32_to_cpu(m->bytes_allocated)) { + ntfs_debug("No enough space in the MFT record\n"); + return -ENOSPC; + } + /* Move everything after pos to pos + size. */ + memmove(pos + size, pos, biu - (pos - (u8 *)m)); + /* Update mft record. */ + m->bytes_in_use = cpu_to_le32(biu + size); + return 0; +} + +/* + * ntfs_resident_attr_record_add - add resident attribute to inode + * @ni: opened ntfs inode to which MFT record add attribute + * @type: type of the new attribute + * @name: name of the new attribute + * @name_len: name length of the new attribute + * @val: value of the new attribute + * @size: size of new attribute (length of @val, if @val != NULL) + * @flags: flags of the new attribute + */ +int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, + __le16 *name, u8 name_len, u8 *val, u32 size, + __le16 flags) +{ + struct ntfs_attr_search_ctx *ctx; + u32 length; + struct attr_record *a; + struct mft_record *m; + int err, offset; + struct ntfs_inode *base_ni; + + if (!ni || (!name && name_len)) + return -EINVAL; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x, flags 0x%x.\n", + (long long) ni->mft_no, (unsigned int) le32_to_cpu(type), + (unsigned int) le16_to_cpu(flags)); + + err = ntfs_attr_can_be_resident(ni->vol, type); + if (err) { + if (err == -EPERM) + ntfs_debug("Attribute can't be resident.\n"); + else + ntfs_debug("ntfs_attr_can_be_resident failed.\n"); + return err; + } + + /* Locate place where record should be. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(ni->vol->sb, "%s: Failed to get search context", + __func__); + return -ENOMEM; + } + /* + * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for + * attribute in @ni->mrec, not any extent inode in case if @ni is base + * file record. + */ + err = ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, val, size, ctx); + if (!err) { + err = -EEXIST; + ntfs_debug("Attribute already present.\n"); + goto put_err_out; + } + if (err != -ENOENT) { + err = -EIO; + goto put_err_out; + } + a = ctx->attr; + m = ctx->mrec; + + /* Make room for attribute. */ + length = offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved) + + ((name_len * sizeof(__le16) + 7) & ~7) + + ((size + 7) & ~7); + err = ntfs_make_room_for_attr(ctx->mrec, (u8 *) ctx->attr, length); + if (err) { + ntfs_debug("Failed to make room for attribute.\n"); + goto put_err_out; + } + + /* Setup record fields. */ + offset = ((u8 *)a - (u8 *)m); + a->type = type; + a->length = cpu_to_le32(length); + a->non_resident = 0; + a->name_length = name_len; + a->name_offset = + name_len ? cpu_to_le16((offsetof(struct attr_record, data.resident.reserved) + + sizeof(a->data.resident.reserved))) : cpu_to_le16(0); + + a->flags = flags; + a->instance = m->next_attr_instance; + a->data.resident.value_length = cpu_to_le32(size); + a->data.resident.value_offset = cpu_to_le16(length - ((size + 7) & ~7)); + if (val) + memcpy((u8 *)a + le16_to_cpu(a->data.resident.value_offset), val, size); + else + memset((u8 *)a + le16_to_cpu(a->data.resident.value_offset), 0, size); + if (type == AT_FILE_NAME) + a->data.resident.flags = RESIDENT_ATTR_IS_INDEXED; + else + a->data.resident.flags = 0; + if (name_len) + memcpy((u8 *)a + le16_to_cpu(a->name_offset), + name, sizeof(__le16) * name_len); + m->next_attr_instance = + cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff); + if (ni->nr_extents == -1) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) { + err = ntfs_attrlist_entry_add(ni, a); + if (err) { + ntfs_attr_record_resize(m, a, 0); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_debug("Failed add attribute entry to ATTRIBUTE_LIST.\n"); + goto put_err_out; + } + } + mark_mft_record_dirty(ni); + ntfs_attr_put_search_ctx(ctx); + return offset; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + return -EIO; +} + +/* + * ntfs_non_resident_attr_record_add - add extent of non-resident attribute + * @ni: opened ntfs inode to which MFT record add attribute + * @type: type of the new attribute extent + * @name: name of the new attribute extent + * @name_len: name length of the new attribute extent + * @lowest_vcn: lowest vcn of the new attribute extent + * @dataruns_size: dataruns size of the new attribute extent + * @flags: flags of the new attribute extent + */ +static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, + __le16 *name, u8 name_len, s64 lowest_vcn, int dataruns_size, + __le16 flags) +{ + struct ntfs_attr_search_ctx *ctx; + u32 length; + struct attr_record *a; + struct mft_record *m; + struct ntfs_inode *base_ni; + int err, offset; + + if (!ni || dataruns_size <= 0 || (!name && name_len)) + return -EINVAL; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld, dataruns_size %d, flags 0x%x.\n", + (long long) ni->mft_no, (unsigned int) le32_to_cpu(type), + (long long) lowest_vcn, dataruns_size, + (unsigned int) le16_to_cpu(flags)); + + err = ntfs_attr_can_be_non_resident(ni->vol, type); + if (err) { + if (err == -EPERM) + pr_err("Attribute can't be non resident\n"); + else + pr_err("ntfs_attr_can_be_non_resident failed\n"); + return err; + } + + /* Locate place where record should be. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + pr_err("%s: Failed to get search context\n", __func__); + return -ENOMEM; + } + /* + * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for + * attribute in @ni->mrec, not any extent inode in case if @ni is base + * file record. + */ + err = ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, NULL, 0, ctx); + if (!err) { + err = -EEXIST; + pr_err("Attribute 0x%x already present\n", type); + goto put_err_out; + } + if (err != -ENOENT) { + pr_err("ntfs_attr_find failed\n"); + err = -EIO; + goto put_err_out; + } + a = ctx->attr; + m = ctx->mrec; + + /* Make room for attribute. */ + dataruns_size = (dataruns_size + 7) & ~7; + length = offsetof(struct attr_record, data.non_resident.compressed_size) + + ((sizeof(__le16) * name_len + 7) & ~7) + dataruns_size + + ((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ? + sizeof(a->data.non_resident.compressed_size) : 0); + err = ntfs_make_room_for_attr(ctx->mrec, (u8 *) ctx->attr, length); + if (err) { + pr_err("Failed to make room for attribute\n"); + goto put_err_out; + } + + /* Setup record fields. */ + a->type = type; + a->length = cpu_to_le32(length); + a->non_resident = 1; + a->name_length = name_len; + a->name_offset = cpu_to_le16(offsetof(struct attr_record, + data.non_resident.compressed_size) + + ((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ? + sizeof(a->data.non_resident.compressed_size) : 0)); + a->flags = flags; + a->instance = m->next_attr_instance; + a->data.non_resident.lowest_vcn = cpu_to_le64(lowest_vcn); + a->data.non_resident.mapping_pairs_offset = cpu_to_le16(length - dataruns_size); + a->data.non_resident.compression_unit = + (flags & ATTR_IS_COMPRESSED) ? STANDARD_COMPRESSION_UNIT : 0; + /* If @lowest_vcn == 0, than setup empty attribute. */ + if (!lowest_vcn) { + a->data.non_resident.highest_vcn = cpu_to_le64(-1); + a->data.non_resident.allocated_size = 0; + a->data.non_resident.data_size = 0; + a->data.non_resident.initialized_size = 0; + /* Set empty mapping pairs. */ + *((u8 *)a + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)) = 0; + } + if (name_len) + memcpy((u8 *)a + le16_to_cpu(a->name_offset), + name, sizeof(__le16) * name_len); + m->next_attr_instance = + cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff); + if (ni->nr_extents == -1) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) { + err = ntfs_attrlist_entry_add(ni, a); + if (err) { + pr_err("Failed add attr entry to attrlist\n"); + ntfs_attr_record_resize(m, a, 0); + goto put_err_out; + } + } + mark_mft_record_dirty(ni); + /* + * Locate offset from start of the MFT record where new attribute is + * placed. We need relookup it, because record maybe moved during + * update of attribute list. + */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, + lowest_vcn, NULL, 0, ctx); + if (err) { + pr_err("%s: attribute lookup failed\n", __func__); + ntfs_attr_put_search_ctx(ctx); + return err; + + } + offset = (u8 *)ctx->attr - (u8 *)ctx->mrec; + ntfs_attr_put_search_ctx(ctx); + return offset; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + return -1; +} + +/* + * ntfs_attr_record_rm - remove attribute extent + * @ctx: search context describing the attribute which should be removed + * + * If this function succeed, user should reinit search context if he/she wants + * use it anymore. + */ +int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *base_ni, *ni; + __le32 type; + int err; + + if (!ctx || !ctx->ntfs_ino || !ctx->mrec || !ctx->attr) + return -EINVAL; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", + (long long) ctx->ntfs_ino->mft_no, + (unsigned int) le32_to_cpu(ctx->attr->type)); + type = ctx->attr->type; + ni = ctx->ntfs_ino; + if (ctx->base_ntfs_ino) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + + /* Remove attribute itself. */ + if (ntfs_attr_record_resize(ctx->mrec, ctx->attr, 0)) { + ntfs_debug("Couldn't remove attribute record. Bug or damaged MFT record.\n"); + return -EIO; + } + mark_mft_record_dirty(ni); + + /* + * Remove record from $ATTRIBUTE_LIST if present and we don't want + * delete $ATTRIBUTE_LIST itself. + */ + if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST) { + err = ntfs_attrlist_entry_rm(ctx); + if (err) { + ntfs_debug("Couldn't delete record from $ATTRIBUTE_LIST.\n"); + return err; + } + } + + /* Post $ATTRIBUTE_LIST delete setup. */ + if (type == AT_ATTRIBUTE_LIST) { + if (NInoAttrList(base_ni) && base_ni->attr_list) + kvfree(base_ni->attr_list); + base_ni->attr_list = NULL; + NInoClearAttrList(base_ni); + } + + /* Free MFT record, if it doesn't contain attributes. */ + if (le32_to_cpu(ctx->mrec->bytes_in_use) - + le16_to_cpu(ctx->mrec->attrs_offset) == 8) { + if (ntfs_mft_record_free(ni->vol, ni)) { + ntfs_debug("Couldn't free MFT record.\n"); + return -EIO; + } + /* Remove done if we freed base inode. */ + if (ni == base_ni) + return 0; + ntfs_inode_close(ni); + ctx->ntfs_ino = ni = NULL; + } + + if (type == AT_ATTRIBUTE_LIST || !NInoAttrList(base_ni)) + return 0; + + /* Remove attribute list if we don't need it any more. */ + if (!ntfs_attrlist_need(base_ni)) { + struct ntfs_attr na; + struct inode *attr_vi; + + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx)) { + ntfs_debug("Couldn't find attribute list. Succeed anyway.\n"); + return 0; + } + /* Deallocate clusters. */ + if (ctx->attr->non_resident) { + struct runlist_element *al_rl; + size_t new_rl_count; + + al_rl = ntfs_mapping_pairs_decompress(base_ni->vol, + ctx->attr, NULL, &new_rl_count); + if (IS_ERR(al_rl)) { + ntfs_debug("Couldn't decompress attribute list runlist. Succeed anyway.\n"); + return 0; + } + if (ntfs_cluster_free_from_rl(base_ni->vol, al_rl)) + ntfs_debug("Leaking clusters! Run chkdsk. Couldn't free clusters from attribute list runlist.\n"); + kvfree(al_rl); + } + /* Remove attribute record itself. */ + if (ntfs_attr_record_rm(ctx)) { + ntfs_debug("Couldn't remove attribute list. Succeed anyway.\n"); + return 0; + } + + na.mft_no = VFS_I(base_ni)->i_ino; + na.type = AT_ATTRIBUTE_LIST; + na.name = NULL; + na.name_len = 0; + + attr_vi = ilookup5(VFS_I(base_ni)->i_sb, VFS_I(base_ni)->i_ino, + ntfs_test_inode, &na); + if (attr_vi) { + clear_nlink(attr_vi); + iput(attr_vi); + } + + } + return 0; +} + +/* + * ntfs_attr_add - add attribute to inode + * @ni: opened ntfs inode to which add attribute + * @type: type of the new attribute + * @name: name in unicode of the new attribute + * @name_len: name length in unicode characters of the new attribute + * @val: value of new attribute + * @size: size of the new attribute / length of @val (if specified) + * + * @val should always be specified for always resident attributes (eg. FILE_NAME + * attribute), for attributes that can become non-resident @val can be NULL + * (eg. DATA attribute). @size can be specified even if @val is NULL, in this + * case data size will be equal to @size and initialized size will be equal + * to 0. + * + * If inode haven't got enough space to add attribute, add attribute to one of + * it extents, if no extents present or no one of them have enough space, than + * allocate new extent and add attribute to it. + * + * If on one of this steps attribute list is needed but not present, than it is + * added transparently to caller. So, this function should not be called with + * @type == AT_ATTRIBUTE_LIST, if you really need to add attribute list call + * ntfs_inode_add_attrlist instead. + * + * On success return 0. On error return -1 with errno set to the error code. + */ +int ntfs_attr_add(struct ntfs_inode *ni, __le32 type, + __le16 *name, u8 name_len, u8 *val, s64 size) +{ + struct super_block *sb; + u32 attr_rec_size; + int err, i, offset; + bool is_resident; + bool can_be_non_resident = false; + struct ntfs_inode *attr_ni; + struct inode *attr_vi; + struct mft_record *ni_mrec; + + if (!ni || size < 0 || type == AT_ATTRIBUTE_LIST) + return -EINVAL; + + ntfs_debug("Entering for inode 0x%llx, attr %x, size %lld.\n", + (long long) ni->mft_no, type, size); + + if (ni->nr_extents == -1) + ni = ni->ext.base_ntfs_ino; + + /* Check the attribute type and the size. */ + err = ntfs_attr_size_bounds_check(ni->vol, type, size); + if (err) { + if (err == -ENOENT) + err = -EIO; + return err; + } + + sb = ni->vol->sb; + /* Sanity checks for always resident attributes. */ + err = ntfs_attr_can_be_non_resident(ni->vol, type); + if (err) { + if (err != -EPERM) { + ntfs_error(sb, "ntfs_attr_can_be_non_resident failed"); + goto err_out; + } + /* @val is mandatory. */ + if (!val) { + ntfs_error(sb, + "val is mandatory for always resident attributes"); + return -EINVAL; + } + if (size > ni->vol->mft_record_size) { + ntfs_error(sb, "Attribute is too big"); + return -ERANGE; + } + } else + can_be_non_resident = true; + + /* + * Determine resident or not will be new attribute. We add 8 to size in + * non resident case for mapping pairs. + */ + err = ntfs_attr_can_be_resident(ni->vol, type); + if (!err) { + is_resident = true; + } else { + if (err != -EPERM) { + ntfs_error(sb, "ntfs_attr_can_be_resident failed"); + goto err_out; + } + is_resident = false; + } + + /* Calculate attribute record size. */ + if (is_resident) + attr_rec_size = offsetof(struct attr_record, data.resident.reserved) + + 1 + + ((name_len * sizeof(__le16) + 7) & ~7) + + ((size + 7) & ~7); + else + attr_rec_size = offsetof(struct attr_record, data.non_resident.compressed_size) + + ((name_len * sizeof(__le16) + 7) & ~7) + 8; + + /* + * If we have enough free space for the new attribute in the base MFT + * record, then add attribute to it. + */ +retry: + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto err_out; + } + + if (le32_to_cpu(ni_mrec->bytes_allocated) - + le32_to_cpu(ni_mrec->bytes_in_use) >= attr_rec_size) { + attr_ni = ni; + unmap_mft_record(ni); + goto add_attr_record; + } + unmap_mft_record(ni); + + /* Try to add to extent inodes. */ + err = ntfs_inode_attach_all_extents(ni); + if (err) { + ntfs_error(sb, "Failed to attach all extents to inode"); + goto err_out; + } + + for (i = 0; i < ni->nr_extents; i++) { + attr_ni = ni->ext.extent_ntfs_inos[i]; + ni_mrec = map_mft_record(attr_ni); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto err_out; + } + + if (le32_to_cpu(ni_mrec->bytes_allocated) - + le32_to_cpu(ni_mrec->bytes_in_use) >= + attr_rec_size) { + unmap_mft_record(attr_ni); + goto add_attr_record; + } + unmap_mft_record(attr_ni); + } + + /* There is no extent that contain enough space for new attribute. */ + if (!NInoAttrList(ni)) { + /* Add attribute list not present, add it and retry. */ + err = ntfs_inode_add_attrlist(ni); + if (err) { + ntfs_error(sb, "Failed to add attribute list"); + goto err_out; + } + goto retry; + } + + attr_ni = NULL; + /* Allocate new extent. */ + err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL); + if (err) { + ntfs_error(sb, "Failed to allocate extent record"); + goto err_out; + } + unmap_mft_record(attr_ni); + +add_attr_record: + if (is_resident) { + /* Add resident attribute. */ + offset = ntfs_resident_attr_record_add(attr_ni, type, name, + name_len, val, size, 0); + if (offset < 0) { + if (offset == -ENOSPC && can_be_non_resident) + goto add_non_resident; + err = offset; + ntfs_error(sb, "Failed to add resident attribute"); + goto free_err_out; + } + return 0; + } + +add_non_resident: + /* Add non resident attribute. */ + offset = ntfs_non_resident_attr_record_add(attr_ni, type, name, + name_len, 0, 8, 0); + if (offset < 0) { + err = offset; + ntfs_error(sb, "Failed to add non resident attribute"); + goto free_err_out; + } + + /* If @size == 0, we are done. */ + if (!size) + return 0; + + /* Open new attribute and resize it. */ + attr_vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len); + if (IS_ERR(attr_vi)) { + err = PTR_ERR(attr_vi); + ntfs_error(sb, "Failed to open just added attribute"); + goto rm_attr_err_out; + } + attr_ni = NTFS_I(attr_vi); + + /* Resize and set attribute value. */ + if (ntfs_attr_truncate(attr_ni, size) || + (val && (ntfs_inode_attr_pwrite(attr_vi, 0, size, val, false) != size))) { + err = -EIO; + ntfs_error(sb, "Failed to initialize just added attribute"); + if (ntfs_attr_rm(attr_ni)) + ntfs_error(sb, "Failed to remove just added attribute"); + iput(attr_vi); + goto err_out; + } + iput(attr_vi); + return 0; + +rm_attr_err_out: + /* Remove just added attribute. */ + ni_mrec = map_mft_record(attr_ni); + if (!IS_ERR(ni_mrec)) { + if (ntfs_attr_record_resize(ni_mrec, + (struct attr_record *)((u8 *)ni_mrec + offset), 0)) + ntfs_error(sb, "Failed to remove just added attribute #2"); + unmap_mft_record(attr_ni); + } else + pr_err("EIO when try to remove new added attr\n"); + +free_err_out: + /* Free MFT record, if it doesn't contain attributes. */ + ni_mrec = map_mft_record(attr_ni); + if (!IS_ERR(ni_mrec)) { + int attr_size; + + attr_size = le32_to_cpu(ni_mrec->bytes_in_use) - + le16_to_cpu(ni_mrec->attrs_offset); + unmap_mft_record(attr_ni); + if (attr_size == 8) { + if (ntfs_mft_record_free(attr_ni->vol, attr_ni)) + ntfs_error(sb, "Failed to free MFT record"); + if (attr_ni->nr_extents < 0) + ntfs_inode_close(attr_ni); + } + } else + pr_err("EIO when testing mft record is free-able\n"); + +err_out: + return err; +} + +/* + * __ntfs_attr_init - primary initialization of an ntfs attribute structure + * @ni: ntfs attribute inode to initialize + * @ni: ntfs inode with which to initialize the ntfs attribute + * @type: attribute type + * @name: attribute name in little endian Unicode or NULL + * @name_len: length of attribute @name in Unicode characters (if @name given) + * + * Initialize the ntfs attribute @na with @ni, @type, @name, and @name_len. + */ +static void __ntfs_attr_init(struct ntfs_inode *ni, + const __le32 type, __le16 *name, const u32 name_len) +{ + ni->runlist.rl = NULL; + ni->type = type; + ni->name = name; + if (name) + ni->name_len = name_len; + else + ni->name_len = 0; +} + +/* + * ntfs_attr_init - initialize an ntfs_attr with data sizes and status + * @ni: ntfs inode to initialize + * @non_resident: true if attribute is non-resident + * @compressed: true if attribute is compressed + * @encrypted: true if attribute is encrypted + * @sparse: true if attribute is sparse + * @allocated_size: allocated size of the attribute + * @data_size: actual data size of the attribute + * @initialized_size: initialized size of the attribute + * @compressed_size: compressed size (if compressed or sparse) + * @compression_unit: compression unit size (log2 of clusters) + * + * Final initialization for an ntfs attribute. + */ +static void ntfs_attr_init(struct ntfs_inode *ni, const bool non_resident, + const bool compressed, const bool encrypted, const bool sparse, + const s64 allocated_size, const s64 data_size, + const s64 initialized_size, const s64 compressed_size, + const u8 compression_unit) +{ + if (non_resident) + NInoSetNonResident(ni); + if (compressed) { + NInoSetCompressed(ni); + ni->flags |= FILE_ATTR_COMPRESSED; + } + if (encrypted) { + NInoSetEncrypted(ni); + ni->flags |= FILE_ATTR_ENCRYPTED; + } + if (sparse) { + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + } + ni->allocated_size = allocated_size; + ni->data_size = data_size; + ni->initialized_size = initialized_size; + if (compressed || sparse) { + struct ntfs_volume *vol = ni->vol; + + ni->itype.compressed.size = compressed_size; + ni->itype.compressed.block_clusters = 1 << compression_unit; + ni->itype.compressed.block_size = 1 << (compression_unit + + vol->cluster_size_bits); + ni->itype.compressed.block_size_bits = ffs( + ni->itype.compressed.block_size) - 1; + } +} + +/* + * ntfs_attr_open - open an ntfs attribute for access + * @ni: open ntfs inode in which the ntfs attribute resides + * @type: attribute type + * @name: attribute name in little endian Unicode or AT_UNNAMED or NULL + * @name_len: length of attribute @name in Unicode characters (if @name given) + */ +int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type, + __le16 *name, u32 name_len) +{ + struct ntfs_attr_search_ctx *ctx; + __le16 *newname = NULL; + struct attr_record *a; + bool cs; + struct ntfs_inode *base_ni; + int err; + + ntfs_debug("Entering for inode %lld, attr 0x%x.\n", + (unsigned long long)ni->mft_no, type); + + if (!ni || !ni->vol) + return -EINVAL; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + if (name && name != AT_UNNAMED && name != I30) { + name = ntfs_ucsndup(name, name_len); + if (!name) { + err = -ENOMEM; + goto err_out; + } + newname = name; + } + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + err = -ENOMEM; + pr_err("%s: Failed to get search context\n", __func__); + goto err_out; + } + + err = ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx); + if (err) + goto put_err_out; + + a = ctx->attr; + + if (!name) { + if (a->name_length) { + name = ntfs_ucsndup((__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length); + if (!name) + goto put_err_out; + newname = name; + name_len = a->name_length; + } else { + name = AT_UNNAMED; + name_len = 0; + } + } + + __ntfs_attr_init(ni, type, name, name_len); + + /* + * Wipe the flags in case they are not zero for an attribute list + * attribute. Windows does not complain about invalid flags and chkdsk + * does not detect or fix them so we need to cope with it, too. + */ + if (type == AT_ATTRIBUTE_LIST) + a->flags = 0; + + if ((type == AT_DATA) && + (a->non_resident ? !a->data.non_resident.initialized_size : + !a->data.resident.value_length)) { + /* + * Define/redefine the compression state if stream is + * empty, based on the compression mark on parent + * directory (for unnamed data streams) or on current + * inode (for named data streams). The compression mark + * may change any time, the compression state can only + * change when stream is wiped out. + * + * Also prevent compression on NTFS version < 3.0 + * or cluster size > 4K or compression is disabled + */ + a->flags &= ~ATTR_COMPRESSION_MASK; + if (NInoCompressed(ni) + && (ni->vol->major_ver >= 3) + && NVolCompression(ni->vol) + && (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)) + a->flags |= ATTR_IS_COMPRESSED; + } + + cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE); + + if (ni->type == AT_DATA && ni->name == AT_UNNAMED && + ((!(a->flags & ATTR_IS_COMPRESSED) != !NInoCompressed(ni)) || + (!(a->flags & ATTR_IS_SPARSE) != !NInoSparse(ni)) || + (!(a->flags & ATTR_IS_ENCRYPTED) != !NInoEncrypted(ni)))) { + err = -EIO; + pr_err("Inode %lld has corrupt attribute flags (0x%x <> 0x%x)\n", + (unsigned long long)ni->mft_no, + a->flags, ni->flags); + goto put_err_out; + } + + if (a->non_resident) { + if (((a->flags & ATTR_COMPRESSION_MASK) || a->data.non_resident.compression_unit) && + (ni->vol->major_ver < 3)) { + err = -EIO; + pr_err("Compressed inode %lld not allowed on NTFS %d.%d\n", + (unsigned long long)ni->mft_no, + ni->vol->major_ver, + ni->vol->major_ver); + goto put_err_out; + } + + if ((a->flags & ATTR_IS_COMPRESSED) && !a->data.non_resident.compression_unit) { + err = -EIO; + pr_err("Compressed inode %lld attr 0x%x has no compression unit\n", + (unsigned long long)ni->mft_no, type); + goto put_err_out; + } + if ((a->flags & ATTR_COMPRESSION_MASK) && + (a->data.non_resident.compression_unit != STANDARD_COMPRESSION_UNIT)) { + err = -EIO; + pr_err("Compressed inode %lld attr 0x%lx has an unsupported compression unit %d\n", + (unsigned long long)ni->mft_no, + (long)le32_to_cpu(type), + (int)a->data.non_resident.compression_unit); + goto put_err_out; + } + ntfs_attr_init(ni, true, a->flags & ATTR_IS_COMPRESSED, + a->flags & ATTR_IS_ENCRYPTED, + a->flags & ATTR_IS_SPARSE, + le64_to_cpu(a->data.non_resident.allocated_size), + le64_to_cpu(a->data.non_resident.data_size), + le64_to_cpu(a->data.non_resident.initialized_size), + cs ? le64_to_cpu(a->data.non_resident.compressed_size) : 0, + cs ? a->data.non_resident.compression_unit : 0); + } else { + s64 l = le32_to_cpu(a->data.resident.value_length); + + ntfs_attr_init(ni, false, a->flags & ATTR_IS_COMPRESSED, + a->flags & ATTR_IS_ENCRYPTED, + a->flags & ATTR_IS_SPARSE, (l + 7) & ~7, l, l, + cs ? (l + 7) & ~7 : 0, 0); + } + ntfs_attr_put_search_ctx(ctx); +out: + ntfs_debug("\n"); + return err; + +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + kfree(newname); + goto out; +} + +/* + * ntfs_attr_close - free an ntfs attribute structure + * @ni: ntfs inode to free + * + * Release all memory associated with the ntfs attribute @na and then release + * @na itself. + */ +void ntfs_attr_close(struct ntfs_inode *ni) +{ + if (NInoNonResident(ni) && ni->runlist.rl) + kvfree(ni->runlist.rl); + /* Don't release if using an internal constant. */ + if (ni->name != AT_UNNAMED && ni->name != I30) + kfree(ni->name); +} + +/* + * ntfs_attr_map_whole_runlist - map the whole runlist of an ntfs attribute + * @ni: ntfs inode for which to map the runlist + * + * Map the whole runlist of the ntfs attribute @na. For an attribute made up + * of only one attribute extent this is the same as calling + * ntfs_map_runlist(ni, 0) but for an attribute with multiple extents this + * will map the runlist fragments from each of the extents thus giving access + * to the entirety of the disk allocation of an attribute. + */ +int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni) +{ + s64 next_vcn, last_vcn, highest_vcn; + struct ntfs_attr_search_ctx *ctx; + struct ntfs_volume *vol = ni->vol; + struct super_block *sb = vol->sb; + struct attr_record *a; + int err; + struct ntfs_inode *base_ni; + int not_mapped; + size_t new_rl_count; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", + (unsigned long long)ni->mft_no, ni->type); + + if (NInoFullyMapped(ni) && ni->runlist.rl) + return 0; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + /* Map all attribute extents one by one. */ + next_vcn = last_vcn = highest_vcn = 0; + a = NULL; + while (1) { + struct runlist_element *rl; + + not_mapped = 0; + if (ntfs_rl_vcn_to_lcn(ni->runlist.rl, next_vcn) == LCN_RL_NOT_MAPPED) + not_mapped = 1; + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, next_vcn, NULL, 0, ctx); + if (err) + break; + + a = ctx->attr; + + if (not_mapped) { + /* Decode the runlist. */ + rl = ntfs_mapping_pairs_decompress(ni->vol, a, &ni->runlist, + &new_rl_count); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + goto err_out; + } + ni->runlist.rl = rl; + ni->runlist.count = new_rl_count; + } + + /* Are we in the first extent? */ + if (!next_vcn) { + if (a->data.non_resident.lowest_vcn) { + err = -EIO; + ntfs_error(sb, + "First extent of inode %llu attribute has non-zero lowest_vcn", + (unsigned long long)ni->mft_no); + goto err_out; + } + /* Get the last vcn in the attribute. */ + last_vcn = ntfs_bytes_to_cluster(vol, + le64_to_cpu(a->data.non_resident.allocated_size)); + } + + /* Get the lowest vcn for the next extent. */ + highest_vcn = le64_to_cpu(a->data.non_resident.highest_vcn); + next_vcn = highest_vcn + 1; + + /* Only one extent or error, which we catch below. */ + if (next_vcn <= 0) { + err = -ENOENT; + break; + } + + /* Avoid endless loops due to corruption. */ + if (next_vcn < le64_to_cpu(a->data.non_resident.lowest_vcn)) { + err = -EIO; + ntfs_error(sb, "Inode %llu has corrupt attribute list", + (unsigned long long)ni->mft_no); + goto err_out; + } + } + if (!a) { + ntfs_error(sb, "Couldn't find attribute for runlist mapping"); + goto err_out; + } + if (not_mapped && highest_vcn && highest_vcn != last_vcn - 1) { + err = -EIO; + ntfs_error(sb, + "Failed to load full runlist: inode: %llu highest_vcn: 0x%llx last_vcn: 0x%llx", + (unsigned long long)ni->mft_no, + (long long)highest_vcn, (long long)last_vcn); + goto err_out; + } + ntfs_attr_put_search_ctx(ctx); + if (err == -ENOENT) { + NInoSetFullyMapped(ni); + return 0; + } + + return err; + +err_out: + ntfs_attr_put_search_ctx(ctx); + return err; +} + +/* + * ntfs_attr_record_move_to - move attribute record to target inode + * @ctx: attribute search context describing the attribute record + * @ni: opened ntfs inode to which move attribute record + */ +int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode *ni) +{ + struct ntfs_attr_search_ctx *nctx; + struct attr_record *a; + int err; + struct mft_record *ni_mrec; + struct super_block *sb; + + if (!ctx || !ctx->attr || !ctx->ntfs_ino || !ni) { + ntfs_debug("Invalid arguments passed.\n"); + return -EINVAL; + } + + sb = ni->vol->sb; + ntfs_debug("Entering for ctx->attr->type 0x%x, ctx->ntfs_ino->mft_no 0x%llx, ni->mft_no 0x%llx.\n", + (unsigned int) le32_to_cpu(ctx->attr->type), + (long long) ctx->ntfs_ino->mft_no, + (long long) ni->mft_no); + + if (ctx->ntfs_ino == ni) + return 0; + + if (!ctx->al_entry) { + ntfs_debug("Inode should contain attribute list to use this function.\n"); + return -EINVAL; + } + + /* Find place in MFT record where attribute will be moved. */ + a = ctx->attr; + nctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!nctx) { + ntfs_error(sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + /* + * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for + * attribute in @ni->mrec, not any extent inode in case if @ni is base + * file record. + */ + err = ntfs_attr_find(a->type, (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length, CASE_SENSITIVE, NULL, + 0, nctx); + if (!err) { + ntfs_debug("Attribute of such type, with same name already present in this MFT record.\n"); + err = -EEXIST; + goto put_err_out; + } + if (err != -ENOENT) { + ntfs_debug("Attribute lookup failed.\n"); + goto put_err_out; + } + + /* Make space and move attribute. */ + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto put_err_out; + } + + err = ntfs_make_room_for_attr(ni_mrec, (u8 *) nctx->attr, + le32_to_cpu(a->length)); + if (err) { + ntfs_debug("Couldn't make space for attribute.\n"); + unmap_mft_record(ni); + goto put_err_out; + } + memcpy(nctx->attr, a, le32_to_cpu(a->length)); + nctx->attr->instance = nctx->mrec->next_attr_instance; + nctx->mrec->next_attr_instance = + cpu_to_le16((le16_to_cpu(nctx->mrec->next_attr_instance) + 1) & 0xffff); + ntfs_attr_record_resize(ctx->mrec, a, 0); + mark_mft_record_dirty(ctx->ntfs_ino); + mark_mft_record_dirty(ni); + + /* Update attribute list. */ + ctx->al_entry->mft_reference = + MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number)); + ctx->al_entry->instance = nctx->attr->instance; + unmap_mft_record(ni); +put_err_out: + ntfs_attr_put_search_ctx(nctx); + return err; +} + +/* + * ntfs_attr_record_move_away - move away attribute record from it's mft record + * @ctx: attribute search context describing the attribute record + * @extra: minimum amount of free space in the new holder of record + */ +int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) +{ + struct ntfs_inode *base_ni, *ni = NULL; + struct mft_record *m; + int i, err; + struct super_block *sb; + + if (!ctx || !ctx->attr || !ctx->ntfs_ino || extra < 0) + return -EINVAL; + + ntfs_debug("Entering for attr 0x%x, inode %llu\n", + (unsigned int) le32_to_cpu(ctx->attr->type), + (unsigned long long)ctx->ntfs_ino->mft_no); + + if (ctx->ntfs_ino->nr_extents == -1) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + + sb = ctx->ntfs_ino->vol->sb; + if (!NInoAttrList(base_ni)) { + ntfs_error(sb, "Inode %llu has no attrlist", + (unsigned long long)base_ni->mft_no); + return -EINVAL; + } + + err = ntfs_inode_attach_all_extents(ctx->ntfs_ino); + if (err) { + ntfs_error(sb, "Couldn't attach extents, inode=%llu", + (unsigned long long)base_ni->mft_no); + return err; + } + + mutex_lock(&base_ni->extent_lock); + /* Walk through all extents and try to move attribute to them. */ + for (i = 0; i < base_ni->nr_extents; i++) { + ni = base_ni->ext.extent_ntfs_inos[i]; + + if (ctx->ntfs_ino->mft_no == ni->mft_no) + continue; + m = map_mft_record(ni); + if (IS_ERR(m)) { + ntfs_error(sb, "Can not map mft record for mft_no %lld", + (unsigned long long)ni->mft_no); + mutex_unlock(&base_ni->extent_lock); + return -EIO; + } + if (le32_to_cpu(m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use) < le32_to_cpu(ctx->attr->length) + extra) { + unmap_mft_record(ni); + continue; + } + unmap_mft_record(ni); + + /* + * ntfs_attr_record_move_to can fail if extent with other lowest + * s64 already present in inode we trying move record to. So, + * do not return error. + */ + if (!ntfs_attr_record_move_to(ctx, ni)) { + mutex_unlock(&base_ni->extent_lock); + return 0; + } + } + mutex_unlock(&base_ni->extent_lock); + + /* + * Failed to move attribute to one of the current extents, so allocate + * new extent and move attribute to it. + */ + ni = NULL; + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL); + if (err) { + ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err); + return err; + } + unmap_mft_record(ni); + + err = ntfs_attr_record_move_to(ctx, ni); + if (err) + ntfs_error(sb, "Couldn't move attribute to MFT record"); + + return err; +} + +/* + * If we are in the first extent, then set/clean sparse bit, + * update allocated and compressed size. + */ +static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, + struct mft_record *m, struct ntfs_attr_search_ctx *ctx) +{ + int sparse, err = 0; + struct ntfs_inode *base_ni; + struct super_block *sb = ni->vol->sb; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x\n", + (unsigned long long)ni->mft_no, ni->type); + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + if (a->data.non_resident.lowest_vcn) + goto out; + + a->data.non_resident.allocated_size = cpu_to_le64(ni->allocated_size); + + sparse = ntfs_rl_sparse(ni->runlist.rl); + if (sparse < 0) { + err = -EIO; + goto out; + } + + /* Attribute become sparse. */ + if (sparse && !(a->flags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) { + /* + * Move attribute to another mft record, if attribute is too + * small to add compressed_size field to it and we have no + * free space in the current mft record. + */ + if ((le32_to_cpu(a->length) - + le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) && + !(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) { + + if (!NInoAttrList(base_ni)) { + err = ntfs_inode_add_attrlist(base_ni); + if (err) + goto out; + err = -EAGAIN; + goto out; + } + err = ntfs_attr_record_move_away(ctx, 8); + if (err) { + ntfs_error(sb, "Failed to move attribute"); + goto out; + } + + err = ntfs_attrlist_update(base_ni); + if (err) + goto out; + err = -EAGAIN; + goto out; + } + if (!(le32_to_cpu(a->length) - + le16_to_cpu(a->data.non_resident.mapping_pairs_offset))) { + err = -EIO; + ntfs_error(sb, "Mapping pairs space is 0"); + goto out; + } + + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + a->flags |= ATTR_IS_SPARSE; + a->data.non_resident.compression_unit = 0; + + memmove((u8 *)a + le16_to_cpu(a->name_offset) + 8, + (u8 *)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(__le16)); + + a->name_offset = cpu_to_le16(le16_to_cpu(a->name_offset) + 8); + + a->data.non_resident.mapping_pairs_offset = + cpu_to_le16(le16_to_cpu(a->data.non_resident.mapping_pairs_offset) + 8); + } + + /* Attribute no longer sparse. */ + if (!sparse && (a->flags & ATTR_IS_SPARSE) && + !(a->flags & ATTR_IS_COMPRESSED)) { + NInoClearSparse(ni); + ni->flags &= ~FILE_ATTR_SPARSE_FILE; + a->flags &= ~ATTR_IS_SPARSE; + a->data.non_resident.compression_unit = 0; + + memmove((u8 *)a + le16_to_cpu(a->name_offset) - 8, + (u8 *)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(__le16)); + + if (le16_to_cpu(a->name_offset) >= 8) + a->name_offset = cpu_to_le16(le16_to_cpu(a->name_offset) - 8); + + a->data.non_resident.mapping_pairs_offset = + cpu_to_le16(le16_to_cpu(a->data.non_resident.mapping_pairs_offset) - 8); + } + + /* Update compressed size if required. */ + if (NInoFullyMapped(ni) && (sparse || NInoCompressed(ni))) { + s64 new_compr_size; + + new_compr_size = ntfs_rl_get_compressed_size(ni->vol, ni->runlist.rl); + if (new_compr_size < 0) { + err = new_compr_size; + goto out; + } + + ni->itype.compressed.size = new_compr_size; + a->data.non_resident.compressed_size = cpu_to_le64(new_compr_size); + } + + if (NInoSparse(ni) || NInoCompressed(ni)) + VFS_I(base_ni)->i_blocks = ni->itype.compressed.size >> 9; + else + VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; + /* + * Set FILE_NAME dirty flag, to update sparse bit and + * allocated size in the index. + */ + if (ni->type == AT_DATA && ni->name == AT_UNNAMED) + NInoSetFileNameDirty(ni); +out: + return err; +} + +#define NTFS_VCN_DELETE_MARK -2 +/* + * ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute + * @ni: non-resident ntfs inode for which we need update + * @from_vcn: update runlist starting this VCN + * + * Build mapping pairs from @na->rl and write them to the disk. Also, this + * function updates sparse bit, allocated and compressed size (allocates/frees + * space for this field if required). + * + * @na->allocated_size should be set to correct value for the new runlist before + * call to this function. Vice-versa @na->compressed_size will be calculated and + * set to correct value during this function. + */ +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +{ + struct ntfs_attr_search_ctx *ctx; + struct ntfs_inode *base_ni; + struct mft_record *m; + struct attr_record *a; + s64 stop_vcn; + int err = 0, mp_size, cur_max_mp_size, exp_max_mp_size; + bool finished_build; + bool first_updated = false; + struct super_block *sb; + struct runlist_element *start_rl; + unsigned int de_cluster_count = 0; + +retry: + if (!ni || !ni->runlist.rl) + return -EINVAL; + + ntfs_debug("Entering for inode %llu, attr 0x%x\n", + (unsigned long long)ni->mft_no, ni->type); + + sb = ni->vol->sb; + if (!NInoNonResident(ni)) { + ntfs_error(sb, "%s: resident attribute", __func__); + return -EINVAL; + } + + if (ni->nr_extents == -1) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + /* Fill attribute records with new mapping pairs. */ + stop_vcn = 0; + finished_build = false; + start_rl = ni->runlist.rl; + while (!(err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, from_vcn, NULL, 0, ctx))) { + unsigned int de_cnt = 0; + + a = ctx->attr; + m = ctx->mrec; + if (!a->data.non_resident.lowest_vcn) + first_updated = true; + + /* + * If runlist is updating not from the beginning, then set + * @stop_vcn properly, i.e. to the lowest vcn of record that + * contain @from_vcn. Also we do not need @from_vcn anymore, + * set it to 0 to make ntfs_attr_lookup enumerate attributes. + */ + if (from_vcn) { + s64 first_lcn; + + stop_vcn = le64_to_cpu(a->data.non_resident.lowest_vcn); + from_vcn = 0; + /* + * Check whether the first run we need to update is + * the last run in runlist, if so, then deallocate + * all attrubute extents starting this one. + */ + first_lcn = ntfs_rl_vcn_to_lcn(ni->runlist.rl, stop_vcn); + if (first_lcn == LCN_EINVAL) { + err = -EIO; + ntfs_error(sb, "Bad runlist"); + goto put_err_out; + } + if (first_lcn == LCN_ENOENT || + first_lcn == LCN_RL_NOT_MAPPED) + finished_build = true; + } + + /* + * Check whether we finished mapping pairs build, if so mark + * extent as need to delete (by setting highest vcn to + * NTFS_VCN_DELETE_MARK (-2), we shall check it later and + * delete extent) and continue search. + */ + if (finished_build) { + ntfs_debug("Mark attr 0x%x for delete in inode 0x%llx.\n", + (unsigned int)le32_to_cpu(a->type), ctx->ntfs_ino->mft_no); + a->data.non_resident.highest_vcn = cpu_to_le64(NTFS_VCN_DELETE_MARK); + mark_mft_record_dirty(ctx->ntfs_ino); + continue; + } + + err = ntfs_attr_update_meta(a, ni, m, ctx); + if (err < 0) { + if (err == -EAGAIN) { + ntfs_attr_put_search_ctx(ctx); + goto retry; + } + goto put_err_out; + } + + /* + * Determine maximum possible length of mapping pairs, + * if we shall *not* expand space for mapping pairs. + */ + cur_max_mp_size = le32_to_cpu(a->length) - + le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + /* + * Determine maximum possible length of mapping pairs in the + * current mft record, if we shall expand space for mapping + * pairs. + */ + exp_max_mp_size = le32_to_cpu(m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use) + cur_max_mp_size; + + /* Get the size for the rest of mapping pairs array. */ + mp_size = ntfs_get_size_for_mapping_pairs(ni->vol, start_rl, + stop_vcn, -1, exp_max_mp_size); + if (mp_size <= 0) { + err = mp_size; + ntfs_error(sb, "%s: get MP size failed", __func__); + goto put_err_out; + } + /* Test mapping pairs for fitting in the current mft record. */ + if (mp_size > exp_max_mp_size) { + /* + * Mapping pairs of $ATTRIBUTE_LIST attribute must fit + * in the base mft record. Try to move out other + * attributes and try again. + */ + if (ni->type == AT_ATTRIBUTE_LIST) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_free_space(base_ni, mp_size - + cur_max_mp_size)) { + ntfs_debug("Attribute list is too big. Defragment the volume\n"); + return -ENOSPC; + } + if (ntfs_attrlist_update(base_ni)) + return -EIO; + goto retry; + } + + /* Add attribute list if it isn't present, and retry. */ + if (!NInoAttrList(base_ni)) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_add_attrlist(base_ni)) { + ntfs_error(sb, "Can not add attrlist"); + return -EIO; + } + goto retry; + } + + /* + * Set mapping pairs size to maximum possible for this + * mft record. We shall write the rest of mapping pairs + * to another MFT records. + */ + mp_size = exp_max_mp_size; + } + + /* Change space for mapping pairs if we need it. */ + if (((mp_size + 7) & ~7) != cur_max_mp_size) { + if (ntfs_attr_record_resize(m, a, + le16_to_cpu(a->data.non_resident.mapping_pairs_offset) + + mp_size)) { + err = -EIO; + ntfs_error(sb, "Failed to resize attribute"); + goto put_err_out; + } + } + + /* Update lowest vcn. */ + a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn); + mark_mft_record_dirty(ctx->ntfs_ino); + if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) && + ctx->attr->type != AT_ATTRIBUTE_LIST) { + ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn); + err = ntfs_attrlist_update(base_ni); + if (err) + goto put_err_out; + } + + /* + * Generate the new mapping pairs array directly into the + * correct destination, i.e. the attribute record itself. + */ + err = ntfs_mapping_pairs_build(ni->vol, + (u8 *)a + le16_to_cpu(a->data.non_resident.mapping_pairs_offset), + mp_size, start_rl, stop_vcn, -1, &stop_vcn, &start_rl, &de_cnt); + if (!err) + finished_build = true; + if (!finished_build && err != -ENOSPC) { + ntfs_error(sb, "Failed to build mapping pairs"); + goto put_err_out; + } + a->data.non_resident.highest_vcn = cpu_to_le64(stop_vcn - 1); + mark_mft_record_dirty(ctx->ntfs_ino); + de_cluster_count += de_cnt; + } + + /* Check whether error occurred. */ + if (err && err != -ENOENT) { + ntfs_error(sb, "%s: Attribute lookup failed", __func__); + goto put_err_out; + } + + /* + * If the base extent was skipped in the above process, + * we still may have to update the sizes. + */ + if (!first_updated) { + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (!err) { + a = ctx->attr; + a->data.non_resident.allocated_size = cpu_to_le64(ni->allocated_size); + if (NInoCompressed(ni) || NInoSparse(ni)) + a->data.non_resident.compressed_size = + cpu_to_le64(ni->itype.compressed.size); + /* Updating sizes taints the extent holding the attr */ + if (ni->type == AT_DATA && ni->name == AT_UNNAMED) + NInoSetFileNameDirty(ni); + mark_mft_record_dirty(ctx->ntfs_ino); + } else { + ntfs_error(sb, "Failed to update sizes in base extent\n"); + goto put_err_out; + } + } + + /* Deallocate not used attribute extents and return with success. */ + if (finished_build) { + ntfs_attr_reinit_search_ctx(ctx); + ntfs_debug("Deallocate marked extents.\n"); + while (!(err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx))) { + if (le64_to_cpu(ctx->attr->data.non_resident.highest_vcn) != + NTFS_VCN_DELETE_MARK) + continue; + /* Remove unused attribute record. */ + err = ntfs_attr_record_rm(ctx); + if (err) { + ntfs_error(sb, "Could not remove unused attr"); + goto put_err_out; + } + ntfs_attr_reinit_search_ctx(ctx); + } + if (err && err != -ENOENT) { + ntfs_error(sb, "%s: Attr lookup failed", __func__); + goto put_err_out; + } + ntfs_debug("Deallocate done.\n"); + ntfs_attr_put_search_ctx(ctx); + goto out; + } + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + + /* Allocate new MFT records for the rest of mapping pairs. */ + while (1) { + struct ntfs_inode *ext_ni = NULL; + unsigned int de_cnt = 0; + + /* Allocate new mft record. */ + err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL); + if (err) { + ntfs_error(sb, "Failed to allocate extent record"); + goto put_err_out; + } + unmap_mft_record(ext_ni); + + m = map_mft_record(ext_ni); + if (IS_ERR(m)) { + ntfs_error(sb, "Could not map new MFT record"); + if (ntfs_mft_record_free(ni->vol, ext_ni)) + ntfs_error(sb, "Could not free MFT record"); + ntfs_inode_close(ext_ni); + err = -ENOMEM; + ext_ni = NULL; + goto put_err_out; + } + /* + * If mapping size exceed available space, set them to + * possible maximum. + */ + cur_max_mp_size = le32_to_cpu(m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use) - + (sizeof(struct attr_record) + + ((NInoCompressed(ni) || NInoSparse(ni)) ? + sizeof(a->data.non_resident.compressed_size) : 0)) - + ((sizeof(__le16) * ni->name_len + 7) & ~7); + + /* Calculate size of rest mapping pairs. */ + mp_size = ntfs_get_size_for_mapping_pairs(ni->vol, + start_rl, stop_vcn, -1, cur_max_mp_size); + if (mp_size <= 0) { + unmap_mft_record(ext_ni); + ntfs_inode_close(ext_ni); + err = mp_size; + ntfs_error(sb, "%s: get mp size failed", __func__); + goto put_err_out; + } + + if (mp_size > cur_max_mp_size) + mp_size = cur_max_mp_size; + /* Add attribute extent to new record. */ + err = ntfs_non_resident_attr_record_add(ext_ni, ni->type, + ni->name, ni->name_len, stop_vcn, mp_size, 0); + if (err < 0) { + ntfs_error(sb, "Could not add attribute extent"); + unmap_mft_record(ext_ni); + if (ntfs_mft_record_free(ni->vol, ext_ni)) + ntfs_error(sb, "Could not free MFT record"); + ntfs_inode_close(ext_ni); + goto put_err_out; + } + a = (struct attr_record *)((u8 *)m + err); + + err = ntfs_mapping_pairs_build(ni->vol, (u8 *)a + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset), + mp_size, start_rl, stop_vcn, -1, &stop_vcn, &start_rl, + &de_cnt); + if (err < 0 && err != -ENOSPC) { + ntfs_error(sb, "Failed to build MP"); + unmap_mft_record(ext_ni); + if (ntfs_mft_record_free(ni->vol, ext_ni)) + ntfs_error(sb, "Couldn't free MFT record"); + goto put_err_out; + } + a->data.non_resident.highest_vcn = cpu_to_le64(stop_vcn - 1); + mark_mft_record_dirty(ext_ni); + unmap_mft_record(ext_ni); + + de_cluster_count += de_cnt; + /* All mapping pairs has been written. */ + if (!err) + break; + } +out: + if (from_vcn == 0) + ni->i_dealloc_clusters = de_cluster_count; + return 0; + +put_err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + return err; +} + +/* + * ntfs_attr_make_resident - convert a non-resident to a resident attribute + * @ni: open ntfs attribute to make resident + * @ctx: ntfs search context describing the attribute + * + * Convert a non-resident ntfs attribute to a resident one. + */ +static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_volume *vol = ni->vol; + struct super_block *sb = vol->sb; + struct attr_record *a = ctx->attr; + int name_ofs, val_ofs, err; + s64 arec_size; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", + (unsigned long long)ni->mft_no, ni->type); + + /* Should be called for the first extent of the attribute. */ + if (le64_to_cpu(a->data.non_resident.lowest_vcn)) { + ntfs_debug("Eeek! Should be called for the first extent of the attribute. Aborting...\n"); + return -EINVAL; + } + + /* Some preliminary sanity checking. */ + if (!NInoNonResident(ni)) { + ntfs_debug("Eeek! Trying to make resident attribute resident. Aborting...\n"); + return -EINVAL; + } + + /* Make sure this is not $MFT/$BITMAP or Windows will not boot! */ + if (ni->type == AT_BITMAP && ni->mft_no == FILE_MFT) + return -EPERM; + + /* Check that the attribute is allowed to be resident. */ + err = ntfs_attr_can_be_resident(vol, ni->type); + if (err) + return err; + + if (NInoCompressed(ni) || NInoEncrypted(ni)) { + ntfs_debug("Making compressed or encrypted files resident is not implemented yet.\n"); + return -EOPNOTSUPP; + } + + /* Work out offsets into and size of the resident attribute. */ + name_ofs = 24; /* = sizeof(resident_struct attr_record); */ + val_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7; + arec_size = (val_ofs + ni->data_size + 7) & ~7; + + /* Sanity check the size before we start modifying the attribute. */ + if (le32_to_cpu(ctx->mrec->bytes_in_use) - le32_to_cpu(a->length) + + arec_size > le32_to_cpu(ctx->mrec->bytes_allocated)) { + ntfs_debug("Not enough space to make attribute resident\n"); + return -ENOSPC; + } + + /* Read and cache the whole runlist if not already done. */ + err = ntfs_attr_map_whole_runlist(ni); + if (err) + return err; + + /* Move the attribute name if it exists and update the offset. */ + if (a->name_length) { + memmove((u8 *)a + name_ofs, (u8 *)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(__le16)); + } + a->name_offset = cpu_to_le16(name_ofs); + + /* Resize the resident part of the attribute record. */ + if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) { + /* + * Bug, because ntfs_attr_record_resize should not fail (we + * already checked that attribute fits MFT record). + */ + ntfs_error(ctx->ntfs_ino->vol->sb, "BUG! Failed to resize attribute record. "); + return -EIO; + } + + /* Convert the attribute record to describe a resident attribute. */ + a->non_resident = 0; + a->flags = 0; + a->data.resident.value_length = cpu_to_le32(ni->data_size); + a->data.resident.value_offset = cpu_to_le16(val_ofs); + /* + * File names cannot be non-resident so we would never see this here + * but at least it serves as a reminder that there may be attributes + * for which we do need to set this flag. (AIA) + */ + if (a->type == AT_FILE_NAME) + a->data.resident.flags = RESIDENT_ATTR_IS_INDEXED; + else + a->data.resident.flags = 0; + a->data.resident.reserved = 0; + + /* + * Deallocate clusters from the runlist. + * + * NOTE: We can use ntfs_cluster_free() because we have already mapped + * the whole run list and thus it doesn't matter that the attribute + * record is in a transiently corrupted state at this moment in time. + */ + err = ntfs_cluster_free(ni, 0, -1, ctx); + if (err) { + ntfs_error(sb, "Eeek! Failed to release allocated clusters"); + ntfs_debug("Ignoring error and leaving behind wasted clusters.\n"); + } + + /* Throw away the now unused runlist. */ + kvfree(ni->runlist.rl); + ni->runlist.rl = NULL; + ni->runlist.count = 0; + /* Update in-memory struct ntfs_attr. */ + NInoClearNonResident(ni); + NInoClearCompressed(ni); + ni->flags &= ~FILE_ATTR_COMPRESSED; + NInoClearSparse(ni); + ni->flags &= ~FILE_ATTR_SPARSE_FILE; + NInoClearEncrypted(ni); + ni->flags &= ~FILE_ATTR_ENCRYPTED; + ni->initialized_size = ni->data_size; + ni->allocated_size = ni->itype.compressed.size = (ni->data_size + 7) & ~7; + ni->itype.compressed.block_size = 0; + ni->itype.compressed.block_size_bits = ni->itype.compressed.block_clusters = 0; + return 0; +} + +/* + * ntfs_non_resident_attr_shrink - shrink a non-resident, open ntfs attribute + * @ni: non-resident ntfs attribute to shrink + * @newsize: new size (in bytes) to which to shrink the attribute + * + * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. + */ +static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize) +{ + struct ntfs_volume *vol; + struct ntfs_attr_search_ctx *ctx; + s64 first_free_vcn; + s64 nr_freed_clusters; + int err; + struct ntfs_inode *base_ni; + + ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n", + (unsigned long long)ni->mft_no, ni->type, (long long)newsize); + + vol = ni->vol; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + /* + * Check the attribute type and the corresponding minimum size + * against @newsize and fail if @newsize is too small. + */ + err = ntfs_attr_size_bounds_check(vol, ni->type, newsize); + if (err) { + if (err == -ERANGE) + ntfs_debug("Eeek! Size bounds check failed. Aborting...\n"); + else if (err == -ENOENT) + err = -EIO; + return err; + } + + /* The first cluster outside the new allocation. */ + if (NInoCompressed(ni)) + /* + * For compressed files we must keep full compressions blocks, + * but currently we do not decompress/recompress the last + * block to truncate the data, so we may leave more allocated + * clusters than really needed. + */ + first_free_vcn = ntfs_bytes_to_cluster(vol, + ((newsize - 1) | (ni->itype.compressed.block_size - 1)) + 1); + else + first_free_vcn = + ntfs_bytes_to_cluster(vol, newsize + vol->cluster_size - 1); + + if (first_free_vcn < 0) + return -EINVAL; + /* + * Compare the new allocation with the old one and only deallocate + * clusters if there is a change. + */ + if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) { + struct ntfs_attr_search_ctx *ctx; + + err = ntfs_attr_map_whole_runlist(ni); + if (err) { + ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n"); + return err; + } + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(vol->sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + /* Deallocate all clusters starting with the first free one. */ + nr_freed_clusters = ntfs_cluster_free(ni, first_free_vcn, -1, ctx); + if (nr_freed_clusters < 0) { + ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n"); + ntfs_attr_put_search_ctx(ctx); + return (int)nr_freed_clusters; + } + ntfs_attr_put_search_ctx(ctx); + + /* Truncate the runlist itself. */ + if (ntfs_rl_truncate_nolock(vol, &ni->runlist, first_free_vcn)) { + /* + * Failed to truncate the runlist, so just throw it + * away, it will be mapped afresh on next use. + */ + kvfree(ni->runlist.rl); + ni->runlist.rl = NULL; + ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n"); + return -EIO; + } + + /* Prepare to mapping pairs update. */ + ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn); + + if (NInoSparse(ni) || NInoCompressed(ni)) { + if (nr_freed_clusters) { + ni->itype.compressed.size -= + ntfs_cluster_to_bytes(vol, nr_freed_clusters); + VFS_I(base_ni)->i_blocks = ni->itype.compressed.size >> 9; + } + } else + VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; + + /* Write mapping pairs for new runlist. */ + err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/); + if (err) { + ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n"); + return err; + } + } + + /* Get the first attribute record. */ + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + ntfs_error(vol->sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (err) { + if (err == -ENOENT) + err = -EIO; + ntfs_debug("Eeek! Lookup of first attribute extent failed. Leaving inconstant metadata.\n"); + goto put_err_out; + } + + /* Update data and initialized size. */ + ni->data_size = newsize; + ctx->attr->data.non_resident.data_size = cpu_to_le64(newsize); + if (newsize < ni->initialized_size) { + ni->initialized_size = newsize; + ctx->attr->data.non_resident.initialized_size = cpu_to_le64(newsize); + } + /* Update data size in the index. */ + if (ni->type == AT_DATA && ni->name == AT_UNNAMED) + NInoSetFileNameDirty(ni); + + /* If the attribute now has zero size, make it resident. */ + if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) { + err = ntfs_attr_make_resident(ni, ctx); + if (err) { + /* If couldn't make resident, just continue. */ + if (err != -EPERM) + ntfs_error(ni->vol->sb, + "Failed to make attribute resident. Leaving as is...\n"); + } + } + + /* Set the inode dirty so it is written out later. */ + mark_mft_record_dirty(ctx->ntfs_ino); + /* Done! */ + ntfs_attr_put_search_ctx(ctx); + return 0; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + return err; +} + +/* + * ntfs_non_resident_attr_expand - expand a non-resident, open ntfs attribute + * @ni: non-resident ntfs attribute to expand + * @prealloc_size: preallocation size (in bytes) to which to expand the attribute + * @newsize: new size (in bytes) to which to expand the attribute + * @holes: how to create a hole if expanding + * @need_lock: whether mrec lock is needed or not + * + * Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes, + * by allocating new clusters. + */ +static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, unsigned int holes, bool need_lock) +{ + s64 lcn_seek_from; + s64 first_free_vcn; + struct ntfs_volume *vol; + struct ntfs_attr_search_ctx *ctx = NULL; + struct runlist_element *rl, *rln; + s64 org_alloc_size, org_compressed_size; + int err, err2; + struct ntfs_inode *base_ni; + struct super_block *sb = ni->vol->sb; + size_t new_rl_count; + + ntfs_debug("Inode 0x%llx, attr 0x%x, new size %lld old size %lld\n", + (unsigned long long)ni->mft_no, ni->type, + (long long)newsize, (long long)ni->data_size); + + vol = ni->vol; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + /* + * Check the attribute type and the corresponding maximum size + * against @newsize and fail if @newsize is too big. + */ + err = ntfs_attr_size_bounds_check(vol, ni->type, newsize); + if (err < 0) { + ntfs_error(sb, "%s: bounds check failed", __func__); + return err; + } + + /* Save for future use. */ + org_alloc_size = ni->allocated_size; + org_compressed_size = ni->itype.compressed.size; + + /* The first cluster outside the new allocation. */ + if (prealloc_size) + first_free_vcn = + ntfs_bytes_to_cluster(vol, prealloc_size + vol->cluster_size - 1); + else + first_free_vcn = + ntfs_bytes_to_cluster(vol, newsize + vol->cluster_size - 1); + if (first_free_vcn < 0) + return -EFBIG; + + /* + * Compare the new allocation with the old one and only allocate + * clusters if there is a change. + */ + if (ntfs_bytes_to_cluster(vol, ni->allocated_size) < first_free_vcn) { + err = ntfs_attr_map_whole_runlist(ni); + if (err) { + ntfs_error(sb, "ntfs_attr_map_whole_runlist failed"); + return err; + } + + /* + * If we extend $DATA attribute on NTFS 3+ volume, we can add + * sparse runs instead of real allocation of clusters. + */ + if ((ni->type == AT_DATA && (vol->major_ver >= 3 || !NInoSparseDisabled(ni))) && + (holes != HOLES_NO)) { + if (NInoCompressed(ni)) { + int last = 0, i = 0; + s64 alloc_size; + u64 more_entries = round_up(first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size), + ni->itype.compressed.block_clusters); + + do_div(more_entries, ni->itype.compressed.block_clusters); + + while (ni->runlist.rl[last].length) + last++; + + rl = ntfs_rl_realloc(ni->runlist.rl, last + 1, + last + more_entries + 1); + if (IS_ERR(rl)) { + err = -ENOMEM; + goto put_err_out; + } + + alloc_size = ni->allocated_size; + while (i++ < more_entries) { + rl[last].vcn = ntfs_bytes_to_cluster(vol, + round_up(alloc_size, vol->cluster_size)); + rl[last].length = ni->itype.compressed.block_clusters - + (rl[last].vcn & + (ni->itype.compressed.block_clusters - 1)); + rl[last].lcn = LCN_HOLE; + last++; + alloc_size += ni->itype.compressed.block_size; + } + + rl[last].vcn = first_free_vcn; + rl[last].lcn = LCN_ENOENT; + rl[last].length = 0; + + ni->runlist.rl = rl; + ni->runlist.count += more_entries; + } else { + rl = kmalloc(sizeof(struct runlist_element) * 2, GFP_NOFS); + if (!rl) { + err = -ENOMEM; + goto put_err_out; + } + + rl[0].vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); + rl[0].lcn = LCN_HOLE; + rl[0].length = first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size); + rl[1].vcn = first_free_vcn; + rl[1].lcn = LCN_ENOENT; + rl[1].length = 0; + } + } else { + /* + * Determine first after last LCN of attribute. + * We will start seek clusters from this LCN to avoid + * fragmentation. If there are no valid LCNs in the + * attribute let the cluster allocator choose the + * starting LCN. + */ + lcn_seek_from = -1; + if (ni->runlist.rl->length) { + /* Seek to the last run list element. */ + for (rl = ni->runlist.rl; (rl + 1)->length; rl++) + ; + /* + * If the last LCN is a hole or similar seek + * back to last valid LCN. + */ + while (rl->lcn < 0 && rl != ni->runlist.rl) + rl--; + /* + * Only set lcn_seek_from it the LCN is valid. + */ + if (rl->lcn >= 0) + lcn_seek_from = rl->lcn + rl->length; + } + + rl = ntfs_cluster_alloc(vol, + ntfs_bytes_to_cluster(vol, ni->allocated_size), + first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size), + lcn_seek_from, DATA_ZONE, false, false, false); + if (IS_ERR(rl)) { + ntfs_debug("Cluster allocation failed (%lld)", + (long long)first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size)); + return PTR_ERR(rl); + } + } + + if (!NInoCompressed(ni)) { + /* Append new clusters to attribute runlist. */ + rln = ntfs_runlists_merge(&ni->runlist, rl, 0, &new_rl_count); + if (IS_ERR(rln)) { + /* Failed, free just allocated clusters. */ + ntfs_error(sb, "Run list merge failed"); + ntfs_cluster_free_from_rl(vol, rl); + kvfree(rl); + return -EIO; + } + ni->runlist.rl = rln; + ni->runlist.count = new_rl_count; + } + + /* Prepare to mapping pairs update. */ + ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn); + err = ntfs_attr_update_mapping_pairs(ni, 0); + if (err) { + ntfs_debug("Mapping pairs update failed"); + goto rollback; + } + } + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + err = -ENOMEM; + if (ni->allocated_size == org_alloc_size) + return err; + goto rollback; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (err) { + if (err == -ENOENT) + err = -EIO; + if (ni->allocated_size != org_alloc_size) + goto rollback; + goto put_err_out; + } + + /* Update data size. */ + ni->data_size = newsize; + ctx->attr->data.non_resident.data_size = cpu_to_le64(newsize); + /* Update data size in the index. */ + if (ni->type == AT_DATA && ni->name == AT_UNNAMED) + NInoSetFileNameDirty(ni); + /* Set the inode dirty so it is written out later. */ + mark_mft_record_dirty(ctx->ntfs_ino); + /* Done! */ + ntfs_attr_put_search_ctx(ctx); + return 0; +rollback: + /* Free allocated clusters. */ + err2 = ntfs_cluster_free(ni, ntfs_bytes_to_cluster(vol, org_alloc_size), + -1, ctx); + if (err2) + ntfs_debug("Leaking clusters"); + + /* Now, truncate the runlist itself. */ + if (need_lock) + down_write(&ni->runlist.lock); + err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist, + ntfs_bytes_to_cluster(vol, org_alloc_size)); + if (need_lock) + up_write(&ni->runlist.lock); + if (err2) { + /* + * Failed to truncate the runlist, so just throw it away, it + * will be mapped afresh on next use. + */ + kvfree(ni->runlist.rl); + ni->runlist.rl = NULL; + ntfs_error(sb, "Couldn't truncate runlist. Rollback failed"); + } else { + /* Prepare to mapping pairs update. */ + ni->allocated_size = org_alloc_size; + /* Restore mapping pairs. */ + if (need_lock) + down_read(&ni->runlist.lock); + if (ntfs_attr_update_mapping_pairs(ni, 0)) + ntfs_error(sb, "Failed to restore old mapping pairs"); + if (need_lock) + up_read(&ni->runlist.lock); + + if (NInoSparse(ni) || NInoCompressed(ni)) { + ni->itype.compressed.size = org_compressed_size; + VFS_I(base_ni)->i_blocks = ni->itype.compressed.size >> 9; + } else + VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + return err; +put_err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + return err; +} + +/* + * ntfs_resident_attr_resize - resize a resident, open ntfs attribute + * @attr_ni: resident ntfs inode to resize + * @newsize: new size (in bytes) to which to resize the attribute + * @prealloc_size: preallocation size (in bytes) to which to resize the attribute + * @holes: flags indicating how to handle holes + * + * Change the size of a resident, open ntfs attribute @na to @newsize bytes. + */ +static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsize, + const s64 prealloc_size, unsigned int holes) +{ + struct ntfs_attr_search_ctx *ctx; + struct ntfs_volume *vol = attr_ni->vol; + struct super_block *sb = vol->sb; + int err = -EIO; + struct ntfs_inode *base_ni, *ext_ni = NULL; + +attr_resize_again: + ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n", + (unsigned long long)attr_ni->mft_no, attr_ni->type, + (long long)newsize); + + if (NInoAttr(attr_ni)) + base_ni = attr_ni->ext.base_ntfs_ino; + else + base_ni = attr_ni; + + /* Get the attribute record that needs modification. */ + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + err = ntfs_attr_lookup(attr_ni->type, attr_ni->name, attr_ni->name_len, + 0, 0, NULL, 0, ctx); + if (err) { + ntfs_error(sb, "ntfs_attr_lookup failed"); + goto put_err_out; + } + + /* + * Check the attribute type and the corresponding minimum and maximum + * sizes against @newsize and fail if @newsize is out of bounds. + */ + err = ntfs_attr_size_bounds_check(vol, attr_ni->type, newsize); + if (err) { + if (err == -ENOENT) + err = -EIO; + ntfs_debug("%s: bounds check failed", __func__); + goto put_err_out; + } + /* + * If @newsize is bigger than the mft record we need to make the + * attribute non-resident if the attribute type supports it. If it is + * smaller we can go ahead and attempt the resize. + */ + if (newsize < vol->mft_record_size) { + /* Perform the resize of the attribute record. */ + err = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, + newsize); + if (!err) { + /* Update attribute size everywhere. */ + attr_ni->data_size = attr_ni->initialized_size = newsize; + attr_ni->allocated_size = (newsize + 7) & ~7; + if (NInoCompressed(attr_ni) || NInoSparse(attr_ni)) + attr_ni->itype.compressed.size = attr_ni->allocated_size; + if (attr_ni->type == AT_DATA && attr_ni->name == AT_UNNAMED) + NInoSetFileNameDirty(attr_ni); + goto resize_done; + } + + /* Prefer AT_INDEX_ALLOCATION instead of AT_ATTRIBUTE_LIST */ + if (err == -ENOSPC && ctx->attr->type == AT_INDEX_ROOT) + goto put_err_out; + + } + /* There is not enough space in the mft record to perform the resize. */ + + /* Make the attribute non-resident if possible. */ + err = ntfs_attr_make_non_resident(attr_ni, + le32_to_cpu(ctx->attr->data.resident.value_length)); + if (!err) { + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + /* Resize non-resident attribute */ + return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true); + } else if (err != -ENOSPC && err != -EPERM) { + ntfs_error(sb, "Failed to make attribute non-resident"); + goto put_err_out; + } + + /* Try to make other attributes non-resident and retry each time. */ + ntfs_attr_reinit_search_ctx(ctx); + while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) { + struct inode *tvi; + struct attr_record *a; + + a = ctx->attr; + if (a->non_resident || a->type == AT_ATTRIBUTE_LIST) + continue; + + if (ntfs_attr_can_be_non_resident(vol, a->type)) + continue; + + /* + * Check out whether convert is reasonable. Assume that mapping + * pairs will take 8 bytes. + */ + if (le32_to_cpu(a->length) <= (sizeof(struct attr_record) - sizeof(s64)) + + ((a->name_length * sizeof(__le16) + 7) & ~7) + 8) + continue; + + if (a->type == AT_DATA) + tvi = ntfs_iget(sb, base_ni->mft_no); + else + tvi = ntfs_attr_iget(VFS_I(base_ni), a->type, + (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length); + if (IS_ERR(tvi)) { + ntfs_error(sb, "Couldn't open attribute"); + continue; + } + + if (ntfs_attr_make_non_resident(NTFS_I(tvi), + le32_to_cpu(ctx->attr->data.resident.value_length))) { + iput(tvi); + continue; + } + + mark_mft_record_dirty(ctx->ntfs_ino); + iput(tvi); + ntfs_attr_put_search_ctx(ctx); + goto attr_resize_again; + } + + /* Check whether error occurred. */ + if (err != -ENOENT) { + ntfs_error(sb, "%s: Attribute lookup failed 1", __func__); + goto put_err_out; + } + + /* + * The standard information and attribute list attributes can't be + * moved out from the base MFT record, so try to move out others. + */ + if (attr_ni->type == AT_STANDARD_INFORMATION || + attr_ni->type == AT_ATTRIBUTE_LIST) { + ntfs_attr_put_search_ctx(ctx); + + if (!NInoAttrList(base_ni)) { + err = ntfs_inode_add_attrlist(base_ni); + if (err) + return err; + } + + err = ntfs_inode_free_space(base_ni, sizeof(struct attr_record)); + if (err) { + err = -ENOSPC; + ntfs_error(sb, + "Couldn't free space in the MFT record to make attribute list non resident"); + return err; + } + err = ntfs_attrlist_update(base_ni); + if (err) + return err; + goto attr_resize_again; + } + + /* + * Move the attribute to a new mft record, creating an attribute list + * attribute or modifying it if it is already present. + */ + + /* Point search context back to attribute which we need resize. */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(attr_ni->type, attr_ni->name, attr_ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (err) { + ntfs_error(sb, "%s: Attribute lookup failed 2", __func__); + goto put_err_out; + } + + /* + * Check whether attribute is already single in this MFT record. + * 8 added for the attribute terminator. + */ + if (le32_to_cpu(ctx->mrec->bytes_in_use) == + le16_to_cpu(ctx->mrec->attrs_offset) + le32_to_cpu(ctx->attr->length) + 8) { + err = -ENOSPC; + ntfs_debug("MFT record is filled with one attribute\n"); + goto put_err_out; + } + + /* Add attribute list if not present. */ + if (!NInoAttrList(base_ni)) { + ntfs_attr_put_search_ctx(ctx); + err = ntfs_inode_add_attrlist(base_ni); + if (err) + return err; + goto attr_resize_again; + } + + /* Allocate new mft record. */ + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL); + if (err) { + ntfs_error(sb, "Couldn't allocate MFT record"); + goto put_err_out; + } + unmap_mft_record(ext_ni); + + /* Move attribute to it. */ + err = ntfs_attr_record_move_to(ctx, ext_ni); + if (err) { + ntfs_error(sb, "Couldn't move attribute to new MFT record"); + err = -ENOMEM; + goto put_err_out; + } + + err = ntfs_attrlist_update(base_ni); + if (err < 0) + goto put_err_out; + + ntfs_attr_put_search_ctx(ctx); + /* Try to perform resize once again. */ + goto attr_resize_again; + +resize_done: + /* + * Set the inode (and its base inode if it exists) dirty so it is + * written out later. + */ + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + return 0; + +put_err_out: + ntfs_attr_put_search_ctx(ctx); + return err; +} + +int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, + const s64 i_size) +{ + int err = 0; + + if (newsize < 0 || + (ni->mft_no == FILE_MFT && ni->type == AT_DATA)) { + ntfs_debug("Invalid arguments passed.\n"); + return -EINVAL; + } + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n", + (unsigned long long)ni->mft_no, ni->type, newsize); + + if (NInoNonResident(ni)) { + if (newsize > i_size) { + down_write(&ni->runlist.lock); + err = ntfs_non_resident_attr_expand(ni, newsize, 0, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, + false); + up_write(&ni->runlist.lock); + } else + err = ntfs_non_resident_attr_shrink(ni, newsize); + } else + err = ntfs_resident_attr_resize(ni, newsize, 0, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK); + ntfs_debug("Return status %d\n", err); + return err; +} + +int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size) +{ + int err = 0; + + if (newsize < 0 || + (ni->mft_no == FILE_MFT && ni->type == AT_DATA)) { + ntfs_debug("Invalid arguments passed.\n"); + return -EINVAL; + } + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n", + (unsigned long long)ni->mft_no, ni->type, newsize); + + if (ni->data_size == newsize) { + ntfs_debug("Size is already ok\n"); + return 0; + } + + /* + * Encrypted attributes are not supported. We return access denied, + * which is what Windows NT4 does, too. + */ + if (NInoEncrypted(ni)) { + pr_err("Failed to truncate encrypted attribute\n"); + return -EACCES; + } + + if (NInoNonResident(ni)) { + if (newsize > ni->data_size) + err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, true); + } else + err = ntfs_resident_attr_resize(ni, newsize, prealloc_size, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK); + if (!err) + i_size_write(VFS_I(ni), newsize); + ntfs_debug("Return status %d\n", err); + return err; +} + +/* + * ntfs_attr_truncate_i - resize an ntfs attribute + * @ni: open ntfs inode to resize + * @newsize: new size (in bytes) to which to resize the attribute + * @holes: how to create a hole if expanding + * + * Change the size of an open ntfs attribute @na to @newsize bytes. If the + * attribute is made bigger and the attribute is resident the newly + * "allocated" space is cleared and if the attribute is non-resident the + * newly allocated space is marked as not initialised and no real allocation + * on disk is performed. + */ +int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes) +{ + int err; + + if (newsize < 0 || + (ni->mft_no == FILE_MFT && ni->type == AT_DATA)) { + ntfs_debug("Invalid arguments passed.\n"); + return -EINVAL; + } + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n", + (unsigned long long)ni->mft_no, ni->type, newsize); + + if (ni->data_size == newsize) { + ntfs_debug("Size is already ok\n"); + return 0; + } + + /* + * Encrypted attributes are not supported. We return access denied, + * which is what Windows NT4 does, too. + */ + if (NInoEncrypted(ni)) { + pr_err("Failed to truncate encrypted attribute\n"); + return -EACCES; + } + + if (NInoCompressed(ni)) { + pr_err("Failed to truncate compressed attribute\n"); + return -EOPNOTSUPP; + } + + if (NInoNonResident(ni)) { + if (newsize > ni->data_size) + err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true); + else + err = ntfs_non_resident_attr_shrink(ni, newsize); + } else + err = ntfs_resident_attr_resize(ni, newsize, 0, holes); + ntfs_debug("Return status %d\n", err); + return err; +} + +/* + * Resize an attribute, creating a hole if relevant + */ +int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize) +{ + return ntfs_attr_truncate_i(ni, newsize, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK); +} + +int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, + s64 *lcn_count, s64 max_clu_count, bool *balloc, bool update_mp, + bool skip_holes) +{ + struct ntfs_volume *vol = ni->vol; + struct ntfs_attr_search_ctx *ctx; + struct runlist_element *rl, *rlc; + s64 vcn = vcn_start, lcn, clu_count; + s64 lcn_seek_from = -1; + int err = 0; + size_t new_rl_count; + + err = ntfs_attr_map_whole_runlist(ni); + if (err) + return err; + + if (NInoAttr(ni)) + ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + else + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(vol->sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, vcn, NULL, 0, ctx); + if (err) { + ntfs_error(vol->sb, + "ntfs_attr_lookup failed, ntfs inode(mft_no : %llu) type : 0x%x, err : %d", + ni->mft_no, ni->type, err); + goto out; + } + + rl = ntfs_attr_find_vcn_nolock(ni, vcn, ctx); + if (IS_ERR(rl)) { + ntfs_error(vol->sb, "Failed to find run after mapping runlist."); + err = PTR_ERR(rl); + goto out; + } + + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); + clu_count = min(max_clu_count, rl->length - (vcn - rl->vcn)); + if (lcn >= LCN_HOLE) { + if (lcn > LCN_DELALLOC || + (lcn == LCN_HOLE && skip_holes)) { + *lcn_start = lcn; + *lcn_count = clu_count; + *balloc = false; + goto out; + } + } else { + WARN_ON(lcn == LCN_RL_NOT_MAPPED); + if (lcn == LCN_ENOENT) + err = -ENOENT; + else + err = -EIO; + goto out; + } + + /* Search backwards to find the best lcn to start seek from. */ + rlc = rl; + while (rlc->vcn) { + rlc--; + if (rlc->lcn >= 0) { + /* + * avoid fragmenting a compressed file + * Windows does not do that, and that may + * not be desirable for files which can + * be updated + */ + if (NInoCompressed(ni)) + lcn_seek_from = rlc->lcn + rlc->length; + else + lcn_seek_from = rlc->lcn + (vcn - rlc->vcn); + break; + } + } + + if (lcn_seek_from == -1) { + /* Backwards search failed, search forwards. */ + rlc = rl; + while (rlc->length) { + rlc++; + if (rlc->lcn >= 0) { + lcn_seek_from = rlc->lcn - (rlc->vcn - vcn); + if (lcn_seek_from < -1) + lcn_seek_from = -1; + break; + } + } + } + + rlc = ntfs_cluster_alloc(vol, vcn, clu_count, lcn_seek_from, DATA_ZONE, + false, true, true); + if (IS_ERR(rlc)) { + err = PTR_ERR(rlc); + goto out; + } + + WARN_ON(rlc->vcn != vcn); + lcn = rlc->lcn; + clu_count = rlc->length; + + rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count); + if (IS_ERR(rl)) { + ntfs_error(vol->sb, "Failed to merge runlists"); + err = PTR_ERR(rl); + if (ntfs_cluster_free_from_rl(vol, rlc)) + ntfs_error(vol->sb, "Failed to free hot clusters."); + kvfree(rlc); + goto out; + } + ni->runlist.rl = rl; + ni->runlist.count = new_rl_count; + + if (!update_mp) { + u64 free = atomic64_read(&vol->free_clusters) * 100; + + do_div(free, vol->nr_clusters); + if (free <= 5) + update_mp = true; + } + + if (update_mp) { + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_update_mapping_pairs(ni, 0); + if (err) { + int err2; + + err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx); + if (err2 < 0) + ntfs_error(vol->sb, + "Failed to free cluster allocation. Leaving inconstant metadata.\n"); + goto out; + } + } else { + VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9); + NInoSetRunlistDirty(ni); + mark_mft_record_dirty(ni); + } + + *lcn_start = lcn; + *lcn_count = clu_count; + *balloc = true; +out: + ntfs_attr_put_search_ctx(ctx); + return err; +} + +/* + * ntfs_attr_rm - remove attribute from ntfs inode + * @ni: opened ntfs attribute to delete + * + * Remove attribute and all it's extents from ntfs inode. If attribute was non + * resident also free all clusters allocated by attribute. + */ +int ntfs_attr_rm(struct ntfs_inode *ni) +{ + struct ntfs_attr_search_ctx *ctx; + int err = 0, ret = 0; + struct ntfs_inode *base_ni; + struct super_block *sb = ni->vol->sb; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", + (long long) ni->mft_no, ni->type); + + /* Free cluster allocation. */ + if (NInoNonResident(ni)) { + struct ntfs_attr_search_ctx *ctx; + + err = ntfs_attr_map_whole_runlist(ni); + if (err) + return err; + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + + ret = ntfs_cluster_free(ni, 0, -1, ctx); + if (ret < 0) + ntfs_error(sb, + "Failed to free cluster allocation. Leaving inconstant metadata.\n"); + ntfs_attr_put_search_ctx(ctx); + } + + /* Search for attribute extents and remove them all. */ + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s: Failed to get search context", __func__); + return -ENOMEM; + } + while (!(err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx))) { + err = ntfs_attr_record_rm(ctx); + if (err) { + ntfs_error(sb, + "Failed to remove attribute extent. Leaving inconstant metadata.\n"); + ret = err; + } + ntfs_attr_reinit_search_ctx(ctx); + } + ntfs_attr_put_search_ctx(ctx); + if (err != -ENOENT) { + ntfs_error(sb, "Attribute lookup failed. Probably leaving inconstant metadata.\n"); + ret = err; + } + + return ret; +} + +int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, + u32 name_len) +{ + struct ntfs_attr_search_ctx *ctx; + int ret; + + ntfs_debug("Entering\n"); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(ni->vol->sb, "%s: Failed to get search context", + __func__); + return 0; + } + + ret = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + ntfs_attr_put_search_ctx(ctx); + + return !ret; +} + +int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name, + u32 name_len) +{ + int err; + struct inode *attr_vi; + struct ntfs_inode *attr_ni; + + ntfs_debug("Entering\n"); + + if (!ni) + return -EINVAL; + + attr_vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len); + if (IS_ERR(attr_vi)) { + err = PTR_ERR(attr_vi); + ntfs_error(ni->vol->sb, "Failed to open attribute 0x%02x of inode 0x%llx", + type, (unsigned long long)ni->mft_no); + return err; + } + attr_ni = NTFS_I(attr_vi); + + err = ntfs_attr_rm(attr_ni); + if (err) + ntfs_error(ni->vol->sb, "Failed to remove attribute 0x%02x of inode 0x%llx", + type, (unsigned long long)ni->mft_no); + iput(attr_vi); + return err; +} + +/* + * ntfs_attr_readall - read the entire data from an ntfs attribute + * @ni: open ntfs inode in which the ntfs attribute resides + * @type: attribute type + * @name: attribute name in little endian Unicode or AT_UNNAMED or NULL + * @name_len: length of attribute @name in Unicode characters (if @name given) + * @data_size: if non-NULL then store here the data size + * + * This function will read the entire content of an ntfs attribute. + * If @name is AT_UNNAMED then look specifically for an unnamed attribute. + * If @name is NULL then the attribute could be either named or not. + * In both those cases @name_len is not used at all. + * + * On success a buffer is allocated with the content of the attribute + * and which needs to be freed when it's not needed anymore. If the + * @data_size parameter is non-NULL then the data size is set there. + */ +void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type, + __le16 *name, u32 name_len, s64 *data_size) +{ + struct ntfs_inode *bmp_ni; + struct inode *bmp_vi; + void *data, *ret = NULL; + s64 size; + struct super_block *sb = ni->vol->sb; + + ntfs_debug("Entering\n"); + + bmp_vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len); + if (IS_ERR(bmp_vi)) { + ntfs_debug("ntfs_attr_iget failed"); + goto err_exit; + } + bmp_ni = NTFS_I(bmp_vi); + + data = kvmalloc(bmp_ni->data_size, GFP_NOFS); + if (!data) + goto out; + + size = ntfs_inode_attr_pread(VFS_I(bmp_ni), 0, bmp_ni->data_size, + (u8 *)data); + if (size != bmp_ni->data_size) { + ntfs_error(sb, "ntfs_attr_pread failed"); + kvfree(data); + goto out; + } + ret = data; + if (data_size) + *data_size = size; +out: + iput(bmp_vi); +err_exit: + ntfs_debug("\n"); + return ret; +} + +int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s64 len) +{ + struct ntfs_volume *vol = ni->vol; + struct runlist_element *hole_rl, *rl; + struct ntfs_attr_search_ctx *ctx; + int ret; + size_t new_rl_count; + + if (NInoAttr(ni) || ni->type != AT_DATA) + return -EOPNOTSUPP; + if (start_vcn > ntfs_bytes_to_cluster(vol, ni->allocated_size)) + return -EINVAL; + + hole_rl = kmalloc(sizeof(*hole_rl) * 2, GFP_NOFS); + if (!hole_rl) + return -ENOMEM; + hole_rl[0].vcn = start_vcn; + hole_rl[0].lcn = LCN_HOLE; + hole_rl[0].length = len; + hole_rl[1].vcn = start_vcn + len; + hole_rl[1].lcn = LCN_ENOENT; + hole_rl[1].length = 0; + + down_write(&ni->runlist.lock); + ret = ntfs_attr_map_whole_runlist(ni); + if (ret) { + up_write(&ni->runlist.lock); + return ret; + } + + rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn); + if (!rl) { + up_write(&ni->runlist.lock); + kfree(hole_rl); + return -EIO; + } + + rl = ntfs_rl_insert_range(ni->runlist.rl, (int)ni->runlist.count, + hole_rl, 1, &new_rl_count); + if (IS_ERR(rl)) { + up_write(&ni->runlist.lock); + kfree(hole_rl); + return PTR_ERR(rl); + } + ni->runlist.rl = rl; + ni->runlist.count = new_rl_count; + + ni->allocated_size += ntfs_cluster_to_bytes(vol, len); + ni->data_size += ntfs_cluster_to_bytes(vol, len); + if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size) + ni->initialized_size += ntfs_cluster_to_bytes(vol, len); + ret = ntfs_attr_update_mapping_pairs(ni, 0); + up_write(&ni->runlist.lock); + if (ret) + return ret; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ret = -ENOMEM; + return ret; + } + + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (ret) { + ntfs_attr_put_search_ctx(ctx); + return ret; + } + + ctx->attr->data.non_resident.data_size = cpu_to_le64(ni->data_size); + ctx->attr->data.non_resident.initialized_size = cpu_to_le64(ni->initialized_size); + if (ni->type == AT_DATA && ni->name == AT_UNNAMED) + NInoSetFileNameDirty(ni); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + return ret; +} + +int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, s64 len) +{ + struct ntfs_volume *vol = ni->vol; + struct runlist_element *punch_rl, *rl; + struct ntfs_attr_search_ctx *ctx = NULL; + s64 end_vcn; + int dst_cnt; + int ret; + size_t new_rl_cnt; + + if (NInoAttr(ni) || ni->type != AT_DATA) + return -EOPNOTSUPP; + + end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); + if (start_vcn >= end_vcn) + return -EINVAL; + + down_write(&ni->runlist.lock); + ret = ntfs_attr_map_whole_runlist(ni); + if (ret) { + up_write(&ni->runlist.lock); + return ret; + } + + len = min(len, end_vcn - start_vcn); + for (rl = ni->runlist.rl, dst_cnt = 0; rl && rl->length; rl++) + dst_cnt++; + rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn); + if (!rl) { + up_write(&ni->runlist.lock); + return -EIO; + } + + rl = ntfs_rl_collapse_range(ni->runlist.rl, dst_cnt + 1, + start_vcn, len, &punch_rl, &new_rl_cnt); + if (IS_ERR(rl)) { + up_write(&ni->runlist.lock); + return PTR_ERR(rl); + } + ni->runlist.rl = rl; + ni->runlist.count = new_rl_cnt; + + ni->allocated_size -= ntfs_cluster_to_bytes(vol, len); + if (ni->data_size > ntfs_cluster_to_bytes(vol, start_vcn)) { + if (ni->data_size > ntfs_cluster_to_bytes(vol, (start_vcn + len))) + ni->data_size -= ntfs_cluster_to_bytes(vol, len); + else + ni->data_size = ntfs_cluster_to_bytes(vol, start_vcn); + } + if (ni->initialized_size > ntfs_cluster_to_bytes(vol, start_vcn)) { + if (ni->initialized_size > + ntfs_cluster_to_bytes(vol, start_vcn + len)) + ni->initialized_size -= ntfs_cluster_to_bytes(vol, len); + else + ni->initialized_size = ntfs_cluster_to_bytes(vol, start_vcn); + } + + if (ni->allocated_size > 0) { + ret = ntfs_attr_update_mapping_pairs(ni, 0); + if (ret) { + up_write(&ni->runlist.lock); + goto out_rl; + } + } + up_write(&ni->runlist.lock); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ret = -ENOMEM; + goto out_rl; + } + + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (ret) + goto out_ctx; + + ctx->attr->data.non_resident.data_size = cpu_to_le64(ni->data_size); + ctx->attr->data.non_resident.initialized_size = cpu_to_le64(ni->initialized_size); + if (ni->allocated_size == 0) + ntfs_attr_make_resident(ni, ctx); + mark_mft_record_dirty(ctx->ntfs_ino); + + ret = ntfs_cluster_free_from_rl(vol, punch_rl); + if (ret) + ntfs_error(vol->sb, "Freeing of clusters failed"); +out_ctx: + if (ctx) + ntfs_attr_put_search_ctx(ctx); +out_rl: + kvfree(punch_rl); + mark_mft_record_dirty(ni); + return ret; +} + +int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 len) +{ + struct ntfs_volume *vol = ni->vol; + struct runlist_element *punch_rl, *rl; + s64 end_vcn; + int dst_cnt; + int ret; + size_t new_rl_count; + + if (NInoAttr(ni) || ni->type != AT_DATA) + return -EOPNOTSUPP; + + end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); + if (start_vcn >= end_vcn) + return -EINVAL; + + down_write(&ni->runlist.lock); + ret = ntfs_attr_map_whole_runlist(ni); + if (ret) { + up_write(&ni->runlist.lock); + return ret; + } + + len = min(len, end_vcn - start_vcn + 1); + for (rl = ni->runlist.rl, dst_cnt = 0; rl && rl->length; rl++) + dst_cnt++; + rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn); + if (!rl) { + up_write(&ni->runlist.lock); + return -EIO; + } + + rl = ntfs_rl_punch_hole(ni->runlist.rl, dst_cnt + 1, + start_vcn, len, &punch_rl, &new_rl_count); + if (IS_ERR(rl)) { + up_write(&ni->runlist.lock); + return PTR_ERR(rl); + } + ni->runlist.rl = rl; + ni->runlist.count = new_rl_count; + + ret = ntfs_attr_update_mapping_pairs(ni, 0); + up_write(&ni->runlist.lock); + if (ret) { + kvfree(punch_rl); + return ret; + } + + ret = ntfs_cluster_free_from_rl(vol, punch_rl); + if (ret) + ntfs_error(vol->sb, "Freeing of clusters failed"); + + kvfree(punch_rl); + mark_mft_record_dirty(ni); + return ret; +} + +int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bool keep_size) +{ + struct ntfs_volume *vol = ni->vol; + struct mft_record *mrec; + struct ntfs_attr_search_ctx *ctx; + s64 old_data_size; + s64 vcn_start, vcn_end, vcn_uninit, vcn, try_alloc_cnt; + s64 lcn, alloc_cnt; + int err = 0; + struct runlist_element *rl; + bool balloc; + + if (NInoAttr(ni) || ni->type != AT_DATA) + return -EINVAL; + + if (NInoNonResident(ni) && !NInoFullyMapped(ni)) { + down_write(&ni->runlist.lock); + err = ntfs_attr_map_whole_runlist(ni); + up_write(&ni->runlist.lock); + if (err) + return err; + } + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mrec = map_mft_record(ni); + if (IS_ERR(mrec)) { + mutex_unlock(&ni->mrec_lock); + return PTR_ERR(mrec); + } + + ctx = ntfs_attr_get_search_ctx(ni, mrec); + if (!ctx) { + err = -ENOMEM; + goto out_unmap; + } + + err = ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx); + if (err) { + err = -EIO; + goto out_unmap; + } + + old_data_size = ni->data_size; + if (start + byte_len > ni->data_size) { + err = ntfs_attr_truncate(ni, start + byte_len); + if (err) + goto out_unmap; + if (keep_size) { + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx); + if (err) { + err = -EIO; + goto out_unmap; + } + ni->data_size = old_data_size; + if (NInoNonResident(ni)) + ctx->attr->data.non_resident.data_size = + cpu_to_le64(old_data_size); + else + ctx->attr->data.resident.value_length = + cpu_to_le32((u32)old_data_size); + mark_mft_record_dirty(ni); + } + } + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + mutex_unlock(&ni->mrec_lock); + + if (!NInoNonResident(ni)) + goto out; + + vcn_start = (s64)ntfs_bytes_to_cluster(vol, start); + vcn_end = (s64)ntfs_bytes_to_cluster(vol, + round_up(start + byte_len, vol->cluster_size)); + vcn_uninit = (s64)ntfs_bytes_to_cluster(vol, + round_up(ni->initialized_size, vol->cluster_size)); + vcn_uninit = min_t(s64, vcn_uninit, vcn_end); + + /* + * we have to allocate clusters for holes and delayed within initialized_size, + * and zero out the clusters only for the holes. + */ + vcn = vcn_start; + while (vcn < vcn_uninit) { + down_read(&ni->runlist.lock); + rl = ntfs_attr_find_vcn_nolock(ni, vcn, NULL); + up_read(&ni->runlist.lock); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + goto out; + } + + if (rl->lcn > 0) { + vcn += rl->length - (vcn - rl->vcn); + } else if (rl->lcn == LCN_DELALLOC || rl->lcn == LCN_HOLE) { + try_alloc_cnt = min(rl->length - (vcn - rl->vcn), + vcn_uninit - vcn); + + if (rl->lcn == LCN_DELALLOC) { + vcn += try_alloc_cnt; + continue; + } + + while (try_alloc_cnt > 0) { + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + down_write(&ni->runlist.lock); + err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt, + try_alloc_cnt, &balloc, false, false); + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + if (err) + goto out; + + err = ntfs_dio_zero_range(VFS_I(ni), + lcn << vol->cluster_size_bits, + alloc_cnt << vol->cluster_size_bits); + if (err > 0) + goto out; + + if (signal_pending(current)) + goto out; + + vcn += alloc_cnt; + try_alloc_cnt -= alloc_cnt; + } + } else { + err = -EIO; + goto out; + } + } + + /* allocate clusters outside of initialized_size */ + try_alloc_cnt = vcn_end - vcn; + while (try_alloc_cnt > 0) { + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + down_write(&ni->runlist.lock); + err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt, + try_alloc_cnt, &balloc, false, false); + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + if (err || signal_pending(current)) + goto out; + + vcn += alloc_cnt; + try_alloc_cnt -= alloc_cnt; + cond_resched(); + } + + if (NInoRunlistDirty(ni)) { + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + down_write(&ni->runlist.lock); + err = ntfs_attr_update_mapping_pairs(ni, 0); + if (err) + ntfs_error(ni->vol->sb, "Updating mapping pairs failed"); + else + NInoClearRunlistDirty(ni); + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + } + return err; +out_unmap: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + mutex_unlock(&ni->mrec_lock); +out: + return err >= 0 ? 0 : err; +} diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h new file mode 100644 index 000000000000..f7acc7986b09 --- /dev/null +++ b/fs/ntfs/attrib.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for attribute handling in NTFS Linux kernel driver. + * + * Copyright (c) 2001-2005 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_ATTRIB_H +#define _LINUX_NTFS_ATTRIB_H + +#include "ntfs.h" +#include "dir.h" + +extern __le16 AT_UNNAMED[]; + +/* + * ntfs_attr_search_ctx - used in attribute search functions + * @mrec: buffer containing mft record to search + * @mapped_mrec: true if @mrec was mapped by the search functions + * @attr: attribute record in @mrec where to begin/continue search + * @is_first: if true ntfs_attr_lookup() begins search with @attr, else after + * @ntfs_ino: Inode owning this attribute search + * @al_entry: Current attribute list entry + * @base_ntfs_ino: Base inode + * @mapped_base_mrec: true if @base_mrec was mapped by the search + * @base_attr: Base attribute record pointer + * + * Structure must be initialized to zero before the first call to one of the + * attribute search functions. Initialize @mrec to point to the mft record to + * search, and @attr to point to the first attribute within @mrec (not necessary + * if calling the _first() functions), and set @is_first to 'true' (not necessary + * if calling the _first() functions). + * + * If @is_first is 'true', the search begins with @attr. If @is_first is 'false', + * the search begins after @attr. This is so that, after the first call to one + * of the search attribute functions, we can call the function again, without + * any modification of the search context, to automagically get the next + * matching attribute. + */ +struct ntfs_attr_search_ctx { + struct mft_record *mrec; + bool mapped_mrec; + struct attr_record *attr; + bool is_first; + struct ntfs_inode *ntfs_ino; + struct attr_list_entry *al_entry; + struct ntfs_inode *base_ntfs_ino; + struct mft_record *base_mrec; + bool mapped_base_mrec; + struct attr_record *base_attr; +}; + +enum { /* ways of processing holes when expanding */ + HOLES_NO, + HOLES_OK, +}; + +int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, + struct ntfs_attr_search_ctx *ctx); +int ntfs_map_runlist(struct ntfs_inode *ni, s64 vcn); +s64 ntfs_attr_vcn_to_lcn_nolock(struct ntfs_inode *ni, const s64 vcn, + const bool write_locked); +struct runlist_element *ntfs_attr_find_vcn_nolock(struct ntfs_inode *ni, + const s64 vcn, struct ntfs_attr_search_ctx *ctx); +struct runlist_element *__ntfs_attr_find_vcn_nolock(struct runlist *runlist, + const s64 vcn); +int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni); +int ntfs_attr_lookup(const __le32 type, const __le16 *name, + const u32 name_len, const u32 ic, + const s64 lowest_vcn, const u8 *val, const u32 val_len, + struct ntfs_attr_search_ctx *ctx); +int load_attribute_list(struct ntfs_inode *base_ni, + u8 *al_start, const s64 size); + +static inline s64 ntfs_attr_size(const struct attr_record *a) +{ + if (!a->non_resident) + return (s64)le32_to_cpu(a->data.resident.value_length); + return le64_to_cpu(a->data.non_resident.data_size); +} + +void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx); +struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni, + struct mft_record *mrec); +void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx); +int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol, + const __le32 type, const s64 size); +int ntfs_attr_can_be_resident(const struct ntfs_volume *vol, + const __le32 type); +int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, + s64 *lcn_count, s64 max_clu_count, bool *balloc, bool update_mp, bool skip_holes); +int ntfs_attr_record_resize(struct mft_record *m, struct attr_record *a, u32 new_size); +int ntfs_resident_attr_value_resize(struct mft_record *m, struct attr_record *a, + const u32 new_size); +int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size); +int ntfs_attr_set(struct ntfs_inode *ni, const s64 ofs, const s64 cnt, + const u8 val); +int ntfs_attr_set_initialized_size(struct ntfs_inode *ni, loff_t new_size); +int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type, + __le16 *name, u32 name_len); +void ntfs_attr_close(struct ntfs_inode *n); +int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bool keep_size); +int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s64 len); +int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, s64 len); +int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 len); +int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, + const s64 i_size); +int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size); +int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes); +int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize); +int ntfs_attr_rm(struct ntfs_inode *ni); +int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, + u32 name_len); +int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name, + u32 name_len); +int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx); +int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode *ni); +int ntfs_attr_add(struct ntfs_inode *ni, __le32 type, + __le16 *name, u8 name_len, u8 *val, s64 size); +int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra); +char *ntfs_attr_name_get(const struct ntfs_volume *vol, const __le16 *uname, + const int uname_len); +void ntfs_attr_name_free(unsigned char **name); +void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type, + __le16 *name, u32 name_len, s64 *data_size); +int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, + __le16 *name, u8 name_len, u8 *val, u32 size, + __le16 flags); +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn); +struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn); + +/* + * ntfs_attrs_walk - syntactic sugar for walking all attributes in an inode + * @ctx: initialised attribute search context + * + * Syntactic sugar for walking attributes in an inode. + * + * Return 0 on success and -1 on error with errno set to the error code from + * ntfs_attr_lookup(). + * + * Example: When you want to enumerate all attributes in an open ntfs inode + * @ni, you can simply do: + * + * int err; + * struct ntfs_attr_search_ctx *ctx = ntfs_attr_get_search_ctx(ni, NULL); + * if (!ctx) + * // Error code is in errno. Handle this case. + * while (!(err = ntfs_attrs_walk(ctx))) { + * struct attr_record *attr = ctx->attr; + * // attr now contains the next attribute. Do whatever you want + * // with it and then just continue with the while loop. + * } + * if (err && errno != ENOENT) + * // Ooops. An error occurred! You should handle this case. + * // Now finished with all attributes in the inode. + */ +static inline int ntfs_attrs_walk(struct ntfs_attr_search_ctx *ctx) +{ + return ntfs_attr_lookup(AT_UNUSED, NULL, 0, CASE_SENSITIVE, 0, + NULL, 0, ctx); +} +#endif /* _LINUX_NTFS_ATTRIB_H */ diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c new file mode 100644 index 000000000000..bd501e8a628c --- /dev/null +++ b/fs/ntfs/attrlist.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Attribute list attribute handling code. + * Part of this file is based on code from the NTFS-3G. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2004-2005 Yura Pakhuchiy + * Copyright (c) 2006 Szabolcs Szakacsits + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include "mft.h" +#include "attrib.h" +#include "attrlist.h" + +/* + * ntfs_attrlist_need - check whether inode need attribute list + * @ni: opened ntfs inode for which perform check + * + * Check whether all are attributes belong to one MFT record, in that case + * attribute list is not needed. + * + * Return 1 if inode need attribute list, 0 if not, or -errno on error. + */ +int ntfs_attrlist_need(struct ntfs_inode *ni) +{ + struct attr_list_entry *ale; + + if (!ni) { + ntfs_debug("Invalid arguments.\n"); + return -EINVAL; + } + ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + if (!NInoAttrList(ni)) { + ntfs_debug("Inode haven't got attribute list.\n"); + return -EINVAL; + } + + if (!ni->attr_list) { + ntfs_debug("Corrupt in-memory struct.\n"); + return -EINVAL; + } + + ale = (struct attr_list_entry *)ni->attr_list; + while ((u8 *)ale < ni->attr_list + ni->attr_list_size) { + if (MREF_LE(ale->mft_reference) != ni->mft_no) + return 1; + ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); + } + return 0; +} + +int ntfs_attrlist_update(struct ntfs_inode *base_ni) +{ + struct inode *attr_vi; + struct ntfs_inode *attr_ni; + int err; + + attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (IS_ERR(attr_vi)) { + err = PTR_ERR(attr_vi); + return err; + } + attr_ni = NTFS_I(attr_vi); + + err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); + if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) { + err = ntfs_attr_truncate(attr_ni, 0); + if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) { + iput(attr_vi); + ntfs_error(base_ni->vol->sb, + "Failed to truncate attribute list of inode %#llx", + (long long)base_ni->mft_no); + return -EIO; + } + } else if (err) { + iput(attr_vi); + ntfs_error(base_ni->vol->sb, + "Failed to truncate attribute list of inode %#llx", + (long long)base_ni->mft_no); + return -EIO; + } + + i_size_write(attr_vi, base_ni->attr_list_size); + + if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni)) + NInoSetAttrListNonResident(base_ni); + + if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, + base_ni->attr_list, false) != + base_ni->attr_list_size) { + iput(attr_vi); + ntfs_error(base_ni->vol->sb, + "Failed to write attribute list of inode %#llx", + (long long)base_ni->mft_no); + return -EIO; + } + + NInoSetAttrListDirty(base_ni); + iput(attr_vi); + return 0; +} + +/* + * ntfs_attrlist_entry_add - add an attribute list attribute entry + * @ni: opened ntfs inode, which contains that attribute + * @attr: attribute record to add to attribute list + * + * Return 0 on success and -errno on error. + */ +int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr) +{ + struct attr_list_entry *ale; + __le64 mref; + struct ntfs_attr_search_ctx *ctx; + u8 *new_al; + int entry_len, entry_offset, err; + struct mft_record *ni_mrec; + u8 *old_al; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", + (long long) ni->mft_no, + (unsigned int) le32_to_cpu(attr->type)); + + if (!ni || !attr) { + ntfs_debug("Invalid arguments.\n"); + return -EINVAL; + } + + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + ntfs_debug("Invalid arguments.\n"); + return -EIO; + } + + mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number)); + unmap_mft_record(ni); + + if (ni->nr_extents == -1) + ni = ni->ext.base_ntfs_ino; + + if (!NInoAttrList(ni)) { + ntfs_debug("Attribute list isn't present.\n"); + return -ENOENT; + } + + /* Determine size and allocate memory for new attribute list. */ + entry_len = (sizeof(struct attr_list_entry) + sizeof(__le16) * + attr->name_length + 7) & ~7; + new_al = kvzalloc(ni->attr_list_size + entry_len, GFP_NOFS); + if (!new_al) + return -ENOMEM; + + /* Find place for the new entry. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = -ENOMEM; + ntfs_error(ni->vol->sb, "Failed to get search context"); + goto err_out; + } + + err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *) + ((u8 *)attr + le16_to_cpu(attr->name_offset)) : + AT_UNNAMED, attr->name_length, CASE_SENSITIVE, + (attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) : + 0, (attr->non_resident) ? NULL : ((u8 *)attr + + le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ? + 0 : le32_to_cpu(attr->data.resident.value_length), ctx); + if (!err) { + /* Found some extent, check it to be before new extent. */ + if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) { + err = -EEXIST; + ntfs_debug("Such attribute already present in the attribute list.\n"); + ntfs_attr_put_search_ctx(ctx); + goto err_out; + } + /* Add new entry after this extent. */ + ale = (struct attr_list_entry *)((u8 *)ctx->al_entry + + le16_to_cpu(ctx->al_entry->length)); + } else { + /* Check for real errors. */ + if (err != -ENOENT) { + ntfs_debug("Attribute lookup failed.\n"); + ntfs_attr_put_search_ctx(ctx); + goto err_out; + } + /* No previous extents found. */ + ale = ctx->al_entry; + } + /* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */ + ntfs_attr_put_search_ctx(ctx); + + /* Determine new entry offset. */ + entry_offset = ((u8 *)ale - ni->attr_list); + /* Set pointer to new entry. */ + ale = (struct attr_list_entry *)(new_al + entry_offset); + memset(ale, 0, entry_len); + /* Form new entry. */ + ale->type = attr->type; + ale->length = cpu_to_le16(entry_len); + ale->name_length = attr->name_length; + ale->name_offset = offsetof(struct attr_list_entry, name); + if (attr->non_resident) + ale->lowest_vcn = attr->data.non_resident.lowest_vcn; + else + ale->lowest_vcn = 0; + ale->mft_reference = mref; + ale->instance = attr->instance; + memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset), + attr->name_length * sizeof(__le16)); + + /* Copy entries from old attribute list to new. */ + memcpy(new_al, ni->attr_list, entry_offset); + memcpy(new_al + entry_offset + entry_len, ni->attr_list + + entry_offset, ni->attr_list_size - entry_offset); + + /* Set new runlist. */ + old_al = ni->attr_list; + ni->attr_list = new_al; + ni->attr_list_size = ni->attr_list_size + entry_len; + + err = ntfs_attrlist_update(ni); + if (err) { + ni->attr_list = old_al; + ni->attr_list_size -= entry_len; + goto err_out; + } + kvfree(old_al); + return 0; +err_out: + kvfree(new_al); + return err; +} + +/* + * ntfs_attrlist_entry_rm - remove an attribute list attribute entry + * @ctx: attribute search context describing the attribute list entry + * + * Remove the attribute list entry @ctx->al_entry from the attribute list. + * + * Return 0 on success and -errno on error. + */ +int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx) +{ + u8 *new_al; + int new_al_len; + struct ntfs_inode *base_ni; + struct attr_list_entry *ale; + + if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) { + ntfs_debug("Invalid arguments.\n"); + return -EINVAL; + } + + if (ctx->base_ntfs_ino) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + ale = ctx->al_entry; + + ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n", + (long long)ctx->ntfs_ino->mft_no, + (unsigned int)le32_to_cpu(ctx->al_entry->type), + (long long)le64_to_cpu(ctx->al_entry->lowest_vcn)); + + if (!NInoAttrList(base_ni)) { + ntfs_debug("Attribute list isn't present.\n"); + return -ENOENT; + } + + /* Allocate memory for new attribute list. */ + new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length); + new_al = kvzalloc(new_al_len, GFP_NOFS); + if (!new_al) + return -ENOMEM; + + /* Copy entries from old attribute list to new. */ + memcpy(new_al, base_ni->attr_list, (u8 *)ale - base_ni->attr_list); + memcpy(new_al + ((u8 *)ale - base_ni->attr_list), (u8 *)ale + le16_to_cpu( + ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list)); + + /* Set new runlist. */ + kvfree(base_ni->attr_list); + base_ni->attr_list = new_al; + base_ni->attr_list_size = new_al_len; + + return ntfs_attrlist_update(base_ni); +} diff --git a/fs/ntfs/attrlist.h b/fs/ntfs/attrlist.h new file mode 100644 index 000000000000..1892a3934d3a --- /dev/null +++ b/fs/ntfs/attrlist.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Exports for attribute list attribute handling. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2004 Yura Pakhuchiy + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _NTFS_ATTRLIST_H +#define _NTFS_ATTRLIST_H + +#include "attrib.h" + +int ntfs_attrlist_need(struct ntfs_inode *ni); +int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr); +int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx); +int ntfs_attrlist_update(struct ntfs_inode *base_ni); + +#endif /* defined _NTFS_ATTRLIST_H */ diff --git a/fs/ntfs/bdev-io.c b/fs/ntfs/bdev-io.c new file mode 100644 index 000000000000..67e65c88d681 --- /dev/null +++ b/fs/ntfs/bdev-io.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS block device I/O. + * + * Copyright (c) 2026 LG Electronics Co., Ltd. + */ + +#include + +#include "ntfs.h" + +/* + * ntfs_bdev_read - Read data directly from block device using bio + * @bdev: block device to read from + * @data: destination buffer + * @start: starting byte offset on the block device + * @size: number of bytes to read + * + * Reads @size bytes starting from byte offset @start directly from the block + * device using one or more BIOs. This function bypasses the page cache + * completely and performs synchronous I/O with REQ_META | REQ_SYNC flags set. + * + * The @start offset must be sector-aligned (512 bytes). If it is not aligned, + * the function will return -EINVAL. + * + * If the destination buffer @data is not a vmalloc address, it falls back + * to the more efficient bdev_rw_virt() helper. + * + * Return: 0 on success, negative error code on failure. + */ +int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t size) +{ + unsigned int done = 0, added; + int error; + struct bio *bio; + enum req_op op; + sector_t sector = start >> SECTOR_SHIFT; + + if (start & (SECTOR_SIZE - 1)) + return -EINVAL; + + op = REQ_OP_READ | REQ_META | REQ_SYNC; + if (!is_vmalloc_addr(data)) + return bdev_rw_virt(bdev, sector, data, size, op); + + bio = bio_alloc(bdev, + bio_max_segs(DIV_ROUND_UP(size, PAGE_SIZE)), + op, GFP_KERNEL); + bio->bi_iter.bi_sector = sector; + + do { + added = bio_add_vmalloc_chunk(bio, data + done, size - done); + if (!added) { + struct bio *prev = bio; + + bio = bio_alloc(prev->bi_bdev, + bio_max_segs(DIV_ROUND_UP(size - done, PAGE_SIZE)), + prev->bi_opf, GFP_KERNEL); + bio->bi_iter.bi_sector = bio_end_sector(prev); + bio_chain(prev, bio); + submit_bio(prev); + } + done += added; + } while (done < size); + + error = submit_bio_wait(bio); + bio_put(bio); + + if (op == REQ_OP_READ) + invalidate_kernel_vmap_range(data, size); + return error; +} + +/* + * ntfs_bdev_write - Update block device contents via page cache + * @sb: super block of the mounted NTFS filesystem + * @buf: source buffer containing data to write + * @start: starting byte offset on the block device + * @size: number of bytes to write + * + * Writes @size bytes from @buf to the block device (sb->s_bdev) starting + * at byte offset @start. The write is performed entirely through the page + * cache of the block device's address space. + */ +int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size) +{ + pgoff_t idx, idx_end; + loff_t offset, end = start + size; + u32 from, to, buf_off = 0; + struct folio *folio; + + idx = start >> PAGE_SHIFT; + idx_end = end >> PAGE_SHIFT; + from = start & ~PAGE_MASK; + + if (idx == idx_end) + idx_end++; + + for (; idx < idx_end; idx++, from = 0) { + folio = read_mapping_folio(sb->s_bdev->bd_mapping, idx, NULL); + if (IS_ERR(folio)) { + ntfs_error(sb, "Unable to read %ld page", idx); + return PTR_ERR(folio); + } + + offset = (loff_t)idx << PAGE_SHIFT; + to = min_t(u32, end - offset, PAGE_SIZE); + + memcpy_to_folio(folio, from, buf + buf_off, to); + buf_off += to; + folio_mark_uptodate(folio); + folio_mark_dirty(folio); + folio_put(folio); + } + + return 0; +} diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c new file mode 100644 index 000000000000..656d802333e3 --- /dev/null +++ b/fs/ntfs/bitmap.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel bitmap handling. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include +#include + +#include "bitmap.h" +#include "ntfs.h" + +int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) +{ + size_t buf_clusters; + pgoff_t index, start_index, end_index; + struct file_ra_state *ra; + struct folio *folio; + unsigned long *bitmap; + char *kaddr; + u64 end, trimmed = 0, start_buf, end_buf, end_cluster; + u64 start_cluster = ntfs_bytes_to_cluster(vol, range->start); + u32 dq = bdev_discard_granularity(vol->sb->s_bdev); + int ret = 0; + + if (!dq) + dq = vol->cluster_size; + + if (start_cluster >= vol->nr_clusters) + return -EINVAL; + + if (range->len == (u64)-1) + end_cluster = vol->nr_clusters; + else { + end_cluster = ntfs_bytes_to_cluster(vol, + (range->start + range->len + vol->cluster_size - 1)); + if (end_cluster > vol->nr_clusters) + end_cluster = vol->nr_clusters; + } + + ra = kzalloc(sizeof(*ra), GFP_NOFS); + if (!ra) + return -ENOMEM; + + buf_clusters = PAGE_SIZE * 8; + start_index = start_cluster >> 15; + end_index = (end_cluster + buf_clusters - 1) >> 15; + + for (index = start_index; index < end_index; index++) { + folio = ntfs_get_locked_folio(vol->lcnbmp_ino->i_mapping, + index, end_index, ra); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + goto out_free; + } + + kaddr = kmap_local_folio(folio, 0); + bitmap = (unsigned long *)kaddr; + + start_buf = max_t(u64, index * buf_clusters, start_cluster); + end_buf = min_t(u64, (index + 1) * buf_clusters, end_cluster); + + end = start_buf; + while (end < end_buf) { + u64 aligned_start, aligned_count; + u64 start = find_next_zero_bit(bitmap, end_buf - start_buf, + end - start_buf) + start_buf; + if (start >= end_buf) + break; + + end = find_next_bit(bitmap, end_buf - start_buf, + start - start_buf) + start_buf; + + aligned_start = ALIGN(ntfs_cluster_to_bytes(vol, start), dq); + aligned_count = + ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end - start), dq); + if (aligned_count >= range->minlen) { + ret = blkdev_issue_discard(vol->sb->s_bdev, aligned_start >> 9, + aligned_count >> 9, GFP_NOFS); + if (ret) + goto out_unmap; + trimmed += aligned_count; + } + } + +out_unmap: + kunmap_local(kaddr); + folio_unlock(folio); + folio_put(folio); + + if (ret) + goto out_free; + } + + range->len = trimmed; + +out_free: + kfree(ra); + return ret; +} + +/* + * __ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value + * @vi: vfs inode describing the bitmap + * @start_bit: first bit to set + * @count: number of bits to set + * @value: value to set the bits to (i.e. 0 or 1) + * @is_rollback: if 'true' this is a rollback operation + * + * Set @count bits starting at bit @start_bit in the bitmap described by the + * vfs inode @vi to @value, where @value is either 0 or 1. + * + * @is_rollback should always be 'false', it is for internal use to rollback + * errors. You probably want to use ntfs_bitmap_set_bits_in_run() instead. + * + * Return 0 on success and -errno on error. + */ +int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit, + const s64 count, const u8 value, const bool is_rollback) +{ + s64 cnt = count; + pgoff_t index, end_index; + struct address_space *mapping; + struct folio *folio; + u8 *kaddr; + int pos, len; + u8 bit; + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + + ntfs_debug("Entering for i_ino 0x%llx, start_bit 0x%llx, count 0x%llx, value %u.%s", + ni->mft_no, (unsigned long long)start_bit, + (unsigned long long)cnt, (unsigned int)value, + is_rollback ? " (rollback)" : ""); + + if (start_bit < 0 || cnt < 0 || value > 1) + return -EINVAL; + + /* + * Calculate the indices for the pages containing the first and last + * bits, i.e. @start_bit and @start_bit + @cnt - 1, respectively. + */ + index = start_bit >> (3 + PAGE_SHIFT); + end_index = (start_bit + cnt - 1) >> (3 + PAGE_SHIFT); + + /* Get the page containing the first bit (@start_bit). */ + mapping = vi->i_mapping; + folio = read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) { + if (!is_rollback) + ntfs_error(vi->i_sb, + "Failed to map first page (error %li), aborting.", + PTR_ERR(folio)); + return PTR_ERR(folio); + } + + folio_lock(folio); + kaddr = kmap_local_folio(folio, 0); + + /* Set @pos to the position of the byte containing @start_bit. */ + pos = (start_bit >> 3) & ~PAGE_MASK; + + /* Calculate the position of @start_bit in the first byte. */ + bit = start_bit & 7; + + /* If the first byte is partial, modify the appropriate bits in it. */ + if (bit) { + u8 *byte = kaddr + pos; + + if (ni->mft_no == FILE_Bitmap) + ntfs_set_lcn_empty_bits(vol, index, value, min_t(s64, 8 - bit, cnt)); + while ((bit & 7) && cnt) { + cnt--; + if (value) + *byte |= 1 << bit++; + else + *byte &= ~(1 << bit++); + } + /* If we are done, unmap the page and return success. */ + if (!cnt) + goto done; + + /* Update @pos to the new position. */ + pos++; + } + /* + * Depending on @value, modify all remaining whole bytes in the page up + * to @cnt. + */ + len = min_t(s64, cnt >> 3, PAGE_SIZE - pos); + memset(kaddr + pos, value ? 0xff : 0, len); + cnt -= len << 3; + if (ni->mft_no == FILE_Bitmap) + ntfs_set_lcn_empty_bits(vol, index, value, len << 3); + + /* Update @len to point to the first not-done byte in the page. */ + if (cnt < 8) + len += pos; + + /* If we are not in the last page, deal with all subsequent pages. */ + while (index < end_index) { + if (cnt <= 0) + goto rollback; + + /* Update @index and get the next folio. */ + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(kaddr); + folio_put(folio); + folio = read_mapping_folio(mapping, ++index, NULL); + if (IS_ERR(folio)) { + ntfs_error(vi->i_sb, + "Failed to map subsequent page (error %li), aborting.", + PTR_ERR(folio)); + goto rollback; + } + + folio_lock(folio); + kaddr = kmap_local_folio(folio, 0); + /* + * Depending on @value, modify all remaining whole bytes in the + * page up to @cnt. + */ + len = min_t(s64, cnt >> 3, PAGE_SIZE); + memset(kaddr, value ? 0xff : 0, len); + cnt -= len << 3; + if (ni->mft_no == FILE_Bitmap) + ntfs_set_lcn_empty_bits(vol, index, value, len << 3); + } + /* + * The currently mapped page is the last one. If the last byte is + * partial, modify the appropriate bits in it. Note, @len is the + * position of the last byte inside the page. + */ + if (cnt) { + u8 *byte; + + WARN_ON(cnt > 7); + + bit = cnt; + byte = kaddr + len; + if (ni->mft_no == FILE_Bitmap) + ntfs_set_lcn_empty_bits(vol, index, value, bit); + while (bit--) { + if (value) + *byte |= 1 << bit; + else + *byte &= ~(1 << bit); + } + } +done: + /* We are done. Unmap the folio and return success. */ + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(kaddr); + folio_put(folio); + ntfs_debug("Done."); + return 0; +rollback: + /* + * Current state: + * - no pages are mapped + * - @count - @cnt is the number of bits that have been modified + */ + if (is_rollback) + return PTR_ERR(folio); + if (count != cnt) + pos = __ntfs_bitmap_set_bits_in_run(vi, start_bit, count - cnt, + value ? 0 : 1, true); + else + pos = 0; + if (!pos) { + /* Rollback was successful. */ + ntfs_error(vi->i_sb, + "Failed to map subsequent page (error %li), aborting.", + PTR_ERR(folio)); + } else { + /* Rollback failed. */ + ntfs_error(vi->i_sb, + "Failed to map subsequent page (error %li) and rollback failed (error %i). Aborting and leaving inconsistent metadata. Unmount and run chkdsk.", + PTR_ERR(folio), pos); + NVolSetErrors(NTFS_SB(vi->i_sb)); + } + return PTR_ERR(folio); +} diff --git a/fs/ntfs/bitmap.h b/fs/ntfs/bitmap.h new file mode 100644 index 000000000000..31177fe61da6 --- /dev/null +++ b/fs/ntfs/bitmap.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for NTFS kernel bitmap handling. + * + * Copyright (c) 2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_BITMAP_H +#define _LINUX_NTFS_BITMAP_H + +#include + +#include "volume.h" + +int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range); +int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit, + const s64 count, const u8 value, const bool is_rollback); + +/* + * ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value + * @vi: vfs inode describing the bitmap + * @start_bit: first bit to set + * @count: number of bits to set + * @value: value to set the bits to (i.e. 0 or 1) + * + * Set @count bits starting at bit @start_bit in the bitmap described by the + * vfs inode @vi to @value, where @value is either 0 or 1. + * + * Return 0 on success and -errno on error. + */ +static inline int ntfs_bitmap_set_bits_in_run(struct inode *vi, + const s64 start_bit, const s64 count, const u8 value) +{ + return __ntfs_bitmap_set_bits_in_run(vi, start_bit, count, value, + false); +} + +/* + * ntfs_bitmap_set_run - set a run of bits in a bitmap + * @vi: vfs inode describing the bitmap + * @start_bit: first bit to set + * @count: number of bits to set + * + * Set @count bits starting at bit @start_bit in the bitmap described by the + * vfs inode @vi. + * + * Return 0 on success and -errno on error. + */ +static inline int ntfs_bitmap_set_run(struct inode *vi, const s64 start_bit, + const s64 count) +{ + return ntfs_bitmap_set_bits_in_run(vi, start_bit, count, 1); +} + +/* + * ntfs_bitmap_clear_run - clear a run of bits in a bitmap + * @vi: vfs inode describing the bitmap + * @start_bit: first bit to clear + * @count: number of bits to clear + * + * Clear @count bits starting at bit @start_bit in the bitmap described by the + * vfs inode @vi. + * + * Return 0 on success and -errno on error. + */ +static inline int ntfs_bitmap_clear_run(struct inode *vi, const s64 start_bit, + const s64 count) +{ + return ntfs_bitmap_set_bits_in_run(vi, start_bit, count, 0); +} + +/* + * ntfs_bitmap_set_bit - set a bit in a bitmap + * @vi: vfs inode describing the bitmap + * @bit: bit to set + * + * Set bit @bit in the bitmap described by the vfs inode @vi. + * + * Return 0 on success and -errno on error. + */ +static inline int ntfs_bitmap_set_bit(struct inode *vi, const s64 bit) +{ + return ntfs_bitmap_set_run(vi, bit, 1); +} + +/* + * ntfs_bitmap_clear_bit - clear a bit in a bitmap + * @vi: vfs inode describing the bitmap + * @bit: bit to clear + * + * Clear bit @bit in the bitmap described by the vfs inode @vi. + * + * Return 0 on success and -errno on error. + */ +static inline int ntfs_bitmap_clear_bit(struct inode *vi, const s64 bit) +{ + return ntfs_bitmap_clear_run(vi, bit, 1); +} + +#endif /* defined _LINUX_NTFS_BITMAP_H */ diff --git a/fs/ntfs/collate.c b/fs/ntfs/collate.c new file mode 100644 index 000000000000..77e34038902d --- /dev/null +++ b/fs/ntfs/collate.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel collation handling. + * + * Copyright (c) 2004 Anton Altaparmakov + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + */ + +#include "collate.h" +#include "debug.h" +#include "ntfs.h" + +#include + +static int ntfs_collate_binary(struct ntfs_volume *vol, + const void *data1, const u32 data1_len, + const void *data2, const u32 data2_len) +{ + int rc; + + rc = memcmp(data1, data2, min(data1_len, data2_len)); + if (!rc && (data1_len != data2_len)) { + if (data1_len < data2_len) + rc = -1; + else + rc = 1; + } + return rc; +} + +static int ntfs_collate_ntofs_ulong(struct ntfs_volume *vol, + const void *data1, const u32 data1_len, + const void *data2, const u32 data2_len) +{ + int rc; + u32 d1 = le32_to_cpup(data1), d2 = le32_to_cpup(data2); + + if (data1_len != data2_len || data1_len != 4) + return -EINVAL; + + if (d1 < d2) + rc = -1; + else { + if (d1 == d2) + rc = 0; + else + rc = 1; + } + return rc; +} + +/* + * ntfs_collate_ntofs_ulongs - Which of two le32 arrays should be listed first + * @vol: ntfs volume + * @data1: first ulong array to collate + * @data1_len: length in bytes of @data1 + * @data2: second ulong array to collate + * @data2_len: length in bytes of @data2 + * + * Returns: -1, 0 or 1 depending of how the arrays compare + */ +static int ntfs_collate_ntofs_ulongs(struct ntfs_volume *vol, + const void *data1, const u32 data1_len, + const void *data2, const u32 data2_len) +{ + int len; + const __le32 *p1 = data1, *p2 = data2; + u32 d1, d2; + + if (data1_len != data2_len || data1_len & 3) { + ntfs_error(vol->sb, "data1_len or data2_len not valid\n"); + return -1; + } + + len = data1_len; + do { + d1 = le32_to_cpup(p1); + p1++; + d2 = le32_to_cpup(p2); + p2++; + } while (d1 == d2 && (len -= 4) > 0); + return cmp_int(d1, d2); +} + +/* + * ntfs_collate_file_name - Which of two filenames should be listed first + * @vol: ntfs volume + * @data1: first filename to collate + * @data1_len: length in bytes of @data1(unused) + * @data2: second filename to collate + * @data2_len: length in bytes of @data2(unused) + */ +static int ntfs_collate_file_name(struct ntfs_volume *vol, + const void *data1, const u32 data1_len, + const void *data2, const u32 data2_len) +{ + int rc; + + rc = ntfs_file_compare_values(data1, data2, -EINVAL, + IGNORE_CASE, vol->upcase, vol->upcase_len); + if (!rc) + rc = ntfs_file_compare_values(data1, data2, + -EINVAL, CASE_SENSITIVE, vol->upcase, vol->upcase_len); + return rc; +} + +/* + * ntfs_collate - collate two data items using a specified collation rule + * @vol: ntfs volume to which the data items belong + * @cr: collation rule to use when comparing the items + * @data1: first data item to collate + * @data1_len: length in bytes of @data1 + * @data2: second data item to collate + * @data2_len: length in bytes of @data2 + * + * Collate the two data items @data1 and @data2 using the collation rule @cr + * and return -1, 0, ir 1 if @data1 is found, respectively, to collate before, + * to match, or to collate after @data2. return -EINVAL if an error occurred. + */ +int ntfs_collate(struct ntfs_volume *vol, __le32 cr, + const void *data1, const u32 data1_len, + const void *data2, const u32 data2_len) +{ + switch (le32_to_cpu(cr)) { + case le32_to_cpu(COLLATION_BINARY): + return ntfs_collate_binary(vol, data1, data1_len, + data2, data2_len); + case le32_to_cpu(COLLATION_FILE_NAME): + return ntfs_collate_file_name(vol, data1, data1_len, + data2, data2_len); + case le32_to_cpu(COLLATION_NTOFS_ULONG): + return ntfs_collate_ntofs_ulong(vol, data1, data1_len, + data2, data2_len); + case le32_to_cpu(COLLATION_NTOFS_ULONGS): + return ntfs_collate_ntofs_ulongs(vol, data1, data1_len, + data2, data2_len); + default: + ntfs_error(vol->sb, "Unknown collation rule 0x%x", + le32_to_cpu(cr)); + return -EINVAL; + } +} diff --git a/fs/ntfs/collate.h b/fs/ntfs/collate.h new file mode 100644 index 000000000000..8ad1f04be675 --- /dev/null +++ b/fs/ntfs/collate.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for NTFS kernel collation handling. + * + * Copyright (c) 2004 Anton Altaparmakov + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + */ + +#ifndef _LINUX_NTFS_COLLATE_H +#define _LINUX_NTFS_COLLATE_H + +#include "volume.h" + +static inline bool ntfs_is_collation_rule_supported(__le32 cr) +{ + int i; + + if (unlikely(cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG && + cr != COLLATION_FILE_NAME) && cr != COLLATION_NTOFS_ULONGS) + return false; + i = le32_to_cpu(cr); + if (likely(((i >= 0) && (i <= 0x02)) || + ((i >= 0x10) && (i <= 0x13)))) + return true; + return false; +} + +int ntfs_collate(struct ntfs_volume *vol, __le32 cr, + const void *data1, const u32 data1_len, + const void *data2, const u32 data2_len); + +#endif /* _LINUX_NTFS_COLLATE_H */ diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c new file mode 100644 index 000000000000..71a8d9c42674 --- /dev/null +++ b/fs/ntfs/compress.c @@ -0,0 +1,1577 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel compressed attributes handling. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * Copyright (c) 2005 Yura Pakhuchiy + * Copyright (c) 2009-2014 Jean-Pierre Andre + * Copyright (c) 2014 Eric Biggers + */ + +#include +#include +#include +#include + +#include "attrib.h" +#include "inode.h" +#include "debug.h" +#include "ntfs.h" +#include "lcnalloc.h" +#include "mft.h" + +/* + * Constants used in the compression code + */ +enum { + /* Token types and access mask. */ + NTFS_SYMBOL_TOKEN = 0, + NTFS_PHRASE_TOKEN = 1, + NTFS_TOKEN_MASK = 1, + + /* Compression sub-block constants. */ + NTFS_SB_SIZE_MASK = 0x0fff, + NTFS_SB_SIZE = 0x1000, + NTFS_SB_IS_COMPRESSED = 0x8000, + + /* + * The maximum compression block size is by definition 16 * the cluster + * size, with the maximum supported cluster size being 4kiB. Thus the + * maximum compression buffer size is 64kiB, so we use this when + * initializing the compression buffer. + */ + NTFS_MAX_CB_SIZE = 64 * 1024, +}; + +/* + * ntfs_compression_buffer - one buffer for the decompression engine + */ +static u8 *ntfs_compression_buffer; + +/* + * ntfs_cb_lock - mutex lock which protects ntfs_compression_buffer + */ +static DEFINE_MUTEX(ntfs_cb_lock); + +/* + * allocate_compression_buffers - allocate the decompression buffers + * + * Caller has to hold the ntfs_lock mutex. + * + * Return 0 on success or -ENOMEM if the allocations failed. + */ +int allocate_compression_buffers(void) +{ + if (ntfs_compression_buffer) + return 0; + + ntfs_compression_buffer = vmalloc(NTFS_MAX_CB_SIZE); + if (!ntfs_compression_buffer) + return -ENOMEM; + return 0; +} + +/* + * free_compression_buffers - free the decompression buffers + * + * Caller has to hold the ntfs_lock mutex. + */ +void free_compression_buffers(void) +{ + mutex_lock(&ntfs_cb_lock); + if (!ntfs_compression_buffer) { + mutex_unlock(&ntfs_cb_lock); + return; + } + + vfree(ntfs_compression_buffer); + ntfs_compression_buffer = NULL; + mutex_unlock(&ntfs_cb_lock); +} + +/* + * zero_partial_compressed_page - zero out of bounds compressed page region + * @page: page to zero + * @initialized_size: initialized size of the attribute + */ +static void zero_partial_compressed_page(struct page *page, + const s64 initialized_size) +{ + u8 *kp = page_address(page); + unsigned int kp_ofs; + + ntfs_debug("Zeroing page region outside initialized size."); + if (((s64)page->__folio_index << PAGE_SHIFT) >= initialized_size) { + clear_page(kp); + return; + } + kp_ofs = initialized_size & ~PAGE_MASK; + memset(kp + kp_ofs, 0, PAGE_SIZE - kp_ofs); +} + +/* + * handle_bounds_compressed_page - test for&handle out of bounds compressed page + * @page: page to check and handle + * @i_size: file size + * @initialized_size: initialized size of the attribute + */ +static inline void handle_bounds_compressed_page(struct page *page, + const loff_t i_size, const s64 initialized_size) +{ + if ((page->__folio_index >= (initialized_size >> PAGE_SHIFT)) && + (initialized_size < i_size)) + zero_partial_compressed_page(page, initialized_size); +} + +/* + * ntfs_decompress - decompress a compression block into an array of pages + * @dest_pages: destination array of pages + * @completed_pages: scratch space to track completed pages + * @dest_index: current index into @dest_pages (IN/OUT) + * @dest_ofs: current offset within @dest_pages[@dest_index] (IN/OUT) + * @dest_max_index: maximum index into @dest_pages (IN) + * @dest_max_ofs: maximum offset within @dest_pages[@dest_max_index] (IN) + * @xpage: the target page (-1 if none) (IN) + * @xpage_done: set to 1 if xpage was completed successfully (IN/OUT) + * @cb_start: compression block to decompress (IN) + * @cb_size: size of compression block @cb_start in bytes (IN) + * @i_size: file size when we started the read (IN) + * @initialized_size: initialized file size when we started the read (IN) + * + * The caller must have disabled preemption. ntfs_decompress() reenables it when + * the critical section is finished. + * + * This decompresses the compression block @cb_start into the array of + * destination pages @dest_pages starting at index @dest_index into @dest_pages + * and at offset @dest_pos into the page @dest_pages[@dest_index]. + * + * When the page @dest_pages[@xpage] is completed, @xpage_done is set to 1. + * If xpage is -1 or @xpage has not been completed, @xpage_done is not modified. + * + * @cb_start is a pointer to the compression block which needs decompressing + * and @cb_size is the size of @cb_start in bytes (8-64kiB). + * + * Return 0 if success or -EOVERFLOW on error in the compressed stream. + * @xpage_done indicates whether the target page (@dest_pages[@xpage]) was + * completed during the decompression of the compression block (@cb_start). + * + * Warning: This function *REQUIRES* PAGE_SIZE >= 4096 or it will blow up + * unpredicatbly! You have been warned! + * + * Note to hackers: This function may not sleep until it has finished accessing + * the compression block @cb_start as it is a per-CPU buffer. + */ +static int ntfs_decompress(struct page *dest_pages[], int completed_pages[], + int *dest_index, int *dest_ofs, const int dest_max_index, + const int dest_max_ofs, const int xpage, char *xpage_done, + u8 *const cb_start, const u32 cb_size, const loff_t i_size, + const s64 initialized_size) +{ + /* + * Pointers into the compressed data, i.e. the compression block (cb), + * and the therein contained sub-blocks (sb). + */ + u8 *cb_end = cb_start + cb_size; /* End of cb. */ + u8 *cb = cb_start; /* Current position in cb. */ + u8 *cb_sb_start = cb; /* Beginning of the current sb in the cb. */ + u8 *cb_sb_end; /* End of current sb / beginning of next sb. */ + + /* Variables for uncompressed data / destination. */ + struct page *dp; /* Current destination page being worked on. */ + u8 *dp_addr; /* Current pointer into dp. */ + u8 *dp_sb_start; /* Start of current sub-block in dp. */ + u8 *dp_sb_end; /* End of current sb in dp (dp_sb_start + NTFS_SB_SIZE). */ + u16 do_sb_start; /* @dest_ofs when starting this sub-block. */ + u16 do_sb_end; /* @dest_ofs of end of this sb (do_sb_start + NTFS_SB_SIZE). */ + + /* Variables for tag and token parsing. */ + u8 tag; /* Current tag. */ + int token; /* Loop counter for the eight tokens in tag. */ + int nr_completed_pages = 0; + + /* Default error code. */ + int err = -EOVERFLOW; + + ntfs_debug("Entering, cb_size = 0x%x.", cb_size); +do_next_sb: + ntfs_debug("Beginning sub-block at offset = 0x%zx in the cb.", + cb - cb_start); + /* + * Have we reached the end of the compression block or the end of the + * decompressed data? The latter can happen for example if the current + * position in the compression block is one byte before its end so the + * first two checks do not detect it. + */ + if (cb == cb_end || !le16_to_cpup((__le16 *)cb) || + (*dest_index == dest_max_index && + *dest_ofs == dest_max_ofs)) { + int i; + + ntfs_debug("Completed. Returning success (0)."); + err = 0; +return_error: + /* We can sleep from now on, so we drop lock. */ + mutex_unlock(&ntfs_cb_lock); + /* Second stage: finalize completed pages. */ + if (nr_completed_pages > 0) { + for (i = 0; i < nr_completed_pages; i++) { + int di = completed_pages[i]; + + dp = dest_pages[di]; + /* + * If we are outside the initialized size, zero + * the out of bounds page range. + */ + handle_bounds_compressed_page(dp, i_size, + initialized_size); + flush_dcache_page(dp); + kunmap_local(page_address(dp)); + SetPageUptodate(dp); + unlock_page(dp); + if (di == xpage) + *xpage_done = 1; + else + put_page(dp); + dest_pages[di] = NULL; + } + } + return err; + } + + /* Setup offsets for the current sub-block destination. */ + do_sb_start = *dest_ofs; + do_sb_end = do_sb_start + NTFS_SB_SIZE; + + /* Check that we are still within allowed boundaries. */ + if (*dest_index == dest_max_index && do_sb_end > dest_max_ofs) + goto return_overflow; + + /* Does the minimum size of a compressed sb overflow valid range? */ + if (cb + 6 > cb_end) + goto return_overflow; + + /* Setup the current sub-block source pointers and validate range. */ + cb_sb_start = cb; + cb_sb_end = cb_sb_start + (le16_to_cpup((__le16 *)cb) & NTFS_SB_SIZE_MASK) + + 3; + if (cb_sb_end > cb_end) + goto return_overflow; + + /* Get the current destination page. */ + dp = dest_pages[*dest_index]; + if (!dp) { + /* No page present. Skip decompression of this sub-block. */ + cb = cb_sb_end; + + /* Advance destination position to next sub-block. */ + *dest_ofs = (*dest_ofs + NTFS_SB_SIZE) & ~PAGE_MASK; + if (!*dest_ofs && (++*dest_index > dest_max_index)) + goto return_overflow; + goto do_next_sb; + } + + /* We have a valid destination page. Setup the destination pointers. */ + dp_addr = (u8 *)page_address(dp) + do_sb_start; + + /* Now, we are ready to process the current sub-block (sb). */ + if (!(le16_to_cpup((__le16 *)cb) & NTFS_SB_IS_COMPRESSED)) { + ntfs_debug("Found uncompressed sub-block."); + /* This sb is not compressed, just copy it into destination. */ + + /* Advance source position to first data byte. */ + cb += 2; + + /* An uncompressed sb must be full size. */ + if (cb_sb_end - cb != NTFS_SB_SIZE) + goto return_overflow; + + /* Copy the block and advance the source position. */ + memcpy(dp_addr, cb, NTFS_SB_SIZE); + cb += NTFS_SB_SIZE; + + /* Advance destination position to next sub-block. */ + *dest_ofs += NTFS_SB_SIZE; + *dest_ofs &= ~PAGE_MASK; + if (!(*dest_ofs)) { +finalize_page: + /* + * First stage: add current page index to array of + * completed pages. + */ + completed_pages[nr_completed_pages++] = *dest_index; + if (++*dest_index > dest_max_index) + goto return_overflow; + } + goto do_next_sb; + } + ntfs_debug("Found compressed sub-block."); + /* This sb is compressed, decompress it into destination. */ + + /* Setup destination pointers. */ + dp_sb_start = dp_addr; + dp_sb_end = dp_sb_start + NTFS_SB_SIZE; + + /* Forward to the first tag in the sub-block. */ + cb += 2; +do_next_tag: + if (cb == cb_sb_end) { + /* Check if the decompressed sub-block was not full-length. */ + if (dp_addr < dp_sb_end) { + int nr_bytes = do_sb_end - *dest_ofs; + + ntfs_debug("Filling incomplete sub-block with zeroes."); + /* Zero remainder and update destination position. */ + memset(dp_addr, 0, nr_bytes); + *dest_ofs += nr_bytes; + } + /* We have finished the current sub-block. */ + *dest_ofs &= ~PAGE_MASK; + if (!(*dest_ofs)) + goto finalize_page; + goto do_next_sb; + } + + /* Check we are still in range. */ + if (cb > cb_sb_end || dp_addr > dp_sb_end) + goto return_overflow; + + /* Get the next tag and advance to first token. */ + tag = *cb++; + + /* Parse the eight tokens described by the tag. */ + for (token = 0; token < 8; token++, tag >>= 1) { + register u16 i; + u16 lg, pt, length, max_non_overlap; + u8 *dp_back_addr; + + /* Check if we are done / still in range. */ + if (cb >= cb_sb_end || dp_addr > dp_sb_end) + break; + + /* Determine token type and parse appropriately.*/ + if ((tag & NTFS_TOKEN_MASK) == NTFS_SYMBOL_TOKEN) { + /* + * We have a symbol token, copy the symbol across, and + * advance the source and destination positions. + */ + *dp_addr++ = *cb++; + ++*dest_ofs; + + /* Continue with the next token. */ + continue; + } + + /* + * We have a phrase token. Make sure it is not the first tag in + * the sb as this is illegal and would confuse the code below. + */ + if (dp_addr == dp_sb_start) + goto return_overflow; + + /* + * Determine the number of bytes to go back (p) and the number + * of bytes to copy (l). We use an optimized algorithm in which + * we first calculate log2(current destination position in sb), + * which allows determination of l and p in O(1) rather than + * O(n). We just need an arch-optimized log2() function now. + */ + lg = 0; + for (i = *dest_ofs - do_sb_start - 1; i >= 0x10; i >>= 1) + lg++; + + /* Get the phrase token into i. */ + pt = le16_to_cpup((__le16 *)cb); + + /* + * Calculate starting position of the byte sequence in + * the destination using the fact that p = (pt >> (12 - lg)) + 1 + * and make sure we don't go too far back. + */ + dp_back_addr = dp_addr - (pt >> (12 - lg)) - 1; + if (dp_back_addr < dp_sb_start) + goto return_overflow; + + /* Now calculate the length of the byte sequence. */ + length = (pt & (0xfff >> lg)) + 3; + + /* Advance destination position and verify it is in range. */ + *dest_ofs += length; + if (*dest_ofs > do_sb_end) + goto return_overflow; + + /* The number of non-overlapping bytes. */ + max_non_overlap = dp_addr - dp_back_addr; + + if (length <= max_non_overlap) { + /* The byte sequence doesn't overlap, just copy it. */ + memcpy(dp_addr, dp_back_addr, length); + + /* Advance destination pointer. */ + dp_addr += length; + } else { + /* + * The byte sequence does overlap, copy non-overlapping + * part and then do a slow byte by byte copy for the + * overlapping part. Also, advance the destination + * pointer. + */ + memcpy(dp_addr, dp_back_addr, max_non_overlap); + dp_addr += max_non_overlap; + dp_back_addr += max_non_overlap; + length -= max_non_overlap; + while (length--) + *dp_addr++ = *dp_back_addr++; + } + + /* Advance source position and continue with the next token. */ + cb += 2; + } + + /* No tokens left in the current tag. Continue with the next tag. */ + goto do_next_tag; + +return_overflow: + ntfs_error(NULL, "Failed. Returning -EOVERFLOW."); + goto return_error; +} + +/* + * ntfs_read_compressed_block - read a compressed block into the page cache + * @folio: locked folio in the compression block(s) we need to read + * + * When we are called the page has already been verified to be locked and the + * attribute is known to be non-resident, not encrypted, but compressed. + * + * 1. Determine which compression block(s) @page is in. + * 2. Get hold of all pages corresponding to this/these compression block(s). + * 3. Read the (first) compression block. + * 4. Decompress it into the corresponding pages. + * 5. Throw the compressed data away and proceed to 3. for the next compression + * block or return success if no more compression blocks left. + * + * Warning: We have to be careful what we do about existing pages. They might + * have been written to so that we would lose data if we were to just overwrite + * them with the out-of-date uncompressed data. + */ +int ntfs_read_compressed_block(struct folio *folio) +{ + struct page *page = &folio->page; + loff_t i_size; + s64 initialized_size; + struct address_space *mapping = page->mapping; + struct ntfs_inode *ni = NTFS_I(mapping->host); + struct ntfs_volume *vol = ni->vol; + struct super_block *sb = vol->sb; + struct runlist_element *rl; + unsigned long flags; + u8 *cb, *cb_pos, *cb_end; + unsigned long offset, index = page->__folio_index; + u32 cb_size = ni->itype.compressed.block_size; + u64 cb_size_mask = cb_size - 1UL; + s64 vcn; + s64 lcn; + /* The first wanted vcn (minimum alignment is PAGE_SIZE). */ + s64 start_vcn = (((s64)index << PAGE_SHIFT) & ~cb_size_mask) >> + vol->cluster_size_bits; + /* + * The first vcn after the last wanted vcn (minimum alignment is again + * PAGE_SIZE. + */ + s64 end_vcn = ((((s64)(index + 1UL) << PAGE_SHIFT) + cb_size - 1) + & ~cb_size_mask) >> vol->cluster_size_bits; + /* Number of compression blocks (cbs) in the wanted vcn range. */ + unsigned int nr_cbs = ntfs_cluster_to_bytes(vol, end_vcn - start_vcn) >> + ni->itype.compressed.block_size_bits; + /* + * Number of pages required to store the uncompressed data from all + * compression blocks (cbs) overlapping @page. Due to alignment + * guarantees of start_vcn and end_vcn, no need to round up here. + */ + unsigned int nr_pages = ntfs_cluster_to_pidx(vol, end_vcn - start_vcn); + unsigned int xpage, max_page, cur_page, cur_ofs, i, page_ofs, page_index; + unsigned int cb_clusters, cb_max_ofs; + int cb_max_page, err = 0; + struct page **pages; + int *completed_pages; + unsigned char xpage_done = 0; + struct page *lpage; + + ntfs_debug("Entering, page->index = 0x%lx, cb_size = 0x%x, nr_pages = %i.", + index, cb_size, nr_pages); + /* + * Bad things happen if we get here for anything that is not an + * unnamed $DATA attribute. + */ + if (ni->type != AT_DATA || ni->name_len) { + unlock_page(page); + return -EIO; + } + + pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_NOFS); + completed_pages = kmalloc_array(nr_pages + 1, sizeof(int), GFP_NOFS); + + if (unlikely(!pages || !completed_pages)) { + kfree(pages); + kfree(completed_pages); + unlock_page(page); + ntfs_error(vol->sb, "Failed to allocate internal buffers."); + return -ENOMEM; + } + + /* + * We have already been given one page, this is the one we must do. + * Once again, the alignment guarantees keep it simple. + */ + offset = ntfs_cluster_to_pidx(vol, start_vcn); + xpage = index - offset; + pages[xpage] = page; + /* + * The remaining pages need to be allocated and inserted into the page + * cache, alignment guarantees keep all the below much simpler. (-8 + */ + read_lock_irqsave(&ni->size_lock, flags); + i_size = i_size_read(VFS_I(ni)); + initialized_size = ni->initialized_size; + read_unlock_irqrestore(&ni->size_lock, flags); + max_page = ((i_size + PAGE_SIZE - 1) >> PAGE_SHIFT) - + offset; + /* Is the page fully outside i_size? (truncate in progress) */ + if (xpage >= max_page) { + kfree(pages); + kfree(completed_pages); + zero_user_segments(page, 0, PAGE_SIZE, 0, 0); + ntfs_debug("Compressed read outside i_size - truncated?"); + SetPageUptodate(page); + unlock_page(page); + return 0; + } + if (nr_pages < max_page) + max_page = nr_pages; + + for (i = 0; i < max_page; i++, offset++) { + if (i != xpage) + pages[i] = grab_cache_page_nowait(mapping, offset); + page = pages[i]; + if (page) { + /* + * We only (re)read the page if it isn't already read + * in and/or dirty or we would be losing data or at + * least wasting our time. + */ + if (!PageDirty(page) && (!PageUptodate(page))) { + kmap_local_page(page); + continue; + } + unlock_page(page); + put_page(page); + pages[i] = NULL; + } + } + + /* + * We have the runlist, and all the destination pages we need to fill. + * Now read the first compression block. + */ + cur_page = 0; + cur_ofs = 0; + cb_clusters = ni->itype.compressed.block_clusters; +do_next_cb: + nr_cbs--; + + mutex_lock(&ntfs_cb_lock); + if (!ntfs_compression_buffer) + if (allocate_compression_buffers()) { + mutex_unlock(&ntfs_cb_lock); + goto err_out; + } + + + cb = ntfs_compression_buffer; + cb_pos = cb; + cb_end = cb + cb_size; + + rl = NULL; + for (vcn = start_vcn, start_vcn += cb_clusters; vcn < start_vcn; + vcn++) { + bool is_retry = false; + + if (!rl) { +lock_retry_remap: + down_read(&ni->runlist.lock); + rl = ni->runlist.rl; + } + if (likely(rl != NULL)) { + /* Seek to element containing target vcn. */ + while (rl->length && rl[1].vcn <= vcn) + rl++; + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); + } else + lcn = LCN_RL_NOT_MAPPED; + ntfs_debug("Reading vcn = 0x%llx, lcn = 0x%llx.", + (unsigned long long)vcn, + (unsigned long long)lcn); + if (lcn < 0) { + /* + * When we reach the first sparse cluster we have + * finished with the cb. + */ + if (lcn == LCN_HOLE) + break; + if (is_retry || lcn != LCN_RL_NOT_MAPPED) { + mutex_unlock(&ntfs_cb_lock); + goto rl_err; + } + is_retry = true; + /* + * Attempt to map runlist, dropping lock for the + * duration. + */ + up_read(&ni->runlist.lock); + if (!ntfs_map_runlist(ni, vcn)) + goto lock_retry_remap; + mutex_unlock(&ntfs_cb_lock); + goto map_rl_err; + } + + page_ofs = ntfs_cluster_to_poff(vol, lcn); + page_index = ntfs_cluster_to_pidx(vol, lcn); + + lpage = read_mapping_page(sb->s_bdev->bd_mapping, + page_index, NULL); + if (IS_ERR(lpage)) { + err = PTR_ERR(lpage); + mutex_unlock(&ntfs_cb_lock); + goto read_err; + } + + lock_page(lpage); + memcpy(cb_pos, page_address(lpage) + page_ofs, + vol->cluster_size); + unlock_page(lpage); + put_page(lpage); + cb_pos += vol->cluster_size; + } + + /* Release the lock if we took it. */ + if (rl) + up_read(&ni->runlist.lock); + + /* Just a precaution. */ + if (cb_pos + 2 <= cb + cb_size) + *(u16 *)cb_pos = 0; + + /* Reset cb_pos back to the beginning. */ + cb_pos = cb; + + /* We now have both source (if present) and destination. */ + ntfs_debug("Successfully read the compression block."); + + /* The last page and maximum offset within it for the current cb. */ + cb_max_page = (cur_page << PAGE_SHIFT) + cur_ofs + cb_size; + cb_max_ofs = cb_max_page & ~PAGE_MASK; + cb_max_page >>= PAGE_SHIFT; + + /* Catch end of file inside a compression block. */ + if (cb_max_page > max_page) + cb_max_page = max_page; + + if (vcn == start_vcn - cb_clusters) { + /* Sparse cb, zero out page range overlapping the cb. */ + ntfs_debug("Found sparse compression block."); + /* We can sleep from now on, so we drop lock. */ + mutex_unlock(&ntfs_cb_lock); + if (cb_max_ofs) + cb_max_page--; + for (; cur_page < cb_max_page; cur_page++) { + page = pages[cur_page]; + if (page) { + if (likely(!cur_ofs)) + clear_page(page_address(page)); + else + memset(page_address(page) + cur_ofs, 0, + PAGE_SIZE - + cur_ofs); + flush_dcache_page(page); + kunmap_local(page_address(page)); + SetPageUptodate(page); + unlock_page(page); + if (cur_page == xpage) + xpage_done = 1; + else + put_page(page); + pages[cur_page] = NULL; + } + cb_pos += PAGE_SIZE - cur_ofs; + cur_ofs = 0; + if (cb_pos >= cb_end) + break; + } + /* If we have a partial final page, deal with it now. */ + if (cb_max_ofs && cb_pos < cb_end) { + page = pages[cur_page]; + if (page) + memset(page_address(page) + cur_ofs, 0, + cb_max_ofs - cur_ofs); + /* + * No need to update cb_pos at this stage: + * cb_pos += cb_max_ofs - cur_ofs; + */ + cur_ofs = cb_max_ofs; + } + } else if (vcn == start_vcn) { + /* We can't sleep so we need two stages. */ + unsigned int cur2_page = cur_page; + unsigned int cur_ofs2 = cur_ofs; + u8 *cb_pos2 = cb_pos; + + ntfs_debug("Found uncompressed compression block."); + /* Uncompressed cb, copy it to the destination pages. */ + if (cb_max_ofs) + cb_max_page--; + /* First stage: copy data into destination pages. */ + for (; cur_page < cb_max_page; cur_page++) { + page = pages[cur_page]; + if (page) + memcpy(page_address(page) + cur_ofs, cb_pos, + PAGE_SIZE - cur_ofs); + cb_pos += PAGE_SIZE - cur_ofs; + cur_ofs = 0; + if (cb_pos >= cb_end) + break; + } + /* If we have a partial final page, deal with it now. */ + if (cb_max_ofs && cb_pos < cb_end) { + page = pages[cur_page]; + if (page) + memcpy(page_address(page) + cur_ofs, cb_pos, + cb_max_ofs - cur_ofs); + cb_pos += cb_max_ofs - cur_ofs; + cur_ofs = cb_max_ofs; + } + /* We can sleep from now on, so drop lock. */ + mutex_unlock(&ntfs_cb_lock); + /* Second stage: finalize pages. */ + for (; cur2_page < cb_max_page; cur2_page++) { + page = pages[cur2_page]; + if (page) { + /* + * If we are outside the initialized size, zero + * the out of bounds page range. + */ + handle_bounds_compressed_page(page, i_size, + initialized_size); + flush_dcache_page(page); + kunmap_local(page_address(page)); + SetPageUptodate(page); + unlock_page(page); + if (cur2_page == xpage) + xpage_done = 1; + else + put_page(page); + pages[cur2_page] = NULL; + } + cb_pos2 += PAGE_SIZE - cur_ofs2; + cur_ofs2 = 0; + if (cb_pos2 >= cb_end) + break; + } + } else { + /* Compressed cb, decompress it into the destination page(s). */ + unsigned int prev_cur_page = cur_page; + + ntfs_debug("Found compressed compression block."); + err = ntfs_decompress(pages, completed_pages, &cur_page, + &cur_ofs, cb_max_page, cb_max_ofs, xpage, + &xpage_done, cb_pos, cb_size - (cb_pos - cb), + i_size, initialized_size); + /* + * We can sleep from now on, lock already dropped by + * ntfs_decompress(). + */ + if (err) { + ntfs_error(vol->sb, + "ntfs_decompress() failed in inode 0x%llx with error code %i. Skipping this compression block.", + ni->mft_no, -err); + /* Release the unfinished pages. */ + for (; prev_cur_page < cur_page; prev_cur_page++) { + page = pages[prev_cur_page]; + if (page) { + flush_dcache_page(page); + kunmap_local(page_address(page)); + unlock_page(page); + if (prev_cur_page != xpage) + put_page(page); + pages[prev_cur_page] = NULL; + } + } + } + } + + /* Do we have more work to do? */ + if (nr_cbs) + goto do_next_cb; + + /* Clean up if we have any pages left. Should never happen. */ + for (cur_page = 0; cur_page < max_page; cur_page++) { + page = pages[cur_page]; + if (page) { + ntfs_error(vol->sb, + "Still have pages left! Terminating them with extreme prejudice. Inode 0x%llx, page index 0x%lx.", + ni->mft_no, page->__folio_index); + flush_dcache_page(page); + kunmap_local(page_address(page)); + unlock_page(page); + if (cur_page != xpage) + put_page(page); + pages[cur_page] = NULL; + } + } + + /* We no longer need the list of pages. */ + kfree(pages); + kfree(completed_pages); + + /* If we have completed the requested page, we return success. */ + if (likely(xpage_done)) + return 0; + + ntfs_debug("Failed. Returning error code %s.", err == -EOVERFLOW ? + "EOVERFLOW" : (!err ? "EIO" : "unknown error")); + return err < 0 ? err : -EIO; + +map_rl_err: + ntfs_error(vol->sb, "ntfs_map_runlist() failed. Cannot read compression block."); + goto err_out; + +rl_err: + up_read(&ni->runlist.lock); + ntfs_error(vol->sb, "ntfs_rl_vcn_to_lcn() failed. Cannot read compression block."); + goto err_out; + +read_err: + up_read(&ni->runlist.lock); + ntfs_error(vol->sb, "IO error while reading compressed data."); + +err_out: + for (i = cur_page; i < max_page; i++) { + page = pages[i]; + if (page) { + flush_dcache_page(page); + kunmap_local(page_address(page)); + unlock_page(page); + if (i != xpage) + put_page(page); + } + } + kfree(pages); + kfree(completed_pages); + return -EIO; +} + +/* + * Match length at or above which ntfs_best_match() will stop searching for + * longer matches. + */ +#define NICE_MATCH_LEN 18 + +/* + * Maximum number of potential matches that ntfs_best_match() will consider at + * each position. + */ +#define MAX_SEARCH_DEPTH 24 + +/* log base 2 of the number of entries in the hash table for match-finding. */ +#define HASH_SHIFT 14 + +/* + * Constant for the multiplicative hash function. These hashing constants + * are used solely for the match-finding algorithm during compression. + * They are NOT part of the on-disk format. The decompressor does not + * utilize this hash. + */ +#define HASH_MULTIPLIER 0x1E35A7BD + +struct compress_context { + const unsigned char *inbuf; + int bufsize; + int size; + int rel; + int mxsz; + s16 head[1 << HASH_SHIFT]; + s16 prev[NTFS_SB_SIZE]; +}; + +/* + * Hash the next 3-byte sequence in the input buffer + */ +static inline unsigned int ntfs_hash(const u8 *p) +{ + u32 str; + u32 hash; + + /* + * Unaligned access allowed, and little endian CPU. + * Callers ensure that at least 4 (not 3) bytes are remaining. + */ + str = *(const u32 *)p & 0xFFFFFF; + hash = str * HASH_MULTIPLIER; + + /* High bits are more random than the low bits. */ + return hash >> (32 - HASH_SHIFT); +} + +/* + * Search for the longest sequence matching current position + * + * A hash table, each entry of which points to a chain of sequence + * positions sharing the corresponding hash code, is maintained to speed up + * searching for matches. To maintain the hash table, either + * ntfs_best_match() or ntfs_skip_position() has to be called for each + * consecutive position. + * + * This function is heavily used; it has to be optimized carefully. + * + * This function sets pctx->size and pctx->rel to the length and offset, + * respectively, of the longest match found. + * + * The minimum match length is assumed to be 3, and the maximum match + * length is assumed to be pctx->mxsz. If this function produces + * pctx->size < 3, then no match was found. + * + * Note: for the following reasons, this function is not guaranteed to find + * *the* longest match up to pctx->mxsz: + * + * (1) If this function finds a match of NICE_MATCH_LEN bytes or greater, + * it ends early because a match this long is good enough and it's not + * worth spending more time searching. + * + * (2) If this function considers MAX_SEARCH_DEPTH matches with a single + * position, it ends early and returns the longest match found so far. + * This saves a lot of time on degenerate inputs. + */ +static void ntfs_best_match(struct compress_context *pctx, const int i, + int best_len) +{ + const u8 * const inbuf = pctx->inbuf; + const u8 * const strptr = &inbuf[i]; /* String we're matching against */ + s16 * const prev = pctx->prev; + const int max_len = min(pctx->bufsize - i, pctx->mxsz); + const int nice_len = min(NICE_MATCH_LEN, max_len); + int depth_remaining = MAX_SEARCH_DEPTH; + const u8 *best_matchptr = strptr; + unsigned int hash; + s16 cur_match; + const u8 *matchptr; + int len; + + if (max_len < 4) + goto out; + + /* Insert the current sequence into the appropriate hash chain. */ + hash = ntfs_hash(strptr); + cur_match = pctx->head[hash]; + prev[i] = cur_match; + pctx->head[hash] = i; + + if (best_len >= max_len) { + /* + * Lazy match is being attempted, but there aren't enough length + * bits remaining to code a longer match. + */ + goto out; + } + + /* Search the appropriate hash chain for matches. */ + + for (; cur_match >= 0 && depth_remaining--; cur_match = prev[cur_match]) { + matchptr = &inbuf[cur_match]; + + /* + * Considering the potential match at 'matchptr': is it longer + * than 'best_len'? + * + * The bytes at index 'best_len' are the most likely to differ, + * so check them first. + * + * The bytes at indices 'best_len - 1' and '0' are less + * important to check separately. But doing so still gives a + * slight performance improvement, at least on x86_64, probably + * because they create separate branches for the CPU to predict + * independently of the branches in the main comparison loops. + */ + if (matchptr[best_len] != strptr[best_len] || + matchptr[best_len - 1] != strptr[best_len - 1] || + matchptr[0] != strptr[0]) + goto next_match; + + for (len = 1; len < best_len - 1; len++) + if (matchptr[len] != strptr[len]) + goto next_match; + + /* + * The match is the longest found so far --- + * at least 'best_len' + 1 bytes. Continue extending it. + */ + + best_matchptr = matchptr; + + do { + if (++best_len >= nice_len) { + /* + * 'nice_len' reached; don't waste time + * searching for longer matches. Extend the + * match as far as possible and terminate the + * search. + */ + while (best_len < max_len && + (best_matchptr[best_len] == + strptr[best_len])) + best_len++; + goto out; + } + } while (best_matchptr[best_len] == strptr[best_len]); + + /* Found a longer match, but 'nice_len' not yet reached. */ + +next_match: + /* Continue to next match in the chain. */ + ; + } + + /* + * Reached end of chain, or ended early due to reaching the maximum + * search depth. + */ + +out: + /* Return the longest match we were able to find. */ + pctx->size = best_len; + pctx->rel = best_matchptr - strptr; /* given as a negative number! */ +} + +/* + * Advance the match-finder, but don't search for matches. + */ +static void ntfs_skip_position(struct compress_context *pctx, const int i) +{ + unsigned int hash; + + if (pctx->bufsize - i < 4) + return; + + /* Insert the current sequence into the appropriate hash chain. */ + hash = ntfs_hash(pctx->inbuf + i); + pctx->prev[i] = pctx->head[hash]; + pctx->head[hash] = i; +} + +/* + * Compress a 4096-byte block + * + * Returns a header of two bytes followed by the compressed data. + * If compression is not effective, the header and an uncompressed + * block is returned. + * + * Note : two bytes may be output before output buffer overflow + * is detected, so a 4100-bytes output buffer must be reserved. + * + * Returns the size of the compressed block, including the + * header (minimal size is 2, maximum size is 4098) + * 0 if an error has been met. + */ +static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize, + char *outbuf) +{ + struct compress_context *pctx; + int i; /* current position */ + int j; /* end of best match from current position */ + int k; /* end of best match from next position */ + int offs; /* offset to best match */ + int bp; /* bits to store offset */ + int bp_cur; /* saved bits to store offset at current position */ + int mxoff; /* max match offset : 1 << bp */ + unsigned int xout; + unsigned int q; /* aggregated offset and size */ + int have_match; /* do we have a match at the current position? */ + char *ptag; /* location reserved for a tag */ + int tag; /* current value of tag */ + int ntag; /* count of bits still undefined in tag */ + + pctx = kvzalloc(sizeof(struct compress_context), GFP_NOFS); + if (!pctx) + return -ENOMEM; + + /* + * All hash chains start as empty. The special value '-1' indicates the + * end of each hash chain. + */ + memset(pctx->head, 0xFF, sizeof(pctx->head)); + + pctx->inbuf = (const unsigned char *)inbuf; + pctx->bufsize = bufsize; + xout = 2; + i = 0; + bp = 4; + mxoff = 1 << bp; + pctx->mxsz = (1 << (16 - bp)) + 2; + have_match = 0; + tag = 0; + ntag = 8; + ptag = &outbuf[xout++]; + + while ((i < bufsize) && (xout < (NTFS_SB_SIZE + 2))) { + + /* + * This implementation uses "lazy" parsing: it always chooses + * the longest match, unless the match at the next position is + * longer. This is the same strategy used by the high + * compression modes of zlib. + */ + if (!have_match) { + /* + * Find the longest match at the current position. But + * first adjust the maximum match length if needed. + * (This loop might need to run more than one time in + * the case that we just output a long match.) + */ + while (mxoff < i) { + bp++; + mxoff <<= 1; + pctx->mxsz = (pctx->mxsz + 2) >> 1; + } + ntfs_best_match(pctx, i, 2); + } + + if (pctx->size >= 3) { + /* Found a match at the current position. */ + j = i + pctx->size; + bp_cur = bp; + offs = pctx->rel; + + if (pctx->size >= NICE_MATCH_LEN) { + /* Choose long matches immediately. */ + q = (~offs << (16 - bp_cur)) + (j - i - 3); + outbuf[xout++] = q & 255; + outbuf[xout++] = (q >> 8) & 255; + tag |= (1 << (8 - ntag)); + + if (j == bufsize) { + /* + * Shortcut if the match extends to the + * end of the buffer. + */ + i = j; + --ntag; + break; + } + i += 1; + do { + ntfs_skip_position(pctx, i); + } while (++i != j); + have_match = 0; + } else { + /* + * Check for a longer match at the next + * position. + */ + + /* + * Doesn't need to be while() since we just + * adjusted the maximum match length at the + * previous position. + */ + if (mxoff < i + 1) { + bp++; + mxoff <<= 1; + pctx->mxsz = (pctx->mxsz + 2) >> 1; + } + ntfs_best_match(pctx, i + 1, pctx->size); + k = i + 1 + pctx->size; + + if (k > (j + 1)) { + /* + * Next match is longer. + * Output a literal. + */ + outbuf[xout++] = inbuf[i++]; + have_match = 1; + } else { + /* + * Next match isn't longer. + * Output the current match. + */ + q = (~offs << (16 - bp_cur)) + + (j - i - 3); + outbuf[xout++] = q & 255; + outbuf[xout++] = (q >> 8) & 255; + tag |= (1 << (8 - ntag)); + + /* + * The minimum match length is 3, and + * we've run two bytes through the + * matchfinder already. So the minimum + * number of positions we need to skip + * is 1. + */ + i += 2; + do { + ntfs_skip_position(pctx, i); + } while (++i != j); + have_match = 0; + } + } + } else { + /* No match at current position. Output a literal. */ + outbuf[xout++] = inbuf[i++]; + have_match = 0; + } + + /* Store the tag if fully used. */ + if (!--ntag) { + *ptag = tag; + ntag = 8; + ptag = &outbuf[xout++]; + tag = 0; + } + } + + /* Store the last tag if partially used. */ + if (ntag == 8) + xout--; + else + *ptag = tag; + + /* Determine whether to store the data compressed or uncompressed. */ + if ((i >= bufsize) && (xout < (NTFS_SB_SIZE + 2))) { + /* Compressed. */ + outbuf[0] = (xout - 3) & 255; + outbuf[1] = 0xb0 + (((xout - 3) >> 8) & 15); + } else { + /* Uncompressed. */ + memcpy(&outbuf[2], inbuf, bufsize); + if (bufsize < NTFS_SB_SIZE) + memset(&outbuf[bufsize + 2], 0, NTFS_SB_SIZE - bufsize); + outbuf[0] = 0xff; + outbuf[1] = 0x3f; + xout = NTFS_SB_SIZE + 2; + } + + /* + * Free the compression context and return the total number of bytes + * written to 'outbuf'. + */ + kvfree(pctx); + return xout; +} + +static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, + int pages_per_cb) +{ + struct ntfs_volume *vol = ni->vol; + char *outbuf = NULL, *pbuf, *inbuf; + u32 compsz, p, insz = pages_per_cb << PAGE_SHIFT; + s32 rounded, bio_size; + unsigned int sz, bsz; + bool fail = false, allzeroes; + /* a single compressed zero */ + static char onezero[] = {0x01, 0xb0, 0x00, 0x00}; + /* a couple of compressed zeroes */ + static char twozeroes[] = {0x02, 0xb0, 0x00, 0x00, 0x00}; + /* more compressed zeroes, to be followed by some count */ + static char morezeroes[] = {0x03, 0xb0, 0x02, 0x00}; + struct page **pages_disk = NULL, *pg; + s64 bio_lcn; + struct runlist_element *rlc, *rl; + int i, err; + int pages_count = (round_up(ni->itype.compressed.block_size + 2 * + (ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2, PAGE_SIZE)) / PAGE_SIZE; + size_t new_rl_count; + struct bio *bio = NULL; + loff_t new_length; + s64 new_vcn; + + inbuf = vmap(pages, pages_per_cb, VM_MAP, PAGE_KERNEL_RO); + if (!inbuf) + return -ENOMEM; + + /* may need 2 extra bytes per block and 2 more bytes */ + pages_disk = kcalloc(pages_count, sizeof(struct page *), GFP_NOFS); + if (!pages_disk) { + vunmap(inbuf); + return -ENOMEM; + } + + for (i = 0; i < pages_count; i++) { + pg = alloc_page(GFP_KERNEL); + if (!pg) { + err = -ENOMEM; + goto out; + } + pages_disk[i] = pg; + lock_page(pg); + kmap_local_page(pg); + } + + outbuf = vmap(pages_disk, pages_count, VM_MAP, PAGE_KERNEL); + if (!outbuf) { + err = -ENOMEM; + goto out; + } + + compsz = 0; + allzeroes = true; + for (p = 0; (p < insz) && !fail; p += NTFS_SB_SIZE) { + if ((p + NTFS_SB_SIZE) < insz) + bsz = NTFS_SB_SIZE; + else + bsz = insz - p; + pbuf = &outbuf[compsz]; + sz = ntfs_compress_block(&inbuf[p], bsz, pbuf); + /* fail if all the clusters (or more) are needed */ + if (!sz || ((compsz + sz + vol->cluster_size + 2) > + ni->itype.compressed.block_size)) + fail = true; + else { + if (allzeroes) { + /* check whether this is all zeroes */ + switch (sz) { + case 4: + allzeroes = !memcmp(pbuf, onezero, 4); + break; + case 5: + allzeroes = !memcmp(pbuf, twozeroes, 5); + break; + case 6: + allzeroes = !memcmp(pbuf, morezeroes, 4); + break; + default: + allzeroes = false; + break; + } + } + compsz += sz; + } + } + + if (!fail && !allzeroes) { + outbuf[compsz++] = 0; + outbuf[compsz++] = 0; + rounded = ((compsz - 1) | (vol->cluster_size - 1)) + 1; + memset(&outbuf[compsz], 0, rounded - compsz); + bio_size = rounded; + pages = pages_disk; + } else if (allzeroes) { + err = 0; + goto out; + } else { + bio_size = insz; + } + + new_vcn = ntfs_bytes_to_cluster(vol, pos & ~(ni->itype.compressed.block_size - 1)); + new_length = ntfs_bytes_to_cluster(vol, round_up(bio_size, vol->cluster_size)); + + err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, ni->itype.compressed.block_clusters); + if (err < 0) + goto out; + + rlc = ntfs_cluster_alloc(vol, new_vcn, new_length, -1, DATA_ZONE, + false, true, true); + if (IS_ERR(rlc)) { + err = PTR_ERR(rlc); + goto out; + } + + bio_lcn = rlc->lcn; + down_write(&ni->runlist.lock); + rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count); + if (IS_ERR(rl)) { + up_write(&ni->runlist.lock); + ntfs_error(vol->sb, "Failed to merge runlists"); + err = PTR_ERR(rl); + if (ntfs_cluster_free_from_rl(vol, rlc)) + ntfs_error(vol->sb, "Failed to free hot clusters."); + kvfree(rlc); + goto out; + } + + ni->runlist.count = new_rl_count; + ni->runlist.rl = rl; + + err = ntfs_attr_update_mapping_pairs(ni, 0); + up_write(&ni->runlist.lock); + if (err) { + err = -EIO; + goto out; + } + + i = 0; + while (bio_size > 0) { + int page_size; + + if (bio_size >= PAGE_SIZE) { + page_size = PAGE_SIZE; + bio_size -= PAGE_SIZE; + } else { + page_size = bio_size; + bio_size = 0; + } + +setup_bio: + if (!bio) { + bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, + GFP_NOIO); + bio->bi_iter.bi_sector = + ntfs_bytes_to_sector(vol, + ntfs_cluster_to_bytes(vol, bio_lcn + i)); + } + + if (!bio_add_page(bio, pages[i], page_size, 0)) { + err = submit_bio_wait(bio); + bio_put(bio); + if (err) + goto out; + bio = NULL; + goto setup_bio; + } + i++; + } + + err = submit_bio_wait(bio); + bio_put(bio); +out: + vunmap(outbuf); + for (i = 0; i < pages_count; i++) { + pg = pages_disk[i]; + if (pg) { + kunmap_local(page_address(pg)); + unlock_page(pg); + put_page(pg); + } + } + kfree(pages_disk); + vunmap(inbuf); + NInoSetFileNameDirty(ni); + mark_mft_record_dirty(ni); + + return err; +} + +int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, + struct iov_iter *from) +{ + struct folio *folio; + struct page **pages = NULL, *page; + int pages_per_cb = ni->itype.compressed.block_size >> PAGE_SHIFT; + int cb_size = ni->itype.compressed.block_size, cb_off, err = 0; + int i, ip; + size_t written = 0; + struct address_space *mapping = VFS_I(ni)->i_mapping; + + if (NInoCompressed(ni) && pos + count > ni->allocated_size) { + int err; + loff_t end = pos + count; + + err = ntfs_attr_expand(ni, end, + round_up(end, ni->itype.compressed.block_size)); + if (err) + return err; + } + + pages = kmalloc_array(pages_per_cb, sizeof(struct page *), GFP_NOFS); + if (!pages) + return -ENOMEM; + + while (count) { + pgoff_t index; + size_t copied, bytes; + int off; + + off = pos & (cb_size - 1); + bytes = cb_size - off; + if (bytes > count) + bytes = count; + + cb_off = pos & ~(cb_size - 1); + index = cb_off >> PAGE_SHIFT; + + if (unlikely(fault_in_iov_iter_readable(from, bytes))) { + err = -EFAULT; + goto out; + } + + for (i = 0; i < pages_per_cb; i++) { + folio = read_mapping_folio(mapping, index + i, NULL); + if (IS_ERR(folio)) { + for (ip = 0; ip < i; ip++) { + folio_unlock(page_folio(pages[ip])); + folio_put(page_folio(pages[ip])); + } + err = PTR_ERR(folio); + goto out; + } + + folio_lock(folio); + pages[i] = folio_page(folio, 0); + } + + WARN_ON(!bytes); + copied = 0; + ip = off >> PAGE_SHIFT; + off = offset_in_page(pos); + + for (;;) { + size_t cp, tail = PAGE_SIZE - off; + + page = pages[ip]; + cp = copy_folio_from_iter_atomic(page_folio(page), off, + min(tail, bytes), from); + flush_dcache_page(page); + + copied += cp; + bytes -= cp; + if (!bytes || !cp) + break; + + if (cp < tail) { + off += cp; + } else { + ip++; + off = 0; + } + } + + err = ntfs_write_cb(ni, pos, pages, pages_per_cb); + + for (i = 0; i < pages_per_cb; i++) { + folio = page_folio(pages[i]); + if (i < ip) { + folio_clear_dirty(folio); + folio_mark_uptodate(folio); + } + folio_unlock(folio); + folio_put(folio); + } + + if (err) + goto out; + + cond_resched(); + pos += copied; + written += copied; + count = iov_iter_count(from); + } + +out: + kfree(pages); + if (err < 0) + written = err; + + return written; +} diff --git a/fs/ntfs/debug.c b/fs/ntfs/debug.c new file mode 100644 index 000000000000..f241ebc97d37 --- /dev/null +++ b/fs/ntfs/debug.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel debug support. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include "debug.h" + +/* + * __ntfs_warning - output a warning to the syslog + * @function: name of function outputting the warning + * @sb: super block of mounted ntfs filesystem + * @fmt: warning string containing format specifications + * @...: a variable number of arguments specified in @fmt + * + * Outputs a warning to the syslog for the mounted ntfs filesystem described + * by @sb. + * + * @fmt and the corresponding @... is printf style format string containing + * the warning string and the corresponding format arguments, respectively. + * + * @function is the name of the function from which __ntfs_warning is being + * called. + * + * Note, you should be using debug.h::ntfs_warning(@sb, @fmt, @...) instead + * as this provides the @function parameter automatically. + */ +void __ntfs_warning(const char *function, const struct super_block *sb, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int flen = 0; + + if (function) + flen = strlen(function); + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; +#ifdef DEBUG + if (sb) + pr_warn("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_warn("%s(): %pV\n", flen ? function : "", &vaf); +#else + if (sb) + pr_warn_ratelimited("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_warn_ratelimited("%s(): %pV\n", flen ? function : "", &vaf); +#endif + va_end(args); +} + +/* + * __ntfs_error - output an error to the syslog + * @function: name of function outputting the error + * @sb: super block of mounted ntfs filesystem + * @fmt: error string containing format specifications + * @...: a variable number of arguments specified in @fmt + * + * Outputs an error to the syslog for the mounted ntfs filesystem described + * by @sb. + * + * @fmt and the corresponding @... is printf style format string containing + * the error string and the corresponding format arguments, respectively. + * + * @function is the name of the function from which __ntfs_error is being + * called. + * + * Note, you should be using debug.h::ntfs_error(@sb, @fmt, @...) instead + * as this provides the @function parameter automatically. + */ +void __ntfs_error(const char *function, struct super_block *sb, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int flen = 0; + + if (function) + flen = strlen(function); + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; +#ifdef DEBUG + if (sb) + pr_err("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_err("%s(): %pV\n", flen ? function : "", &vaf); +#else + if (sb) + pr_err_ratelimited("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_err_ratelimited("%s(): %pV\n", flen ? function : "", &vaf); +#endif + va_end(args); + + if (sb) + ntfs_handle_error(sb); +} + +#ifdef DEBUG + +/* If 1, output debug messages, and if 0, don't. */ +int debug_msgs; + +void __ntfs_debug(const char *file, int line, const char *function, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int flen = 0; + + if (!debug_msgs) + return; + if (function) + flen = strlen(function); + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + pr_debug("(%s, %d): %s(): %pV", file, line, flen ? function : "", &vaf); + va_end(args); +} + +/* Dump a runlist. Caller has to provide synchronisation for @rl. */ +void ntfs_debug_dump_runlist(const struct runlist_element *rl) +{ + int i; + const char *lcn_str[5] = { "LCN_DELALLOC ", "LCN_HOLE ", + "LCN_RL_NOT_MAPPED", "LCN_ENOENT ", + "LCN_unknown " }; + + if (!debug_msgs) + return; + pr_debug("Dumping runlist (values in hex):\n"); + if (!rl) { + pr_debug("Run list not present.\n"); + return; + } + pr_debug("VCN LCN Run length\n"); + for (i = 0; ; i++) { + s64 lcn = (rl + i)->lcn; + + if (lcn < 0) { + int index = -lcn - 1; + + if (index > -LCN_ENOENT - 1) + index = 3; + pr_debug("%-16Lx %s %-16Lx%s\n", + (long long)(rl + i)->vcn, lcn_str[index], + (long long)(rl + i)->length, + (rl + i)->length ? "" : + " (runlist end)"); + } else + pr_debug("%-16Lx %-16Lx %-16Lx%s\n", + (long long)(rl + i)->vcn, + (long long)(rl + i)->lcn, + (long long)(rl + i)->length, + (rl + i)->length ? "" : + " (runlist end)"); + if (!(rl + i)->length) + break; + } +} + +#endif diff --git a/fs/ntfs/debug.h b/fs/ntfs/debug.h new file mode 100644 index 000000000000..d8831d492d7d --- /dev/null +++ b/fs/ntfs/debug.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * NTFS kernel debug support. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_DEBUG_H +#define _LINUX_NTFS_DEBUG_H + +#include + +#include "runlist.h" + +#ifdef DEBUG + +extern int debug_msgs; + +extern __printf(4, 5) +void __ntfs_debug(const char *file, int line, const char *function, + const char *format, ...); +/* + * ntfs_debug - write a debug level message to syslog + * @f: a printf format string containing the message + * @...: the variables to substitute into @f + * + * ntfs_debug() writes a DEBUG level message to the syslog but only if the + * driver was compiled with -DDEBUG. Otherwise, the call turns into a NOP. + */ +#define ntfs_debug(f, a...) \ + __ntfs_debug(__FILE__, __LINE__, __func__, f, ##a) + +void ntfs_debug_dump_runlist(const struct runlist_element *rl); + +#else /* !DEBUG */ + +#define ntfs_debug(fmt, ...) \ +do { \ + if (0) \ + no_printk(fmt, ##__VA_ARGS__); \ +} while (0) + +#define ntfs_debug_dump_runlist(rl) \ +do { \ + if (0) \ + (void)rl; \ +} while (0) + +#endif /* !DEBUG */ + +extern __printf(3, 4) +void __ntfs_warning(const char *function, const struct super_block *sb, + const char *fmt, ...); +#define ntfs_warning(sb, f, a...) __ntfs_warning(__func__, sb, f, ##a) + +extern __printf(3, 4) +void __ntfs_error(const char *function, struct super_block *sb, + const char *fmt, ...); +#define ntfs_error(sb, f, a...) __ntfs_error(__func__, sb, f, ##a) + +void ntfs_handle_error(struct super_block *sb); + +#endif /* _LINUX_NTFS_DEBUG_H */ diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c new file mode 100644 index 000000000000..bfa904d2ce66 --- /dev/null +++ b/fs/ntfs/dir.c @@ -0,0 +1,1238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel directory operations. + * + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include + +#include "dir.h" +#include "mft.h" +#include "ntfs.h" +#include "index.h" +#include "reparse.h" + +#include + +/* + * The little endian Unicode string $I30 as a global constant. + */ +__le16 I30[5] = { cpu_to_le16('$'), cpu_to_le16('I'), + cpu_to_le16('3'), cpu_to_le16('0'), 0 }; + +/* + * ntfs_lookup_inode_by_name - find an inode in a directory given its name + * @dir_ni: ntfs inode of the directory in which to search for the name + * @uname: Unicode name for which to search in the directory + * @uname_len: length of the name @uname in Unicode characters + * @res: return the found file name if necessary (see below) + * + * Look for an inode with name @uname in the directory with inode @dir_ni. + * ntfs_lookup_inode_by_name() walks the contents of the directory looking for + * the Unicode name. If the name is found in the directory, the corresponding + * inode number (>= 0) is returned as a mft reference in cpu format, i.e. it + * is a 64-bit number containing the sequence number. + * + * On error, a negative value is returned corresponding to the error code. In + * particular if the inode is not found -ENOENT is returned. Note that you + * can't just check the return value for being negative, you have to check the + * inode number for being negative which you can extract using MREC(return + * value). + * + * Note, @uname_len does not include the (optional) terminating NULL character. + * + * Note, we look for a case sensitive match first but we also look for a case + * insensitive match at the same time. If we find a case insensitive match, we + * save that for the case that we don't find an exact match, where we return + * the case insensitive match and setup @res (which we allocate!) with the mft + * reference, the file name type, length and with a copy of the little endian + * Unicode file name itself. If we match a file name which is in the DOS name + * space, we only return the mft reference and file name type in @res. + * ntfs_lookup() then uses this to find the long file name in the inode itself. + * This is to avoid polluting the dcache with short file names. We want them to + * work but we don't care for how quickly one can access them. This also fixes + * the dcache aliasing issues. + * + * Locking: - Caller must hold i_mutex on the directory. + * - Each page cache page in the index allocation mapping must be + * locked whilst being accessed otherwise we may find a corrupt + * page due to it being under ->writepage at the moment which + * applies the mst protection fixups before writing out and then + * removes them again after the write is complete after which it + * unlocks the page. + */ +u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, + const int uname_len, struct ntfs_name **res) +{ + struct ntfs_volume *vol = dir_ni->vol; + struct super_block *sb = vol->sb; + struct inode *ia_vi = NULL; + struct mft_record *m; + struct index_root *ir; + struct index_entry *ie; + struct index_block *ia; + u8 *index_end; + u64 mref; + struct ntfs_attr_search_ctx *ctx; + int err, rc; + s64 vcn, old_vcn; + struct address_space *ia_mapping; + struct folio *folio; + u8 *kaddr = NULL; + struct ntfs_name *name = NULL; + + /* Get hold of the mft record for the directory. */ + m = map_mft_record(dir_ni); + if (IS_ERR(m)) { + ntfs_error(sb, "map_mft_record() failed with error code %ld.", + -PTR_ERR(m)); + return ERR_MREF(PTR_ERR(m)); + } + ctx = ntfs_attr_get_search_ctx(dir_ni, m); + if (unlikely(!ctx)) { + err = -ENOMEM; + goto err_out; + } + /* Find the index root attribute in the mft record. */ + err = ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (unlikely(err)) { + if (err == -ENOENT) { + ntfs_error(sb, + "Index root attribute missing in directory inode 0x%llx.", + dir_ni->mft_no); + err = -EIO; + } + goto err_out; + } + /* Get to the index root value (it's been verified in read_inode). */ + ir = (struct index_root *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length); + /* The first index entry. */ + ie = (struct index_entry *)((u8 *)&ir->index + + le32_to_cpu(ir->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) { + /* Bounds checks. */ + if ((u8 *)ie < (u8 *)ctx->mrec || + (u8 *)ie + sizeof(struct index_entry_header) > index_end || + (u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) > + index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) + goto dir_err_out; + /* + * The last entry cannot contain a name. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ie->flags & INDEX_ENTRY_END) + break; + /* Key length should not be zero if it is not last entry. */ + if (!ie->key_length) + goto dir_err_out; + /* Check the consistency of an index entry */ + if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME, + dir_ni->mft_no)) + goto dir_err_out; + /* + * We perform a case sensitive comparison and if that matches + * we are done and return the mft reference of the inode (i.e. + * the inode number together with the sequence number for + * consistency checking). We convert it to cpu format before + * returning. + */ + if (ntfs_are_names_equal(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + CASE_SENSITIVE, vol->upcase, vol->upcase_len)) { +found_it: + /* + * We have a perfect match, so we don't need to care + * about having matched imperfectly before, so we can + * free name and set *res to NULL. + * However, if the perfect match is a short file name, + * we need to signal this through *res, so that + * ntfs_lookup() can fix dcache aliasing issues. + * As an optimization we just reuse an existing + * allocation of *res. + */ + if (ie->key.file_name.file_name_type == FILE_NAME_DOS) { + if (!name) { + name = kmalloc(sizeof(struct ntfs_name), + GFP_NOFS); + if (!name) { + err = -ENOMEM; + goto err_out; + } + } + name->mref = le64_to_cpu( + ie->data.dir.indexed_file); + name->type = FILE_NAME_DOS; + name->len = 0; + *res = name; + } else { + kfree(name); + *res = NULL; + } + mref = le64_to_cpu(ie->data.dir.indexed_file); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(dir_ni); + return mref; + } + /* + * For a case insensitive mount, we also perform a case + * insensitive comparison (provided the file name is not in the + * POSIX namespace). If the comparison matches, and the name is + * in the WIN32 namespace, we cache the filename in *res so + * that the caller, ntfs_lookup(), can work on it. If the + * comparison matches, and the name is in the DOS namespace, we + * only cache the mft reference and the file name type (we set + * the name length to zero for simplicity). + */ + if ((!NVolCaseSensitive(vol) || + ie->key.file_name.file_name_type == FILE_NAME_DOS) && + ntfs_are_names_equal(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + IGNORE_CASE, vol->upcase, + vol->upcase_len)) { + int name_size = sizeof(struct ntfs_name); + u8 type = ie->key.file_name.file_name_type; + u8 len = ie->key.file_name.file_name_length; + + /* Only one case insensitive matching name allowed. */ + if (name) { + ntfs_error(sb, + "Found already allocated name in phase 1. Please run chkdsk"); + goto dir_err_out; + } + + if (type != FILE_NAME_DOS) + name_size += len * sizeof(__le16); + name = kmalloc(name_size, GFP_NOFS); + if (!name) { + err = -ENOMEM; + goto err_out; + } + name->mref = le64_to_cpu(ie->data.dir.indexed_file); + name->type = type; + if (type != FILE_NAME_DOS) { + name->len = len; + memcpy(name->name, ie->key.file_name.file_name, + len * sizeof(__le16)); + } else + name->len = 0; + *res = name; + } + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_collate_names(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If uname collates before the name of the current entry, there + * is definitely no such name in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + /* The names are not equal, continue the search. */ + if (rc) + continue; + /* + * Names match with case insensitive comparison, now try the + * case sensitive comparison, which is required for proper + * collation. + */ + rc = ntfs_collate_names(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (rc == -1) + break; + if (rc) + continue; + /* + * Perfect match, this will never happen as the + * ntfs_are_names_equal() call will have gotten a match but we + * still treat it correctly. + */ + goto found_it; + } + /* + * We have finished with this index without success. Check for the + * presence of a child node and if not present return -ENOENT, unless + * we have got a matching name cached in name in which case return the + * mft reference associated with it. + */ + if (!(ie->flags & INDEX_ENTRY_NODE)) { + if (name) { + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(dir_ni); + return name->mref; + } + ntfs_debug("Entry not found."); + err = -ENOENT; + goto err_out; + } /* Child node present, descend into it. */ + + /* Get the starting vcn of the index_block holding the child node. */ + vcn = le64_to_cpup((__le64 *)((u8 *)ie + le16_to_cpu(ie->length) - 8)); + + /* + * We are done with the index root and the mft record. Release them, + * otherwise we deadlock with read_mapping_folio(). + */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(dir_ni); + m = NULL; + ctx = NULL; + + ia_vi = ntfs_index_iget(VFS_I(dir_ni), I30, 4); + if (IS_ERR(ia_vi)) { + err = PTR_ERR(ia_vi); + goto err_out; + } + + ia_mapping = ia_vi->i_mapping; +descend_into_child_node: + /* + * Convert vcn to index into the index allocation attribute in units + * of PAGE_SIZE and map the page cache page, reading it from + * disk if necessary. + */ + folio = read_mapping_folio(ia_mapping, vcn << + dir_ni->itype.index.vcn_size_bits >> PAGE_SHIFT, NULL); + if (IS_ERR(folio)) { + ntfs_error(sb, "Failed to map directory index page, error %ld.", + -PTR_ERR(folio)); + err = PTR_ERR(folio); + goto err_out; + } + + folio_lock(folio); + kaddr = kmalloc(PAGE_SIZE, GFP_NOFS); + if (!kaddr) { + err = -ENOMEM; + folio_unlock(folio); + folio_put(folio); + goto unm_err_out; + } + + memcpy_from_folio(kaddr, folio, 0, PAGE_SIZE); + post_read_mst_fixup((struct ntfs_record *)kaddr, PAGE_SIZE); + folio_unlock(folio); + folio_put(folio); +fast_descend_into_child_node: + /* Get to the index allocation block. */ + ia = (struct index_block *)(kaddr + ((vcn << + dir_ni->itype.index.vcn_size_bits) & ~PAGE_MASK)); + /* Bounds checks. */ + if ((u8 *)ia < kaddr || (u8 *)ia > kaddr + PAGE_SIZE) { + ntfs_error(sb, + "Out of bounds check failed. Corrupt directory inode 0x%llx or driver bug.", + dir_ni->mft_no); + goto unm_err_out; + } + /* Catch multi sector transfer fixup errors. */ + if (unlikely(!ntfs_is_indx_record(ia->magic))) { + ntfs_error(sb, + "Directory index record with vcn 0x%llx is corrupt. Corrupt inode 0x%llx. Run chkdsk.", + vcn, dir_ni->mft_no); + goto unm_err_out; + } + if (le64_to_cpu(ia->index_block_vcn) != vcn) { + ntfs_error(sb, + "Actual VCN (0x%llx) of index buffer is different from expected VCN (0x%llx). Directory inode 0x%llx is corrupt or driver bug.", + le64_to_cpu(ia->index_block_vcn), + vcn, dir_ni->mft_no); + goto unm_err_out; + } + if (le32_to_cpu(ia->index.allocated_size) + 0x18 != + dir_ni->itype.index.block_size) { + ntfs_error(sb, + "Index buffer (VCN 0x%llx) of directory inode 0x%llx has a size (%u) differing from the directory specified size (%u). Directory inode is corrupt or driver bug.", + vcn, dir_ni->mft_no, + le32_to_cpu(ia->index.allocated_size) + 0x18, + dir_ni->itype.index.block_size); + goto unm_err_out; + } + index_end = (u8 *)ia + dir_ni->itype.index.block_size; + if (index_end > kaddr + PAGE_SIZE) { + ntfs_error(sb, + "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.", + vcn, dir_ni->mft_no); + goto unm_err_out; + } + index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length); + if (index_end > (u8 *)ia + dir_ni->itype.index.block_size) { + ntfs_error(sb, + "Size of index buffer (VCN 0x%llx) of directory inode 0x%llx exceeds maximum size.", + vcn, dir_ni->mft_no); + goto unm_err_out; + } + /* The first index entry. */ + ie = (struct index_entry *)((u8 *)&ia->index + + le32_to_cpu(ia->index.entries_offset)); + /* + * Iterate similar to above big loop but applied to index buffer, thus + * loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) { + /* Bounds checks. */ + if ((u8 *)ie < (u8 *)ia || + (u8 *)ie + sizeof(struct index_entry_header) > index_end || + (u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) > + index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) { + ntfs_error(sb, "Index entry out of bounds in directory inode 0x%llx.", + dir_ni->mft_no); + goto unm_err_out; + } + /* + * The last entry cannot contain a name. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ie->flags & INDEX_ENTRY_END) + break; + /* Key length should not be zero if it is not last entry. */ + if (!ie->key_length) + goto unm_err_out; + /* Check the consistency of an index entry */ + if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME, + dir_ni->mft_no)) + goto unm_err_out; + /* + * We perform a case sensitive comparison and if that matches + * we are done and return the mft reference of the inode (i.e. + * the inode number together with the sequence number for + * consistency checking). We convert it to cpu format before + * returning. + */ + if (ntfs_are_names_equal(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + CASE_SENSITIVE, vol->upcase, vol->upcase_len)) { +found_it2: + /* + * We have a perfect match, so we don't need to care + * about having matched imperfectly before, so we can + * free name and set *res to NULL. + * However, if the perfect match is a short file name, + * we need to signal this through *res, so that + * ntfs_lookup() can fix dcache aliasing issues. + * As an optimization we just reuse an existing + * allocation of *res. + */ + if (ie->key.file_name.file_name_type == FILE_NAME_DOS) { + if (!name) { + name = kmalloc(sizeof(struct ntfs_name), + GFP_NOFS); + if (!name) { + err = -ENOMEM; + goto unm_err_out; + } + } + name->mref = le64_to_cpu( + ie->data.dir.indexed_file); + name->type = FILE_NAME_DOS; + name->len = 0; + *res = name; + } else { + kfree(name); + *res = NULL; + } + mref = le64_to_cpu(ie->data.dir.indexed_file); + kfree(kaddr); + iput(ia_vi); + return mref; + } + /* + * For a case insensitive mount, we also perform a case + * insensitive comparison (provided the file name is not in the + * POSIX namespace). If the comparison matches, and the name is + * in the WIN32 namespace, we cache the filename in *res so + * that the caller, ntfs_lookup(), can work on it. If the + * comparison matches, and the name is in the DOS namespace, we + * only cache the mft reference and the file name type (we set + * the name length to zero for simplicity). + */ + if ((!NVolCaseSensitive(vol) || + ie->key.file_name.file_name_type == FILE_NAME_DOS) && + ntfs_are_names_equal(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + IGNORE_CASE, vol->upcase, + vol->upcase_len)) { + int name_size = sizeof(struct ntfs_name); + u8 type = ie->key.file_name.file_name_type; + u8 len = ie->key.file_name.file_name_length; + + /* Only one case insensitive matching name allowed. */ + if (name) { + ntfs_error(sb, + "Found already allocated name in phase 2. Please run chkdsk"); + kfree(kaddr); + goto dir_err_out; + } + + if (type != FILE_NAME_DOS) + name_size += len * sizeof(__le16); + name = kmalloc(name_size, GFP_NOFS); + if (!name) { + err = -ENOMEM; + goto unm_err_out; + } + name->mref = le64_to_cpu(ie->data.dir.indexed_file); + name->type = type; + if (type != FILE_NAME_DOS) { + name->len = len; + memcpy(name->name, ie->key.file_name.file_name, + len * sizeof(__le16)); + } else + name->len = 0; + *res = name; + } + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_collate_names(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If uname collates before the name of the current entry, there + * is definitely no such name in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + /* The names are not equal, continue the search. */ + if (rc) + continue; + /* + * Names match with case insensitive comparison, now try the + * case sensitive comparison, which is required for proper + * collation. + */ + rc = ntfs_collate_names(uname, uname_len, + (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (rc == -1) + break; + if (rc) + continue; + /* + * Perfect match, this will never happen as the + * ntfs_are_names_equal() call will have gotten a match but we + * still treat it correctly. + */ + goto found_it2; + } + /* + * We have finished with this index buffer without success. Check for + * the presence of a child node. + */ + if (ie->flags & INDEX_ENTRY_NODE) { + if ((ia->index.flags & NODE_MASK) == LEAF_NODE) { + ntfs_error(sb, + "Index entry with child node found in a leaf node in directory inode 0x%llx.", + dir_ni->mft_no); + goto unm_err_out; + } + /* Child node present, descend into it. */ + old_vcn = vcn; + vcn = le64_to_cpup((__le64 *)((u8 *)ie + + le16_to_cpu(ie->length) - 8)); + if (vcn >= 0) { + /* + * If vcn is in the same page cache page as old_vcn we + * recycle the mapped page. + */ + if (ntfs_cluster_to_pidx(vol, old_vcn) == + ntfs_cluster_to_pidx(vol, vcn)) + goto fast_descend_into_child_node; + kfree(kaddr); + kaddr = NULL; + goto descend_into_child_node; + } + ntfs_error(sb, "Negative child node vcn in directory inode 0x%llx.", + dir_ni->mft_no); + goto unm_err_out; + } + /* + * No child node present, return -ENOENT, unless we have got a matching + * name cached in name in which case return the mft reference + * associated with it. + */ + if (name) { + kfree(kaddr); + iput(ia_vi); + return name->mref; + } + ntfs_debug("Entry not found."); + err = -ENOENT; +unm_err_out: + kfree(kaddr); +err_out: + if (!err) + err = -EIO; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(dir_ni); + kfree(name); + *res = NULL; + if (!IS_ERR_OR_NULL(ia_vi)) + iput(ia_vi); + return ERR_MREF(err); +dir_err_out: + ntfs_error(sb, "Corrupt directory. Aborting lookup."); + goto err_out; +} + +/* + * ntfs_filldir - ntfs specific filldir method + * @vol: current ntfs volume + * @ndir: ntfs inode of current directory + * @ia_page: page in which the index allocation buffer @ie is in resides + * @ie: current index entry + * @name: buffer to use for the converted name + * @actor: what to feed the entries to + * + * Convert the Unicode @name to the loaded NLS and pass it to the @filldir + * callback. + * + * If @ia_page is not NULL it is the locked page containing the index + * allocation block containing the index entry @ie. + * + * Note, we drop (and then reacquire) the page lock on @ia_page across the + * @filldir() call otherwise we would deadlock with NFSd when it calls ->lookup + * since ntfs_lookup() will lock the same page. As an optimization, we do not + * retake the lock if we are returning a non-zero value as ntfs_readdir() + * would need to drop the lock immediately anyway. + */ +static inline int ntfs_filldir(struct ntfs_volume *vol, + struct ntfs_inode *ndir, struct page *ia_page, struct index_entry *ie, + u8 *name, struct dir_context *actor) +{ + unsigned long mref; + int name_len; + unsigned int dt_type; + u8 name_type; + + name_type = ie->key.file_name.file_name_type; + if (name_type == FILE_NAME_DOS) { + ntfs_debug("Skipping DOS name space entry."); + return 0; + } + if (MREF_LE(ie->data.dir.indexed_file) == FILE_root) { + ntfs_debug("Skipping root directory self reference entry."); + return 0; + } + if (MREF_LE(ie->data.dir.indexed_file) < FILE_first_user && + !NVolShowSystemFiles(vol)) { + ntfs_debug("Skipping system file."); + return 0; + } + if (!NVolShowHiddenFiles(vol) && + (ie->key.file_name.file_attributes & FILE_ATTR_HIDDEN)) { + ntfs_debug("Skipping hidden file."); + return 0; + } + + name_len = ntfs_ucstonls(vol, (__le16 *)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, &name, + NTFS_MAX_NAME_LEN * NLS_MAX_CHARSET_SIZE + 1); + if (name_len <= 0) { + ntfs_warning(vol->sb, "Skipping unrepresentable inode 0x%llx.", + (long long)MREF_LE(ie->data.dir.indexed_file)); + return 0; + } + + mref = MREF_LE(ie->data.dir.indexed_file); + if (ie->key.file_name.file_attributes & + FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT) + dt_type = DT_DIR; + else if (ie->key.file_name.file_attributes & FILE_ATTR_REPARSE_POINT) + dt_type = ntfs_reparse_tag_dt_types(vol, mref); + else + dt_type = DT_REG; + + /* + * Drop the page lock otherwise we deadlock with NFS when it calls + * ->lookup since ntfs_lookup() will lock the same page. + */ + if (ia_page) + unlock_page(ia_page); + ntfs_debug("Calling filldir for %s with len %i, fpos 0x%llx, inode 0x%lx, DT_%s.", + name, name_len, actor->pos, mref, dt_type == DT_DIR ? "DIR" : "REG"); + if (!dir_emit(actor, name, name_len, mref, dt_type)) + return 1; + /* Relock the page but not if we are aborting ->readdir. */ + if (ia_page) + lock_page(ia_page); + return 0; +} + +struct ntfs_file_private { + void *key; + __le16 key_length; + bool end_in_iterate; + loff_t curr_pos; +}; + +struct ntfs_index_ra { + unsigned long start_index; + unsigned int count; + struct rb_node rb_node; +}; + +static void ntfs_insert_rb(struct ntfs_index_ra *nir, struct rb_root *root) +{ + struct rb_node **new = &root->rb_node, *parent = NULL; + struct ntfs_index_ra *cnir; + + while (*new) { + parent = *new; + cnir = rb_entry(parent, struct ntfs_index_ra, rb_node); + if (nir->start_index < cnir->start_index) + new = &parent->rb_left; + else if (nir->start_index >= cnir->start_index + cnir->count) + new = &parent->rb_right; + else { + pr_err("nir start index : %ld, count : %d, cnir start_index : %ld, count : %d\n", + nir->start_index, nir->count, cnir->start_index, cnir->count); + return; + } + } + + rb_link_node(&nir->rb_node, parent, new); + rb_insert_color(&nir->rb_node, root); +} + +static int ntfs_ia_blocks_readahead(struct ntfs_inode *ia_ni, loff_t pos) +{ + unsigned long dir_start_index, dir_end_index; + struct inode *ia_vi = VFS_I(ia_ni); + struct file_ra_state *dir_ra; + + dir_end_index = (i_size_read(ia_vi) + PAGE_SIZE - 1) >> PAGE_SHIFT; + dir_start_index = (pos + PAGE_SIZE - 1) >> PAGE_SHIFT; + + if (dir_start_index >= dir_end_index) + return 0; + + dir_ra = kzalloc(sizeof(*dir_ra), GFP_NOFS); + if (!dir_ra) + return -ENOMEM; + + file_ra_state_init(dir_ra, ia_vi->i_mapping); + dir_end_index = (i_size_read(ia_vi) + PAGE_SIZE - 1) >> PAGE_SHIFT; + dir_start_index = (pos + PAGE_SIZE - 1) >> PAGE_SHIFT; + dir_ra->ra_pages = dir_end_index - dir_start_index; + page_cache_sync_readahead(ia_vi->i_mapping, dir_ra, NULL, + dir_start_index, dir_end_index - dir_start_index); + kfree(dir_ra); + + return 0; +} + +static int ntfs_readdir(struct file *file, struct dir_context *actor) +{ + struct inode *vdir = file_inode(file); + struct super_block *sb = vdir->i_sb; + struct ntfs_inode *ndir = NTFS_I(vdir); + struct ntfs_volume *vol = NTFS_SB(sb); + struct ntfs_attr_search_ctx *ctx = NULL; + struct ntfs_index_context *ictx = NULL; + u8 *name; + struct index_root *ir; + struct index_entry *next = NULL; + struct ntfs_file_private *private = NULL; + int err = 0; + loff_t ie_pos = 2; /* initialize it with dot and dotdot size */ + struct ntfs_index_ra *nir = NULL; + unsigned long index; + struct rb_root ra_root = RB_ROOT; + struct file_ra_state *ra; + + ntfs_debug("Entering for inode 0x%llx, fpos 0x%llx.", + ndir->mft_no, actor->pos); + + if (file->private_data) { + private = file->private_data; + + if (actor->pos != private->curr_pos) { + /* + * If actor->pos is different from the previous passed + * one, Discard the private->key and fill dirent buffer + * with linear lookup. + */ + kfree(private->key); + private->key = NULL; + private->end_in_iterate = false; + } else if (private->end_in_iterate) { + kfree(private->key); + kfree(file->private_data); + file->private_data = NULL; + return 0; + } + } + + /* Emulate . and .. for all directories. */ + if (!dir_emit_dots(file, actor)) + return 0; + + /* + * Allocate a buffer to store the current name being processed + * converted to format determined by current NLS. + */ + name = kmalloc(NTFS_MAX_NAME_LEN * NLS_MAX_CHARSET_SIZE + 1, GFP_NOFS); + if (unlikely(!name)) + return -ENOMEM; + + mutex_lock_nested(&ndir->mrec_lock, NTFS_INODE_MUTEX_PARENT); + ictx = ntfs_index_ctx_get(ndir, I30, 4); + if (!ictx) { + kfree(name); + mutex_unlock(&ndir->mrec_lock); + return -ENOMEM; + } + + ra = kzalloc(sizeof(struct file_ra_state), GFP_NOFS); + if (!ra) { + kfree(name); + ntfs_index_ctx_put(ictx); + mutex_unlock(&ndir->mrec_lock); + return -ENOMEM; + } + file_ra_state_init(ra, vol->mft_ino->i_mapping); + + if (private && private->key) { + /* + * Find index witk private->key using ntfs_index_lookup() + * instead of linear index lookup. + */ + err = ntfs_index_lookup(private->key, + le16_to_cpu(private->key_length), + ictx); + if (!err) { + next = ictx->entry; + /* + * Update ie_pos with private->curr_pos + * to make next d_off of dirent correct. + */ + ie_pos = private->curr_pos; + + if (actor->pos > vol->mft_record_size && ictx->ia_ni) { + err = ntfs_ia_blocks_readahead(ictx->ia_ni, actor->pos); + if (err) + goto out; + } + + goto nextdir; + } else { + goto out; + } + } else if (!private) { + private = kzalloc(sizeof(struct ntfs_file_private), GFP_KERNEL); + if (!private) { + err = -ENOMEM; + goto out; + } + file->private_data = private; + } + + ctx = ntfs_attr_get_search_ctx(ndir, NULL); + if (!ctx) { + err = -ENOMEM; + goto out; + } + + /* Find the index root attribute in the mft record. */ + if (ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, 0, NULL, 0, + ctx)) { + ntfs_error(sb, "Index root attribute missing in directory inode %llu", + ndir->mft_no); + ntfs_attr_put_search_ctx(ctx); + err = -ENOMEM; + goto out; + } + + /* Get to the index root value. */ + ir = (struct index_root *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + + ictx->ir = ir; + ictx->actx = ctx; + ictx->parent_vcn[ictx->pindex] = VCN_INDEX_ROOT_PARENT; + ictx->is_in_root = true; + ictx->parent_pos[ictx->pindex] = 0; + + ictx->block_size = le32_to_cpu(ir->index_block_size); + if (ictx->block_size < NTFS_BLOCK_SIZE) { + ntfs_error(sb, "Index block size (%d) is smaller than the sector size (%d)", + ictx->block_size, NTFS_BLOCK_SIZE); + err = -EIO; + goto out; + } + + if (vol->cluster_size <= ictx->block_size) + ictx->vcn_size_bits = vol->cluster_size_bits; + else + ictx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS; + + /* The first index entry. */ + next = (struct index_entry *)((u8 *)&ir->index + + le32_to_cpu(ir->index.entries_offset)); + + if (next->flags & INDEX_ENTRY_NODE) { + ictx->ia_ni = ntfs_ia_open(ictx, ictx->idx_ni); + if (!ictx->ia_ni) { + err = -EINVAL; + goto out; + } + + err = ntfs_ia_blocks_readahead(ictx->ia_ni, actor->pos); + if (err) + goto out; + } + + if (next->flags & INDEX_ENTRY_NODE) { + next = ntfs_index_walk_down(next, ictx); + if (!next) { + err = -EIO; + goto out; + } + } + + if (next && !(next->flags & INDEX_ENTRY_END)) + goto nextdir; + + while ((next = ntfs_index_next(next, ictx)) != NULL) { +nextdir: + /* Check the consistency of an index entry */ + if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME, + ndir->mft_no)) { + err = -EIO; + goto out; + } + + if (ie_pos < actor->pos) { + ie_pos += le16_to_cpu(next->length); + continue; + } + + actor->pos = ie_pos; + + index = ntfs_mft_no_to_pidx(vol, + MREF_LE(next->data.dir.indexed_file)); + if (nir) { + struct ntfs_index_ra *cnir; + struct rb_node *node = ra_root.rb_node; + + if (nir->start_index <= index && + index < nir->start_index + nir->count) { + /* No behavior */ + goto filldir; + } + + while (node) { + cnir = rb_entry(node, struct ntfs_index_ra, rb_node); + if (cnir->start_index <= index && + index < cnir->start_index + cnir->count) { + goto filldir; + } else if (cnir->start_index + cnir->count == index) { + cnir->count++; + goto filldir; + } else if (!cnir->start_index && cnir->start_index - 1 == index) { + cnir->start_index = index; + goto filldir; + } + + if (index < cnir->start_index) + node = node->rb_left; + else if (index >= cnir->start_index + cnir->count) + node = node->rb_right; + } + + if (nir->start_index + nir->count == index) { + nir->count++; + } else if (!nir->start_index && nir->start_index - 1 == index) { + nir->start_index = index; + } else if (nir->count > 2) { + ntfs_insert_rb(nir, &ra_root); + nir = NULL; + } else { + nir->start_index = index; + nir->count = 1; + } + } + + if (!nir) { + nir = kzalloc(sizeof(struct ntfs_index_ra), GFP_KERNEL); + if (nir) { + nir->start_index = index; + nir->count = 1; + } + } + +filldir: + /* Submit the name to the filldir callback. */ + err = ntfs_filldir(vol, ndir, NULL, next, name, actor); + if (err) { + /* + * Store index key value to file private_data to start + * from current index offset on next round. + */ + private = file->private_data; + kfree(private->key); + private->key = kmalloc(le16_to_cpu(next->key_length), GFP_KERNEL); + if (!private->key) { + err = -ENOMEM; + goto out; + } + + memcpy(private->key, &next->key.file_name, le16_to_cpu(next->key_length)); + private->key_length = next->key_length; + break; + } + ie_pos += le16_to_cpu(next->length); + } + + if (!err) + private->end_in_iterate = true; + else + err = 0; + + private->curr_pos = actor->pos = ie_pos; +out: + while (!RB_EMPTY_ROOT(&ra_root)) { + struct ntfs_index_ra *cnir; + struct rb_node *node; + + node = rb_first(&ra_root); + cnir = rb_entry(node, struct ntfs_index_ra, rb_node); + ra->ra_pages = cnir->count; + page_cache_sync_readahead(vol->mft_ino->i_mapping, ra, NULL, + cnir->start_index, cnir->count); + rb_erase(node, &ra_root); + kfree(cnir); + } + + if (err) { + if (private) { + private->curr_pos = actor->pos; + private->end_in_iterate = true; + } + err = 0; + } + ntfs_index_ctx_put(ictx); + kfree(name); + kfree(nir); + kfree(ra); + mutex_unlock(&ndir->mrec_lock); + return err; +} + +int ntfs_check_empty_dir(struct ntfs_inode *ni, struct mft_record *ni_mrec) +{ + struct ntfs_attr_search_ctx *ctx; + int ret = 0; + + if (!(ni_mrec->flags & MFT_RECORD_IS_DIRECTORY)) + return 0; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(ni->vol->sb, "Failed to get search context"); + return -ENOMEM; + } + + /* Find the index root attribute in the mft record. */ + ret = ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (ret) { + ntfs_error(ni->vol->sb, "Index root attribute missing in directory inode %llu", + ni->mft_no); + ntfs_attr_put_search_ctx(ctx); + return ret; + } + + /* Non-empty directory? */ + if (le32_to_cpu(ctx->attr->data.resident.value_length) != + sizeof(struct index_root) + sizeof(struct index_entry_header)) { + /* Both ENOTEMPTY and EEXIST are ok. We use the more common. */ + ret = -ENOTEMPTY; + ntfs_debug("Directory is not empty\n"); + } + + ntfs_attr_put_search_ctx(ctx); + + return ret; +} + +/* + * ntfs_dir_open - called when an inode is about to be opened + * @vi: inode to be opened + * @filp: file structure describing the inode + * + * Limit directory size to the page cache limit on architectures where unsigned + * long is 32-bits. This is the most we can do for now without overflowing the + * page cache page index. Doing it this way means we don't run into problems + * because of existing too large directories. It would be better to allow the + * user to read the accessible part of the directory but I doubt very much + * anyone is going to hit this check on a 32-bit architecture, so there is no + * point in adding the extra complexity required to support this. + * + * On 64-bit architectures, the check is hopefully optimized away by the + * compiler. + */ +static int ntfs_dir_open(struct inode *vi, struct file *filp) +{ + if (sizeof(unsigned long) < 8) { + if (i_size_read(vi) > MAX_LFS_FILESIZE) + return -EFBIG; + } + return 0; +} + +static int ntfs_dir_release(struct inode *vi, struct file *filp) +{ + if (filp->private_data) { + kfree(((struct ntfs_file_private *)filp->private_data)->key); + kfree(filp->private_data); + filp->private_data = NULL; + } + return 0; +} + +/* + * ntfs_dir_fsync - sync a directory to disk + * @filp: file describing the directory to be synced + * @start: start offset to be synced + * @end: end offset to be synced + * @datasync: if non-zero only flush user data and not metadata + * + * Data integrity sync of a directory to disk. Used for fsync, fdatasync, and + * msync system calls. This function is based on file.c::ntfs_file_fsync(). + * + * Write the mft record and all associated extent mft records as well as the + * $INDEX_ALLOCATION and $BITMAP attributes and then sync the block device. + * + * If @datasync is true, we do not wait on the inode(s) to be written out + * but we always wait on the page cache pages to be written out. + * + * Note: In the past @filp could be NULL so we ignore it as we don't need it + * anyway. + * + * Locking: Caller must hold i_mutex on the inode. + */ +static int ntfs_dir_fsync(struct file *filp, loff_t start, loff_t end, + int datasync) +{ + struct inode *bmp_vi, *vi = filp->f_mapping->host; + struct ntfs_volume *vol = NTFS_I(vi)->vol; + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_attr_search_ctx *ctx; + struct inode *parent_vi, *ia_vi; + int err, ret; + struct ntfs_attr na; + + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + + if (NVolShutdown(vol)) + return -EIO; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -ENOMEM; + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_CHILD); + while (!(err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, ctx))) { + struct file_name_attr *fn = (struct file_name_attr *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + + if (MREF_LE(fn->parent_directory) == ni->mft_no) + continue; + + parent_vi = ntfs_iget(vi->i_sb, MREF_LE(fn->parent_directory)); + if (IS_ERR(parent_vi)) + continue; + mutex_lock_nested(&NTFS_I(parent_vi)->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + ia_vi = ntfs_index_iget(parent_vi, I30, 4); + mutex_unlock(&NTFS_I(parent_vi)->mrec_lock); + if (IS_ERR(ia_vi)) { + iput(parent_vi); + continue; + } + write_inode_now(ia_vi, 1); + iput(ia_vi); + write_inode_now(parent_vi, 1); + iput(parent_vi); + } + mutex_unlock(&ni->mrec_lock); + ntfs_attr_put_search_ctx(ctx); + + err = file_write_and_wait_range(filp, start, end); + if (err) + return err; + inode_lock(vi); + + /* If the bitmap attribute inode is in memory sync it, too. */ + na.mft_no = vi->i_ino; + na.type = AT_BITMAP; + na.name = I30; + na.name_len = 4; + bmp_vi = ilookup5(vi->i_sb, vi->i_ino, ntfs_test_inode, &na); + if (bmp_vi) { + write_inode_now(bmp_vi, !datasync); + iput(bmp_vi); + } + ret = __ntfs_write_inode(vi, 1); + + write_inode_now(vi, !datasync); + + write_inode_now(vol->mftbmp_ino, 1); + down_write(&vol->lcnbmp_lock); + write_inode_now(vol->lcnbmp_ino, 1); + up_write(&vol->lcnbmp_lock); + write_inode_now(vol->mft_ino, 1); + + err = sync_blockdev(vi->i_sb->s_bdev); + if (unlikely(err && !ret)) + ret = err; + if (likely(!ret)) + ntfs_debug("Done."); + else + ntfs_warning(vi->i_sb, + "Failed to f%ssync inode 0x%llx. Error %u.", + datasync ? "data" : "", ni->mft_no, -ret); + inode_unlock(vi); + return ret; +} + +const struct file_operations ntfs_dir_ops = { + .llseek = generic_file_llseek, /* Seek inside directory. */ + .read = generic_read_dir, /* Return -EISDIR. */ + .iterate_shared = ntfs_readdir, /* Read directory contents. */ + .fsync = ntfs_dir_fsync, /* Sync a directory to disk. */ + .open = ntfs_dir_open, /* Open directory. */ + .release = ntfs_dir_release, + .unlocked_ioctl = ntfs_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ntfs_compat_ioctl, +#endif + .setlease = generic_setlease, +}; diff --git a/fs/ntfs/dir.h b/fs/ntfs/dir.h new file mode 100644 index 000000000000..a38c6b071678 --- /dev/null +++ b/fs/ntfs/dir.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for directory handling in NTFS Linux kernel driver. + * + * Copyright (c) 2002-2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_DIR_H +#define _LINUX_NTFS_DIR_H + +#include "inode.h" + +/* + * ntfs_name is used to return the file name to the caller of + * ntfs_lookup_inode_by_name() in order for the caller (namei.c::ntfs_lookup()) + * to be able to deal with dcache aliasing issues. + */ +struct ntfs_name { + u64 mref; + u8 type; + u8 len; + __le16 name[]; +} __packed; + +/* The little endian Unicode string $I30 as a global constant. */ +extern __le16 I30[5]; + +u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, + const __le16 *uname, const int uname_len, struct ntfs_name **res); +int ntfs_check_empty_dir(struct ntfs_inode *ni, struct mft_record *ni_mrec); + +#endif /* _LINUX_NTFS_FS_DIR_H */ diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c new file mode 100644 index 000000000000..ee99baf9c7d2 --- /dev/null +++ b/fs/ntfs/ea.c @@ -0,0 +1,954 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pocessing of EA's + * + * Part of this file is based on code from the NTFS-3G. + * + * Copyright (c) 2014-2021 Jean-Pierre Andre + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include +#include +#include +#include + +#include "layout.h" +#include "attrib.h" +#include "index.h" +#include "dir.h" +#include "ea.h" + +static int ntfs_write_ea(struct ntfs_inode *ni, __le32 type, char *value, s64 ea_off, + s64 ea_size, bool need_truncate) +{ + struct inode *ea_vi; + int err = 0; + s64 written; + + ea_vi = ntfs_attr_iget(VFS_I(ni), type, AT_UNNAMED, 0); + if (IS_ERR(ea_vi)) + return PTR_ERR(ea_vi); + + written = ntfs_inode_attr_pwrite(ea_vi, ea_off, ea_size, value, false); + if (written != ea_size) + err = -EIO; + else { + struct ntfs_inode *ea_ni = NTFS_I(ea_vi); + + if (need_truncate && ea_ni->data_size > ea_off + ea_size) + ntfs_attr_truncate(ea_ni, ea_off + ea_size); + mark_mft_record_dirty(ni); + } + + iput(ea_vi); + return err; +} + +static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name, + int name_len, s64 *ea_offset, s64 *ea_size) +{ + const struct ea_attr *p_ea; + size_t actual_size; + loff_t offset, p_ea_size; + unsigned int next; + + if (ea_buf_size < sizeof(struct ea_attr)) + goto out; + + offset = 0; + do { + p_ea = (const struct ea_attr *)&ea_buf[offset]; + next = le32_to_cpu(p_ea->next_entry_offset); + p_ea_size = next ? next : (ea_buf_size - offset); + + if (p_ea_size < sizeof(struct ea_attr) || + offset + p_ea_size > ea_buf_size) + break; + + if ((s64)p_ea->ea_name_length + 1 > + p_ea_size - offsetof(struct ea_attr, ea_name)) + break; + + actual_size = ALIGN(struct_size(p_ea, ea_name, 1 + p_ea->ea_name_length + + le16_to_cpu(p_ea->ea_value_length)), 4); + if (actual_size > p_ea_size) + break; + + if (p_ea->ea_name_length == name_len && + !memcmp(p_ea->ea_name, name, name_len)) { + *ea_offset = offset; + *ea_size = next ? next : actual_size; + + if (ea_buf_size < *ea_offset + *ea_size) + goto out; + + return 0; + } + offset += next; + } while (next > 0 && offset < ea_buf_size); + +out: + return -ENOENT; +} + +/* + * Return the existing EA + * + * The EA_INFORMATION is not examined and the consistency of the + * existing EA is not checked. + * + * If successful, the full attribute is returned unchanged + * and its size is returned. + * If the designated buffer is too small, the needed size is + * returned, and the buffer is left unchanged. + * If there is an error, a negative value is returned and errno + * is set according to the error. + */ +static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len, + void *buffer, size_t size) +{ + struct ntfs_inode *ni = NTFS_I(inode); + const struct ea_attr *p_ea; + char *ea_buf; + s64 ea_off, ea_size, all_ea_size, ea_info_size; + int err; + u32 ea_info_qlen; + u16 ea_value_len; + struct ea_information *p_ea_info; + + if (!NInoHasEA(ni)) + return -ENODATA; + + p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, + &ea_info_size); + if (!p_ea_info || ea_info_size != sizeof(struct ea_information)) { + kvfree(p_ea_info); + return -ENODATA; + } + + ea_info_qlen = le32_to_cpu(p_ea_info->ea_query_length); + kvfree(p_ea_info); + + ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size); + if (!ea_buf) + return -ENODATA; + + if (ea_info_qlen > all_ea_size) { + err = -EIO; + goto free_ea_buf; + } + + err = ntfs_ea_lookup(ea_buf, ea_info_qlen, name, name_len, &ea_off, + &ea_size); + if (!err) { + p_ea = (struct ea_attr *)&ea_buf[ea_off]; + ea_value_len = le16_to_cpu(p_ea->ea_value_length); + if (!buffer) { + kvfree(ea_buf); + return ea_value_len; + } + + if (ea_value_len > size) { + err = -ERANGE; + goto free_ea_buf; + } + + memcpy(buffer, &p_ea->ea_name[p_ea->ea_name_length + 1], + ea_value_len); + kvfree(ea_buf); + return ea_value_len; + } + + err = -ENODATA; +free_ea_buf: + kvfree(ea_buf); + return err; +} + +static inline int ea_packed_size(const struct ea_attr *p_ea) +{ + /* + * 4 bytes for header (flags and lengths) + name length + 1 + + * value length. + */ + return 5 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length); +} + +/* + * Set a new EA, and set EA_INFORMATION accordingly + * + * This is roughly the same as ZwSetEaFile() on Windows, however + * the "offset to next" of the last EA should not be cleared. + * + * Consistency of the new EA is first checked. + * + * EA_INFORMATION is set first, and it is restored to its former + * state if setting EA fails. + */ +static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len, + const void *value, size_t val_size, int flags, + __le16 *packed_ea_size) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct ea_information *p_ea_info = NULL; + int ea_packed, err = 0; + struct ea_attr *p_ea; + u32 ea_info_qsize = 0; + char *ea_buf = NULL; + size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4); + s64 ea_off, ea_info_size, all_ea_size, ea_size; + + if (name_len > 255) + return -ENAMETOOLONG; + + if (ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0)) { + p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, + &ea_info_size); + if (!p_ea_info || ea_info_size != sizeof(struct ea_information)) + goto out; + + ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size); + if (!ea_buf) { + ea_info_qsize = 0; + kvfree(p_ea_info); + goto create_ea_info; + } + + ea_info_qsize = le32_to_cpu(p_ea_info->ea_query_length); + } else { +create_ea_info: + p_ea_info = kzalloc(sizeof(struct ea_information), GFP_NOFS); + if (!p_ea_info) + return -ENOMEM; + + ea_info_qsize = 0; + err = ntfs_attr_add(ni, AT_EA_INFORMATION, AT_UNNAMED, 0, + (char *)p_ea_info, sizeof(struct ea_information)); + if (err) + goto out; + + if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { + err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0); + if (err) + goto out; + } + + goto alloc_new_ea; + } + + if (ea_info_qsize > all_ea_size) { + err = -EIO; + goto out; + } + + err = ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off, + &ea_size); + if (ea_info_qsize && !err) { + if (flags & XATTR_CREATE) { + err = -EEXIST; + goto out; + } + + p_ea = (struct ea_attr *)(ea_buf + ea_off); + + if (val_size && + le16_to_cpu(p_ea->ea_value_length) == val_size && + !memcmp(p_ea->ea_name + p_ea->ea_name_length + 1, value, + val_size)) + goto out; + + le16_add_cpu(&p_ea_info->ea_length, 0 - ea_packed_size(p_ea)); + + if (p_ea->flags & NEED_EA) + le16_add_cpu(&p_ea_info->need_ea_count, -1); + + memmove((char *)p_ea, (char *)p_ea + ea_size, ea_info_qsize - (ea_off + ea_size)); + ea_info_qsize -= ea_size; + p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize); + + err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0, + sizeof(struct ea_information), false); + if (err) + goto out; + + err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true); + if (err) + goto out; + + if ((flags & XATTR_REPLACE) && !val_size) { + /* Remove xattr. */ + goto out; + } + } else { + if (flags & XATTR_REPLACE) { + err = -ENODATA; + goto out; + } + } + kvfree(ea_buf); + +alloc_new_ea: + ea_buf = kzalloc(new_ea_size, GFP_NOFS); + if (!ea_buf) { + err = -ENOMEM; + goto out; + } + + /* + * EA and REPARSE_POINT compatibility not checked any more, + * required by Windows 10, but having both may lead to + * problems with earlier versions. + */ + p_ea = (struct ea_attr *)ea_buf; + memcpy(p_ea->ea_name, name, name_len); + p_ea->ea_name_length = name_len; + p_ea->ea_name[name_len] = 0; + memcpy(p_ea->ea_name + name_len + 1, value, val_size); + p_ea->ea_value_length = cpu_to_le16(val_size); + p_ea->next_entry_offset = cpu_to_le32(new_ea_size); + + ea_packed = le16_to_cpu(p_ea_info->ea_length) + ea_packed_size(p_ea); + p_ea_info->ea_length = cpu_to_le16(ea_packed); + p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize + new_ea_size); + + if (ea_packed > 0xffff || + ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_ea_size)) { + err = -EFBIG; + goto out; + } + + /* + * no EA or EA_INFORMATION : add them + */ + if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { + err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, (char *)p_ea, + new_ea_size); + if (err) + goto out; + } else { + err = ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qsize, + new_ea_size, false); + if (err) + goto out; + } + + err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0, + sizeof(struct ea_information), false); + if (err) + goto out; + + if (packed_ea_size) + *packed_ea_size = p_ea_info->ea_length; + mark_mft_record_dirty(ni); +out: + if (ea_info_qsize > 0) + NInoSetHasEA(ni); + else + NInoClearHasEA(ni); + + kvfree(ea_buf); + kvfree(p_ea_info); + + return err; +} + +/* + * Check for the presence of an EA "$LXDEV" (used by WSL) + * and return its value as a device address + */ +int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags) +{ + int err; + __le32 v; + + if (!(flags & NTFS_VOL_UID)) { + /* Load uid to lxuid EA */ + err = ntfs_get_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v, + sizeof(v)); + if (err < 0) + return err; + if (err != sizeof(v)) + return -EIO; + i_uid_write(inode, le32_to_cpu(v)); + } + + if (!(flags & NTFS_VOL_GID)) { + /* Load gid to lxgid EA */ + err = ntfs_get_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v, + sizeof(v)); + if (err < 0) + return err; + if (err != sizeof(v)) + return -EIO; + i_gid_write(inode, le32_to_cpu(v)); + } + + /* Load mode to lxmod EA */ + err = ntfs_get_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, sizeof(v)); + if (err == sizeof(v)) { + inode->i_mode = le32_to_cpu(v); + } else { + /* Everyone gets all permissions. */ + inode->i_mode |= 0777; + } + + /* Load mode to lxdev EA */ + err = ntfs_get_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &v, sizeof(v)); + if (err == sizeof(v)) + *rdevp = le32_to_cpu(v); + err = 0; + + return err; +} + +int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, + unsigned int flags) +{ + __le32 v; + int err; + + if (flags & NTFS_EA_UID) { + /* Store uid to lxuid EA */ + v = cpu_to_le32(i_uid_read(inode)); + err = ntfs_set_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v, + sizeof(v), 0, ea_size); + if (err) + return err; + } + + if (flags & NTFS_EA_GID) { + /* Store gid to lxgid EA */ + v = cpu_to_le32(i_gid_read(inode)); + err = ntfs_set_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v, + sizeof(v), 0, ea_size); + if (err) + return err; + } + + if (flags & NTFS_EA_MODE) { + /* Store mode to lxmod EA */ + v = cpu_to_le32(inode->i_mode); + err = ntfs_set_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, + sizeof(v), 0, ea_size); + if (err) + return err; + } + + if (rdev) { + v = cpu_to_le32(rdev); + err = ntfs_set_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &v, sizeof(v), + 0, ea_size); + } + + return err; +} + +ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_inode *ni = NTFS_I(inode); + const struct ea_attr *p_ea; + s64 offset, ea_buf_size, ea_info_size; + s64 ea_size; + u32 next; + int err = 0; + u32 ea_info_qsize; + char *ea_buf = NULL; + ssize_t ret = 0; + struct ea_information *ea_info; + + if (!NInoHasEA(ni)) + return 0; + + mutex_lock(&NTFS_I(inode)->mrec_lock); + ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, + &ea_info_size); + if (!ea_info || ea_info_size != sizeof(struct ea_information)) + goto out; + + ea_info_qsize = le32_to_cpu(ea_info->ea_query_length); + + ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &ea_buf_size); + if (!ea_buf) + goto out; + + if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0) + goto out; + + if (ea_info_qsize < sizeof(struct ea_attr)) { + err = -EIO; + goto out; + } + + offset = 0; + do { + p_ea = (const struct ea_attr *)&ea_buf[offset]; + next = le32_to_cpu(p_ea->next_entry_offset); + ea_size = next ? next : (ea_info_qsize - offset); + + if (ea_size < sizeof(struct ea_attr) || + offset + ea_size > ea_info_qsize) { + err = -EIO; + goto out; + } + + if ((int)p_ea->ea_name_length + 1 > + ea_size - offsetof(struct ea_attr, ea_name)) { + err = -EIO; + goto out; + } + + if (buffer) { + if (ret + p_ea->ea_name_length + 1 > size) { + err = -ERANGE; + goto out; + } + + memcpy(buffer + ret, p_ea->ea_name, p_ea->ea_name_length); + buffer[ret + p_ea->ea_name_length] = 0; + } + + ret += p_ea->ea_name_length + 1; + offset += ea_size; + } while (next > 0 && offset < ea_info_qsize); + +out: + mutex_unlock(&NTFS_I(inode)->mrec_lock); + kvfree(ea_info); + kvfree(ea_buf); + + return err ? err : ret; +} + +// clang-format off +#define SYSTEM_DOS_ATTRIB "system.dos_attrib" +#define SYSTEM_NTFS_ATTRIB "system.ntfs_attrib" +#define SYSTEM_NTFS_ATTRIB_BE "system.ntfs_attrib_be" +// clang-format on + +static int ntfs_getxattr(const struct xattr_handler *handler, + struct dentry *unused, struct inode *inode, const char *name, + void *buffer, size_t size) +{ + struct ntfs_inode *ni = NTFS_I(inode); + int err; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (!strcmp(name, SYSTEM_DOS_ATTRIB)) { + if (!buffer) { + err = sizeof(u8); + } else if (size < sizeof(u8)) { + err = -ENODATA; + } else { + err = sizeof(u8); + *(u8 *)buffer = (u8)(le32_to_cpu(ni->flags) & 0x3F); + } + goto out; + } + + if (!strcmp(name, SYSTEM_NTFS_ATTRIB) || + !strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) { + if (!buffer) { + err = sizeof(u32); + } else if (size < sizeof(u32)) { + err = -ENODATA; + } else { + err = sizeof(u32); + *(u32 *)buffer = le32_to_cpu(ni->flags); + if (!strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) + *(__be32 *)buffer = cpu_to_be32(*(u32 *)buffer); + } + goto out; + } + + mutex_lock(&ni->mrec_lock); + err = ntfs_get_ea(inode, name, strlen(name), buffer, size); + mutex_unlock(&ni->mrec_lock); + +out: + return err; +} + +static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) +{ + struct ntfs_attr_search_ctx *ctx; + struct mft_record *m; + struct attr_record *a; + __le16 new_aflags; + int mp_size, mp_ofs, name_ofs, arec_size, err; + + m = map_mft_record(ni); + if (IS_ERR(m)) + return PTR_ERR(m); + + ctx = ntfs_attr_get_search_ctx(ni, m); + if (!ctx) { + err = -ENOMEM; + goto err_out; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (err) { + err = -EINVAL; + goto err_out; + } + + a = ctx->attr; + new_aflags = ctx->attr->flags; + + if (fattr & FILE_ATTR_SPARSE_FILE) + new_aflags |= ATTR_IS_SPARSE; + else + new_aflags &= ~ATTR_IS_SPARSE; + + if (fattr & FILE_ATTR_COMPRESSED) + new_aflags |= ATTR_IS_COMPRESSED; + else + new_aflags &= ~ATTR_IS_COMPRESSED; + + if (new_aflags == a->flags) + return 0; + + if ((new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) == + (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) { + pr_err("file can't be sparsed and compressed\n"); + err = -EOPNOTSUPP; + goto err_out; + } + + if (!a->non_resident) + goto out; + + if (a->data.non_resident.data_size) { + pr_err("Can't change sparsed/compressed for non-empty file\n"); + err = -EOPNOTSUPP; + goto err_out; + } + + if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) + name_ofs = (offsetof(struct attr_record, + data.non_resident.compressed_size) + + sizeof(a->data.non_resident.compressed_size) + 7) & ~7; + else + name_ofs = (offsetof(struct attr_record, + data.non_resident.compressed_size) + 7) & ~7; + + mp_size = ntfs_get_size_for_mapping_pairs(ni->vol, ni->runlist.rl, 0, -1, -1); + if (unlikely(mp_size < 0)) { + err = mp_size; + ntfs_debug("Failed to get size for mapping pairs array, error code %i.\n", err); + goto err_out; + } + + mp_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7; + arec_size = (mp_ofs + mp_size + 7) & ~7; + + err = ntfs_attr_record_resize(m, a, arec_size); + if (unlikely(err)) + goto err_out; + + if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) { + a->data.non_resident.compression_unit = 0; + if (new_aflags & ATTR_IS_COMPRESSED || ni->vol->major_ver < 3) + a->data.non_resident.compression_unit = 4; + a->data.non_resident.compressed_size = 0; + ni->itype.compressed.size = 0; + if (a->data.non_resident.compression_unit) { + ni->itype.compressed.block_size = 1U << + (a->data.non_resident.compression_unit + + ni->vol->cluster_size_bits); + ni->itype.compressed.block_size_bits = + ffs(ni->itype.compressed.block_size) - + 1; + ni->itype.compressed.block_clusters = 1U << + a->data.non_resident.compression_unit; + } else { + ni->itype.compressed.block_size = 0; + ni->itype.compressed.block_size_bits = 0; + ni->itype.compressed.block_clusters = 0; + } + + if (new_aflags & ATTR_IS_SPARSE) { + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + } + + if (new_aflags & ATTR_IS_COMPRESSED) { + NInoSetCompressed(ni); + ni->flags |= FILE_ATTR_COMPRESSED; + } + } else { + ni->flags &= ~(FILE_ATTR_SPARSE_FILE | FILE_ATTR_COMPRESSED); + a->data.non_resident.compression_unit = 0; + NInoClearSparse(ni); + NInoClearCompressed(ni); + } + + a->name_offset = cpu_to_le16(name_ofs); + a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs); + +out: + a->flags = new_aflags; + mark_mft_record_dirty(ctx->ntfs_ino); +err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + return err; +} + +static int ntfs_setxattr(const struct xattr_handler *handler, + struct mnt_idmap *idmap, struct dentry *unused, + struct inode *inode, const char *name, const void *value, + size_t size, int flags) +{ + struct ntfs_inode *ni = NTFS_I(inode); + int err; + __le32 fattr; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (!strcmp(name, SYSTEM_DOS_ATTRIB)) { + if (sizeof(u8) != size) { + err = -EINVAL; + goto out; + } + fattr = cpu_to_le32(*(u8 *)value); + goto set_fattr; + } + + if (!strcmp(name, SYSTEM_NTFS_ATTRIB) || + !strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) { + if (size != sizeof(u32)) { + err = -EINVAL; + goto out; + } + if (!strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) + fattr = cpu_to_le32(be32_to_cpu(*(__be32 *)value)); + else + fattr = cpu_to_le32(*(u32 *)value); + + if (S_ISREG(inode->i_mode)) { + mutex_lock(&ni->mrec_lock); + err = ntfs_new_attr_flags(ni, fattr); + mutex_unlock(&ni->mrec_lock); + if (err) + goto out; + } + +set_fattr: + if (S_ISDIR(inode->i_mode)) + fattr |= FILE_ATTR_DIRECTORY; + else + fattr &= ~FILE_ATTR_DIRECTORY; + + if (ni->flags != fattr) { + ni->flags = fattr; + if (fattr & FILE_ATTR_READONLY) + inode->i_mode &= ~0222; + else + inode->i_mode |= 0222; + NInoSetFileNameDirty(ni); + mark_inode_dirty(inode); + } + err = 0; + goto out; + } + + mutex_lock(&ni->mrec_lock); + err = ntfs_set_ea(inode, name, strlen(name), value, size, flags, NULL); + mutex_unlock(&ni->mrec_lock); + +out: + inode_set_ctime_current(inode); + mark_inode_dirty(inode); + return err; +} + +static bool ntfs_xattr_user_list(struct dentry *dentry) +{ + return true; +} + +// clang-format off +static const struct xattr_handler ntfs_other_xattr_handler = { + .prefix = "", + .get = ntfs_getxattr, + .set = ntfs_setxattr, + .list = ntfs_xattr_user_list, +}; + +const struct xattr_handler * const ntfs_xattr_handlers[] = { + &ntfs_other_xattr_handler, + NULL, +}; +// clang-format on + +#ifdef CONFIG_NTFS_FS_POSIX_ACL +struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, + int type) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_inode *ni = NTFS_I(inode); + const char *name; + size_t name_len; + struct posix_acl *acl; + int err; + void *buf; + + buf = kmalloc(PATH_MAX, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + /* Possible values of 'type' was already checked above. */ + if (type == ACL_TYPE_ACCESS) { + name = XATTR_NAME_POSIX_ACL_ACCESS; + name_len = sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1; + } else { + name = XATTR_NAME_POSIX_ACL_DEFAULT; + name_len = sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1; + } + + mutex_lock(&ni->mrec_lock); + err = ntfs_get_ea(inode, name, name_len, buf, PATH_MAX); + mutex_unlock(&ni->mrec_lock); + + /* Translate extended attribute to acl. */ + if (err >= 0) + acl = posix_acl_from_xattr(&init_user_ns, buf, err); + else if (err == -ENODATA) + acl = NULL; + else + acl = ERR_PTR(err); + + if (!IS_ERR(acl)) + set_cached_acl(inode, type, acl); + + kfree(buf); + + return acl; +} + +static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap, + struct inode *inode, struct posix_acl *acl, + int type, bool init_acl) +{ + const char *name; + size_t size, name_len; + void *value; + int err; + int flags; + umode_t mode; + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + + mode = inode->i_mode; + switch (type) { + case ACL_TYPE_ACCESS: + /* Do not change i_mode if we are in init_acl */ + if (acl && !init_acl) { + err = posix_acl_update_mode(idmap, inode, &mode, &acl); + if (err) + return err; + } + name = XATTR_NAME_POSIX_ACL_ACCESS; + name_len = sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1; + break; + + case ACL_TYPE_DEFAULT: + if (!S_ISDIR(inode->i_mode)) + return acl ? -EACCES : 0; + name = XATTR_NAME_POSIX_ACL_DEFAULT; + name_len = sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1; + break; + + default: + return -EINVAL; + } + + if (!acl) { + /* Remove xattr if it can be presented via mode. */ + size = 0; + value = NULL; + flags = XATTR_REPLACE; + } else { + value = posix_acl_to_xattr(&init_user_ns, acl, &size, GFP_NOFS); + if (!value) + return -ENOMEM; + flags = 0; + } + + mutex_lock(&NTFS_I(inode)->mrec_lock); + err = ntfs_set_ea(inode, name, name_len, value, size, flags, NULL); + mutex_unlock(&NTFS_I(inode)->mrec_lock); + if (err == -ENODATA && !size) + err = 0; /* Removing non existed xattr. */ + if (!err) { + __le16 ea_size = 0; + umode_t old_mode = inode->i_mode; + + inode->i_mode = mode; + mutex_lock(&NTFS_I(inode)->mrec_lock); + err = ntfs_ea_set_wsl_inode(inode, 0, &ea_size, NTFS_EA_MODE); + if (err) { + ntfs_set_ea(inode, name, name_len, NULL, 0, + XATTR_REPLACE, NULL); + mutex_unlock(&NTFS_I(inode)->mrec_lock); + inode->i_mode = old_mode; + goto out; + } + mutex_unlock(&NTFS_I(inode)->mrec_lock); + + set_cached_acl(inode, type, acl); + inode_set_ctime_current(inode); + mark_inode_dirty(inode); + } + +out: + kfree(value); + + return err; +} + +int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct posix_acl *acl, int type) +{ + return ntfs_set_acl_ex(idmap, d_inode(dentry), acl, type, false); +} + +int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode, + struct inode *dir) +{ + struct posix_acl *default_acl, *acl; + int err; + + err = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl); + if (err) + return err; + + if (default_acl) { + err = ntfs_set_acl_ex(idmap, inode, default_acl, + ACL_TYPE_DEFAULT, true); + posix_acl_release(default_acl); + } else { + inode->i_default_acl = NULL; + } + + if (acl) { + if (!err) + err = ntfs_set_acl_ex(idmap, inode, acl, + ACL_TYPE_ACCESS, true); + posix_acl_release(acl); + } else { + inode->i_acl = NULL; + } + + return err; +} +#endif diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h new file mode 100644 index 000000000000..1f63bd55e057 --- /dev/null +++ b/fs/ntfs/ea.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _LINUX_NTFS_EA_H +#define _LINUX_NTFS_EA_H + +#define NTFS_EA_UID BIT(1) +#define NTFS_EA_GID BIT(2) +#define NTFS_EA_MODE BIT(3) + +extern const struct xattr_handler *const ntfs_xattr_handlers[]; + +int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t dev); +int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags); +int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, + unsigned int flags); +ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); + +#ifdef CONFIG_NTFS_FS_POSIX_ACL +struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, + int type); +int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct posix_acl *acl, int type); +int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode, + struct inode *dir); +#else +#define ntfs_get_acl NULL +#define ntfs_set_acl NULL +#endif + +#endif /* _LINUX_NTFS_EA_H */ diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c new file mode 100644 index 000000000000..e5b897a6c1e1 --- /dev/null +++ b/fs/ntfs/file.c @@ -0,0 +1,1161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel file operations. + * + * Copyright (c) 2001-2015 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lcnalloc.h" +#include "ntfs.h" +#include "reparse.h" +#include "ea.h" +#include "iomap.h" +#include "bitmap.h" + +#include + +/* + * ntfs_file_open - called when an inode is about to be opened + * @vi: inode to be opened + * @filp: file structure describing the inode + * + * Limit file size to the page cache limit on architectures where unsigned long + * is 32-bits. This is the most we can do for now without overflowing the page + * cache page index. Doing it this way means we don't run into problems because + * of existing too large files. It would be better to allow the user to read + * the beginning of the file but I doubt very much anyone is going to hit this + * check on a 32-bit architecture, so there is no point in adding the extra + * complexity required to support this. + * + * On 64-bit architectures, the check is hopefully optimized away by the + * compiler. + * + * After the check passes, just call generic_file_open() to do its work. + */ +static int ntfs_file_open(struct inode *vi, struct file *filp) +{ + struct ntfs_inode *ni = NTFS_I(vi); + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (sizeof(unsigned long) < 8) { + if (i_size_read(vi) > MAX_LFS_FILESIZE) + return -EOVERFLOW; + } + + filp->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT; + + return generic_file_open(vi, filp); +} + +/* + * Trim preallocated space on file release. + * + * When the preallo_size mount option is set (default 64KB), writes extend + * allocated_size and runlist in units of preallocated size to reduce + * runlist merge overhead for small writes. This can leave + * allocated_size > data_size if not all preallocated space is used. + * + * We perform the trim here because ->release() is called only when + * the file is no longer open. At this point, no further writes can occur, + * so it is safe to reclaim the unused preallocated space. + * + * Returns 0 on success, or negative error on failure. + */ +static int ntfs_trim_prealloc(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + struct runlist_element *rl; + s64 aligned_data_size; + s64 vcn_ds, vcn_tr; + ssize_t rc; + int err = 0; + + inode_lock(vi); + mutex_lock(&ni->mrec_lock); + down_write(&ni->runlist.lock); + + aligned_data_size = round_up(ni->data_size, vol->cluster_size); + if (aligned_data_size >= ni->allocated_size) + goto out_unlock; + + vcn_ds = ntfs_bytes_to_cluster(vol, aligned_data_size); + vcn_tr = -1; + rc = ni->runlist.count - 2; + rl = ni->runlist.rl; + + while (rc >= 0 && rl[rc].lcn == LCN_HOLE && vcn_ds <= rl[rc].vcn) { + vcn_tr = rl[rc].vcn; + rc--; + } + + if (vcn_tr >= 0) { + err = ntfs_rl_truncate_nolock(vol, &ni->runlist, vcn_tr); + if (err) { + kvfree(ni->runlist.rl); + ni->runlist.rl = NULL; + ntfs_error(vol->sb, "Preallocated block rollback failed"); + } else { + ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr); + err = ntfs_attr_update_mapping_pairs(ni, 0); + if (err) + ntfs_error(vol->sb, + "Failed to rollback mapping pairs for prealloc"); + } + } + +out_unlock: + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + inode_unlock(vi); + + return err; +} + +static int ntfs_file_release(struct inode *vi, struct file *filp) +{ + if (!NInoCompressed(NTFS_I(vi))) + return ntfs_trim_prealloc(vi); + + return 0; +} + +/* + * ntfs_file_fsync - sync a file to disk + * @filp: file to be synced + * @start: start offset to be synced + * @end: end offset to be synced + * @datasync: if non-zero only flush user data and not metadata + * + * Data integrity sync of a file to disk. Used for fsync, fdatasync, and msync + * system calls. This function is inspired by fs/buffer.c::file_fsync(). + * + * If @datasync is false, write the mft record and all associated extent mft + * records as well as the $DATA attribute and then sync the block device. + * + * If @datasync is true and the attribute is non-resident, we skip the writing + * of the mft record and all associated extent mft records (this might still + * happen due to the write_inode_now() call). + * + * Also, if @datasync is true, we do not wait on the inode to be written out + * but we always wait on the page cache pages to be written out. + */ +static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, + int datasync) +{ + struct inode *vi = filp->f_mapping->host; + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + int err, ret = 0; + struct inode *parent_vi, *ia_vi; + struct ntfs_attr_search_ctx *ctx; + + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + + if (NVolShutdown(vol)) + return -EIO; + + err = file_write_and_wait_range(filp, start, end); + if (err) + return err; + + if (!datasync || !NInoNonResident(NTFS_I(vi))) + ret = __ntfs_write_inode(vi, 1); + write_inode_now(vi, !datasync); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -ENOMEM; + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_CHILD); + while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) { + if (ctx->attr->type == AT_FILE_NAME) { + struct file_name_attr *fn = (struct file_name_attr *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + + parent_vi = ntfs_iget(vi->i_sb, MREF_LE(fn->parent_directory)); + if (IS_ERR(parent_vi)) + continue; + mutex_lock_nested(&NTFS_I(parent_vi)->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + ia_vi = ntfs_index_iget(parent_vi, I30, 4); + mutex_unlock(&NTFS_I(parent_vi)->mrec_lock); + if (IS_ERR(ia_vi)) { + iput(parent_vi); + continue; + } + write_inode_now(ia_vi, 1); + iput(ia_vi); + write_inode_now(parent_vi, 1); + iput(parent_vi); + } else if (ctx->attr->non_resident) { + struct inode *attr_vi; + __le16 *name; + + name = (__le16 *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->name_offset)); + if (ctx->attr->type == AT_DATA && ctx->attr->name_length == 0) + continue; + + attr_vi = ntfs_attr_iget(vi, ctx->attr->type, + name, ctx->attr->name_length); + if (IS_ERR(attr_vi)) + continue; + spin_lock(&attr_vi->i_lock); + if (inode_state_read_once(attr_vi) & I_DIRTY_PAGES) { + spin_unlock(&attr_vi->i_lock); + filemap_write_and_wait(attr_vi->i_mapping); + } else + spin_unlock(&attr_vi->i_lock); + iput(attr_vi); + } + } + mutex_unlock(&ni->mrec_lock); + ntfs_attr_put_search_ctx(ctx); + + write_inode_now(vol->mftbmp_ino, 1); + down_write(&vol->lcnbmp_lock); + write_inode_now(vol->lcnbmp_ino, 1); + up_write(&vol->lcnbmp_lock); + write_inode_now(vol->mft_ino, 1); + + /* + * NOTE: If we were to use mapping->private_list (see ext2 and + * fs/buffer.c) for dirty blocks then we could optimize the below to be + * sync_mapping_buffers(vi->i_mapping). + */ + err = sync_blockdev(vi->i_sb->s_bdev); + if (unlikely(err && !ret)) + ret = err; + if (likely(!ret)) + ntfs_debug("Done."); + else + ntfs_warning(vi->i_sb, + "Failed to f%ssync inode 0x%llx. Error %u.", + datasync ? "data" : "", ni->mft_no, -ret); + if (!ret) + blkdev_issue_flush(vi->i_sb->s_bdev); + return ret; +} + +static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) +{ + struct ntfs_inode *ni = NTFS_I(vi); + int err; + loff_t old_size = vi->i_size; + + if (NInoCompressed(ni) || NInoEncrypted(ni)) { + ntfs_warning(vi->i_sb, + "Changes in inode size are not supported yet for %s files, ignoring.", + NInoCompressed(ni) ? "compressed" : "encrypted"); + return -EOPNOTSUPP; + } + + err = inode_newsize_ok(vi, attr->ia_size); + if (err) + return err; + + inode_dio_wait(vi); + /* Serialize against page faults */ + if (NInoNonResident(NTFS_I(vi)) && attr->ia_size < old_size) { + err = iomap_truncate_page(vi, attr->ia_size, NULL, + &ntfs_read_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + if (err) + return err; + } + + truncate_setsize(vi, attr->ia_size); + err = ntfs_truncate_vfs(vi, attr->ia_size, old_size); + if (err) { + i_size_write(vi, old_size); + return err; + } + + if (NInoNonResident(ni) && attr->ia_size > old_size && + old_size % PAGE_SIZE != 0) { + loff_t len = min_t(loff_t, + round_up(old_size, PAGE_SIZE) - old_size, + attr->ia_size - old_size); + err = iomap_zero_range(vi, old_size, len, + NULL, &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + } + + return err; +} + +/* + * ntfs_setattr + * + * Called from notify_change() when an attribute is being changed. + * + * NOTE: Changes in inode size are not supported yet for compressed or + * encrypted files. + */ +int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) +{ + struct inode *vi = d_inode(dentry); + int err; + unsigned int ia_valid = attr->ia_valid; + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + + if (NVolShutdown(vol)) + return -EIO; + + err = setattr_prepare(idmap, dentry, attr); + if (err) + goto out; + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + if (ia_valid & ATTR_SIZE) { + err = ntfs_setattr_size(vi, attr); + if (err) + goto out; + + ia_valid |= ATTR_MTIME | ATTR_CTIME; + } + + setattr_copy(idmap, vi, attr); + + if (vol->sb->s_flags & SB_POSIXACL && !S_ISLNK(vi->i_mode)) { + err = posix_acl_chmod(idmap, dentry, vi->i_mode); + if (err) + goto out; + } + + if (0222 & vi->i_mode) + ni->flags &= ~FILE_ATTR_READONLY; + else + ni->flags |= FILE_ATTR_READONLY; + + if (ia_valid & (ATTR_UID | ATTR_GID | ATTR_MODE)) { + unsigned int flags = 0; + + if (ia_valid & ATTR_UID) + flags |= NTFS_EA_UID; + if (ia_valid & ATTR_GID) + flags |= NTFS_EA_GID; + if (ia_valid & ATTR_MODE) + flags |= NTFS_EA_MODE; + + if (S_ISDIR(vi->i_mode)) + vi->i_mode &= ~vol->dmask; + else + vi->i_mode &= ~vol->fmask; + + mutex_lock(&ni->mrec_lock); + ntfs_ea_set_wsl_inode(vi, 0, NULL, flags); + mutex_unlock(&ni->mrec_lock); + } + + mark_inode_dirty(vi); +out: + return err; +} + +int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags) +{ + struct inode *inode = d_backing_inode(path->dentry); + struct ntfs_inode *ni = NTFS_I(inode); + + generic_fillattr(idmap, request_mask, inode, stat); + + stat->blksize = NTFS_SB(inode->i_sb)->cluster_size; + stat->blocks = (((u64)NTFS_I(inode)->i_dealloc_clusters << + NTFS_SB(inode->i_sb)->cluster_size_bits) >> 9) + inode->i_blocks; + stat->result_mask |= STATX_BTIME; + stat->btime = NTFS_I(inode)->i_crtime; + + if (NInoCompressed(ni)) + stat->attributes |= STATX_ATTR_COMPRESSED; + + if (NInoEncrypted(ni)) + stat->attributes |= STATX_ATTR_ENCRYPTED; + + if (inode->i_flags & S_IMMUTABLE) + stat->attributes |= STATX_ATTR_IMMUTABLE; + + if (inode->i_flags & S_APPEND) + stat->attributes |= STATX_ATTR_APPEND; + + stat->attributes_mask |= STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED | + STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND; + + /* + * If it's a compressed or encrypted file, NTFS currently + * does not support DIO. For normal files, we report the bdev + * logical block size. + */ + if (request_mask & STATX_DIOALIGN && S_ISREG(inode->i_mode)) { + unsigned int align = + bdev_logical_block_size(inode->i_sb->s_bdev); + + stat->result_mask |= STATX_DIOALIGN; + if (!NInoCompressed(ni) && !NInoEncrypted(ni)) { + stat->dio_mem_align = align; + stat->dio_offset_align = align; + } + } + + return 0; +} + +static loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence) +{ + struct inode *inode = file->f_mapping->host; + + switch (whence) { + case SEEK_HOLE: + inode_lock_shared(inode); + offset = iomap_seek_hole(inode, offset, &ntfs_seek_iomap_ops); + inode_unlock_shared(inode); + break; + case SEEK_DATA: + inode_lock_shared(inode); + offset = iomap_seek_data(inode, offset, &ntfs_seek_iomap_ops); + inode_unlock_shared(inode); + break; + default: + return generic_file_llseek_size(file, offset, whence, + inode->i_sb->s_maxbytes, + i_size_read(inode)); + } + if (offset < 0) + return offset; + return vfs_setpos(file, offset, inode->i_sb->s_maxbytes); +} + +static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct inode *vi = file_inode(iocb->ki_filp); + struct super_block *sb = vi->i_sb; + ssize_t ret; + + if (NVolShutdown(NTFS_SB(sb))) + return -EIO; + + if (NInoCompressed(NTFS_I(vi)) && iocb->ki_flags & IOCB_DIRECT) + return -EOPNOTSUPP; + + inode_lock_shared(vi); + + if (iocb->ki_flags & IOCB_DIRECT) { + size_t count = iov_iter_count(to); + + if ((iocb->ki_pos | count) & (sb->s_blocksize - 1)) { + ret = -EINVAL; + goto inode_unlock; + } + + file_accessed(iocb->ki_filp); + ret = iomap_dio_rw(iocb, to, &ntfs_read_iomap_ops, NULL, 0, + NULL, 0); + } else { + ret = generic_file_read_iter(iocb, to); + } + +inode_unlock: + inode_unlock_shared(vi); + + return ret; +} + +static int ntfs_file_write_dio_end_io(struct kiocb *iocb, ssize_t size, + int error, unsigned int flags) +{ + struct inode *inode = file_inode(iocb->ki_filp); + + if (error) + return error; + + if (size) { + if (i_size_read(inode) < iocb->ki_pos + size) { + i_size_write(inode, iocb->ki_pos + size); + mark_inode_dirty(inode); + } + } + + return 0; +} + +static const struct iomap_dio_ops ntfs_write_dio_ops = { + .end_io = ntfs_file_write_dio_end_io, +}; + +static ssize_t ntfs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + ssize_t ret; + + ret = iomap_dio_rw(iocb, from, &ntfs_dio_iomap_ops, + &ntfs_write_dio_ops, 0, NULL, 0); + if (ret == -ENOTBLK) + ret = 0; + else if (ret < 0) + goto out; + + if (iov_iter_count(from)) { + loff_t offset, end; + ssize_t written; + int ret2; + + offset = iocb->ki_pos; + iocb->ki_flags &= ~IOCB_DIRECT; + written = iomap_file_buffered_write(iocb, from, + &ntfs_write_iomap_ops, &ntfs_iomap_folio_ops, + NULL); + if (written < 0) { + ret = written; + goto out; + } + + ret += written; + end = iocb->ki_pos + written - 1; + ret2 = filemap_write_and_wait_range(iocb->ki_filp->f_mapping, + offset, end); + if (ret2) { + ret = -EIO; + goto out; + } + if (!ret2) + invalidate_mapping_pages(iocb->ki_filp->f_mapping, + offset >> PAGE_SHIFT, + end >> PAGE_SHIFT); + } + +out: + return ret; +} + +static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + struct file *file = iocb->ki_filp; + struct inode *vi = file->f_mapping->host; + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + ssize_t ret; + ssize_t count; + loff_t pos; + int err; + loff_t old_data_size, old_init_size; + + if (NVolShutdown(vol)) + return -EIO; + + if (NInoEncrypted(ni)) { + ntfs_error(vi->i_sb, "Writing for %s files is not supported yet", + NInoCompressed(ni) ? "Compressed" : "Encrypted"); + return -EOPNOTSUPP; + } + + if (NInoCompressed(ni) && iocb->ki_flags & IOCB_DIRECT) + return -EOPNOTSUPP; + + if (iocb->ki_flags & IOCB_NOWAIT) { + if (!inode_trylock(vi)) + return -EAGAIN; + } else + inode_lock(vi); + + ret = generic_write_checks(iocb, from); + if (ret <= 0) + goto out_lock; + + err = file_modified(iocb->ki_filp); + if (err) { + ret = err; + goto out_lock; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + pos = iocb->ki_pos; + count = ret; + + old_data_size = ni->data_size; + old_init_size = ni->initialized_size; + + if (NInoNonResident(ni) && NInoCompressed(ni)) { + ret = ntfs_compress_write(ni, pos, count, from); + if (ret > 0) + iocb->ki_pos += ret; + goto out; + } + + if (NInoNonResident(ni) && iocb->ki_flags & IOCB_DIRECT) + ret = ntfs_dio_write_iter(iocb, from); + else + ret = iomap_file_buffered_write(iocb, from, &ntfs_write_iomap_ops, + &ntfs_iomap_folio_ops, NULL); +out: + if (ret < 0 && ret != -EIOCBQUEUED) { + if (ni->initialized_size != old_init_size) { + mutex_lock(&ni->mrec_lock); + ntfs_attr_set_initialized_size(ni, old_init_size); + mutex_unlock(&ni->mrec_lock); + } + if (ni->data_size != old_data_size) { + truncate_setsize(vi, old_data_size); + ntfs_attr_truncate(ni, old_data_size); + } + } +out_lock: + inode_unlock(vi); + if (ret > 0) + ret = generic_write_sync(iocb, ret); + return ret; +} + +static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) +{ + struct inode *inode = file_inode(vmf->vma->vm_file); + vm_fault_t ret; + + sb_start_pagefault(inode->i_sb); + file_update_time(vmf->vma->vm_file); + + ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL); + sb_end_pagefault(inode->i_sb); + return ret; +} + +static const struct vm_operations_struct ntfs_file_vm_ops = { + .fault = filemap_fault, + .map_pages = filemap_map_pages, + .page_mkwrite = ntfs_filemap_page_mkwrite, +}; + +static int ntfs_file_mmap_prepare(struct vm_area_desc *desc) +{ + struct file *file = desc->file; + struct inode *inode = file_inode(file); + + if (NVolShutdown(NTFS_SB(file->f_mapping->host->i_sb))) + return -EIO; + + if (NInoCompressed(NTFS_I(inode))) + return -EOPNOTSUPP; + + if (vma_desc_test(desc, VMA_WRITE_BIT)) { + struct inode *inode = file_inode(file); + loff_t from, to; + int err; + + from = ((loff_t)desc->pgoff << PAGE_SHIFT); + to = min_t(loff_t, i_size_read(inode), + from + desc->end - desc->start); + + if (NTFS_I(inode)->initialized_size < to) { + err = ntfs_extend_initialized_size(inode, to, to, false); + if (err) + return err; + } + } + + + file_accessed(file); + desc->vm_ops = &ntfs_file_vm_ops; + return 0; +} + +static int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, + u64 start, u64 len) +{ + return iomap_fiemap(inode, fieinfo, start, len, &ntfs_read_iomap_ops); +} + +static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode, + struct delayed_call *done) +{ + if (!NTFS_I(inode)->target) + return ERR_PTR(-EINVAL); + + return NTFS_I(inode)->target; +} + +static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, unsigned int flags) +{ + if (NVolShutdown(NTFS_SB(in->f_mapping->host->i_sb))) + return -EIO; + + return filemap_splice_read(in, ppos, pipe, len, flags); +} + +static int ntfs_ioctl_shutdown(struct super_block *sb, unsigned long arg) +{ + u32 flags; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (get_user(flags, (__u32 __user *)arg)) + return -EFAULT; + + return ntfs_force_shutdown(sb, flags); +} + +static int ntfs_ioctl_get_volume_label(struct file *filp, unsigned long arg) +{ + struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb); + char __user *buf = (char __user *)arg; + + if (!vol->volume_label) { + if (copy_to_user(buf, "", 1)) + return -EFAULT; + } else if (copy_to_user(buf, vol->volume_label, + MIN(FSLABEL_MAX, strlen(vol->volume_label) + 1))) + return -EFAULT; + return 0; +} + +static int ntfs_ioctl_set_volume_label(struct file *filp, unsigned long arg) +{ + struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb); + char *label; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + label = strndup_user((const char __user *)arg, FSLABEL_MAX); + if (IS_ERR(label)) + return PTR_ERR(label); + + ret = mnt_want_write_file(filp); + if (ret) + goto out; + + ret = ntfs_write_volume_label(vol, label); + mnt_drop_write_file(filp); +out: + kfree(label); + return ret; +} + +static int ntfs_ioctl_fitrim(struct ntfs_volume *vol, unsigned long arg) +{ + struct fstrim_range __user *user_range; + struct fstrim_range range; + struct block_device *dev; + int err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + dev = vol->sb->s_bdev; + if (!bdev_max_discard_sectors(dev)) + return -EOPNOTSUPP; + + user_range = (struct fstrim_range __user *)arg; + if (copy_from_user(&range, user_range, sizeof(range))) + return -EFAULT; + + if (range.len == 0) + return -EINVAL; + + if (range.len < vol->cluster_size) + return -EINVAL; + + range.minlen = max_t(u32, range.minlen, bdev_discard_granularity(dev)); + + err = ntfs_trim_fs(vol, &range); + if (err < 0) + return err; + + if (copy_to_user(user_range, &range, sizeof(range))) + return -EFAULT; + + return 0; +} + +long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FS_IOC_SHUTDOWN: + return ntfs_ioctl_shutdown(file_inode(filp)->i_sb, arg); + case FS_IOC_GETFSLABEL: + return ntfs_ioctl_get_volume_label(filp, arg); + case FS_IOC_SETFSLABEL: + return ntfs_ioctl_set_volume_label(filp, arg); + case FITRIM: + return ntfs_ioctl_fitrim(NTFS_SB(file_inode(filp)->i_sb), arg); + default: + return -ENOTTY; + } +} + +#ifdef CONFIG_COMPAT +long ntfs_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return ntfs_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static int ntfs_allocate_range(struct ntfs_inode *ni, int mode, loff_t offset, + loff_t len) +{ + struct inode *vi = VFS_I(ni); + struct ntfs_volume *vol = ni->vol; + s64 need_space; + loff_t old_size, new_size; + s64 start_vcn, end_vcn; + int err; + + old_size = i_size_read(vi); + new_size = max_t(loff_t, old_size, offset + len); + start_vcn = ntfs_bytes_to_cluster(vol, offset); + end_vcn = ntfs_bytes_to_cluster(vol, offset + len - 1) + 1; + + err = inode_newsize_ok(vi, new_size); + if (err) + goto out; + + need_space = ntfs_bytes_to_cluster(vol, ni->allocated_size); + if (need_space > start_vcn) + need_space = end_vcn - need_space; + else + need_space = end_vcn - start_vcn; + if (need_space > 0 && + need_space > (atomic64_read(&vol->free_clusters) - + atomic64_read(&vol->dirty_clusters))) { + err = -ENOSPC; + goto out; + } + + err = ntfs_attr_fallocate(ni, offset, len, + mode & FALLOC_FL_KEEP_SIZE ? true : false); + + if (!(mode & FALLOC_FL_KEEP_SIZE) && new_size != old_size) + i_size_write(vi, ni->data_size); +out: + return err; +} + +static int ntfs_punch_hole(struct ntfs_inode *ni, int mode, loff_t offset, + loff_t len) +{ + struct ntfs_volume *vol = ni->vol; + struct inode *vi = VFS_I(ni); + loff_t end_offset; + s64 start_vcn, end_vcn; + int err = 0; + + loff_t offset_down = round_down(offset, max_t(unsigned int, + vol->cluster_size, PAGE_SIZE)); + + if (NVolDisableSparse(vol)) { + err = -EOPNOTSUPP; + goto out; + } + + if (offset >= ni->data_size) + goto out; + + if (offset + len > ni->data_size) + end_offset = ni->data_size; + else + end_offset = offset + len; + + err = filemap_write_and_wait_range(vi->i_mapping, offset_down, LLONG_MAX); + if (err) + goto out; + truncate_pagecache(vi, offset_down); + + start_vcn = ntfs_bytes_to_cluster(vol, offset); + end_vcn = ntfs_bytes_to_cluster(vol, end_offset - 1) + 1; + + if (offset & vol->cluster_size_mask) { + loff_t to; + + to = min_t(loff_t, ntfs_cluster_to_bytes(vol, start_vcn + 1), + end_offset); + err = iomap_zero_range(vi, offset, to - offset, NULL, + &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + if (err < 0 || (end_vcn - start_vcn) == 1) + goto out; + start_vcn++; + } + + if (end_offset & vol->cluster_size_mask) { + loff_t from; + + from = ntfs_cluster_to_bytes(vol, end_vcn - 1); + err = iomap_zero_range(vi, from, end_offset - from, NULL, + &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + if (err < 0 || (end_vcn - start_vcn) == 1) + goto out; + end_vcn--; + } + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + err = ntfs_non_resident_attr_punch_hole(ni, start_vcn, + end_vcn - start_vcn); + mutex_unlock(&ni->mrec_lock); +out: + return err; +} + +static int ntfs_collapse_range(struct ntfs_inode *ni, loff_t offset, loff_t len) +{ + struct ntfs_volume *vol = ni->vol; + struct inode *vi = VFS_I(ni); + loff_t old_size, new_size; + s64 start_vcn, end_vcn; + int err; + + loff_t offset_down = round_down(offset, + max_t(unsigned long, vol->cluster_size, PAGE_SIZE)); + + if ((offset & vol->cluster_size_mask) || + (len & vol->cluster_size_mask) || + offset >= ni->allocated_size) { + err = -EINVAL; + goto out; + } + + old_size = i_size_read(vi); + start_vcn = ntfs_bytes_to_cluster(vol, offset); + end_vcn = ntfs_bytes_to_cluster(vol, offset + len - 1) + 1; + + if (ntfs_cluster_to_bytes(vol, end_vcn) > ni->allocated_size) + end_vcn = (round_up(ni->allocated_size - 1, + vol->cluster_size) >> vol->cluster_size_bits) + 1; + new_size = old_size - ntfs_cluster_to_bytes(vol, end_vcn - start_vcn); + if (new_size < 0) + new_size = 0; + err = filemap_write_and_wait_range(vi->i_mapping, + offset_down, LLONG_MAX); + if (err) + goto out; + + truncate_pagecache(vi, offset_down); + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + err = ntfs_non_resident_attr_collapse_range(ni, start_vcn, + end_vcn - start_vcn); + mutex_unlock(&ni->mrec_lock); + + if (new_size != old_size) + i_size_write(vi, ni->data_size); +out: + return err; +} + +static int ntfs_insert_range(struct ntfs_inode *ni, loff_t offset, loff_t len) +{ + struct ntfs_volume *vol = ni->vol; + struct inode *vi = VFS_I(ni); + loff_t offset_down = round_down(offset, + max_t(unsigned long, vol->cluster_size, PAGE_SIZE)); + loff_t alloc_size, end_offset = offset + len; + loff_t old_size, new_size; + s64 start_vcn, end_vcn; + int err; + + if (NVolDisableSparse(vol)) { + err = -EOPNOTSUPP; + goto out; + } + + if ((offset & vol->cluster_size_mask) || + (len & vol->cluster_size_mask) || + offset >= ni->allocated_size) { + err = -EINVAL; + goto out; + } + + old_size = i_size_read(vi); + start_vcn = ntfs_bytes_to_cluster(vol, offset); + end_vcn = ntfs_bytes_to_cluster(vol, end_offset - 1) + 1; + + new_size = old_size + ntfs_cluster_to_bytes(vol, end_vcn - start_vcn); + alloc_size = ni->allocated_size + + ntfs_cluster_to_bytes(vol, end_vcn - start_vcn); + if (alloc_size < 0) { + err = -EFBIG; + goto out; + } + err = inode_newsize_ok(vi, alloc_size); + if (err) + goto out; + + err = filemap_write_and_wait_range(vi->i_mapping, + offset_down, LLONG_MAX); + if (err) + goto out; + + truncate_pagecache(vi, offset_down); + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + err = ntfs_non_resident_attr_insert_range(ni, start_vcn, + end_vcn - start_vcn); + mutex_unlock(&ni->mrec_lock); + + if (new_size != old_size) + i_size_write(vi, ni->data_size); +out: + return err; +} + +#define NTFS_FALLOC_FL_SUPPORTED \ + (FALLOC_FL_ALLOCATE_RANGE | FALLOC_FL_KEEP_SIZE | \ + FALLOC_FL_INSERT_RANGE | FALLOC_FL_PUNCH_HOLE | \ + FALLOC_FL_COLLAPSE_RANGE) + +static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) +{ + struct inode *vi = file_inode(file); + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + int err = 0; + loff_t old_size; + bool map_locked = false; + + if (mode & ~(NTFS_FALLOC_FL_SUPPORTED)) + return -EOPNOTSUPP; + + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + + if ((ni->vol->mft_zone_end - ni->vol->mft_zone_start) == 0) + return -ENOSPC; + + if (NInoNonResident(ni) && !NInoFullyMapped(ni)) { + down_write(&ni->runlist.lock); + err = ntfs_attr_map_whole_runlist(ni); + up_write(&ni->runlist.lock); + if (err) + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) { + err = ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + if (err) + return err; + } + + old_size = i_size_read(vi); + + inode_lock(vi); + if (NInoCompressed(ni) || NInoEncrypted(ni)) { + err = -EOPNOTSUPP; + goto out; + } + + inode_dio_wait(vi); + if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE | + FALLOC_FL_INSERT_RANGE)) { + filemap_invalidate_lock(vi->i_mapping); + map_locked = true; + } + + switch (mode & FALLOC_FL_MODE_MASK) { + case FALLOC_FL_ALLOCATE_RANGE: + case FALLOC_FL_KEEP_SIZE: + err = ntfs_allocate_range(ni, mode, offset, len); + break; + case FALLOC_FL_PUNCH_HOLE: + err = ntfs_punch_hole(ni, mode, offset, len); + break; + case FALLOC_FL_COLLAPSE_RANGE: + err = ntfs_collapse_range(ni, offset, len); + break; + case FALLOC_FL_INSERT_RANGE: + err = ntfs_insert_range(ni, offset, len); + break; + default: + err = -EOPNOTSUPP; + } + + if (err) + goto out; + + err = file_modified(file); +out: + if (map_locked) + filemap_invalidate_unlock(vi->i_mapping); + if (!err) { + if (mode == 0 && NInoNonResident(ni) && + offset > old_size && old_size % PAGE_SIZE != 0) { + loff_t len = min_t(loff_t, + round_up(old_size, PAGE_SIZE) - old_size, + offset - old_size); + err = iomap_zero_range(vi, old_size, len, NULL, + &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + } + NInoSetFileNameDirty(ni); + inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); + mark_inode_dirty(vi); + } + + inode_unlock(vi); + return err; +} + +const struct file_operations ntfs_file_ops = { + .llseek = ntfs_file_llseek, + .read_iter = ntfs_file_read_iter, + .write_iter = ntfs_file_write_iter, + .fsync = ntfs_file_fsync, + .mmap_prepare = ntfs_file_mmap_prepare, + .open = ntfs_file_open, + .release = ntfs_file_release, + .splice_read = ntfs_file_splice_read, + .splice_write = iter_file_splice_write, + .unlocked_ioctl = ntfs_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ntfs_compat_ioctl, +#endif + .fallocate = ntfs_fallocate, + .setlease = generic_setlease, +}; + +const struct inode_operations ntfs_file_inode_ops = { + .setattr = ntfs_setattr, + .getattr = ntfs_getattr, + .listxattr = ntfs_listxattr, + .get_acl = ntfs_get_acl, + .set_acl = ntfs_set_acl, + .fiemap = ntfs_fiemap, +}; + +const struct inode_operations ntfs_symlink_inode_operations = { + .get_link = ntfs_get_link, + .setattr = ntfs_setattr, + .listxattr = ntfs_listxattr, +}; + +const struct inode_operations ntfs_special_inode_operations = { + .setattr = ntfs_setattr, + .getattr = ntfs_getattr, + .listxattr = ntfs_listxattr, + .get_acl = ntfs_get_acl, + .set_acl = ntfs_set_acl, +}; + +const struct file_operations ntfs_empty_file_ops = {}; + +const struct inode_operations ntfs_empty_inode_ops = {}; diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c new file mode 100644 index 000000000000..2080f3969137 --- /dev/null +++ b/fs/ntfs/index.c @@ -0,0 +1,2117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel index handling. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2025 LG Electronics Co., Ltd. + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2005-2008 Szabolcs Szakacsits + * Copyright (c) 2007-2021 Jean-Pierre Andre + */ + +#include "collate.h" +#include "index.h" +#include "ntfs.h" +#include "attrlist.h" + +/* + * ntfs_index_entry_inconsistent - Check the consistency of an index entry + * + * Make sure data and key do not overflow from entry. + * As a side effect, an entry with zero length is rejected. + * This entry must be a full one (no INDEX_ENTRY_END flag), and its + * length must have been checked beforehand to not overflow from the + * index record. + */ +int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, + struct ntfs_volume *vol, const struct index_entry *ie, + __le32 collation_rule, u64 inum) +{ + if (icx) { + struct index_header *ih; + u8 *ie_start, *ie_end; + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + if ((le32_to_cpu(ih->index_length) > le32_to_cpu(ih->allocated_size)) || + (le32_to_cpu(ih->index_length) > icx->block_size)) { + ntfs_error(vol->sb, "%s Index entry(0x%p)'s length is too big.", + icx->is_in_root ? "Index root" : "Index block", + (u8 *)icx->entry); + return -EINVAL; + } + + ie_start = (u8 *)ih + le32_to_cpu(ih->entries_offset); + ie_end = (u8 *)ih + le32_to_cpu(ih->index_length); + + if (ie_start > (u8 *)ie || + ie_end <= (u8 *)ie + le16_to_cpu(ie->length) || + le16_to_cpu(ie->length) > le32_to_cpu(ih->allocated_size) || + le16_to_cpu(ie->length) > icx->block_size) { + ntfs_error(vol->sb, "Index entry(0x%p) is out of range from %s", + (u8 *)icx->entry, + icx->is_in_root ? "index root" : "index block"); + return -EIO; + } + } + + if (ie->key_length && + ((le16_to_cpu(ie->key_length) + offsetof(struct index_entry, key)) > + le16_to_cpu(ie->length))) { + ntfs_error(vol->sb, "Overflow from index entry in inode %lld\n", + (long long)inum); + return -EIO; + + } else { + if (collation_rule == COLLATION_FILE_NAME) { + if ((offsetof(struct index_entry, key.file_name.file_name) + + ie->key.file_name.file_name_length * sizeof(__le16)) > + le16_to_cpu(ie->length)) { + ntfs_error(vol->sb, + "File name overflow from index entry in inode %lld\n", + (long long)inum); + return -EIO; + } + } else { + if (ie->data.vi.data_length && + ((le16_to_cpu(ie->data.vi.data_offset) + + le16_to_cpu(ie->data.vi.data_length)) > + le16_to_cpu(ie->length))) { + ntfs_error(vol->sb, + "Data overflow from index entry in inode %lld\n", + (long long)inum); + return -EIO; + } + } + } + + return 0; +} + +/* + * ntfs_index_entry_mark_dirty - mark an index entry dirty + * @ictx: ntfs index context describing the index entry + * + * Mark the index entry described by the index entry context @ictx dirty. + * + * If the index entry is in the index root attribute, simply mark the inode + * containing the index root attribute dirty. This ensures the mftrecord, and + * hence the index root attribute, will be written out to disk later. + * + * If the index entry is in an index block belonging to the index allocation + * attribute, set ib_dirty to true, thus index block will be updated during + * ntfs_index_ctx_put. + */ +void ntfs_index_entry_mark_dirty(struct ntfs_index_context *ictx) +{ + if (ictx->is_in_root) + mark_mft_record_dirty(ictx->actx->ntfs_ino); + else if (ictx->ib) + ictx->ib_dirty = true; +} + +static s64 ntfs_ib_vcn_to_pos(struct ntfs_index_context *icx, s64 vcn) +{ + return vcn << icx->vcn_size_bits; +} + +static s64 ntfs_ib_pos_to_vcn(struct ntfs_index_context *icx, s64 pos) +{ + return pos >> icx->vcn_size_bits; +} + +static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib) +{ + s64 ret, vcn = le64_to_cpu(ib->index_block_vcn); + + ntfs_debug("vcn: %lld\n", vcn); + + ret = pre_write_mst_fixup((struct ntfs_record *)ib, icx->block_size); + if (ret) + return -EIO; + + ret = ntfs_inode_attr_pwrite(VFS_I(icx->ia_ni), + ntfs_ib_vcn_to_pos(icx, vcn), icx->block_size, + (u8 *)ib, icx->sync_write); + if (ret != icx->block_size) { + ntfs_debug("Failed to write index block %lld, inode %llu", + vcn, (unsigned long long)icx->idx_ni->mft_no); + return ret; + } + + return 0; +} + +static int ntfs_icx_ib_write(struct ntfs_index_context *icx) +{ + int err; + + err = ntfs_ib_write(icx, icx->ib); + if (err) + return err; + + icx->ib_dirty = false; + + return 0; +} + +int ntfs_icx_ib_sync_write(struct ntfs_index_context *icx) +{ + int ret; + + if (icx->ib_dirty == false) + return 0; + + icx->sync_write = true; + + ret = ntfs_ib_write(icx, icx->ib); + if (!ret) { + kvfree(icx->ib); + icx->ib = NULL; + icx->ib_dirty = false; + } else { + post_write_mst_fixup((struct ntfs_record *)icx->ib); + icx->sync_write = false; + } + + return ret; +} + +/* + * ntfs_index_ctx_get - allocate and initialize a new index context + * @ni: ntfs inode with which to initialize the context + * @name: name of the which context describes + * @name_len: length of the index name + * + * Allocate a new index context, initialize it with @ni and return it. + * Return NULL if allocation failed. + */ +struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, + __le16 *name, u32 name_len) +{ + struct ntfs_index_context *icx; + + ntfs_debug("Entering\n"); + + if (!ni) + return NULL; + + if (ni->nr_extents == -1) + ni = ni->ext.base_ntfs_ino; + + icx = kmem_cache_alloc(ntfs_index_ctx_cache, GFP_NOFS); + if (icx) + *icx = (struct ntfs_index_context) { + .idx_ni = ni, + .name = name, + .name_len = name_len, + }; + return icx; +} + +static void ntfs_index_ctx_free(struct ntfs_index_context *icx) +{ + ntfs_debug("Entering\n"); + + if (icx->actx) { + ntfs_attr_put_search_ctx(icx->actx); + icx->actx = NULL; + } + + if (!icx->is_in_root) { + if (icx->ib_dirty) + ntfs_ib_write(icx, icx->ib); + kvfree(icx->ib); + icx->ib = NULL; + } + + if (icx->ia_ni) { + iput(VFS_I(icx->ia_ni)); + icx->ia_ni = NULL; + } +} + +/* + * ntfs_index_ctx_put - release an index context + * @icx: index context to free + * + * Release the index context @icx, releasing all associated resources. + */ +void ntfs_index_ctx_put(struct ntfs_index_context *icx) +{ + ntfs_index_ctx_free(icx); + kmem_cache_free(ntfs_index_ctx_cache, icx); +} + +/* + * ntfs_index_ctx_reinit - reinitialize an index context + * @icx: index context to reinitialize + * + * Reinitialize the index context @icx so it can be used for ntfs_index_lookup. + */ +void ntfs_index_ctx_reinit(struct ntfs_index_context *icx) +{ + ntfs_debug("Entering\n"); + + ntfs_index_ctx_free(icx); + + *icx = (struct ntfs_index_context) { + .idx_ni = icx->idx_ni, + .name = icx->name, + .name_len = icx->name_len, + }; +} + +static __le64 *ntfs_ie_get_vcn_addr(struct index_entry *ie) +{ + return (__le64 *)((u8 *)ie + le16_to_cpu(ie->length) - sizeof(s64)); +} + +/* + * Get the subnode vcn to which the index entry refers. + */ +static s64 ntfs_ie_get_vcn(struct index_entry *ie) +{ + return le64_to_cpup(ntfs_ie_get_vcn_addr(ie)); +} + +static struct index_entry *ntfs_ie_get_first(struct index_header *ih) +{ + return (struct index_entry *)((u8 *)ih + le32_to_cpu(ih->entries_offset)); +} + +static struct index_entry *ntfs_ie_get_next(struct index_entry *ie) +{ + return (struct index_entry *)((char *)ie + le16_to_cpu(ie->length)); +} + +static u8 *ntfs_ie_get_end(struct index_header *ih) +{ + return (u8 *)ih + le32_to_cpu(ih->index_length); +} + +static int ntfs_ie_end(struct index_entry *ie) +{ + return ie->flags & INDEX_ENTRY_END || !ie->length; +} + +/* + * Find the last entry in the index block + */ +static struct index_entry *ntfs_ie_get_last(struct index_entry *ie, char *ies_end) +{ + ntfs_debug("Entering\n"); + + while ((char *)ie < ies_end && !ntfs_ie_end(ie)) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static struct index_entry *ntfs_ie_get_by_pos(struct index_header *ih, int pos) +{ + struct index_entry *ie; + + ntfs_debug("pos: %d\n", pos); + + ie = ntfs_ie_get_first(ih); + + while (pos-- > 0) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static struct index_entry *ntfs_ie_prev(struct index_header *ih, struct index_entry *ie) +{ + struct index_entry *ie_prev = NULL; + struct index_entry *tmp; + + ntfs_debug("Entering\n"); + + tmp = ntfs_ie_get_first(ih); + + while (tmp != ie) { + ie_prev = tmp; + tmp = ntfs_ie_get_next(tmp); + } + + return ie_prev; +} + +static int ntfs_ih_numof_entries(struct index_header *ih) +{ + int n; + struct index_entry *ie; + u8 *end; + + ntfs_debug("Entering\n"); + + end = ntfs_ie_get_end(ih); + ie = ntfs_ie_get_first(ih); + for (n = 0; !ntfs_ie_end(ie) && (u8 *)ie < end; n++) + ie = ntfs_ie_get_next(ie); + return n; +} + +static int ntfs_ih_one_entry(struct index_header *ih) +{ + return (ntfs_ih_numof_entries(ih) == 1); +} + +static int ntfs_ih_zero_entry(struct index_header *ih) +{ + return (ntfs_ih_numof_entries(ih) == 0); +} + +static void ntfs_ie_delete(struct index_header *ih, struct index_entry *ie) +{ + u32 new_size; + + ntfs_debug("Entering\n"); + + new_size = le32_to_cpu(ih->index_length) - le16_to_cpu(ie->length); + ih->index_length = cpu_to_le32(new_size); + memmove(ie, (u8 *)ie + le16_to_cpu(ie->length), + new_size - ((u8 *)ie - (u8 *)ih)); +} + +static void ntfs_ie_set_vcn(struct index_entry *ie, s64 vcn) +{ + *ntfs_ie_get_vcn_addr(ie) = cpu_to_le64(vcn); +} + +/* + * Insert @ie index entry at @pos entry. Used @ih values should be ok already. + */ +static void ntfs_ie_insert(struct index_header *ih, struct index_entry *ie, + struct index_entry *pos) +{ + int ie_size = le16_to_cpu(ie->length); + + ntfs_debug("Entering\n"); + + ih->index_length = cpu_to_le32(le32_to_cpu(ih->index_length) + ie_size); + memmove((u8 *)pos + ie_size, pos, + le32_to_cpu(ih->index_length) - ((u8 *)pos - (u8 *)ih) - ie_size); + memcpy(pos, ie, ie_size); +} + +static struct index_entry *ntfs_ie_dup(struct index_entry *ie) +{ + ntfs_debug("Entering\n"); + + return kmemdup(ie, le16_to_cpu(ie->length), GFP_NOFS); +} + +static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie) +{ + struct index_entry *dup; + int size = le16_to_cpu(ie->length); + + ntfs_debug("Entering\n"); + + if (ie->flags & INDEX_ENTRY_NODE) + size -= sizeof(s64); + + dup = kmemdup(ie, size, GFP_NOFS); + if (dup) { + dup->flags &= ~INDEX_ENTRY_NODE; + dup->length = cpu_to_le16(size); + } + return dup; +} + +/* + * Check the consistency of an index block + * + * Make sure the index block does not overflow from the index record. + * The size of block is assumed to have been checked to be what is + * defined in the index root. + * + * Returns 0 if no error was found -1 otherwise (with errno unchanged) + * + * |<--->| offsetof(struct index_block, index) + * | |<--->| sizeof(struct index_header) + * | | | + * | | | seq index entries unused + * |=====|=====|=====|===========================|==============| + * | | | | | + * | |<--------->| entries_offset | | + * | |<---------------- index_length ------->| | + * | |<--------------------- allocated_size --------------->| + * |<--------------------------- block_size ------------------->| + * + * size(struct index_header) <= ent_offset < ind_length <= alloc_size < bk_size + */ +static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx, + struct index_block *ib, s64 vcn) +{ + u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) + + offsetof(struct index_block, index); + struct super_block *sb = icx->idx_ni->vol->sb; + unsigned long long inum = icx->idx_ni->mft_no; + + ntfs_debug("Entering\n"); + + if (!ntfs_is_indx_record(ib->magic)) { + + ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n", + vcn, (unsigned long long)icx->idx_ni->mft_no); + return -1; + } + + if (le64_to_cpu(ib->index_block_vcn) != vcn) { + ntfs_error(sb, + "Corrupt index block: s64 (%lld) is different from expected s64 (%lld) in inode %llu\n", + (long long)le64_to_cpu(ib->index_block_vcn), + vcn, inum); + return -1; + } + + if (ib_size != icx->block_size) { + ntfs_error(sb, + "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n", + vcn, inum, ib_size, icx->block_size); + return -1; + } + + if (le32_to_cpu(ib->index.entries_offset) < sizeof(struct index_header)) { + ntfs_error(sb, "Invalid index entry offset in inode %lld\n", inum); + return -1; + } + if (le32_to_cpu(ib->index.index_length) <= + le32_to_cpu(ib->index.entries_offset)) { + ntfs_error(sb, "No space for index entries in inode %lld\n", inum); + return -1; + } + if (le32_to_cpu(ib->index.allocated_size) < + le32_to_cpu(ib->index.index_length)) { + ntfs_error(sb, "Index entries overflow in inode %lld\n", inum); + return -1; + } + + return 0; +} + +static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name, + u32 name_len, struct ntfs_attr_search_ctx **ctx) +{ + struct attr_record *a; + struct index_root *ir = NULL; + + ntfs_debug("Entering\n"); + *ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!*ctx) { + ntfs_error(ni->vol->sb, "%s, Failed to get search context", __func__); + return NULL; + } + + if (ntfs_attr_lookup(AT_INDEX_ROOT, name, name_len, CASE_SENSITIVE, + 0, NULL, 0, *ctx)) { + ntfs_error(ni->vol->sb, "Failed to lookup $INDEX_ROOT"); + goto err_out; + } + + a = (*ctx)->attr; + if (a->non_resident) { + ntfs_error(ni->vol->sb, "Non-resident $INDEX_ROOT detected"); + goto err_out; + } + + ir = (struct index_root *)((char *)a + le16_to_cpu(a->data.resident.value_offset)); +err_out: + if (!ir) { + ntfs_attr_put_search_ctx(*ctx); + *ctx = NULL; + } + return ir; +} + +static struct index_root *ntfs_ir_lookup2(struct ntfs_inode *ni, __le16 *name, u32 len) +{ + struct ntfs_attr_search_ctx *ctx; + struct index_root *ir; + + ir = ntfs_ir_lookup(ni, name, len, &ctx); + if (ir) + ntfs_attr_put_search_ctx(ctx); + return ir; +} + +/* + * Find a key in the index block. + */ +static int ntfs_ie_lookup(const void *key, const u32 key_len, + struct ntfs_index_context *icx, struct index_header *ih, + s64 *vcn, struct index_entry **ie_out) +{ + struct index_entry *ie; + u8 *index_end; + int rc, item = 0; + + ntfs_debug("Entering\n"); + + index_end = ntfs_ie_get_end(ih); + + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (ie = ntfs_ie_get_first(ih); ; ie = ntfs_ie_get_next(ie)) { + /* Bounds checks. */ + if ((u8 *)ie + sizeof(struct index_entry_header) > index_end || + (u8 *)ie + le16_to_cpu(ie->length) > index_end) { + ntfs_error(icx->idx_ni->vol->sb, + "Index entry out of bounds in inode %llu.\n", + (unsigned long long)icx->idx_ni->mft_no); + return -ERANGE; + } + + /* + * The last entry cannot contain a key. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ntfs_ie_end(ie)) + break; + + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_collate(icx->idx_ni->vol, icx->cr, key, key_len, &ie->key, + le16_to_cpu(ie->key_length)); + if (rc == -EINVAL) { + ntfs_error(icx->idx_ni->vol->sb, + "Collation error. Perhaps a filename contains invalid characters?\n"); + return -ERANGE; + } + /* + * If @key collates before the key of the current entry, there + * is definitely no such key in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + + if (!rc) { + *ie_out = ie; + icx->parent_pos[icx->pindex] = item; + return 0; + } + + item++; + } + /* + * We have finished with this index block without success. Check for the + * presence of a child node and if not present return with errno ENOENT, + * otherwise we will keep searching in another index block. + */ + if (!(ie->flags & INDEX_ENTRY_NODE)) { + ntfs_debug("Index entry wasn't found.\n"); + *ie_out = ie; + return -ENOENT; + } + + /* Get the starting vcn of the index_block holding the child node. */ + *vcn = ntfs_ie_get_vcn(ie); + if (*vcn < 0) { + ntfs_error(icx->idx_ni->vol->sb, "Negative vcn in inode %llu\n", + (unsigned long long)icx->idx_ni->mft_no); + return -EINVAL; + } + + ntfs_debug("Parent entry number %d\n", item); + icx->parent_pos[icx->pindex] = item; + + return -EAGAIN; +} + +struct ntfs_inode *ntfs_ia_open(struct ntfs_index_context *icx, struct ntfs_inode *ni) +{ + struct inode *ia_vi; + + ia_vi = ntfs_index_iget(VFS_I(ni), icx->name, icx->name_len); + if (IS_ERR(ia_vi)) { + ntfs_error(icx->idx_ni->vol->sb, + "Failed to open index allocation of inode %llu", + (unsigned long long)ni->mft_no); + return NULL; + } + + return NTFS_I(ia_vi); +} + +static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_block *dst) +{ + s64 pos, ret; + + ntfs_debug("vcn: %lld\n", vcn); + + pos = ntfs_ib_vcn_to_pos(icx, vcn); + + ret = ntfs_inode_attr_pread(VFS_I(icx->ia_ni), pos, icx->block_size, (u8 *)dst); + if (ret != icx->block_size) { + if (ret == -1) + ntfs_error(icx->idx_ni->vol->sb, "Failed to read index block"); + else + ntfs_error(icx->idx_ni->vol->sb, + "Failed to read full index block at %lld\n", pos); + return -1; + } + + post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size); + if (ntfs_index_block_inconsistent(icx, dst, vcn)) + return -1; + + return 0; +} + +static int ntfs_icx_parent_inc(struct ntfs_index_context *icx) +{ + icx->pindex++; + if (icx->pindex >= MAX_PARENT_VCN) { + ntfs_error(icx->idx_ni->vol->sb, "Index is over %d level deep", MAX_PARENT_VCN); + return -EOPNOTSUPP; + } + return 0; +} + +static int ntfs_icx_parent_dec(struct ntfs_index_context *icx) +{ + icx->pindex--; + if (icx->pindex < 0) { + ntfs_error(icx->idx_ni->vol->sb, "Corrupt index pointer (%d)", icx->pindex); + return -EINVAL; + } + return 0; +} + +/* + * ntfs_index_lookup - find a key in an index and return its index entry + * @key: key for which to search in the index + * @key_len: length of @key in bytes + * @icx: context describing the index and the returned entry + * + * Before calling ntfs_index_lookup(), @icx must have been obtained from a + * call to ntfs_index_ctx_get(). + * + * Look for the @key in the index specified by the index lookup context @icx. + * ntfs_index_lookup() walks the contents of the index looking for the @key. + * + * If the @key is found in the index, 0 is returned and @icx is setup to + * describe the index entry containing the matching @key. @icx->entry is the + * index entry and @icx->data and @icx->data_len are the index entry data and + * its length in bytes, respectively. + * + * If the @key is not found in the index, -ENOENT is returned and + * @icx is setup to describe the index entry whose key collates immediately + * after the search @key, i.e. this is the position in the index at which + * an index entry with a key of @key would need to be inserted. + * + * When finished with the entry and its data, call ntfs_index_ctx_put() to free + * the context and other associated resources. + * + * If the index entry was modified, call ntfs_index_entry_mark_dirty() before + * the call to ntfs_index_ctx_put() to ensure that the changes are written + * to disk. + */ +int ntfs_index_lookup(const void *key, const u32 key_len, struct ntfs_index_context *icx) +{ + s64 old_vcn, vcn; + struct ntfs_inode *ni = icx->idx_ni; + struct super_block *sb = ni->vol->sb; + struct index_root *ir; + struct index_entry *ie; + struct index_block *ib = NULL; + int err = 0; + + ntfs_debug("Entering\n"); + + if (!key) { + ntfs_error(sb, "key: %p key_len: %d", key, key_len); + return -EINVAL; + } + + ir = ntfs_ir_lookup(ni, icx->name, icx->name_len, &icx->actx); + if (!ir) + return -EIO; + + icx->block_size = le32_to_cpu(ir->index_block_size); + if (icx->block_size < NTFS_BLOCK_SIZE) { + err = -EINVAL; + ntfs_error(sb, + "Index block size (%d) is smaller than the sector size (%d)", + icx->block_size, NTFS_BLOCK_SIZE); + goto err_out; + } + + if (ni->vol->cluster_size <= icx->block_size) + icx->vcn_size_bits = ni->vol->cluster_size_bits; + else + icx->vcn_size_bits = ni->vol->sector_size_bits; + + icx->cr = ir->collation_rule; + if (!ntfs_is_collation_rule_supported(icx->cr)) { + err = -EOPNOTSUPP; + ntfs_error(sb, "Unknown collation rule 0x%x", + (unsigned int)le32_to_cpu(icx->cr)); + goto err_out; + } + + old_vcn = VCN_INDEX_ROOT_PARENT; + err = ntfs_ie_lookup(key, key_len, icx, &ir->index, &vcn, &ie); + if (err == -ERANGE || err == -EINVAL) + goto err_out; + + icx->ir = ir; + if (err != -EAGAIN) { + icx->is_in_root = true; + icx->parent_vcn[icx->pindex] = old_vcn; + goto done; + } + + /* Child node present, descend into it. */ + icx->ia_ni = ntfs_ia_open(icx, ni); + if (!icx->ia_ni) { + err = -ENOENT; + goto err_out; + } + + ib = kvzalloc(icx->block_size, GFP_NOFS); + if (!ib) { + err = -ENOMEM; + goto err_out; + } + +descend_into_child_node: + icx->parent_vcn[icx->pindex] = old_vcn; + if (ntfs_icx_parent_inc(icx)) { + err = -EIO; + goto err_out; + } + old_vcn = vcn; + + ntfs_debug("Descend into node with s64 %lld.\n", vcn); + + if (ntfs_ib_read(icx, vcn, ib)) { + err = -EIO; + goto err_out; + } + err = ntfs_ie_lookup(key, key_len, icx, &ib->index, &vcn, &ie); + if (err != -EAGAIN) { + if (err == -EINVAL || err == -ERANGE) + goto err_out; + + icx->is_in_root = false; + icx->ib = ib; + icx->parent_vcn[icx->pindex] = vcn; + goto done; + } + + if ((ib->index.flags & NODE_MASK) == LEAF_NODE) { + ntfs_error(icx->idx_ni->vol->sb, + "Index entry with child node found in a leaf node in inode 0x%llx.\n", + (unsigned long long)ni->mft_no); + goto err_out; + } + + goto descend_into_child_node; +err_out: + if (icx->actx) { + ntfs_attr_put_search_ctx(icx->actx); + icx->actx = NULL; + } + kvfree(ib); + if (!err) + err = -EIO; + return err; +done: + icx->entry = ie; + icx->data = (u8 *)ie + offsetof(struct index_entry, key); + icx->data_len = le16_to_cpu(ie->key_length); + ntfs_debug("Done.\n"); + return err; + +} + +static struct index_block *ntfs_ib_alloc(s64 ib_vcn, u32 ib_size, + u8 node_type) +{ + struct index_block *ib; + int ih_size = sizeof(struct index_header); + + ntfs_debug("Entering ib_vcn = %lld ib_size = %u\n", ib_vcn, ib_size); + + ib = kvzalloc(ib_size, GFP_NOFS); + if (!ib) + return NULL; + + ib->magic = magic_INDX; + ib->usa_ofs = cpu_to_le16(sizeof(struct index_block)); + ib->usa_count = cpu_to_le16(ib_size / NTFS_BLOCK_SIZE + 1); + /* Set USN to 1 */ + *(__le16 *)((char *)ib + le16_to_cpu(ib->usa_ofs)) = cpu_to_le16(1); + ib->lsn = 0; + ib->index_block_vcn = cpu_to_le64(ib_vcn); + ib->index.entries_offset = cpu_to_le32((ih_size + + le16_to_cpu(ib->usa_count) * 2 + 7) & ~7); + ib->index.index_length = 0; + ib->index.allocated_size = cpu_to_le32(ib_size - + (sizeof(struct index_block) - ih_size)); + ib->index.flags = node_type; + + return ib; +} + +/* + * Find the median by going through all the entries + */ +static struct index_entry *ntfs_ie_get_median(struct index_header *ih) +{ + struct index_entry *ie, *ie_start; + u8 *ie_end; + int i = 0, median; + + ntfs_debug("Entering\n"); + + ie = ie_start = ntfs_ie_get_first(ih); + ie_end = (u8 *)ntfs_ie_get_end(ih); + + while ((u8 *)ie < ie_end && !ntfs_ie_end(ie)) { + ie = ntfs_ie_get_next(ie); + i++; + } + /* + * NOTE: this could be also the entry at the half of the index block. + */ + median = i / 2 - 1; + + ntfs_debug("Entries: %d median: %d\n", i, median); + + for (i = 0, ie = ie_start; i <= median; i++) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static u64 ntfs_ibm_vcn_to_pos(struct ntfs_index_context *icx, s64 vcn) +{ + u64 pos = ntfs_ib_vcn_to_pos(icx, vcn); + + do_div(pos, icx->block_size); + return pos; +} + +static s64 ntfs_ibm_pos_to_vcn(struct ntfs_index_context *icx, s64 pos) +{ + return ntfs_ib_pos_to_vcn(icx, pos * icx->block_size); +} + +static int ntfs_ibm_add(struct ntfs_index_context *icx) +{ + u8 bmp[8]; + + ntfs_debug("Entering\n"); + + if (ntfs_attr_exist(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len)) + return 0; + /* + * AT_BITMAP must be at least 8 bytes. + */ + memset(bmp, 0, sizeof(bmp)); + if (ntfs_attr_add(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len, + bmp, sizeof(bmp))) { + ntfs_error(icx->idx_ni->vol->sb, "Failed to add AT_BITMAP"); + return -EINVAL; + } + + return 0; +} + +static int ntfs_ibm_modify(struct ntfs_index_context *icx, s64 vcn, int set) +{ + u8 byte; + u64 pos = ntfs_ibm_vcn_to_pos(icx, vcn); + u32 bpos = pos / 8; + u32 bit = 1 << (pos % 8); + struct ntfs_inode *bmp_ni; + struct inode *bmp_vi; + int ret = 0; + + ntfs_debug("%s vcn: %lld\n", set ? "set" : "clear", vcn); + + bmp_vi = ntfs_attr_iget(VFS_I(icx->idx_ni), AT_BITMAP, icx->name, icx->name_len); + if (IS_ERR(bmp_vi)) { + ntfs_error(icx->idx_ni->vol->sb, "Failed to open $BITMAP attribute"); + return PTR_ERR(bmp_vi); + } + + bmp_ni = NTFS_I(bmp_vi); + + if (set) { + if (bmp_ni->data_size < bpos + 1) { + ret = ntfs_attr_truncate(bmp_ni, (bmp_ni->data_size + 8) & ~7); + if (ret) { + ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate AT_BITMAP"); + goto err; + } + i_size_write(bmp_vi, (loff_t)bmp_ni->data_size); + } + } + + if (ntfs_inode_attr_pread(bmp_vi, bpos, 1, &byte) != 1) { + ret = -EIO; + ntfs_error(icx->idx_ni->vol->sb, "Failed to read $BITMAP"); + goto err; + } + + if (set) + byte |= bit; + else + byte &= ~bit; + + if (ntfs_inode_attr_pwrite(bmp_vi, bpos, 1, &byte, false) != 1) { + ret = -EIO; + ntfs_error(icx->idx_ni->vol->sb, "Failed to write $Bitmap"); + goto err; + } + +err: + iput(bmp_vi); + return ret; +} + +static int ntfs_ibm_set(struct ntfs_index_context *icx, s64 vcn) +{ + return ntfs_ibm_modify(icx, vcn, 1); +} + +static int ntfs_ibm_clear(struct ntfs_index_context *icx, s64 vcn) +{ + return ntfs_ibm_modify(icx, vcn, 0); +} + +static s64 ntfs_ibm_get_free(struct ntfs_index_context *icx) +{ + u8 *bm; + int bit; + s64 vcn, byte, size; + + ntfs_debug("Entering\n"); + + bm = ntfs_attr_readall(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len, + &size); + if (!bm) + return (s64)-1; + + for (byte = 0; byte < size; byte++) { + if (bm[byte] == 255) + continue; + + for (bit = 0; bit < 8; bit++) { + if (!(bm[byte] & (1 << bit))) { + vcn = ntfs_ibm_pos_to_vcn(icx, byte * 8 + bit); + goto out; + } + } + } + + vcn = ntfs_ibm_pos_to_vcn(icx, size * 8); +out: + ntfs_debug("allocated vcn: %lld\n", vcn); + + if (ntfs_ibm_set(icx, vcn)) + vcn = (s64)-1; + + kvfree(bm); + return vcn; +} + +static struct index_block *ntfs_ir_to_ib(struct index_root *ir, s64 ib_vcn) +{ + struct index_block *ib; + struct index_entry *ie_last; + char *ies_start, *ies_end; + int i; + + ntfs_debug("Entering\n"); + + ib = ntfs_ib_alloc(ib_vcn, le32_to_cpu(ir->index_block_size), LEAF_NODE); + if (!ib) + return NULL; + + ies_start = (char *)ntfs_ie_get_first(&ir->index); + ies_end = (char *)ntfs_ie_get_end(&ir->index); + ie_last = ntfs_ie_get_last((struct index_entry *)ies_start, ies_end); + /* + * Copy all entries, including the termination entry + * as well, which can never have any data. + */ + i = (char *)ie_last - ies_start + le16_to_cpu(ie_last->length); + memcpy(ntfs_ie_get_first(&ib->index), ies_start, i); + + ib->index.flags = ir->index.flags; + ib->index.index_length = cpu_to_le32(i + + le32_to_cpu(ib->index.entries_offset)); + return ib; +} + +static void ntfs_ir_nill(struct index_root *ir) +{ + struct index_entry *ie_last; + char *ies_start, *ies_end; + + ntfs_debug("Entering\n"); + + ies_start = (char *)ntfs_ie_get_first(&ir->index); + ies_end = (char *)ntfs_ie_get_end(&ir->index); + ie_last = ntfs_ie_get_last((struct index_entry *)ies_start, ies_end); + /* + * Move the index root termination entry forward + */ + if ((char *)ie_last > ies_start) { + memmove((char *)ntfs_ie_get_first(&ir->index), + (char *)ie_last, le16_to_cpu(ie_last->length)); + ie_last = (struct index_entry *)ies_start; + } +} + +static int ntfs_ib_copy_tail(struct ntfs_index_context *icx, struct index_block *src, + struct index_entry *median, s64 new_vcn) +{ + u8 *ies_end; + struct index_entry *ie_head; /* first entry after the median */ + int tail_size, ret; + struct index_block *dst; + + ntfs_debug("Entering\n"); + + dst = ntfs_ib_alloc(new_vcn, icx->block_size, + src->index.flags & NODE_MASK); + if (!dst) + return -ENOMEM; + + ie_head = ntfs_ie_get_next(median); + + ies_end = (u8 *)ntfs_ie_get_end(&src->index); + tail_size = ies_end - (u8 *)ie_head; + memcpy(ntfs_ie_get_first(&dst->index), ie_head, tail_size); + + dst->index.index_length = cpu_to_le32(tail_size + + le32_to_cpu(dst->index.entries_offset)); + ret = ntfs_ib_write(icx, dst); + + kvfree(dst); + return ret; +} + +static int ntfs_ib_cut_tail(struct ntfs_index_context *icx, struct index_block *ib, + struct index_entry *ie) +{ + char *ies_start, *ies_end; + struct index_entry *ie_last; + int ret; + + ntfs_debug("Entering\n"); + + ies_start = (char *)ntfs_ie_get_first(&ib->index); + ies_end = (char *)ntfs_ie_get_end(&ib->index); + + ie_last = ntfs_ie_get_last((struct index_entry *)ies_start, ies_end); + if (ie_last->flags & INDEX_ENTRY_NODE) + ntfs_ie_set_vcn(ie_last, ntfs_ie_get_vcn(ie)); + + unsafe_memcpy(ie, ie_last, le16_to_cpu(ie_last->length), + /* alloc is larger than ie_last->length, see ntfs_ie_get_last() */); + + ib->index.index_length = cpu_to_le32(((char *)ie - ies_start) + + le16_to_cpu(ie->length) + le32_to_cpu(ib->index.entries_offset)); + + ret = ntfs_ib_write(icx, ib); + return ret; +} + +static int ntfs_ia_add(struct ntfs_index_context *icx) +{ + int ret; + + ntfs_debug("Entering\n"); + + ret = ntfs_ibm_add(icx); + if (ret) + return ret; + + if (!ntfs_attr_exist(icx->idx_ni, AT_INDEX_ALLOCATION, icx->name, icx->name_len)) { + ret = ntfs_attr_add(icx->idx_ni, AT_INDEX_ALLOCATION, icx->name, + icx->name_len, NULL, 0); + if (ret) { + ntfs_error(icx->idx_ni->vol->sb, "Failed to add AT_INDEX_ALLOCATION"); + return ret; + } + } + + icx->ia_ni = ntfs_ia_open(icx, icx->idx_ni); + if (!icx->ia_ni) + return -ENOENT; + + return 0; +} + +static int ntfs_ir_reparent(struct ntfs_index_context *icx) +{ + struct ntfs_attr_search_ctx *ctx = NULL; + struct index_root *ir; + struct index_entry *ie; + struct index_block *ib = NULL; + s64 new_ib_vcn; + int ix_root_size; + int ret = 0; + + ntfs_debug("Entering\n"); + + ir = ntfs_ir_lookup2(icx->idx_ni, icx->name, icx->name_len); + if (!ir) { + ret = -ENOENT; + goto out; + } + + if ((ir->index.flags & NODE_MASK) == SMALL_INDEX) { + ret = ntfs_ia_add(icx); + if (ret) + goto out; + } + + new_ib_vcn = ntfs_ibm_get_free(icx); + if (new_ib_vcn < 0) { + ret = -EINVAL; + goto out; + } + + ir = ntfs_ir_lookup2(icx->idx_ni, icx->name, icx->name_len); + if (!ir) { + ret = -ENOENT; + goto clear_bmp; + } + + ib = ntfs_ir_to_ib(ir, new_ib_vcn); + if (ib == NULL) { + ret = -EIO; + ntfs_error(icx->idx_ni->vol->sb, "Failed to move index root to index block"); + goto clear_bmp; + } + + ret = ntfs_ib_write(icx, ib); + if (ret) + goto clear_bmp; + +retry: + ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx); + if (!ir) { + ret = -ENOENT; + goto clear_bmp; + } + + ntfs_ir_nill(ir); + + ie = ntfs_ie_get_first(&ir->index); + ie->flags |= INDEX_ENTRY_NODE; + ie->length = cpu_to_le16(sizeof(struct index_entry_header) + sizeof(s64)); + + ir->index.flags = LARGE_INDEX; + NInoSetIndexAllocPresent(icx->idx_ni); + ir->index.index_length = cpu_to_le32(le32_to_cpu(ir->index.entries_offset) + + le16_to_cpu(ie->length)); + ir->index.allocated_size = ir->index.index_length; + + ix_root_size = sizeof(struct index_root) - sizeof(struct index_header) + + le32_to_cpu(ir->index.allocated_size); + ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); + if (ret) { + /* + * When there is no space to build a non-resident + * index, we may have to move the root to an extent + */ + if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) { + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx); + if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size - + le32_to_cpu(ctx->attr->data.resident.value_length))) { + if (ntfs_attrlist_update(ctx->base_ntfs_ino ? + ctx->base_ntfs_ino : ctx->ntfs_ino)) + goto clear_bmp; + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + goto retry; + } + } + goto clear_bmp; + } else { + icx->idx_ni->data_size = icx->idx_ni->initialized_size = ix_root_size; + icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; + } + ntfs_ie_set_vcn(ie, new_ib_vcn); + +err_out: + kvfree(ib); + if (ctx) + ntfs_attr_put_search_ctx(ctx); +out: + return ret; +clear_bmp: + ntfs_ibm_clear(icx, new_ib_vcn); + goto err_out; +} + +/* + * ntfs_ir_truncate - Truncate index root attribute + * @icx: index context + * @data_size: new data size for the index root + */ +static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size) +{ + int ret; + + ntfs_debug("Entering\n"); + + /* + * INDEX_ROOT must be resident and its entries can be moved to + * struct index_block, so ENOSPC isn't a real error. + */ + ret = ntfs_attr_truncate(icx->idx_ni, data_size + offsetof(struct index_root, index)); + if (!ret) { + i_size_write(VFS_I(icx->idx_ni), icx->idx_ni->initialized_size); + icx->ir = ntfs_ir_lookup2(icx->idx_ni, icx->name, icx->name_len); + if (!icx->ir) + return -ENOENT; + + icx->ir->index.allocated_size = cpu_to_le32(data_size); + } else if (ret != -ENOSPC) + ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT"); + + return ret; +} + +/* + * ntfs_ir_make_space - Make more space for the index root attribute + * @icx: index context + * @data_size: required data size for the index root + */ +static int ntfs_ir_make_space(struct ntfs_index_context *icx, int data_size) +{ + int ret; + + ntfs_debug("Entering\n"); + + ret = ntfs_ir_truncate(icx, data_size); + if (ret == -ENOSPC) { + ret = ntfs_ir_reparent(icx); + if (!ret) + ret = -EAGAIN; + else + ntfs_error(icx->idx_ni->vol->sb, "Failed to modify INDEX_ROOT"); + } + + return ret; +} + +/* + * NOTE: 'ie' must be a copy of a real index entry. + */ +static int ntfs_ie_add_vcn(struct index_entry **ie) +{ + struct index_entry *p, *old = *ie; + + old->length = cpu_to_le16(le16_to_cpu(old->length) + sizeof(s64)); + p = krealloc(old, le16_to_cpu(old->length), GFP_NOFS); + if (!p) + return -ENOMEM; + + p->flags |= INDEX_ENTRY_NODE; + *ie = p; + return 0; +} + +static int ntfs_ih_insert(struct index_header *ih, struct index_entry *orig_ie, s64 new_vcn, + int pos) +{ + struct index_entry *ie_node, *ie; + int ret = 0; + s64 old_vcn; + + ntfs_debug("Entering\n"); + ie = ntfs_ie_dup(orig_ie); + if (!ie) + return -ENOMEM; + + if (!(ie->flags & INDEX_ENTRY_NODE)) { + ret = ntfs_ie_add_vcn(&ie); + if (ret) + goto out; + } + + ie_node = ntfs_ie_get_by_pos(ih, pos); + old_vcn = ntfs_ie_get_vcn(ie_node); + ntfs_ie_set_vcn(ie_node, new_vcn); + + ntfs_ie_insert(ih, ie, ie_node); + ntfs_ie_set_vcn(ie_node, old_vcn); +out: + kfree(ie); + return ret; +} + +static s64 ntfs_icx_parent_vcn(struct ntfs_index_context *icx) +{ + return icx->parent_vcn[icx->pindex]; +} + +static s64 ntfs_icx_parent_pos(struct ntfs_index_context *icx) +{ + return icx->parent_pos[icx->pindex]; +} + +static int ntfs_ir_insert_median(struct ntfs_index_context *icx, struct index_entry *median, + s64 new_vcn) +{ + u32 new_size; + int ret; + + ntfs_debug("Entering\n"); + + icx->ir = ntfs_ir_lookup2(icx->idx_ni, icx->name, icx->name_len); + if (!icx->ir) + return -ENOENT; + + new_size = le32_to_cpu(icx->ir->index.index_length) + + le16_to_cpu(median->length); + if (!(median->flags & INDEX_ENTRY_NODE)) + new_size += sizeof(s64); + + ret = ntfs_ir_make_space(icx, new_size); + if (ret) + return ret; + + icx->ir = ntfs_ir_lookup2(icx->idx_ni, icx->name, icx->name_len); + if (!icx->ir) + return -ENOENT; + + return ntfs_ih_insert(&icx->ir->index, median, new_vcn, + ntfs_icx_parent_pos(icx)); +} + +static int ntfs_ib_split(struct ntfs_index_context *icx, struct index_block *ib); + +struct split_info { + struct list_head entry; + s64 new_vcn; + struct index_block *ib; +}; + +static int ntfs_ib_insert(struct ntfs_index_context *icx, struct index_entry *ie, s64 new_vcn, + struct split_info *si) +{ + struct index_block *ib; + u32 idx_size, allocated_size; + int err; + s64 old_vcn; + + ntfs_debug("Entering\n"); + + ib = kvzalloc(icx->block_size, GFP_NOFS); + if (!ib) + return -ENOMEM; + + old_vcn = ntfs_icx_parent_vcn(icx); + + err = ntfs_ib_read(icx, old_vcn, ib); + if (err) + goto err_out; + + idx_size = le32_to_cpu(ib->index.index_length); + allocated_size = le32_to_cpu(ib->index.allocated_size); + if (idx_size + le16_to_cpu(ie->length) + sizeof(s64) > allocated_size) { + si->ib = ib; + si->new_vcn = new_vcn; + return -EAGAIN; + } + + err = ntfs_ih_insert(&ib->index, ie, new_vcn, ntfs_icx_parent_pos(icx)); + if (err) + goto err_out; + + err = ntfs_ib_write(icx, ib); + +err_out: + kvfree(ib); + return err; +} + +/* + * ntfs_ib_split - Split an index block + * @icx: index context + * @ib: index block to split + */ +static int ntfs_ib_split(struct ntfs_index_context *icx, struct index_block *ib) +{ + struct index_entry *median; + s64 new_vcn; + int ret; + struct split_info *si; + LIST_HEAD(ntfs_cut_tail_list); + + ntfs_debug("Entering\n"); + +resplit: + ret = ntfs_icx_parent_dec(icx); + if (ret) + goto out; + + median = ntfs_ie_get_median(&ib->index); + new_vcn = ntfs_ibm_get_free(icx); + if (new_vcn < 0) { + ret = -EINVAL; + goto out; + } + + ret = ntfs_ib_copy_tail(icx, ib, median, new_vcn); + if (ret) { + ntfs_ibm_clear(icx, new_vcn); + goto out; + } + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) { + ret = ntfs_ir_insert_median(icx, median, new_vcn); + if (ret) { + ntfs_ibm_clear(icx, new_vcn); + goto out; + } + } else { + si = kzalloc(sizeof(struct split_info), GFP_NOFS); + if (!si) { + ntfs_ibm_clear(icx, new_vcn); + ret = -ENOMEM; + goto out; + } + + ret = ntfs_ib_insert(icx, median, new_vcn, si); + if (ret == -EAGAIN) { + list_add_tail(&si->entry, &ntfs_cut_tail_list); + ib = si->ib; + goto resplit; + } else if (ret) { + kvfree(si->ib); + kfree(si); + ntfs_ibm_clear(icx, new_vcn); + goto out; + } + kfree(si); + } + + ret = ntfs_ib_cut_tail(icx, ib, median); + +out: + while (!list_empty(&ntfs_cut_tail_list)) { + si = list_last_entry(&ntfs_cut_tail_list, struct split_info, entry); + ntfs_ibm_clear(icx, si->new_vcn); + kvfree(si->ib); + list_del(&si->entry); + kfree(si); + if (!ret) + ret = -EAGAIN; + } + + return ret; +} + +int ntfs_ie_add(struct ntfs_index_context *icx, struct index_entry *ie) +{ + struct index_header *ih; + int allocated_size, new_size; + int ret; + + while (1) { + ret = ntfs_index_lookup(&ie->key, le16_to_cpu(ie->key_length), icx); + if (!ret) { + ret = -EEXIST; + ntfs_error(icx->idx_ni->vol->sb, "Index already have such entry"); + goto err_out; + } + if (ret != -ENOENT) { + ntfs_error(icx->idx_ni->vol->sb, "Failed to find place for new entry"); + goto err_out; + } + ret = 0; + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + allocated_size = le32_to_cpu(ih->allocated_size); + new_size = le32_to_cpu(ih->index_length) + le16_to_cpu(ie->length); + + if (new_size <= allocated_size) + break; + + ntfs_debug("index block sizes: allocated: %d needed: %d\n", + allocated_size, new_size); + + if (icx->is_in_root) + ret = ntfs_ir_make_space(icx, new_size); + else + ret = ntfs_ib_split(icx, icx->ib); + if (ret && ret != -EAGAIN) + goto err_out; + + mark_mft_record_dirty(icx->actx->ntfs_ino); + ntfs_index_ctx_reinit(icx); + } + + ntfs_ie_insert(ih, ie, icx->entry); + ntfs_index_entry_mark_dirty(icx); + +err_out: + ntfs_debug("%s\n", ret ? "Failed" : "Done"); + return ret; +} + +/* + * ntfs_index_add_filename - add filename to directory index + * @ni: ntfs inode describing directory to which index add filename + * @fn: FILE_NAME attribute to add + * @mref: reference of the inode which @fn describes + */ +int ntfs_index_add_filename(struct ntfs_inode *ni, struct file_name_attr *fn, u64 mref) +{ + struct index_entry *ie; + struct ntfs_index_context *icx; + int fn_size, ie_size, err; + + ntfs_debug("Entering\n"); + + if (!ni || !fn) + return -EINVAL; + + fn_size = (fn->file_name_length * sizeof(__le16)) + + sizeof(struct file_name_attr); + ie_size = (sizeof(struct index_entry_header) + fn_size + 7) & ~7; + + ie = kzalloc(ie_size, GFP_NOFS); + if (!ie) + return -ENOMEM; + + ie->data.dir.indexed_file = cpu_to_le64(mref); + ie->length = cpu_to_le16(ie_size); + ie->key_length = cpu_to_le16(fn_size); + + unsafe_memcpy(&ie->key, fn, fn_size, + /* "fn_size" was correctly calculated above */); + + icx = ntfs_index_ctx_get(ni, I30, 4); + if (!icx) { + err = -ENOMEM; + goto out; + } + + err = ntfs_ie_add(icx, ie); + ntfs_index_ctx_put(icx); +out: + kfree(ie); + return err; +} + +static int ntfs_ih_takeout(struct ntfs_index_context *icx, struct index_header *ih, + struct index_entry *ie, struct index_block *ib) +{ + struct index_entry *ie_roam; + int freed_space; + bool full; + int ret = 0; + + ntfs_debug("Entering\n"); + + full = ih->index_length == ih->allocated_size; + ie_roam = ntfs_ie_dup_novcn(ie); + if (!ie_roam) + return -ENOMEM; + + ntfs_ie_delete(ih, ie); + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) { + /* + * Recover the space which may have been freed + * while deleting an entry from root index + */ + freed_space = le32_to_cpu(ih->allocated_size) - + le32_to_cpu(ih->index_length); + if (full && (freed_space > 0) && !(freed_space & 7)) { + ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); + /* do nothing if truncation fails */ + } + + mark_mft_record_dirty(icx->actx->ntfs_ino); + } else { + ret = ntfs_ib_write(icx, ib); + if (ret) + goto out; + } + + ntfs_index_ctx_reinit(icx); + + ret = ntfs_ie_add(icx, ie_roam); +out: + kfree(ie_roam); + return ret; +} + +/* + * Used if an empty index block to be deleted has END entry as the parent + * in the INDEX_ROOT which is the only one there. + */ +static void ntfs_ir_leafify(struct ntfs_index_context *icx, struct index_header *ih) +{ + struct index_entry *ie; + + ntfs_debug("Entering\n"); + + ie = ntfs_ie_get_first(ih); + ie->flags &= ~INDEX_ENTRY_NODE; + ie->length = cpu_to_le16(le16_to_cpu(ie->length) - sizeof(s64)); + + ih->index_length = cpu_to_le32(le32_to_cpu(ih->index_length) - sizeof(s64)); + ih->flags &= ~LARGE_INDEX; + NInoClearIndexAllocPresent(icx->idx_ni); + + /* Not fatal error */ + ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); +} + +/* + * Used if an empty index block to be deleted has END entry as the parent + * in the INDEX_ROOT which is not the only one there. + */ +static int ntfs_ih_reparent_end(struct ntfs_index_context *icx, struct index_header *ih, + struct index_block *ib) +{ + struct index_entry *ie, *ie_prev; + + ntfs_debug("Entering\n"); + + ie = ntfs_ie_get_by_pos(ih, ntfs_icx_parent_pos(icx)); + ie_prev = ntfs_ie_prev(ih, ie); + if (!ie_prev) + return -EIO; + ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(ie_prev)); + + return ntfs_ih_takeout(icx, ih, ie_prev, ib); +} + +static int ntfs_index_rm_leaf(struct ntfs_index_context *icx) +{ + struct index_block *ib = NULL; + struct index_header *parent_ih; + struct index_entry *ie; + int ret; + + ntfs_debug("pindex: %d\n", icx->pindex); + + ret = ntfs_icx_parent_dec(icx); + if (ret) + return ret; + + ret = ntfs_ibm_clear(icx, icx->parent_vcn[icx->pindex + 1]); + if (ret) + return ret; + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + parent_ih = &icx->ir->index; + else { + ib = kvzalloc(icx->block_size, GFP_NOFS); + if (!ib) + return -ENOMEM; + + ret = ntfs_ib_read(icx, ntfs_icx_parent_vcn(icx), ib); + if (ret) + goto out; + + parent_ih = &ib->index; + } + + ie = ntfs_ie_get_by_pos(parent_ih, ntfs_icx_parent_pos(icx)); + if (!ntfs_ie_end(ie)) { + ret = ntfs_ih_takeout(icx, parent_ih, ie, ib); + goto out; + } + + if (ntfs_ih_zero_entry(parent_ih)) { + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) { + ntfs_ir_leafify(icx, parent_ih); + goto out; + } + + ret = ntfs_index_rm_leaf(icx); + goto out; + } + + ret = ntfs_ih_reparent_end(icx, parent_ih, ib); +out: + kvfree(ib); + return ret; +} + +static int ntfs_index_rm_node(struct ntfs_index_context *icx) +{ + int entry_pos, pindex; + s64 vcn; + struct index_block *ib = NULL; + struct index_entry *ie_succ, *ie, *entry = icx->entry; + struct index_header *ih; + u32 new_size; + int delta, ret; + + ntfs_debug("Entering\n"); + + if (!icx->ia_ni) { + icx->ia_ni = ntfs_ia_open(icx, icx->idx_ni); + if (!icx->ia_ni) + return -EINVAL; + } + + ib = kvzalloc(icx->block_size, GFP_NOFS); + if (!ib) + return -ENOMEM; + + ie_succ = ntfs_ie_get_next(icx->entry); + entry_pos = icx->parent_pos[icx->pindex]++; + pindex = icx->pindex; +descend: + vcn = ntfs_ie_get_vcn(ie_succ); + ret = ntfs_ib_read(icx, vcn, ib); + if (ret) + goto out; + + ie_succ = ntfs_ie_get_first(&ib->index); + + ret = ntfs_icx_parent_inc(icx); + if (ret) + goto out; + + icx->parent_vcn[icx->pindex] = vcn; + icx->parent_pos[icx->pindex] = 0; + + if ((ib->index.flags & NODE_MASK) == INDEX_NODE) + goto descend; + + if (ntfs_ih_zero_entry(&ib->index)) { + ret = -EIO; + ntfs_error(icx->idx_ni->vol->sb, "Empty index block"); + goto out; + } + + ie = ntfs_ie_dup(ie_succ); + if (!ie) { + ret = -ENOMEM; + goto out; + } + + ret = ntfs_ie_add_vcn(&ie); + if (ret) + goto out2; + + ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(icx->entry)); + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + delta = le16_to_cpu(ie->length) - le16_to_cpu(icx->entry->length); + new_size = le32_to_cpu(ih->index_length) + delta; + if (delta > 0) { + if (icx->is_in_root) { + ret = ntfs_ir_make_space(icx, new_size); + if (ret != 0) + goto out2; + + ih = &icx->ir->index; + entry = ntfs_ie_get_by_pos(ih, entry_pos); + + } else if (new_size > le32_to_cpu(ih->allocated_size)) { + icx->pindex = pindex; + ret = ntfs_ib_split(icx, icx->ib); + if (!ret) + ret = -EAGAIN; + goto out2; + } + } + + ntfs_ie_delete(ih, entry); + ntfs_ie_insert(ih, ie, entry); + + if (icx->is_in_root) + ret = ntfs_ir_truncate(icx, new_size); + else + ret = ntfs_icx_ib_write(icx); + if (ret) + goto out2; + + ntfs_ie_delete(&ib->index, ie_succ); + + if (ntfs_ih_zero_entry(&ib->index)) + ret = ntfs_index_rm_leaf(icx); + else + ret = ntfs_ib_write(icx, ib); + +out2: + kfree(ie); +out: + kvfree(ib); + return ret; +} + +/* + * ntfs_index_rm - remove entry from the index + * @icx: index context describing entry to delete + * + * Delete entry described by @icx from the index. Index context is always + * reinitialized after use of this function, so it can be used for index + * lookup once again. + */ +int ntfs_index_rm(struct ntfs_index_context *icx) +{ + struct index_header *ih; + int ret = 0; + + ntfs_debug("Entering\n"); + + if (!icx || (!icx->ib && !icx->ir) || ntfs_ie_end(icx->entry)) { + ret = -EINVAL; + goto err_out; + } + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + if (icx->entry->flags & INDEX_ENTRY_NODE) { + ret = ntfs_index_rm_node(icx); + if (ret) + goto err_out; + } else if (icx->is_in_root || !ntfs_ih_one_entry(ih)) { + ntfs_ie_delete(ih, icx->entry); + + if (icx->is_in_root) + ret = ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); + else + ret = ntfs_icx_ib_write(icx); + if (ret) + goto err_out; + } else { + ret = ntfs_index_rm_leaf(icx); + if (ret) + goto err_out; + } + + return 0; +err_out: + return ret; +} + +int ntfs_index_remove(struct ntfs_inode *dir_ni, const void *key, const u32 keylen) +{ + int ret = 0; + struct ntfs_index_context *icx; + + icx = ntfs_index_ctx_get(dir_ni, I30, 4); + if (!icx) + return -EINVAL; + + while (1) { + ret = ntfs_index_lookup(key, keylen, icx); + if (ret) + goto err_out; + + ret = ntfs_index_rm(icx); + if (ret && ret != -EAGAIN) + goto err_out; + else if (!ret) + break; + + mark_mft_record_dirty(icx->actx->ntfs_ino); + ntfs_index_ctx_reinit(icx); + } + + mark_mft_record_dirty(icx->actx->ntfs_ino); + + ntfs_index_ctx_put(icx); + return 0; +err_out: + ntfs_index_ctx_put(icx); + ntfs_error(dir_ni->vol->sb, "Delete failed"); + return ret; +} + +/* + * ntfs_index_walk_down - walk down the index tree (leaf bound) + * until there are no subnode in the first index entry returns + * the entry at the bottom left in subnode + */ +struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_index_context *ictx) +{ + struct index_entry *entry; + s64 vcn; + + entry = ie; + do { + vcn = ntfs_ie_get_vcn(entry); + if (ictx->is_in_root) { + /* down from level zero */ + ictx->ir = NULL; + ictx->ib = kvzalloc(ictx->block_size, GFP_NOFS); + ictx->pindex = 1; + ictx->is_in_root = false; + } else { + /* down from non-zero level */ + ictx->pindex++; + } + + ictx->parent_pos[ictx->pindex] = 0; + ictx->parent_vcn[ictx->pindex] = vcn; + if (!ntfs_ib_read(ictx, vcn, ictx->ib)) { + ictx->entry = ntfs_ie_get_first(&ictx->ib->index); + entry = ictx->entry; + } else + entry = NULL; + } while (entry && (entry->flags & INDEX_ENTRY_NODE)); + + return entry; +} + +/* + * ntfs_index_walk_up - walk up the index tree (root bound) until + * there is a valid data entry in parent returns the parent entry + * or NULL if no more parent. + * @ie: current index entry + * @ictx: index context + */ +static struct index_entry *ntfs_index_walk_up(struct index_entry *ie, + struct ntfs_index_context *ictx) +{ + struct index_entry *entry = ie; + s64 vcn; + + if (ictx->pindex <= 0) + return NULL; + + do { + ictx->pindex--; + if (!ictx->pindex) { + /* we have reached the root */ + kfree(ictx->ib); + ictx->ib = NULL; + ictx->is_in_root = true; + /* a new search context is to be allocated */ + if (ictx->actx) + ntfs_attr_put_search_ctx(ictx->actx); + ictx->ir = ntfs_ir_lookup(ictx->idx_ni, ictx->name, + ictx->name_len, &ictx->actx); + if (ictx->ir) + entry = ntfs_ie_get_by_pos( + &ictx->ir->index, + ictx->parent_pos[ictx->pindex]); + else + entry = NULL; + } else { + /* up into non-root node */ + vcn = ictx->parent_vcn[ictx->pindex]; + if (!ntfs_ib_read(ictx, vcn, ictx->ib)) { + entry = ntfs_ie_get_by_pos( + &ictx->ib->index, + ictx->parent_pos[ictx->pindex]); + } else + entry = NULL; + } + ictx->entry = entry; + } while (entry && (ictx->pindex > 0) && + (entry->flags & INDEX_ENTRY_END)); + return entry; +} + +/* + * ntfs_index_next - get next entry in an index according to collating sequence. + * Returns next entry or NULL if none. + * + * Sample layout : + * + * +---+---+---+---+---+---+---+---+ n ptrs to subnodes + * | | | 10| 25| 33| | | | n-1 keys in between + * +---+---+---+---+---+---+---+---+ no key in last entry + * | A | A + * | | | +-------------------------------+ + * +--------------------------+ | +-----+ | + * | +--+ | | + * V | V | + * +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+ + * | 11| 12| 13| 14| 15| 16| 17| | | | 26| 27| 28| 29| 30| 31| 32| | + * +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+ + * | | + * +-----------------------+ | + * | | + * +---+---+---+---+---+---+---+---+ + * | 18| 19| 20| 21| 22| 23| 24| | + * +---+---+---+---+---+---+---+---+ + * + * @ie: current index entry + * @ictx: index context + */ +struct index_entry *ntfs_index_next(struct index_entry *ie, struct ntfs_index_context *ictx) +{ + struct index_entry *next; + __le16 flags; + + /* + * lookup() may have returned an invalid node + * when searching for a partial key + * if this happens, walk up + */ + if (ie->flags & INDEX_ENTRY_END) + next = ntfs_index_walk_up(ie, ictx); + else { + /* + * get next entry in same node + * there is always one after any entry with data + */ + next = (struct index_entry *)((char *)ie + le16_to_cpu(ie->length)); + ++ictx->parent_pos[ictx->pindex]; + flags = next->flags; + + /* walk down if it has a subnode */ + if (flags & INDEX_ENTRY_NODE) { + if (!ictx->ia_ni) + ictx->ia_ni = ntfs_ia_open(ictx, ictx->idx_ni); + + next = ntfs_index_walk_down(next, ictx); + } else { + + /* walk up it has no subnode, nor data */ + if (flags & INDEX_ENTRY_END) + next = ntfs_index_walk_up(next, ictx); + } + } + + /* return NULL if stuck at end of a block */ + if (next && (next->flags & INDEX_ENTRY_END)) + next = NULL; + + return next; +} diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h new file mode 100644 index 000000000000..e68d6fabaf9f --- /dev/null +++ b/fs/ntfs/index.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for NTFS kernel index handling. + * + * Copyright (c) 2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_INDEX_H +#define _LINUX_NTFS_INDEX_H + +#include + +#include "attrib.h" +#include "mft.h" + +#define VCN_INDEX_ROOT_PARENT ((s64)-2) + +#define MAX_PARENT_VCN 32 + +/* + * @idx_ni: index inode containing the @entry described by this context + * @name: Unicode name of the indexed attribute + * (usually $I30 for directories) + * @name_len: length of @name in Unicode characters + * @entry: index entry (points into @ir or @ia) + * @cr: creation time of the entry (for sorting/validation) + * @data: index entry data (points into @entry) + * @data_len: length in bytes of @data + * @is_in_root: 'true' if @entry is in @ir and 'false' if it is in @ia + * @ir: index root if @is_in_root and NULL otherwise + * @actx: attribute search context if @is_in_root and NULL otherwise + * @ib: index block header (valid when @is_in_root is 'false') + * @ia_ni: index allocation inode (extent inode) for @ia + * @parent_pos: array of parent entry positions in the B-tree nodes + * @parent_vcn: VCNs of parent index blocks in the B-tree traversal + * @pindex: current depth (number of parent nodes) in the traversal + * (maximum is MAX_PARENT_VCN) + * @ib_dirty: true if the current index block (@ia/@ib) was modified + * @block_size: size of index blocks in bytes (from $INDEX_ROOT or $Boot) + * @vcn_size_bits: log2(cluster size) + * @sync_write: true if synchronous writeback is requested for this context + * + * @idx_ni is the index inode this context belongs to. + * + * @entry is the index entry described by this context. @data and @data_len + * are the index entry data and its length in bytes, respectively. @data + * simply points into @entry. This is probably what the user is interested in. + * + * If @is_in_root is 'true', @entry is in the index root attribute @ir described + * by the attribute search context @actx and the base inode @base_ni. @ia and + * @page are NULL in this case. + * + * If @is_in_root is 'false', @entry is in the index allocation attribute and @ia + * and @page point to the index allocation block and the mapped, locked page it + * is in, respectively. @ir, @actx and @base_ni are NULL in this case. + * + * To obtain a context call ntfs_index_ctx_get(). + * + * We use this context to allow ntfs_index_lookup() to return the found index + * @entry and its @data without having to allocate a buffer and copy the @entry + * and/or its @data into it. + * + * When finished with the @entry and its @data, call ntfs_index_ctx_put() to + * free the context and other associated resources. + * + * If the index entry was modified, ntfs_index_entry_mark_dirty() + * or ntfs_index_entry_write() before the call to ntfs_index_ctx_put() to + * ensure that the changes are written to disk. + */ +struct ntfs_index_context { + struct ntfs_inode *idx_ni; + __le16 *name; + u32 name_len; + struct index_entry *entry; + __le32 cr; + void *data; + u16 data_len; + bool is_in_root; + struct index_root *ir; + struct ntfs_attr_search_ctx *actx; + struct index_block *ib; + struct ntfs_inode *ia_ni; + int parent_pos[MAX_PARENT_VCN]; + s64 parent_vcn[MAX_PARENT_VCN]; + int pindex; + bool ib_dirty; + u32 block_size; + u8 vcn_size_bits; + bool sync_write; +}; + +int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol, + const struct index_entry *ie, __le32 collation_rule, u64 inum); +struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name, + u32 name_len); +void ntfs_index_ctx_put(struct ntfs_index_context *ictx); +int ntfs_index_lookup(const void *key, const u32 key_len, + struct ntfs_index_context *ictx); + +void ntfs_index_entry_mark_dirty(struct ntfs_index_context *ictx); +int ntfs_index_add_filename(struct ntfs_inode *ni, struct file_name_attr *fn, u64 mref); +int ntfs_index_remove(struct ntfs_inode *ni, const void *key, const u32 keylen); +struct ntfs_inode *ntfs_ia_open(struct ntfs_index_context *icx, struct ntfs_inode *ni); +struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_index_context *ictx); +struct index_entry *ntfs_index_next(struct index_entry *ie, struct ntfs_index_context *ictx); +int ntfs_index_rm(struct ntfs_index_context *icx); +void ntfs_index_ctx_reinit(struct ntfs_index_context *icx); +int ntfs_ie_add(struct ntfs_index_context *icx, struct index_entry *ie); +int ntfs_icx_ib_sync_write(struct ntfs_index_context *icx); + +#endif /* _LINUX_NTFS_INDEX_H */ diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c new file mode 100644 index 000000000000..16890d411194 --- /dev/null +++ b/fs/ntfs/inode.c @@ -0,0 +1,3787 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel inode handling. + * + * Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include +#include + +#include "lcnalloc.h" +#include "time.h" +#include "ntfs.h" +#include "index.h" +#include "attrlist.h" +#include "reparse.h" +#include "ea.h" +#include "attrib.h" +#include "iomap.h" +#include "object_id.h" + +/* + * ntfs_test_inode - compare two (possibly fake) inodes for equality + * @vi: vfs inode which to test + * @data: data which is being tested with + * + * Compare the ntfs attribute embedded in the ntfs specific part of the vfs + * inode @vi for equality with the ntfs attribute @data. + * + * If searching for the normal file/directory inode, set @na->type to AT_UNUSED. + * @na->name and @na->name_len are then ignored. + * + * Return 1 if the attributes match and 0 if not. + * + * NOTE: This function runs with the inode_hash_lock spin lock held so it is not + * allowed to sleep. + */ +int ntfs_test_inode(struct inode *vi, void *data) +{ + struct ntfs_attr *na = data; + struct ntfs_inode *ni = NTFS_I(vi); + + if (vi->i_ino != na->mft_no) + return 0; + + /* If !NInoAttr(ni), @vi is a normal file or directory inode. */ + if (likely(!NInoAttr(ni))) { + /* If not looking for a normal inode this is a mismatch. */ + if (unlikely(na->type != AT_UNUSED)) + return 0; + } else { + /* A fake inode describing an attribute. */ + if (ni->type != na->type) + return 0; + if (ni->name_len != na->name_len) + return 0; + if (na->name_len && memcmp(ni->name, na->name, + na->name_len * sizeof(__le16))) + return 0; + if (!ni->ext.base_ntfs_ino) + return 0; + } + + /* Match! */ + return 1; +} + +/* + * ntfs_init_locked_inode - initialize an inode + * @vi: vfs inode to initialize + * @data: data which to initialize @vi to + * + * Initialize the vfs inode @vi with the values from the ntfs attribute @data in + * order to enable ntfs_test_inode() to do its work. + * + * If initializing the normal file/directory inode, set @na->type to AT_UNUSED. + * In that case, @na->name and @na->name_len should be set to NULL and 0, + * respectively. Although that is not strictly necessary as + * ntfs_read_locked_inode() will fill them in later. + * + * Return 0 on success and error. + * + * NOTE: This function runs with the inode->i_lock spin lock held so it is not + * allowed to sleep. (Hence the GFP_ATOMIC allocation.) + */ +static int ntfs_init_locked_inode(struct inode *vi, void *data) +{ + struct ntfs_attr *na = data; + struct ntfs_inode *ni = NTFS_I(vi); + + vi->i_ino = (unsigned long)na->mft_no; + + if (na->type == AT_INDEX_ALLOCATION) + NInoSetMstProtected(ni); + else + ni->type = na->type; + + ni->name = na->name; + ni->name_len = na->name_len; + ni->folio = NULL; + atomic_set(&ni->count, 1); + + /* If initializing a normal inode, we are done. */ + if (likely(na->type == AT_UNUSED)) + return 0; + + /* It is a fake inode. */ + NInoSetAttr(ni); + + /* + * We have I30 global constant as an optimization as it is the name + * in >99.9% of named attributes! The other <0.1% incur a GFP_ATOMIC + * allocation but that is ok. And most attributes are unnamed anyway, + * thus the fraction of named attributes with name != I30 is actually + * absolutely tiny. + */ + if (na->name_len && na->name != I30) { + unsigned int i; + + i = na->name_len * sizeof(__le16); + ni->name = kmalloc(i + sizeof(__le16), GFP_ATOMIC); + if (!ni->name) + return -ENOMEM; + memcpy(ni->name, na->name, i); + ni->name[na->name_len] = 0; + } + return 0; +} + +static int ntfs_read_locked_inode(struct inode *vi); +static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi); +static int ntfs_read_locked_index_inode(struct inode *base_vi, + struct inode *vi); + +/* + * ntfs_iget - obtain a struct inode corresponding to a specific normal inode + * @sb: super block of mounted volume + * @mft_no: mft record number / inode number to obtain + * + * Obtain the struct inode corresponding to a specific normal inode (i.e. a + * file or directory). + * + * If the inode is in the cache, it is just returned with an increased + * reference count. Otherwise, a new struct inode is allocated and initialized, + * and finally ntfs_read_locked_inode() is called to read in the inode and + * fill in the remainder of the inode structure. + * + * Return the struct inode on success. Check the return value with IS_ERR() and + * if true, the function failed and the error code is obtained from PTR_ERR(). + */ +struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) +{ + struct inode *vi; + int err; + struct ntfs_attr na; + + na.mft_no = mft_no; + na.type = AT_UNUSED; + na.name = NULL; + na.name_len = 0; + + vi = iget5_locked(sb, mft_no, ntfs_test_inode, + ntfs_init_locked_inode, &na); + if (unlikely(!vi)) + return ERR_PTR(-ENOMEM); + + err = 0; + + /* If this is a freshly allocated inode, need to read it now. */ + if (inode_state_read_once(vi) & I_NEW) { + err = ntfs_read_locked_inode(vi); + unlock_new_inode(vi); + } + /* + * There is no point in keeping bad inodes around if the failure was + * due to ENOMEM. We want to be able to retry again later. + */ + if (unlikely(err == -ENOMEM)) { + iput(vi); + vi = ERR_PTR(err); + } + return vi; +} + +/* + * ntfs_attr_iget - obtain a struct inode corresponding to an attribute + * @base_vi: vfs base inode containing the attribute + * @type: attribute type + * @name: Unicode name of the attribute (NULL if unnamed) + * @name_len: length of @name in Unicode characters (0 if unnamed) + * + * Obtain the (fake) struct inode corresponding to the attribute specified by + * @type, @name, and @name_len, which is present in the base mft record + * specified by the vfs inode @base_vi. + * + * If the attribute inode is in the cache, it is just returned with an + * increased reference count. Otherwise, a new struct inode is allocated and + * initialized, and finally ntfs_read_locked_attr_inode() is called to read the + * attribute and fill in the inode structure. + * + * Note, for index allocation attributes, you need to use ntfs_index_iget() + * instead of ntfs_attr_iget() as working with indices is a lot more complex. + * + * Return the struct inode of the attribute inode on success. Check the return + * value with IS_ERR() and if true, the function failed and the error code is + * obtained from PTR_ERR(). + */ +struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, + __le16 *name, u32 name_len) +{ + struct inode *vi; + int err; + struct ntfs_attr na; + + /* Make sure no one calls ntfs_attr_iget() for indices. */ + WARN_ON(type == AT_INDEX_ALLOCATION); + + na.mft_no = base_vi->i_ino; + na.type = type; + na.name = name; + na.name_len = name_len; + + vi = iget5_locked(base_vi->i_sb, na.mft_no, ntfs_test_inode, + ntfs_init_locked_inode, &na); + if (unlikely(!vi)) + return ERR_PTR(-ENOMEM); + err = 0; + + /* If this is a freshly allocated inode, need to read it now. */ + if (inode_state_read_once(vi) & I_NEW) { + err = ntfs_read_locked_attr_inode(base_vi, vi); + unlock_new_inode(vi); + } + /* + * There is no point in keeping bad attribute inodes around. This also + * simplifies things in that we never need to check for bad attribute + * inodes elsewhere. + */ + if (unlikely(err)) { + iput(vi); + vi = ERR_PTR(err); + } + return vi; +} + +/* + * ntfs_index_iget - obtain a struct inode corresponding to an index + * @base_vi: vfs base inode containing the index related attributes + * @name: Unicode name of the index + * @name_len: length of @name in Unicode characters + * + * Obtain the (fake) struct inode corresponding to the index specified by @name + * and @name_len, which is present in the base mft record specified by the vfs + * inode @base_vi. + * + * If the index inode is in the cache, it is just returned with an increased + * reference count. Otherwise, a new struct inode is allocated and + * initialized, and finally ntfs_read_locked_index_inode() is called to read + * the index related attributes and fill in the inode structure. + * + * Return the struct inode of the index inode on success. Check the return + * value with IS_ERR() and if true, the function failed and the error code is + * obtained from PTR_ERR(). + */ +struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, + u32 name_len) +{ + struct inode *vi; + int err; + struct ntfs_attr na; + + na.mft_no = base_vi->i_ino; + na.type = AT_INDEX_ALLOCATION; + na.name = name; + na.name_len = name_len; + + vi = iget5_locked(base_vi->i_sb, na.mft_no, ntfs_test_inode, + ntfs_init_locked_inode, &na); + if (unlikely(!vi)) + return ERR_PTR(-ENOMEM); + + err = 0; + + /* If this is a freshly allocated inode, need to read it now. */ + if (inode_state_read_once(vi) & I_NEW) { + err = ntfs_read_locked_index_inode(base_vi, vi); + unlock_new_inode(vi); + } + /* + * There is no point in keeping bad index inodes around. This also + * simplifies things in that we never need to check for bad index + * inodes elsewhere. + */ + if (unlikely(err)) { + iput(vi); + vi = ERR_PTR(err); + } + return vi; +} + +struct inode *ntfs_alloc_big_inode(struct super_block *sb) +{ + struct ntfs_inode *ni; + + ntfs_debug("Entering."); + ni = alloc_inode_sb(sb, ntfs_big_inode_cache, GFP_NOFS); + if (likely(ni != NULL)) { + ni->state = 0; + ni->type = 0; + ni->mft_no = 0; + return VFS_I(ni); + } + ntfs_error(sb, "Allocation of NTFS big inode structure failed."); + return NULL; +} + +void ntfs_free_big_inode(struct inode *inode) +{ + kmem_cache_free(ntfs_big_inode_cache, NTFS_I(inode)); +} + +static int ntfs_non_resident_dealloc_clusters(struct ntfs_inode *ni) +{ + struct super_block *sb = ni->vol->sb; + struct ntfs_attr_search_ctx *actx; + int err = 0; + + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) + return -ENOMEM; + WARN_ON(actx->mrec->link_count != 0); + + /** + * ntfs_truncate_vfs cannot be called in evict() context due + * to some limitations, which are the @ni vfs inode is marked + * with I_FREEING, and etc. + */ + if (NInoRunlistDirty(ni)) { + err = ntfs_cluster_free_from_rl(ni->vol, ni->runlist.rl); + if (err) + ntfs_error(sb, + "Failed to free clusters. Leaving inconsistent metadata.\n"); + } + + while ((err = ntfs_attrs_walk(actx)) == 0) { + if (actx->attr->non_resident && + (!NInoRunlistDirty(ni) || actx->attr->type != AT_DATA)) { + struct runlist_element *rl; + size_t new_rl_count; + + rl = ntfs_mapping_pairs_decompress(ni->vol, actx->attr, NULL, + &new_rl_count); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + ntfs_error(sb, + "Failed to decompress runlist. Leaving inconsistent metadata.\n"); + continue; + } + + err = ntfs_cluster_free_from_rl(ni->vol, rl); + if (err) + ntfs_error(sb, + "Failed to free attribute clusters. Leaving inconsistent metadata.\n"); + kvfree(rl); + } + } + + ntfs_release_dirty_clusters(ni->vol, ni->i_dealloc_clusters); + ntfs_attr_put_search_ctx(actx); + return err; +} + +int ntfs_drop_big_inode(struct inode *inode) +{ + struct ntfs_inode *ni = NTFS_I(inode); + + if (!inode_unhashed(inode) && inode_state_read_once(inode) & I_SYNC) { + if (ni->type == AT_DATA || ni->type == AT_INDEX_ALLOCATION) { + if (!inode->i_nlink) { + struct ntfs_inode *ni = NTFS_I(inode); + + if (ni->data_size == 0) + return 0; + + /* To avoid evict_inode call simultaneously */ + atomic_inc(&inode->i_count); + spin_unlock(&inode->i_lock); + + truncate_setsize(VFS_I(ni), 0); + ntfs_truncate_vfs(VFS_I(ni), 0, 1); + + sb_start_intwrite(inode->i_sb); + i_size_write(inode, 0); + ni->allocated_size = ni->initialized_size = ni->data_size = 0; + + truncate_inode_pages_final(inode->i_mapping); + sb_end_intwrite(inode->i_sb); + + spin_lock(&inode->i_lock); + atomic_dec(&inode->i_count); + } + } + return 0; + } + + return inode_generic_drop(inode); +} + +static inline struct ntfs_inode *ntfs_alloc_extent_inode(void) +{ + struct ntfs_inode *ni; + + ntfs_debug("Entering."); + ni = kmem_cache_alloc(ntfs_inode_cache, GFP_NOFS); + if (likely(ni != NULL)) { + ni->state = 0; + return ni; + } + ntfs_error(NULL, "Allocation of NTFS inode structure failed."); + return NULL; +} + +static void ntfs_destroy_extent_inode(struct ntfs_inode *ni) +{ + ntfs_debug("Entering."); + + if (!atomic_dec_and_test(&ni->count)) + WARN_ON(1); + if (ni->folio) + folio_put(ni->folio); + kfree(ni->mrec); + kmem_cache_free(ntfs_inode_cache, ni); +} + +static struct lock_class_key attr_inode_mrec_lock_class; +static struct lock_class_key attr_list_inode_mrec_lock_class; + +/* + * The attribute runlist lock has separate locking rules from the + * normal runlist lock, so split the two lock-classes: + */ +static struct lock_class_key attr_list_rl_lock_class; + +/* + * __ntfs_init_inode - initialize ntfs specific part of an inode + * @sb: super block of mounted volume + * @ni: freshly allocated ntfs inode which to initialize + * + * Initialize an ntfs inode to defaults. + * + * NOTE: ni->mft_no, ni->state, ni->type, ni->name, and ni->name_len are left + * untouched. Make sure to initialize them elsewhere. + */ +void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni) +{ + ntfs_debug("Entering."); + rwlock_init(&ni->size_lock); + ni->initialized_size = ni->allocated_size = 0; + ni->seq_no = 0; + atomic_set(&ni->count, 1); + ni->vol = NTFS_SB(sb); + ntfs_init_runlist(&ni->runlist); + mutex_init(&ni->mrec_lock); + if (ni->type == AT_ATTRIBUTE_LIST) { + lockdep_set_class(&ni->mrec_lock, + &attr_list_inode_mrec_lock_class); + lockdep_set_class(&ni->runlist.lock, + &attr_list_rl_lock_class); + } else if (NInoAttr(ni)) { + lockdep_set_class(&ni->mrec_lock, + &attr_inode_mrec_lock_class); + } + + ni->folio = NULL; + ni->folio_ofs = 0; + ni->mrec = NULL; + ni->attr_list_size = 0; + ni->attr_list = NULL; + ni->itype.index.block_size = 0; + ni->itype.index.vcn_size = 0; + ni->itype.index.collation_rule = 0; + ni->itype.index.block_size_bits = 0; + ni->itype.index.vcn_size_bits = 0; + mutex_init(&ni->extent_lock); + ni->nr_extents = 0; + ni->ext.base_ntfs_ino = NULL; + ni->flags = 0; + ni->mft_lcn[0] = LCN_RL_NOT_MAPPED; + ni->mft_lcn_count = 0; + ni->target = NULL; + ni->i_dealloc_clusters = 0; +} + +/* + * Extent inodes get MFT-mapped in a nested way, while the base inode + * is still mapped. Teach this nesting to the lock validator by creating + * a separate class for nested inode's mrec_lock's: + */ +static struct lock_class_key extent_inode_mrec_lock_key; + +inline struct ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, + u64 mft_no) +{ + struct ntfs_inode *ni = ntfs_alloc_extent_inode(); + + ntfs_debug("Entering."); + if (likely(ni != NULL)) { + __ntfs_init_inode(sb, ni); + lockdep_set_class(&ni->mrec_lock, &extent_inode_mrec_lock_key); + ni->mft_no = mft_no; + ni->type = AT_UNUSED; + ni->name = NULL; + ni->name_len = 0; + } + return ni; +} + +/* + * ntfs_is_extended_system_file - check if a file is in the $Extend directory + * @ctx: initialized attribute search context + * + * Search all file name attributes in the inode described by the attribute + * search context @ctx and check if any of the names are in the $Extend system + * directory. + * + * Return values: + * 3: file is $ObjId in $Extend directory + * 2: file is $Reparse in $Extend directory + * 1: file is in $Extend directory + * 0: file is not in $Extend directory + * -errno: failed to determine if the file is in the $Extend directory + */ +static int ntfs_is_extended_system_file(struct ntfs_attr_search_ctx *ctx) +{ + int nr_links, err; + + /* Restart search. */ + ntfs_attr_reinit_search_ctx(ctx); + + /* Get number of hard links. */ + nr_links = le16_to_cpu(ctx->mrec->link_count); + + /* Loop through all hard links. */ + while (!(err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, + ctx))) { + struct file_name_attr *file_name_attr; + struct attr_record *attr = ctx->attr; + u8 *p, *p2; + + nr_links--; + /* + * Maximum sanity checking as we are called on an inode that + * we suspect might be corrupt. + */ + p = (u8 *)attr + le32_to_cpu(attr->length); + if (p < (u8 *)ctx->mrec || (u8 *)p > (u8 *)ctx->mrec + + le32_to_cpu(ctx->mrec->bytes_in_use)) { +err_corrupt_attr: + ntfs_error(ctx->ntfs_ino->vol->sb, + "Corrupt file name attribute. You should run chkdsk."); + return -EIO; + } + if (attr->non_resident) { + ntfs_error(ctx->ntfs_ino->vol->sb, + "Non-resident file name. You should run chkdsk."); + return -EIO; + } + if (attr->flags) { + ntfs_error(ctx->ntfs_ino->vol->sb, + "File name with invalid flags. You should run chkdsk."); + return -EIO; + } + if (!(attr->data.resident.flags & RESIDENT_ATTR_IS_INDEXED)) { + ntfs_error(ctx->ntfs_ino->vol->sb, + "Unindexed file name. You should run chkdsk."); + return -EIO; + } + file_name_attr = (struct file_name_attr *)((u8 *)attr + + le16_to_cpu(attr->data.resident.value_offset)); + p2 = (u8 *)file_name_attr + le32_to_cpu(attr->data.resident.value_length); + if (p2 < (u8 *)attr || p2 > p) + goto err_corrupt_attr; + /* This attribute is ok, but is it in the $Extend directory? */ + if (MREF_LE(file_name_attr->parent_directory) == FILE_Extend) { + unsigned char *s; + + s = ntfs_attr_name_get(ctx->ntfs_ino->vol, + file_name_attr->file_name, + file_name_attr->file_name_length); + if (!s) + return 1; + if (!strcmp("$Reparse", s)) { + ntfs_attr_name_free(&s); + return 2; /* it's reparse point file */ + } + if (!strcmp("$ObjId", s)) { + ntfs_attr_name_free(&s); + return 3; /* it's object id file */ + } + ntfs_attr_name_free(&s); + return 1; /* YES, it's an extended system file. */ + } + } + if (unlikely(err != -ENOENT)) + return err; + if (unlikely(nr_links)) { + ntfs_error(ctx->ntfs_ino->vol->sb, + "Inode hard link count doesn't match number of name attributes. You should run chkdsk."); + return -EIO; + } + return 0; /* NO, it is not an extended system file. */ +} + +static struct lock_class_key ntfs_dir_inval_lock_key; + +void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev) +{ + if (S_ISDIR(mode)) { + if (!NInoAttr(NTFS_I(inode))) { + inode->i_op = &ntfs_dir_inode_ops; + inode->i_fop = &ntfs_dir_ops; + } + inode->i_mapping->a_ops = &ntfs_aops; + lockdep_set_class(&inode->i_mapping->invalidate_lock, + &ntfs_dir_inval_lock_key); + } else if (S_ISLNK(mode)) { + inode->i_op = &ntfs_symlink_inode_operations; + inode->i_mapping->a_ops = &ntfs_aops; + } else if (S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) { + inode->i_op = &ntfs_special_inode_operations; + init_special_inode(inode, inode->i_mode, dev); + } else { + if (!NInoAttr(NTFS_I(inode))) { + inode->i_op = &ntfs_file_inode_ops; + inode->i_fop = &ntfs_file_ops; + } + if (inode->i_ino == FILE_MFT) + inode->i_mapping->a_ops = &ntfs_mft_aops; + else + inode->i_mapping->a_ops = &ntfs_aops; + } +} + +/* + * ntfs_read_locked_inode - read an inode from its device + * @vi: inode to read + * + * ntfs_read_locked_inode() is called from ntfs_iget() to read the inode + * described by @vi into memory from the device. + * + * The only fields in @vi that we need to/can look at when the function is + * called are i_sb, pointing to the mounted device's super block, and i_ino, + * the number of the inode to load. + * + * ntfs_read_locked_inode() maps, pins and locks the mft record number i_ino + * for reading and sets up the necessary @vi fields as well as initializing + * the ntfs inode. + * + * Q: What locks are held when the function is called? + * A: i_state has I_NEW set, hence the inode is locked, also + * i_count is set to 1, so it is not going to go away + * i_flags is set to 0 and we have no business touching it. Only an ioctl() + * is allowed to write to them. We should of course be honouring them but + * we need to do that using the IS_* macros defined in include/linux/fs.h. + * In any case ntfs_read_locked_inode() has nothing to do with i_flags. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_read_locked_inode(struct inode *vi) +{ + struct ntfs_volume *vol = NTFS_SB(vi->i_sb); + struct ntfs_inode *ni = NTFS_I(vi); + struct mft_record *m; + struct attr_record *a; + struct standard_information *si; + struct ntfs_attr_search_ctx *ctx; + int err = 0; + __le16 *name = I30; + unsigned int name_len = 4, flags = 0; + int extend_sys = 0; + dev_t dev = 0; + bool vol_err = true; + + ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); + + if (uid_valid(vol->uid)) { + vi->i_uid = vol->uid; + flags |= NTFS_VOL_UID; + } else + vi->i_uid = GLOBAL_ROOT_UID; + + if (gid_valid(vol->gid)) { + vi->i_gid = vol->gid; + flags |= NTFS_VOL_GID; + } else + vi->i_gid = GLOBAL_ROOT_GID; + + vi->i_mode = 0777; + + /* + * Initialize the ntfs specific part of @vi special casing + * FILE_MFT which we need to do at mount time. + */ + if (vi->i_ino != FILE_MFT) + ntfs_init_big_inode(vi); + + m = map_mft_record(ni); + if (IS_ERR(m)) { + err = PTR_ERR(m); + goto err_out; + } + + ctx = ntfs_attr_get_search_ctx(ni, m); + if (!ctx) { + err = -ENOMEM; + goto unm_err_out; + } + + if (!(m->flags & MFT_RECORD_IN_USE)) { + err = -ENOENT; + vol_err = false; + goto unm_err_out; + } + + if (m->base_mft_record) { + ntfs_error(vi->i_sb, "Inode is an extent inode!"); + goto unm_err_out; + } + + /* Transfer information from mft record into vfs and ntfs inodes. */ + vi->i_generation = ni->seq_no = le16_to_cpu(m->sequence_number); + + if (le16_to_cpu(m->link_count) < 1) { + ntfs_error(vi->i_sb, "Inode link count is 0!"); + goto unm_err_out; + } + set_nlink(vi, le16_to_cpu(m->link_count)); + + /* If read-only, no one gets write permissions. */ + if (IS_RDONLY(vi)) + vi->i_mode &= ~0222; + + /* + * Find the standard information attribute in the mft record. At this + * stage we haven't setup the attribute list stuff yet, so this could + * in fact fail if the standard information is in an extent record, but + * I don't think this actually ever happens. + */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, 0, 0, NULL, 0, + ctx); + if (unlikely(err)) { + if (err == -ENOENT) + ntfs_error(vi->i_sb, "$STANDARD_INFORMATION attribute is missing."); + goto unm_err_out; + } + a = ctx->attr; + /* Get the standard information attribute value. */ + si = (struct standard_information *)((u8 *)a + + le16_to_cpu(a->data.resident.value_offset)); + + /* Transfer information from the standard information into vi. */ + /* + * Note: The i_?times do not quite map perfectly onto the NTFS times, + * but they are close enough, and in the end it doesn't really matter + * that much... + */ + /* + * mtime is the last change of the data within the file. Not changed + * when only metadata is changed, e.g. a rename doesn't affect mtime. + */ + ni->i_crtime = ntfs2utc(si->creation_time); + + inode_set_mtime_to_ts(vi, ntfs2utc(si->last_data_change_time)); + /* + * ctime is the last change of the metadata of the file. This obviously + * always changes, when mtime is changed. ctime can be changed on its + * own, mtime is then not changed, e.g. when a file is renamed. + */ + inode_set_ctime_to_ts(vi, ntfs2utc(si->last_mft_change_time)); + /* + * Last access to the data within the file. Not changed during a rename + * for example but changed whenever the file is written to. + */ + inode_set_atime_to_ts(vi, ntfs2utc(si->last_access_time)); + ni->flags = si->file_attributes; + + /* Find the attribute list attribute if present. */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx); + if (err) { + if (unlikely(err != -ENOENT)) { + ntfs_error(vi->i_sb, "Failed to lookup attribute list attribute."); + goto unm_err_out; + } + } else { + if (vi->i_ino == FILE_MFT) + goto skip_attr_list_load; + ntfs_debug("Attribute list found in inode 0x%llx.", ni->mft_no); + NInoSetAttrList(ni); + a = ctx->attr; + if (a->flags & ATTR_COMPRESSION_MASK) { + ntfs_error(vi->i_sb, + "Attribute list attribute is compressed."); + goto unm_err_out; + } + if (a->flags & ATTR_IS_ENCRYPTED || + a->flags & ATTR_IS_SPARSE) { + if (a->non_resident) { + ntfs_error(vi->i_sb, + "Non-resident attribute list attribute is encrypted/sparse."); + goto unm_err_out; + } + ntfs_warning(vi->i_sb, + "Resident attribute list attribute in inode 0x%llx is marked encrypted/sparse which is not true. However, Windows allows this and chkdsk does not detect or correct it so we will just ignore the invalid flags and pretend they are not set.", + ni->mft_no); + } + /* Now allocate memory for the attribute list. */ + ni->attr_list_size = (u32)ntfs_attr_size(a); + if (!ni->attr_list_size) { + ntfs_error(vi->i_sb, "Attr_list_size is zero"); + goto unm_err_out; + } + ni->attr_list = kvzalloc(ni->attr_list_size, GFP_NOFS); + if (!ni->attr_list) { + ntfs_error(vi->i_sb, + "Not enough memory to allocate buffer for attribute list."); + err = -ENOMEM; + goto unm_err_out; + } + if (a->non_resident) { + NInoSetAttrListNonResident(ni); + if (a->data.non_resident.lowest_vcn) { + ntfs_error(vi->i_sb, "Attribute list has non zero lowest_vcn."); + goto unm_err_out; + } + + /* Now load the attribute list. */ + err = load_attribute_list(ni, ni->attr_list, ni->attr_list_size); + if (err) { + ntfs_error(vi->i_sb, "Failed to load attribute list attribute."); + goto unm_err_out; + } + } else /* if (!a->non_resident) */ { + /* Now copy the attribute list. */ + memcpy(ni->attr_list, (u8 *)a + le16_to_cpu( + a->data.resident.value_offset), + le32_to_cpu( + a->data.resident.value_length)); + } + } +skip_attr_list_load: + err = ntfs_attr_lookup(AT_EA_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx); + if (!err) { + NInoSetHasEA(ni); + ntfs_ea_get_wsl_inode(vi, &dev, flags); + } + + if (m->flags & MFT_RECORD_IS_DIRECTORY) { + vi->i_mode |= S_IFDIR; + /* + * Apply the directory permissions mask set in the mount + * options. + */ + vi->i_mode &= ~vol->dmask; + /* Things break without this kludge! */ + if (vi->i_nlink > 1) + set_nlink(vi, 1); + } else { + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + unsigned int mode; + + mode = ntfs_make_symlink(ni); + if (mode) + vi->i_mode |= mode; + else { + vi->i_mode &= ~S_IFLNK; + vi->i_mode |= S_IFREG; + } + } else + vi->i_mode |= S_IFREG; + /* Apply the file permissions mask set in the mount options. */ + vi->i_mode &= ~vol->fmask; + } + + /* + * If an attribute list is present we now have the attribute list value + * in ntfs_ino->attr_list and it is ntfs_ino->attr_list_size bytes. + */ + if (S_ISDIR(vi->i_mode)) { + struct index_root *ir; + u8 *ir_end, *index_end; + +view_index_meta: + /* It is a directory, find index root attribute. */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(AT_INDEX_ROOT, name, name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (unlikely(err)) { + if (err == -ENOENT) + ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is missing."); + goto unm_err_out; + } + a = ctx->attr; + /* Set up the state. */ + if (unlikely(a->non_resident)) { + ntfs_error(vol->sb, + "$INDEX_ROOT attribute is not resident."); + goto unm_err_out; + } + /* Ensure the attribute name is placed before the value. */ + if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >= + le16_to_cpu(a->data.resident.value_offset)))) { + ntfs_error(vol->sb, + "$INDEX_ROOT attribute name is placed after the attribute value."); + goto unm_err_out; + } + /* + * Compressed/encrypted index root just means that the newly + * created files in that directory should be created compressed/ + * encrypted. However index root cannot be both compressed and + * encrypted. + */ + if (a->flags & ATTR_COMPRESSION_MASK) { + NInoSetCompressed(ni); + ni->flags |= FILE_ATTR_COMPRESSED; + } + if (a->flags & ATTR_IS_ENCRYPTED) { + if (a->flags & ATTR_COMPRESSION_MASK) { + ntfs_error(vi->i_sb, "Found encrypted and compressed attribute."); + goto unm_err_out; + } + NInoSetEncrypted(ni); + ni->flags |= FILE_ATTR_ENCRYPTED; + } + if (a->flags & ATTR_IS_SPARSE) { + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + } + ir = (struct index_root *)((u8 *)a + + le16_to_cpu(a->data.resident.value_offset)); + ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length); + index_end = (u8 *)&ir->index + + le32_to_cpu(ir->index.index_length); + if (index_end > ir_end) { + ntfs_error(vi->i_sb, "Directory index is corrupt."); + goto unm_err_out; + } + + if (extend_sys) { + if (ir->type) { + ntfs_error(vi->i_sb, "Indexed attribute is not zero."); + goto unm_err_out; + } + } else { + if (ir->type != AT_FILE_NAME) { + ntfs_error(vi->i_sb, "Indexed attribute is not $FILE_NAME."); + goto unm_err_out; + } + + if (ir->collation_rule != COLLATION_FILE_NAME) { + ntfs_error(vi->i_sb, + "Index collation rule is not COLLATION_FILE_NAME."); + goto unm_err_out; + } + } + + ni->itype.index.collation_rule = ir->collation_rule; + ni->itype.index.block_size = le32_to_cpu(ir->index_block_size); + if (ni->itype.index.block_size & + (ni->itype.index.block_size - 1)) { + ntfs_error(vi->i_sb, "Index block size (%u) is not a power of two.", + ni->itype.index.block_size); + goto unm_err_out; + } + if (ni->itype.index.block_size > PAGE_SIZE) { + ntfs_error(vi->i_sb, + "Index block size (%u) > PAGE_SIZE (%ld) is not supported.", + ni->itype.index.block_size, + PAGE_SIZE); + err = -EOPNOTSUPP; + goto unm_err_out; + } + if (ni->itype.index.block_size < NTFS_BLOCK_SIZE) { + ntfs_error(vi->i_sb, + "Index block size (%u) < NTFS_BLOCK_SIZE (%i) is not supported.", + ni->itype.index.block_size, + NTFS_BLOCK_SIZE); + err = -EOPNOTSUPP; + goto unm_err_out; + } + ni->itype.index.block_size_bits = + ffs(ni->itype.index.block_size) - 1; + /* Determine the size of a vcn in the directory index. */ + if (vol->cluster_size <= ni->itype.index.block_size) { + ni->itype.index.vcn_size = vol->cluster_size; + ni->itype.index.vcn_size_bits = vol->cluster_size_bits; + } else { + ni->itype.index.vcn_size = vol->sector_size; + ni->itype.index.vcn_size_bits = vol->sector_size_bits; + } + + /* Setup the index allocation attribute, even if not present. */ + ni->type = AT_INDEX_ROOT; + ni->name = name; + ni->name_len = name_len; + vi->i_size = ni->initialized_size = ni->data_size = + le32_to_cpu(a->data.resident.value_length); + ni->allocated_size = (ni->data_size + 7) & ~7; + /* We are done with the mft record, so we release it. */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + m = NULL; + ctx = NULL; + /* Setup the operations for this inode. */ + ntfs_set_vfs_operations(vi, S_IFDIR, 0); + if (ir->index.flags & LARGE_INDEX) + NInoSetIndexAllocPresent(ni); + } else { + /* It is a file. */ + ntfs_attr_reinit_search_ctx(ctx); + + /* Setup the data attribute, even if not present. */ + ni->type = AT_DATA; + ni->name = AT_UNNAMED; + ni->name_len = 0; + + /* Find first extent of the unnamed data attribute. */ + err = ntfs_attr_lookup(AT_DATA, NULL, 0, 0, 0, NULL, 0, ctx); + if (unlikely(err)) { + vi->i_size = ni->initialized_size = + ni->allocated_size = 0; + if (err != -ENOENT) { + ntfs_error(vi->i_sb, "Failed to lookup $DATA attribute."); + goto unm_err_out; + } + /* + * FILE_Secure does not have an unnamed $DATA + * attribute, so we special case it here. + */ + if (vi->i_ino == FILE_Secure) + goto no_data_attr_special_case; + /* + * Most if not all the system files in the $Extend + * system directory do not have unnamed data + * attributes so we need to check if the parent + * directory of the file is FILE_Extend and if it is + * ignore this error. To do this we need to get the + * name of this inode from the mft record as the name + * contains the back reference to the parent directory. + */ + extend_sys = ntfs_is_extended_system_file(ctx); + if (extend_sys > 0) { + if (m->flags & MFT_RECORD_IS_VIEW_INDEX) { + if (extend_sys == 2) { + name = reparse_index_name; + name_len = 2; + goto view_index_meta; + } else if (extend_sys == 3) { + name = objid_index_name; + name_len = 2; + goto view_index_meta; + } + } + goto no_data_attr_special_case; + } + + err = extend_sys; + ntfs_error(vi->i_sb, "$DATA attribute is missing, err : %d", err); + goto unm_err_out; + } + a = ctx->attr; + /* Setup the state. */ + if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) { + if (a->flags & ATTR_COMPRESSION_MASK) { + NInoSetCompressed(ni); + ni->flags |= FILE_ATTR_COMPRESSED; + if (vol->cluster_size > 4096) { + ntfs_error(vi->i_sb, + "Found compressed data but compression is disabled due to cluster size (%i) > 4kiB.", + vol->cluster_size); + goto unm_err_out; + } + if ((a->flags & ATTR_COMPRESSION_MASK) + != ATTR_IS_COMPRESSED) { + ntfs_error(vi->i_sb, + "Found unknown compression method or corrupt file."); + goto unm_err_out; + } + } + if (a->flags & ATTR_IS_SPARSE) { + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + } + } + if (a->flags & ATTR_IS_ENCRYPTED) { + if (NInoCompressed(ni)) { + ntfs_error(vi->i_sb, "Found encrypted and compressed data."); + goto unm_err_out; + } + NInoSetEncrypted(ni); + ni->flags |= FILE_ATTR_ENCRYPTED; + } + if (a->non_resident) { + NInoSetNonResident(ni); + if (NInoCompressed(ni) || NInoSparse(ni)) { + if (NInoCompressed(ni) && + a->data.non_resident.compression_unit != 4) { + ntfs_error(vi->i_sb, + "Found non-standard compression unit (%u instead of 4). Cannot handle this.", + a->data.non_resident.compression_unit); + err = -EOPNOTSUPP; + goto unm_err_out; + } + + if (NInoSparse(ni) && + a->data.non_resident.compression_unit && + a->data.non_resident.compression_unit != + vol->sparse_compression_unit) { + ntfs_error(vi->i_sb, + "Found non-standard compression unit (%u instead of 0 or %d). Cannot handle this.", + a->data.non_resident.compression_unit, + vol->sparse_compression_unit); + err = -EOPNOTSUPP; + goto unm_err_out; + } + + + if (a->data.non_resident.compression_unit) { + ni->itype.compressed.block_size = 1U << + (a->data.non_resident.compression_unit + + vol->cluster_size_bits); + ni->itype.compressed.block_size_bits = + ffs(ni->itype.compressed.block_size) - 1; + ni->itype.compressed.block_clusters = + 1U << a->data.non_resident.compression_unit; + } else { + ni->itype.compressed.block_size = 0; + ni->itype.compressed.block_size_bits = + 0; + ni->itype.compressed.block_clusters = + 0; + } + ni->itype.compressed.size = le64_to_cpu( + a->data.non_resident.compressed_size); + } + if (a->data.non_resident.lowest_vcn) { + ntfs_error(vi->i_sb, + "First extent of $DATA attribute has non zero lowest_vcn."); + goto unm_err_out; + } + vi->i_size = ni->data_size = le64_to_cpu(a->data.non_resident.data_size); + ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); + ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); + } else { /* Resident attribute. */ + vi->i_size = ni->data_size = ni->initialized_size = le32_to_cpu( + a->data.resident.value_length); + ni->allocated_size = le32_to_cpu(a->length) - + le16_to_cpu( + a->data.resident.value_offset); + if (vi->i_size > ni->allocated_size) { + ntfs_error(vi->i_sb, + "Resident data attribute is corrupt (size exceeds allocation)."); + goto unm_err_out; + } + } +no_data_attr_special_case: + /* We are done with the mft record, so we release it. */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + m = NULL; + ctx = NULL; + /* Setup the operations for this inode. */ + ntfs_set_vfs_operations(vi, vi->i_mode, dev); + } + + if (NVolSysImmutable(vol) && (ni->flags & FILE_ATTR_SYSTEM) && + !S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && !S_ISLNK(vi->i_mode)) + vi->i_flags |= S_IMMUTABLE; + + /* + * The number of 512-byte blocks used on disk (for stat). This is in so + * far inaccurate as it doesn't account for any named streams or other + * special non-resident attributes, but that is how Windows works, too, + * so we are at least consistent with Windows, if not entirely + * consistent with the Linux Way. Doing it the Linux Way would cause a + * significant slowdown as it would involve iterating over all + * attributes in the mft record and adding the allocated/compressed + * sizes of all non-resident attributes present to give us the Linux + * correct size that should go into i_blocks (after division by 512). + */ + if (S_ISREG(vi->i_mode) && (NInoCompressed(ni) || NInoSparse(ni))) + vi->i_blocks = ni->itype.compressed.size >> 9; + else + vi->i_blocks = ni->allocated_size >> 9; + + ntfs_debug("Done."); + return 0; +unm_err_out: + if (!err) + err = -EIO; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(ni); +err_out: + if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) { + ntfs_error(vol->sb, + "Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.", + err, ni->mft_no); + NVolSetErrors(vol); + } + return err; +} + +/* + * ntfs_read_locked_attr_inode - read an attribute inode from its base inode + * @base_vi: base inode + * @vi: attribute inode to read + * + * ntfs_read_locked_attr_inode() is called from ntfs_attr_iget() to read the + * attribute inode described by @vi into memory from the base mft record + * described by @base_ni. + * + * ntfs_read_locked_attr_inode() maps, pins and locks the base inode for + * reading and looks up the attribute described by @vi before setting up the + * necessary fields in @vi as well as initializing the ntfs inode. + * + * Q: What locks are held when the function is called? + * A: i_state has I_NEW set, hence the inode is locked, also + * i_count is set to 1, so it is not going to go away + * + * Return 0 on success and -errno on error. + * + * Note this cannot be called for AT_INDEX_ALLOCATION. + */ +static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) +{ + struct ntfs_volume *vol = NTFS_SB(vi->i_sb); + struct ntfs_inode *ni = NTFS_I(vi), *base_ni = NTFS_I(base_vi); + struct mft_record *m; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + int err = 0; + + ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); + + ntfs_init_big_inode(vi); + + /* Just mirror the values from the base inode. */ + vi->i_uid = base_vi->i_uid; + vi->i_gid = base_vi->i_gid; + set_nlink(vi, base_vi->i_nlink); + inode_set_mtime_to_ts(vi, inode_get_mtime(base_vi)); + inode_set_ctime_to_ts(vi, inode_get_ctime(base_vi)); + inode_set_atime_to_ts(vi, inode_get_atime(base_vi)); + vi->i_generation = ni->seq_no = base_ni->seq_no; + + /* Set inode type to zero but preserve permissions. */ + vi->i_mode = base_vi->i_mode & ~S_IFMT; + + m = map_mft_record(base_ni); + if (IS_ERR(m)) { + err = PTR_ERR(m); + goto err_out; + } + ctx = ntfs_attr_get_search_ctx(base_ni, m); + if (!ctx) { + err = -ENOMEM; + goto unm_err_out; + } + /* Find the attribute. */ + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) + goto unm_err_out; + a = ctx->attr; + if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) { + if (a->flags & ATTR_COMPRESSION_MASK) { + NInoSetCompressed(ni); + ni->flags |= FILE_ATTR_COMPRESSED; + if ((ni->type != AT_DATA) || (ni->type == AT_DATA && + ni->name_len)) { + ntfs_error(vi->i_sb, + "Found compressed non-data or named data attribute."); + goto unm_err_out; + } + if (vol->cluster_size > 4096) { + ntfs_error(vi->i_sb, + "Found compressed attribute but compression is disabled due to cluster size (%i) > 4kiB.", + vol->cluster_size); + goto unm_err_out; + } + if ((a->flags & ATTR_COMPRESSION_MASK) != + ATTR_IS_COMPRESSED) { + ntfs_error(vi->i_sb, "Found unknown compression method."); + goto unm_err_out; + } + } + /* + * The compressed/sparse flag set in an index root just means + * to compress all files. + */ + if (NInoMstProtected(ni) && ni->type != AT_INDEX_ROOT) { + ntfs_error(vi->i_sb, + "Found mst protected attribute but the attribute is %s.", + NInoCompressed(ni) ? "compressed" : "sparse"); + goto unm_err_out; + } + if (a->flags & ATTR_IS_SPARSE) { + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + } + } + if (a->flags & ATTR_IS_ENCRYPTED) { + if (NInoCompressed(ni)) { + ntfs_error(vi->i_sb, "Found encrypted and compressed data."); + goto unm_err_out; + } + /* + * The encryption flag set in an index root just means to + * encrypt all files. + */ + if (NInoMstProtected(ni) && ni->type != AT_INDEX_ROOT) { + ntfs_error(vi->i_sb, + "Found mst protected attribute but the attribute is encrypted."); + goto unm_err_out; + } + if (ni->type != AT_DATA) { + ntfs_error(vi->i_sb, + "Found encrypted non-data attribute."); + goto unm_err_out; + } + NInoSetEncrypted(ni); + ni->flags |= FILE_ATTR_ENCRYPTED; + } + if (!a->non_resident) { + /* Ensure the attribute name is placed before the value. */ + if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >= + le16_to_cpu(a->data.resident.value_offset)))) { + ntfs_error(vol->sb, + "Attribute name is placed after the attribute value."); + goto unm_err_out; + } + if (NInoMstProtected(ni)) { + ntfs_error(vi->i_sb, + "Found mst protected attribute but the attribute is resident."); + goto unm_err_out; + } + vi->i_size = ni->initialized_size = ni->data_size = le32_to_cpu( + a->data.resident.value_length); + ni->allocated_size = le32_to_cpu(a->length) - + le16_to_cpu(a->data.resident.value_offset); + if (vi->i_size > ni->allocated_size) { + ntfs_error(vi->i_sb, + "Resident attribute is corrupt (size exceeds allocation)."); + goto unm_err_out; + } + } else { + NInoSetNonResident(ni); + /* + * Ensure the attribute name is placed before the mapping pairs + * array. + */ + if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >= + le16_to_cpu( + a->data.non_resident.mapping_pairs_offset)))) { + ntfs_error(vol->sb, + "Attribute name is placed after the mapping pairs array."); + goto unm_err_out; + } + if (NInoCompressed(ni) || NInoSparse(ni)) { + if (NInoCompressed(ni) && a->data.non_resident.compression_unit != 4) { + ntfs_error(vi->i_sb, + "Found non-standard compression unit (%u instead of 4). Cannot handle this.", + a->data.non_resident.compression_unit); + err = -EOPNOTSUPP; + goto unm_err_out; + } + if (a->data.non_resident.compression_unit) { + ni->itype.compressed.block_size = 1U << + (a->data.non_resident.compression_unit + + vol->cluster_size_bits); + ni->itype.compressed.block_size_bits = + ffs(ni->itype.compressed.block_size) - 1; + ni->itype.compressed.block_clusters = 1U << + a->data.non_resident.compression_unit; + } else { + ni->itype.compressed.block_size = 0; + ni->itype.compressed.block_size_bits = 0; + ni->itype.compressed.block_clusters = 0; + } + ni->itype.compressed.size = le64_to_cpu( + a->data.non_resident.compressed_size); + } + if (a->data.non_resident.lowest_vcn) { + ntfs_error(vi->i_sb, "First extent of attribute has non-zero lowest_vcn."); + goto unm_err_out; + } + vi->i_size = ni->data_size = le64_to_cpu(a->data.non_resident.data_size); + ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); + ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); + } + vi->i_mapping->a_ops = &ntfs_aops; + if ((NInoCompressed(ni) || NInoSparse(ni)) && ni->type != AT_INDEX_ROOT) + vi->i_blocks = ni->itype.compressed.size >> 9; + else + vi->i_blocks = ni->allocated_size >> 9; + /* + * Make sure the base inode does not go away and attach it to the + * attribute inode. + */ + if (!igrab(base_vi)) { + err = -ENOENT; + goto unm_err_out; + } + ni->ext.base_ntfs_ino = base_ni; + ni->nr_extents = -1; + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); + + ntfs_debug("Done."); + return 0; + +unm_err_out: + if (!err) + err = -EIO; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); +err_out: + if (err != -ENOENT) + ntfs_error(vol->sb, + "Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.", + err, ni->mft_no, ni->type, ni->name_len, + base_ni->mft_no); + if (err != -ENOENT && err != -ENOMEM) + NVolSetErrors(vol); + return err; +} + +/* + * ntfs_read_locked_index_inode - read an index inode from its base inode + * @base_vi: base inode + * @vi: index inode to read + * + * ntfs_read_locked_index_inode() is called from ntfs_index_iget() to read the + * index inode described by @vi into memory from the base mft record described + * by @base_ni. + * + * ntfs_read_locked_index_inode() maps, pins and locks the base inode for + * reading and looks up the attributes relating to the index described by @vi + * before setting up the necessary fields in @vi as well as initializing the + * ntfs inode. + * + * Note, index inodes are essentially attribute inodes (NInoAttr() is true) + * with the attribute type set to AT_INDEX_ALLOCATION. Apart from that, they + * are setup like directory inodes since directories are a special case of + * indices ao they need to be treated in much the same way. Most importantly, + * for small indices the index allocation attribute might not actually exist. + * However, the index root attribute always exists but this does not need to + * have an inode associated with it and this is why we define a new inode type + * index. Also, like for directories, we need to have an attribute inode for + * the bitmap attribute corresponding to the index allocation attribute and we + * can store this in the appropriate field of the inode, just like we do for + * normal directory inodes. + * + * Q: What locks are held when the function is called? + * A: i_state has I_NEW set, hence the inode is locked, also + * i_count is set to 1, so it is not going to go away + * + * Return 0 on success and -errno on error. + */ +static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) +{ + loff_t bvi_size; + struct ntfs_volume *vol = NTFS_SB(vi->i_sb); + struct ntfs_inode *ni = NTFS_I(vi), *base_ni = NTFS_I(base_vi), *bni; + struct inode *bvi; + struct mft_record *m; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + struct index_root *ir; + u8 *ir_end, *index_end; + int err = 0; + + ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); + lockdep_assert_held(&base_ni->mrec_lock); + + ntfs_init_big_inode(vi); + /* Just mirror the values from the base inode. */ + vi->i_uid = base_vi->i_uid; + vi->i_gid = base_vi->i_gid; + set_nlink(vi, base_vi->i_nlink); + inode_set_mtime_to_ts(vi, inode_get_mtime(base_vi)); + inode_set_ctime_to_ts(vi, inode_get_ctime(base_vi)); + inode_set_atime_to_ts(vi, inode_get_atime(base_vi)); + vi->i_generation = ni->seq_no = base_ni->seq_no; + /* Set inode type to zero but preserve permissions. */ + vi->i_mode = base_vi->i_mode & ~S_IFMT; + /* Map the mft record for the base inode. */ + m = map_mft_record(base_ni); + if (IS_ERR(m)) { + err = PTR_ERR(m); + goto err_out; + } + ctx = ntfs_attr_get_search_ctx(base_ni, m); + if (!ctx) { + err = -ENOMEM; + goto unm_err_out; + } + /* Find the index root attribute. */ + err = ntfs_attr_lookup(AT_INDEX_ROOT, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + if (err == -ENOENT) + ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is missing."); + goto unm_err_out; + } + a = ctx->attr; + /* Set up the state. */ + if (unlikely(a->non_resident)) { + ntfs_error(vol->sb, "$INDEX_ROOT attribute is not resident."); + goto unm_err_out; + } + /* Ensure the attribute name is placed before the value. */ + if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >= + le16_to_cpu(a->data.resident.value_offset)))) { + ntfs_error(vol->sb, + "$INDEX_ROOT attribute name is placed after the attribute value."); + goto unm_err_out; + } + + ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); + ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length); + index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length); + if (index_end > ir_end) { + ntfs_error(vi->i_sb, "Index is corrupt."); + goto unm_err_out; + } + + ni->itype.index.collation_rule = ir->collation_rule; + ntfs_debug("Index collation rule is 0x%x.", + le32_to_cpu(ir->collation_rule)); + ni->itype.index.block_size = le32_to_cpu(ir->index_block_size); + if (!is_power_of_2(ni->itype.index.block_size)) { + ntfs_error(vi->i_sb, "Index block size (%u) is not a power of two.", + ni->itype.index.block_size); + goto unm_err_out; + } + if (ni->itype.index.block_size > PAGE_SIZE) { + ntfs_error(vi->i_sb, "Index block size (%u) > PAGE_SIZE (%ld) is not supported.", + ni->itype.index.block_size, PAGE_SIZE); + err = -EOPNOTSUPP; + goto unm_err_out; + } + if (ni->itype.index.block_size < NTFS_BLOCK_SIZE) { + ntfs_error(vi->i_sb, + "Index block size (%u) < NTFS_BLOCK_SIZE (%i) is not supported.", + ni->itype.index.block_size, NTFS_BLOCK_SIZE); + err = -EOPNOTSUPP; + goto unm_err_out; + } + ni->itype.index.block_size_bits = ffs(ni->itype.index.block_size) - 1; + /* Determine the size of a vcn in the index. */ + if (vol->cluster_size <= ni->itype.index.block_size) { + ni->itype.index.vcn_size = vol->cluster_size; + ni->itype.index.vcn_size_bits = vol->cluster_size_bits; + } else { + ni->itype.index.vcn_size = vol->sector_size; + ni->itype.index.vcn_size_bits = vol->sector_size_bits; + } + + /* Find index allocation attribute. */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(AT_INDEX_ALLOCATION, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + if (err == -ENOENT) { + /* No index allocation. */ + vi->i_size = ni->initialized_size = ni->allocated_size = 0; + /* We are done with the mft record, so we release it. */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); + m = NULL; + ctx = NULL; + goto skip_large_index_stuff; + } else + ntfs_error(vi->i_sb, "Failed to lookup $INDEX_ALLOCATION attribute."); + goto unm_err_out; + } + NInoSetIndexAllocPresent(ni); + NInoSetNonResident(ni); + ni->type = AT_INDEX_ALLOCATION; + + a = ctx->attr; + if (!a->non_resident) { + ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is resident."); + goto unm_err_out; + } + /* + * Ensure the attribute name is placed before the mapping pairs array. + */ + if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >= + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)))) { + ntfs_error(vol->sb, + "$INDEX_ALLOCATION attribute name is placed after the mapping pairs array."); + goto unm_err_out; + } + if (a->flags & ATTR_IS_ENCRYPTED) { + ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is encrypted."); + goto unm_err_out; + } + if (a->flags & ATTR_IS_SPARSE) { + ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is sparse."); + goto unm_err_out; + } + if (a->flags & ATTR_COMPRESSION_MASK) { + ntfs_error(vi->i_sb, + "$INDEX_ALLOCATION attribute is compressed."); + goto unm_err_out; + } + if (a->data.non_resident.lowest_vcn) { + ntfs_error(vi->i_sb, + "First extent of $INDEX_ALLOCATION attribute has non zero lowest_vcn."); + goto unm_err_out; + } + vi->i_size = ni->data_size = le64_to_cpu(a->data.non_resident.data_size); + ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); + ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); + /* + * We are done with the mft record, so we release it. Otherwise + * we would deadlock in ntfs_attr_iget(). + */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); + m = NULL; + ctx = NULL; + /* Get the index bitmap attribute inode. */ + bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len); + if (IS_ERR(bvi)) { + ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); + err = PTR_ERR(bvi); + goto unm_err_out; + } + bni = NTFS_I(bvi); + if (NInoCompressed(bni) || NInoEncrypted(bni) || + NInoSparse(bni)) { + ntfs_error(vi->i_sb, + "$BITMAP attribute is compressed and/or encrypted and/or sparse."); + goto iput_unm_err_out; + } + /* Consistency check bitmap size vs. index allocation size. */ + bvi_size = i_size_read(bvi); + if ((bvi_size << 3) < (vi->i_size >> ni->itype.index.block_size_bits)) { + ntfs_error(vi->i_sb, + "Index bitmap too small (0x%llx) for index allocation (0x%llx).", + bvi_size << 3, vi->i_size); + goto iput_unm_err_out; + } + iput(bvi); +skip_large_index_stuff: + /* Setup the operations for this index inode. */ + ntfs_set_vfs_operations(vi, S_IFDIR, 0); + vi->i_blocks = ni->allocated_size >> 9; + /* + * Make sure the base inode doesn't go away and attach it to the + * index inode. + */ + if (!igrab(base_vi)) + goto unm_err_out; + ni->ext.base_ntfs_ino = base_ni; + ni->nr_extents = -1; + + ntfs_debug("Done."); + return 0; +iput_unm_err_out: + iput(bvi); +unm_err_out: + if (!err) + err = -EIO; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(base_ni); +err_out: + ntfs_error(vi->i_sb, + "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", + err, ni->mft_no, ni->name_len); + if (err != -EOPNOTSUPP && err != -ENOMEM) + NVolSetErrors(vol); + return err; +} + +/* + * load_attribute_list_mount - load an attribute list into memory + * @vol: ntfs volume from which to read + * @rl: runlist of the attribute list + * @al_start: destination buffer + * @size: size of the destination buffer in bytes + * @initialized_size: initialized size of the attribute list + * + * Walk the runlist @rl and load all clusters from it copying them into + * the linear buffer @al. The maximum number of bytes copied to @al is @size + * bytes. Note, @size does not need to be a multiple of the cluster size. If + * @initialized_size is less than @size, the region in @al between + * @initialized_size and @size will be zeroed and not read from disk. + * + * Return 0 on success or -errno on error. + */ +static int load_attribute_list_mount(struct ntfs_volume *vol, + struct runlist_element *rl, u8 *al_start, const s64 size, + const s64 initialized_size) +{ + s64 lcn; + u8 *al = al_start; + u8 *al_end = al + initialized_size; + struct super_block *sb; + int err = 0; + loff_t rl_byte_off, rl_byte_len; + + ntfs_debug("Entering."); + if (!vol || !rl || !al || size <= 0 || initialized_size < 0 || + initialized_size > size) + return -EINVAL; + if (!initialized_size) { + memset(al, 0, size); + return 0; + } + sb = vol->sb; + + /* Read all clusters specified by the runlist one run at a time. */ + while (rl->length) { + lcn = ntfs_rl_vcn_to_lcn(rl, rl->vcn); + ntfs_debug("Reading vcn = 0x%llx, lcn = 0x%llx.", + (unsigned long long)rl->vcn, + (unsigned long long)lcn); + /* The attribute list cannot be sparse. */ + if (lcn < 0) { + ntfs_error(sb, "ntfs_rl_vcn_to_lcn() failed. Cannot read attribute list."); + return -EIO; + } + + rl_byte_off = ntfs_cluster_to_bytes(vol, lcn); + rl_byte_len = ntfs_cluster_to_bytes(vol, rl->length); + + if (al + rl_byte_len > al_end) + rl_byte_len = al_end - al; + + err = ntfs_bdev_read(sb->s_bdev, al, rl_byte_off, + round_up(rl_byte_len, SECTOR_SIZE)); + if (err) { + ntfs_error(sb, "Cannot read attribute list."); + return -EIO; + } + + if (al + rl_byte_len >= al_end) { + if (initialized_size < size) + goto initialize; + goto done; + } + + al += rl_byte_len; + rl++; + } + if (initialized_size < size) { +initialize: + memset(al_start + initialized_size, 0, size - initialized_size); + } +done: + return err; +} + +/* + * The MFT inode has special locking, so teach the lock validator + * about this by splitting off the locking rules of the MFT from + * the locking rules of other inodes. The MFT inode can never be + * accessed from the VFS side (or even internally), only by the + * map_mft functions. + */ +static struct lock_class_key mft_ni_runlist_lock_key, mft_ni_mrec_lock_key; + +/* + * ntfs_read_inode_mount - special read_inode for mount time use only + * @vi: inode to read + * + * Read inode FILE_MFT at mount time, only called with super_block lock + * held from within the read_super() code path. + * + * This function exists because when it is called the page cache for $MFT/$DATA + * is not initialized and hence we cannot get at the contents of mft records + * by calling map_mft_record*(). + * + * Further it needs to cope with the circular references problem, i.e. cannot + * load any attributes other than $ATTRIBUTE_LIST until $DATA is loaded, because + * we do not know where the other extent mft records are yet and again, because + * we cannot call map_mft_record*() yet. Obviously this applies only when an + * attribute list is actually present in $MFT inode. + * + * We solve these problems by starting with the $DATA attribute before anything + * else and iterating using ntfs_attr_lookup($DATA) over all extents. As each + * extent is found, we ntfs_mapping_pairs_decompress() including the implied + * ntfs_runlists_merge(). Each step of the iteration necessarily provides + * sufficient information for the next step to complete. + * + * This should work but there are two possible pit falls (see inline comments + * below), but only time will tell if they are real pits or just smoke... + */ +int ntfs_read_inode_mount(struct inode *vi) +{ + s64 next_vcn, last_vcn, highest_vcn; + struct super_block *sb = vi->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + struct ntfs_inode *ni = NTFS_I(vi); + struct mft_record *m = NULL; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + unsigned int i, nr_blocks; + int err; + size_t new_rl_count; + + ntfs_debug("Entering."); + + /* Initialize the ntfs specific part of @vi. */ + ntfs_init_big_inode(vi); + + + /* Setup the data attribute. It is special as it is mst protected. */ + NInoSetNonResident(ni); + NInoSetMstProtected(ni); + NInoSetSparseDisabled(ni); + ni->type = AT_DATA; + ni->name = AT_UNNAMED; + ni->name_len = 0; + /* + * This sets up our little cheat allowing us to reuse the async read io + * completion handler for directories. + */ + ni->itype.index.block_size = vol->mft_record_size; + ni->itype.index.block_size_bits = vol->mft_record_size_bits; + + /* Very important! Needed to be able to call map_mft_record*(). */ + vol->mft_ino = vi; + + /* Allocate enough memory to read the first mft record. */ + if (vol->mft_record_size > 64 * 1024) { + ntfs_error(sb, "Unsupported mft record size %i (max 64kiB).", + vol->mft_record_size); + goto err_out; + } + + i = vol->mft_record_size; + if (i < sb->s_blocksize) + i = sb->s_blocksize; + + m = kzalloc(i, GFP_NOFS); + if (!m) { + ntfs_error(sb, "Failed to allocate buffer for $MFT record 0."); + goto err_out; + } + + /* Determine the first block of the $MFT/$DATA attribute. */ + nr_blocks = ntfs_bytes_to_sector(vol, vol->mft_record_size); + if (!nr_blocks) + nr_blocks = 1; + + /* Load $MFT/$DATA's first mft record. */ + err = ntfs_bdev_read(sb->s_bdev, (char *)m, + ntfs_cluster_to_bytes(vol, vol->mft_lcn), i); + if (err) { + ntfs_error(sb, "Device read failed."); + goto err_out; + } + + if (le32_to_cpu(m->bytes_allocated) != vol->mft_record_size) { + ntfs_error(sb, "Incorrect mft record size %u in superblock, should be %u.", + le32_to_cpu(m->bytes_allocated), vol->mft_record_size); + goto err_out; + } + + /* Apply the mst fixups. */ + if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size)) { + ntfs_error(sb, "MST fixup failed. $MFT is corrupt."); + goto err_out; + } + + if (ntfs_mft_record_check(vol, m, FILE_MFT)) { + ntfs_error(sb, "ntfs_mft_record_check failed. $MFT is corrupt."); + goto err_out; + } + + /* Need this to sanity check attribute list references to $MFT. */ + vi->i_generation = ni->seq_no = le16_to_cpu(m->sequence_number); + + /* Provides read_folio() for map_mft_record(). */ + vi->i_mapping->a_ops = &ntfs_mft_aops; + + ctx = ntfs_attr_get_search_ctx(ni, m); + if (!ctx) { + err = -ENOMEM; + goto err_out; + } + + /* Find the attribute list attribute if present. */ + err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx); + if (err) { + if (unlikely(err != -ENOENT)) { + ntfs_error(sb, + "Failed to lookup attribute list attribute. You should run chkdsk."); + goto put_err_out; + } + } else /* if (!err) */ { + struct attr_list_entry *al_entry, *next_al_entry; + u8 *al_end; + static const char *es = " Not allowed. $MFT is corrupt. You should run chkdsk."; + + ntfs_debug("Attribute list attribute found in $MFT."); + NInoSetAttrList(ni); + a = ctx->attr; + if (a->flags & ATTR_COMPRESSION_MASK) { + ntfs_error(sb, + "Attribute list attribute is compressed.%s", + es); + goto put_err_out; + } + if (a->flags & ATTR_IS_ENCRYPTED || + a->flags & ATTR_IS_SPARSE) { + if (a->non_resident) { + ntfs_error(sb, + "Non-resident attribute list attribute is encrypted/sparse.%s", + es); + goto put_err_out; + } + ntfs_warning(sb, + "Resident attribute list attribute in $MFT system file is marked encrypted/sparse which is not true. However, Windows allows this and chkdsk does not detect or correct it so we will just ignore the invalid flags and pretend they are not set."); + } + /* Now allocate memory for the attribute list. */ + ni->attr_list_size = (u32)ntfs_attr_size(a); + if (!ni->attr_list_size) { + ntfs_error(sb, "Attr_list_size is zero"); + goto put_err_out; + } + ni->attr_list = kvzalloc(round_up(ni->attr_list_size, SECTOR_SIZE), + GFP_NOFS); + if (!ni->attr_list) { + ntfs_error(sb, "Not enough memory to allocate buffer for attribute list."); + goto put_err_out; + } + if (a->non_resident) { + struct runlist_element *rl; + size_t new_rl_count; + + NInoSetAttrListNonResident(ni); + if (a->data.non_resident.lowest_vcn) { + ntfs_error(sb, + "Attribute list has non zero lowest_vcn. $MFT is corrupt. You should run chkdsk."); + goto put_err_out; + } + + rl = ntfs_mapping_pairs_decompress(vol, a, NULL, &new_rl_count); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + ntfs_error(sb, + "Mapping pairs decompression failed with error code %i.", + -err); + goto put_err_out; + } + + err = load_attribute_list_mount(vol, rl, ni->attr_list, ni->attr_list_size, + le64_to_cpu(a->data.non_resident.initialized_size)); + kvfree(rl); + if (err) { + ntfs_error(sb, + "Failed to load attribute list with error code %i.", + -err); + goto put_err_out; + } + } else /* if (!ctx.attr->non_resident) */ { + /* Now copy the attribute list. */ + memcpy(ni->attr_list, (u8 *)a + le16_to_cpu( + a->data.resident.value_offset), + le32_to_cpu(a->data.resident.value_length)); + } + /* The attribute list is now setup in memory. */ + al_entry = (struct attr_list_entry *)ni->attr_list; + al_end = (u8 *)al_entry + ni->attr_list_size; + for (;; al_entry = next_al_entry) { + /* Out of bounds check. */ + if ((u8 *)al_entry < ni->attr_list || + (u8 *)al_entry > al_end) + goto em_put_err_out; + /* Catch the end of the attribute list. */ + if ((u8 *)al_entry == al_end) + goto em_put_err_out; + if (!al_entry->length) + goto em_put_err_out; + if ((u8 *)al_entry + 6 > al_end || + (u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end) + goto em_put_err_out; + next_al_entry = (struct attr_list_entry *)((u8 *)al_entry + + le16_to_cpu(al_entry->length)); + if (le32_to_cpu(al_entry->type) > le32_to_cpu(AT_DATA)) + goto em_put_err_out; + if (al_entry->type != AT_DATA) + continue; + /* We want an unnamed attribute. */ + if (al_entry->name_length) + goto em_put_err_out; + /* Want the first entry, i.e. lowest_vcn == 0. */ + if (al_entry->lowest_vcn) + goto em_put_err_out; + /* First entry has to be in the base mft record. */ + if (MREF_LE(al_entry->mft_reference) != vi->i_ino) { + /* MFT references do not match, logic fails. */ + ntfs_error(sb, + "BUG: The first $DATA extent of $MFT is not in the base mft record."); + goto put_err_out; + } else { + /* Sequence numbers must match. */ + if (MSEQNO_LE(al_entry->mft_reference) != + ni->seq_no) + goto em_put_err_out; + /* Got it. All is ok. We can stop now. */ + break; + } + } + } + + ntfs_attr_reinit_search_ctx(ctx); + + /* Now load all attribute extents. */ + a = NULL; + next_vcn = last_vcn = highest_vcn = 0; + while (!(err = ntfs_attr_lookup(AT_DATA, NULL, 0, 0, next_vcn, NULL, 0, + ctx))) { + struct runlist_element *nrl; + + /* Cache the current attribute. */ + a = ctx->attr; + /* $MFT must be non-resident. */ + if (!a->non_resident) { + ntfs_error(sb, + "$MFT must be non-resident but a resident extent was found. $MFT is corrupt. Run chkdsk."); + goto put_err_out; + } + /* $MFT must be uncompressed and unencrypted. */ + if (a->flags & ATTR_COMPRESSION_MASK || + a->flags & ATTR_IS_ENCRYPTED || + a->flags & ATTR_IS_SPARSE) { + ntfs_error(sb, + "$MFT must be uncompressed, non-sparse, and unencrypted but a compressed/sparse/encrypted extent was found. $MFT is corrupt. Run chkdsk."); + goto put_err_out; + } + /* + * Decompress the mapping pairs array of this extent and merge + * the result into the existing runlist. No need for locking + * as we have exclusive access to the inode at this time and we + * are a mount in progress task, too. + */ + nrl = ntfs_mapping_pairs_decompress(vol, a, &ni->runlist, + &new_rl_count); + if (IS_ERR(nrl)) { + ntfs_error(sb, + "ntfs_mapping_pairs_decompress() failed with error code %ld.", + PTR_ERR(nrl)); + goto put_err_out; + } + ni->runlist.rl = nrl; + ni->runlist.count = new_rl_count; + + /* Are we in the first extent? */ + if (!next_vcn) { + if (a->data.non_resident.lowest_vcn) { + ntfs_error(sb, + "First extent of $DATA attribute has non zero lowest_vcn. $MFT is corrupt. You should run chkdsk."); + goto put_err_out; + } + /* Get the last vcn in the $DATA attribute. */ + last_vcn = ntfs_bytes_to_cluster(vol, + le64_to_cpu(a->data.non_resident.allocated_size)); + /* Fill in the inode size. */ + vi->i_size = le64_to_cpu(a->data.non_resident.data_size); + ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); + ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); + /* + * Verify the number of mft records does not exceed + * 2^32 - 1. + */ + if ((vi->i_size >> vol->mft_record_size_bits) >= + (1ULL << 32)) { + ntfs_error(sb, "$MFT is too big! Aborting."); + goto put_err_out; + } + /* + * We have got the first extent of the runlist for + * $MFT which means it is now relatively safe to call + * the normal ntfs_read_inode() function. + * Complete reading the inode, this will actually + * re-read the mft record for $MFT, this time entering + * it into the page cache with which we complete the + * kick start of the volume. It should be safe to do + * this now as the first extent of $MFT/$DATA is + * already known and we would hope that we don't need + * further extents in order to find the other + * attributes belonging to $MFT. Only time will tell if + * this is really the case. If not we will have to play + * magic at this point, possibly duplicating a lot of + * ntfs_read_inode() at this point. We will need to + * ensure we do enough of its work to be able to call + * ntfs_read_inode() on extents of $MFT/$DATA. But lets + * hope this never happens... + */ + err = ntfs_read_locked_inode(vi); + if (err) { + ntfs_error(sb, "ntfs_read_inode() of $MFT failed.\n"); + ntfs_attr_put_search_ctx(ctx); + /* Revert to the safe super operations. */ + kfree(m); + return -1; + } + /* + * Re-initialize some specifics about $MFT's inode as + * ntfs_read_inode() will have set up the default ones. + */ + /* Set uid and gid to root. */ + vi->i_uid = GLOBAL_ROOT_UID; + vi->i_gid = GLOBAL_ROOT_GID; + /* Regular file. No access for anyone. */ + vi->i_mode = S_IFREG; + /* No VFS initiated operations allowed for $MFT. */ + vi->i_op = &ntfs_empty_inode_ops; + vi->i_fop = &ntfs_empty_file_ops; + } + + /* Get the lowest vcn for the next extent. */ + highest_vcn = le64_to_cpu(a->data.non_resident.highest_vcn); + next_vcn = highest_vcn + 1; + + /* Only one extent or error, which we catch below. */ + if (next_vcn <= 0) + break; + + /* Avoid endless loops due to corruption. */ + if (next_vcn < le64_to_cpu(a->data.non_resident.lowest_vcn)) { + ntfs_error(sb, "$MFT has corrupt attribute list attribute. Run chkdsk."); + goto put_err_out; + } + } + if (err != -ENOENT) { + ntfs_error(sb, "Failed to lookup $MFT/$DATA attribute extent. Run chkdsk.\n"); + goto put_err_out; + } + if (!a) { + ntfs_error(sb, "$MFT/$DATA attribute not found. $MFT is corrupt. Run chkdsk."); + goto put_err_out; + } + if (highest_vcn && highest_vcn != last_vcn - 1) { + ntfs_error(sb, "Failed to load the complete runlist for $MFT/$DATA. Run chkdsk."); + ntfs_debug("highest_vcn = 0x%llx, last_vcn - 1 = 0x%llx", + (unsigned long long)highest_vcn, + (unsigned long long)last_vcn - 1); + goto put_err_out; + } + ntfs_attr_put_search_ctx(ctx); + ntfs_debug("Done."); + kfree(m); + + /* + * Split the locking rules of the MFT inode from the + * locking rules of other inodes: + */ + lockdep_set_class(&ni->runlist.lock, &mft_ni_runlist_lock_key); + lockdep_set_class(&ni->mrec_lock, &mft_ni_mrec_lock_key); + + return 0; + +em_put_err_out: + ntfs_error(sb, + "Couldn't find first extent of $DATA attribute in attribute list. $MFT is corrupt. Run chkdsk."); +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + ntfs_error(sb, "Failed. Marking inode as bad."); + kfree(m); + return -1; +} + +static void __ntfs_clear_inode(struct ntfs_inode *ni) +{ + /* Free all alocated memory. */ + if (NInoNonResident(ni) && ni->runlist.rl) { + kvfree(ni->runlist.rl); + ni->runlist.rl = NULL; + } + + if (ni->attr_list) { + kvfree(ni->attr_list); + ni->attr_list = NULL; + } + + if (ni->name_len && ni->name != I30 && + ni->name != reparse_index_name && + ni->name != objid_index_name) { + WARN_ON(!ni->name); + kfree(ni->name); + } +} + +void ntfs_clear_extent_inode(struct ntfs_inode *ni) +{ + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + + WARN_ON(NInoAttr(ni)); + WARN_ON(ni->nr_extents != -1); + + __ntfs_clear_inode(ni); + ntfs_destroy_extent_inode(ni); +} + +static int ntfs_delete_base_inode(struct ntfs_inode *ni) +{ + struct super_block *sb = ni->vol->sb; + int err; + + if (NInoAttr(ni) || ni->nr_extents == -1) + return 0; + + err = ntfs_non_resident_dealloc_clusters(ni); + + /* + * Deallocate extent mft records and free extent inodes. + * No need to lock as no one else has a reference. + */ + while (ni->nr_extents) { + err = ntfs_mft_record_free(ni->vol, *(ni->ext.extent_ntfs_inos)); + if (err) + ntfs_error(sb, + "Failed to free extent MFT record. Leaving inconsistent metadata.\n"); + ntfs_inode_close(*(ni->ext.extent_ntfs_inos)); + } + + /* Deallocate base mft record */ + err = ntfs_mft_record_free(ni->vol, ni); + if (err) + ntfs_error(sb, "Failed to free base MFT record. Leaving inconsistent metadata.\n"); + return err; +} + +/* + * ntfs_evict_big_inode - clean up the ntfs specific part of an inode + * @vi: vfs inode pending annihilation + * + * When the VFS is going to remove an inode from memory, ntfs_clear_big_inode() + * is called, which deallocates all memory belonging to the NTFS specific part + * of the inode and returns. + * + * If the MFT record is dirty, we commit it before doing anything else. + */ +void ntfs_evict_big_inode(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + + truncate_inode_pages_final(&vi->i_data); + + if (!vi->i_nlink) { + if (!NInoAttr(ni)) { + /* Never called with extent inodes */ + WARN_ON(ni->nr_extents == -1); + ntfs_delete_base_inode(ni); + } + goto release; + } + + if (NInoDirty(ni)) { + /* Committing the inode also commits all extent inodes. */ + ntfs_commit_inode(vi); + + if (NInoDirty(ni)) { + ntfs_debug("Failed to commit dirty inode 0x%llx. Losing data!", + ni->mft_no); + NInoClearAttrListDirty(ni); + NInoClearDirty(ni); + } + } + + /* No need to lock at this stage as no one else has a reference. */ + if (ni->nr_extents > 0) { + int i; + + for (i = 0; i < ni->nr_extents; i++) { + if (ni->ext.extent_ntfs_inos[i]) + ntfs_clear_extent_inode(ni->ext.extent_ntfs_inos[i]); + } + ni->nr_extents = 0; + kvfree(ni->ext.extent_ntfs_inos); + } + +release: + clear_inode(vi); + __ntfs_clear_inode(ni); + + if (NInoAttr(ni)) { + /* Release the base inode if we are holding it. */ + if (ni->nr_extents == -1) { + iput(VFS_I(ni->ext.base_ntfs_ino)); + ni->nr_extents = 0; + ni->ext.base_ntfs_ino = NULL; + } + } + + if (!atomic_dec_and_test(&ni->count)) + WARN_ON(1); + if (ni->folio) + folio_put(ni->folio); + kfree(ni->mrec); + kvfree(ni->target); +} + +/* + * ntfs_show_options - show mount options in /proc/mounts + * @sf: seq_file in which to write our mount options + * @root: root of the mounted tree whose mount options to display + * + * Called by the VFS once for each mounted ntfs volume when someone reads + * /proc/mounts in order to display the NTFS specific mount options of each + * mount. The mount options of fs specified by @root are written to the seq file + * @sf and success is returned. + */ +int ntfs_show_options(struct seq_file *sf, struct dentry *root) +{ + struct ntfs_volume *vol = NTFS_SB(root->d_sb); + int i; + + if (uid_valid(vol->uid)) + seq_printf(sf, ",uid=%i", from_kuid_munged(&init_user_ns, vol->uid)); + if (gid_valid(vol->gid)) + seq_printf(sf, ",gid=%i", from_kgid_munged(&init_user_ns, vol->gid)); + if (vol->fmask == vol->dmask) + seq_printf(sf, ",umask=0%o", vol->fmask); + else { + seq_printf(sf, ",fmask=0%o", vol->fmask); + seq_printf(sf, ",dmask=0%o", vol->dmask); + } + seq_printf(sf, ",iocharset=%s", vol->nls_map->charset); + if (NVolCaseSensitive(vol)) + seq_puts(sf, ",case_sensitive"); + else + seq_puts(sf, ",nocase"); + if (NVolShowSystemFiles(vol)) + seq_puts(sf, ",show_sys_files,showmeta"); + for (i = 0; on_errors_arr[i].val; i++) { + if (on_errors_arr[i].val == vol->on_errors) + seq_printf(sf, ",errors=%s", on_errors_arr[i].str); + } + seq_printf(sf, ",mft_zone_multiplier=%i", vol->mft_zone_multiplier); + if (NVolSysImmutable(vol)) + seq_puts(sf, ",sys_immutable"); + if (!NVolShowHiddenFiles(vol)) + seq_puts(sf, ",nohidden"); + if (NVolHideDotFiles(vol)) + seq_puts(sf, ",hide_dot_files"); + if (NVolCheckWindowsNames(vol)) + seq_puts(sf, ",windows_names"); + if (NVolDiscard(vol)) + seq_puts(sf, ",discard"); + if (NVolDisableSparse(vol)) + seq_puts(sf, ",disable_sparse"); + if (vol->sb->s_flags & SB_POSIXACL) + seq_puts(sf, ",acl"); + return 0; +} + +int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, + const loff_t new_size, bool bsync) +{ + struct ntfs_inode *ni = NTFS_I(vi); + loff_t old_init_size; + unsigned long flags; + int err; + + read_lock_irqsave(&ni->size_lock, flags); + old_init_size = ni->initialized_size; + read_unlock_irqrestore(&ni->size_lock, flags); + + if (!NInoNonResident(ni)) + return -EINVAL; + if (old_init_size >= new_size) + return 0; + + err = ntfs_attr_map_whole_runlist(ni); + if (err) + return err; + + if (!NInoCompressed(ni) && old_init_size < offset) { + err = iomap_zero_range(vi, old_init_size, + offset - old_init_size, + NULL, &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + if (err) + return err; + if (bsync) + err = filemap_write_and_wait_range(vi->i_mapping, + old_init_size, + offset - 1); + } + + + mutex_lock(&ni->mrec_lock); + err = ntfs_attr_set_initialized_size(ni, new_size); + mutex_unlock(&ni->mrec_lock); + if (err) + truncate_setsize(vi, old_init_size); + return err; +} + +int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size) +{ + struct ntfs_inode *ni = NTFS_I(vi); + int err; + + mutex_lock(&ni->mrec_lock); + err = __ntfs_attr_truncate_vfs(ni, new_size, i_size); + mutex_unlock(&ni->mrec_lock); + if (err < 0) + return err; + + inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); + return 0; +} + +/* + * ntfs_inode_sync_standard_information - update standard information attribute + * @vi: inode to update standard information + * @m: mft record + * + * Return 0 on success or -errno on error. + */ +static int ntfs_inode_sync_standard_information(struct inode *vi, struct mft_record *m) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_attr_search_ctx *ctx; + struct standard_information *si; + __le64 nt; + int err = 0; + bool modified = false; + + /* Update the access times in the standard information attribute. */ + ctx = ntfs_attr_get_search_ctx(ni, m); + if (unlikely(!ctx)) + return -ENOMEM; + err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + ntfs_attr_put_search_ctx(ctx); + return err; + } + si = (struct standard_information *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + if (si->file_attributes != ni->flags) { + si->file_attributes = ni->flags; + modified = true; + } + + /* Update the creation times if they have changed. */ + nt = utc2ntfs(ni->i_crtime); + if (si->creation_time != nt) { + ntfs_debug("Updating creation time for inode 0x%llx: old = 0x%llx, new = 0x%llx", + ni->mft_no, le64_to_cpu(si->creation_time), + le64_to_cpu(nt)); + si->creation_time = nt; + modified = true; + } + + /* Update the access times if they have changed. */ + nt = utc2ntfs(inode_get_mtime(vi)); + if (si->last_data_change_time != nt) { + ntfs_debug("Updating mtime for inode 0x%llx: old = 0x%llx, new = 0x%llx", + ni->mft_no, le64_to_cpu(si->last_data_change_time), + le64_to_cpu(nt)); + si->last_data_change_time = nt; + modified = true; + } + + nt = utc2ntfs(inode_get_ctime(vi)); + if (si->last_mft_change_time != nt) { + ntfs_debug("Updating ctime for inode 0x%llx: old = 0x%llx, new = 0x%llx", + ni->mft_no, le64_to_cpu(si->last_mft_change_time), + le64_to_cpu(nt)); + si->last_mft_change_time = nt; + modified = true; + } + nt = utc2ntfs(inode_get_atime(vi)); + if (si->last_access_time != nt) { + ntfs_debug("Updating atime for inode 0x%llx: old = 0x%llx, new = 0x%llx", + ni->mft_no, + le64_to_cpu(si->last_access_time), + le64_to_cpu(nt)); + si->last_access_time = nt; + modified = true; + } + + /* + * If we just modified the standard information attribute we need to + * mark the mft record it is in dirty. We do this manually so that + * mark_inode_dirty() is not called which would redirty the inode and + * hence result in an infinite loop of trying to write the inode. + * There is no need to mark the base inode nor the base mft record + * dirty, since we are going to write this mft record below in any case + * and the base mft record may actually not have been modified so it + * might not need to be written out. + * NOTE: It is not a problem when the inode for $MFT itself is being + * written out as ntfs_mft_mark_dirty() will only set I_DIRTY_PAGES + * on the $MFT inode and hence ntfs_write_inode() will not be + * re-invoked because of it which in turn is ok since the dirtied mft + * record will be cleaned and written out to disk below, i.e. before + * this function returns. + */ + if (modified) + NInoSetDirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + + return err; +} + +/* + * ntfs_inode_sync_filename - update FILE_NAME attributes + * @ni: ntfs inode to update FILE_NAME attributes + * + * Update all FILE_NAME attributes for inode @ni in the index. + * + * Return 0 on success or error. + */ +int ntfs_inode_sync_filename(struct ntfs_inode *ni) +{ + struct inode *index_vi; + struct super_block *sb = VFS_I(ni)->i_sb; + struct ntfs_attr_search_ctx *ctx = NULL; + struct ntfs_index_context *ictx; + struct ntfs_inode *index_ni; + struct file_name_attr *fn; + struct file_name_attr *fnx; + struct reparse_point *rpp; + __le32 reparse_tag; + int err = 0; + unsigned long flags; + + ntfs_debug("Entering for inode %llu\n", ni->mft_no); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -ENOMEM; + + /* Collect the reparse tag, if any */ + reparse_tag = cpu_to_le32(0); + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + if (!ntfs_attr_lookup(AT_REPARSE_POINT, NULL, + 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + rpp = (struct reparse_point *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + reparse_tag = rpp->reparse_tag; + } + ntfs_attr_reinit_search_ctx(ctx); + } + + /* Walk through all FILE_NAME attributes and update them. */ + while (!(err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, ctx))) { + fn = (struct file_name_attr *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + if (MREF_LE(fn->parent_directory) == ni->mft_no) + continue; + + index_vi = ntfs_iget(sb, MREF_LE(fn->parent_directory)); + if (IS_ERR(index_vi)) { + ntfs_error(sb, "Failed to open inode %lld with index", + (long long)MREF_LE(fn->parent_directory)); + continue; + } + + index_ni = NTFS_I(index_vi); + + mutex_lock_nested(&index_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + if (NInoBeingDeleted(ni)) { + iput(index_vi); + mutex_unlock(&index_ni->mrec_lock); + continue; + } + + ictx = ntfs_index_ctx_get(index_ni, I30, 4); + if (!ictx) { + ntfs_error(sb, "Failed to get index ctx, inode %llu", + index_ni->mft_no); + iput(index_vi); + mutex_unlock(&index_ni->mrec_lock); + continue; + } + + err = ntfs_index_lookup(fn, sizeof(struct file_name_attr), ictx); + if (err) { + ntfs_debug("Index lookup failed, inode %llu", + index_ni->mft_no); + ntfs_index_ctx_put(ictx); + iput(index_vi); + mutex_unlock(&index_ni->mrec_lock); + continue; + } + /* Update flags and file size. */ + fnx = (struct file_name_attr *)ictx->data; + fnx->file_attributes = + (fnx->file_attributes & ~FILE_ATTR_VALID_FLAGS) | + (ni->flags & FILE_ATTR_VALID_FLAGS); + if (ctx->mrec->flags & MFT_RECORD_IS_DIRECTORY) + fnx->data_size = fnx->allocated_size = 0; + else { + read_lock_irqsave(&ni->size_lock, flags); + if (NInoSparse(ni) || NInoCompressed(ni)) + fnx->allocated_size = cpu_to_le64(ni->itype.compressed.size); + else + fnx->allocated_size = cpu_to_le64(ni->allocated_size); + fnx->data_size = cpu_to_le64(ni->data_size); + + /* + * The file name record has also to be fixed if some + * attribute update implied the unnamed data to be + * made non-resident + */ + fn->allocated_size = fnx->allocated_size; + fn->data_size = fnx->data_size; + read_unlock_irqrestore(&ni->size_lock, flags); + } + + /* update or clear the reparse tag in the index */ + fnx->type.rp.reparse_point_tag = reparse_tag; + fnx->creation_time = fn->creation_time; + fnx->last_data_change_time = fn->last_data_change_time; + fnx->last_mft_change_time = fn->last_mft_change_time; + fnx->last_access_time = fn->last_access_time; + ntfs_index_entry_mark_dirty(ictx); + ntfs_icx_ib_sync_write(ictx); + NInoSetDirty(ctx->ntfs_ino); + ntfs_index_ctx_put(ictx); + mutex_unlock(&index_ni->mrec_lock); + iput(index_vi); + } + /* Check for real error occurred. */ + if (err != -ENOENT) { + ntfs_error(sb, "Attribute lookup failed, err : %d, inode %llu", err, + ni->mft_no); + } else + err = 0; + + ntfs_attr_put_search_ctx(ctx); + return err; +} + +int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni) +{ + s64 vcn; + struct runlist_element *rl; + + if (ni->mft_lcn[0] != LCN_RL_NOT_MAPPED) + return 0; + + vcn = (s64)ni->mft_no << mft_ni->vol->mft_record_size_bits >> + mft_ni->vol->cluster_size_bits; + + rl = mft_ni->runlist.rl; + if (!rl) { + ntfs_error(mft_ni->vol->sb, "$MFT runlist is not present"); + return -EIO; + } + + /* Seek to element containing target vcn. */ + while (rl->length && rl[1].vcn <= vcn) + rl++; + ni->mft_lcn[0] = ntfs_rl_vcn_to_lcn(rl, vcn); + ni->mft_lcn_count = 1; + + if (mft_ni->vol->cluster_size < mft_ni->vol->mft_record_size && + (rl->length - (vcn - rl->vcn)) <= 1) { + rl++; + ni->mft_lcn[1] = ntfs_rl_vcn_to_lcn(rl, vcn + 1); + ni->mft_lcn_count++; + } + return 0; +} + +/* + * __ntfs_write_inode - write out a dirty inode + * @vi: inode to write out + * @sync: if true, write out synchronously + * + * Write out a dirty inode to disk including any extent inodes if present. + * + * If @sync is true, commit the inode to disk and wait for io completion. This + * is done using write_mft_record(). + * + * If @sync is false, just schedule the write to happen but do not wait for i/o + * completion. + * + * Return 0 on success and -errno on error. + */ +int __ntfs_write_inode(struct inode *vi, int sync) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *mft_ni = NTFS_I(ni->vol->mft_ino); + struct mft_record *m; + int err = 0; + bool need_iput = false; + + ntfs_debug("Entering for %sinode 0x%llx.", NInoAttr(ni) ? "attr " : "", + ni->mft_no); + + if (NVolShutdown(ni->vol)) + return -EIO; + + /* + * Dirty attribute inodes are written via their real inodes so just + * clean them here. Access time updates are taken care off when the + * real inode is written. + */ + if (NInoAttr(ni) || ni->nr_extents == -1) { + NInoClearDirty(ni); + ntfs_debug("Done."); + return 0; + } + + /* igrab prevents vi from being evicted while mrec_lock is hold. */ + if (igrab(vi) != NULL) + need_iput = true; + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + /* Map, pin, and lock the mft record belonging to the inode. */ + m = map_mft_record(ni); + if (IS_ERR(m)) { + mutex_unlock(&ni->mrec_lock); + err = PTR_ERR(m); + goto err_out; + } + + if (NInoNonResident(ni) && NInoRunlistDirty(ni)) { + down_write(&ni->runlist.lock); + err = ntfs_attr_update_mapping_pairs(ni, 0); + if (!err) + NInoClearRunlistDirty(ni); + up_write(&ni->runlist.lock); + } + + err = ntfs_inode_sync_standard_information(vi, m); + if (err) + goto unm_err_out; + + /* + * when being umounted and inodes are evicted, write_inode() + * is called with all inodes being marked with I_FREEING. + * then ntfs_inode_sync_filename() waits infinitly because + * of ntfs_iget. This situation happens only where sync_filesysem() + * from umount fails because of a disk unplug and etc. + * the absent of SB_ACTIVE means umounting. + */ + if ((vi->i_sb->s_flags & SB_ACTIVE) && NInoTestClearFileNameDirty(ni)) + ntfs_inode_sync_filename(ni); + + /* Now the access times are updated, write the base mft record. */ + if (NInoDirty(ni)) { + down_read(&mft_ni->runlist.lock); + err = ntfs_get_block_mft_record(mft_ni, ni); + up_read(&mft_ni->runlist.lock); + if (err) + goto unm_err_out; + + err = write_mft_record(ni, m, sync); + if (err) + ntfs_error(vi->i_sb, "write_mft_record failed, err : %d\n", err); + } + unmap_mft_record(ni); + + /* Map any unmapped extent mft records with LCNs. */ + down_read(&mft_ni->runlist.lock); + mutex_lock(&ni->extent_lock); + if (ni->nr_extents > 0) { + int i; + + for (i = 0; i < ni->nr_extents; i++) { + err = ntfs_get_block_mft_record(mft_ni, + ni->ext.extent_ntfs_inos[i]); + if (err) { + mutex_unlock(&ni->extent_lock); + up_read(&mft_ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + goto err_out; + } + } + } + mutex_unlock(&ni->extent_lock); + up_read(&mft_ni->runlist.lock); + + /* Write all attached extent mft records. */ + mutex_lock(&ni->extent_lock); + if (ni->nr_extents > 0) { + struct ntfs_inode **extent_nis = ni->ext.extent_ntfs_inos; + int i; + + ntfs_debug("Writing %i extent inodes.", ni->nr_extents); + for (i = 0; i < ni->nr_extents; i++) { + struct ntfs_inode *tni = extent_nis[i]; + + if (NInoDirty(tni)) { + struct mft_record *tm; + int ret; + + mutex_lock(&tni->mrec_lock); + tm = map_mft_record(tni); + if (IS_ERR(tm)) { + mutex_unlock(&tni->mrec_lock); + if (!err || err == -ENOMEM) + err = PTR_ERR(tm); + continue; + } + + ret = write_mft_record(tni, tm, sync); + unmap_mft_record(tni); + mutex_unlock(&tni->mrec_lock); + + if (unlikely(ret)) { + if (!err || err == -ENOMEM) + err = ret; + } + } + } + } + mutex_unlock(&ni->extent_lock); + mutex_unlock(&ni->mrec_lock); + + if (unlikely(err)) + goto err_out; + if (need_iput) + iput(vi); + ntfs_debug("Done."); + return 0; +unm_err_out: + unmap_mft_record(ni); + mutex_unlock(&ni->mrec_lock); +err_out: + if (err == -ENOMEM) + mark_inode_dirty(vi); + else { + ntfs_error(vi->i_sb, "Failed (error %i): Run chkdsk.", -err); + NVolSetErrors(ni->vol); + } + if (need_iput) + iput(vi); + return err; +} + +/* + * ntfs_extent_inode_open - load an extent inode and attach it to its base + * @base_ni: base ntfs inode + * @mref: mft reference of the extent inode to load (in little endian) + * + * First check if the extent inode @mref is already attached to the base ntfs + * inode @base_ni, and if so, return a pointer to the attached extent inode. + * + * If the extent inode is not already attached to the base inode, allocate an + * ntfs_inode structure and initialize it for the given inode @mref. @mref + * specifies the inode number / mft record to read, including the sequence + * number, which can be 0 if no sequence number checking is to be performed. + * + * Then, allocate a buffer for the mft record, read the mft record from the + * volume @base_ni->vol, and attach it to the ntfs_inode structure (->mrec). + * The mft record is mst deprotected and sanity checked for validity and we + * abort if deprotection or checks fail. + * + * Finally attach the ntfs inode to its base inode @base_ni and return a + * pointer to the ntfs_inode structure on success or NULL on error, with errno + * set to the error code. + * + * Note, extent inodes are never closed directly. They are automatically + * disposed off by the closing of the base inode. + */ +static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, + const __le64 mref) +{ + u64 mft_no = MREF_LE(mref); + struct ntfs_inode *ni = NULL; + struct ntfs_inode **extent_nis; + int i; + struct mft_record *ni_mrec; + struct super_block *sb; + + if (!base_ni) + return NULL; + + sb = base_ni->vol->sb; + ntfs_debug("Opening extent inode %llu (base mft record %llu).\n", + mft_no, base_ni->mft_no); + + /* Is the extent inode already open and attached to the base inode? */ + if (base_ni->nr_extents > 0) { + extent_nis = base_ni->ext.extent_ntfs_inos; + for (i = 0; i < base_ni->nr_extents; i++) { + u16 seq_no; + + ni = extent_nis[i]; + if (mft_no != ni->mft_no) + continue; + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + ntfs_error(sb, "failed to map mft record for %llu", + ni->mft_no); + goto out; + } + /* Verify the sequence number if given. */ + seq_no = MSEQNO_LE(mref); + if (seq_no && + seq_no != le16_to_cpu(ni_mrec->sequence_number)) { + ntfs_error(sb, "Found stale extent mft reference mft=%llu", + ni->mft_no); + unmap_mft_record(ni); + goto out; + } + unmap_mft_record(ni); + goto out; + } + } + /* Wasn't there, we need to load the extent inode. */ + ni = ntfs_new_extent_inode(base_ni->vol->sb, mft_no); + if (!ni) + goto out; + + ni->seq_no = (u16)MSEQNO_LE(mref); + ni->nr_extents = -1; + ni->ext.base_ntfs_ino = base_ni; + /* Attach extent inode to base inode, reallocating memory if needed. */ + if (!(base_ni->nr_extents & 3)) { + i = (base_ni->nr_extents + 4) * sizeof(struct ntfs_inode *); + + extent_nis = kvzalloc(i, GFP_NOFS); + if (!extent_nis) + goto err_out; + if (base_ni->nr_extents) { + memcpy(extent_nis, base_ni->ext.extent_ntfs_inos, + i - 4 * sizeof(struct ntfs_inode *)); + kvfree(base_ni->ext.extent_ntfs_inos); + } + base_ni->ext.extent_ntfs_inos = extent_nis; + } + base_ni->ext.extent_ntfs_inos[base_ni->nr_extents++] = ni; + +out: + ntfs_debug("\n"); + return ni; +err_out: + ntfs_destroy_ext_inode(ni); + ni = NULL; + goto out; +} + +/* + * ntfs_inode_attach_all_extents - attach all extents for target inode + * @ni: opened ntfs inode for which perform attach + * + * Return 0 on success and error. + */ +int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) +{ + struct attr_list_entry *ale; + u64 prev_attached = 0; + + if (!ni) { + ntfs_debug("Invalid arguments.\n"); + return -EINVAL; + } + + if (NInoAttr(ni)) + ni = ni->ext.base_ntfs_ino; + + ntfs_debug("Entering for inode 0x%llx.\n", ni->mft_no); + + /* Inode haven't got attribute list, thus nothing to attach. */ + if (!NInoAttrList(ni)) + return 0; + + if (!ni->attr_list) { + ntfs_debug("Corrupt in-memory struct.\n"); + return -EINVAL; + } + + /* Walk through attribute list and attach all extents. */ + ale = (struct attr_list_entry *)ni->attr_list; + while ((u8 *)ale < ni->attr_list + ni->attr_list_size) { + if (ni->mft_no != MREF_LE(ale->mft_reference) && + prev_attached != MREF_LE(ale->mft_reference)) { + if (!ntfs_extent_inode_open(ni, ale->mft_reference)) { + ntfs_debug("Couldn't attach extent inode.\n"); + return -1; + } + prev_attached = MREF_LE(ale->mft_reference); + } + ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); + } + return 0; +} + +/* + * ntfs_inode_add_attrlist - add attribute list to inode and fill it + * @ni: opened ntfs inode to which add attribute list + * + * Return 0 on success or error. + */ +int ntfs_inode_add_attrlist(struct ntfs_inode *ni) +{ + int err; + struct ntfs_attr_search_ctx *ctx; + u8 *al = NULL, *aln; + int al_len = 0; + struct attr_list_entry *ale = NULL; + struct mft_record *ni_mrec; + u32 attr_al_len; + + if (!ni) + return -EINVAL; + + ntfs_debug("inode %llu\n", ni->mft_no); + + if (NInoAttrList(ni) || ni->nr_extents) { + ntfs_error(ni->vol->sb, "Inode already has attribute list"); + return -EEXIST; + } + + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) + return -EIO; + + /* Form attribute list. */ + ctx = ntfs_attr_get_search_ctx(ni, ni_mrec); + if (!ctx) { + err = -ENOMEM; + goto err_out; + } + + /* Walk through all attributes. */ + while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) { + int ale_size; + + if (ctx->attr->type == AT_ATTRIBUTE_LIST) { + err = -EIO; + ntfs_error(ni->vol->sb, "Attribute list already present"); + goto put_err_out; + } + + ale_size = (sizeof(struct attr_list_entry) + sizeof(__le16) * + ctx->attr->name_length + 7) & ~7; + al_len += ale_size; + + aln = kvrealloc(al, al_len, GFP_NOFS); + if (!aln) { + err = -ENOMEM; + ntfs_error(ni->vol->sb, "Failed to realloc %d bytes", al_len); + goto put_err_out; + } + ale = (struct attr_list_entry *)(aln + ((u8 *)ale - al)); + al = aln; + + memset(ale, 0, ale_size); + + /* Add attribute to attribute list. */ + ale->type = ctx->attr->type; + ale->length = cpu_to_le16((sizeof(struct attr_list_entry) + + sizeof(__le16) * ctx->attr->name_length + 7) & ~7); + ale->name_length = ctx->attr->name_length; + ale->name_offset = (u8 *)ale->name - (u8 *)ale; + if (ctx->attr->non_resident) + ale->lowest_vcn = + ctx->attr->data.non_resident.lowest_vcn; + else + ale->lowest_vcn = 0; + ale->mft_reference = MK_LE_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number)); + ale->instance = ctx->attr->instance; + memcpy(ale->name, (u8 *)ctx->attr + + le16_to_cpu(ctx->attr->name_offset), + ctx->attr->name_length * sizeof(__le16)); + ale = (struct attr_list_entry *)(al + al_len); + } + + /* Check for real error occurred. */ + if (err != -ENOENT) { + ntfs_error(ni->vol->sb, "%s: Attribute lookup failed, inode %llu", + __func__, ni->mft_no); + goto put_err_out; + } + + /* Set in-memory attribute list. */ + ni->attr_list = al; + ni->attr_list_size = al_len; + NInoSetAttrList(ni); + + attr_al_len = offsetof(struct attr_record, data.resident.reserved) + 1 + + ((al_len + 7) & ~7); + /* Free space if there is not enough it for $ATTRIBUTE_LIST. */ + if (le32_to_cpu(ni_mrec->bytes_allocated) - + le32_to_cpu(ni_mrec->bytes_in_use) < attr_al_len) { + if (ntfs_inode_free_space(ni, (int)attr_al_len)) { + /* Failed to free space. */ + err = -ENOSPC; + ntfs_error(ni->vol->sb, "Failed to free space for attrlist"); + goto rollback; + } + } + + /* Add $ATTRIBUTE_LIST to mft record. */ + err = ntfs_resident_attr_record_add(ni, AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, + NULL, al_len, 0); + if (err < 0) { + ntfs_error(ni->vol->sb, "Couldn't add $ATTRIBUTE_LIST to MFT"); + goto rollback; + } + + err = ntfs_attrlist_update(ni); + if (err < 0) + goto remove_attrlist_record; + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + return 0; + +remove_attrlist_record: + /* Prevent ntfs_attr_recorm_rm from freeing attribute list. */ + ni->attr_list = NULL; + NInoClearAttrList(ni); + /* Remove $ATTRIBUTE_LIST record. */ + ntfs_attr_reinit_search_ctx(ctx); + if (!ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + if (ntfs_attr_record_rm(ctx)) + ntfs_error(ni->vol->sb, "Rollback failed to remove attrlist"); + } else { + ntfs_error(ni->vol->sb, "Rollback failed to find attrlist"); + } + + /* Setup back in-memory runlist. */ + ni->attr_list = al; + ni->attr_list_size = al_len; + NInoSetAttrList(ni); +rollback: + /* + * Scan attribute list for attributes that placed not in the base MFT + * record and move them to it. + */ + ntfs_attr_reinit_search_ctx(ctx); + ale = (struct attr_list_entry *)al; + while ((u8 *)ale < al + al_len) { + if (MREF_LE(ale->mft_reference) != ni->mft_no) { + if (!ntfs_attr_lookup(ale->type, ale->name, + ale->name_length, + CASE_SENSITIVE, + le64_to_cpu(ale->lowest_vcn), + NULL, 0, ctx)) { + if (ntfs_attr_record_move_to(ctx, ni)) + ntfs_error(ni->vol->sb, + "Rollback failed to move attribute"); + } else { + ntfs_error(ni->vol->sb, "Rollback failed to find attr"); + } + ntfs_attr_reinit_search_ctx(ctx); + } + ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); + } + + /* Remove in-memory attribute list. */ + ni->attr_list = NULL; + ni->attr_list_size = 0; + NInoClearAttrList(ni); + NInoClearAttrListDirty(ni); +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + kvfree(al); + unmap_mft_record(ni); + return err; +} + +/* + * ntfs_inode_close - close an ntfs inode and free all associated memory + * @ni: ntfs inode to close + * + * Make sure the ntfs inode @ni is clean. + * + * If the ntfs inode @ni is a base inode, close all associated extent inodes, + * then deallocate all memory attached to it, and finally free the ntfs inode + * structure itself. + * + * If it is an extent inode, we disconnect it from its base inode before we + * destroy it. + * + * It is OK to pass NULL to this function, it is just noop in this case. + * + * Return 0 on success or error. + */ +int ntfs_inode_close(struct ntfs_inode *ni) +{ + int err = -1; + struct ntfs_inode **tmp_nis; + struct ntfs_inode *base_ni; + s32 i; + + if (!ni) + return 0; + + ntfs_debug("Entering for inode %llu\n", ni->mft_no); + + /* Is this a base inode with mapped extent inodes? */ + /* + * If the inode is an extent inode, disconnect it from the + * base inode before destroying it. + */ + base_ni = ni->ext.base_ntfs_ino; + tmp_nis = base_ni->ext.extent_ntfs_inos; + if (!tmp_nis) + goto out; + for (i = 0; i < base_ni->nr_extents; ++i) { + if (tmp_nis[i] != ni) + continue; + /* Found it. Disconnect. */ + memmove(tmp_nis + i, tmp_nis + i + 1, + (base_ni->nr_extents - i - 1) * + sizeof(struct ntfs_inode *)); + /* Buffer should be for multiple of four extents. */ + if ((--base_ni->nr_extents) & 3) + break; + /* + * ElectricFence is unhappy with realloc(x,0) as free(x) + * thus we explicitly separate these two cases. + */ + if (base_ni->nr_extents) { + /* Resize the memory buffer. */ + tmp_nis = kvrealloc(tmp_nis, base_ni->nr_extents * + sizeof(struct ntfs_inode *), GFP_NOFS); + /* Ignore errors, they don't really matter. */ + if (tmp_nis) + base_ni->ext.extent_ntfs_inos = tmp_nis; + } else if (tmp_nis) { + kvfree(tmp_nis); + base_ni->ext.extent_ntfs_inos = NULL; + } + break; + } + +out: + if (NInoDirty(ni)) + ntfs_error(ni->vol->sb, "Releasing dirty inode %llu!\n", + ni->mft_no); + if (NInoAttrList(ni) && ni->attr_list) + kvfree(ni->attr_list); + ntfs_destroy_ext_inode(ni); + err = 0; + ntfs_debug("\n"); + return err; +} + +void ntfs_destroy_ext_inode(struct ntfs_inode *ni) +{ + ntfs_debug("Entering."); + if (ni == NULL) + return; + + ntfs_attr_close(ni); + + if (NInoDirty(ni)) + ntfs_error(ni->vol->sb, "Releasing dirty ext inode %llu!\n", + ni->mft_no); + if (NInoAttrList(ni) && ni->attr_list) + kvfree(ni->attr_list); + kfree(ni->mrec); + kmem_cache_free(ntfs_inode_cache, ni); +} + +static struct ntfs_inode *ntfs_inode_base(struct ntfs_inode *ni) +{ + if (ni->nr_extents == -1) + return ni->ext.base_ntfs_ino; + return ni; +} + +static int ntfs_attr_position(__le32 type, struct ntfs_attr_search_ctx *ctx) +{ + int err; + + err = ntfs_attr_lookup(type, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (err) { + __le32 atype; + + if (err != -ENOENT) + return err; + + atype = ctx->attr->type; + if (atype == AT_END) + return -ENOSPC; + + /* + * if ntfs_external_attr_lookup return -ENOENT, ctx->al_entry + * could point to an attribute in an extent mft record, but + * ctx->attr and ctx->ntfs_ino always points to an attibute in + * a base mft record. + */ + if (ctx->al_entry && + MREF_LE(ctx->al_entry->mft_reference) != ctx->ntfs_ino->mft_no) { + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(atype, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (err) + return err; + } + } + return 0; +} + +/* + * ntfs_inode_free_space - free space in the MFT record of inode + * @ni: ntfs inode in which MFT record free space + * @size: amount of space needed to free + * + * Return 0 on success or error. + */ +int ntfs_inode_free_space(struct ntfs_inode *ni, int size) +{ + struct ntfs_attr_search_ctx *ctx; + int freed, err; + struct mft_record *ni_mrec; + struct super_block *sb; + + if (!ni || size < 0) + return -EINVAL; + ntfs_debug("Entering for inode %llu, size %d\n", ni->mft_no, size); + + sb = ni->vol->sb; + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) + return -EIO; + + freed = (le32_to_cpu(ni_mrec->bytes_allocated) - + le32_to_cpu(ni_mrec->bytes_in_use)); + + unmap_mft_record(ni); + + if (size <= freed) + return 0; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s, Failed to get search context", __func__); + return -ENOMEM; + } + + /* + * Chkdsk complain if $STANDARD_INFORMATION is not in the base MFT + * record. + * + * Also we can't move $ATTRIBUTE_LIST from base MFT_RECORD, so position + * search context on first attribute after $STANDARD_INFORMATION and + * $ATTRIBUTE_LIST. + * + * Why we reposition instead of simply skip this attributes during + * enumeration? Because in case we have got only in-memory attribute + * list ntfs_attr_lookup will fail when it will try to find + * $ATTRIBUTE_LIST. + */ + err = ntfs_attr_position(AT_FILE_NAME, ctx); + if (err) + goto put_err_out; + + while (1) { + int record_size; + + /* + * Check whether attribute is from different MFT record. If so, + * find next, because we don't need such. + */ + while (ctx->ntfs_ino->mft_no != ni->mft_no) { +retry: + err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (err) { + if (err != -ENOENT) + ntfs_error(sb, "Attr lookup failed #2"); + else if (ctx->attr->type == AT_END) + err = -ENOSPC; + else + err = 0; + + if (err) + goto put_err_out; + } + } + + if (ntfs_inode_base(ctx->ntfs_ino)->mft_no == FILE_MFT && + ctx->attr->type == AT_DATA) + goto retry; + + if (ctx->attr->type == AT_INDEX_ROOT) + goto retry; + + record_size = le32_to_cpu(ctx->attr->length); + + /* Move away attribute. */ + err = ntfs_attr_record_move_away(ctx, 0); + if (err) { + ntfs_error(sb, "Failed to move out attribute #2"); + break; + } + freed += record_size; + + /* Check whether we done. */ + if (size <= freed) { + ntfs_attr_put_search_ctx(ctx); + return 0; + } + + /* + * Reposition to first attribute after $STANDARD_INFORMATION and + * $ATTRIBUTE_LIST (see comments upwards). + */ + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_position(AT_FILE_NAME, ctx); + if (err) + break; + } +put_err_out: + ntfs_attr_put_search_ctx(ctx); + if (err == -ENOSPC) + ntfs_debug("No attributes left that can be moved out.\n"); + return err; +} + +s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf) +{ + struct address_space *mapping = vi->i_mapping; + struct folio *folio; + struct ntfs_inode *ni = NTFS_I(vi); + s64 isize; + u32 attr_len, total = 0, offset; + pgoff_t index; + int err = 0; + + WARN_ON(!NInoAttr(ni)); + if (!count) + return 0; + + mutex_lock(&ni->mrec_lock); + isize = i_size_read(vi); + if (pos > isize) { + mutex_unlock(&ni->mrec_lock); + return -EINVAL; + } + if (pos + count > isize) + count = isize - pos; + + if (!NInoNonResident(ni)) { + struct ntfs_attr_search_ctx *ctx; + u8 *attr; + + ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + if (!ctx) { + ntfs_error(vi->i_sb, "Failed to get attr search ctx"); + err = -ENOMEM; + mutex_unlock(&ni->mrec_lock); + goto out; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (err) { + ntfs_error(vi->i_sb, "Failed to look up attr %#x", ni->type); + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&ni->mrec_lock); + goto out; + } + + attr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); + memcpy(buf, (u8 *)attr + pos, count); + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&ni->mrec_lock); + return count; + } + mutex_unlock(&ni->mrec_lock); + + index = pos >> PAGE_SHIFT; + do { + /* Update @index and get the next folio. */ + folio = read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) + break; + + offset = offset_in_folio(folio, pos); + attr_len = min_t(size_t, (size_t)count, folio_size(folio) - offset); + + folio_lock(folio); + memcpy_from_folio(buf, folio, offset, attr_len); + folio_unlock(folio); + folio_put(folio); + + total += attr_len; + buf += attr_len; + pos += attr_len; + count -= attr_len; + index++; + } while (count); +out: + return err ? (s64)err : total; +} + +static inline int ntfs_enlarge_attribute(struct inode *vi, s64 pos, s64 count, + struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct super_block *sb = vi->i_sb; + int ret; + + if (pos + count <= ni->initialized_size) + return 0; + + if (NInoEncrypted(ni) && NInoNonResident(ni)) + return -EACCES; + + if (NInoCompressed(ni)) + return -EOPNOTSUPP; + + if (pos + count > ni->data_size) { + if (ntfs_attr_truncate(ni, pos + count)) { + ntfs_debug("Failed to truncate attribute"); + return -1; + } + + ntfs_attr_reinit_search_ctx(ctx); + ret = ntfs_attr_lookup(ni->type, + ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (ret) { + ntfs_error(sb, "Failed to look up attr %#x", ni->type); + return ret; + } + } + + if (!NInoNonResident(ni)) { + if (likely(i_size_read(vi) < ni->data_size)) + i_size_write(vi, ni->data_size); + return 0; + } + + if (pos + count > ni->initialized_size) { + ctx->attr->data.non_resident.initialized_size = cpu_to_le64(pos + count); + mark_mft_record_dirty(ctx->ntfs_ino); + ni->initialized_size = pos + count; + if (i_size_read(vi) < ni->initialized_size) + i_size_write(vi, ni->initialized_size); + } + return 0; +} + +static s64 __ntfs_inode_resident_attr_pwrite(struct inode *vi, + s64 pos, s64 count, u8 *buf, + struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct folio *folio; + struct address_space *mapping = vi->i_mapping; + u8 *addr; + int err = 0; + + WARN_ON(NInoNonResident(ni)); + if (pos + count > PAGE_SIZE) { + ntfs_error(vi->i_sb, "Out of write into resident attr %#x", ni->type); + return -EINVAL; + } + + /* Copy to mft record page */ + addr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); + memcpy(addr + pos, buf, count); + mark_mft_record_dirty(ctx->ntfs_ino); + + /* Keep the first page clean and uptodate */ + folio = __filemap_get_folio(mapping, 0, FGP_WRITEBEGIN | FGP_NOFS, + mapping_gfp_mask(mapping)); + if (IS_ERR(folio)) { + err = PTR_ERR(folio); + ntfs_error(vi->i_sb, "Failed to read a page 0 for attr %#x: %d", + ni->type, err); + goto out; + } + if (!folio_test_uptodate(folio)) + folio_fill_tail(folio, 0, addr, + le32_to_cpu(ctx->attr->data.resident.value_length)); + else + memcpy_to_folio(folio, offset_in_folio(folio, pos), buf, count); + folio_mark_uptodate(folio); + folio_unlock(folio); + folio_put(folio); +out: + return err ? err : count; +} + +static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, + s64 pos, s64 count, u8 *buf, + struct ntfs_attr_search_ctx *ctx, + bool sync) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct address_space *mapping = vi->i_mapping; + struct folio *folio; + pgoff_t index; + unsigned long offset, length; + size_t attr_len; + s64 ret = 0, written = 0; + + WARN_ON(!NInoNonResident(ni)); + + index = pos >> PAGE_SHIFT; + while (count) { + if (count == PAGE_SIZE) { + folio = __filemap_get_folio(vi->i_mapping, index, + FGP_CREAT | FGP_LOCK, + mapping_gfp_mask(mapping)); + if (IS_ERR(folio)) { + ret = -ENOMEM; + break; + } + } else { + folio = read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + ntfs_error(vi->i_sb, "Failed to read a page %lu for attr %#x: %ld", + index, ni->type, PTR_ERR(folio)); + break; + } + + folio_lock(folio); + } + + if (count == PAGE_SIZE) { + offset = 0; + attr_len = count; + } else { + offset = offset_in_folio(folio, pos); + attr_len = min_t(size_t, (size_t)count, folio_size(folio) - offset); + } + memcpy_to_folio(folio, offset, buf, attr_len); + + if (sync) { + struct ntfs_volume *vol = ni->vol; + s64 lcn, lcn_count; + unsigned int lcn_folio_off = 0; + struct bio *bio; + u64 rl_length = 0; + s64 vcn; + struct runlist_element *rl; + + lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len)); + vcn = ntfs_pidx_to_cluster(vol, folio->index); + + do { + down_write(&ni->runlist.lock); + rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn); + if (IS_ERR(rl)) { + ret = PTR_ERR(rl); + up_write(&ni->runlist.lock); + goto err_unlock_folio; + } + + rl_length = rl->length - (vcn - rl->vcn); + if (rl_length < lcn_count) { + lcn_count -= rl_length; + } else { + rl_length = lcn_count; + lcn_count = 0; + } + up_write(&ni->runlist.lock); + + if (vol->cluster_size_bits > PAGE_SHIFT) { + lcn_folio_off = folio->index << PAGE_SHIFT; + lcn_folio_off &= vol->cluster_size_mask; + } + + bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, + GFP_NOIO); + bio->bi_iter.bi_sector = + ntfs_bytes_to_sector(vol, + ntfs_cluster_to_bytes(vol, lcn) + + lcn_folio_off); + + length = min_t(unsigned long, + ntfs_cluster_to_bytes(vol, rl_length), + folio_size(folio)); + if (!bio_add_folio(bio, folio, length, offset)) { + ret = -EIO; + bio_put(bio); + goto err_unlock_folio; + } + + submit_bio_wait(bio); + bio_put(bio); + vcn += rl_length; + offset += length; + } while (lcn_count != 0); + + folio_mark_uptodate(folio); + } else { + folio_mark_uptodate(folio); + folio_mark_dirty(folio); + } +err_unlock_folio: + folio_unlock(folio); + folio_put(folio); + + if (ret) + break; + + written += attr_len; + buf += attr_len; + pos += attr_len; + count -= attr_len; + index++; + + cond_resched(); + } + + return ret ? ret : written; +} + +s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool sync) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_attr_search_ctx *ctx; + s64 ret; + + WARN_ON(!NInoAttr(ni)); + + ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + if (!ctx) { + ntfs_error(vi->i_sb, "Failed to get attr search ctx"); + return -ENOMEM; + } + + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (ret) { + ntfs_attr_put_search_ctx(ctx); + ntfs_error(vi->i_sb, "Failed to look up attr %#x", ni->type); + return ret; + } + + mutex_lock(&ni->mrec_lock); + ret = ntfs_enlarge_attribute(vi, pos, count, ctx); + mutex_unlock(&ni->mrec_lock); + if (ret) + goto out; + + if (NInoNonResident(ni)) + ret = __ntfs_inode_non_resident_attr_pwrite(vi, pos, count, buf, ctx, sync); + else + ret = __ntfs_inode_resident_attr_pwrite(vi, pos, count, buf, ctx); +out: + ntfs_attr_put_search_ctx(ctx); + return ret; +} + +struct folio *ntfs_get_locked_folio(struct address_space *mapping, + pgoff_t index, pgoff_t end_index, struct file_ra_state *ra) +{ + struct folio *folio; + + folio = filemap_lock_folio(mapping, index); + if (IS_ERR(folio)) { + if (PTR_ERR(folio) != -ENOENT) + return folio; + + page_cache_sync_readahead(mapping, ra, NULL, index, + end_index - index); + folio = read_mapping_folio(mapping, index, NULL); + if (!IS_ERR(folio)) + folio_lock(folio); + } + + return folio; +} diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h new file mode 100644 index 000000000000..67942b97fac6 --- /dev/null +++ b/fs/ntfs/inode.h @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for inode structures NTFS Linux kernel driver. + * + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_INODE_H +#define _LINUX_NTFS_INODE_H + +#include "debug.h" + +enum ntfs_inode_mutex_lock_class { + NTFS_INODE_MUTEX_PARENT, + NTFS_INODE_MUTEX_NORMAL, + NTFS_INODE_MUTEX_NORMAL_CHILD, + NTFS_INODE_MUTEX_PARENT_2, + NTFS_INODE_MUTEX_NORMAL_2, + NTFS_EXTEND_MUTEX_PARENT, + NTFS_EA_MUTEX_NORMAL +}; + +/* + * The NTFS in-memory inode structure. It is just used as an extension to the + * fields already provided in the VFS inode. + * @size_lock: Lock serializing access to inode sizes. + * @state: NTFS specific flags describing this inode. + * @flags: Flags describing the file. (Copy from STANDARD_INFORMATION). + * @mft_no: Number of the mft record / inode. + * @seq_no: Sequence number of the mft record. + * @count: Inode reference count for book keeping. + * @vol: Pointer to the ntfs volume of this inode. + * + * If NInoAttr() is true, the below fields describe the attribute which + * this fake inode belongs to. The actual inode of this attribute is + * pointed to by base_ntfs_ino and nr_extents is always set to -1 (see + * below). For real inodes, we also set the type (AT_DATA for files and + * AT_INDEX_ALLOCATION for directories), with the name = NULL and + * name_len = 0 for files and name = I30 (global constant) and + * name_len = 4 for directories. + * @type: Attribute type of this fake inode. + * @name: Attribute name of this fake inode. + * @name_len: Attribute name length of this fake inode. + * @runlist: If state has the NI_NonResident bit set, the runlist of + * the unnamed data attribute (if a file) or of the index allocation + * attribute (directory) or of the attribute described by the fake inode + * (if NInoAttr()). If runlist.rl is NULL, the runlist has not been read + * in yet or has been unmapped. If NI_NonResident is clear, the attribute + * is resident (file and fake inode) or there is no $I30 index allocation + * attribute (small directory). In the latter case runlist.rl is always + * NULL. + * @data_size: Copy from the attribute record. + * @initialized_size: Copy from the attribute record. + * @allocated_size: Copy from the attribute record. + * @i_crtime: File Creation time. + * @mrec: MFT record + * @mrec_lock: Lock for serializing access to the mft record belonging to + * this inode. + * @folio: The folio containing the mft record of the inode. + * @folio_ofs: Offset into the folio at which the mft record begins. + * @mft_lcn: Number containing the mft record. + * @mft_lcn_count: Number of clusters per mft record. + * + * Attribute list support (only for use by the attribute lookup + * functions). Setup during read_inode for all inodes with attribute + * lists. Only valid if NI_AttrList is set in state. + * @attr_list_size: Length of attribute list value in bytes. + * @attr_list: Attribute list value itself. + * + * It is a directory, $MFT, or an index inode. + * @block_size: Size of an index block. + * @vcn_size: Size of a vcn in this index. + * @collation_rule: The collation rule for the index. + * @block_size_bits: Log2 of the above. + * @vcn_size_bits: Log2 of the above. + * + * It is a compressed/sparse file/attribute inode. + * @size: Copy of compressed_size from $DATA. + * @block_size: Size of a compression block (cb). + * @block_size_bits: Log2 of the size of a cb. + * @block_clusters: Number of clusters per cb. + * @extent_lock: Lock for accessing/modifying the below. + * @nr_extents: For a base mft record, the number of attached extent inodes + * (0 if none), for extent records and for fake inodes describing an + * attribute this is -1. + * + * This union is only used if nr_extents != 0. + * @extent_ntfs_inos: For nr_extents > 0, array of the ntfs inodes of + * the extent mft records belonging to this base inode which have been + * loaded. + * @base_ntfs_ino: For nr_extents == -1, the ntfs inode of the base mft + * record. For fake inodes, the real (base) inode to which the attribute + * belongs. + * @i_dealloc_clusters: delayed allocated clusters. + * @target: symlink buffer. + */ +struct ntfs_inode { + rwlock_t size_lock; + unsigned long state; + __le32 flags; + u64 mft_no; + u16 seq_no; + atomic_t count; + struct ntfs_volume *vol; + __le32 type; + __le16 *name; + u32 name_len; + struct runlist runlist; + s64 data_size; + s64 initialized_size; + s64 allocated_size; + struct timespec64 i_crtime; + void *mrec; + struct mutex mrec_lock; + struct folio *folio; + int folio_ofs; + s64 mft_lcn[2]; + unsigned int mft_lcn_count; + u32 attr_list_size; + u8 *attr_list; + union { + struct { + u32 block_size; + u32 vcn_size; + __le32 collation_rule; + u8 block_size_bits; + u8 vcn_size_bits; + } index; + struct { + s64 size; + u32 block_size; + u8 block_size_bits; + u8 block_clusters; + } compressed; + } itype; + struct mutex extent_lock; + s32 nr_extents; + union { + struct ntfs_inode **extent_ntfs_inos; + struct ntfs_inode *base_ntfs_ino; + } ext; + unsigned int i_dealloc_clusters; + char *target; +}; + +/* + * Defined bits for the state field in the ntfs_inode structure. + * (f) = files only, (d) = directories only, (a) = attributes/fake inodes only + * + * NI_Dirty Mft record needs to be written to disk. + * NI_AttrListDirty Mft record contains an attribute list. + * NI_AttrList Mft record contains an attribute list. + * NI_AttrListNonResident Attribute list is non-resident. Implies + * NI_AttrList is set. + * NI_Attr 1: Fake inode for attribute i/o. + * 0: Real inode or extent inode. + * NI_MstProtected Attribute is protected by MST fixups. + * NI_NonResident Unnamed data attr is non-resident (f) + * Attribute is non-resident (a). + * NI_IndexAllocPresent $I30 index alloc attr is present (d). + * NI_Compressed Unnamed data attr is compressed (f). + * Create compressed files by default (d). + * Attribute is compressed (a). + * NI_Encrypted Unnamed data attr is encrypted (f). + * Create encrypted files by default (d). + * Attribute is encrypted (a). + * NI_Sparse Unnamed data attr is sparse (f). + * Create sparse files by default (d). + * Attribute is sparse (a). + * NI_SparseDisabled May not create sparse regions. + * NI_FullyMapped Runlist is fully mapped. + * NI_FileNameDirty FILE_NAME attributes need to be updated. + * NI_BeingDeleted ntfs inode is being delated. + * NI_BeingCreated ntfs inode is being created. + * NI_HasEA ntfs inode has EA attribute. + * NI_RunlistDirty runlist need to be updated. + */ +enum { + NI_Dirty, + NI_AttrListDirty, + NI_AttrList, + NI_AttrListNonResident, + NI_Attr, + NI_MstProtected, + NI_NonResident, + NI_IndexAllocPresent, + NI_Compressed, + NI_Encrypted, + NI_Sparse, + NI_SparseDisabled, + NI_FullyMapped, + NI_FileNameDirty, + NI_BeingDeleted, + NI_BeingCreated, + NI_HasEA, + NI_RunlistDirty, +}; + +/* + * NOTE: We should be adding dirty mft records to a list somewhere and they + * should be independent of the (ntfs/vfs) inode structure so that an inode can + * be removed but the record can be left dirty for syncing later. + */ + +/* + * Macro tricks to expand the NInoFoo(), NInoSetFoo(), and NInoClearFoo() + * functions. + */ +#define NINO_FNS(flag) \ +static inline int NIno##flag(struct ntfs_inode *ni) \ +{ \ + return test_bit(NI_##flag, &(ni)->state); \ +} \ +static inline void NInoSet##flag(struct ntfs_inode *ni) \ +{ \ + set_bit(NI_##flag, &(ni)->state); \ +} \ +static inline void NInoClear##flag(struct ntfs_inode *ni) \ +{ \ + clear_bit(NI_##flag, &(ni)->state); \ +} + +/* + * As above for NInoTestSetFoo() and NInoTestClearFoo(). + */ +#define TAS_NINO_FNS(flag) \ +static inline int NInoTestSet##flag(struct ntfs_inode *ni) \ +{ \ + return test_and_set_bit(NI_##flag, &(ni)->state); \ +} \ +static inline int NInoTestClear##flag(struct ntfs_inode *ni) \ +{ \ + return test_and_clear_bit(NI_##flag, &(ni)->state); \ +} + +/* Emit the ntfs inode bitops functions. */ +NINO_FNS(Dirty) +TAS_NINO_FNS(Dirty) +NINO_FNS(AttrList) +NINO_FNS(AttrListDirty) +NINO_FNS(AttrListNonResident) +NINO_FNS(Attr) +NINO_FNS(MstProtected) +NINO_FNS(NonResident) +NINO_FNS(IndexAllocPresent) +NINO_FNS(Compressed) +NINO_FNS(Encrypted) +NINO_FNS(Sparse) +NINO_FNS(SparseDisabled) +NINO_FNS(FullyMapped) +NINO_FNS(FileNameDirty) +TAS_NINO_FNS(FileNameDirty) +NINO_FNS(BeingDeleted) +NINO_FNS(HasEA) +NINO_FNS(RunlistDirty) + +/* + * The full structure containing a ntfs_inode and a vfs struct inode. Used for + * all real and fake inodes but not for extent inodes which lack the vfs struct + * inode. + */ +struct big_ntfs_inode { + struct ntfs_inode ntfs_inode; + struct inode vfs_inode; /* The vfs inode structure. */ +}; + +/* + * NTFS_I - return the ntfs inode given a vfs inode + * @inode: VFS inode + * + * NTFS_I() returns the ntfs inode associated with the VFS @inode. + */ +static inline struct ntfs_inode *NTFS_I(struct inode *inode) +{ + return &container_of(inode, struct big_ntfs_inode, vfs_inode)->ntfs_inode; +} + +static inline struct inode *VFS_I(struct ntfs_inode *ni) +{ + return &container_of(ni, struct big_ntfs_inode, ntfs_inode)->vfs_inode; +} + +/* + * ntfs_attr - ntfs in memory attribute structure + * + * This structure exists only to provide a small structure for the + * ntfs_{attr_}iget()/ntfs_test_inode()/ntfs_init_locked_inode() mechanism. + * + * NOTE: Elements are ordered by size to make the structure as compact as + * possible on all architectures. + */ +struct ntfs_attr { + u64 mft_no; + __le16 *name; + u32 name_len; + __le32 type; + unsigned long state; +}; + +int ntfs_test_inode(struct inode *vi, void *data); +struct inode *ntfs_iget(struct super_block *sb, u64 mft_no); +struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, + __le16 *name, u32 name_len); +struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, + u32 name_len); +struct inode *ntfs_alloc_big_inode(struct super_block *sb); +void ntfs_free_big_inode(struct inode *inode); +int ntfs_drop_big_inode(struct inode *inode); +void ntfs_evict_big_inode(struct inode *vi); +void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni); + +static inline void ntfs_init_big_inode(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + + ntfs_debug("Entering."); + __ntfs_init_inode(vi->i_sb, ni); + ni->mft_no = vi->i_ino; +} + +struct ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, u64 mft_no); +void ntfs_clear_extent_inode(struct ntfs_inode *ni); +int ntfs_read_inode_mount(struct inode *vi); +int ntfs_show_options(struct seq_file *sf, struct dentry *root); +int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size); + +int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr); +int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags); + +int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni); +int __ntfs_write_inode(struct inode *vi, int sync); +int ntfs_inode_attach_all_extents(struct ntfs_inode *ni); +int ntfs_inode_add_attrlist(struct ntfs_inode *ni); +void ntfs_destroy_ext_inode(struct ntfs_inode *ni); +int ntfs_inode_free_space(struct ntfs_inode *ni, int size); +s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf); +s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, + bool sync); +int ntfs_inode_close(struct ntfs_inode *ni); + +static inline void ntfs_commit_inode(struct inode *vi) +{ + __ntfs_write_inode(vi, 1); +} + +int ntfs_inode_sync_filename(struct ntfs_inode *ni); +int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, + const loff_t new_size, bool bsync); +void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev); +struct folio *ntfs_get_locked_folio(struct address_space *mapping, + pgoff_t index, pgoff_t end_index, struct file_ra_state *ra); + +#endif /* _LINUX_NTFS_INODE_H */ diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c new file mode 100644 index 000000000000..621645fbbf2e --- /dev/null +++ b/fs/ntfs/iomap.c @@ -0,0 +1,870 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * iomap callack functions + * + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include + +#include "attrib.h" +#include "mft.h" +#include "ntfs.h" +#include "iomap.h" + +static void ntfs_iomap_put_folio_non_resident(struct inode *inode, loff_t pos, + unsigned int len, struct folio *folio) +{ + struct ntfs_inode *ni = NTFS_I(inode); + unsigned long sector_size = 1UL << inode->i_blkbits; + loff_t start_down, end_up, init; + + start_down = round_down(pos, sector_size); + end_up = (pos + len - 1) | (sector_size - 1); + init = ni->initialized_size; + + if (init >= start_down && init <= end_up) { + if (init < pos) { + loff_t offset = offset_in_folio(folio, pos + len); + + if (offset == 0) + offset = folio_size(folio); + folio_zero_segments(folio, + offset_in_folio(folio, init), + offset_in_folio(folio, pos), + offset, + folio_size(folio)); + + } else { + loff_t offset = max_t(loff_t, pos + len, init); + + offset = offset_in_folio(folio, offset); + if (offset == 0) + offset = folio_size(folio); + folio_zero_segment(folio, + offset, + folio_size(folio)); + } + } else if (init <= pos) { + loff_t offset = 0, offset2 = offset_in_folio(folio, pos + len); + + if ((init >> folio_shift(folio)) == (pos >> folio_shift(folio))) + offset = offset_in_folio(folio, init); + if (offset2 == 0) + offset2 = folio_size(folio); + folio_zero_segments(folio, + offset, + offset_in_folio(folio, pos), + offset2, + folio_size(folio)); + } + folio_unlock(folio); + folio_put(folio); +} + +/* + * iomap_zero_range is called for an area beyond the initialized size, + * garbage values can be read, so zeroing out is needed. + */ +static void ntfs_iomap_put_folio(struct inode *inode, loff_t pos, + unsigned int len, struct folio *folio) +{ + if (NInoNonResident(NTFS_I(inode))) + return ntfs_iomap_put_folio_non_resident(inode, pos, + len, folio); + folio_unlock(folio); + folio_put(folio); +} + +const struct iomap_write_ops ntfs_iomap_folio_ops = { + .put_folio = ntfs_iomap_put_folio, +}; + +static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, loff_t length, + unsigned int flags, struct iomap *iomap) +{ + struct ntfs_inode *base_ni, *ni = NTFS_I(inode); + struct ntfs_attr_search_ctx *ctx; + loff_t i_size; + u32 attr_len; + int err = 0; + char *kattr; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + err = -ENOMEM; + goto out; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) + goto out; + + attr_len = le32_to_cpu(ctx->attr->data.resident.value_length); + if (unlikely(attr_len > ni->initialized_size)) + attr_len = ni->initialized_size; + i_size = i_size_read(inode); + + if (unlikely(attr_len > i_size)) { + /* Race with shrinking truncate. */ + attr_len = i_size; + } + + if (offset >= attr_len) { + if (flags & IOMAP_REPORT) + err = -ENOENT; + else { + iomap->type = IOMAP_HOLE; + iomap->offset = offset; + iomap->length = length; + } + goto out; + } + + kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); + + iomap->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL); + if (!iomap->inline_data) { + err = -ENOMEM; + goto out; + } + + iomap->type = IOMAP_INLINE; + iomap->offset = 0; + iomap->length = attr_len; + +out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + + return err; +} + +/* + * ntfs_read_iomap_begin_non_resident - map non-resident NTFS file data + * @inode: inode to map + * @offset: file offset to map + * @length: length of mapping + * @flags: IOMAP flags + * @iomap: iomap structure to fill + * @need_unwritten: true if UNWRITTEN extent type is needed + * + * Map a range of a non-resident NTFS file to an iomap extent. + * + * NTFS UNWRITTEN extent handling: + * ================================ + * The concept of an unwritten extent in NTFS is slightly different from + * that of other filesystems. NTFS conceptually manages only a single + * continuous unwritten region, which is strictly defined based on + * initialized_size. + * + * File offset layout: + * 0 initialized_size i_size(EOF) + * |----------#0----------|----------#1----------|----------#2----------| + * | Actual data | Pre-allocated | Pre-allocated | + * | (user written) | (within initialized) | (initialized ~ EOF) | + * |----------------------|----------------------|----------------------| + * MAPPED MAPPED UNWRITTEN (conditionally) + * + * Region #0: User-written data, initialized and valid. + * Region #1: Pre-allocated within initialized_size, must be zero-initialized + * by the filesystem before exposure to userspace. + * Region #2: Pre-allocated beyond initialized_size, does not need initialization. + * + * The @need_unwritten parameter controls whether region #2 is mapped as + * IOMAP_UNWRITTEN or IOMAP_MAPPED: + * - For seek operations (SEEK_DATA/SEEK_HOLE): IOMAP_MAPPED is needed to + * prevent iomap_seek_data from incorrectly interpreting pre-allocated + * space as a hole. Since NTFS does not support multiple unwritten extents, + * all pre-allocated regions should be treated as data, not holes. + * - For zero_range operations: IOMAP_MAPPED is needed to be zeroed out. + * + * Return: 0 on success, negative error code on failure. + */ +static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, struct iomap *iomap, + bool need_unwritten) +{ + struct ntfs_inode *ni = NTFS_I(inode); + s64 vcn; + s64 lcn; + struct runlist_element *rl; + struct ntfs_volume *vol = ni->vol; + loff_t vcn_ofs; + loff_t rl_length; + + vcn = ntfs_bytes_to_cluster(vol, offset); + vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset); + + down_write(&ni->runlist.lock); + rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn); + if (IS_ERR(rl)) { + up_write(&ni->runlist.lock); + return PTR_ERR(rl); + } + + if (flags & IOMAP_REPORT) { + if (lcn < LCN_HOLE) { + up_write(&ni->runlist.lock); + return -ENOENT; + } + } else if (lcn < LCN_ENOENT) { + up_write(&ni->runlist.lock); + return -EINVAL; + } + + iomap->bdev = inode->i_sb->s_bdev; + iomap->offset = offset; + + if (lcn <= LCN_DELALLOC) { + if (lcn == LCN_DELALLOC) + iomap->type = IOMAP_DELALLOC; + else + iomap->type = IOMAP_HOLE; + iomap->addr = IOMAP_NULL_ADDR; + } else { + if (need_unwritten && offset >= ni->initialized_size) + iomap->type = IOMAP_UNWRITTEN; + else + iomap->type = IOMAP_MAPPED; + iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs; + } + + rl_length = ntfs_cluster_to_bytes(vol, rl->length - (vcn - rl->vcn)); + + if (rl_length == 0 && rl->lcn > LCN_DELALLOC) { + ntfs_error(inode->i_sb, + "runlist(vcn : %lld, length : %lld, lcn : %lld) is corrupted\n", + rl->vcn, rl->length, rl->lcn); + up_write(&ni->runlist.lock); + return -EIO; + } + + if (rl_length && length > rl_length - vcn_ofs) + iomap->length = rl_length - vcn_ofs; + else + iomap->length = length; + up_write(&ni->runlist.lock); + + if (!(flags & IOMAP_ZERO) && + iomap->type == IOMAP_MAPPED && + iomap->offset < ni->initialized_size && + iomap->offset + iomap->length > ni->initialized_size) { + iomap->length = round_up(ni->initialized_size, 1 << inode->i_blkbits) - + iomap->offset; + } + iomap->flags |= IOMAP_F_MERGED; + + return 0; +} + +static int __ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length, + unsigned int flags, struct iomap *iomap, struct iomap *srcmap, + bool need_unwritten) +{ + if (NInoNonResident(NTFS_I(inode))) + return ntfs_read_iomap_begin_non_resident(inode, offset, length, + flags, iomap, need_unwritten); + return ntfs_read_iomap_begin_resident(inode, offset, length, + flags, iomap); +} + +static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length, + unsigned int flags, struct iomap *iomap, struct iomap *srcmap) +{ + return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap, + srcmap, true); +} + +static int ntfs_read_iomap_end(struct inode *inode, loff_t pos, loff_t length, + ssize_t written, unsigned int flags, struct iomap *iomap) +{ + if (iomap->type == IOMAP_INLINE) + kfree(iomap->inline_data); + + return written; +} + +const struct iomap_ops ntfs_read_iomap_ops = { + .iomap_begin = ntfs_read_iomap_begin, + .iomap_end = ntfs_read_iomap_end, +}; + +/* + * Check that the cached iomap still matches the NTFS runlist before + * iomap_zero_range() is called. if the runlist changes while iomap is + * iterating a cached iomap, iomap_zero_range() may overwrite folios + * that have been already written with valid data. + */ +static bool ntfs_iomap_valid(struct inode *inode, const struct iomap *iomap) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct runlist_element *rl; + s64 vcn, lcn; + + if (!NInoNonResident(ni)) + return false; + + vcn = iomap->offset >> ni->vol->cluster_size_bits; + + down_read(&ni->runlist.lock); + rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn); + if (IS_ERR(rl)) { + up_read(&ni->runlist.lock); + return false; + } + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); + up_read(&ni->runlist.lock); + return lcn == LCN_DELALLOC; +} + +static const struct iomap_write_ops ntfs_zero_iomap_folio_ops = { + .put_folio = ntfs_iomap_put_folio, + .iomap_valid = ntfs_iomap_valid, +}; + +static int ntfs_seek_iomap_begin(struct inode *inode, loff_t offset, loff_t length, + unsigned int flags, struct iomap *iomap, struct iomap *srcmap) +{ + return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap, + srcmap, false); +} + +static int ntfs_zero_read_iomap_end(struct inode *inode, loff_t pos, loff_t length, + ssize_t written, unsigned int flags, struct iomap *iomap) +{ + if ((flags & IOMAP_ZERO) && (iomap->flags & IOMAP_F_STALE)) + return -EPERM; + return written; +} + +static const struct iomap_ops ntfs_zero_read_iomap_ops = { + .iomap_begin = ntfs_seek_iomap_begin, + .iomap_end = ntfs_zero_read_iomap_end, +}; + +const struct iomap_ops ntfs_seek_iomap_ops = { + .iomap_begin = ntfs_seek_iomap_begin, + .iomap_end = ntfs_read_iomap_end, +}; + +int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length) +{ + if ((offset | length) & (SECTOR_SIZE - 1)) + return -EINVAL; + + return blkdev_issue_zeroout(inode->i_sb->s_bdev, + offset >> SECTOR_SHIFT, + length >> SECTOR_SHIFT, + GFP_NOFS, + BLKDEV_ZERO_NOUNMAP); +} + +static int ntfs_zero_range(struct inode *inode, loff_t offset, loff_t length) +{ + return iomap_zero_range(inode, + offset, length, + NULL, + &ntfs_zero_read_iomap_ops, + &ntfs_zero_iomap_folio_ops, + NULL); +} + +static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_t offset, + loff_t length, struct iomap *iomap) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_volume *vol = ni->vol; + loff_t vcn_ofs, rl_length; + struct runlist_element *rl, *rlc; + bool is_retry = false; + int err; + s64 vcn, lcn; + s64 max_clu_count = + ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size)); + + vcn = ntfs_bytes_to_cluster(vol, offset); + vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset); + + down_read(&ni->runlist.lock); + rl = ni->runlist.rl; + if (!rl) { + up_read(&ni->runlist.lock); + err = ntfs_map_runlist(ni, vcn); + if (err) { + mutex_unlock(&ni->mrec_lock); + return -ENOENT; + } + down_read(&ni->runlist.lock); + rl = ni->runlist.rl; + } + up_read(&ni->runlist.lock); + + down_write(&ni->runlist.lock); +remap_rl: + /* Seek to element containing target vcn. */ + rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn); + if (IS_ERR(rl)) { + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + return -EIO; + } + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); + + if (lcn <= LCN_RL_NOT_MAPPED && is_retry == false) { + is_retry = true; + if (!ntfs_map_runlist_nolock(ni, vcn, NULL)) { + rl = ni->runlist.rl; + goto remap_rl; + } + } + + max_clu_count = min(max_clu_count, rl->length - (vcn - rl->vcn)); + if (max_clu_count == 0) { + ntfs_error(inode->i_sb, + "runlist(vcn : %lld, length : %lld) is corrupted\n", + rl->vcn, rl->length); + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + return -EIO; + } + + iomap->bdev = inode->i_sb->s_bdev; + iomap->offset = offset; + + if (lcn <= LCN_DELALLOC) { + if (lcn < LCN_DELALLOC) { + max_clu_count = + ntfs_available_clusters_count(vol, max_clu_count); + if (max_clu_count < 0) { + err = max_clu_count; + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + return err; + } + } + + iomap->type = IOMAP_DELALLOC; + iomap->addr = IOMAP_NULL_ADDR; + + if (lcn <= LCN_HOLE) { + size_t new_rl_count; + + rlc = kmalloc(sizeof(struct runlist_element) * 2, + GFP_NOFS); + if (!rlc) { + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + return -ENOMEM; + } + + rlc->vcn = vcn; + rlc->lcn = LCN_DELALLOC; + rlc->length = max_clu_count; + + rlc[1].vcn = vcn + max_clu_count; + rlc[1].lcn = LCN_RL_NOT_MAPPED; + rlc[1].length = 0; + + rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, + &new_rl_count); + if (IS_ERR(rl)) { + ntfs_error(vol->sb, "Failed to merge runlists"); + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + kvfree(rlc); + return PTR_ERR(rl); + } + + ni->runlist.rl = rl; + ni->runlist.count = new_rl_count; + ni->i_dealloc_clusters += max_clu_count; + } + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + + if (lcn < LCN_DELALLOC) + ntfs_hold_dirty_clusters(vol, max_clu_count); + + rl_length = ntfs_cluster_to_bytes(vol, max_clu_count); + if (length > rl_length - vcn_ofs) + iomap->length = rl_length - vcn_ofs; + else + iomap->length = length; + + iomap->flags = IOMAP_F_NEW; + if (lcn <= LCN_HOLE) { + loff_t end = offset + length; + + if (vcn_ofs || ((vol->cluster_size > iomap->length) && + end < ni->initialized_size)) { + loff_t z_start, z_end; + + z_start = vcn << vol->cluster_size_bits; + z_end = min_t(loff_t, z_start + vol->cluster_size, + i_size_read(inode)); + if (z_end > z_start) + err = ntfs_zero_range(inode, + z_start, + z_end - z_start); + } + if ((!err || err == -EPERM) && + max_clu_count > 1 && + (iomap->length & vol->cluster_size_mask && + end < ni->initialized_size)) { + loff_t z_start, z_end; + + z_start = (vcn + max_clu_count - 1) << + vol->cluster_size_bits; + z_end = min_t(loff_t, z_start + vol->cluster_size, + i_size_read(inode)); + if (z_end > z_start) + err = ntfs_zero_range(inode, + z_start, + z_end - z_start); + } + + if (err == -EPERM) + err = 0; + if (err) { + ntfs_release_dirty_clusters(vol, max_clu_count); + return err; + } + } + } else { + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + + iomap->type = IOMAP_MAPPED; + iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs; + + rl_length = ntfs_cluster_to_bytes(vol, max_clu_count); + if (length > rl_length - vcn_ofs) + iomap->length = rl_length - vcn_ofs; + else + iomap->length = length; + } + + return 0; +} + +#define NTFS_IOMAP_FLAGS_BEGIN BIT(1) +#define NTFS_IOMAP_FLAGS_DIO BIT(2) +#define NTFS_IOMAP_FLAGS_MKWRITE BIT(3) +#define NTFS_IOMAP_FLAGS_WRITEBACK BIT(4) + +static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode, + loff_t offset, loff_t length, unsigned int flags, + struct iomap *iomap, int ntfs_iomap_flags) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_volume *vol = ni->vol; + loff_t vcn_ofs, rl_length; + s64 vcn, start_lcn, lcn_count; + bool balloc = false, update_mp; + int err; + s64 max_clu_count = + ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size)); + + vcn = ntfs_bytes_to_cluster(vol, offset); + vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset); + + update_mp = ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_DIO | NTFS_IOMAP_FLAGS_MKWRITE) || + NInoAttr(ni) || ni->mft_no < FILE_first_user; + down_write(&ni->runlist.lock); + err = ntfs_attr_map_cluster(ni, vcn, &start_lcn, &lcn_count, + max_clu_count, &balloc, update_mp, + ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK); + up_write(&ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + if (err) { + ni->i_dealloc_clusters = 0; + return err; + } + + iomap->bdev = inode->i_sb->s_bdev; + iomap->offset = offset; + + rl_length = ntfs_cluster_to_bytes(vol, lcn_count); + if (length > rl_length - vcn_ofs) + iomap->length = rl_length - vcn_ofs; + else + iomap->length = length; + + if (start_lcn == LCN_HOLE) + iomap->type = IOMAP_HOLE; + else + iomap->type = IOMAP_MAPPED; + if (balloc == true) + iomap->flags = IOMAP_F_NEW; + + iomap->addr = ntfs_cluster_to_bytes(vol, start_lcn) + vcn_ofs; + + if (balloc == true) { + if (flags & IOMAP_DIRECT || + ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE) { + loff_t end = offset + length; + + if (vcn_ofs || ((vol->cluster_size > iomap->length) && + end < ni->initialized_size)) + err = ntfs_dio_zero_range(inode, + start_lcn << + vol->cluster_size_bits, + vol->cluster_size); + if (!err && lcn_count > 1 && + (iomap->length & vol->cluster_size_mask && + end < ni->initialized_size)) + err = ntfs_dio_zero_range(inode, + (start_lcn + lcn_count - 1) << + vol->cluster_size_bits, + vol->cluster_size); + } else { + if (lcn_count > ni->i_dealloc_clusters) + ni->i_dealloc_clusters = 0; + else + ni->i_dealloc_clusters -= lcn_count; + } + if (err < 0) + return err; + } + + if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE && + iomap->offset + iomap->length > ni->initialized_size) { + err = ntfs_attr_set_initialized_size(ni, iomap->offset + + iomap->length); + } + + return err; +} + +static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset, + struct iomap *iomap) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + u32 attr_len; + int err = 0; + char *kattr; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = -ENOMEM; + goto out; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (err) { + if (err == -ENOENT) + err = -EIO; + goto out; + } + + a = ctx->attr; + /* The total length of the attribute value. */ + attr_len = le32_to_cpu(a->data.resident.value_length); + kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset); + + iomap->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL); + if (!iomap->inline_data) { + err = -ENOMEM; + goto out; + } + + iomap->type = IOMAP_INLINE; + iomap->offset = 0; + /* iomap requires there is only one INLINE_DATA extent */ + iomap->length = attr_len; + +out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&ni->mrec_lock); + return err; +} + +static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, + struct iomap *iomap, int ntfs_iomap_flags) +{ + struct ntfs_inode *ni = NTFS_I(inode); + + if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) && + offset + length > ni->initialized_size) { + int ret; + + ret = ntfs_extend_initialized_size(inode, offset, + offset + length, + ntfs_iomap_flags & + NTFS_IOMAP_FLAGS_DIO); + if (ret < 0) + return ret; + } + + mutex_lock(&ni->mrec_lock); + if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN) + return ntfs_write_simple_iomap_begin_non_resident(inode, offset, + length, iomap); + else + return ntfs_write_da_iomap_begin_non_resident(inode, + offset, length, + flags, iomap, + ntfs_iomap_flags); +} + +static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, + struct iomap *iomap, int ntfs_iomap_flags) +{ + struct ntfs_inode *ni = NTFS_I(inode); + loff_t end = offset + length; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) && + end > ni->data_size) { + struct ntfs_volume *vol = ni->vol; + int ret; + + mutex_lock(&ni->mrec_lock); + if (end > ni->allocated_size && + end < ni->allocated_size + vol->preallocated_size) + ret = ntfs_attr_expand(ni, end, + ni->allocated_size + vol->preallocated_size); + else + ret = ntfs_attr_expand(ni, end, 0); + mutex_unlock(&ni->mrec_lock); + if (ret) + return ret; + } + + if (!NInoNonResident(ni)) { + mutex_lock(&ni->mrec_lock); + return ntfs_write_iomap_begin_resident(inode, offset, iomap); + } + return ntfs_write_iomap_begin_non_resident(inode, offset, length, flags, + iomap, ntfs_iomap_flags); +} + +static int ntfs_write_iomap_begin(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap, + NTFS_IOMAP_FLAGS_BEGIN); +} + +static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos, + loff_t length, ssize_t written, + unsigned int flags, struct iomap *iomap) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_attr_search_ctx *ctx; + u32 attr_len; + int err; + char *kattr; + + mutex_lock(&ni->mrec_lock); + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + written = -ENOMEM; + mutex_unlock(&ni->mrec_lock); + return written; + } + + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (err) { + if (err == -ENOENT) + err = -EIO; + written = err; + goto err_out; + } + + /* The total length of the attribute value. */ + attr_len = le32_to_cpu(ctx->attr->data.resident.value_length); + if (pos >= attr_len || pos + written > attr_len) + goto err_out; + + kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); + memcpy(kattr + pos, iomap_inline_data(iomap, pos), written); + mark_mft_record_dirty(ctx->ntfs_ino); +err_out: + ntfs_attr_put_search_ctx(ctx); + kfree(iomap->inline_data); + mutex_unlock(&ni->mrec_lock); + return written; + +} + +static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length, + ssize_t written, unsigned int flags, + struct iomap *iomap) +{ + if (iomap->type == IOMAP_INLINE) + return ntfs_write_iomap_end_resident(inode, pos, length, + written, flags, iomap); + return written; +} + +const struct iomap_ops ntfs_write_iomap_ops = { + .iomap_begin = ntfs_write_iomap_begin, + .iomap_end = ntfs_write_iomap_end, +}; + +static int ntfs_page_mkwrite_iomap_begin(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap, + NTFS_IOMAP_FLAGS_MKWRITE); +} + +const struct iomap_ops ntfs_page_mkwrite_iomap_ops = { + .iomap_begin = ntfs_page_mkwrite_iomap_begin, + .iomap_end = ntfs_write_iomap_end, +}; + +static int ntfs_dio_iomap_begin(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap, + NTFS_IOMAP_FLAGS_DIO); +} + +const struct iomap_ops ntfs_dio_iomap_ops = { + .iomap_begin = ntfs_dio_iomap_begin, + .iomap_end = ntfs_write_iomap_end, +}; + +static ssize_t ntfs_writeback_range(struct iomap_writepage_ctx *wpc, + struct folio *folio, u64 offset, unsigned int len, u64 end_pos) +{ + if (offset < wpc->iomap.offset || + offset >= wpc->iomap.offset + wpc->iomap.length) { + int error; + + error = __ntfs_write_iomap_begin(wpc->inode, offset, + NTFS_I(wpc->inode)->allocated_size - offset, + IOMAP_WRITE, &wpc->iomap, + NTFS_IOMAP_FLAGS_WRITEBACK); + if (error) + return error; + } + + return iomap_add_to_ioend(wpc, folio, offset, end_pos, len); +} + +const struct iomap_writeback_ops ntfs_writeback_ops = { + .writeback_range = ntfs_writeback_range, + .writeback_submit = iomap_ioend_writeback_submit, +}; diff --git a/fs/ntfs/iomap.h b/fs/ntfs/iomap.h new file mode 100644 index 000000000000..3abc1d493e91 --- /dev/null +++ b/fs/ntfs/iomap.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_IOMAP_H +#define _LINUX_NTFS_IOMAP_H + +#include +#include + +#include "volume.h" +#include "inode.h" + +extern const struct iomap_ops ntfs_write_iomap_ops; +extern const struct iomap_ops ntfs_read_iomap_ops; +extern const struct iomap_ops ntfs_seek_iomap_ops; +extern const struct iomap_ops ntfs_page_mkwrite_iomap_ops; +extern const struct iomap_ops ntfs_dio_iomap_ops; +extern const struct iomap_writeback_ops ntfs_writeback_ops; +extern const struct iomap_write_ops ntfs_iomap_folio_ops; +extern int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length); +#endif /* _LINUX_NTFS_IOMAP_H */ diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h new file mode 100644 index 000000000000..d94f914e830f --- /dev/null +++ b/fs/ntfs/layout.h @@ -0,0 +1,2346 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * All NTFS associated on-disk structures. + * + * Copyright (c) 2001-2005 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + */ + +#ifndef _LINUX_NTFS_LAYOUT_H +#define _LINUX_NTFS_LAYOUT_H + +#include +#include +#include +#include + +/* The NTFS oem_id "NTFS " */ +#define magicNTFS cpu_to_le64(0x202020205346544eULL) + +/* + * Location of bootsector on partition: + * The standard NTFS_BOOT_SECTOR is on sector 0 of the partition. + * On NT4 and above there is one backup copy of the boot sector to + * be found on the last sector of the partition (not normally accessible + * from within Windows as the bootsector contained number of sectors + * value is one less than the actual value!). + * On versions of NT 3.51 and earlier, the backup copy was located at + * number of sectors/2 (integer divide), i.e. in the middle of the volume. + */ + +/* + * BIOS parameter block (bpb) structure. + * + * @bytes_per_sector: Size of a sector in bytes (usually 512). + * Matches the logical sector size of the underlying device. + * @sectors_per_cluster: Size of a cluster in sectors (NTFS cluster size / sector size). + * @reserved_sectors: Number of reserved sectors at the beginning of the volume. + * Always set to 0 in NTFS. + * @fats: Number of FAT tables. + * Always 0 in NTFS (no FAT tables exist). + * @root_entries: Number of entries in the root directory. + * Always 0 in NTFS. + * @sectors: Total number of sectors on the volume. + * Always 0 in NTFS (use @large_sectors instead). + * @media_type: Media descriptor byte. + * 0xF8 for hard disk (fixed media) in NTFS. + * @sectors_per_fat: Number of sectors per FAT table. + * Always 0 in NTFS. + * @sectors_per_track: Number of sectors per track. + * Irrelevant for NTFS. + * @heads: Number of heads (CHS geometry). + * Irrelevant for NTFS. + * @hidden_sectors: Number of hidden sectors before the start of the partition. + * Always 0 in NTFS boot sector. + * @large_sectors: Total number of sectors on the volume. + */ +struct bios_parameter_block { + __le16 bytes_per_sector; + u8 sectors_per_cluster; + __le16 reserved_sectors; + u8 fats; + __le16 root_entries; + __le16 sectors; + u8 media_type; + __le16 sectors_per_fat; + __le16 sectors_per_track; + __le16 heads; + __le32 hidden_sectors; + __le32 large_sectors; +} __packed; + +/* + * NTFS boot sector structure. + * + * @jump: 3-byte jump instruction to boot code (irrelevant for NTFS). + * Typically 0xEB 0x52 0x90 or similar. + * @oem_id: OEM identifier string (8 bytes). + * Always "NTFS " (with trailing spaces) in NTFS volumes. + * @bpb: Legacy BIOS Parameter Block (see struct bios_parameter_block). + * Mostly zeroed or set to fixed values for NTFS compatibility. + * @unused: 4 bytes, reserved/unused. + * NTFS disk editors show it as: + * - physical_drive (0x80 for fixed disk) + * - current_head (0) + * - extended_boot_signature (0x80 or 0x28) + * - unused (0) + * Always zero in practice for NTFS. + * @number_of_sectors: Number of sectors in volume. Gives maximum volume + * size of 2^63 sectors. Assuming standard sector + * size of 512 bytes, the maximum byte size is + * approx. 4.7x10^21 bytes. (-; + * @mft_lcn: Logical cluster number (LCN) of the $MFT data attribute. + * Location of the Master File Table. + * @mftmirr_lcn: LCN of the $MFTMirr (first 3-4 MFT records copy). + * Mirror for boot-time recovery. + * @clusters_per_mft_record: + * Size of each MFT record in clusters. + * @reserved0: 3 bytes, reserved/zero. + * @clusters_per_index_record: + * Size of each index block/record in clusters. + * @reserved1: 3 bytes, reserved/zero. + * @volume_serial_number: + * 64-bit volume serial number. + * Used for identification (irrelevant for NTFS operation). + * @checksum: 32-bit checksum of the boot sector (excluding this field). + * Used to detect boot sector corruption. + * @bootstrap: 426 bytes of bootstrap code. + * Irrelevant for NTFS (contains x86 boot loader stub). + * @end_of_sector_marker: + * 2-byte end-of-sector signature. + * Always 0xAA55 (little-endian magic number). + */ +struct ntfs_boot_sector { + u8 jump[3]; + __le64 oem_id; + struct bios_parameter_block bpb; + u8 unused[4]; + __le64 number_of_sectors; + __le64 mft_lcn; + __le64 mftmirr_lcn; + s8 clusters_per_mft_record; + u8 reserved0[3]; + s8 clusters_per_index_record; + u8 reserved1[3]; + __le64 volume_serial_number; + __le32 checksum; + u8 bootstrap[426]; + __le16 end_of_sector_marker; +} __packed; + +static_assert(sizeof(struct ntfs_boot_sector) == 512); + +/* + * Magic identifiers present at the beginning of all ntfs record containing + * records (like mft records for example). + * + * magic_FILE: MFT entry header ("FILE" in ASCII). + * Used in $MFT/$DATA for all master file table records. + * magic_INDX: Index buffer header ("INDX" in ASCII). + * Used in $INDEX_ALLOCATION attributes (directories, $I30 indexes). + * magic_HOLE: Hole marker ("HOLE" in ASCII). + * Introduced in NTFS 3.0+, used for sparse/hole regions in some contexts. + * magic_RSTR: Restart page header ("RSTR" in ASCII). + * Used in LogFile for restart pages (transaction log recovery). + * magic_RCRD: Log record page header ("RCRD" in ASCII). + * Used in LogFile for individual log record pages. + * magic_CHKD: Chkdsk modified marker ("CHKD" in ASCII). + * Set by chkdsk when it modifies a record; indicates repair was done. + * magic_BAAD: Bad record marker ("BAAD" in ASCII). + * Indicates a multi-sector transfer failure was detected. + * The record is corrupted/unusable; often set during I/O errors. + * magic_empty: Empty/uninitialized page marker (0xffffffff). + * Used in LogFile when a page is filled with 0xff bytes + * and has not yet been initialized. Must be formatted before use. + */ +enum { + magic_FILE = cpu_to_le32(0x454c4946), + magic_INDX = cpu_to_le32(0x58444e49), + magic_HOLE = cpu_to_le32(0x454c4f48), + magic_RSTR = cpu_to_le32(0x52545352), + magic_RCRD = cpu_to_le32(0x44524352), + magic_CHKD = cpu_to_le32(0x444b4843), + magic_BAAD = cpu_to_le32(0x44414142), + magic_empty = cpu_to_le32(0xffffffff) +}; + +/* + * Generic magic comparison macros. Finally found a use for the ## preprocessor + * operator! (-8 + */ + +static inline bool __ntfs_is_magic(__le32 x, __le32 r) +{ + return (x == r); +} +#define ntfs_is_magic(x, m) __ntfs_is_magic(x, magic_##m) + +static inline bool __ntfs_is_magicp(__le32 *p, __le32 r) +{ + return (*p == r); +} +#define ntfs_is_magicp(p, m) __ntfs_is_magicp(p, magic_##m) + +/* + * Specialised magic comparison macros for the NTFS_RECORD_TYPEs defined above. + */ +#define ntfs_is_file_record(x) (ntfs_is_magic(x, FILE)) +#define ntfs_is_file_recordp(p) (ntfs_is_magicp(p, FILE)) +#define ntfs_is_mft_record(x) (ntfs_is_file_record(x)) +#define ntfs_is_mft_recordp(p) (ntfs_is_file_recordp(p)) +#define ntfs_is_indx_record(x) (ntfs_is_magic(x, INDX)) +#define ntfs_is_indx_recordp(p) (ntfs_is_magicp(p, INDX)) +#define ntfs_is_hole_record(x) (ntfs_is_magic(x, HOLE)) +#define ntfs_is_hole_recordp(p) (ntfs_is_magicp(p, HOLE)) + +#define ntfs_is_rstr_record(x) (ntfs_is_magic(x, RSTR)) +#define ntfs_is_rstr_recordp(p) (ntfs_is_magicp(p, RSTR)) +#define ntfs_is_rcrd_record(x) (ntfs_is_magic(x, RCRD)) +#define ntfs_is_rcrd_recordp(p) (ntfs_is_magicp(p, RCRD)) + +#define ntfs_is_chkd_record(x) (ntfs_is_magic(x, CHKD)) +#define ntfs_is_chkd_recordp(p) (ntfs_is_magicp(p, CHKD)) + +#define ntfs_is_baad_record(x) (ntfs_is_magic(x, BAAD)) +#define ntfs_is_baad_recordp(p) (ntfs_is_magicp(p, BAAD)) + +#define ntfs_is_empty_record(x) (ntfs_is_magic(x, empty)) +#define ntfs_is_empty_recordp(p) (ntfs_is_magicp(p, empty)) + +/* + * struct ntfs_record - Common header for all multi-sector protected NTFS records + * + * @magic: 4-byte magic identifier for the record type and/or status. + * Common values are defined in the magic_* enum (FILE, INDX, RSTR, + * RCRD, CHKD, BAAD, HOLE, empty). + * - "FILE" = MFT record + * - "INDX" = Index allocation block + * - "BAAD" = Record corrupted (multi-sector fixup failed) + * - 0xffffffff = Uninitialized/empty page + * @usa_ofs: Offset (in bytes) from the start of this record to the Update + * Sequence Array (USA). + * The USA is located at record + usa_ofs. + * @usa_count: Number of 16-bit entries in the USA array (including the Update + * Sequence Number itself). + * - Number of fixup locations = usa_count - 1 + * - Each fixup location is a 16-bit value in the record that needs + * protection against torn writes. + * + * The Update Sequence Array (usa) is an array of the __le16 values which belong + * to the end of each sector protected by the update sequence record in which + * this array is contained. Note that the first entry is the Update Sequence + * Number (usn), a cyclic counter of how many times the protected record has + * been written to disk. The values 0 and -1 (ie. 0xffff) are not used. All + * last le16's of each sector have to be equal to the usn (during reading) or + * are set to it (during writing). If they are not, an incomplete multi sector + * transfer has occurred when the data was written. + * The maximum size for the update sequence array is fixed to: + * maximum size = usa_ofs + (usa_count * 2) = 510 bytes + * The 510 bytes comes from the fact that the last __le16 in the array has to + * (obviously) finish before the last __le16 of the first 512-byte sector. + * This formula can be used as a consistency check in that usa_ofs + + * (usa_count * 2) has to be less than or equal to 510. + */ +struct ntfs_record { + __le32 magic; + __le16 usa_ofs; + __le16 usa_count; +} __packed; + +/* + * System files mft record numbers. All these files are always marked as used + * in the bitmap attribute of the mft; presumably in order to avoid accidental + * allocation for random other mft records. Also, the sequence number for each + * of the system files is always equal to their mft record number and it is + * never modified. + * + * FILE_MFT: Master File Table (MFT) itself. + * Data attribute contains all MFT entries; + * Bitmap attribute tracks which records are in use (bit==1). + * FILE_MFTMirr: MFT mirror: copy of the first four (or more) MFT records + * in its data attribute. + * If cluster size > 4 KiB, copies first N records where + * N = cluster_size / mft_record_size. + * FILE_LogFile: Journaling log (LogFile) in data attribute. + * Used for transaction logging and recovery. + * FILE_Volume: Volume information and name. + * Contains $VolumeName (label) and $VolumeInformation + * (flags, NTFS version). Windows calls this the volume DASD. + * FILE_AttrDef: Attribute definitions array in data attribute. + * Defines all possible attribute types and their properties. + * FILE_root: Root directory ($Root). + * The top-level directory of the filesystem. + * FILE_Bitmap: Cluster allocation bitmap ($Bitmap) in data attribute. + * Tracks free/used clusters (LCNs) on the volume. + * FILE_Boot: Boot sector ($Boot) in data attribute. + * Always located at cluster 0; contains BPB and NTFS parameters. + * FILE_BadClus: Bad cluster list ($BadClus) in non-resident data attribute. + * Marks all known bad clusters. + * FILE_Secure: Security descriptors ($Secure). + * Contains shared $SDS (security descriptors) and two indexes + * ($SDH, $SII). Introduced in Windows 2000. + * Before that, it was called $Quota but was unused. + * FILE_UpCase: Uppercase table ($UpCase) in data attribute. + * Maps all 65536 Unicode characters to their uppercase forms. + * FILE_Extend: System directory ($Extend). + * Contains additional system files ($ObjId, $Quota, $Reparse, + * $UsnJrnl, etc.). Introduced in NTFS 3.0 (Windows 2000). + * FILE_reserved12: Reserved for future use (MFT records 12–15). + * FILE_reserved13: Reserved. + * FILE_reserved14: Reserved. + * FILE_reserved15: Reserved. + * FILE_first_user: First possible user-created file MFT record number. + * Used as a boundary to distinguish system files from user files. + */ +enum { + FILE_MFT = 0, + FILE_MFTMirr = 1, + FILE_LogFile = 2, + FILE_Volume = 3, + FILE_AttrDef = 4, + FILE_root = 5, + FILE_Bitmap = 6, + FILE_Boot = 7, + FILE_BadClus = 8, + FILE_Secure = 9, + FILE_UpCase = 10, + FILE_Extend = 11, + FILE_reserved12 = 12, + FILE_reserved13 = 13, + FILE_reserved14 = 14, + FILE_reserved15 = 15, + FILE_first_user = 16, +}; + +/* + * enum - Flags for MFT record header + * + * These are the so far known MFT_RECORD_* flags (16-bit) which contain + * information about the mft record in which they are present. + * + * MFT_RECORD_IN_USE: This MFT record is allocated and in use. + * (bit set = record is valid/used; clear = free) + * MFT_RECORD_IS_DIRECTORY: This MFT record represents a directory. + * (Used to quickly distinguish files from directories) + * MFT_RECORD_IS_4: Indicates the record is a special "record 4" type. + * (Rarely used; related to NTFS internal special cases, + * often for $AttrDef or early system files) + * MFT_RECORD_IS_VIEW_INDEX: This MFT record is used as a view index. + * (Specific to NTFS indexed views or object ID indexes) + * MFT_REC_SPACE_FILLER: Dummy value to force the enum to be 16-bit wide. + * (Not a real flag; just a sentinel to ensure the type + * is __le16 and no higher bits are accidentally used) + */ +enum { + MFT_RECORD_IN_USE = cpu_to_le16(0x0001), + MFT_RECORD_IS_DIRECTORY = cpu_to_le16(0x0002), + MFT_RECORD_IS_4 = cpu_to_le16(0x0004), + MFT_RECORD_IS_VIEW_INDEX = cpu_to_le16(0x0008), + MFT_REC_SPACE_FILLER = cpu_to_le16(0xffff), /*Just to make flags 16-bit.*/ +} __packed; + +/* + * mft references (aka file references or file record segment references) are + * used whenever a structure needs to refer to a record in the mft. + * + * A reference consists of a 48-bit index into the mft and a 16-bit sequence + * number used to detect stale references. + * + * For error reporting purposes we treat the 48-bit index as a signed quantity. + * + * The sequence number is a circular counter (skipping 0) describing how many + * times the referenced mft record has been (re)used. This has to match the + * sequence number of the mft record being referenced, otherwise the reference + * is considered stale and removed. + * + * If the sequence number is zero it is assumed that no sequence number + * consistency checking should be performed. + */ + +/* + * Define two unpacking macros to get to the reference (MREF) and + * sequence number (MSEQNO) respectively. + * The _LE versions are to be applied on little endian MFT_REFs. + * Note: The _LE versions will return a CPU endian formatted value! + */ +#define MFT_REF_MASK_CPU 0x0000ffffffffffffULL +#define MFT_REF_MASK_LE cpu_to_le64(MFT_REF_MASK_CPU) + +#define MK_MREF(m, s) ((u64)(((u64)(s) << 48) | \ + ((u64)(m) & MFT_REF_MASK_CPU))) +#define MK_LE_MREF(m, s) cpu_to_le64(MK_MREF(m, s)) + +#define MREF(x) ((unsigned long)((x) & MFT_REF_MASK_CPU)) +#define MSEQNO(x) ((u16)(((x) >> 48) & 0xffff)) +#define MREF_LE(x) ((unsigned long)(le64_to_cpu(x) & MFT_REF_MASK_CPU)) +#define MREF_INO(x) ((unsigned long)MREF_LE(x)) +#define MSEQNO_LE(x) ((u16)((le64_to_cpu(x) >> 48) & 0xffff)) + +#define IS_ERR_MREF(x) (((x) & 0x0000800000000000ULL) ? true : false) +#define ERR_MREF(x) ((u64)((s64)(x))) +#define MREF_ERR(x) ((int)((s64)(x))) + +/* + * struct mft_record - NTFS Master File Table (MFT) record header + * + * The mft record header present at the beginning of every record in the mft. + * This is followed by a sequence of variable length attribute records which + * is terminated by an attribute of type AT_END which is a truncated attribute + * in that it only consists of the attribute type code AT_END and none of the + * other members of the attribute structure are present. + * + * magic: Record magic ("FILE" for valid MFT entries). + * See ntfs_record magic enum for other values. + * usa_ofs: Offset to Update Sequence Array (see ntfs_record). + * usa_count: Number of entries in USA (see ntfs_record). + * lsn: Log sequence number (LSN) from LogFile. + * Incremented on every modification to this record. + * sequence_number: Reuse count of this MFT record slot. + * Incremented (skipping zero) when the file is deleted. + * Zero means never reused or special case. + * Part of MFT reference (together with record number). + * link_count: Number of hard links (directory entries) to this file. + * Only meaningful in base MFT records. + * When deleting a directory entry: + * - If link_count == 1, delete the whole file + * - Else remove only the $FILE_NAME attribute and decrement + * attrs_offset: Byte offset from start of MFT record to first attribute. + * Must be 8-byte aligned. + * flags: Bit array of MFT_RECORD_* flags (see MFT_RECORD_IN_USE enum). + * MFT_RECORD_IN_USE cleared when record is freed/deleted. + * bytes_in_use: Number of bytes actually used in this MFT record. + * Must be 8-byte aligned. + * Includes header + all attributes + padding. + * bytes_allocated: Total allocated size of this MFT record. + * Usually equal to MFT record size (1024 bytes or cluster size). + * base_mft_record: MFT reference to the base record. + * 0 for base records. + * Non-zero for extension records → points to base record + * containing the $ATTRIBUTE_LIST that describes this extension. + * next_attr_instance: Next attribute instance number to assign. + * Incremented after each use. + * Reset to 0 when MFT record is reused. + * First instance is always 0. + * reserved: Reserved for alignment (NTFS 3.1+). + * mft_record_number: This MFT record's number (index in $MFT). + * Only present in NTFS 3.1+ (Windows XP and above). + */ +struct mft_record { + __le32 magic; + __le16 usa_ofs; + __le16 usa_count; + + __le64 lsn; + __le16 sequence_number; + __le16 link_count; + __le16 attrs_offset; + __le16 flags; + __le32 bytes_in_use; + __le32 bytes_allocated; + __le64 base_mft_record; + __le16 next_attr_instance; + __le16 reserved; + __le32 mft_record_number; +} __packed; + +static_assert(sizeof(struct mft_record) == 48); + +/**x + * struct mft_record_old - Old NTFS MFT record header (pre-NTFS 3.1 / Windows XP) + * + * This is the older version of the MFT record header used in NTFS versions + * prior to 3.1 (Windows XP and later). It lacks the additional fields + * @reserved and @mft_record_number that were added in NTFS 3.1+. + * + * @magic: Record magic ("FILE" for valid MFT entries). + * See ntfs_record magic enum for other values. + * @usa_ofs: Offset to Update Sequence Array (see ntfs_record). + * @usa_count: Number of entries in USA (see ntfs_record). + * @lsn: Log sequence number (LSN) from LogFile. + * Incremented on every modification to this record. + * @sequence_number: Reuse count of this MFT record slot. + * Incremented (skipping zero) when the file is deleted. + * Zero means never reused or special case. + * Part of MFT reference (together with record number). + * @link_count: Number of hard links (directory entries) to this file. + * Only meaningful in base MFT records. + * When deleting a directory entry: + * - If link_count == 1, delete the whole file + * - Else remove only the $FILE_NAME attribute and decrement + * @attrs_offset: Byte offset from start of MFT record to first attribute. + * Must be 8-byte aligned. + * @flags: Bit array of MFT_RECORD_* flags (see MFT_RECORD_IN_USE enum). + * MFT_RECORD_IN_USE cleared when record is freed/deleted. + * @bytes_in_use: Number of bytes actually used in this MFT record. + * Must be 8-byte aligned. + * Includes header + all attributes + padding. + * @bytes_allocated: Total allocated size of this MFT record. + * Usually equal to MFT record size (1024 bytes or cluster size). + * @base_mft_record: MFT reference to the base record. + * 0 for base records. + * Non-zero for extension records → points to base record + * containing the $ATTRIBUTE_LIST that describes this extension. + * @next_attr_instance: Next attribute instance number to assign. + * Incremented after each use. + * Reset to 0 when MFT record is reused. + * First instance is always 0. + */ +struct mft_record_old { + __le32 magic; + __le16 usa_ofs; + __le16 usa_count; + + __le64 lsn; + __le16 sequence_number; + __le16 link_count; + __le16 attrs_offset; + __le16 flags; + __le32 bytes_in_use; + __le32 bytes_allocated; + __le64 base_mft_record; + __le16 next_attr_instance; +} __packed; + +static_assert(sizeof(struct mft_record_old) == 42); + +/* + * System defined attributes (32-bit). Each attribute type has a corresponding + * attribute name (Unicode string of maximum 64 character length) as described + * by the attribute definitions present in the data attribute of the $AttrDef + * system file. On NTFS 3.0 volumes the names are just as the types are named + * in the below defines exchanging AT_ for the dollar sign ($). If that is not + * a revealing choice of symbol I do not know what is... (-; + */ +enum { + AT_UNUSED = cpu_to_le32(0), + AT_STANDARD_INFORMATION = cpu_to_le32(0x10), + AT_ATTRIBUTE_LIST = cpu_to_le32(0x20), + AT_FILE_NAME = cpu_to_le32(0x30), + AT_OBJECT_ID = cpu_to_le32(0x40), + AT_SECURITY_DESCRIPTOR = cpu_to_le32(0x50), + AT_VOLUME_NAME = cpu_to_le32(0x60), + AT_VOLUME_INFORMATION = cpu_to_le32(0x70), + AT_DATA = cpu_to_le32(0x80), + AT_INDEX_ROOT = cpu_to_le32(0x90), + AT_INDEX_ALLOCATION = cpu_to_le32(0xa0), + AT_BITMAP = cpu_to_le32(0xb0), + AT_REPARSE_POINT = cpu_to_le32(0xc0), + AT_EA_INFORMATION = cpu_to_le32(0xd0), + AT_EA = cpu_to_le32(0xe0), + AT_PROPERTY_SET = cpu_to_le32(0xf0), + AT_LOGGED_UTILITY_STREAM = cpu_to_le32(0x100), + AT_FIRST_USER_DEFINED_ATTRIBUTE = cpu_to_le32(0x1000), + AT_END = cpu_to_le32(0xffffffff) +}; + +/* + * The collation rules for sorting views/indexes/etc (32-bit). + * + * COLLATION_BINARY - Collate by binary compare where the first byte is most + * significant. + * COLLATION_UNICODE_STRING - Collate Unicode strings by comparing their binary + * Unicode values, except that when a character can be uppercased, the + * upper case value collates before the lower case one. + * COLLATION_FILE_NAME - Collate file names as Unicode strings. The collation + * is done very much like COLLATION_UNICODE_STRING. In fact I have no idea + * what the difference is. Perhaps the difference is that file names + * would treat some special characters in an odd way (see + * unistr.c::ntfs_collate_names() and unistr.c::legal_ansi_char_array[] + * for what I mean but COLLATION_UNICODE_STRING would not give any special + * treatment to any characters at all, but this is speculation. + * COLLATION_NTOFS_ULONG - Sorting is done according to ascending __le32 key + * values. E.g. used for $SII index in FILE_Secure, which sorts by + * security_id (le32). + * COLLATION_NTOFS_SID - Sorting is done according to ascending SID values. + * E.g. used for $O index in FILE_Extend/$Quota. + * COLLATION_NTOFS_SECURITY_HASH - Sorting is done first by ascending hash + * values and second by ascending security_id values. E.g. used for $SDH + * index in FILE_Secure. + * COLLATION_NTOFS_ULONGS - Sorting is done according to a sequence of ascending + * __le32 key values. E.g. used for $O index in FILE_Extend/$ObjId, which + * sorts by object_id (16-byte), by splitting up the object_id in four + * __le32 values and using them as individual keys. E.g. take the following + * two security_ids, stored as follows on disk: + * 1st: a1 61 65 b7 65 7b d4 11 9e 3d 00 e0 81 10 42 59 + * 2nd: 38 14 37 d2 d2 f3 d4 11 a5 21 c8 6b 79 b1 97 45 + * To compare them, they are split into four __le32 values each, like so: + * 1st: 0xb76561a1 0x11d47b65 0xe0003d9e 0x59421081 + * 2nd: 0xd2371438 0x11d4f3d2 0x6bc821a5 0x4597b179 + * Now, it is apparent why the 2nd object_id collates after the 1st: the + * first __le32 value of the 1st object_id is less than the first __le32 of + * the 2nd object_id. If the first __le32 values of both object_ids were + * equal then the second __le32 values would be compared, etc. + */ +enum { + COLLATION_BINARY = cpu_to_le32(0x00), + COLLATION_FILE_NAME = cpu_to_le32(0x01), + COLLATION_UNICODE_STRING = cpu_to_le32(0x02), + COLLATION_NTOFS_ULONG = cpu_to_le32(0x10), + COLLATION_NTOFS_SID = cpu_to_le32(0x11), + COLLATION_NTOFS_SECURITY_HASH = cpu_to_le32(0x12), + COLLATION_NTOFS_ULONGS = cpu_to_le32(0x13), +}; + +/* + * enum - Attribute definition flags + * + * The flags (32-bit) describing attribute properties in the attribute + * definition structure. + * The INDEXABLE flag is fairly certainly correct as only the file + * name attribute has this flag set and this is the only attribute indexed in + * NT4. + * + * ATTR_DEF_INDEXABLE: Attribute can be indexed. + * (Used for creating indexes like $I30, $SDH, etc.) + * ATTR_DEF_MULTIPLE: Attribute type can be present multiple times + * in the MFT record of an inode. + * (e.g., multiple $FILE_NAME, $DATA streams) + * ATTR_DEF_NOT_ZERO: Attribute value must contain at least one non-zero byte. + * (Prevents empty or all-zero values) + * ATTR_DEF_INDEXED_UNIQUE: Attribute must be indexed and the value must be unique + * for this attribute type across all MFT records of an inode. + * (e.g., security descriptor IDs in $Secure) + * ATTR_DEF_NAMED_UNIQUE: Attribute must be named and the name must be unique + * for this attribute type across all MFT records of an inode. + * (e.g., named $DATA streams or alternate data streams) + * ATTR_DEF_RESIDENT: Attribute must be resident (stored in MFT record). + * (Cannot be non-resident/sparse/compressed) + * ATTR_DEF_ALWAYS_LOG: Always log modifications to this attribute in LogFile, + * regardless of whether it is resident or non-resident. + * Without this flag, modifications are logged only if resident. + * (Used for critical metadata attributes) + */ +enum { + ATTR_DEF_INDEXABLE = cpu_to_le32(0x02), + ATTR_DEF_MULTIPLE = cpu_to_le32(0x04), + ATTR_DEF_NOT_ZERO = cpu_to_le32(0x08), + ATTR_DEF_INDEXED_UNIQUE = cpu_to_le32(0x10), + ATTR_DEF_NAMED_UNIQUE = cpu_to_le32(0x20), + ATTR_DEF_RESIDENT = cpu_to_le32(0x40), + ATTR_DEF_ALWAYS_LOG = cpu_to_le32(0x80), +}; + +/* + * struct attr_def - Attribute definition entry ($AttrDef array) + * + * The data attribute of FILE_AttrDef contains a sequence of attribute + * definitions for the NTFS volume. With this, it is supposed to be safe for an + * older NTFS driver to mount a volume containing a newer NTFS version without + * damaging it (that's the theory. In practice it's: not damaging it too much). + * Entries are sorted by attribute type. The flags describe whether the + * attribute can be resident/non-resident and possibly other things, but the + * actual bits are unknown. + * + * @name: Unicode (UTF-16LE) name of the attribute (e.g. "$DATA", "$FILE_NAME"). + * Zero-terminated string, maximum 0x40 characters (128 bytes). + * Used for human-readable display and debugging. + * @type: Attribute type code (ATTR_TYPE_* constants). + * Defines which attribute this entry describes. + * @display_rule: Default display rule (usually 0; rarely used in modern NTFS). + * Controls how the attribute is displayed in tools (legacy). + * @collation_rule: Default collation rule for indexing this attribute. + * Determines sort order when indexed (e.g. CASE_SENSITIVE, UNICODE). + * Used in $I30, $SDH, $SII, etc. + * @flags: Bit array of attribute constraints (ATTR_DEF_* flags). + * See ATTR_DEF_INDEXABLE, ATTR_DEF_MULTIPLE, etc. + * Defines whether the attribute can be indexed, multiple, resident-only, etc. + * @min_size: Optional minimum size of the attribute value (in bytes). + * 0 means no minimum enforced. + * @max_size: Maximum allowed size of the attribute value (in bytes). + */ +struct attr_def { + __le16 name[0x40]; + __le32 type; + __le32 display_rule; + __le32 collation_rule; + __le32 flags; + __le64 min_size; + __le64 max_size; +} __packed; + +static_assert(sizeof(struct attr_def) == 160); + +/* + * enum - Attribute flags (16-bit) for non-resident attributes + * + * ATTR_IS_COMPRESSED: Attribute is compressed. + * If set, data is compressed using the method in + * ATTR_COMPRESSION_MASK. + * ATTR_COMPRESSION_MASK: Mask for compression method. + * Valid values are defined in NTFS compression types + * (e.g., 0x02 = LZNT1, etc.). + * Also serves as the first illegal value for method. + * ATTR_IS_ENCRYPTED: Attribute is encrypted. + * Data is encrypted using EFS (Encrypting File System). + * ATTR_IS_SPARSE: Attribute is sparse. + * Contains holes (unallocated regions) that read as zeros. + */ +enum { + ATTR_IS_COMPRESSED = cpu_to_le16(0x0001), + ATTR_COMPRESSION_MASK = cpu_to_le16(0x00ff), + ATTR_IS_ENCRYPTED = cpu_to_le16(0x4000), + ATTR_IS_SPARSE = cpu_to_le16(0x8000), +} __packed; + +/* + * Attribute compression. + * + * Only the data attribute is ever compressed in the current ntfs driver in + * Windows. Further, compression is only applied when the data attribute is + * non-resident. Finally, to use compression, the maximum allowed cluster size + * on a volume is 4kib. + * + * The compression method is based on independently compressing blocks of X + * clusters, where X is determined from the compression_unit value found in the + * non-resident attribute record header (more precisely: X = 2^compression_unit + * clusters). On Windows NT/2k, X always is 16 clusters (compression_unit = 4). + * + * There are three different cases of how a compression block of X clusters + * can be stored: + * + * 1) The data in the block is all zero (a sparse block): + * This is stored as a sparse block in the runlist, i.e. the runlist + * entry has length = X and lcn = -1. The mapping pairs array actually + * uses a delta_lcn value length of 0, i.e. delta_lcn is not present at + * all, which is then interpreted by the driver as lcn = -1. + * NOTE: Even uncompressed files can be sparse on NTFS 3.0 volumes, then + * the same principles apply as above, except that the length is not + * restricted to being any particular value. + * + * 2) The data in the block is not compressed: + * This happens when compression doesn't reduce the size of the block + * in clusters. I.e. if compression has a small effect so that the + * compressed data still occupies X clusters, then the uncompressed data + * is stored in the block. + * This case is recognised by the fact that the runlist entry has + * length = X and lcn >= 0. The mapping pairs array stores this as + * normal with a run length of X and some specific delta_lcn, i.e. + * delta_lcn has to be present. + * + * 3) The data in the block is compressed: + * The common case. This case is recognised by the fact that the run + * list entry has length L < X and lcn >= 0. The mapping pairs array + * stores this as normal with a run length of X and some specific + * delta_lcn, i.e. delta_lcn has to be present. This runlist entry is + * immediately followed by a sparse entry with length = X - L and + * lcn = -1. The latter entry is to make up the vcn counting to the + * full compression block size X. + * + * In fact, life is more complicated because adjacent entries of the same type + * can be coalesced. This means that one has to keep track of the number of + * clusters handled and work on a basis of X clusters at a time being one + * block. An example: if length L > X this means that this particular runlist + * entry contains a block of length X and part of one or more blocks of length + * L - X. Another example: if length L < X, this does not necessarily mean that + * the block is compressed as it might be that the lcn changes inside the block + * and hence the following runlist entry describes the continuation of the + * potentially compressed block. The block would be compressed if the + * following runlist entry describes at least X - L sparse clusters, thus + * making up the compression block length as described in point 3 above. (Of + * course, there can be several runlist entries with small lengths so that the + * sparse entry does not follow the first data containing entry with + * length < X.) + * + * NOTE: At the end of the compressed attribute value, there most likely is not + * just the right amount of data to make up a compression block, thus this data + * is not even attempted to be compressed. It is just stored as is, unless + * the number of clusters it occupies is reduced when compressed in which case + * it is stored as a compressed compression block, complete with sparse + * clusters at the end. + */ + +/* + * enum - Flags for resident attributes (8-bit) + * + * RESIDENT_ATTR_IS_INDEXED: Attribute is referenced in an index. + * (e.g., part of an index key or entry) + * Has implications for deletion and modification: + * - Cannot be freely removed if indexed + * - Index must be updated when value changes + * - Used for attributes like $FILE_NAME in directories + */ +enum { + RESIDENT_ATTR_IS_INDEXED = 0x01, +} __packed; + +/* + * struct attr_record - NTFS attribute record header + * + * Common header for both resident and non-resident attributes. + * Always aligned to an 8-byte boundary on disk. + * Located at attrs_offset in the MFT record (see struct mft_record). + * + * @type: 32-bit attribute type (ATTR_TYPE_* constants). + * Identifies the attribute + * (e.g. 0x10 = $STANDARD_INFORMATION). + * @length: Total byte size of this attribute record (resident). + * 8-byte aligned; used to locate the next attribute. + * @non_resident: 0 = resident attribute + * 1 = non-resident attribute + * @name_length: Number of Unicode characters in the attribute name. + * 0 if unnamed (most system attributes are unnamed). + * @name_offset: Byte offset from start of attribute record to the name. + * 8-byte aligned; when creating, place at end of header. + * @flags: Attribute flags (see ATTR_IS_COMPRESSED, + * ATTR_IS_ENCRYPTED, etc.). + * For resident: see RESIDENT_ATTR_* flags. + * @instance: Unique instance number within this MFT record. + * Incremented via next_attr_instance; unique per record. + * + * Resident attributes (when @non_resident == 0): + * @data.resident.value_length: Byte size of the attribute value. + * @data.resident.value_offset: Byte offset from start of attribute + * record to the value data. + * 8-byte aligned if name present. + * @data.resident.flags: Resident-specific flags + * @data.resident.reserved: Reserved/alignment to 8 bytes. + * + * Non-resident attributes (when @non_resident == 1): + * @data.non_resident.lowest_vcn: Lowest valid VCN in this extent. + * Usually 0 unless attribute list is used. + * @data.non_resident.highest_vcn: Highest valid VCN in this extent. + * -1 for zero-length, 0 for single extent. + * @data.non_resident.mapping_pairs_offset: + * Byte offset to mapping pairs array + * (VCN → LCN mappings). + * 8-byte aligned when creating. + * @data.non_resident.compression_unit: + * Log2 of clusters per compression unit. + * 0 = not compressed. + * WinNT4 used 4; sparse files use 0 + * on XP SP2+. + * @data.non_resident.reserved: 5 bytes for 8-byte alignment. + * @data.non_resident.allocated_size: + * Allocated disk space in bytes. + * For compressed: logical allocated size. + * @data.non_resident.data_size: Logical attribute value size in bytes. + * Can be larger than allocated_size if + * compressed/sparse. + * @data.non_resident.initialized_size: + * Initialized portion size in bytes. + * Usually equals data_size. + * @data.non_resident.compressed_size: + * Compressed on-disk size in bytes. + * Only present when compressed or sparse. + * Actual disk usage. + */ +struct attr_record { + __le32 type; + __le32 length; + u8 non_resident; + u8 name_length; + __le16 name_offset; + __le16 flags; + __le16 instance; + union { + struct { + __le32 value_length; + __le16 value_offset; + u8 flags; + s8 reserved; + } __packed resident; + struct { + __le64 lowest_vcn; + __le64 highest_vcn; + __le16 mapping_pairs_offset; + u8 compression_unit; + u8 reserved[5]; + __le64 allocated_size; + __le64 data_size; + __le64 initialized_size; + __le64 compressed_size; + } __packed non_resident; + } __packed data; +} __packed; + +/* + * enum - NTFS file attribute flags (32-bit) + * + * File attribute flags (32-bit) appearing in the file_attributes fields of the + * STANDARD_INFORMATION attribute of MFT_RECORDs and the FILENAME_ATTR + * attributes of MFT_RECORDs and directory index entries. + * + * All of the below flags appear in the directory index entries but only some + * appear in the STANDARD_INFORMATION attribute whilst only some others appear + * in the FILENAME_ATTR attribute of MFT_RECORDs. Unless otherwise stated the + * flags appear in all of the above. + * + * FILE_ATTR_READONLY: File is read-only. + * FILE_ATTR_HIDDEN: File is hidden (not shown by default). + * FILE_ATTR_SYSTEM: System file (protected by OS). + * FILE_ATTR_DIRECTORY: Directory flag (reserved in NT; use MFT flag instead). + * FILE_ATTR_ARCHIVE: File needs archiving (backup flag). + * FILE_ATTR_DEVICE: Device file (rarely used). + * FILE_ATTR_NORMAL: Normal file (no special attributes). + * FILE_ATTR_TEMPORARY: Temporary file (delete on close). + * FILE_ATTR_SPARSE_FILE: Sparse file (contains holes). + * FILE_ATTR_REPARSE_POINT: Reparse point (junction, symlink, mount point). + * FILE_ATTR_COMPRESSED: File is compressed. + * FILE_ATTR_OFFLINE: File data is offline (not locally available). + * FILE_ATTR_NOT_CONTENT_INDEXED: + * File is excluded from content indexing. + * FILE_ATTR_ENCRYPTED: File is encrypted (EFS). + * FILE_ATTR_VALID_FLAGS: Mask of all valid flags for reading. + * FILE_ATTR_VALID_SET_FLAGS: Mask of flags that can be set by user. + * FILE_ATTRIBUTE_RECALL_ON_OPEN: + * Recall data on open (cloud/HSM related). + * FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT: + * $FILE_NAME has duplicate index entry. + * FILE_ATTR_DUP_VIEW_INDEX_PRESENT: + * Duplicate view index present (object ID, quota, etc.). + */ +enum { + FILE_ATTR_READONLY = cpu_to_le32(0x00000001), + FILE_ATTR_HIDDEN = cpu_to_le32(0x00000002), + FILE_ATTR_SYSTEM = cpu_to_le32(0x00000004), + /* Old DOS volid. Unused in NT. = cpu_to_le32(0x00000008), */ + FILE_ATTR_DIRECTORY = cpu_to_le32(0x00000010), + FILE_ATTR_ARCHIVE = cpu_to_le32(0x00000020), + FILE_ATTR_DEVICE = cpu_to_le32(0x00000040), + FILE_ATTR_NORMAL = cpu_to_le32(0x00000080), + + FILE_ATTR_TEMPORARY = cpu_to_le32(0x00000100), + FILE_ATTR_SPARSE_FILE = cpu_to_le32(0x00000200), + FILE_ATTR_REPARSE_POINT = cpu_to_le32(0x00000400), + FILE_ATTR_COMPRESSED = cpu_to_le32(0x00000800), + + FILE_ATTR_OFFLINE = cpu_to_le32(0x00001000), + FILE_ATTR_NOT_CONTENT_INDEXED = cpu_to_le32(0x00002000), + FILE_ATTR_ENCRYPTED = cpu_to_le32(0x00004000), + + FILE_ATTR_VALID_FLAGS = cpu_to_le32(0x00007fb7), + FILE_ATTR_VALID_SET_FLAGS = cpu_to_le32(0x000031a7), + FILE_ATTRIBUTE_RECALL_ON_OPEN = cpu_to_le32(0x00040000), + FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT = cpu_to_le32(0x10000000), + FILE_ATTR_DUP_VIEW_INDEX_PRESENT = cpu_to_le32(0x20000000), +}; + +/* + * NOTE on times in NTFS: All times are in MS standard time format, i.e. they + * are the number of 100-nanosecond intervals since 1st January 1601, 00:00:00 + * universal coordinated time (UTC). (In Linux time starts 1st January 1970, + * 00:00:00 UTC and is stored as the number of 1-second intervals since then.) + */ + +/* + * struct standard_information - $STANDARD_INFORMATION attribute content + * + * NOTE: Always resident. + * NOTE: Present in all base file records on a volume. + * NOTE: There is conflicting information about the meaning of each of the time + * fields but the meaning as defined below has been verified to be + * correct by practical experimentation on Windows NT4 SP6a and is hence + * assumed to be the one and only correct interpretation. + * + * @creation_time: File creation time (NTFS timestamp). + * Updated on filename change(?). + * @last_data_change_time: Last modification time of data streams. + * @last_mft_change_time: Last modification time of this MFT record. + * @last_access_time: Last access time (approximate). + * Not updated on read-only volumes; can be disabled. + * @file_attributes: File attribute flags (FILE_ATTR_* bits). + * + * Union (version-specific fields): + * @ver.v1.reserved12: 12 bytes reserved/alignment (NTFS 1.2 only). + * + * @ver.v3 (NTFS 3.x / Windows 2000+): + * @maximum_versions: Max allowed file versions (0 = disabled). + * @version_number: Current version number (0 if disabled). + * @class_id: Class ID (from bidirectional index?). + * @owner_id: Owner ID (maps to $Quota via $Q index). + * @security_id: Security ID (maps to $Secure $SII/$SDS). + * @quota_charged: Quota charge in bytes (0 if quotas disabled). + * @usn: Last USN from $UsnJrnl (0 if disabled). + */ +struct standard_information { + __le64 creation_time; + __le64 last_data_change_time; + __le64 last_mft_change_time; + __le64 last_access_time; + __le32 file_attributes; + union { + struct { + u8 reserved12[12]; + } __packed v1; + struct { + __le32 maximum_versions; + __le32 version_number; + __le32 class_id; + __le32 owner_id; + __le32 security_id; + __le64 quota_charged; + __le64 usn; + } __packed v3; + } __packed ver; +} __packed; + +/* + * struct attr_list_entry - Entry in $ATTRIBUTE_LIST attribute. + * + * @type: Attribute type code (ATTR_TYPE_*). + * @length: Byte size of this entry (8-byte aligned). + * @name_length: Unicode char count of attribute name (0 if unnamed). + * @name_offset: Byte offset from start of entry to name (always set). + * @lowest_vcn: Lowest VCN of this attribute extent (usually 0). + * Signed value; non-zero when attribute spans extents. + * @mft_reference: MFT record reference holding this attribute extent. + * @instance: Attribute instance number (if lowest_vcn == 0); else 0. + * @name: Variable Unicode name (use @name_offset when reading). + * + * - Can be either resident or non-resident. + * - Value consists of a sequence of variable length, 8-byte aligned, + * ATTR_LIST_ENTRY records. + * - The list is not terminated by anything at all! The only way to know when + * the end is reached is to keep track of the current offset and compare it to + * the attribute value size. + * - The attribute list attribute contains one entry for each attribute of + * the file in which the list is located, except for the list attribute + * itself. The list is sorted: first by attribute type, second by attribute + * name (if present), third by instance number. The extents of one + * non-resident attribute (if present) immediately follow after the initial + * extent. They are ordered by lowest_vcn and have their instance set to zero. + * It is not allowed to have two attributes with all sorting keys equal. + * - Further restrictions: + * - If not resident, the vcn to lcn mapping array has to fit inside the + * base mft record. + * - The attribute list attribute value has a maximum size of 256kb. This + * is imposed by the Windows cache manager. + * - Attribute lists are only used when the attributes of mft record do not + * fit inside the mft record despite all attributes (that can be made + * non-resident) having been made non-resident. This can happen e.g. when: + * - File has a large number of hard links (lots of file name + * attributes present). + * - The mapping pairs array of some non-resident attribute becomes so + * large due to fragmentation that it overflows the mft record. + * - The security descriptor is very complex (not applicable to + * NTFS 3.0 volumes). + * - There are many named streams. + */ +struct attr_list_entry { + __le32 type; + __le16 length; + u8 name_length; + u8 name_offset; + __le64 lowest_vcn; + __le64 mft_reference; + __le16 instance; + __le16 name[]; +} __packed; + +/* + * The maximum allowed length for a file name. + */ +#define MAXIMUM_FILE_NAME_LENGTH 255 + +/* + * enum - Possible namespaces for filenames in ntfs (8-bit). + * + * FILE_NAME_POSIX POSIX namespace (case sensitive, most permissive). + * Allows all Unicode except '\0' and '/'. + * WinNT/2k/2003 default utilities ignore case + * differences. SFU (Services For Unix) enables true + * case sensitivity. + * SFU restricts some chars: '"', '/', '<', '>', '\'. + * FILE_NAME_WIN32 Standard WinNT/2k long filename namespace + * (case insensitive). + * Disallows '\0', '"', '*', '/', ':', '<', '>', '?', + * '\', '|'. Names cannot end with '.' or space. + * FILE_NAME_DOS DOS 8.3 namespace (uppercase only). + * Allows 8-bit chars > space except '"', '*', '+', + * ',', '/', ':', ';', '<', '=', '>', '?', '\'. + * FILE_NAME_WIN32_AND_DOS + * Win32 and DOS names are identical (single record). + * Value 0x03 indicates both are stored in one entry. + */ +enum { + FILE_NAME_POSIX = 0x00, + FILE_NAME_WIN32 = 0x01, + FILE_NAME_DOS = 0x02, + FILE_NAME_WIN32_AND_DOS = 0x03, +} __packed; + +/* + * struct file_name_attr - $FILE_NAME attribute content + * + * NOTE: Always resident. + * NOTE: All fields, except the parent_directory, are only updated when the + * filename is changed. Until then, they just become out of sync with + * reality and the more up to date values are present in the standard + * information attribute. + * NOTE: There is conflicting information about the meaning of each of the time + * fields but the meaning as defined below has been verified to be + * correct by practical experimentation on Windows NT4 SP6a and is hence + * assumed to be the one and only correct interpretation. + * + * @parent_directory: MFT reference to parent directory. + * @creation_time: File creation time (NTFS timestamp). + * @last_data_change_time: + * Last data modification time. + * @last_mft_change_time: + * Last MFT record modification time. + * @last_access_time: Last access time (approximate; may not + * update always). + * @allocated_size: On-disk allocated size for unnamed $DATA. + * Equals compressed_size if compressed/sparse. + * 0 for directories or no $DATA. + * Multiple of cluster size. + * @data_size: Logical size of unnamed $DATA. + * 0 for directories or no $DATA. + * @file_attributes: File attribute flags (FILE_ATTR_* bits). + * @type.ea.packed_ea_size: + * Size needed to pack EAs (if present). + * @type.ea.reserved: Alignment padding. + * @type.rp.reparse_point_tag: + * Reparse point type (if reparse point, no EAs). + * @file_name_length: Length of filename in Unicode characters. + * @file_name_type: Namespace (FILE_NAME_POSIX, WIN32, DOS, etc.). + * @file_name: Variable-length Unicode filename. + */ +struct file_name_attr { + __le64 parent_directory; + __le64 creation_time; + __le64 last_data_change_time; + __le64 last_mft_change_time; + __le64 last_access_time; + __le64 allocated_size; + __le64 data_size; + __le32 file_attributes; + union { + struct { + __le16 packed_ea_size; + __le16 reserved; + } __packed ea; + struct { + __le32 reparse_point_tag; + } __packed rp; + } __packed type; + u8 file_name_length; + u8 file_name_type; + __le16 file_name[]; +} __packed; + +/* + * struct guid - Globally Unique Identifier (GUID) structure + * + * GUID structures store globally unique identifiers (GUID). A GUID is a + * 128-bit value consisting of one group of eight hexadecimal digits, followed + * by three groups of four hexadecimal digits each, followed by one group of + * twelve hexadecimal digits. GUIDs are Microsoft's implementation of the + * distributed computing environment (DCE) universally unique identifier (UUID). + * Example of a GUID: + * 1F010768-5A73-BC91-0010A52216A7 + * + * @data1: First 32 bits (first 8 hex digits). + * @data2: Next 16 bits (first group of 4 hex digits). + * @data3: Next 16 bits (second group of 4 hex digits). + * @data4: Final 64 bits (third group of 4 + last 12 hex digits). + * data4[0-1]: third group; data4[2-7]: remaining part. + */ +struct guid { + __le32 data1; + __le16 data2; + __le16 data3; + u8 data4[8]; +} __packed; + +/* + * struct object_id_attr - $OBJECT_ID attribute content (NTFS 3.0+) + * + * NOTE: Always resident. + * + * @object_id: Unique 128-bit GUID assigned to the file. + * Core identifier; always present. + * + * Optional extended info (union; total value size 16–64 bytes): + * @extended_info.birth_volume_id: + * Birth volume GUID (where file was first created). + * @extended_info.birth_object_id: + * Birth object GUID (original ID before copy/move). + * @extended_info.domain_id: + * Domain GUID (usually zero; reserved). + */ +struct object_id_attr { + struct guid object_id; + union { + struct { + struct guid birth_volume_id; + struct guid birth_object_id; + struct guid domain_id; + } __packed; + u8 extended_info[48]; + } __packed; +} __packed; + +/* + * enum - RIDs (Relative Identifiers) in Windows/NTFS security + * + * These relative identifiers (RIDs) are used with the above identifier + * authorities to make up universal well-known SIDs. + * + * SECURITY_NULL_RID S-1-0 (Null authority) + * SECURITY_WORLD_RID S-1-1 (World/Everyone) + * SECURITY_LOCAL_RID S-1-2 (Local) + * SECURITY_CREATOR_OWNER_RID S-1-3-0 (Creator Owner) + * SECURITY_CREATOR_GROUP_RID S-1-3-1 (Creator Group) + * SECURITY_CREATOR_OWNER_SERVER_RID S-1-3-2 (Server Creator Owner) + * SECURITY_CREATOR_GROUP_SERVER_RID S-1-3-3 (Server Creator Group) + * SECURITY_DIALUP_RID S-1-5-1 (Dialup) + * SECURITY_NETWORK_RID S-1-5-2 (Network) + * SECURITY_BATCH_RID S-1-5-3 (Batch) + * SECURITY_INTERACTIVE_RID S-1-5-4 (Interactive) + * SECURITY_SERVICE_RID S-1-5-6 (Service) + * SECURITY_ANONYMOUS_LOGON_RID S-1-5-7 (Anonymous Logon) + * SECURITY_PROXY_RID S-1-5-8 (Proxy) + * SECURITY_ENTERPRISE_CONTROLLERS_RID S-1-5-9 (Enterprise DCs) + * SECURITY_SERVER_LOGON_RID S-1-5-9 (Server Logon alias) + * SECURITY_PRINCIPAL_SELF_RID S-1-5-10 (Self/PrincipalSelf) + * SECURITY_AUTHENTICATED_USER_RID S-1-5-11 (Authenticated Users) + * SECURITY_RESTRICTED_CODE_RID S-1-5-12 (Restricted Code) + * SECURITY_TERMINAL_SERVER_RID S-1-5-13 (Terminal Server) + * SECURITY_LOGON_IDS_RID S-1-5-5 (Logon session IDs base) + * SECURITY_LOCAL_SYSTEM_RID S-1-5-18 (Local System) + * SECURITY_NT_NON_UNIQUE S-1-5-21 (NT non-unique authority) + * SECURITY_BUILTIN_DOMAIN_RID S-1-5-32 (Built-in domain) + * + * Built-in domain relative RIDs (S-1-5-32-...): + * Users: + * DOMAIN_USER_RID_ADMIN Administrator + * DOMAIN_USER_RID_GUEST Guest + * DOMAIN_USER_RID_KRBTGT krbtgt (Kerberos ticket-granting) + * + * Groups: + * DOMAIN_GROUP_RID_ADMINS Administrators + * DOMAIN_GROUP_RID_USERS Users + * DOMAIN_GROUP_RID_GUESTS Guests + * DOMAIN_GROUP_RID_COMPUTERS Computers + * DOMAIN_GROUP_RID_CONTROLLERS Domain Controllers + * DOMAIN_GROUP_RID_CERT_ADMINS Cert Publishers + * DOMAIN_GROUP_RID_SCHEMA_ADMINS Schema Admins + * DOMAIN_GROUP_RID_ENTERPRISE_ADMINS Enterprise Admins + * DOMAIN_GROUP_RID_POLICY_ADMINS Policy Admins (if present) + * + * Aliases: + * DOMAIN_ALIAS_RID_ADMINS Administrators alias + * DOMAIN_ALIAS_RID_USERS Users alias + * DOMAIN_ALIAS_RID_GUESTS Guests alias + * DOMAIN_ALIAS_RID_POWER_USERS Power Users + * DOMAIN_ALIAS_RID_ACCOUNT_OPS Account Operators + * DOMAIN_ALIAS_RID_SYSTEM_OPS Server Operators + * DOMAIN_ALIAS_RID_PRINT_OPS Print Operators + * DOMAIN_ALIAS_RID_BACKUP_OPS Backup Operators + * DOMAIN_ALIAS_RID_REPLICATOR Replicator + * DOMAIN_ALIAS_RID_RAS_SERVERS RAS Servers + * DOMAIN_ALIAS_RID_PREW2KCOMPACCESS Pre-Windows 2000 Compatible Access + * + * Note: The relative identifier (RID) refers to the portion of a SID, which + * identifies a user or group in relation to the authority that issued the SID. + * For example, the universal well-known SID Creator Owner ID (S-1-3-0) is + * made up of the identifier authority SECURITY_CREATOR_SID_AUTHORITY (3) and + * the relative identifier SECURITY_CREATOR_OWNER_RID (0). + */ +enum { /* Identifier authority. */ + SECURITY_NULL_RID = 0, /* S-1-0 */ + SECURITY_WORLD_RID = 0, /* S-1-1 */ + SECURITY_LOCAL_RID = 0, /* S-1-2 */ + + SECURITY_CREATOR_OWNER_RID = 0, /* S-1-3 */ + SECURITY_CREATOR_GROUP_RID = 1, /* S-1-3 */ + + SECURITY_CREATOR_OWNER_SERVER_RID = 2, /* S-1-3 */ + SECURITY_CREATOR_GROUP_SERVER_RID = 3, /* S-1-3 */ + + SECURITY_DIALUP_RID = 1, + SECURITY_NETWORK_RID = 2, + SECURITY_BATCH_RID = 3, + SECURITY_INTERACTIVE_RID = 4, + SECURITY_SERVICE_RID = 6, + SECURITY_ANONYMOUS_LOGON_RID = 7, + SECURITY_PROXY_RID = 8, + SECURITY_ENTERPRISE_CONTROLLERS_RID = 9, + SECURITY_SERVER_LOGON_RID = 9, + SECURITY_PRINCIPAL_SELF_RID = 0xa, + SECURITY_AUTHENTICATED_USER_RID = 0xb, + SECURITY_RESTRICTED_CODE_RID = 0xc, + SECURITY_TERMINAL_SERVER_RID = 0xd, + + SECURITY_LOGON_IDS_RID = 5, + SECURITY_LOGON_IDS_RID_COUNT = 3, + + SECURITY_LOCAL_SYSTEM_RID = 0x12, + + SECURITY_NT_NON_UNIQUE = 0x15, + + SECURITY_BUILTIN_DOMAIN_RID = 0x20, + + /* + * Well-known domain relative sub-authority values (RIDs). + */ + + /* Users. */ + DOMAIN_USER_RID_ADMIN = 0x1f4, + DOMAIN_USER_RID_GUEST = 0x1f5, + DOMAIN_USER_RID_KRBTGT = 0x1f6, + + /* Groups. */ + DOMAIN_GROUP_RID_ADMINS = 0x200, + DOMAIN_GROUP_RID_USERS = 0x201, + DOMAIN_GROUP_RID_GUESTS = 0x202, + DOMAIN_GROUP_RID_COMPUTERS = 0x203, + DOMAIN_GROUP_RID_CONTROLLERS = 0x204, + DOMAIN_GROUP_RID_CERT_ADMINS = 0x205, + DOMAIN_GROUP_RID_SCHEMA_ADMINS = 0x206, + DOMAIN_GROUP_RID_ENTERPRISE_ADMINS = 0x207, + DOMAIN_GROUP_RID_POLICY_ADMINS = 0x208, + + /* Aliases. */ + DOMAIN_ALIAS_RID_ADMINS = 0x220, + DOMAIN_ALIAS_RID_USERS = 0x221, + DOMAIN_ALIAS_RID_GUESTS = 0x222, + DOMAIN_ALIAS_RID_POWER_USERS = 0x223, + + DOMAIN_ALIAS_RID_ACCOUNT_OPS = 0x224, + DOMAIN_ALIAS_RID_SYSTEM_OPS = 0x225, + DOMAIN_ALIAS_RID_PRINT_OPS = 0x226, + DOMAIN_ALIAS_RID_BACKUP_OPS = 0x227, + + DOMAIN_ALIAS_RID_REPLICATOR = 0x228, + DOMAIN_ALIAS_RID_RAS_SERVERS = 0x229, + DOMAIN_ALIAS_RID_PREW2KCOMPACCESS = 0x22a, +}; + +/* + * The universal well-known SIDs: + * + * NULL_SID S-1-0-0 + * WORLD_SID S-1-1-0 + * LOCAL_SID S-1-2-0 + * CREATOR_OWNER_SID S-1-3-0 + * CREATOR_GROUP_SID S-1-3-1 + * CREATOR_OWNER_SERVER_SID S-1-3-2 + * CREATOR_GROUP_SERVER_SID S-1-3-3 + * + * (Non-unique IDs) S-1-4 + * + * NT well-known SIDs: + * + * NT_AUTHORITY_SID S-1-5 + * DIALUP_SID S-1-5-1 + * + * NETWORD_SID S-1-5-2 + * BATCH_SID S-1-5-3 + * INTERACTIVE_SID S-1-5-4 + * SERVICE_SID S-1-5-6 + * ANONYMOUS_LOGON_SID S-1-5-7 (aka null logon session) + * PROXY_SID S-1-5-8 + * SERVER_LOGON_SID S-1-5-9 (aka domain controller account) + * SELF_SID S-1-5-10 (self RID) + * AUTHENTICATED_USER_SID S-1-5-11 + * RESTRICTED_CODE_SID S-1-5-12 (running restricted code) + * TERMINAL_SERVER_SID S-1-5-13 (running on terminal server) + * + * (Logon IDs) S-1-5-5-X-Y + * + * (NT non-unique IDs) S-1-5-0x15-... + * + * (Built-in domain) S-1-5-0x20 + */ + +/* + * struct ntfs_sid - Security Identifier (SID) structure + * + * @revision: SID revision level (usually 1). + * @sub_authority_count: Number of sub-authorities (1 or more). + * @identifier_authority: + * 48-bit identifier authority (S-1-x-...). + * @parts.high_part: high 16 bits. + * @parts.low_part: low 32 bits. + * @value: raw 6-byte array. + * @sub_authority: Variable array of 32-bit RIDs. + * At least one; defines the SID relative to authority. + * + * The SID structure is a variable-length structure used to uniquely identify + * users or groups. SID stands for security identifier. + * + * The standard textual representation of the SID is of the form: + * S-R-I-S-S... + * Where: + * - The first "S" is the literal character 'S' identifying the following + * digits as a SID. + * - R is the revision level of the SID expressed as a sequence of digits + * either in decimal or hexadecimal (if the later, prefixed by "0x"). + * - I is the 48-bit identifier_authority, expressed as digits as R above. + * - S... is one or more sub_authority values, expressed as digits as above. + * + * Example SID; the domain-relative SID of the local Administrators group on + * Windows NT/2k: + * S-1-5-32-544 + * This translates to a SID with: + * revision = 1, + * sub_authority_count = 2, + * identifier_authority = {0,0,0,0,0,5}, // SECURITY_NT_AUTHORITY + * sub_authority[0] = 32, // SECURITY_BUILTIN_DOMAIN_RID + * sub_authority[1] = 544 // DOMAIN_ALIAS_RID_ADMINS + */ +struct ntfs_sid { + u8 revision; + u8 sub_authority_count; + union { + struct { + u16 high_part; + u32 low_part; + } __packed parts; + u8 value[6]; + } identifier_authority; + __le32 sub_authority[]; +} __packed; + +/* + * enum - Predefined ACE types (8-bit) for NTFS security descriptors + * + * ACCESS_MIN_MS_ACE_TYPE: Minimum MS ACE type (0). + * ACCESS_ALLOWED_ACE_TYPE: Allow access (standard ACE). + * ACCESS_DENIED_ACE_TYPE: Deny access (standard ACE). + * SYSTEM_AUDIT_ACE_TYPE: Audit successful/failed access. + * SYSTEM_ALARM_ACE_TYPE: Alarm on access (not in Win2k+). + * ACCESS_MAX_MS_V2_ACE_TYPE: Max for V2 ACE types. + * ACCESS_ALLOWED_COMPOUND_ACE_TYPE: + * Compound ACE (legacy). + * ACCESS_MAX_MS_V3_ACE_TYPE: Max for V3 ACE types. + * ACCESS_MIN_MS_OBJECT_ACE_TYPE: Min for object ACE types (Win2k+). + * ACCESS_ALLOWED_OBJECT_ACE_TYPE: Allow with object-specific rights. + * ACCESS_DENIED_OBJECT_ACE_TYPE: Deny with object-specific rights. + * SYSTEM_AUDIT_OBJECT_ACE_TYPE: Audit with object-specific rights. + * SYSTEM_ALARM_OBJECT_ACE_TYPE: Alarm with object-specific rights. + * ACCESS_MAX_MS_OBJECT_ACE_TYPE: Max for object ACE types. + * ACCESS_MAX_MS_V4_ACE_TYPE: Max for V4 ACE types. + * ACCESS_MAX_MS_ACE_TYPE: Overall max ACE type (WinNT/2k). + */ +enum { + ACCESS_MIN_MS_ACE_TYPE = 0, + ACCESS_ALLOWED_ACE_TYPE = 0, + ACCESS_DENIED_ACE_TYPE = 1, + SYSTEM_AUDIT_ACE_TYPE = 2, + SYSTEM_ALARM_ACE_TYPE = 3, + ACCESS_MAX_MS_V2_ACE_TYPE = 3, + + ACCESS_ALLOWED_COMPOUND_ACE_TYPE = 4, + ACCESS_MAX_MS_V3_ACE_TYPE = 4, + ACCESS_MIN_MS_OBJECT_ACE_TYPE = 5, + ACCESS_ALLOWED_OBJECT_ACE_TYPE = 5, + ACCESS_DENIED_OBJECT_ACE_TYPE = 6, + SYSTEM_AUDIT_OBJECT_ACE_TYPE = 7, + SYSTEM_ALARM_OBJECT_ACE_TYPE = 8, + ACCESS_MAX_MS_OBJECT_ACE_TYPE = 8, + + ACCESS_MAX_MS_V4_ACE_TYPE = 8, + ACCESS_MAX_MS_ACE_TYPE = 8, +} __packed; + +/* + * enum - ACE inheritance and audit flags (8-bit) + * + * OBJECT_INHERIT_ACE: Object inherit (files inherit this ACE). + * CONTAINER_INHERIT_ACE: Container inherit (subdirectories inherit). + * NO_PROPAGATE_INHERIT_ACE: No propagation (stop inheritance after this level). + * INHERIT_ONLY_ACE: Inherit only (not applied to current object). + * INHERITED_ACE: ACE was inherited (Win2k+ only). + * VALID_INHERIT_FLAGS: Mask of all valid inheritance flags (0x1f). + * SUCCESSFUL_ACCESS_ACE_FLAG: Audit successful access (system audit ACE). + * FAILED_ACCESS_ACE_FLAG: Audit failed access (system audit ACE). + * + * SUCCESSFUL_ACCESS_ACE_FLAG is only used with system audit and alarm ACE + * types to indicate that a message is generated (in Windows!) for successful + * accesses. + * + * FAILED_ACCESS_ACE_FLAG is only used with system audit and alarm ACE types + * to indicate that a message is generated (in Windows!) for failed accesses. + */ +enum { + OBJECT_INHERIT_ACE = 0x01, + CONTAINER_INHERIT_ACE = 0x02, + NO_PROPAGATE_INHERIT_ACE = 0x04, + INHERIT_ONLY_ACE = 0x08, + INHERITED_ACE = 0x10, + VALID_INHERIT_FLAGS = 0x1f, + SUCCESSFUL_ACCESS_ACE_FLAG = 0x40, + FAILED_ACCESS_ACE_FLAG = 0x80, +} __packed; + +/* + * enum - NTFS access rights masks (32-bit) + * + * FILE_READ_DATA / FILE_LIST_DIRECTORY: Read file data / list dir contents. + * FILE_WRITE_DATA / FILE_ADD_FILE: Write file data / create file in dir. + * FILE_APPEND_DATA / FILE_ADD_SUBDIRECTORY: Append data / create subdir. + * FILE_READ_EA: Read extended attributes. + * FILE_WRITE_EA: Write extended attributes. + * FILE_EXECUTE / FILE_TRAVERSE: Execute file / traverse dir. + * FILE_DELETE_CHILD: Delete children in dir. + * FILE_READ_ATTRIBUTES: Read attributes. + * FILE_WRITE_ATTRIBUTES: Write attributes. + * + * Standard rights (object-independent): + * DELETE: Delete object. + * READ_CONTROL: Read security descriptor/owner. + * WRITE_DAC: Modify DACL. + * WRITE_OWNER: Change owner. + * SYNCHRONIZE: Wait on object signal state. + * + * Combinations: + * STANDARD_RIGHTS_READ / WRITE / EXECUTE: Aliases for READ_CONTROL. + * STANDARD_RIGHTS_REQUIRED: DELETE + READ_CONTROL + + * WRITE_DAC + WRITE_OWNER. + * STANDARD_RIGHTS_ALL: Above + SYNCHRONIZE. + * + * System/access types: + * ACCESS_SYSTEM_SECURITY: Access system ACL. + * MAXIMUM_ALLOWED: Maximum allowed access. + * + * Generic rights (high bits, map to specific/standard): + * GENERIC_ALL: Full access. + * GENERIC_EXECUTE: Execute/traverse. + * GENERIC_WRITE: Write (append, attrs, data, EA, etc.). + * GENERIC_READ: Read (attrs, data, EA, etc.). + * + * The specific rights (bits 0 to 15). These depend on the type of the object + * being secured by the ACE. + */ +enum { + FILE_READ_DATA = cpu_to_le32(0x00000001), + FILE_LIST_DIRECTORY = cpu_to_le32(0x00000001), + FILE_WRITE_DATA = cpu_to_le32(0x00000002), + FILE_ADD_FILE = cpu_to_le32(0x00000002), + FILE_APPEND_DATA = cpu_to_le32(0x00000004), + FILE_ADD_SUBDIRECTORY = cpu_to_le32(0x00000004), + FILE_READ_EA = cpu_to_le32(0x00000008), + FILE_WRITE_EA = cpu_to_le32(0x00000010), + FILE_EXECUTE = cpu_to_le32(0x00000020), + FILE_TRAVERSE = cpu_to_le32(0x00000020), + FILE_DELETE_CHILD = cpu_to_le32(0x00000040), + FILE_READ_ATTRIBUTES = cpu_to_le32(0x00000080), + FILE_WRITE_ATTRIBUTES = cpu_to_le32(0x00000100), + DELETE = cpu_to_le32(0x00010000), + READ_CONTROL = cpu_to_le32(0x00020000), + WRITE_DAC = cpu_to_le32(0x00040000), + WRITE_OWNER = cpu_to_le32(0x00080000), + SYNCHRONIZE = cpu_to_le32(0x00100000), + STANDARD_RIGHTS_READ = cpu_to_le32(0x00020000), + STANDARD_RIGHTS_WRITE = cpu_to_le32(0x00020000), + STANDARD_RIGHTS_EXECUTE = cpu_to_le32(0x00020000), + STANDARD_RIGHTS_REQUIRED = cpu_to_le32(0x000f0000), + STANDARD_RIGHTS_ALL = cpu_to_le32(0x001f0000), + ACCESS_SYSTEM_SECURITY = cpu_to_le32(0x01000000), + MAXIMUM_ALLOWED = cpu_to_le32(0x02000000), + GENERIC_ALL = cpu_to_le32(0x10000000), + GENERIC_EXECUTE = cpu_to_le32(0x20000000), + GENERIC_WRITE = cpu_to_le32(0x40000000), + GENERIC_READ = cpu_to_le32(0x80000000), +}; + +/* + * struct ntfs_ace - Access Control Entry (ACE) structure + * + * @type: ACE type (ACCESS_ALLOWED_ACE_TYPE, ACCESS_DENIED_ACE_TYPE, etc.). + * @flags: Inheritance and audit flags (OBJECT_INHERIT_ACE, etc.). + * @size: Total byte size of this ACE (header + SID + variable data). + * @mask: Access rights mask (FILE_READ_DATA, DELETE, GENERIC_ALL, etc.). + * @sid: Security Identifier (SID) this ACE applies to. + */ +struct ntfs_ace { + u8 type; + u8 flags; + __le16 size; + __le32 mask; + struct ntfs_sid sid; +} __packed; + +/* + * The object ACE flags (32-bit). + */ +enum { + ACE_OBJECT_TYPE_PRESENT = cpu_to_le32(1), + ACE_INHERITED_OBJECT_TYPE_PRESENT = cpu_to_le32(2), +}; + +/* + * struct ntfs_acl - NTFS Access Control List (ACL) header + * + * An ACL is an access-control list (ACL). + * An ACL starts with an ACL header structure, which specifies the size of + * the ACL and the number of ACEs it contains. The ACL header is followed by + * zero or more access control entries (ACEs). The ACL as well as each ACE + * are aligned on 4-byte boundaries. + * + * @revision: ACL revision level (usually 2 or 4). + * @alignment1: Padding/alignment byte (zero). + * @size: Total allocated size in bytes (header + all ACEs + + * free space). + * @ace_count: Number of ACE entries following the header. + * @alignment2: Padding/alignment (zero). + */ +struct ntfs_acl { + u8 revision; + u8 alignment1; + __le16 size; + __le16 ace_count; + __le16 alignment2; +} __packed; + +static_assert(sizeof(struct ntfs_acl) == 8); + +/* + * The security descriptor control flags (16-bit). + * + * SE_OWNER_DEFAULTED - This boolean flag, when set, indicates that the SID + * pointed to by the Owner field was provided by a defaulting mechanism + * rather than explicitly provided by the original provider of the + * security descriptor. This may affect the treatment of the SID with + * respect to inheritance of an owner. + * + * SE_GROUP_DEFAULTED - This boolean flag, when set, indicates that the SID in + * the Group field was provided by a defaulting mechanism rather than + * explicitly provided by the original provider of the security + * descriptor. This may affect the treatment of the SID with respect to + * inheritance of a primary group. + * + * SE_DACL_PRESENT - This boolean flag, when set, indicates that the security + * descriptor contains a discretionary ACL. If this flag is set and the + * Dacl field of the SECURITY_DESCRIPTOR is null, then a null ACL is + * explicitly being specified. + * + * SE_DACL_DEFAULTED - This boolean flag, when set, indicates that the ACL + * pointed to by the Dacl field was provided by a defaulting mechanism + * rather than explicitly provided by the original provider of the + * security descriptor. This may affect the treatment of the ACL with + * respect to inheritance of an ACL. This flag is ignored if the + * DaclPresent flag is not set. + * + * SE_SACL_PRESENT - This boolean flag, when set, indicates that the security + * descriptor contains a system ACL pointed to by the Sacl field. If this + * flag is set and the Sacl field of the SECURITY_DESCRIPTOR is null, then + * an empty (but present) ACL is being specified. + * + * SE_SACL_DEFAULTED - This boolean flag, when set, indicates that the ACL + * pointed to by the Sacl field was provided by a defaulting mechanism + * rather than explicitly provided by the original provider of the + * security descriptor. This may affect the treatment of the ACL with + * respect to inheritance of an ACL. This flag is ignored if the + * SaclPresent flag is not set. + * + * SE_SELF_RELATIVE - This boolean flag, when set, indicates that the security + * descriptor is in self-relative form. In this form, all fields of the + * security descriptor are contiguous in memory and all pointer fields are + * expressed as offsets from the beginning of the security descriptor. + */ +enum { + SE_OWNER_DEFAULTED = cpu_to_le16(0x0001), + SE_GROUP_DEFAULTED = cpu_to_le16(0x0002), + SE_DACL_PRESENT = cpu_to_le16(0x0004), + SE_DACL_DEFAULTED = cpu_to_le16(0x0008), + + SE_SACL_PRESENT = cpu_to_le16(0x0010), + SE_SACL_DEFAULTED = cpu_to_le16(0x0020), + + SE_DACL_AUTO_INHERIT_REQ = cpu_to_le16(0x0100), + SE_SACL_AUTO_INHERIT_REQ = cpu_to_le16(0x0200), + SE_DACL_AUTO_INHERITED = cpu_to_le16(0x0400), + SE_SACL_AUTO_INHERITED = cpu_to_le16(0x0800), + + SE_DACL_PROTECTED = cpu_to_le16(0x1000), + SE_SACL_PROTECTED = cpu_to_le16(0x2000), + SE_RM_CONTROL_VALID = cpu_to_le16(0x4000), + SE_SELF_RELATIVE = cpu_to_le16(0x8000) +} __packed; + +/* + * struct security_descriptor_relative - Relative security descriptor + * + * Self-relative security descriptor. Contains the owner and group SIDs as well + * as the sacl and dacl ACLs inside the security descriptor itself. + * + * @revision: Security descriptor revision (usually 1). + * @alignment: Padding/alignment byte (zero). + * @control: Control flags (SE_OWNER_DEFAULTED, SE_DACL_PRESENT, + * SE_SACL_PRESENT, SE_SACL_AUTO_INHERITED, etc.). + * @owner: Byte offset to owner SID (from start of descriptor). + * 0 if no owner SID present. + * @group: Byte offset to primary group SID. + * 0 if no group SID present. + * @sacl: Byte offset to System ACL (SACL). + * Valid only if SE_SACL_PRESENT in @control. + * 0 means NULL SACL. + * @dacl: Byte offset to Discretionary ACL (DACL). + * Valid only if SE_DACL_PRESENT in @control. + * 0 means NULL DACL (full access granted). + */ +struct security_descriptor_relative { + u8 revision; + u8 alignment; + __le16 control; + __le32 owner; + __le32 group; + __le32 sacl; + __le32 dacl; +} __packed; + +static_assert(sizeof(struct security_descriptor_relative) == 20); + +/* + * On NTFS 3.0+, all security descriptors are stored in FILE_Secure. Only one + * referenced instance of each unique security descriptor is stored. + * + * FILE_Secure contains no unnamed data attribute, i.e. it has zero length. It + * does, however, contain two indexes ($SDH and $SII) as well as a named data + * stream ($SDS). + * + * Every unique security descriptor is assigned a unique security identifier + * (security_id, not to be confused with a SID). The security_id is unique for + * the NTFS volume and is used as an index into the $SII index, which maps + * security_ids to the security descriptor's storage location within the $SDS + * data attribute. The $SII index is sorted by ascending security_id. + * + * A simple hash is computed from each security descriptor. This hash is used + * as an index into the $SDH index, which maps security descriptor hashes to + * the security descriptor's storage location within the $SDS data attribute. + * The $SDH index is sorted by security descriptor hash and is stored in a B+ + * tree. When searching $SDH (with the intent of determining whether or not a + * new security descriptor is already present in the $SDS data stream), if a + * matching hash is found, but the security descriptors do not match, the + * search in the $SDH index is continued, searching for a next matching hash. + * + * When a precise match is found, the security_id coresponding to the security + * descriptor in the $SDS attribute is read from the found $SDH index entry and + * is stored in the $STANDARD_INFORMATION attribute of the file/directory to + * which the security descriptor is being applied. The $STANDARD_INFORMATION + * attribute is present in all base mft records (i.e. in all files and + * directories). + * + * If a match is not found, the security descriptor is assigned a new unique + * security_id and is added to the $SDS data attribute. Then, entries + * referencing the this security descriptor in the $SDS data attribute are + * added to the $SDH and $SII indexes. + * + * Note: Entries are never deleted from FILE_Secure, even if nothing + * references an entry any more. + */ + +/* + * struct sii_index_key - Key for $SII index in $Secure file + * + * The index entry key used in the $SII index. The collation type is + * COLLATION_NTOFS_ULONG. + * + * @security_id: 32-bit security identifier. + * Unique ID assigned to a security descriptor. + */ +struct sii_index_key { + __le32 security_id; +} __packed; + +/* + * struct sdh_index_key - Key for $SDH index in $Secure file + * + * The index entry key used in the $SDH index. The keys are sorted first by + * hash and then by security_id. The collation rule is + * COLLATION_NTOFS_SECURITY_HASH. + * + * @hash: 32-bit hash of the security descriptor. + * Used for quick collision checks and indexing. + * @security_id: 32-bit security identifier. + * Unique ID assigned to the descriptor. + */ +struct sdh_index_key { + __le32 hash; + __le32 security_id; +} __packed; + +/* + * enum - NTFS volume flags (16-bit) + * + * These flags are stored in $VolumeInformation attribute. + * They indicate volume state and required actions. + * + * VOLUME_IS_DIRTY: Volume is dirty (needs chkdsk). + * VOLUME_RESIZE_LOG_FILE: Resize LogFile on next mount. + * VOLUME_UPGRADE_ON_MOUNT: Upgrade volume on mount (old NTFS). + * VOLUME_MOUNTED_ON_NT4: Mounted on NT4 (compatibility flag). + * VOLUME_DELETE_USN_UNDERWAY: USN journal deletion in progress. + * VOLUME_REPAIR_OBJECT_ID: Repair $ObjId on next mount. + * VOLUME_CHKDSK_UNDERWAY: Chkdsk is running. + * VOLUME_MODIFIED_BY_CHKDSK: Modified by chkdsk. + * VOLUME_FLAGS_MASK: Mask of all valid flags (0xc03f). + * VOLUME_MUST_MOUNT_RO_MASK: Flags forcing read-only mount (0xc027). + * If any set, mount read-only. + */ +enum { + VOLUME_IS_DIRTY = cpu_to_le16(0x0001), + VOLUME_RESIZE_LOG_FILE = cpu_to_le16(0x0002), + VOLUME_UPGRADE_ON_MOUNT = cpu_to_le16(0x0004), + VOLUME_MOUNTED_ON_NT4 = cpu_to_le16(0x0008), + + VOLUME_DELETE_USN_UNDERWAY = cpu_to_le16(0x0010), + VOLUME_REPAIR_OBJECT_ID = cpu_to_le16(0x0020), + + VOLUME_CHKDSK_UNDERWAY = cpu_to_le16(0x4000), + VOLUME_MODIFIED_BY_CHKDSK = cpu_to_le16(0x8000), + + VOLUME_FLAGS_MASK = cpu_to_le16(0xc03f), + + VOLUME_MUST_MOUNT_RO_MASK = cpu_to_le16(0xc027), +} __packed; + +/* + * struct volume_information - $VOLUME_INFORMATION (0x70) + * + * @reserved: Reserved 64-bit field (currently unused). + * @major_ver: Major NTFS version number (e.g., 3 for NTFS 3.1). + * @minor_ver: Minor NTFS version number (e.g., 1 for NTFS 3.1). + * @flags: Volume flags (VOLUME_IS_DIRTY, VOLUME_CHKDSK_UNDERWAY, etc.). + * See volume flags enum for details. + * + * NOTE: Always resident. + * NOTE: Present only in FILE_Volume. + * NOTE: Windows 2000 uses NTFS 3.0 while Windows NT4 service pack 6a uses + * NTFS 1.2. I haven't personally seen other values yet. + */ +struct volume_information { + __le64 reserved; + u8 major_ver; + u8 minor_ver; + __le16 flags; +} __packed; + +/* + * enum - Index header flags + * + * These flags are stored in the index header (INDEX_HEADER.flags) for both + * index root ($INDEX_ROOT) and index allocation blocks ($INDEX_ALLOCATION). + * + * For index root ($INDEX_ROOT attribute): + * SMALL_INDEX: Index fits entirely in root attribute (no $INDEX_ALLOCATION). + * LARGE_INDEX: Index too large for root; $INDEX_ALLOCATION present. + * + * For index blocks ($INDEX_ALLOCATION): + * LEAF_NODE: Leaf node (no child nodes; contains actual entries). + * INDEX_NODE: Internal node (indexes other nodes; contains keys/pointers). + * + * NODE_MASK: Mask to extract node type bits (0x01). + */ +enum { + SMALL_INDEX = 0, + LARGE_INDEX = 1, + LEAF_NODE = 0, + INDEX_NODE = 1, + NODE_MASK = 1, +} __packed; + +/* + * struct index_header - Common header for index root and index blocks + * + * entries_offset: Byte offset to first INDEX_ENTRY (8-byte aligned). + * index_length: Bytes used by index entries (8-byte aligned). + * From entries_offset to end of used data. + * allocated_size: Total allocated bytes for this index block. + * Fixed size in index allocation; dynamic in root. + * flags: Index flags (SMALL_INDEX, LARGE_INDEX, LEAF_NODE, etc.). + * See INDEX_HEADER_FLAGS enum. + * reserved: 3 bytes reserved/padding (zero, 8-byte aligned). + * + * This is the header for indexes, describing the INDEX_ENTRY records, which + * follow the index_header. Together the index header and the index entries + * make up a complete index. + * + * IMPORTANT NOTE: The offset, length and size structure members are counted + * relative to the start of the index header structure and not relative to the + * start of the index root or index allocation structures themselves. + * + * For the index root attribute, the above two numbers are always + * equal, as the attribute is resident and it is resized as needed. In + * the case of the index allocation attribute the attribute is not + * resident and hence the allocated_size is a fixed value and must + * equal the index_block_size specified by the INDEX_ROOT attribute + * corresponding to the INDEX_ALLOCATION attribute this INDEX_BLOCK + * belongs to. + */ +struct index_header { + __le32 entries_offset; + __le32 index_length; + __le32 allocated_size; + u8 flags; + u8 reserved[3]; +} __packed; + +/* + * struct index_root - $INDEX_ROOT attribute (0x90). + * + * @type: Indexed attribute type ($FILE_NAME for dirs, + * 0 for view indexes). + * @collation_rule: Collation rule for sorting entries + * (COLLATION_FILE_NAME for $FILE_NAME). + * @index_block_size: Size of each index block in bytes + * (in $INDEX_ALLOCATION). + * @clusters_per_index_block: + * Clusters per index block (or log2(bytes) + * if < cluster). + * Power of 2; used for encoding block size. + * @reserved: 3 bytes reserved/alignment (zero). + * @index: Index header for root entries (entries follow + * immediately). + * + * NOTE: Always resident. + * + * This is followed by a sequence of index entries (INDEX_ENTRY structures) + * as described by the index header. + * + * When a directory is small enough to fit inside the index root then this + * is the only attribute describing the directory. When the directory is too + * large to fit in the index root, on the other hand, two additional attributes + * are present: an index allocation attribute, containing sub-nodes of the B+ + * directory tree (see below), and a bitmap attribute, describing which virtual + * cluster numbers (vcns) in the index allocation attribute are in use by an + * index block. + * + * NOTE: The root directory (FILE_root) contains an entry for itself. Other + * directories do not contain entries for themselves, though. + */ +struct index_root { + __le32 type; + __le32 collation_rule; + __le32 index_block_size; + u8 clusters_per_index_block; + u8 reserved[3]; + struct index_header index; +} __packed; + +/* + * struct index_block - Index allocation (0xa0). + * + * @magic: Magic value "INDX" (see magic_INDX). + * @usa_ofs: Offset to Update Sequence Array (see ntfs_record). + * @usa_count: Number of USA entries (see ntfs_record). + * @lsn: Log sequence number of last modification. + * @index_block_vcn: VCN of this index block. + * Units: clusters if cluster_size <= index_block_size; + * sectors otherwise. + * @index: Index header describing entries in this block. + * + * When creating the index block, we place the update sequence array at this + * offset, i.e. before we start with the index entries. This also makes sense, + * otherwise we could run into problems with the update sequence array + * containing in itself the last two bytes of a sector which would mean that + * multi sector transfer protection wouldn't work. As you can't protect data + * by overwriting it since you then can't get it back... + * When reading use the data from the ntfs record header. + * + * NOTE: Always non-resident (doesn't make sense to be resident anyway!). + * + * This is an array of index blocks. Each index block starts with an + * index_block structure containing an index header, followed by a sequence of + * index entries (INDEX_ENTRY structures), as described by the struct index_header. + */ +struct index_block { + __le32 magic; + __le16 usa_ofs; + __le16 usa_count; + __le64 lsn; + __le64 index_block_vcn; + struct index_header index; +} __packed; + +static_assert(sizeof(struct index_block) == 40); + +/* + * struct reparse_index_key - Key for $R reparse index in $Extend/$Reparse + * + * @reparse_tag: Reparse point type (including flags, REPARSE_TAG_*). + * @file_id: MFT record number of the file with $REPARSE_POINT + * attribute. + * + * The system file FILE_Extend/$Reparse contains an index named $R listing + * all reparse points on the volume. The index entry keys are as defined + * below. Note, that there is no index data associated with the index entries. + * + * The index entries are sorted by the index key file_id. The collation rule is + * COLLATION_NTOFS_ULONGS. + */ +struct reparse_index_key { + __le32 reparse_tag; + __le64 file_id; +} __packed; + +/* + * enum - Quota entry flags (32-bit) in $Quota/$Q + * + * These flags are stored in quota control entries ($Quota file). + * They control quota tracking, limits, and state. + * + * User quota flags (mask 0x00000007): + * @QUOTA_FLAG_DEFAULT_LIMITS: Use default limits. + * @QUOTA_FLAG_LIMIT_REACHED: Quota limit reached. + * @QUOTA_FLAG_ID_DELETED: Quota ID deleted. + * @QUOTA_FLAG_USER_MASK: Mask for user quota flags (0x00000007). + * + * Default entry flags (owner_id = QUOTA_DEFAULTS_ID): + * @QUOTA_FLAG_TRACKING_ENABLED: Quota tracking enabled. + * @QUOTA_FLAG_ENFORCEMENT_ENABLED: Quota enforcement enabled. + * @QUOTA_FLAG_TRACKING_REQUESTED: Tracking requested (pending). + * @QUOTA_FLAG_LOG_THRESHOLD: Log when threshold reached. + * @QUOTA_FLAG_LOG_LIMIT: Log when limit reached. + * @QUOTA_FLAG_OUT_OF_DATE: Quota data out of date. + * @QUOTA_FLAG_CORRUPT: Quota entry corrupt. + * @QUOTA_FLAG_PENDING_DELETES: Pending quota deletes. + * + */ +enum { + QUOTA_FLAG_DEFAULT_LIMITS = cpu_to_le32(0x00000001), + QUOTA_FLAG_LIMIT_REACHED = cpu_to_le32(0x00000002), + QUOTA_FLAG_ID_DELETED = cpu_to_le32(0x00000004), + + QUOTA_FLAG_USER_MASK = cpu_to_le32(0x00000007), + QUOTA_FLAG_TRACKING_ENABLED = cpu_to_le32(0x00000010), + QUOTA_FLAG_ENFORCEMENT_ENABLED = cpu_to_le32(0x00000020), + QUOTA_FLAG_TRACKING_REQUESTED = cpu_to_le32(0x00000040), + QUOTA_FLAG_LOG_THRESHOLD = cpu_to_le32(0x00000080), + + QUOTA_FLAG_LOG_LIMIT = cpu_to_le32(0x00000100), + QUOTA_FLAG_OUT_OF_DATE = cpu_to_le32(0x00000200), + QUOTA_FLAG_CORRUPT = cpu_to_le32(0x00000400), + QUOTA_FLAG_PENDING_DELETES = cpu_to_le32(0x00000800), +}; + +/* + * struct quota_control_entry - Quota entry in $Quota/$Q + * + * @version: Currently 2. + * @flags: Quota flags (QUOTA_FLAG_* bits). + * @bytes_used: Current quota usage in bytes. + * @change_time: Last modification time (NTFS timestamp). + * @threshold: Soft quota limit (-1 = unlimited). + * @limit: Hard quota limit (-1 = unlimited). + * @exceeded_time: Time soft quota has been exceeded. + * @sid: SID of user/object (zero for defaults entry). + * + * The system file FILE_Extend/$Quota contains two indexes $O and $Q. Quotas + * are on a per volume and per user basis. + * + * The $Q index contains one entry for each existing user_id on the volume. The + * index key is the user_id of the user/group owning this quota control entry, + * i.e. the key is the owner_id. The user_id of the owner of a file, i.e. the + * owner_id, is found in the standard information attribute. The collation rule + * for $Q is COLLATION_NTOFS_ULONG. + * + * The $O index contains one entry for each user/group who has been assigned + * a quota on that volume. The index key holds the SID of the user_id the + * entry belongs to, i.e. the owner_id. The collation rule for $O is + * COLLATION_NTOFS_SID. + * + * The $O index entry data is the user_id of the user corresponding to the SID. + * This user_id is used as an index into $Q to find the quota control entry + * associated with the SID. + * + * The $Q index entry data is the quota control entry and is defined below. + */ +struct quota_control_entry { + __le32 version; + __le32 flags; + __le64 bytes_used; + __le64 change_time; + __le64 threshold; + __le64 limit; + __le64 exceeded_time; + struct ntfs_sid sid; +} __packed; + +/* + * Predefined owner_id values (32-bit). + */ +enum { + QUOTA_INVALID_ID = cpu_to_le32(0x00000000), + QUOTA_DEFAULTS_ID = cpu_to_le32(0x00000001), + QUOTA_FIRST_USER_ID = cpu_to_le32(0x00000100), +}; + +/* + * Current constants for quota control entries. + */ +enum { + /* Current version. */ + QUOTA_VERSION = 2, +}; + +/* + * enum - Index entry flags (16-bit) + * + * These flags are in INDEX_ENTRY.flags (after key data). + * They describe entry type and status in index blocks/root. + * + * @INDEX_ENTRY_NODE: Entry points to a sub-node (index block VCN). + * (Not a leaf entry; internal node reference.) + * i.e. a reference to an index block in form of + * a virtual cluster number + * @INDEX_ENTRY_END: Last entry in index block/root. + * Does not represent a real file; can point to sub-node. + * @INDEX_ENTRY_SPACE_FILLER: + * Dummy value to force enum to 16-bit width. + */ +enum { + INDEX_ENTRY_NODE = cpu_to_le16(1), + INDEX_ENTRY_END = cpu_to_le16(2), + INDEX_ENTRY_SPACE_FILLER = cpu_to_le16(0xffff), +} __packed; + +/* + * struct index_entry_header - Common header for all NTFS index entries + * + * This is the fixed header at the start of every INDEX_ENTRY in index + * blocks or index root. It is followed by the variable key, data, and + * sub-node VCN. + * + * Union @data: + * - When INDEX_ENTRY_END is not set: + * @data.dir.indexed_file: MFT reference of the file described by + * this entry. Used in directory indexes ($I30). + * - When INDEX_ENTRY_END is set or for view indexes: + * @data.vi.data_offset: Byte offset from end of this header to + * entry data. + * @data.vi.data_length: Length of data in bytes. + * @data.vi.reservedV: Reserved (zero). + * + * @length: Total byte size of this index entry + * (multiple of 8 bytes). + * @key_length: Byte size of the key (not multiple of 8 bytes). + * Key follows the header immediately. + * @flags: Bit field of INDEX_ENTRY_* flags (INDEX_ENTRY_NODE, etc.). + * @reserved: Reserved/padding (zero; align to 8 bytes). + */ +struct index_entry_header { + union { + struct { + __le64 indexed_file; + } __packed dir; + struct { + __le16 data_offset; + __le16 data_length; + __le32 reservedV; + } __packed vi; + } __packed data; + __le16 length; + __le16 key_length; + __le16 flags; + __le16 reserved; +} __packed; + +static_assert(sizeof(struct index_entry_header) == 16); + +/* + * struct index_entry - NTFS index entry structure + * + * This is an index entry. A sequence of such entries follows each index_header + * structure. Together they make up a complete index. The index follows either + * an index root attribute or an index allocation attribute. + * + * Union @data (valid when INDEX_ENTRY_END not set): + * @data.dir.indexed_file: MFT ref of file (for directory indexes). + * @data.vi.data_offset: Offset to data after key. + * @data.vi.data_length: Length of data in bytes. + * @data.vi.reservedV: Reserved (zero). + * + * Fields: + * @length: Total byte size of entry (multiple of 8 bytes). + * @key_length: Byte size of key (not multiple of 8). + * @flags: INDEX_ENTRY_* flags (NODE, END, etc.). + * @reserved: Reserved/padding (zero). + * + * Union @key (valid when INDEX_ENTRY_END not set) + * The key of the indexed attribute. NOTE: Only present + * if INDEX_ENTRY_END bit in flags is not set. NOTE: On + * NTFS versions before 3.0 the only valid key is the + * struct file_name_attr. On NTFS 3.0+ the following + * additional index keys are defined: + * @key.file_name: $FILE_NAME attr (for $I30 directory indexes). + * @key.sii: $SII key (for $Secure $SII index). + * @key.sdh: $SDH key (for $Secure $SDH index). + * @key.object_id: GUID (for $ObjId $O index). + * @key.reparse: Reparse tag + file ID (for $Reparse $R). + * @key.sid: SID (for $Quota $O index). + * @key.owner_id: User ID (for $Quota $Q index). + * + * The (optional) index data is inserted here when creating. + * __le64 vcn; If INDEX_ENTRY_NODE bit in flags is set, the last + * eight bytes of this index entry contain the virtual + * cluster number of the index block that holds the + * entries immediately preceding the current entry (the + * vcn references the corresponding cluster in the data + * of the non-resident index allocation attribute). If + * the key_length is zero, then the vcn immediately + * follows the INDEX_ENTRY_HEADER. Regardless of + * key_length, the address of the 8-byte boundary + * aligned vcn of INDEX_ENTRY{_HEADER} *ie is given by + * (char*)ie + le16_to_cpu(ie*)->length) - sizeof(VCN), + * where sizeof(VCN) can be hardcoded as 8 if wanted. + * + * NOTE: Before NTFS 3.0 only filename attributes were indexed. + */ +struct index_entry { + union { + struct { + __le64 indexed_file; + } __packed dir; + struct { + __le16 data_offset; + __le16 data_length; + __le32 reservedV; + } __packed vi; + } __packed data; + __le16 length; + __le16 key_length; + __le16 flags; + __le16 reserved; + union { + struct file_name_attr file_name; + struct sii_index_key sii; + struct sdh_index_key sdh; + struct guid object_id; + struct reparse_index_key reparse; + struct ntfs_sid sid; + __le32 owner_id; + } __packed key; +} __packed; + +/* + * The reparse point tag defines the type of the reparse point. It also + * includes several flags, which further describe the reparse point. + * + * The reparse point tag is an unsigned 32-bit value divided in three parts: + * + * 1. The least significant 16 bits (i.e. bits 0 to 15) specify the type of + * the reparse point. + * 2. The 12 bits after this (i.e. bits 16 to 27) are reserved for future use. + * 3. The most significant four bits are flags describing the reparse point. + * They are defined as follows: + * bit 28: Directory bit. If set, the directory is not a surrogate + * and can be used the usual way. + * bit 29: Name surrogate bit. If set, the filename is an alias for + * another object in the system. + * bit 30: High-latency bit. If set, accessing the first byte of data will + * be slow. (E.g. the data is stored on a tape drive.) + * bit 31: Microsoft bit. If set, the tag is owned by Microsoft. User + * defined tags have to use zero here. + * 4. Moreover, on Windows 10 : + * Some flags may be used in bits 12 to 15 to further describe the + * reparse point. + */ +enum { + IO_REPARSE_TAG_DIRECTORY = cpu_to_le32(0x10000000), + IO_REPARSE_TAG_IS_ALIAS = cpu_to_le32(0x20000000), + IO_REPARSE_TAG_IS_HIGH_LATENCY = cpu_to_le32(0x40000000), + IO_REPARSE_TAG_IS_MICROSOFT = cpu_to_le32(0x80000000), + + IO_REPARSE_TAG_RESERVED_ZERO = cpu_to_le32(0x00000000), + IO_REPARSE_TAG_RESERVED_ONE = cpu_to_le32(0x00000001), + IO_REPARSE_TAG_RESERVED_RANGE = cpu_to_le32(0x00000001), + + IO_REPARSE_TAG_CSV = cpu_to_le32(0x80000009), + IO_REPARSE_TAG_DEDUP = cpu_to_le32(0x80000013), + IO_REPARSE_TAG_DFS = cpu_to_le32(0x8000000A), + IO_REPARSE_TAG_DFSR = cpu_to_le32(0x80000012), + IO_REPARSE_TAG_HSM = cpu_to_le32(0xC0000004), + IO_REPARSE_TAG_HSM2 = cpu_to_le32(0x80000006), + IO_REPARSE_TAG_MOUNT_POINT = cpu_to_le32(0xA0000003), + IO_REPARSE_TAG_NFS = cpu_to_le32(0x80000014), + IO_REPARSE_TAG_SIS = cpu_to_le32(0x80000007), + IO_REPARSE_TAG_SYMLINK = cpu_to_le32(0xA000000C), + IO_REPARSE_TAG_WIM = cpu_to_le32(0x80000008), + IO_REPARSE_TAG_DFM = cpu_to_le32(0x80000016), + IO_REPARSE_TAG_WOF = cpu_to_le32(0x80000017), + IO_REPARSE_TAG_WCI = cpu_to_le32(0x80000018), + IO_REPARSE_TAG_CLOUD = cpu_to_le32(0x9000001A), + IO_REPARSE_TAG_APPEXECLINK = cpu_to_le32(0x8000001B), + IO_REPARSE_TAG_GVFS = cpu_to_le32(0x9000001C), + IO_REPARSE_TAG_LX_SYMLINK = cpu_to_le32(0xA000001D), + IO_REPARSE_TAG_AF_UNIX = cpu_to_le32(0x80000023), + IO_REPARSE_TAG_LX_FIFO = cpu_to_le32(0x80000024), + IO_REPARSE_TAG_LX_CHR = cpu_to_le32(0x80000025), + IO_REPARSE_TAG_LX_BLK = cpu_to_le32(0x80000026), + + IO_REPARSE_TAG_VALID_VALUES = cpu_to_le32(0xf000ffff), + IO_REPARSE_PLUGIN_SELECT = cpu_to_le32(0xffff0fff), +}; + +/* + * struct reparse_point - $REPARSE_POINT attribute content (0xc0)\ + * + * @reparse_tag: Reparse point type (with flags; REPARSE_TAG_*). + * @reparse_data_length: Byte size of @reparse_data. + * @reserved: Reserved/padding (zero; 8-byte alignment). + * @reparse_data: Variable reparse data (meaning depends on @reparse_tag). + * - Symbolic link/junction: struct reparse_symlink + * - Mount point: similar symlink structure + * - Other tags: vendor-specific or extended data + * + * NOTE: Can be resident or non-resident. + */ +struct reparse_point { + __le32 reparse_tag; + __le16 reparse_data_length; + __le16 reserved; + u8 reparse_data[]; +} __packed; + +/* + * struct ea_information - $EA_INFORMATION attribute content (0xd0) + * + * @ea_length: Byte size of packed EAs. + * @need_ea_count: Number of EAs with NEED_EA bit set. + * @ea_query_length: Byte size needed to unpack/query EAs via ZwQueryEaFile(). + * (Unpacked format size.) + * + * NOTE: Always resident. (Is this true???) + */ +struct ea_information { + __le16 ea_length; + __le16 need_ea_count; + __le32 ea_query_length; +} __packed; + +/* + * enum - Extended attribute flags (8-bit) + * + * These flags are stored in the EA header of each extended attribute + * (in $EA attribute, type 0xe0). + * + * @NEED_EA: If set, the file cannot be properly interpreted + * without understanding its associated EAs. + * (Critical EA; applications must process it.) + */ +enum { + NEED_EA = 0x80 +} __packed; + +/* + * struct ea_attr - Extended attribute (EA) entry (0xe0) + * + * @next_entry_offset: Byte offset to the next EA_ATTR entry. + * (From start of current entry.) + * @flags: EA flags (NEED_EA = 0x80 if critical). + * @ea_name_length: Length of @ea_name in bytes (excluding '\0'). + * @ea_value_length: Byte size of the EA value. + * @ea_name: ASCII name of the EA (zero-terminated). + * Value immediately follows the name. + * u8 ea_value[]; The value of the EA. Immediately follows the name. + * + * This is one variable-length record in the $EA attribute value. + * The attribute can be resident or non-resident. + * Sequence of these entries forms the packed EA list. + * + * NOTE: Can be resident or non-resident. + */ +struct ea_attr { + __le32 next_entry_offset; + u8 flags; + u8 ea_name_length; + __le16 ea_value_length; + u8 ea_name[]; +} __packed; + +#endif /* _LINUX_NTFS_LAYOUT_H */ diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c new file mode 100644 index 000000000000..835a041023a2 --- /dev/null +++ b/fs/ntfs/lcnalloc.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Cluster (de)allocation code. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2025 LG Electronics Co., Ltd. + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2002-2004 Anton Altaparmakov + * Copyright (c) 2004 Yura Pakhuchiy + * Copyright (c) 2004-2008 Szabolcs Szakacsits + * Copyright (c) 2008-2009 Jean-Pierre Andre + */ + +#include + +#include "lcnalloc.h" +#include "bitmap.h" +#include "ntfs.h" + +/* + * ntfs_cluster_free_from_rl_nolock - free clusters from runlist + * @vol: mounted ntfs volume on which to free the clusters + * @rl: runlist describing the clusters to free + * + * Free all the clusters described by the runlist @rl on the volume @vol. In + * the case of an error being returned, at least some of the clusters were not + * freed. + * + * Return 0 on success and -errno on error. + * + * Locking: - The volume lcn bitmap must be locked for writing on entry and is + * left locked on return. + */ +int ntfs_cluster_free_from_rl_nolock(struct ntfs_volume *vol, + const struct runlist_element *rl) +{ + struct inode *lcnbmp_vi = vol->lcnbmp_ino; + int ret = 0; + s64 nr_freed = 0; + + ntfs_debug("Entering."); + if (!rl) + return 0; + + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + + for (; rl->length; rl++) { + int err; + + if (rl->lcn < 0) + continue; + err = ntfs_bitmap_clear_run(lcnbmp_vi, rl->lcn, rl->length); + if (unlikely(err && (!ret || ret == -ENOMEM) && ret != err)) + ret = err; + else + nr_freed += rl->length; + } + ntfs_inc_free_clusters(vol, nr_freed); + ntfs_debug("Done."); + return ret; +} + +static s64 max_empty_bit_range(unsigned char *buf, int size) +{ + int i, j, run = 0; + int max_range = 0; + s64 start_pos = -1; + + ntfs_debug("Entering\n"); + + i = 0; + while (i < size) { + switch (*buf) { + case 0: + do { + buf++; + run += 8; + i++; + } while ((i < size) && !*buf); + break; + case 255: + if (run > max_range) { + max_range = run; + start_pos = (s64)i * 8 - run; + } + run = 0; + do { + buf++; + i++; + } while ((i < size) && (*buf == 255)); + break; + default: + for (j = 0; j < 8; j++) { + int bit = *buf & (1 << j); + + if (bit) { + if (run > max_range) { + max_range = run; + start_pos = (s64)i * 8 + (j - run); + } + run = 0; + } else + run++; + } + i++; + buf++; + } + } + + if (run > max_range) + start_pos = (s64)i * 8 - run; + + return start_pos; +} + +/* + * ntfs_cluster_alloc - allocate clusters on an ntfs volume + * @vol: mounted ntfs volume on which to allocate clusters + * @start_vcn: vcn of the first allocated cluster + * @count: number of clusters to allocate + * @start_lcn: starting lcn at which to allocate the clusters or -1 if none + * @zone: zone from which to allocate (MFT_ZONE or DATA_ZONE) + * @is_extension: if true, the caller is extending an attribute + * @is_contig: if true, require contiguous allocation + * @is_dealloc: if true, the allocation is for deallocation purposes + * + * Allocate @count clusters preferably starting at cluster @start_lcn or at the + * current allocator position if @start_lcn is -1, on the mounted ntfs volume + * @vol. @zone is either DATA_ZONE for allocation of normal clusters or + * MFT_ZONE for allocation of clusters for the master file table, i.e. the + * $MFT/$DATA attribute. + * + * @start_vcn specifies the vcn of the first allocated cluster. This makes + * merging the resulting runlist with the old runlist easier. + * + * If @is_extension is 'true', the caller is allocating clusters to extend an + * attribute and if it is 'false', the caller is allocating clusters to fill a + * hole in an attribute. Practically the difference is that if @is_extension + * is 'true' the returned runlist will be terminated with LCN_ENOENT and if + * @is_extension is 'false' the runlist will be terminated with + * LCN_RL_NOT_MAPPED. + * + * You need to check the return value with IS_ERR(). If this is false, the + * function was successful and the return value is a runlist describing the + * allocated cluster(s). If IS_ERR() is true, the function failed and + * PTR_ERR() gives you the error code. + * + * Notes on the allocation algorithm + * ================================= + * + * There are two data zones. First is the area between the end of the mft zone + * and the end of the volume, and second is the area between the start of the + * volume and the start of the mft zone. On unmodified/standard NTFS 1.x + * volumes, the second data zone does not exist due to the mft zone being + * expanded to cover the start of the volume in order to reserve space for the + * mft bitmap attribute. + * + * This is not the prettiest function but the complexity stems from the need of + * implementing the mft vs data zoned approach and from the fact that we have + * access to the lcn bitmap in portions of up to 8192 bytes at a time, so we + * need to cope with crossing over boundaries of two buffers. Further, the + * fact that the allocator allows for caller supplied hints as to the location + * of where allocation should begin and the fact that the allocator keeps track + * of where in the data zones the next natural allocation should occur, + * contribute to the complexity of the function. But it should all be + * worthwhile, because this allocator should: 1) be a full implementation of + * the MFT zone approach used by Windows NT, 2) cause reduction in + * fragmentation, and 3) be speedy in allocations (the code is not optimized + * for speed, but the algorithm is, so further speed improvements are probably + * possible). + * + * Locking: - The volume lcn bitmap must be unlocked on entry and is unlocked + * on return. + * - This function takes the volume lcn bitmap lock for writing and + * modifies the bitmap contents. + * + * Return: Runlist describing the allocated cluster(s) on success, error pointer + * on failure. + */ +struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 start_vcn, + const s64 count, const s64 start_lcn, + const int zone, + const bool is_extension, + const bool is_contig, + const bool is_dealloc) +{ + s64 zone_start, zone_end, bmp_pos, bmp_initial_pos, last_read_pos, lcn; + s64 prev_lcn = 0, prev_run_len = 0, mft_zone_size; + s64 clusters, free_clusters; + loff_t i_size; + struct inode *lcnbmp_vi; + struct runlist_element *rl = NULL; + struct address_space *mapping; + struct folio *folio = NULL; + u8 *buf = NULL, *byte; + int err = 0, rlpos, rlsize, buf_size, pg_off; + u8 pass, done_zones, search_zone, need_writeback = 0, bit; + unsigned int memalloc_flags; + u8 has_guess, used_zone_pos; + pgoff_t index; + + ntfs_debug("Entering for start_vcn 0x%llx, count 0x%llx, start_lcn 0x%llx, zone %s_ZONE.", + start_vcn, count, start_lcn, + zone == MFT_ZONE ? "MFT" : "DATA"); + + lcnbmp_vi = vol->lcnbmp_ino; + if (start_vcn < 0 || start_lcn < LCN_HOLE || + zone < FIRST_ZONE || zone > LAST_ZONE) + return ERR_PTR(-EINVAL); + + /* Return NULL if @count is zero. */ + if (count < 0 || !count) + return ERR_PTR(-EINVAL); + + memalloc_flags = memalloc_nofs_save(); + + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + free_clusters = atomic64_read(&vol->free_clusters); + + /* Take the lcnbmp lock for writing. */ + down_write(&vol->lcnbmp_lock); + if (is_dealloc == false) + free_clusters -= atomic64_read(&vol->dirty_clusters); + + if (free_clusters < count) { + err = -ENOSPC; + goto out_restore; + } + + /* + * If no specific @start_lcn was requested, use the current data zone + * position, otherwise use the requested @start_lcn but make sure it + * lies outside the mft zone. Also set done_zones to 0 (no zones done) + * and pass depending on whether we are starting inside a zone (1) or + * at the beginning of a zone (2). If requesting from the MFT_ZONE, + * we either start at the current position within the mft zone or at + * the specified position. If the latter is out of bounds then we start + * at the beginning of the MFT_ZONE. + */ + done_zones = 0; + pass = 1; + /* + * zone_start and zone_end are the current search range. search_zone + * is 1 for mft zone, 2 for data zone 1 (end of mft zone till end of + * volume) and 4 for data zone 2 (start of volume till start of mft + * zone). + */ + has_guess = 1; + zone_start = start_lcn; + + if (zone_start < 0) { + if (zone == DATA_ZONE) + zone_start = vol->data1_zone_pos; + else + zone_start = vol->mft_zone_pos; + if (!zone_start) { + /* + * Zone starts at beginning of volume which means a + * single pass is sufficient. + */ + pass = 2; + } + has_guess = 0; + } + + used_zone_pos = has_guess ? 0 : 1; + + if (!zone_start || zone_start == vol->mft_zone_start || + zone_start == vol->mft_zone_end) + pass = 2; + + if (zone_start < vol->mft_zone_start) { + zone_end = vol->mft_zone_start; + search_zone = 4; + /* Skip searching the mft zone. */ + done_zones |= 1; + } else if (zone_start < vol->mft_zone_end) { + zone_end = vol->mft_zone_end; + search_zone = 1; + } else { + zone_end = vol->nr_clusters; + search_zone = 2; + /* Skip searching the mft zone. */ + done_zones |= 1; + } + + /* + * bmp_pos is the current bit position inside the bitmap. We use + * bmp_initial_pos to determine whether or not to do a zone switch. + */ + bmp_pos = bmp_initial_pos = zone_start; + + /* Loop until all clusters are allocated, i.e. clusters == 0. */ + clusters = count; + rlpos = rlsize = 0; + mapping = lcnbmp_vi->i_mapping; + i_size = i_size_read(lcnbmp_vi); + while (1) { + ntfs_debug("Start of outer while loop: done_zones 0x%x, search_zone %i, pass %i, zone_start 0x%llx, zone_end 0x%llx, bmp_initial_pos 0x%llx, bmp_pos 0x%llx, rlpos %i, rlsize %i.", + done_zones, search_zone, pass, + zone_start, zone_end, bmp_initial_pos, + bmp_pos, rlpos, rlsize); + /* Loop until we run out of free clusters. */ + last_read_pos = bmp_pos >> 3; + ntfs_debug("last_read_pos 0x%llx.", last_read_pos); + if (last_read_pos >= i_size) { + ntfs_debug("End of attribute reached. Skipping to zone_pass_done."); + goto zone_pass_done; + } + if (likely(folio)) { + if (need_writeback) { + ntfs_debug("Marking page dirty."); + folio_mark_dirty(folio); + need_writeback = 0; + } + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + folio = NULL; + } + + index = last_read_pos >> PAGE_SHIFT; + pg_off = last_read_pos & ~PAGE_MASK; + buf_size = PAGE_SIZE - pg_off; + if (unlikely(last_read_pos + buf_size > i_size)) + buf_size = i_size - last_read_pos; + buf_size <<= 3; + lcn = bmp_pos & 7; + bmp_pos &= ~(s64)7; + + if (vol->lcn_empty_bits_per_page[index] == 0) + goto next_bmp_pos; + + folio = read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) { + err = PTR_ERR(folio); + ntfs_error(vol->sb, "Failed to map page."); + goto out; + } + + folio_lock(folio); + buf = kmap_local_folio(folio, 0) + pg_off; + ntfs_debug("Before inner while loop: buf_size %i, lcn 0x%llx, bmp_pos 0x%llx, need_writeback %i.", + buf_size, lcn, bmp_pos, need_writeback); + while (lcn < buf_size && lcn + bmp_pos < zone_end) { + byte = buf + (lcn >> 3); + ntfs_debug("In inner while loop: buf_size %i, lcn 0x%llx, bmp_pos 0x%llx, need_writeback %i, byte ofs 0x%x, *byte 0x%x.", + buf_size, lcn, bmp_pos, need_writeback, + (unsigned int)(lcn >> 3), + (unsigned int)*byte); + bit = 1 << (lcn & 7); + ntfs_debug("bit 0x%x.", bit); + + if (has_guess) { + if (*byte & bit) { + if (is_contig == true && prev_run_len > 0) + goto done; + + has_guess = 0; + break; + } + } else { + lcn = max_empty_bit_range(buf, buf_size >> 3); + if (lcn < 0) + break; + has_guess = 1; + continue; + } + /* + * Allocate more memory if needed, including space for + * the terminator element. + * kvzalloc() operates on whole pages only. + */ + if ((rlpos + 2) * sizeof(*rl) > rlsize) { + struct runlist_element *rl2; + + ntfs_debug("Reallocating memory."); + if (!rl) + ntfs_debug("First free bit is at s64 0x%llx.", + lcn + bmp_pos); + rl2 = kvzalloc(rlsize + PAGE_SIZE, GFP_NOFS); + if (unlikely(!rl2)) { + err = -ENOMEM; + ntfs_error(vol->sb, "Failed to allocate memory."); + goto out; + } + memcpy(rl2, rl, rlsize); + kvfree(rl); + rl = rl2; + rlsize += PAGE_SIZE; + ntfs_debug("Reallocated memory, rlsize 0x%x.", + rlsize); + } + /* Allocate the bitmap bit. */ + *byte |= bit; + /* We need to write this bitmap page to disk. */ + need_writeback = 1; + ntfs_debug("*byte 0x%x, need_writeback is set.", + (unsigned int)*byte); + ntfs_dec_free_clusters(vol, 1); + ntfs_set_lcn_empty_bits(vol, index, 1, 1); + + /* + * Coalesce with previous run if adjacent LCNs. + * Otherwise, append a new run. + */ + ntfs_debug("Adding run (lcn 0x%llx, len 0x%llx), prev_lcn 0x%llx, lcn 0x%llx, bmp_pos 0x%llx, prev_run_len 0x%llx, rlpos %i.", + lcn + bmp_pos, 1ULL, prev_lcn, + lcn, bmp_pos, prev_run_len, rlpos); + if (prev_lcn == lcn + bmp_pos - prev_run_len && rlpos) { + ntfs_debug("Coalescing to run (lcn 0x%llx, len 0x%llx).", + rl[rlpos - 1].lcn, + rl[rlpos - 1].length); + rl[rlpos - 1].length = ++prev_run_len; + ntfs_debug("Run now (lcn 0x%llx, len 0x%llx), prev_run_len 0x%llx.", + rl[rlpos - 1].lcn, + rl[rlpos - 1].length, + prev_run_len); + } else { + if (likely(rlpos)) { + ntfs_debug("Adding new run, (previous run lcn 0x%llx, len 0x%llx).", + rl[rlpos - 1].lcn, rl[rlpos - 1].length); + rl[rlpos].vcn = rl[rlpos - 1].vcn + + prev_run_len; + } else { + ntfs_debug("Adding new run, is first run."); + rl[rlpos].vcn = start_vcn; + } + rl[rlpos].lcn = prev_lcn = lcn + bmp_pos; + rl[rlpos].length = prev_run_len = 1; + rlpos++; + } + /* Done? */ + if (!--clusters) { + s64 tc; +done: + if (!used_zone_pos) + goto out; + /* + * Update the current zone position. Positions + * of already scanned zones have been updated + * during the respective zone switches. + */ + tc = lcn + bmp_pos + 1; + ntfs_debug("Done. Updating current zone position, tc 0x%llx, search_zone %i.", + tc, search_zone); + switch (search_zone) { + case 1: + ntfs_debug("Before checks, vol->mft_zone_pos 0x%llx.", + vol->mft_zone_pos); + if (tc >= vol->mft_zone_end) { + vol->mft_zone_pos = + vol->mft_lcn; + if (!vol->mft_zone_end) + vol->mft_zone_pos = 0; + } else if ((bmp_initial_pos >= + vol->mft_zone_pos || + tc > vol->mft_zone_pos) + && tc >= vol->mft_lcn) + vol->mft_zone_pos = tc; + ntfs_debug("After checks, vol->mft_zone_pos 0x%llx.", + vol->mft_zone_pos); + break; + case 2: + ntfs_debug("Before checks, vol->data1_zone_pos 0x%llx.", + vol->data1_zone_pos); + if (tc >= vol->nr_clusters) + vol->data1_zone_pos = + vol->mft_zone_end; + else if ((bmp_initial_pos >= + vol->data1_zone_pos || + tc > vol->data1_zone_pos) + && tc >= vol->mft_zone_end) + vol->data1_zone_pos = tc; + ntfs_debug("After checks, vol->data1_zone_pos 0x%llx.", + vol->data1_zone_pos); + break; + case 4: + ntfs_debug("Before checks, vol->data2_zone_pos 0x%llx.", + vol->data2_zone_pos); + if (tc >= vol->mft_zone_start) + vol->data2_zone_pos = 0; + else if (bmp_initial_pos >= + vol->data2_zone_pos || + tc > vol->data2_zone_pos) + vol->data2_zone_pos = tc; + ntfs_debug("After checks, vol->data2_zone_pos 0x%llx.", + vol->data2_zone_pos); + break; + default: + WARN_ON(1); + } + ntfs_debug("Finished. Going to out."); + goto out; + } + lcn++; + } + + if (!used_zone_pos) { + used_zone_pos = 1; + if (search_zone == 1) + zone_start = vol->mft_zone_pos; + else if (search_zone == 2) + zone_start = vol->data1_zone_pos; + else + zone_start = vol->data2_zone_pos; + + if (!zone_start || zone_start == vol->mft_zone_start || + zone_start == vol->mft_zone_end) + pass = 2; + bmp_pos = zone_start; + } else { +next_bmp_pos: + bmp_pos += buf_size; + } + + ntfs_debug("After inner while loop: buf_size 0x%x, lcn 0x%llx, bmp_pos 0x%llx, need_writeback %i.", + buf_size, lcn, bmp_pos, need_writeback); + if (bmp_pos < zone_end) { + ntfs_debug("Continuing outer while loop, bmp_pos 0x%llx, zone_end 0x%llx.", + bmp_pos, zone_end); + continue; + } +zone_pass_done: /* Finished with the current zone pass. */ + ntfs_debug("At zone_pass_done, pass %i.", pass); + if (pass == 1) { + /* + * Now do pass 2, scanning the first part of the zone + * we omitted in pass 1. + */ + pass = 2; + zone_end = zone_start; + switch (search_zone) { + case 1: /* mft_zone */ + zone_start = vol->mft_zone_start; + break; + case 2: /* data1_zone */ + zone_start = vol->mft_zone_end; + break; + case 4: /* data2_zone */ + zone_start = 0; + break; + default: + WARN_ON(1); + } + /* Sanity check. */ + if (zone_end < zone_start) + zone_end = zone_start; + bmp_pos = zone_start; + ntfs_debug("Continuing outer while loop, pass 2, zone_start 0x%llx, zone_end 0x%llx, bmp_pos 0x%llx.", + zone_start, zone_end, bmp_pos); + continue; + } /* pass == 2 */ +done_zones_check: + ntfs_debug("At done_zones_check, search_zone %i, done_zones before 0x%x, done_zones after 0x%x.", + search_zone, done_zones, + done_zones | search_zone); + done_zones |= search_zone; + if (done_zones < 7) { + ntfs_debug("Switching zone."); + /* Now switch to the next zone we haven't done yet. */ + pass = 1; + switch (search_zone) { + case 1: + ntfs_debug("Switching from mft zone to data1 zone."); + /* Update mft zone position. */ + if (rlpos && used_zone_pos) { + s64 tc; + + ntfs_debug("Before checks, vol->mft_zone_pos 0x%llx.", + vol->mft_zone_pos); + tc = rl[rlpos - 1].lcn + + rl[rlpos - 1].length; + if (tc >= vol->mft_zone_end) { + vol->mft_zone_pos = + vol->mft_lcn; + if (!vol->mft_zone_end) + vol->mft_zone_pos = 0; + } else if ((bmp_initial_pos >= + vol->mft_zone_pos || + tc > vol->mft_zone_pos) + && tc >= vol->mft_lcn) + vol->mft_zone_pos = tc; + ntfs_debug("After checks, vol->mft_zone_pos 0x%llx.", + vol->mft_zone_pos); + } + /* Switch from mft zone to data1 zone. */ +switch_to_data1_zone: search_zone = 2; + zone_start = bmp_initial_pos = + vol->data1_zone_pos; + zone_end = vol->nr_clusters; + if (zone_start == vol->mft_zone_end) + pass = 2; + if (zone_start >= zone_end) { + vol->data1_zone_pos = zone_start = + vol->mft_zone_end; + pass = 2; + } + break; + case 2: + ntfs_debug("Switching from data1 zone to data2 zone."); + /* Update data1 zone position. */ + if (rlpos && used_zone_pos) { + s64 tc; + + ntfs_debug("Before checks, vol->data1_zone_pos 0x%llx.", + vol->data1_zone_pos); + tc = rl[rlpos - 1].lcn + + rl[rlpos - 1].length; + if (tc >= vol->nr_clusters) + vol->data1_zone_pos = + vol->mft_zone_end; + else if ((bmp_initial_pos >= + vol->data1_zone_pos || + tc > vol->data1_zone_pos) + && tc >= vol->mft_zone_end) + vol->data1_zone_pos = tc; + ntfs_debug("After checks, vol->data1_zone_pos 0x%llx.", + vol->data1_zone_pos); + } + /* Switch from data1 zone to data2 zone. */ + search_zone = 4; + zone_start = bmp_initial_pos = + vol->data2_zone_pos; + zone_end = vol->mft_zone_start; + if (!zone_start) + pass = 2; + if (zone_start >= zone_end) { + vol->data2_zone_pos = zone_start = + bmp_initial_pos = 0; + pass = 2; + } + break; + case 4: + ntfs_debug("Switching from data2 zone to data1 zone."); + /* Update data2 zone position. */ + if (rlpos && used_zone_pos) { + s64 tc; + + ntfs_debug("Before checks, vol->data2_zone_pos 0x%llx.", + vol->data2_zone_pos); + tc = rl[rlpos - 1].lcn + + rl[rlpos - 1].length; + if (tc >= vol->mft_zone_start) + vol->data2_zone_pos = 0; + else if (bmp_initial_pos >= + vol->data2_zone_pos || + tc > vol->data2_zone_pos) + vol->data2_zone_pos = tc; + ntfs_debug("After checks, vol->data2_zone_pos 0x%llx.", + vol->data2_zone_pos); + } + /* Switch from data2 zone to data1 zone. */ + goto switch_to_data1_zone; + default: + WARN_ON(1); + } + ntfs_debug("After zone switch, search_zone %i, pass %i, bmp_initial_pos 0x%llx, zone_start 0x%llx, zone_end 0x%llx.", + search_zone, pass, + bmp_initial_pos, + zone_start, + zone_end); + bmp_pos = zone_start; + if (zone_start == zone_end) { + ntfs_debug("Empty zone, going to done_zones_check."); + /* Empty zone. Don't bother searching it. */ + goto done_zones_check; + } + ntfs_debug("Continuing outer while loop."); + continue; + } /* done_zones == 7 */ + ntfs_debug("All zones are finished."); + /* + * All zones are finished! If DATA_ZONE, shrink mft zone. If + * MFT_ZONE, we have really run out of space. + */ + mft_zone_size = vol->mft_zone_end - vol->mft_zone_start; + ntfs_debug("vol->mft_zone_start 0x%llx, vol->mft_zone_end 0x%llx, mft_zone_size 0x%llx.", + vol->mft_zone_start, vol->mft_zone_end, + mft_zone_size); + if (zone == MFT_ZONE || mft_zone_size <= 0) { + ntfs_debug("No free clusters left, going to out."); + /* Really no more space left on device. */ + err = -ENOSPC; + goto out; + } /* zone == DATA_ZONE && mft_zone_size > 0 */ + ntfs_debug("Shrinking mft zone."); + zone_end = vol->mft_zone_end; + mft_zone_size >>= 1; + if (mft_zone_size > 0) + vol->mft_zone_end = vol->mft_zone_start + mft_zone_size; + else /* mft zone and data2 zone no longer exist. */ + vol->data2_zone_pos = vol->mft_zone_start = + vol->mft_zone_end = 0; + if (vol->mft_zone_pos >= vol->mft_zone_end) { + vol->mft_zone_pos = vol->mft_lcn; + if (!vol->mft_zone_end) + vol->mft_zone_pos = 0; + } + bmp_pos = zone_start = bmp_initial_pos = + vol->data1_zone_pos = vol->mft_zone_end; + search_zone = 2; + pass = 2; + done_zones &= ~2; + ntfs_debug("After shrinking mft zone, mft_zone_size 0x%llx, vol->mft_zone_start 0x%llx, vol->mft_zone_end 0x%llx, vol->mft_zone_pos 0x%llx, search_zone 2, pass 2, dones_zones 0x%x, zone_start 0x%llx, zone_end 0x%llx, vol->data1_zone_pos 0x%llx, continuing outer while loop.", + mft_zone_size, vol->mft_zone_start, + vol->mft_zone_end, vol->mft_zone_pos, + done_zones, zone_start, zone_end, + vol->data1_zone_pos); + } + ntfs_debug("After outer while loop."); +out: + ntfs_debug("At out."); + /* Add runlist terminator element. */ + if (likely(rl)) { + rl[rlpos].vcn = rl[rlpos - 1].vcn + rl[rlpos - 1].length; + rl[rlpos].lcn = is_extension ? LCN_ENOENT : LCN_RL_NOT_MAPPED; + rl[rlpos].length = 0; + } + if (!IS_ERR_OR_NULL(folio)) { + if (need_writeback) { + ntfs_debug("Marking page dirty."); + folio_mark_dirty(folio); + need_writeback = 0; + } + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + } + if (likely(!err)) { + if (!rl) { + err = -EIO; + goto out_restore; + } + if (is_dealloc == true) + ntfs_release_dirty_clusters(vol, rl->length); + ntfs_debug("Done."); + goto out_restore; + } + if (err != -ENOSPC) + ntfs_error(vol->sb, + "Failed to allocate clusters, aborting (error %i).", + err); + if (rl) { + int err2; + + if (err == -ENOSPC) + ntfs_debug("Not enough space to complete allocation, err -ENOSPC, first free lcn 0x%llx, could allocate up to 0x%llx clusters.", + rl[0].lcn, count - clusters); + /* Deallocate all allocated clusters. */ + ntfs_debug("Attempting rollback..."); + err2 = ntfs_cluster_free_from_rl_nolock(vol, rl); + if (err2) { + ntfs_error(vol->sb, + "Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.", + err2); + NVolSetErrors(vol); + } + /* Free the runlist. */ + kvfree(rl); + } else if (err == -ENOSPC) + ntfs_debug("No space left at all, err = -ENOSPC, first free lcn = 0x%llx.", + vol->data1_zone_pos); + atomic64_set(&vol->dirty_clusters, 0); + +out_restore: + up_write(&vol->lcnbmp_lock); + memalloc_nofs_restore(memalloc_flags); + + return err < 0 ? ERR_PTR(err) : rl; +} + +/* + * __ntfs_cluster_free - free clusters on an ntfs volume + * @ni: ntfs inode whose runlist describes the clusters to free + * @start_vcn: vcn in the runlist of @ni at which to start freeing clusters + * @count: number of clusters to free or -1 for all clusters + * @ctx: active attribute search context if present or NULL if not + * @is_rollback: true if this is a rollback operation + * + * Free @count clusters starting at the cluster @start_vcn in the runlist + * described by the vfs inode @ni. + * + * If @count is -1, all clusters from @start_vcn to the end of the runlist are + * deallocated. Thus, to completely free all clusters in a runlist, use + * @start_vcn = 0 and @count = -1. + * + * If @ctx is specified, it is an active search context of @ni and its base mft + * record. This is needed when __ntfs_cluster_free() encounters unmapped + * runlist fragments and allows their mapping. If you do not have the mft + * record mapped, you can specify @ctx as NULL and __ntfs_cluster_free() will + * perform the necessary mapping and unmapping. + * + * Note, __ntfs_cluster_free() saves the state of @ctx on entry and restores it + * before returning. Thus, @ctx will be left pointing to the same attribute on + * return as on entry. However, the actual pointers in @ctx may point to + * different memory locations on return, so you must remember to reset any + * cached pointers from the @ctx, i.e. after the call to __ntfs_cluster_free(), + * you will probably want to do: + * m = ctx->mrec; + * a = ctx->attr; + * Assuming you cache ctx->attr in a variable @a of type attr_record * and that + * you cache ctx->mrec in a variable @m of type struct mft_record *. + * + * @is_rollback should always be 'false', it is for internal use to rollback + * errors. You probably want to use ntfs_cluster_free() instead. + * + * Note, __ntfs_cluster_free() does not modify the runlist, so you have to + * remove from the runlist or mark sparse the freed runs later. + * + * Return the number of deallocated clusters (not counting sparse ones) on + * success and -errno on error. + * + * WARNING: If @ctx is supplied, regardless of whether success or failure is + * returned, you need to check IS_ERR(@ctx->mrec) and if 'true' the @ctx + * is no longer valid, i.e. you need to either call + * ntfs_attr_reinit_search_ctx() or ntfs_attr_put_search_ctx() on it. + * In that case PTR_ERR(@ctx->mrec) will give you the error code for + * why the mapping of the old inode failed. + * + * Locking: - The runlist described by @ni must be locked for writing on entry + * and is locked on return. Note the runlist may be modified when + * needed runlist fragments need to be mapped. + * - The volume lcn bitmap must be unlocked on entry and is unlocked + * on return. + * - This function takes the volume lcn bitmap lock for writing and + * modifies the bitmap contents. + * - If @ctx is NULL, the base mft record of @ni must not be mapped on + * entry and it will be left unmapped on return. + * - If @ctx is not NULL, the base mft record must be mapped on entry + * and it will be left mapped on return. + */ +s64 __ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn, s64 count, + struct ntfs_attr_search_ctx *ctx, const bool is_rollback) +{ + s64 delta, to_free, total_freed, real_freed; + struct ntfs_volume *vol; + struct inode *lcnbmp_vi; + struct runlist_element *rl; + int err; + unsigned int memalloc_flags; + + ntfs_debug("Entering for i_ino 0x%llx, start_vcn 0x%llx, count 0x%llx.%s", + ni->mft_no, start_vcn, count, + is_rollback ? " (rollback)" : ""); + vol = ni->vol; + lcnbmp_vi = vol->lcnbmp_ino; + if (start_vcn < 0 || count < -1) + return -EINVAL; + + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + + /* + * Lock the lcn bitmap for writing but only if not rolling back. We + * must hold the lock all the way including through rollback otherwise + * rollback is not possible because once we have cleared a bit and + * dropped the lock, anyone could have set the bit again, thus + * allocating the cluster for another use. + */ + if (likely(!is_rollback)) { + memalloc_flags = memalloc_nofs_save(); + down_write(&vol->lcnbmp_lock); + } + + total_freed = real_freed = 0; + + rl = ntfs_attr_find_vcn_nolock(ni, start_vcn, ctx); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + if (err == -ENOENT) { + if (likely(!is_rollback)) { + up_write(&vol->lcnbmp_lock); + memalloc_nofs_restore(memalloc_flags); + } + return 0; + } + + if (!is_rollback) + ntfs_error(vol->sb, + "Failed to find first runlist element (error %d), aborting.", + err); + goto err_out; + } + if (unlikely(rl->lcn < LCN_HOLE)) { + if (!is_rollback) + ntfs_error(vol->sb, "First runlist element has invalid lcn, aborting."); + err = -EIO; + goto err_out; + } + /* Find the starting cluster inside the run that needs freeing. */ + delta = start_vcn - rl->vcn; + + /* The number of clusters in this run that need freeing. */ + to_free = rl->length - delta; + if (count >= 0 && to_free > count) + to_free = count; + + if (likely(rl->lcn >= 0)) { + /* Do the actual freeing of the clusters in this run. */ + err = ntfs_bitmap_set_bits_in_run(lcnbmp_vi, rl->lcn + delta, + to_free, likely(!is_rollback) ? 0 : 1); + if (unlikely(err)) { + if (!is_rollback) + ntfs_error(vol->sb, + "Failed to clear first run (error %i), aborting.", + err); + goto err_out; + } + /* We have freed @to_free real clusters. */ + real_freed = to_free; + } + /* Go to the next run and adjust the number of clusters left to free. */ + ++rl; + if (count >= 0) + count -= to_free; + + /* Keep track of the total "freed" clusters, including sparse ones. */ + total_freed = to_free; + /* + * Loop over the remaining runs, using @count as a capping value, and + * free them. + */ + for (; rl->length && count != 0; ++rl) { + if (unlikely(rl->lcn < LCN_HOLE)) { + s64 vcn; + + /* Attempt to map runlist. */ + vcn = rl->vcn; + rl = ntfs_attr_find_vcn_nolock(ni, vcn, ctx); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + if (!is_rollback) + ntfs_error(vol->sb, + "Failed to map runlist fragment or failed to find subsequent runlist element."); + goto err_out; + } + if (unlikely(rl->lcn < LCN_HOLE)) { + if (!is_rollback) + ntfs_error(vol->sb, + "Runlist element has invalid lcn (0x%llx).", + rl->lcn); + err = -EIO; + goto err_out; + } + } + /* The number of clusters in this run that need freeing. */ + to_free = rl->length; + if (count >= 0 && to_free > count) + to_free = count; + + if (likely(rl->lcn >= 0)) { + /* Do the actual freeing of the clusters in the run. */ + err = ntfs_bitmap_set_bits_in_run(lcnbmp_vi, rl->lcn, + to_free, likely(!is_rollback) ? 0 : 1); + if (unlikely(err)) { + if (!is_rollback) + ntfs_error(vol->sb, "Failed to clear subsequent run."); + goto err_out; + } + /* We have freed @to_free real clusters. */ + real_freed += to_free; + } + /* Adjust the number of clusters left to free. */ + if (count >= 0) + count -= to_free; + + /* Update the total done clusters. */ + total_freed += to_free; + } + ntfs_inc_free_clusters(vol, real_freed); + if (likely(!is_rollback)) { + up_write(&vol->lcnbmp_lock); + memalloc_nofs_restore(memalloc_flags); + } + + WARN_ON(count > 0); + + if (NVolDiscard(vol) && !is_rollback) { + s64 total_discarded = 0, rl_off; + u32 gran = bdev_discard_granularity(vol->sb->s_bdev); + + rl = ntfs_attr_find_vcn_nolock(ni, start_vcn, ctx); + if (IS_ERR(rl)) + return real_freed; + rl_off = start_vcn - rl->vcn; + while (rl->length && total_discarded < total_freed) { + s64 to_discard = rl->length - rl_off; + + if (to_discard + total_discarded > total_freed) + to_discard = total_freed - total_discarded; + if (rl->lcn >= 0) { + sector_t start_sector, end_sector; + int ret; + + start_sector = ALIGN(NTFS_CLU_TO_B(vol, rl->lcn + rl_off), + gran) >> SECTOR_SHIFT; + end_sector = ALIGN_DOWN(NTFS_CLU_TO_B(vol, + rl->lcn + rl_off + to_discard), + gran) >> SECTOR_SHIFT; + if (start_sector < end_sector) { + ret = blkdev_issue_discard(vol->sb->s_bdev, start_sector, + end_sector - start_sector, + GFP_NOFS); + if (ret) + break; + } + } + + total_discarded += to_discard; + ++rl; + rl_off = 0; + } + } + + /* We are done. Return the number of actually freed clusters. */ + ntfs_debug("Done."); + return real_freed; +err_out: + if (is_rollback) + return err; + /* If no real clusters were freed, no need to rollback. */ + if (!real_freed) { + up_write(&vol->lcnbmp_lock); + memalloc_nofs_restore(memalloc_flags); + return err; + } + /* + * Attempt to rollback and if that succeeds just return the error code. + * If rollback fails, set the volume errors flag, emit an error + * message, and return the error code. + */ + delta = __ntfs_cluster_free(ni, start_vcn, total_freed, ctx, true); + if (delta < 0) { + ntfs_error(vol->sb, + "Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.", + (int)delta); + NVolSetErrors(vol); + } + ntfs_dec_free_clusters(vol, delta); + up_write(&vol->lcnbmp_lock); + memalloc_nofs_restore(memalloc_flags); + ntfs_error(vol->sb, "Aborting (error %i).", err); + return err; +} diff --git a/fs/ntfs/lcnalloc.h b/fs/ntfs/lcnalloc.h new file mode 100644 index 000000000000..d9ac622e4658 --- /dev/null +++ b/fs/ntfs/lcnalloc.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Exports for NTFS kernel cluster (de)allocation. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_LCNALLOC_H +#define _LINUX_NTFS_LCNALLOC_H + +#include + +#include "attrib.h" + +/* + * enum zone_type - Zone identifiers for cluster allocation policy + * + * FIRST_ZONE For sanity checking. + * MFT_ZONE Allocate from $MFT zone. + * DATA_ZONE Allocate from $DATA zone. + * LAST_ZONE For sanity checking. + */ +enum { + FIRST_ZONE = 0, + MFT_ZONE = 0, + DATA_ZONE = 1, + LAST_ZONE = 1, +}; + +struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, + const s64 start_vcn, const s64 count, const s64 start_lcn, + const int zone, + const bool is_extension, + const bool is_contig, + const bool is_dealloc); +s64 __ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn, + s64 count, struct ntfs_attr_search_ctx *ctx, const bool is_rollback); + +/* + * ntfs_cluster_free - free clusters on an ntfs volume + * @ni: ntfs inode whose runlist describes the clusters to free + * @start_vcn: vcn in the runlist of @ni at which to start freeing clusters + * @count: number of clusters to free or -1 for all clusters + * @ctx: active attribute search context if present or NULL if not + * + * Free @count clusters starting at the cluster @start_vcn in the runlist + * described by the ntfs inode @ni. + * + * If @count is -1, all clusters from @start_vcn to the end of the runlist are + * deallocated. Thus, to completely free all clusters in a runlist, use + * @start_vcn = 0 and @count = -1. + * + * If @ctx is specified, it is an active search context of @ni and its base mft + * record. This is needed when ntfs_cluster_free() encounters unmapped runlist + * fragments and allows their mapping. If you do not have the mft record + * mapped, you can specify @ctx as NULL and ntfs_cluster_free() will perform + * the necessary mapping and unmapping. + * + * Note, ntfs_cluster_free() saves the state of @ctx on entry and restores it + * before returning. Thus, @ctx will be left pointing to the same attribute on + * return as on entry. However, the actual pointers in @ctx may point to + * different memory locations on return, so you must remember to reset any + * cached pointers from the @ctx, i.e. after the call to ntfs_cluster_free(), + * you will probably want to do: + * m = ctx->mrec; + * a = ctx->attr; + * Assuming you cache ctx->attr in a variable @a of type ATTR_RECORD * and that + * you cache ctx->mrec in a variable @m of type MFT_RECORD *. + * + * Note, ntfs_cluster_free() does not modify the runlist, so you have to remove + * from the runlist or mark sparse the freed runs later. + * + * Return the number of deallocated clusters (not counting sparse ones) on + * success and -errno on error. + * + * WARNING: If @ctx is supplied, regardless of whether success or failure is + * returned, you need to check IS_ERR(@ctx->mrec) and if 'true' the @ctx + * is no longer valid, i.e. you need to either call + * ntfs_attr_reinit_search_ctx() or ntfs_attr_put_search_ctx() on it. + * In that case PTR_ERR(@ctx->mrec) will give you the error code for + * why the mapping of the old inode failed. + * + * Locking: - The runlist described by @ni must be locked for writing on entry + * and is locked on return. Note the runlist may be modified when + * needed runlist fragments need to be mapped. + * - The volume lcn bitmap must be unlocked on entry and is unlocked + * on return. + * - This function takes the volume lcn bitmap lock for writing and + * modifies the bitmap contents. + * - If @ctx is NULL, the base mft record of @ni must not be mapped on + * entry and it will be left unmapped on return. + * - If @ctx is not NULL, the base mft record must be mapped on entry + * and it will be left mapped on return. + */ +static inline s64 ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn, + s64 count, struct ntfs_attr_search_ctx *ctx) +{ + return __ntfs_cluster_free(ni, start_vcn, count, ctx, false); +} + +int ntfs_cluster_free_from_rl_nolock(struct ntfs_volume *vol, + const struct runlist_element *rl); + +/* + * ntfs_cluster_free_from_rl - free clusters from runlist + * @vol: mounted ntfs volume on which to free the clusters + * @rl: runlist describing the clusters to free + * + * Free all the clusters described by the runlist @rl on the volume @vol. In + * the case of an error being returned, at least some of the clusters were not + * freed. + * + * Return 0 on success and -errno on error. + * + * Locking: - This function takes the volume lcn bitmap lock for writing and + * modifies the bitmap contents. + * - The caller must have locked the runlist @rl for reading or + * writing. + */ +static inline int ntfs_cluster_free_from_rl(struct ntfs_volume *vol, + const struct runlist_element *rl) +{ + int ret; + unsigned int memalloc_flags; + + memalloc_flags = memalloc_nofs_save(); + down_write(&vol->lcnbmp_lock); + ret = ntfs_cluster_free_from_rl_nolock(vol, rl); + up_write(&vol->lcnbmp_lock); + memalloc_nofs_restore(memalloc_flags); + return ret; +} + +#endif /* defined _LINUX_NTFS_LCNALLOC_H */ diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c new file mode 100644 index 000000000000..3f8d1640f1d5 --- /dev/null +++ b/fs/ntfs/logfile.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel journal handling. + * + * Copyright (c) 2002-2007 Anton Altaparmakov + */ + +#include + +#include "attrib.h" +#include "logfile.h" +#include "ntfs.h" + +/* + * ntfs_check_restart_page_header - check the page header for consistency + * @vi: LogFile inode to which the restart page header belongs + * @rp: restart page header to check + * @pos: position in @vi at which the restart page header resides + * + * Check the restart page header @rp for consistency and return 'true' if it is + * consistent and 'false' otherwise. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + */ +static bool ntfs_check_restart_page_header(struct inode *vi, + struct restart_page_header *rp, s64 pos) +{ + u32 logfile_system_page_size, logfile_log_page_size; + u16 ra_ofs, usa_count, usa_ofs, usa_end = 0; + bool have_usa = true; + + ntfs_debug("Entering."); + /* + * If the system or log page sizes are smaller than the ntfs block size + * or either is not a power of 2 we cannot handle this log file. + */ + logfile_system_page_size = le32_to_cpu(rp->system_page_size); + logfile_log_page_size = le32_to_cpu(rp->log_page_size); + if (logfile_system_page_size < NTFS_BLOCK_SIZE || + logfile_log_page_size < NTFS_BLOCK_SIZE || + logfile_system_page_size & + (logfile_system_page_size - 1) || + !is_power_of_2(logfile_log_page_size)) { + ntfs_error(vi->i_sb, "LogFile uses unsupported page size."); + return false; + } + /* + * We must be either at !pos (1st restart page) or at pos = system page + * size (2nd restart page). + */ + if (pos && pos != logfile_system_page_size) { + ntfs_error(vi->i_sb, "Found restart area in incorrect position in LogFile."); + return false; + } + /* We only know how to handle version 1.1. */ + if (le16_to_cpu(rp->major_ver) != 1 || + le16_to_cpu(rp->minor_ver) != 1) { + ntfs_error(vi->i_sb, + "LogFile version %i.%i is not supported. (This driver supports version 1.1 only.)", + (int)le16_to_cpu(rp->major_ver), + (int)le16_to_cpu(rp->minor_ver)); + return false; + } + /* + * If chkdsk has been run the restart page may not be protected by an + * update sequence array. + */ + if (ntfs_is_chkd_record(rp->magic) && !le16_to_cpu(rp->usa_count)) { + have_usa = false; + goto skip_usa_checks; + } + /* Verify the size of the update sequence array. */ + usa_count = 1 + (logfile_system_page_size >> NTFS_BLOCK_SIZE_BITS); + if (usa_count != le16_to_cpu(rp->usa_count)) { + ntfs_error(vi->i_sb, + "LogFile restart page specifies inconsistent update sequence array count."); + return false; + } + /* Verify the position of the update sequence array. */ + usa_ofs = le16_to_cpu(rp->usa_ofs); + usa_end = usa_ofs + usa_count * sizeof(u16); + if (usa_ofs < sizeof(struct restart_page_header) || + usa_end > NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "LogFile restart page specifies inconsistent update sequence array offset."); + return false; + } +skip_usa_checks: + /* + * Verify the position of the restart area. It must be: + * - aligned to 8-byte boundary, + * - after the update sequence array, and + * - within the system page size. + */ + ra_ofs = le16_to_cpu(rp->restart_area_offset); + if (ra_ofs & 7 || (have_usa ? ra_ofs < usa_end : + ra_ofs < sizeof(struct restart_page_header)) || + ra_ofs > logfile_system_page_size) { + ntfs_error(vi->i_sb, + "LogFile restart page specifies inconsistent restart area offset."); + return false; + } + /* + * Only restart pages modified by chkdsk are allowed to have chkdsk_lsn + * set. + */ + if (!ntfs_is_chkd_record(rp->magic) && le64_to_cpu(rp->chkdsk_lsn)) { + ntfs_error(vi->i_sb, + "LogFile restart page is not modified by chkdsk but a chkdsk LSN is specified."); + return false; + } + ntfs_debug("Done."); + return true; +} + +/* + * ntfs_check_restart_area - check the restart area for consistency + * @vi: LogFile inode to which the restart page belongs + * @rp: restart page whose restart area to check + * + * Check the restart area of the restart page @rp for consistency and return + * 'true' if it is consistent and 'false' otherwise. + * + * This function assumes that the restart page header has already been + * consistency checked. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + */ +static bool ntfs_check_restart_area(struct inode *vi, struct restart_page_header *rp) +{ + u64 file_size; + struct restart_area *ra; + u16 ra_ofs, ra_len, ca_ofs; + u8 fs_bits; + + ntfs_debug("Entering."); + ra_ofs = le16_to_cpu(rp->restart_area_offset); + ra = (struct restart_area *)((u8 *)rp + ra_ofs); + /* + * Everything before ra->file_size must be before the first word + * protected by an update sequence number. This ensures that it is + * safe to access ra->client_array_offset. + */ + if (ra_ofs + offsetof(struct restart_area, file_size) > + NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent file offset."); + return false; + } + /* + * Now that we can access ra->client_array_offset, make sure everything + * up to the log client array is before the first word protected by an + * update sequence number. This ensures we can access all of the + * restart area elements safely. Also, the client array offset must be + * aligned to an 8-byte boundary. + */ + ca_ofs = le16_to_cpu(ra->client_array_offset); + if (((ca_ofs + 7) & ~7) != ca_ofs || + ra_ofs + ca_ofs > NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent client array offset."); + return false; + } + /* + * The restart area must end within the system page size both when + * calculated manually and as specified by ra->restart_area_length. + * Also, the calculated length must not exceed the specified length. + */ + ra_len = ca_ofs + le16_to_cpu(ra->log_clients) * + sizeof(struct log_client_record); + if (ra_ofs + ra_len > le32_to_cpu(rp->system_page_size) || + ra_ofs + le16_to_cpu(ra->restart_area_length) > + le32_to_cpu(rp->system_page_size) || + ra_len > le16_to_cpu(ra->restart_area_length)) { + ntfs_error(vi->i_sb, + "LogFile restart area is out of bounds of the system page size specified by the restart page header and/or the specified restart area length is inconsistent."); + return false; + } + /* + * The ra->client_free_list and ra->client_in_use_list must be either + * LOGFILE_NO_CLIENT or less than ra->log_clients or they are + * overflowing the client array. + */ + if ((ra->client_free_list != LOGFILE_NO_CLIENT && + le16_to_cpu(ra->client_free_list) >= + le16_to_cpu(ra->log_clients)) || + (ra->client_in_use_list != LOGFILE_NO_CLIENT && + le16_to_cpu(ra->client_in_use_list) >= + le16_to_cpu(ra->log_clients))) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies overflowing client free and/or in use lists."); + return false; + } + /* + * Check ra->seq_number_bits against ra->file_size for consistency. + * We cannot just use ffs() because the file size is not a power of 2. + */ + file_size = le64_to_cpu(ra->file_size); + fs_bits = 0; + while (file_size) { + file_size >>= 1; + fs_bits++; + } + if (le32_to_cpu(ra->seq_number_bits) != 67 - fs_bits) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent sequence number bits."); + return false; + } + /* The log record header length must be a multiple of 8. */ + if (((le16_to_cpu(ra->log_record_header_length) + 7) & ~7) != + le16_to_cpu(ra->log_record_header_length)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent log record header length."); + return false; + } + /* Dito for the log page data offset. */ + if (((le16_to_cpu(ra->log_page_data_offset) + 7) & ~7) != + le16_to_cpu(ra->log_page_data_offset)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent log page data offset."); + return false; + } + ntfs_debug("Done."); + return true; +} + +/* + * ntfs_check_log_client_array - check the log client array for consistency + * @vi: LogFile inode to which the restart page belongs + * @rp: restart page whose log client array to check + * + * Check the log client array of the restart page @rp for consistency and + * return 'true' if it is consistent and 'false' otherwise. + * + * This function assumes that the restart page header and the restart area have + * already been consistency checked. + * + * Unlike ntfs_check_restart_page_header() and ntfs_check_restart_area(), this + * function needs @rp->system_page_size bytes in @rp, i.e. it requires the full + * restart page and the page must be multi sector transfer deprotected. + */ +static bool ntfs_check_log_client_array(struct inode *vi, + struct restart_page_header *rp) +{ + struct restart_area *ra; + struct log_client_record *ca, *cr; + u16 nr_clients, idx; + bool in_free_list, idx_is_first; + + ntfs_debug("Entering."); + ra = (struct restart_area *)((u8 *)rp + le16_to_cpu(rp->restart_area_offset)); + ca = (struct log_client_record *)((u8 *)ra + + le16_to_cpu(ra->client_array_offset)); + /* + * Check the ra->client_free_list first and then check the + * ra->client_in_use_list. Check each of the log client records in + * each of the lists and check that the array does not overflow the + * ra->log_clients value. Also keep track of the number of records + * visited as there cannot be more than ra->log_clients records and + * that way we detect eventual loops in within a list. + */ + nr_clients = le16_to_cpu(ra->log_clients); + idx = le16_to_cpu(ra->client_free_list); + in_free_list = true; +check_list: + for (idx_is_first = true; idx != LOGFILE_NO_CLIENT_CPU; nr_clients--, + idx = le16_to_cpu(cr->next_client)) { + if (!nr_clients || idx >= le16_to_cpu(ra->log_clients)) + goto err_out; + /* Set @cr to the current log client record. */ + cr = ca + idx; + /* The first log client record must not have a prev_client. */ + if (idx_is_first) { + if (cr->prev_client != LOGFILE_NO_CLIENT) + goto err_out; + idx_is_first = false; + } + } + /* Switch to and check the in use list if we just did the free list. */ + if (in_free_list) { + in_free_list = false; + idx = le16_to_cpu(ra->client_in_use_list); + goto check_list; + } + ntfs_debug("Done."); + return true; +err_out: + ntfs_error(vi->i_sb, "LogFile log client array is corrupt."); + return false; +} + +/* + * ntfs_check_and_load_restart_page - check the restart page for consistency + * @vi: LogFile inode to which the restart page belongs + * @rp: restart page to check + * @pos: position in @vi at which the restart page resides + * @wrp: [OUT] copy of the multi sector transfer deprotected restart page + * @lsn: [OUT] set to the current logfile lsn on success + * + * Check the restart page @rp for consistency and return 0 if it is consistent + * and -errno otherwise. The restart page may have been modified by chkdsk in + * which case its magic is CHKD instead of RSTR. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + * + * If @wrp is not NULL, on success, *@wrp will point to a buffer containing a + * copy of the complete multi sector transfer deprotected page. On failure, + * *@wrp is undefined. + * + * Simillarly, if @lsn is not NULL, on success *@lsn will be set to the current + * logfile lsn according to this restart page. On failure, *@lsn is undefined. + * + * The following error codes are defined: + * -EINVAL - The restart page is inconsistent. + * -ENOMEM - Not enough memory to load the restart page. + * -EIO - Failed to reading from LogFile. + */ +static int ntfs_check_and_load_restart_page(struct inode *vi, + struct restart_page_header *rp, s64 pos, struct restart_page_header **wrp, + s64 *lsn) +{ + struct restart_area *ra; + struct restart_page_header *trp; + int size, err; + + ntfs_debug("Entering."); + /* Check the restart page header for consistency. */ + if (!ntfs_check_restart_page_header(vi, rp, pos)) { + /* Error output already done inside the function. */ + return -EINVAL; + } + /* Check the restart area for consistency. */ + if (!ntfs_check_restart_area(vi, rp)) { + /* Error output already done inside the function. */ + return -EINVAL; + } + ra = (struct restart_area *)((u8 *)rp + le16_to_cpu(rp->restart_area_offset)); + /* + * Allocate a buffer to store the whole restart page so we can multi + * sector transfer deprotect it. + */ + trp = kvzalloc(le32_to_cpu(rp->system_page_size), GFP_NOFS); + if (!trp) { + ntfs_error(vi->i_sb, "Failed to allocate memory for LogFile restart page buffer."); + return -ENOMEM; + } + /* + * Read the whole of the restart page into the buffer. If it fits + * completely inside @rp, just copy it from there. Otherwise map all + * the required pages and copy the data from them. + */ + size = PAGE_SIZE - (pos & ~PAGE_MASK); + if (size >= le32_to_cpu(rp->system_page_size)) { + memcpy(trp, rp, le32_to_cpu(rp->system_page_size)); + } else { + pgoff_t idx; + struct folio *folio; + int have_read, to_read; + + /* First copy what we already have in @rp. */ + memcpy(trp, rp, size); + /* Copy the remaining data one page at a time. */ + have_read = size; + to_read = le32_to_cpu(rp->system_page_size) - size; + idx = (pos + size) >> PAGE_SHIFT; + do { + folio = read_mapping_folio(vi->i_mapping, idx, NULL); + if (IS_ERR(folio)) { + ntfs_error(vi->i_sb, "Error mapping LogFile page (index %lu).", + idx); + err = PTR_ERR(folio); + if (err != -EIO && err != -ENOMEM) + err = -EIO; + goto err_out; + } + size = min_t(int, to_read, PAGE_SIZE); + memcpy((u8 *)trp + have_read, folio_address(folio), size); + folio_put(folio); + have_read += size; + to_read -= size; + idx++; + } while (to_read > 0); + } + /* + * Perform the multi sector transfer deprotection on the buffer if the + * restart page is protected. + */ + if ((!ntfs_is_chkd_record(trp->magic) || le16_to_cpu(trp->usa_count)) && + post_read_mst_fixup((struct ntfs_record *)trp, le32_to_cpu(rp->system_page_size))) { + /* + * A multi sector transfer error was detected. We only need to + * abort if the restart page contents exceed the multi sector + * transfer fixup of the first sector. + */ + if (le16_to_cpu(rp->restart_area_offset) + + le16_to_cpu(ra->restart_area_length) > + NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "Multi sector transfer error detected in LogFile restart page."); + err = -EINVAL; + goto err_out; + } + } + /* + * If the restart page is modified by chkdsk or there are no active + * logfile clients, the logfile is consistent. Otherwise, need to + * check the log client records for consistency, too. + */ + err = 0; + if (ntfs_is_rstr_record(rp->magic) && + ra->client_in_use_list != LOGFILE_NO_CLIENT) { + if (!ntfs_check_log_client_array(vi, trp)) { + err = -EINVAL; + goto err_out; + } + } + if (lsn) { + if (ntfs_is_rstr_record(rp->magic)) + *lsn = le64_to_cpu(ra->current_lsn); + else /* if (ntfs_is_chkd_record(rp->magic)) */ + *lsn = le64_to_cpu(rp->chkdsk_lsn); + } + ntfs_debug("Done."); + if (wrp) + *wrp = trp; + else { +err_out: + kvfree(trp); + } + return err; +} + +/* + * ntfs_check_logfile - check the journal for consistency + * @log_vi: struct inode of loaded journal LogFile to check + * @rp: [OUT] on success this is a copy of the current restart page + * + * Check the LogFile journal for consistency and return 'true' if it is + * consistent and 'false' if not. On success, the current restart page is + * returned in *@rp. Caller must call kvfree(*@rp) when finished with it. + * + * At present we only check the two restart pages and ignore the log record + * pages. + * + * Note that the MstProtected flag is not set on the LogFile inode and hence + * when reading pages they are not deprotected. This is because we do not know + * if the LogFile was created on a system with a different page size to ours + * yet and mst deprotection would fail if our page size is smaller. + */ +bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) +{ + s64 size, pos; + s64 rstr1_lsn, rstr2_lsn; + struct ntfs_volume *vol = NTFS_SB(log_vi->i_sb); + struct address_space *mapping = log_vi->i_mapping; + struct folio *folio = NULL; + u8 *kaddr = NULL; + struct restart_page_header *rstr1_ph = NULL; + struct restart_page_header *rstr2_ph = NULL; + int log_page_size, err; + bool logfile_is_empty = true; + u8 log_page_bits; + + ntfs_debug("Entering."); + /* An empty LogFile must have been clean before it got emptied. */ + if (NVolLogFileEmpty(vol)) + goto is_empty; + size = i_size_read(log_vi); + /* Make sure the file doesn't exceed the maximum allowed size. */ + if (size > MaxLogFileSize) + size = MaxLogFileSize; + /* + * Truncate size to a multiple of the page cache size or the default + * log page size if the page cache size is between the default log page + * log page size if the page cache size is between the default log page + * size and twice that. + */ + if (DefaultLogPageSize <= PAGE_SIZE && + DefaultLogPageSize * 2 <= PAGE_SIZE) + log_page_size = DefaultLogPageSize; + else + log_page_size = PAGE_SIZE; + /* + * Use ntfs_ffs() instead of ffs() to enable the compiler to + * optimize log_page_size and log_page_bits into constants. + */ + log_page_bits = ntfs_ffs(log_page_size) - 1; + size &= ~(s64)(log_page_size - 1); + /* + * Ensure the log file is big enough to store at least the two restart + * pages and the minimum number of log record pages. + */ + if (size < log_page_size * 2 || (size - log_page_size * 2) >> + log_page_bits < MinLogRecordPages) { + ntfs_error(vol->sb, "LogFile is too small."); + return false; + } + /* + * Read through the file looking for a restart page. Since the restart + * page header is at the beginning of a page we only need to search at + * what could be the beginning of a page (for each page size) rather + * than scanning the whole file byte by byte. If all potential places + * contain empty and uninitialzed records, the log file can be assumed + * to be empty. + */ + for (pos = 0; pos < size; pos <<= 1) { + pgoff_t idx = pos >> PAGE_SHIFT; + + if (!folio || folio->index != idx) { + if (folio) { + kunmap_local(kaddr); + folio_put(folio); + } + folio = read_mapping_folio(mapping, idx, NULL); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Error mapping LogFile page (index %lu).", + idx); + goto err_out; + } + } + kaddr = (u8 *)kmap_local_folio(folio, 0) + (pos & ~PAGE_MASK); + /* + * A non-empty block means the logfile is not empty while an + * empty block after a non-empty block has been encountered + * means we are done. + */ + if (!ntfs_is_empty_recordp((__le32 *)kaddr)) + logfile_is_empty = false; + else if (!logfile_is_empty) + break; + /* + * A log record page means there cannot be a restart page after + * this so no need to continue searching. + */ + if (ntfs_is_rcrd_recordp((__le32 *)kaddr)) + break; + /* If not a (modified by chkdsk) restart page, continue. */ + if (!ntfs_is_rstr_recordp((__le32 *)kaddr) && + !ntfs_is_chkd_recordp((__le32 *)kaddr)) { + if (!pos) + pos = NTFS_BLOCK_SIZE >> 1; + continue; + } + /* + * Check the (modified by chkdsk) restart page for consistency + * and get a copy of the complete multi sector transfer + * deprotected restart page. + */ + err = ntfs_check_and_load_restart_page(log_vi, + (struct restart_page_header *)kaddr, pos, + !rstr1_ph ? &rstr1_ph : &rstr2_ph, + !rstr1_ph ? &rstr1_lsn : &rstr2_lsn); + if (!err) { + /* + * If we have now found the first (modified by chkdsk) + * restart page, continue looking for the second one. + */ + if (!pos) { + pos = NTFS_BLOCK_SIZE >> 1; + continue; + } + /* + * We have now found the second (modified by chkdsk) + * restart page, so we can stop looking. + */ + break; + } + /* + * Error output already done inside the function. Note, we do + * not abort if the restart page was invalid as we might still + * find a valid one further in the file. + */ + if (err != -EINVAL) { + kunmap_local(kaddr); + folio_put(folio); + goto err_out; + } + /* Continue looking. */ + if (!pos) + pos = NTFS_BLOCK_SIZE >> 1; + } + if (folio) { + kunmap_local(kaddr); + folio_put(folio); + } + if (logfile_is_empty) { + NVolSetLogFileEmpty(vol); +is_empty: + ntfs_debug("Done. (LogFile is empty.)"); + return true; + } + if (!rstr1_ph) { + ntfs_error(vol->sb, + "Did not find any restart pages in LogFile and it was not empty."); + return false; + } + /* If both restart pages were found, use the more recent one. */ + if (rstr2_ph) { + /* + * If the second restart area is more recent, switch to it. + * Otherwise just throw it away. + */ + if (rstr2_lsn > rstr1_lsn) { + ntfs_debug("Using second restart page as it is more recent."); + kvfree(rstr1_ph); + rstr1_ph = rstr2_ph; + /* rstr1_lsn = rstr2_lsn; */ + } else { + ntfs_debug("Using first restart page as it is more recent."); + kvfree(rstr2_ph); + } + rstr2_ph = NULL; + } + /* All consistency checks passed. */ + if (rp) + *rp = rstr1_ph; + else + kvfree(rstr1_ph); + ntfs_debug("Done."); + return true; +err_out: + if (rstr1_ph) + kvfree(rstr1_ph); + return false; +} + +/* + * ntfs_empty_logfile - empty the contents of the LogFile journal + * @log_vi: struct inode of loaded journal LogFile to empty + * + * Empty the contents of the LogFile journal @log_vi and return 'true' on + * success and 'false' on error. + * + * This function assumes that the LogFile journal has already been consistency + * checked by a call to ntfs_check_logfile() and that ntfs_is_logfile_clean() + * has been used to ensure that the LogFile is clean. + */ +bool ntfs_empty_logfile(struct inode *log_vi) +{ + s64 vcn, end_vcn; + struct ntfs_inode *log_ni = NTFS_I(log_vi); + struct ntfs_volume *vol = log_ni->vol; + struct super_block *sb = vol->sb; + struct runlist_element *rl; + unsigned long flags; + int err; + bool should_wait = true; + char *empty_buf = NULL; + struct file_ra_state *ra = NULL; + + ntfs_debug("Entering."); + if (NVolLogFileEmpty(vol)) { + ntfs_debug("Done."); + return true; + } + + /* + * We cannot use ntfs_attr_set() because we may be still in the middle + * of a mount operation. Thus we do the emptying by hand by first + * zapping the page cache pages for the LogFile/DATA attribute and + * then emptying each of the buffers in each of the clusters specified + * by the runlist by hand. + */ + vcn = 0; + read_lock_irqsave(&log_ni->size_lock, flags); + end_vcn = (log_ni->initialized_size + vol->cluster_size_mask) >> + vol->cluster_size_bits; + read_unlock_irqrestore(&log_ni->size_lock, flags); + truncate_inode_pages(log_vi->i_mapping, 0); + down_write(&log_ni->runlist.lock); + rl = log_ni->runlist.rl; + if (unlikely(!rl || vcn < rl->vcn || !rl->length)) { +map_vcn: + err = ntfs_map_runlist_nolock(log_ni, vcn, NULL); + if (err) { + ntfs_error(sb, "Failed to map runlist fragment (error %d).", -err); + goto err; + } + rl = log_ni->runlist.rl; + } + /* Seek to the runlist element containing @vcn. */ + while (rl->length && vcn >= rl[1].vcn) + rl++; + + err = -ENOMEM; + empty_buf = kvzalloc(vol->cluster_size, GFP_NOFS); + if (!empty_buf) + goto err; + + memset(empty_buf, 0xff, vol->cluster_size); + + ra = kzalloc(sizeof(*ra), GFP_NOFS); + if (!ra) + goto err; + + file_ra_state_init(ra, sb->s_bdev->bd_mapping); + do { + s64 lcn; + loff_t start, end; + s64 len; + + /* + * If this run is not mapped map it now and start again as the + * runlist will have been updated. + */ + lcn = rl->lcn; + if (unlikely(lcn == LCN_RL_NOT_MAPPED)) { + vcn = rl->vcn; + kvfree(empty_buf); + goto map_vcn; + } + /* If this run is not valid abort with an error. */ + if (unlikely(!rl->length || lcn < LCN_HOLE)) + goto rl_err; + /* Skip holes. */ + if (lcn == LCN_HOLE) + continue; + start = NTFS_CLU_TO_B(vol, lcn); + len = rl->length; + if (rl[1].vcn > end_vcn) + len = end_vcn - rl->vcn; + end = NTFS_CLU_TO_B(vol, lcn + len); + + page_cache_sync_readahead(sb->s_bdev->bd_mapping, ra, NULL, + start >> PAGE_SHIFT, (end - start) >> PAGE_SHIFT); + + do { + err = ntfs_bdev_write(sb, empty_buf, start, + vol->cluster_size); + if (err) { + ntfs_error(sb, "ntfs_dev_write failed, err : %d\n", err); + goto io_err; + } + + /* + * Submit the buffer and wait for i/o to complete but + * only for the first buffer so we do not miss really + * serious i/o errors. Once the first buffer has + * completed ignore errors afterwards as we can assume + * that if one buffer worked all of them will work. + */ + if (should_wait) { + should_wait = false; + err = filemap_write_and_wait_range(sb->s_bdev->bd_mapping, + start, start + vol->cluster_size); + if (err) + goto io_err; + } + start += vol->cluster_size; + } while (start < end); + } while ((++rl)->vcn < end_vcn); + up_write(&log_ni->runlist.lock); + kfree(empty_buf); + kfree(ra); + truncate_inode_pages(log_vi->i_mapping, 0); + /* Set the flag so we do not have to do it again on remount. */ + NVolSetLogFileEmpty(vol); + ntfs_debug("Done."); + return true; +io_err: + ntfs_error(sb, "Failed to write buffer. Unmount and run chkdsk."); + goto dirty_err; +rl_err: + ntfs_error(sb, "Runlist is corrupt. Unmount and run chkdsk."); +dirty_err: + NVolSetErrors(vol); + err = -EIO; +err: + kvfree(empty_buf); + kfree(ra); + up_write(&log_ni->runlist.lock); + ntfs_error(sb, "Failed to fill LogFile with 0xff bytes (error %d).", + -err); + return false; +} diff --git a/fs/ntfs/logfile.h b/fs/ntfs/logfile.h new file mode 100644 index 000000000000..dadb56fd0b18 --- /dev/null +++ b/fs/ntfs/logfile.h @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for NTFS kernel journal (LogFile) handling. + * + * Copyright (c) 2000-2005 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_LOGFILE_H +#define _LINUX_NTFS_LOGFILE_H + +#include "layout.h" + +/* + * Journal (LogFile) organization: + * + * Two restart areas present in the first two pages (restart pages, one restart + * area in each page). When the volume is dismounted they should be identical, + * except for the update sequence array which usually has a different update + * sequence number. + * + * These are followed by log records organized in pages headed by a log record + * header going up to log file size. Not all pages contain log records when a + * volume is first formatted, but as the volume ages, all records will be used. + * When the log file fills up, the records at the beginning are purged (by + * modifying the oldest_lsn to a higher value presumably) and writing begins + * at the beginning of the file. Effectively, the log file is viewed as a + * circular entity. + * + * NOTE: Windows NT, 2000, and XP all use log file version 1.1 but they accept + * versions <= 1.x, including 0.-1. (Yes, that is a minus one in there!) We + * probably only want to support 1.1 as this seems to be the current version + * and we don't know how that differs from the older versions. The only + * exception is if the journal is clean as marked by the two restart pages + * then it doesn't matter whether we are on an earlier version. We can just + * reinitialize the logfile and start again with version 1.1. + */ + +/* Some LogFile related constants. */ +#define MaxLogFileSize 0x100000000ULL +#define DefaultLogPageSize 4096 +#define MinLogRecordPages 48 + +/* + * Log file restart page header (begins the restart area). + * + * @magic: The magic is "RSTR". + * @usa_ofs: See ntfs_record struct definition in layout.h. When creating, + * set this to be immediately after this header structure (without any + * alignment). + * @usa_count: See ntfs_record struct definition in layout.h. + * @chkdsk_lsn: The last log file sequence number found by chkdsk. Only + * used when the magic is changed to "CHKD". Otherwise this is zero. + * @system_page_size: Byte size of system pages when the log file was + * created, has to be >= 512 and a power of 2. Use this to calculate + * the required size of the usa (usa_count) and add it to usa_ofs. Then + * verify that the result is less than the value of + * the restart_area_offset. + * @log_page_size: Byte size of log file pages, has to be >= 512 and + * a power of 2. The default is 4096 and is used when the system page + * size is between 4096 and 8192. Otherwise this is set to the system + * page size instead. + * @restart_area_offset: Byte offset from the start of this header to + * the RESTART_AREA. Value has to be aligned to 8-byte boundary. When + * creating, set this to be after the usa. + * @minor_ver: Log file minor version. Only check if major version is 1. + * @major_ver: Log file major version. We only support version 1.1. + */ +struct restart_page_header { + __le32 magic; + __le16 usa_ofs; + __le16 usa_count; + __le64 chkdsk_lsn; + __le32 system_page_size; + __le32 log_page_size; + __le16 restart_area_offset; + __le16 minor_ver; + __le16 major_ver; +} __packed; + +/* + * Constant for the log client indices meaning that there are no client records + * in this particular client array. Also inside the client records themselves, + * this means that there are no client records preceding or following this one. + */ +#define LOGFILE_NO_CLIENT cpu_to_le16(0xffff) +#define LOGFILE_NO_CLIENT_CPU 0xffff + +/* + * These are the so far known RESTART_AREA_* flags (16-bit) which contain + * information about the log file in which they are present. + * gcc: Force enum bit width to 16. + */ +enum { + RESTART_VOLUME_IS_CLEAN = cpu_to_le16(0x0002), + RESTART_SPACE_FILLER = cpu_to_le16(0xffff), +} __packed; + +/* + * Log file restart area record. The offset of this record is found by adding + * the offset of the RESTART_PAGE_HEADER to the restart_area_offset value found + * in it. See notes at restart_area_offset above. + * + * @current_lsn: The current, i.e. last LSN inside the log when + * the restart area was last written. This happens often but what is + * the interval? Is it just fixed time or is it every time a check point + * is written or somethine else? On create set to 0. + * @log_clients: Number of log client records in the array of log client + * records which follows this restart area. Must be 1. + * @client_free_list: The index of the first free log client record in + * the array of log client records. LOGFILE_NO_CLIENT means that there + * are no free log client records in the array. If != LOGFILE_NO_CLIENT, + * check that log_clients > client_free_list. On Win2k and presumably + * earlier, on a clean volume this is != LOGFILE_NO_CLIENT, and it should + * be 0, i.e. the first (and only) client record is free and thus + * the logfile is closed and hence clean. A dirty volume would have left + * the logfile open and hence this would be LOGFILE_NO_CLIENT. On WinXP + * and presumably later, the logfile is always open, even on clean + * shutdown so this should always be LOGFILE_NO_CLIENT. + * @client_in_use_list: The index of the first in-use log client record in + * the array of log client records. LOGFILE_NO_CLIENT means that there + * are no in-use log client records in the array. + * If != LOGFILE_NO_CLIENT check that log_clients > client_in_use_list. + * On Win2k and presumably earlier, on a clean volume this is + * LOGFILE_NO_CLIENT, i.e. there are no client records in use and thus + * the logfile is closed and hence clean. A dirty volume would have left + * the logfile open and hence this would be != LOGFILE_NO_CLIENT, and it + * should be 0, i.e. the first (and only) client record is in use. On + * WinXP and presumably later, the logfile is always open, even on clean + * shutdown so this should always be 0. + * @flags: Flags modifying LFS behaviour. On Win2k and presumably earlier + * this is always 0. On WinXP and presumably later, if the logfile was + * shutdown cleanly, the second bit, RESTART_VOLUME_IS_CLEAN, is set. + * This bit is cleared when the volume is mounted by WinXP and set when + * the volume is dismounted, thus if the logfile is dirty, this bit is + * clear. Thus we don't need to check the Windows version to determine + * if the logfile is clean. Instead if the logfile is closed, we know + * it must be clean. If it is open and this bit is set, we also know + * it must be clean. If on the other hand the logfile is open and this + * bit is clear, we can be almost certain that the logfile is dirty. + * @seq_number_bits: How many bits to use for the sequence number. This + * is calculated as 67 - the number of bits required to store the logfile + * size in bytes and this can be used in with the specified file_size as + * a consistency check. + * @restart_area_length: Length of the restart area including the client + * array. Following checks required if version matches. Otherwise, + * skip them. restart_area_offset + restart_area_length has to be + * <= system_page_size. Also, restart_area_length has to be >= + * client_array_offset + (log_clients * sizeof(log client record)). + * @client_array_offset: Offset from the start of this record to the first + * log client record if versions are matched. When creating, set this + * to be after this restart area structure, aligned to 8-bytes boundary. + * If the versions do not match, this is ignored and the offset is + * assumed to be (sizeof(RESTART_AREA) + 7) & ~7, i.e. rounded up to + * first 8-byte boundary. Either way, client_array_offset has to be + * aligned to an 8-byte boundary. Also, restart_area_offset + + * client_array_offset has to be <= 510. Finally, client_array_offset + + * (log_clients * sizeof(log client record)) has to be <= system_page_size. + * On Win2k and presumably earlier, this is 0x30, i.e. immediately + * following this record. On WinXP and presumably later, this is 0x40, + * i.e. there are 16 extra bytes between this record and the client + * array. This probably means that the RESTART_AREA record is actually + * bigger in WinXP and later. + * @file_size: Usable byte size of the log file. + * If the restart_area_offset + the offset of the file_size are > 510 + * then corruption has occurred. This is the very first check when + * starting with the restart_area as if it fails it means that some of + * the above values will be corrupted by the multi sector transfer + * protection. The file_size has to be rounded down to be a multiple + * of the log_page_size in the RESTART_PAGE_HEADER and then it has to be + * at least big enough to store the two restart pages and 48 (0x30) log + * record pages. + * @last_lsn_data_length: Length of data of last LSN, not including the log + * record header. On create set to 0. + * @log_record_header_length: Byte size of the log record header. If the + * version matches then check that the value of log_record_header_length + * is a multiple of 8, i.e. (log_record_header_length + 7) & ~7 == + * log_record_header_length. When creating set it to + * sizeof(LOG_RECORD_HEADER), aligned to 8 bytes. + * @log_page_data_offset: Offset to the start of data in a log record page. + * Must be a multiple of 8. On create set it to immediately after + * the update sequence array of the log record page. + * @restart_log_open_count: A counter that gets incremented every time + * the logfile is restarted which happens at mount time when the logfile + * is opened. When creating set to a random value. Win2k sets it to + * the low 32 bits of the current system time in NTFS format (see time.h). + * @reserved: Reserved/alignment to 8-byte boundary. + */ +struct restart_area { + __le64 current_lsn; + __le16 log_clients; + __le16 client_free_list; + __le16 client_in_use_list; + __le16 flags; + __le32 seq_number_bits; + __le16 restart_area_length; + __le16 client_array_offset; + __le64 file_size; + __le32 last_lsn_data_length; + __le16 log_record_header_length; + __le16 log_page_data_offset; + __le32 restart_log_open_count; + __le32 reserved; +} __packed; + +/* + * Log client record. The offset of this record is found by adding the offset + * of the RESTART_AREA to the client_array_offset value found in it. + * + * @oldest_lsn: Oldest LSN needed by this client. On create set to 0. + * @client_restart_lsn: LSN at which this client needs to restart + * the volume, i.e. the current position within the log file. + * At present, if clean this should = current_lsn in restart area but it + * probably also = current_lsn when dirty most of the time. + * At create set to 0. + * @prev_client: The offset to the previous log client record in the array + * of log client records. LOGFILE_NO_CLIENT means there is no previous + * client record, i.e. this is the first one. This is always + * LOGFILE_NO_CLIENT. + * @next_client: The offset to the next log client record in the array of + * log client records. LOGFILE_NO_CLIENT means there are no next client + * records, i.e. this is the last one. This is always LOGFILE_NO_CLIENT. + * @seq_number: On Win2k and presumably earlier, this is set to zero every + * time the logfile is restarted and it is incremented when the logfile + * is closed at dismount time. Thus it is 0 when dirty and 1 when clean. + * On WinXP and presumably later, this is always 0. + * @reserved[6]: Reserved/alignment. + * @client_name_length: Length of client name in bytes. Should always be 8. + * @client_name[64]: Name of the client in Unicode. Should always be "NTFS" + * with the remaining bytes set to 0. + */ +struct log_client_record { + __le64 oldest_lsn; + __le64 client_restart_lsn; + __le16 prev_client; + __le16 next_client; + __le16 seq_number; + u8 reserved[6]; + __le32 client_name_length; + __le16 client_name[64]; +} __packed; + +bool ntfs_check_logfile(struct inode *log_vi, + struct restart_page_header **rp); +bool ntfs_empty_logfile(struct inode *log_vi); +#endif /* _LINUX_NTFS_LOGFILE_H */ diff --git a/fs/ntfs/malloc.h b/fs/ntfs/malloc.h new file mode 100644 index 000000000000..7068425735f1 --- /dev/null +++ b/fs/ntfs/malloc.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * malloc.h - NTFS kernel memory handling. Part of the Linux-NTFS project. + * + * Copyright (c) 2001-2005 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_MALLOC_H +#define _LINUX_NTFS_MALLOC_H + +#include +#include +#include + +/** + * __ntfs_malloc - allocate memory in multiples of pages + * @size: number of bytes to allocate + * @gfp_mask: extra flags for the allocator + * + * Internal function. You probably want ntfs_malloc_nofs()... + * + * Allocates @size bytes of memory, rounded up to multiples of PAGE_SIZE and + * returns a pointer to the allocated memory. + * + * If there was insufficient memory to complete the request, return NULL. + * Depending on @gfp_mask the allocation may be guaranteed to succeed. + */ +static inline void *__ntfs_malloc(unsigned long size, gfp_t gfp_mask) +{ + if (likely(size <= PAGE_SIZE)) { + BUG_ON(!size); + /* kmalloc() has per-CPU caches so is faster for now. */ + return kmalloc(PAGE_SIZE, gfp_mask & ~__GFP_HIGHMEM); + /* return (void *)__get_free_page(gfp_mask); */ + } + if (likely((size >> PAGE_SHIFT) < totalram_pages())) + return __vmalloc(size, gfp_mask); + return NULL; +} + +/** + * ntfs_malloc_nofs - allocate memory in multiples of pages + * @size: number of bytes to allocate + * + * Allocates @size bytes of memory, rounded up to multiples of PAGE_SIZE and + * returns a pointer to the allocated memory. + * + * If there was insufficient memory to complete the request, return NULL. + */ +static inline void *ntfs_malloc_nofs(unsigned long size) +{ + return __ntfs_malloc(size, GFP_NOFS | __GFP_HIGHMEM); +} + +/** + * ntfs_malloc_nofs_nofail - allocate memory in multiples of pages + * @size: number of bytes to allocate + * + * Allocates @size bytes of memory, rounded up to multiples of PAGE_SIZE and + * returns a pointer to the allocated memory. + * + * This function guarantees that the allocation will succeed. It will sleep + * for as long as it takes to complete the allocation. + * + * If there was insufficient memory to complete the request, return NULL. + */ +static inline void *ntfs_malloc_nofs_nofail(unsigned long size) +{ + return __ntfs_malloc(size, GFP_NOFS | __GFP_HIGHMEM | __GFP_NOFAIL); +} + +static inline void ntfs_free(void *addr) +{ + kvfree(addr); +} + +#endif /* _LINUX_NTFS_MALLOC_H */ diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c new file mode 100644 index 000000000000..bf028c1aea26 --- /dev/null +++ b/fs/ntfs/mft.c @@ -0,0 +1,2924 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel mft record operations. + * Part of this file is based on code from the NTFS-3G. + * + * Copyright (c) 2001-2012 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include +#include +#include + +#include "bitmap.h" +#include "lcnalloc.h" +#include "mft.h" +#include "ntfs.h" + +/* + * ntfs_mft_record_check - Check the consistency of an MFT record + * + * Make sure its general fields are safe, then examine all its + * attributes and apply generic checks to them. + * + * Returns 0 if the checks are successful. If not, return -EIO. + */ +int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m, + u64 mft_no) +{ + struct attr_record *a; + struct super_block *sb = vol->sb; + + if (!ntfs_is_file_record(m->magic)) { + ntfs_error(sb, "Record %llu has no FILE magic (0x%x)\n", + mft_no, le32_to_cpu(*(__le32 *)m)); + goto err_out; + } + + if (le16_to_cpu(m->usa_ofs) & 0x1 || + (vol->mft_record_size >> NTFS_BLOCK_SIZE_BITS) + 1 != le16_to_cpu(m->usa_count) || + le16_to_cpu(m->usa_ofs) + le16_to_cpu(m->usa_count) * 2 > vol->mft_record_size) { + ntfs_error(sb, "Record %llu has corrupt fix-up values fields\n", + mft_no); + goto err_out; + } + + if (le32_to_cpu(m->bytes_allocated) != vol->mft_record_size) { + ntfs_error(sb, "Record %llu has corrupt allocation size (%u <> %u)\n", + mft_no, vol->mft_record_size, + le32_to_cpu(m->bytes_allocated)); + goto err_out; + } + + if (le32_to_cpu(m->bytes_in_use) > vol->mft_record_size) { + ntfs_error(sb, "Record %llu has corrupt in-use size (%u > %u)\n", + mft_no, le32_to_cpu(m->bytes_in_use), + vol->mft_record_size); + goto err_out; + } + + if (le16_to_cpu(m->attrs_offset) & 7) { + ntfs_error(sb, "Attributes badly aligned in record %llu\n", + mft_no); + goto err_out; + } + + a = (struct attr_record *)((char *)m + le16_to_cpu(m->attrs_offset)); + if ((char *)a < (char *)m || (char *)a > (char *)m + vol->mft_record_size) { + ntfs_error(sb, "Record %llu is corrupt\n", mft_no); + goto err_out; + } + + return 0; + +err_out: + return -EIO; +} + +/* + * map_mft_record_folio - map the folio in which a specific mft record resides + * @ni: ntfs inode whose mft record page to map + * + * This maps the folio in which the mft record of the ntfs inode @ni is + * situated. + * + * This allocates a new buffer (@ni->mrec), copies the MFT record data from + * the mapped folio into this buffer, and applies the MST (Multi Sector + * Transfer) fixups on the copy. + * + * The folio is pinned (referenced) in @ni->folio to ensure the data remains + * valid in the page cache, but the returned pointer is the allocated copy. + * + * Return: A pointer to the allocated and fixed-up mft record (@ni->mrec). + * The return value needs to be checked with IS_ERR(). If it is true, + * PTR_ERR() contains the negative error code. + */ +static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni) +{ + loff_t i_size; + struct ntfs_volume *vol = ni->vol; + struct inode *mft_vi = vol->mft_ino; + struct folio *folio; + unsigned long index, end_index; + unsigned int ofs; + + WARN_ON(ni->folio); + /* + * The index into the page cache and the offset within the page cache + * page of the wanted mft record. + */ + index = NTFS_MFT_NR_TO_PIDX(vol, ni->mft_no); + ofs = NTFS_MFT_NR_TO_POFS(vol, ni->mft_no); + + i_size = i_size_read(mft_vi); + /* The maximum valid index into the page cache for $MFT's data. */ + end_index = i_size >> PAGE_SHIFT; + + /* If the wanted index is out of bounds the mft record doesn't exist. */ + if (unlikely(index >= end_index)) { + if (index > end_index || (i_size & ~PAGE_MASK) < ofs + + vol->mft_record_size) { + folio = ERR_PTR(-ENOENT); + ntfs_error(vol->sb, + "Attempt to read mft record 0x%llx, which is beyond the end of the mft. This is probably a bug in the ntfs driver.", + ni->mft_no); + goto err_out; + } + } + + /* Read, map, and pin the folio. */ + folio = read_mapping_folio(mft_vi->i_mapping, index, NULL); + if (!IS_ERR(folio)) { + u8 *addr; + + ni->mrec = kmalloc(vol->mft_record_size, GFP_NOFS); + if (!ni->mrec) { + folio_put(folio); + folio = ERR_PTR(-ENOMEM); + goto err_out; + } + + addr = kmap_local_folio(folio, 0); + memcpy(ni->mrec, addr + ofs, vol->mft_record_size); + post_read_mst_fixup((struct ntfs_record *)ni->mrec, vol->mft_record_size); + + /* Catch multi sector transfer fixup errors. */ + if (!ntfs_mft_record_check(vol, (struct mft_record *)ni->mrec, ni->mft_no)) { + kunmap_local(addr); + ni->folio = folio; + ni->folio_ofs = ofs; + return ni->mrec; + } + kunmap_local(addr); + folio_put(folio); + kfree(ni->mrec); + ni->mrec = NULL; + folio = ERR_PTR(-EIO); + NVolSetErrors(vol); + } +err_out: + ni->folio = NULL; + ni->folio_ofs = 0; + return (struct mft_record *)folio; +} + +/* + * map_mft_record - map and pin an mft record + * @ni: ntfs inode whose MFT record to map + * + * This function ensures the MFT record for the given inode is mapped and + * accessible. + * + * It increments the reference count of the ntfs inode. If the record is + * already mapped (@ni->folio is set), it returns the cached record + * immediately. + * + * Otherwise, it calls map_mft_record_folio() to read the folio from disk + * (if necessary via read_mapping_folio), allocate a buffer, and copy the + * record data. + * + * Return: A pointer to the mft record. You need to check the returned + * pointer with IS_ERR(). + */ +struct mft_record *map_mft_record(struct ntfs_inode *ni) +{ + struct mft_record *m; + + if (!ni) + return ERR_PTR(-EINVAL); + + ntfs_debug("Entering for mft_no 0x%llx.", ni->mft_no); + + /* Make sure the ntfs inode doesn't go away. */ + atomic_inc(&ni->count); + + if (ni->folio) + return (struct mft_record *)ni->mrec; + + m = map_mft_record_folio(ni); + if (!IS_ERR(m)) + return m; + + atomic_dec(&ni->count); + ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); + return m; +} + +/* + * unmap_mft_record - release a reference to a mapped mft record + * @ni: ntfs inode whose MFT record to unmap + * + * This decrements the reference count of the ntfs inode. + * + * It releases the caller's hold on the inode. If the reference count indicates + * that there are still other users (count > 1), the function returns + * immediately, keeping the resources (folio and mrec buffer) pinned for + * those users. + * + * NOTE: If caller has modified the mft record, it is imperative to set the mft + * record dirty BEFORE calling unmap_mft_record(). + */ +void unmap_mft_record(struct ntfs_inode *ni) +{ + struct folio *folio; + + if (!ni) + return; + + ntfs_debug("Entering for mft_no 0x%llx.", ni->mft_no); + + folio = ni->folio; + if (atomic_dec_return(&ni->count) > 1) + return; + WARN_ON(!folio); +} + +/* + * map_extent_mft_record - load an extent inode and attach it to its base + * @base_ni: base ntfs inode + * @mref: mft reference of the extent inode to load + * @ntfs_ino: on successful return, pointer to the struct ntfs_inode structure + * + * Load the extent mft record @mref and attach it to its base inode @base_ni. + * Return the mapped extent mft record if IS_ERR(result) is false. Otherwise + * PTR_ERR(result) gives the negative error code. + * + * On successful return, @ntfs_ino contains a pointer to the ntfs_inode + * structure of the mapped extent inode. + */ +struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref, + struct ntfs_inode **ntfs_ino) +{ + struct mft_record *m; + struct ntfs_inode *ni = NULL; + struct ntfs_inode **extent_nis = NULL; + int i; + u64 mft_no = MREF(mref); + u16 seq_no = MSEQNO(mref); + bool destroy_ni = false; + + ntfs_debug("Mapping extent mft record 0x%llx (base mft record 0x%llx).", + mft_no, base_ni->mft_no); + /* Make sure the base ntfs inode doesn't go away. */ + atomic_inc(&base_ni->count); + /* + * Check if this extent inode has already been added to the base inode, + * in which case just return it. If not found, add it to the base + * inode before returning it. + */ +retry: + mutex_lock(&base_ni->extent_lock); + if (base_ni->nr_extents > 0) { + extent_nis = base_ni->ext.extent_ntfs_inos; + for (i = 0; i < base_ni->nr_extents; i++) { + if (mft_no != extent_nis[i]->mft_no) + continue; + ni = extent_nis[i]; + /* Make sure the ntfs inode doesn't go away. */ + atomic_inc(&ni->count); + break; + } + } + if (likely(ni != NULL)) { + mutex_unlock(&base_ni->extent_lock); + atomic_dec(&base_ni->count); + /* We found the record; just have to map and return it. */ + m = map_mft_record(ni); + /* map_mft_record() has incremented this on success. */ + atomic_dec(&ni->count); + if (!IS_ERR(m)) { + /* Verify the sequence number. */ + if (likely(le16_to_cpu(m->sequence_number) == seq_no)) { + ntfs_debug("Done 1."); + *ntfs_ino = ni; + return m; + } + unmap_mft_record(ni); + ntfs_error(base_ni->vol->sb, + "Found stale extent mft reference! Corrupt filesystem. Run chkdsk."); + return ERR_PTR(-EIO); + } +map_err_out: + ntfs_error(base_ni->vol->sb, + "Failed to map extent mft record, error code %ld.", + -PTR_ERR(m)); + return m; + } + mutex_unlock(&base_ni->extent_lock); + + /* Record wasn't there. Get a new ntfs inode and initialize it. */ + ni = ntfs_new_extent_inode(base_ni->vol->sb, mft_no); + if (unlikely(!ni)) { + atomic_dec(&base_ni->count); + return ERR_PTR(-ENOMEM); + } + ni->vol = base_ni->vol; + ni->seq_no = seq_no; + ni->nr_extents = -1; + ni->ext.base_ntfs_ino = base_ni; + /* Now map the record. */ + m = map_mft_record(ni); + if (IS_ERR(m)) { + atomic_dec(&base_ni->count); + ntfs_clear_extent_inode(ni); + goto map_err_out; + } + /* Verify the sequence number if it is present. */ + if (seq_no && (le16_to_cpu(m->sequence_number) != seq_no)) { + ntfs_error(base_ni->vol->sb, + "Found stale extent mft reference! Corrupt filesystem. Run chkdsk."); + destroy_ni = true; + m = ERR_PTR(-EIO); + goto unm_nolock_err_out; + } + + mutex_lock(&base_ni->extent_lock); + for (i = 0; i < base_ni->nr_extents; i++) { + if (mft_no == extent_nis[i]->mft_no) { + mutex_unlock(&base_ni->extent_lock); + ntfs_clear_extent_inode(ni); + goto retry; + } + } + /* Attach extent inode to base inode, reallocating memory if needed. */ + if (!(base_ni->nr_extents & 3)) { + struct ntfs_inode **tmp; + int new_size = (base_ni->nr_extents + 4) * sizeof(struct ntfs_inode *); + + tmp = kvzalloc(new_size, GFP_NOFS); + if (unlikely(!tmp)) { + ntfs_error(base_ni->vol->sb, "Failed to allocate internal buffer."); + destroy_ni = true; + m = ERR_PTR(-ENOMEM); + goto unm_err_out; + } + if (base_ni->nr_extents) { + WARN_ON(!base_ni->ext.extent_ntfs_inos); + memcpy(tmp, base_ni->ext.extent_ntfs_inos, new_size - + 4 * sizeof(struct ntfs_inode *)); + kvfree(base_ni->ext.extent_ntfs_inos); + } + base_ni->ext.extent_ntfs_inos = tmp; + } + base_ni->ext.extent_ntfs_inos[base_ni->nr_extents++] = ni; + mutex_unlock(&base_ni->extent_lock); + atomic_dec(&base_ni->count); + ntfs_debug("Done 2."); + *ntfs_ino = ni; + return m; +unm_err_out: + mutex_unlock(&base_ni->extent_lock); +unm_nolock_err_out: + unmap_mft_record(ni); + atomic_dec(&base_ni->count); + /* + * If the extent inode was not attached to the base inode we need to + * release it or we will leak memory. + */ + if (destroy_ni) + ntfs_clear_extent_inode(ni); + return m; +} + +/* + * __mark_mft_record_dirty - mark the base vfs inode dirty + * @ni: ntfs inode describing the mapped mft record + * + * Internal function. Users should call mark_mft_record_dirty() instead. + * + * This function determines the base ntfs inode (in case @ni is an extent + * inode) and marks the corresponding VFS inode dirty. + * + * NOTE: We only set I_DIRTY_DATASYNC (and not I_DIRTY_PAGES) + * on the base vfs inode, because even though file data may have been modified, + * it is dirty in the inode meta data rather than the data page cache of the + * inode, and thus there are no data pages that need writing out. Therefore, a + * full mark_inode_dirty() is overkill. A mark_inode_dirty_sync(), on the + * other hand, is not sufficient, because ->write_inode needs to be called even + * in case of fdatasync. This needs to happen or the file data would not + * necessarily hit the device synchronously, even though the vfs inode has the + * O_SYNC flag set. Also, I_DIRTY_DATASYNC simply "feels" better than just + * I_DIRTY_SYNC, since the file data has not actually hit the block device yet, + * which is not what I_DIRTY_SYNC on its own would suggest. + */ +void __mark_mft_record_dirty(struct ntfs_inode *ni) +{ + struct ntfs_inode *base_ni; + + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + WARN_ON(NInoAttr(ni)); + /* Determine the base vfs inode and mark it dirty, too. */ + if (likely(ni->nr_extents >= 0)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + __mark_inode_dirty(VFS_I(base_ni), I_DIRTY_DATASYNC); +} + +/* + * ntfs_bio_end_io - bio completion callback for MFT record writes + * + * Decrements the folio reference count that was incremented before + * submit_bio(). This prevents a race condition where umount could + * evict the inode and release the folio while I/O is still in flight, + * potentially causing data corruption or use-after-free. + */ +static void ntfs_bio_end_io(struct bio *bio) +{ + if (bio->bi_private) + folio_put((struct folio *)bio->bi_private); + bio_put(bio); +} + +/* + * ntfs_sync_mft_mirror - synchronize an mft record to the mft mirror + * @vol: ntfs volume on which the mft record to synchronize resides + * @mft_no: mft record number of mft record to synchronize + * @m: mapped, mst protected (extent) mft record to synchronize + * + * Write the mapped, mst protected (extent) mft record @m with mft record + * number @mft_no to the mft mirror ($MFTMirr) of the ntfs volume @vol. + * + * On success return 0. On error return -errno and set the volume errors flag + * in the ntfs volume @vol. + * + * NOTE: We always perform synchronous i/o. + */ +int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, + struct mft_record *m) +{ + u8 *kmirr = NULL; + struct folio *folio; + unsigned int folio_ofs, lcn_folio_off = 0; + int err = 0; + struct bio *bio; + + ntfs_debug("Entering for inode 0x%llx.", mft_no); + + if (unlikely(!vol->mftmirr_ino)) { + /* This could happen during umount... */ + err = -EIO; + goto err_out; + } + /* Get the page containing the mirror copy of the mft record @m. */ + folio = read_mapping_folio(vol->mftmirr_ino->i_mapping, + NTFS_MFT_NR_TO_PIDX(vol, mft_no), NULL); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Failed to map mft mirror page."); + err = PTR_ERR(folio); + goto err_out; + } + + folio_lock(folio); + folio_clear_uptodate(folio); + /* Offset of the mft mirror record inside the page. */ + folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no); + /* The address in the page of the mirror copy of the mft record @m. */ + kmirr = kmap_local_folio(folio, 0) + folio_ofs; + /* Copy the mst protected mft record to the mirror. */ + memcpy(kmirr, m, vol->mft_record_size); + + if (vol->cluster_size_bits > PAGE_SHIFT) { + lcn_folio_off = folio->index << PAGE_SHIFT; + lcn_folio_off &= vol->cluster_size_mask; + } + + bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); + bio->bi_iter.bi_sector = + NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + + lcn_folio_off + folio_ofs); + + if (!bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) { + err = -EIO; + bio_put(bio); + goto unlock_folio; + } + + bio->bi_end_io = ntfs_bio_end_io; + submit_bio(bio); + /* Current state: all buffers are clean, unlocked, and uptodate. */ + folio_mark_uptodate(folio); + +unlock_folio: + folio_unlock(folio); + kunmap_local(kmirr); + folio_put(folio); + if (likely(!err)) { + ntfs_debug("Done."); + } else { + ntfs_error(vol->sb, "I/O error while writing mft mirror record 0x%llx!", mft_no); +err_out: + ntfs_error(vol->sb, + "Failed to synchronize $MFTMirr (error code %i). Volume will be left marked dirty on umount. Run chkdsk on the partition after umounting to correct this.", + err); + NVolSetErrors(vol); + } + return err; +} + +/* + * write_mft_record_nolock - write out a mapped (extent) mft record + * @ni: ntfs inode describing the mapped (extent) mft record + * @m: mapped (extent) mft record to write + * @sync: if true, wait for i/o completion + * + * Write the mapped (extent) mft record @m described by the (regular or extent) + * ntfs inode @ni to backing store. If the mft record @m has a counterpart in + * the mft mirror, that is also updated. + * + * We only write the mft record if the ntfs inode @ni is dirty. + * + * On success, clean the mft record and return 0. + * On error (specifically ENOMEM), we redirty the record so it can be retried. + * For other errors, we mark the volume with errors. + */ +int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int sync) +{ + struct ntfs_volume *vol = ni->vol; + struct folio *folio = ni->folio; + int err = 0, i = 0; + u8 *kaddr; + struct mft_record *fixup_m; + struct bio *bio; + unsigned int offset = 0, folio_size; + + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + + WARN_ON(NInoAttr(ni)); + WARN_ON(!folio_test_locked(folio)); + + /* + * If the struct ntfs_inode is clean no need to do anything. If it is dirty, + * mark it as clean now so that it can be redirtied later on if needed. + * There is no danger of races since the caller is holding the locks + * for the mft record @m and the page it is in. + */ + if (!NInoTestClearDirty(ni)) + goto done; + + kaddr = kmap_local_folio(folio, 0); + fixup_m = (struct mft_record *)(kaddr + ni->folio_ofs); + memcpy(fixup_m, m, vol->mft_record_size); + + /* Apply the mst protection fixups. */ + err = pre_write_mst_fixup((struct ntfs_record *)fixup_m, vol->mft_record_size); + if (err) { + ntfs_error(vol->sb, "Failed to apply mst fixups!"); + goto err_out; + } + + folio_size = vol->mft_record_size / ni->mft_lcn_count; + while (i < ni->mft_lcn_count) { + unsigned int clu_off; + + clu_off = (unsigned int)((s64)ni->mft_no * vol->mft_record_size + offset) & + vol->cluster_size_mask; + + bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); + bio->bi_iter.bi_sector = + NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) + + clu_off); + + if (!bio_add_folio(bio, folio, folio_size, + ni->folio_ofs + offset)) { + err = -EIO; + goto put_bio_out; + } + + /* Synchronize the mft mirror now if not @sync. */ + if (!sync && ni->mft_no < vol->mftmirr_size) + ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m); + + folio_get(folio); + bio->bi_private = folio; + bio->bi_end_io = ntfs_bio_end_io; + submit_bio(bio); + offset += vol->cluster_size; + i++; + } + + /* If @sync, now synchronize the mft mirror. */ + if (sync && ni->mft_no < vol->mftmirr_size) + ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m); + kunmap_local(kaddr); + if (unlikely(err)) { + /* I/O error during writing. This is really bad! */ + ntfs_error(vol->sb, + "I/O error while writing mft record 0x%llx! Marking base inode as bad. You should unmount the volume and run chkdsk.", + ni->mft_no); + goto err_out; + } +done: + ntfs_debug("Done."); + return 0; +put_bio_out: + bio_put(bio); +err_out: + /* + * Current state: all buffers are clean, unlocked, and uptodate. + * The caller should mark the base inode as bad so that no more i/o + * happens. ->drop_inode() will still be invoked so all extent inodes + * and other allocated memory will be freed. + */ + if (err == -ENOMEM) { + ntfs_error(vol->sb, + "Not enough memory to write mft record. Redirtying so the write is retried later."); + mark_mft_record_dirty(ni); + err = 0; + } else + NVolSetErrors(vol); + return err; +} + +static int ntfs_test_inode_wb(struct inode *vi, u64 ino, void *data) +{ + struct ntfs_attr *na = data; + + if (!ntfs_test_inode(vi, na)) + return 0; + + /* + * Without this, ntfs_write_mst_block() could call iput_final() + * , and ntfs_evict_big_inode() could try to unlink this inode + * and the contex could be blocked infinitly in map_mft_record(). + */ + if (NInoBeingDeleted(NTFS_I(vi))) { + na->state = NI_BeingDeleted; + return -1; + } + + /* + * This condition can prevent ntfs_write_mst_block() + * from applying/undo fixups while ntfs_create() being + * called + */ + spin_lock(&vi->i_lock); + if (inode_state_read_once(vi) & I_CREATING) { + spin_unlock(&vi->i_lock); + na->state = NI_BeingCreated; + return -1; + } + spin_unlock(&vi->i_lock); + + return igrab(vi) ? 1 : -1; +} + +/* + * ntfs_may_write_mft_record - check if an mft record may be written out + * @vol: [IN] ntfs volume on which the mft record to check resides + * @mft_no: [IN] mft record number of the mft record to check + * @m: [IN] mapped mft record to check + * @locked_ni: [OUT] caller has to unlock this ntfs inode if one is returned + * @ref_vi: [OUT] caller has to drop this vfs inode if one is returned + * + * Check if the mapped (base or extent) mft record @m with mft record number + * @mft_no belonging to the ntfs volume @vol may be written out. If necessary + * and possible the ntfs inode of the mft record is locked and the base vfs + * inode is pinned. The locked ntfs inode is then returned in @locked_ni. The + * caller is responsible for unlocking the ntfs inode and unpinning the base + * vfs inode. + * + * To avoid deadlock when the caller holds a folio lock, if the function + * returns @ref_vi it defers dropping the vfs inode reference by returning + * it in @ref_vi instead of calling iput() directly. The caller must call + * iput() on @ref_vi after releasing the folio lock. + * + * Return 'true' if the mft record may be written out and 'false' if not. + * + * The caller has locked the page and cleared the uptodate flag on it which + * means that we can safely write out any dirty mft records that do not have + * their inodes in icache as determined by find_inode_nowait(). + * + * Here is a description of the tests we perform: + * + * If the inode is found in icache we know the mft record must be a base mft + * record. If it is dirty, we do not write it and return 'false' as the vfs + * inode write paths will result in the access times being updated which would + * cause the base mft record to be redirtied and written out again. + * + * If the inode is in icache and not dirty, we attempt to lock the mft record + * and if we find the lock was already taken, it is not safe to write the mft + * record and we return 'false'. + * + * If we manage to obtain the lock we have exclusive access to the mft record, + * which also allows us safe writeout of the mft record. We then set + * @locked_ni to the locked ntfs inode and return 'true'. + * + * Note we cannot just lock the mft record and sleep while waiting for the lock + * because this would deadlock due to lock reversal. + * + * If the inode is not in icache we need to perform further checks. + * + * If the mft record is not a FILE record or it is a base mft record, we can + * safely write it and return 'true'. + * + * We now know the mft record is an extent mft record. We check if the inode + * corresponding to its base mft record is in icache. If it is not, we cannot + * safely determine the state of the extent inode, so we return 'false'. + * + * We now have the base inode for the extent mft record. We check if it has an + * ntfs inode for the extent mft record attached. If not, it is safe to write + * the extent mft record and we return 'true'. + * + * If the extent inode is attached, we check if it is dirty. If so, we return + * 'false' (letting the standard write_inode path handle it). + * + * If it is not dirty, we attempt to lock the extent mft record. If the lock + * was already taken, it is not safe to write and we return 'false'. + * + * If we manage to obtain the lock we have exclusive access to the extent mft + * record. We set @locked_ni to the now locked ntfs inode and return 'true'. + */ +static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, + const struct mft_record *m, struct ntfs_inode **locked_ni, + struct inode **ref_vi) +{ + struct super_block *sb = vol->sb; + struct inode *mft_vi = vol->mft_ino; + struct inode *vi; + struct ntfs_inode *ni, *eni, **extent_nis; + int i; + struct ntfs_attr na = {0}; + + ntfs_debug("Entering for inode 0x%llx.", mft_no); + /* + * Normally we do not return a locked inode so set @locked_ni to NULL. + */ + *locked_ni = NULL; + *ref_vi = NULL; + + /* + * Check if the inode corresponding to this mft record is in the VFS + * inode cache and obtain a reference to it if it is. + */ + ntfs_debug("Looking for inode 0x%llx in icache.", mft_no); + na.mft_no = mft_no; + na.type = AT_UNUSED; + /* + * Optimize inode 0, i.e. $MFT itself, since we have it in memory and + * we get here for it rather often. + */ + if (!mft_no) { + /* Balance the below iput(). */ + vi = igrab(mft_vi); + WARN_ON(vi != mft_vi); + } else { + /* + * Have to use find_inode_nowait() since ilookup5_nowait() + * waits for inode with I_FREEING, which causes ntfs to deadlock + * when inodes are unlinked concurrently + */ + vi = find_inode_nowait(sb, mft_no, ntfs_test_inode_wb, &na); + if (na.state == NI_BeingDeleted || na.state == NI_BeingCreated) + return false; + } + if (vi) { + ntfs_debug("Base inode 0x%llx is in icache.", mft_no); + /* The inode is in icache. */ + ni = NTFS_I(vi); + /* Take a reference to the ntfs inode. */ + atomic_inc(&ni->count); + /* If the inode is dirty, do not write this record. */ + if (NInoDirty(ni)) { + ntfs_debug("Inode 0x%llx is dirty, do not write it.", + mft_no); + atomic_dec(&ni->count); + *ref_vi = vi; + return false; + } + ntfs_debug("Inode 0x%llx is not dirty.", mft_no); + /* The inode is not dirty, try to take the mft record lock. */ + if (unlikely(!mutex_trylock(&ni->mrec_lock))) { + ntfs_debug("Mft record 0x%llx is already locked, do not write it.", mft_no); + atomic_dec(&ni->count); + *ref_vi = vi; + return false; + } + ntfs_debug("Managed to lock mft record 0x%llx, write it.", + mft_no); + /* + * The write has to occur while we hold the mft record lock so + * return the locked ntfs inode. + */ + *locked_ni = ni; + return true; + } + ntfs_debug("Inode 0x%llx is not in icache.", mft_no); + /* The inode is not in icache. */ + /* Write the record if it is not a mft record (type "FILE"). */ + if (!ntfs_is_mft_record(m->magic)) { + ntfs_debug("Mft record 0x%llx is not a FILE record, write it.", + mft_no); + return true; + } + /* Write the mft record if it is a base inode. */ + if (!m->base_mft_record) { + ntfs_debug("Mft record 0x%llx is a base record, write it.", + mft_no); + return true; + } + /* + * This is an extent mft record. Check if the inode corresponding to + * its base mft record is in icache and obtain a reference to it if it + * is. + */ + na.mft_no = MREF_LE(m->base_mft_record); + na.state = 0; + ntfs_debug("Mft record 0x%llx is an extent record. Looking for base inode 0x%llx in icache.", + mft_no, na.mft_no); + if (!na.mft_no) { + /* Balance the below iput(). */ + vi = igrab(mft_vi); + WARN_ON(vi != mft_vi); + } else { + vi = find_inode_nowait(sb, mft_no, ntfs_test_inode_wb, &na); + if (na.state == NI_BeingDeleted || na.state == NI_BeingCreated) + return false; + } + + if (!vi) + return false; + ntfs_debug("Base inode 0x%llx is in icache.", na.mft_no); + /* + * The base inode is in icache. Check if it has the extent inode + * corresponding to this extent mft record attached. + */ + ni = NTFS_I(vi); + mutex_lock(&ni->extent_lock); + if (ni->nr_extents <= 0) { + /* + * The base inode has no attached extent inodes, write this + * extent mft record. + */ + mutex_unlock(&ni->extent_lock); + *ref_vi = vi; + ntfs_debug("Base inode 0x%llx has no attached extent inodes, write the extent record.", + na.mft_no); + return true; + } + /* Iterate over the attached extent inodes. */ + extent_nis = ni->ext.extent_ntfs_inos; + for (eni = NULL, i = 0; i < ni->nr_extents; ++i) { + if (mft_no == extent_nis[i]->mft_no) { + /* + * Found the extent inode corresponding to this extent + * mft record. + */ + eni = extent_nis[i]; + break; + } + } + /* + * If the extent inode was not attached to the base inode, write this + * extent mft record. + */ + if (!eni) { + mutex_unlock(&ni->extent_lock); + *ref_vi = vi; + ntfs_debug("Extent inode 0x%llx is not attached to its base inode 0x%llx, write the extent record.", + mft_no, na.mft_no); + return true; + } + ntfs_debug("Extent inode 0x%llx is attached to its base inode 0x%llx.", + mft_no, na.mft_no); + /* Take a reference to the extent ntfs inode. */ + atomic_inc(&eni->count); + mutex_unlock(&ni->extent_lock); + + /* if extent inode is dirty, write_inode will write it */ + if (NInoDirty(eni)) { + atomic_dec(&eni->count); + *ref_vi = vi; + return false; + } + + /* + * Found the extent inode coresponding to this extent mft record. + * Try to take the mft record lock. + */ + if (unlikely(!mutex_trylock(&eni->mrec_lock))) { + atomic_dec(&eni->count); + *ref_vi = vi; + ntfs_debug("Extent mft record 0x%llx is already locked, do not write it.", + mft_no); + return false; + } + ntfs_debug("Managed to lock extent mft record 0x%llx, write it.", + mft_no); + /* + * The write has to occur while we hold the mft record lock so return + * the locked extent ntfs inode. + */ + *locked_ni = eni; + return true; +} + +static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk."; + +#define RESERVED_MFT_RECORDS 64 + +/* + * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name + * @vol: volume on which to search for a free mft record + * @base_ni: open base inode if allocating an extent mft record or NULL + * + * Search for a free mft record in the mft bitmap attribute on the ntfs volume + * @vol. + * + * If @base_ni is NULL start the search at the default allocator position. + * + * If @base_ni is not NULL start the search at the mft record after the base + * mft record @base_ni. + * + * Return the free mft record on success and -errno on error. An error code of + * -ENOSPC means that there are no free mft records in the currently + * initialized mft bitmap. + * + * Locking: Caller must hold vol->mftbmp_lock for writing. + */ +static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol, + struct ntfs_inode *base_ni) +{ + s64 pass_end, ll, data_pos, pass_start, ofs, bit; + unsigned long flags; + struct address_space *mftbmp_mapping; + u8 *buf = NULL, *byte; + struct folio *folio; + unsigned int folio_ofs, size; + u8 pass, b; + + ntfs_debug("Searching for free mft record in the currently initialized mft bitmap."); + mftbmp_mapping = vol->mftbmp_ino->i_mapping; + /* + * Set the end of the pass making sure we do not overflow the mft + * bitmap. + */ + read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags); + pass_end = NTFS_I(vol->mft_ino)->allocated_size >> + vol->mft_record_size_bits; + read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags); + read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); + ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3; + read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); + if (pass_end > ll) + pass_end = ll; + pass = 1; + if (!base_ni) + data_pos = vol->mft_data_pos; + else + data_pos = base_ni->mft_no + 1; + if (data_pos < RESERVED_MFT_RECORDS) + data_pos = RESERVED_MFT_RECORDS; + if (data_pos >= pass_end) { + data_pos = RESERVED_MFT_RECORDS; + pass = 2; + /* This happens on a freshly formatted volume. */ + if (data_pos >= pass_end) + return -ENOSPC; + } + + if (base_ni && base_ni->mft_no == FILE_MFT) { + data_pos = 0; + pass = 2; + } + + pass_start = data_pos; + ntfs_debug("Starting bitmap search: pass %u, pass_start 0x%llx, pass_end 0x%llx, data_pos 0x%llx.", + pass, pass_start, pass_end, data_pos); + /* Loop until a free mft record is found. */ + for (; pass <= 2;) { + /* Cap size to pass_end. */ + ofs = data_pos >> 3; + folio_ofs = ofs & ~PAGE_MASK; + size = PAGE_SIZE - folio_ofs; + ll = ((pass_end + 7) >> 3) - ofs; + if (size > ll) + size = ll; + size <<= 3; + /* + * If we are still within the active pass, search the next page + * for a zero bit. + */ + if (size) { + folio = read_mapping_folio(mftbmp_mapping, + ofs >> PAGE_SHIFT, NULL); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Failed to read mft bitmap, aborting."); + return PTR_ERR(folio); + } + folio_lock(folio); + buf = (u8 *)kmap_local_folio(folio, 0) + folio_ofs; + bit = data_pos & 7; + data_pos &= ~7ull; + ntfs_debug("Before inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx", + size, data_pos, bit); + for (; bit < size && data_pos + bit < pass_end; + bit &= ~7ull, bit += 8) { + /* + * If we're extending $MFT and running out of the first + * mft record (base record) then give up searching since + * no guarantee that the found record will be accessible. + */ + if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) { + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + return -ENOSPC; + } + + byte = buf + (bit >> 3); + if (*byte == 0xff) + continue; + b = ffz((unsigned long)*byte); + if (b < 8 && b >= (bit & 7)) { + ll = data_pos + (bit & ~7ull) + b; + if (unlikely(ll > (1ll << 32))) { + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + return -ENOSPC; + } + *byte |= 1 << b; + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", + ll); + return ll; + } + } + ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx", + size, data_pos, bit); + data_pos += size; + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + /* + * If the end of the pass has not been reached yet, + * continue searching the mft bitmap for a zero bit. + */ + if (data_pos < pass_end) + continue; + } + /* Do the next pass. */ + if (++pass == 2) { + /* + * Starting the second pass, in which we scan the first + * part of the zone which we omitted earlier. + */ + pass_end = pass_start; + data_pos = pass_start = RESERVED_MFT_RECORDS; + ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.", + pass, pass_start, pass_end); + if (data_pos >= pass_end) + break; + } + } + /* No free mft records in currently initialized mft bitmap. */ + ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)"); + return -ENOSPC; +} + +static int ntfs_mft_attr_extend(struct ntfs_inode *ni) +{ + int ret = 0; + struct ntfs_inode *base_ni; + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + if (!NInoAttrList(base_ni)) { + ret = ntfs_inode_add_attrlist(base_ni); + if (ret) { + pr_err("Can not add attrlist\n"); + goto out; + } else { + ret = -EAGAIN; + goto out; + } + } + + ret = ntfs_attr_update_mapping_pairs(ni, 0); + if (ret) + pr_err("MP update failed\n"); + +out: + return ret; +} + +/* + * ntfs_mft_bitmap_extend_allocation_nolock - extend mft bitmap by a cluster + * @vol: volume on which to extend the mft bitmap attribute + * + * Extend the mft bitmap attribute on the ntfs volume @vol by one cluster. + * + * Note: Only changes allocated_size, i.e. does not touch initialized_size or + * data_size. + * + * Return 0 on success and -errno on error. + * + * Locking: - Caller must hold vol->mftbmp_lock for writing. + * - This function takes NTFS_I(vol->mftbmp_ino)->runlist.lock for + * writing and releases it before returning. + * - This function takes vol->lcnbmp_lock for writing and releases it + * before returning. + */ +static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) +{ + s64 lcn; + s64 ll; + unsigned long flags; + struct folio *folio; + struct ntfs_inode *mft_ni, *mftbmp_ni; + struct runlist_element *rl, *rl2 = NULL; + struct ntfs_attr_search_ctx *ctx = NULL; + struct mft_record *mrec; + struct attr_record *a = NULL; + int ret, mp_size; + u32 old_alen = 0; + u8 *b, tb; + struct { + u8 added_cluster:1; + u8 added_run:1; + u8 mp_rebuilt:1; + u8 mp_extended:1; + } status = { 0, 0, 0, 0 }; + size_t new_rl_count; + + ntfs_debug("Extending mft bitmap allocation."); + mft_ni = NTFS_I(vol->mft_ino); + mftbmp_ni = NTFS_I(vol->mftbmp_ino); + /* + * Determine the last lcn of the mft bitmap. The allocated size of the + * mft bitmap cannot be zero so we are ok to do this. + */ + down_write(&mftbmp_ni->runlist.lock); + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + ll = mftbmp_ni->allocated_size; + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + rl = ntfs_attr_find_vcn_nolock(mftbmp_ni, + NTFS_B_TO_CLU(vol, ll - 1), NULL); + if (IS_ERR(rl) || unlikely(!rl->length || rl->lcn < 0)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, + "Failed to determine last allocated cluster of mft bitmap attribute."); + if (!IS_ERR(rl)) + ret = -EIO; + else + ret = PTR_ERR(rl); + return ret; + } + lcn = rl->lcn + rl->length; + ntfs_debug("Last lcn of mft bitmap attribute is 0x%llx.", + (long long)lcn); + /* + * Attempt to get the cluster following the last allocated cluster by + * hand as it may be in the MFT zone so the allocator would not give it + * to us. + */ + ll = lcn >> 3; + folio = read_mapping_folio(vol->lcnbmp_ino->i_mapping, + ll >> PAGE_SHIFT, NULL); + if (IS_ERR(folio)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to read from lcn bitmap."); + return PTR_ERR(folio); + } + + down_write(&vol->lcnbmp_lock); + folio_lock(folio); + b = (u8 *)kmap_local_folio(folio, 0) + (ll & ~PAGE_MASK); + tb = 1 << (lcn & 7ull); + if (*b != 0xff && !(*b & tb)) { + /* Next cluster is free, allocate it. */ + *b |= tb; + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(b); + folio_put(folio); + up_write(&vol->lcnbmp_lock); + /* Update the mft bitmap runlist. */ + rl->length++; + rl[1].vcn++; + status.added_cluster = 1; + ntfs_debug("Appending one cluster to mft bitmap."); + } else { + folio_unlock(folio); + kunmap_local(b); + folio_put(folio); + up_write(&vol->lcnbmp_lock); + /* Allocate a cluster from the DATA_ZONE. */ + rl2 = ntfs_cluster_alloc(vol, rl[1].vcn, 1, lcn, DATA_ZONE, + true, false, false); + if (IS_ERR(rl2)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, + "Failed to allocate a cluster for the mft bitmap."); + return PTR_ERR(rl2); + } + rl = ntfs_runlists_merge(&mftbmp_ni->runlist, rl2, 0, &new_rl_count); + if (IS_ERR(rl)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to merge runlists for mft bitmap."); + if (ntfs_cluster_free_from_rl(vol, rl2)) { + ntfs_error(vol->sb, "Failed to deallocate allocated cluster.%s", + es); + NVolSetErrors(vol); + } + kvfree(rl2); + return PTR_ERR(rl); + } + mftbmp_ni->runlist.rl = rl; + mftbmp_ni->runlist.count = new_rl_count; + status.added_run = 1; + ntfs_debug("Adding one run to mft bitmap."); + /* Find the last run in the new runlist. */ + for (; rl[1].length; rl++) + ; + } + /* + * Update the attribute record as well. Note: @rl is the last + * (non-terminator) runlist element of mft bitmap. + */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record."); + ret = PTR_ERR(mrec); + goto undo_alloc; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + ret = -ENOMEM; + goto undo_alloc; + } + ret = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn, NULL, + 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, + "Failed to find last attribute extent of mft bitmap attribute."); + if (ret == -ENOENT) + ret = -EIO; + goto undo_alloc; + } + a = ctx->attr; + ll = le64_to_cpu(a->data.non_resident.lowest_vcn); + /* Search back for the previous last allocated cluster of mft bitmap. */ + for (rl2 = rl; rl2 > mftbmp_ni->runlist.rl; rl2--) { + if (ll >= rl2->vcn) + break; + } + WARN_ON(ll < rl2->vcn); + WARN_ON(ll >= rl2->vcn + rl2->length); + /* Get the size for the new mapping pairs array for this extent. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl2, ll, -1, -1); + if (unlikely(mp_size <= 0)) { + ntfs_error(vol->sb, + "Get size for mapping pairs failed for mft bitmap attribute extent."); + ret = mp_size; + if (!ret) + ret = -EIO; + goto undo_alloc; + } + /* Expand the attribute record if necessary. */ + old_alen = le32_to_cpu(a->length); + ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); + if (unlikely(ret)) { + ret = ntfs_mft_attr_extend(mftbmp_ni); + if (!ret) + goto extended_ok; + if (ret != -EAGAIN) + status.mp_extended = 1; + goto undo_alloc; + } + status.mp_rebuilt = 1; + /* Generate the mapping pairs array directly into the attr record. */ + ret = ntfs_mapping_pairs_build(vol, (u8 *)a + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset), + mp_size, rl2, ll, -1, NULL, NULL, NULL); + if (unlikely(ret)) { + ntfs_error(vol->sb, + "Failed to build mapping pairs array for mft bitmap attribute."); + goto undo_alloc; + } + /* Update the highest_vcn. */ + a->data.non_resident.highest_vcn = cpu_to_le64(rl[1].vcn - 1); + /* + * We now have extended the mft bitmap allocated_size by one cluster. + * Reflect this in the struct ntfs_inode structure and the attribute record. + */ + if (a->data.non_resident.lowest_vcn) { + /* + * We are not in the first attribute extent, switch to it, but + * first ensure the changes will make it to disk later. + */ + mark_mft_record_dirty(ctx->ntfs_ino); +extended_ok: + ntfs_attr_reinit_search_ctx(ctx); + ret = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, + "Failed to find first attribute extent of mft bitmap attribute."); + goto restore_undo_alloc; + } + a = ctx->attr; + } + + write_lock_irqsave(&mftbmp_ni->size_lock, flags); + mftbmp_ni->allocated_size += vol->cluster_size; + a->data.non_resident.allocated_size = + cpu_to_le64(mftbmp_ni->allocated_size); + write_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + /* Ensure the changes make it to disk. */ + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mftbmp_ni->runlist.lock); + ntfs_debug("Done."); + return 0; + +restore_undo_alloc: + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn, NULL, + 0, ctx)) { + ntfs_error(vol->sb, + "Failed to find last attribute extent of mft bitmap attribute.%s", es); + write_lock_irqsave(&mftbmp_ni->size_lock, flags); + mftbmp_ni->allocated_size += vol->cluster_size; + write_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mftbmp_ni->runlist.lock); + /* + * The only thing that is now wrong is ->allocated_size of the + * base attribute extent which chkdsk should be able to fix. + */ + NVolSetErrors(vol); + return ret; + } + a = ctx->attr; + a->data.non_resident.highest_vcn = cpu_to_le64(rl[1].vcn - 2); +undo_alloc: + if (status.added_cluster) { + /* Truncate the last run in the runlist by one cluster. */ + rl->length--; + rl[1].vcn--; + } else if (status.added_run) { + lcn = rl->lcn; + /* Remove the last run from the runlist. */ + rl->lcn = rl[1].lcn; + rl->length = 0; + mftbmp_ni->runlist.count--; + } + /* Deallocate the cluster. */ + down_write(&vol->lcnbmp_lock); + if (ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn)) { + ntfs_error(vol->sb, "Failed to free allocated cluster.%s", es); + NVolSetErrors(vol); + } else + ntfs_inc_free_clusters(vol, 1); + up_write(&vol->lcnbmp_lock); + if (status.mp_rebuilt) { + if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + old_alen - le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + rl2, ll, -1, NULL, NULL, NULL)) { + ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es); + NVolSetErrors(vol); + } + if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) { + ntfs_error(vol->sb, "Failed to restore attribute record.%s", es); + NVolSetErrors(vol); + } + mark_mft_record_dirty(ctx->ntfs_ino); + } else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) { + ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es); + NVolSetErrors(vol); + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (!IS_ERR(mrec)) + unmap_mft_record(mft_ni); + up_write(&mftbmp_ni->runlist.lock); + return ret; +} + +/* + * ntfs_mft_bitmap_extend_initialized_nolock - extend mftbmp initialized data + * @vol: volume on which to extend the mft bitmap attribute + * + * Extend the initialized portion of the mft bitmap attribute on the ntfs + * volume @vol by 8 bytes. + * + * Note: Only changes initialized_size and data_size, i.e. requires that + * allocated_size is big enough to fit the new initialized_size. + * + * Return 0 on success and -error on error. + * + * Locking: Caller must hold vol->mftbmp_lock for writing. + */ +static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) +{ + s64 old_data_size, old_initialized_size; + unsigned long flags; + struct inode *mftbmp_vi; + struct ntfs_inode *mft_ni, *mftbmp_ni; + struct ntfs_attr_search_ctx *ctx; + struct mft_record *mrec; + struct attr_record *a; + int ret; + + ntfs_debug("Extending mft bitmap initialized (and data) size."); + mft_ni = NTFS_I(vol->mft_ino); + mftbmp_vi = vol->mftbmp_ino; + mftbmp_ni = NTFS_I(mftbmp_vi); + /* Get the attribute record. */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record."); + return PTR_ERR(mrec); + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + ret = -ENOMEM; + goto unm_err_out; + } + ret = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, + "Failed to find first attribute extent of mft bitmap attribute."); + if (ret == -ENOENT) + ret = -EIO; + goto put_err_out; + } + a = ctx->attr; + write_lock_irqsave(&mftbmp_ni->size_lock, flags); + old_data_size = i_size_read(mftbmp_vi); + old_initialized_size = mftbmp_ni->initialized_size; + /* + * We can simply update the initialized_size before filling the space + * with zeroes because the caller is holding the mft bitmap lock for + * writing which ensures that no one else is trying to access the data. + */ + mftbmp_ni->initialized_size += 8; + a->data.non_resident.initialized_size = + cpu_to_le64(mftbmp_ni->initialized_size); + if (mftbmp_ni->initialized_size > old_data_size) { + i_size_write(mftbmp_vi, mftbmp_ni->initialized_size); + a->data.non_resident.data_size = + cpu_to_le64(mftbmp_ni->initialized_size); + } + write_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + /* Ensure the changes make it to disk. */ + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + /* Initialize the mft bitmap attribute value with zeroes. */ + ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0); + if (likely(!ret)) { + ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap."); + ntfs_inc_free_mft_records(vol, 8 * 8); + return 0; + } + ntfs_error(vol->sb, "Failed to write to mft bitmap."); + /* Try to recover from the error. */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record.%s", es); + NVolSetErrors(vol); + return ret; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context.%s", es); + NVolSetErrors(vol); + goto unm_err_out; + } + if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + ntfs_error(vol->sb, + "Failed to find first attribute extent of mft bitmap attribute.%s", es); + NVolSetErrors(vol); +put_err_out: + ntfs_attr_put_search_ctx(ctx); +unm_err_out: + unmap_mft_record(mft_ni); + goto err_out; + } + a = ctx->attr; + write_lock_irqsave(&mftbmp_ni->size_lock, flags); + mftbmp_ni->initialized_size = old_initialized_size; + a->data.non_resident.initialized_size = + cpu_to_le64(old_initialized_size); + if (i_size_read(mftbmp_vi) != old_data_size) { + i_size_write(mftbmp_vi, old_data_size); + a->data.non_resident.data_size = cpu_to_le64(old_data_size); + } + write_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); +#ifdef DEBUG + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + ntfs_debug("Restored status of mftbmp: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + mftbmp_ni->allocated_size, i_size_read(mftbmp_vi), + mftbmp_ni->initialized_size); + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); +#endif /* DEBUG */ +err_out: + return ret; +} + +/* + * ntfs_mft_data_extend_allocation_nolock - extend mft data attribute + * @vol: volume on which to extend the mft data attribute + * + * Extend the mft data attribute on the ntfs volume @vol by 16 mft records + * worth of clusters or if not enough space for this by one mft record worth + * of clusters. + * + * Note: Only changes allocated_size, i.e. does not touch initialized_size or + * data_size. + * + * Return 0 on success and -errno on error. + * + * Locking: - Caller must hold vol->mftbmp_lock for writing. + * - This function takes NTFS_I(vol->mft_ino)->runlist.lock for + * writing and releases it before returning. + * - This function calls functions which take vol->lcnbmp_lock for + * writing and release it before returning. + */ +static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) +{ + s64 lcn; + s64 old_last_vcn; + s64 min_nr, nr, ll; + unsigned long flags; + struct ntfs_inode *mft_ni; + struct runlist_element *rl, *rl2; + struct ntfs_attr_search_ctx *ctx = NULL; + struct mft_record *mrec; + struct attr_record *a = NULL; + int ret, mp_size; + u32 old_alen = 0; + bool mp_rebuilt = false, mp_extended = false; + size_t new_rl_count; + + ntfs_debug("Extending mft data allocation."); + mft_ni = NTFS_I(vol->mft_ino); + /* + * Determine the preferred allocation location, i.e. the last lcn of + * the mft data attribute. The allocated size of the mft data + * attribute cannot be zero so we are ok to do this. + */ + down_write(&mft_ni->runlist.lock); + read_lock_irqsave(&mft_ni->size_lock, flags); + ll = mft_ni->allocated_size; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + rl = ntfs_attr_find_vcn_nolock(mft_ni, + NTFS_B_TO_CLU(vol, ll - 1), NULL); + if (IS_ERR(rl) || unlikely(!rl->length || rl->lcn < 0)) { + up_write(&mft_ni->runlist.lock); + ntfs_error(vol->sb, + "Failed to determine last allocated cluster of mft data attribute."); + if (!IS_ERR(rl)) + ret = -EIO; + else + ret = PTR_ERR(rl); + return ret; + } + lcn = rl->lcn + rl->length; + ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn); + /* Minimum allocation is one mft record worth of clusters. */ + min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size); + if (!min_nr) + min_nr = 1; + /* Want to allocate 16 mft records worth of clusters. */ + nr = vol->mft_record_size << 4 >> vol->cluster_size_bits; + if (!nr) + nr = min_nr; + /* Ensure we do not go above 2^32-1 mft records. */ + read_lock_irqsave(&mft_ni->size_lock, flags); + ll = mft_ni->allocated_size; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + if (unlikely((ll + NTFS_CLU_TO_B(vol, nr)) >> + vol->mft_record_size_bits >= (1ll << 32))) { + nr = min_nr; + if (unlikely((ll + NTFS_CLU_TO_B(vol, nr)) >> + vol->mft_record_size_bits >= (1ll << 32))) { + ntfs_warning(vol->sb, + "Cannot allocate mft record because the maximum number of inodes (2^32) has already been reached."); + up_write(&mft_ni->runlist.lock); + return -ENOSPC; + } + } + ntfs_debug("Trying mft data allocation with %s cluster count %lli.", + nr > min_nr ? "default" : "minimal", (long long)nr); + old_last_vcn = rl[1].vcn; + /* + * We can release the mft_ni runlist lock, Because this function is + * the only one that expends $MFT data attribute and is called with + * mft_ni->mrec_lock. + * This is required for the lock order, vol->lcnbmp_lock => + * mft_ni->runlist.lock. + */ + up_write(&mft_ni->runlist.lock); + + do { + rl2 = ntfs_cluster_alloc(vol, old_last_vcn, nr, lcn, MFT_ZONE, + true, false, false); + if (!IS_ERR(rl2)) + break; + if (PTR_ERR(rl2) != -ENOSPC || nr == min_nr) { + ntfs_error(vol->sb, + "Failed to allocate the minimal number of clusters (%lli) for the mft data attribute.", + nr); + return PTR_ERR(rl2); + } + /* + * There is not enough space to do the allocation, but there + * might be enough space to do a minimal allocation so try that + * before failing. + */ + nr = min_nr; + ntfs_debug("Retrying mft data allocation with minimal cluster count %lli.", nr); + } while (1); + + down_write(&mft_ni->runlist.lock); + rl = ntfs_runlists_merge(&mft_ni->runlist, rl2, 0, &new_rl_count); + if (IS_ERR(rl)) { + up_write(&mft_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to merge runlists for mft data attribute."); + if (ntfs_cluster_free_from_rl(vol, rl2)) { + ntfs_error(vol->sb, + "Failed to deallocate clusters from the mft data attribute.%s", es); + NVolSetErrors(vol); + } + kvfree(rl2); + return PTR_ERR(rl); + } + mft_ni->runlist.rl = rl; + mft_ni->runlist.count = new_rl_count; + ntfs_debug("Allocated %lli clusters.", (long long)nr); + /* Find the last run in the new runlist. */ + for (; rl[1].length; rl++) + ; + up_write(&mft_ni->runlist.lock); + + /* Update the attribute record as well. */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record."); + ret = PTR_ERR(mrec); + down_write(&mft_ni->runlist.lock); + goto undo_alloc; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + ret = -ENOMEM; + goto undo_alloc; + } + ret = ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len, + CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to find last attribute extent of mft data attribute."); + if (ret == -ENOENT) + ret = -EIO; + goto undo_alloc; + } + a = ctx->attr; + ll = le64_to_cpu(a->data.non_resident.lowest_vcn); + + down_write(&mft_ni->runlist.lock); + /* Search back for the previous last allocated cluster of mft bitmap. */ + for (rl2 = rl; rl2 > mft_ni->runlist.rl; rl2--) { + if (ll >= rl2->vcn) + break; + } + WARN_ON(ll < rl2->vcn); + WARN_ON(ll >= rl2->vcn + rl2->length); + /* Get the size for the new mapping pairs array for this extent. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl2, ll, -1, -1); + if (unlikely(mp_size <= 0)) { + ntfs_error(vol->sb, + "Get size for mapping pairs failed for mft data attribute extent."); + ret = mp_size; + if (!ret) + ret = -EIO; + up_write(&mft_ni->runlist.lock); + goto undo_alloc; + } + up_write(&mft_ni->runlist.lock); + + /* Expand the attribute record if necessary. */ + old_alen = le32_to_cpu(a->length); + ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); + if (unlikely(ret)) { + ret = ntfs_mft_attr_extend(mft_ni); + if (!ret) + goto extended_ok; + if (ret != -EAGAIN) + mp_extended = true; + goto undo_alloc; + } + mp_rebuilt = true; + /* Generate the mapping pairs array directly into the attr record. */ + ret = ntfs_mapping_pairs_build(vol, (u8 *)a + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset), + mp_size, rl2, ll, -1, NULL, NULL, NULL); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to build mapping pairs array of mft data attribute."); + goto undo_alloc; + } + /* Update the highest_vcn. */ + a->data.non_resident.highest_vcn = cpu_to_le64(rl[1].vcn - 1); + /* + * We now have extended the mft data allocated_size by nr clusters. + * Reflect this in the struct ntfs_inode structure and the attribute record. + * @rl is the last (non-terminator) runlist element of mft data + * attribute. + */ + if (a->data.non_resident.lowest_vcn) { + /* + * We are not in the first attribute extent, switch to it, but + * first ensure the changes will make it to disk later. + */ + mark_mft_record_dirty(ctx->ntfs_ino); +extended_ok: + ntfs_attr_reinit_search_ctx(ctx); + ret = ntfs_attr_lookup(mft_ni->type, mft_ni->name, + mft_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, + ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, + "Failed to find first attribute extent of mft data attribute."); + goto restore_undo_alloc; + } + a = ctx->attr; + } + + write_lock_irqsave(&mft_ni->size_lock, flags); + mft_ni->allocated_size += NTFS_CLU_TO_B(vol, nr); + a->data.non_resident.allocated_size = + cpu_to_le64(mft_ni->allocated_size); + write_unlock_irqrestore(&mft_ni->size_lock, flags); + /* Ensure the changes make it to disk. */ + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + ntfs_debug("Done."); + return 0; +restore_undo_alloc: + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len, + CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx)) { + ntfs_error(vol->sb, + "Failed to find last attribute extent of mft data attribute.%s", es); + write_lock_irqsave(&mft_ni->size_lock, flags); + mft_ni->allocated_size += NTFS_CLU_TO_B(vol, nr); + write_unlock_irqrestore(&mft_ni->size_lock, flags); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mft_ni->runlist.lock); + /* + * The only thing that is now wrong is ->allocated_size of the + * base attribute extent which chkdsk should be able to fix. + */ + NVolSetErrors(vol); + return ret; + } + ctx->attr->data.non_resident.highest_vcn = + cpu_to_le64(old_last_vcn - 1); +undo_alloc: + if (ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx) < 0) { + ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es); + NVolSetErrors(vol); + } + + if (ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn)) { + ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es); + NVolSetErrors(vol); + } + if (mp_extended && ntfs_attr_update_mapping_pairs(mft_ni, 0)) { + ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", + es); + NVolSetErrors(vol); + } + if (ctx) { + a = ctx->attr; + if (mp_rebuilt && !IS_ERR(ctx->mrec)) { + if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + old_alen - le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + rl2, ll, -1, NULL, NULL, NULL)) { + ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es); + NVolSetErrors(vol); + } + if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) { + ntfs_error(vol->sb, "Failed to restore attribute record.%s", es); + NVolSetErrors(vol); + } + mark_mft_record_dirty(ctx->ntfs_ino); + } else if (IS_ERR(ctx->mrec)) { + ntfs_error(vol->sb, "Failed to restore attribute search context.%s", es); + NVolSetErrors(vol); + } + ntfs_attr_put_search_ctx(ctx); + } + if (!IS_ERR(mrec)) + unmap_mft_record(mft_ni); + return ret; +} + +/* + * ntfs_mft_record_layout - layout an mft record into a memory buffer + * @vol: volume to which the mft record will belong + * @mft_no: mft reference specifying the mft record number + * @m: destination buffer of size >= @vol->mft_record_size bytes + * + * Layout an empty, unused mft record with the mft record number @mft_no into + * the buffer @m. The volume @vol is needed because the mft record structure + * was modified in NTFS 3.1 so we need to know which volume version this mft + * record will be used on. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_mft_record_layout(const struct ntfs_volume *vol, const s64 mft_no, + struct mft_record *m) +{ + struct attr_record *a; + + ntfs_debug("Entering for mft record 0x%llx.", (long long)mft_no); + if (mft_no >= (1ll << 32)) { + ntfs_error(vol->sb, "Mft record number 0x%llx exceeds maximum of 2^32.", + (long long)mft_no); + return -ERANGE; + } + /* Start by clearing the whole mft record to gives us a clean slate. */ + memset(m, 0, vol->mft_record_size); + /* Aligned to 2-byte boundary. */ + if (vol->major_ver < 3 || (vol->major_ver == 3 && !vol->minor_ver)) + m->usa_ofs = cpu_to_le16((sizeof(struct mft_record_old) + 1) & ~1); + else { + m->usa_ofs = cpu_to_le16((sizeof(struct mft_record) + 1) & ~1); + /* + * Set the NTFS 3.1+ specific fields while we know that the + * volume version is 3.1+. + */ + m->reserved = 0; + m->mft_record_number = cpu_to_le32((u32)mft_no); + } + m->magic = magic_FILE; + if (vol->mft_record_size >= NTFS_BLOCK_SIZE) + m->usa_count = cpu_to_le16(vol->mft_record_size / + NTFS_BLOCK_SIZE + 1); + else { + m->usa_count = cpu_to_le16(1); + ntfs_warning(vol->sb, + "Sector size is bigger than mft record size. Setting usa_count to 1. If chkdsk reports this as corruption"); + } + /* Set the update sequence number to 1. */ + *(__le16 *)((u8 *)m + le16_to_cpu(m->usa_ofs)) = cpu_to_le16(1); + m->lsn = 0; + m->sequence_number = cpu_to_le16(1); + m->link_count = 0; + /* + * Place the attributes straight after the update sequence array, + * aligned to 8-byte boundary. + */ + m->attrs_offset = cpu_to_le16((le16_to_cpu(m->usa_ofs) + + (le16_to_cpu(m->usa_count) << 1) + 7) & ~7); + m->flags = 0; + /* + * Using attrs_offset plus eight bytes (for the termination attribute). + * attrs_offset is already aligned to 8-byte boundary, so no need to + * align again. + */ + m->bytes_in_use = cpu_to_le32(le16_to_cpu(m->attrs_offset) + 8); + m->bytes_allocated = cpu_to_le32(vol->mft_record_size); + m->base_mft_record = 0; + m->next_attr_instance = 0; + /* Add the termination attribute. */ + a = (struct attr_record *)((u8 *)m + le16_to_cpu(m->attrs_offset)); + a->type = AT_END; + a->length = 0; + ntfs_debug("Done."); + return 0; +} + +/* + * ntfs_mft_record_format - format an mft record on an ntfs volume + * @vol: volume on which to format the mft record + * @mft_no: mft record number to format + * + * Format the mft record @mft_no in $MFT/$DATA, i.e. lay out an empty, unused + * mft record into the appropriate place of the mft data attribute. This is + * used when extending the mft data attribute. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_no) +{ + loff_t i_size; + struct inode *mft_vi = vol->mft_ino; + struct folio *folio; + struct mft_record *m; + pgoff_t index, end_index; + unsigned int ofs; + int err; + + ntfs_debug("Entering for mft record 0x%llx.", (long long)mft_no); + /* + * The index into the page cache and the offset within the page cache + * page of the wanted mft record. + */ + index = NTFS_MFT_NR_TO_PIDX(vol, mft_no); + ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no); + /* The maximum valid index into the page cache for $MFT's data. */ + i_size = i_size_read(mft_vi); + end_index = i_size >> PAGE_SHIFT; + if (unlikely(index >= end_index)) { + if (unlikely(index > end_index || + ofs + vol->mft_record_size > (i_size & ~PAGE_MASK))) { + ntfs_error(vol->sb, "Tried to format non-existing mft record 0x%llx.", + (long long)mft_no); + return -ENOENT; + } + } + + /* Read, map, and pin the folio containing the mft record. */ + folio = read_mapping_folio(mft_vi->i_mapping, index, NULL); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Failed to map page containing mft record to format 0x%llx.", + (long long)mft_no); + return PTR_ERR(folio); + } + folio_lock(folio); + folio_clear_uptodate(folio); + m = (struct mft_record *)((u8 *)kmap_local_folio(folio, 0) + ofs); + err = ntfs_mft_record_layout(vol, mft_no, m); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to layout mft record 0x%llx.", + (long long)mft_no); + folio_mark_uptodate(folio); + folio_unlock(folio); + kunmap_local(m); + folio_put(folio); + return err; + } + pre_write_mst_fixup((struct ntfs_record *)m, vol->mft_record_size); + folio_mark_uptodate(folio); + /* + * Make sure the mft record is written out to disk. We could use + * ilookup5() to check if an inode is in icache and so on but this is + * unnecessary as ntfs_writepage() will write the dirty record anyway. + */ + ntfs_mft_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(m); + folio_put(folio); + ntfs_debug("Done."); + return 0; +} + +/* + * ntfs_mft_record_alloc - allocate an mft record on an ntfs volume + * @vol: [IN] volume on which to allocate the mft record + * @mode: [IN] mode if want a file or directory, i.e. base inode or 0 + * @ni: [OUT] on success, set to the allocated ntfs inode + * @base_ni: [IN] open base inode if allocating an extent mft record or NULL + * @ni_mrec: [OUT] on successful return this is the mapped mft record + * + * Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol. + * + * If @base_ni is NULL make the mft record a base mft record, i.e. a file or + * direvctory inode, and allocate it at the default allocator position. In + * this case @mode is the file mode as given to us by the caller. We in + * particular use @mode to distinguish whether a file or a directory is being + * created (S_IFDIR(mode) and S_IFREG(mode), respectively). + * + * If @base_ni is not NULL make the allocated mft record an extent record, + * allocate it starting at the mft record after the base mft record and attach + * the allocated and opened ntfs inode to the base inode @base_ni. In this + * case @mode must be 0 as it is meaningless for extent inodes. + * + * You need to check the return value with IS_ERR(). If false, the function + * was successful and the return value is the now opened ntfs inode of the + * allocated mft record. *@mrec is then set to the allocated, mapped, pinned, + * and locked mft record. If IS_ERR() is true, the function failed and the + * error code is obtained from PTR_ERR(return value). *@mrec is undefined in + * this case. + * + * Allocation strategy: + * + * To find a free mft record, we scan the mft bitmap for a zero bit. To + * optimize this we start scanning at the place specified by @base_ni or if + * @base_ni is NULL we start where we last stopped and we perform wrap around + * when we reach the end. Note, we do not try to allocate mft records below + * number 64 because numbers 0 to 15 are the defined system files anyway and 16 + * to 64 are special in that they are used for storing extension mft records + * for the $DATA attribute of $MFT. This is required to avoid the possibility + * of creating a runlist with a circular dependency which once written to disk + * can never be read in again. Windows will only use records 16 to 24 for + * normal files if the volume is completely out of space. We never use them + * which means that when the volume is really out of space we cannot create any + * more files while Windows can still create up to 8 small files. We can start + * doing this at some later time, it does not matter much for now. + * + * When scanning the mft bitmap, we only search up to the last allocated mft + * record. If there are no free records left in the range 64 to number of + * allocated mft records, then we extend the $MFT/$DATA attribute in order to + * create free mft records. We extend the allocated size of $MFT/$DATA by 16 + * records at a time or one cluster, if cluster size is above 16kiB. If there + * is not sufficient space to do this, we try to extend by a single mft record + * or one cluster, if cluster size is above the mft record size. + * + * No matter how many mft records we allocate, we initialize only the first + * allocated mft record, incrementing mft data size and initialized size + * accordingly, open an struct ntfs_inode for it and return it to the caller, unless + * there are less than 64 mft records, in which case we allocate and initialize + * mft records until we reach record 64 which we consider as the first free mft + * record for use by normal files. + * + * If during any stage we overflow the initialized data in the mft bitmap, we + * extend the initialized size (and data size) by 8 bytes, allocating another + * cluster if required. The bitmap data size has to be at least equal to the + * number of mft records in the mft, but it can be bigger, in which case the + * superfluous bits are padded with zeroes. + * + * Thus, when we return successfully (IS_ERR() is false), we will have: + * - initialized / extended the mft bitmap if necessary, + * - initialized / extended the mft data if necessary, + * - set the bit corresponding to the mft record being allocated in the + * mft bitmap, + * - opened an struct ntfs_inode for the allocated mft record, and we will have + * - returned the struct ntfs_inode as well as the allocated mapped, pinned, and + * locked mft record. + * + * On error, the volume will be left in a consistent state and no record will + * be allocated. If rolling back a partial operation fails, we may leave some + * inconsistent metadata in which case we set NVolErrors() so the volume is + * left dirty when unmounted. + * + * Note, this function cannot make use of most of the normal functions, like + * for example for attribute resizing, etc, because when the run list overflows + * the base mft record and an attribute list is used, it is very important that + * the extension mft records used to store the $DATA attribute of $MFT can be + * reached without having to read the information contained inside them, as + * this would make it impossible to find them in the first place after the + * volume is unmounted. $MFT/$BITMAP probably does not need to follow this + * rule because the bitmap is not essential for finding the mft records, but on + * the other hand, handling the bitmap in this special way would make life + * easier because otherwise there might be circular invocations of functions + * when reading the bitmap. + */ +int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, + struct ntfs_inode **ni, struct ntfs_inode *base_ni, + struct mft_record **ni_mrec) +{ + s64 ll, bit, old_data_initialized, old_data_size; + unsigned long flags; + struct folio *folio; + struct ntfs_inode *mft_ni, *mftbmp_ni; + struct ntfs_attr_search_ctx *ctx; + struct mft_record *m = NULL; + struct attr_record *a; + pgoff_t index; + unsigned int ofs; + int err; + __le16 seq_no, usn; + bool record_formatted = false; + unsigned int memalloc_flags; + + if (base_ni && *ni) + return -EINVAL; + + /* @mode and @base_ni are mutually exclusive. */ + if (mode && base_ni) + return -EINVAL; + + if (base_ni) + ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).", + (long long)base_ni->mft_no); + else + ntfs_debug("Entering (allocating a base mft record)."); + + memalloc_flags = memalloc_nofs_save(); + + mft_ni = NTFS_I(vol->mft_ino); + if (!base_ni || base_ni->mft_no != FILE_MFT) + mutex_lock(&mft_ni->mrec_lock); + mftbmp_ni = NTFS_I(vol->mftbmp_ino); +search_free_rec: + if (!base_ni || base_ni->mft_no != FILE_MFT) + down_write(&vol->mftbmp_lock); + bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni); + if (bit >= 0) { + ntfs_debug("Found and allocated free record (#1), bit 0x%llx.", + (long long)bit); + goto have_alloc_rec; + } + if (bit != -ENOSPC) { + if (!base_ni || base_ni->mft_no != FILE_MFT) { + up_write(&vol->mftbmp_lock); + mutex_unlock(&mft_ni->mrec_lock); + } + memalloc_nofs_restore(memalloc_flags); + return bit; + } + + if (base_ni && base_ni->mft_no == FILE_MFT) { + memalloc_nofs_restore(memalloc_flags); + return bit; + } + + /* + * No free mft records left. If the mft bitmap already covers more + * than the currently used mft records, the next records are all free, + * so we can simply allocate the first unused mft record. + * Note: We also have to make sure that the mft bitmap at least covers + * the first 24 mft records as they are special and whilst they may not + * be in use, we do not allocate from them. + */ + read_lock_irqsave(&mft_ni->size_lock, flags); + ll = mft_ni->initialized_size >> vol->mft_record_size_bits; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + old_data_initialized = mftbmp_ni->initialized_size; + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + if (old_data_initialized << 3 > ll && + old_data_initialized > RESERVED_MFT_RECORDS / 8) { + bit = ll; + if (bit < RESERVED_MFT_RECORDS) + bit = RESERVED_MFT_RECORDS; + if (unlikely(bit >= (1ll << 32))) + goto max_err_out; + ntfs_debug("Found free record (#2), bit 0x%llx.", + (long long)bit); + goto found_free_rec; + } + /* + * The mft bitmap needs to be expanded until it covers the first unused + * mft record that we can allocate. + * Note: The smallest mft record we allocate is mft record 24. + */ + bit = old_data_initialized << 3; + if (unlikely(bit >= (1ll << 32))) + goto max_err_out; + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + old_data_size = mftbmp_ni->allocated_size; + ntfs_debug("Status of mftbmp before extension: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + old_data_size, i_size_read(vol->mftbmp_ino), + old_data_initialized); + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + if (old_data_initialized + 8 > old_data_size) { + /* Need to extend bitmap by one more cluster. */ + ntfs_debug("mftbmp: initialized_size + 8 > allocated_size."); + err = ntfs_mft_bitmap_extend_allocation_nolock(vol); + if (err == -EAGAIN) + err = ntfs_mft_bitmap_extend_allocation_nolock(vol); + + if (unlikely(err)) { + if (!base_ni || base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); + goto err_out; + } +#ifdef DEBUG + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + ntfs_debug("Status of mftbmp after allocation extension: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + mftbmp_ni->allocated_size, + i_size_read(vol->mftbmp_ino), + mftbmp_ni->initialized_size); + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); +#endif /* DEBUG */ + } + /* + * We now have sufficient allocated space, extend the initialized_size + * as well as the data_size if necessary and fill the new space with + * zeroes. + */ + err = ntfs_mft_bitmap_extend_initialized_nolock(vol); + if (unlikely(err)) { + if (!base_ni || base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); + goto err_out; + } +#ifdef DEBUG + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + ntfs_debug("Status of mftbmp after initialized extension: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + mftbmp_ni->allocated_size, + i_size_read(vol->mftbmp_ino), + mftbmp_ni->initialized_size); + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); +#endif /* DEBUG */ + ntfs_debug("Found free record (#3), bit 0x%llx.", (long long)bit); +found_free_rec: + /* @bit is the found free mft record, allocate it in the mft bitmap. */ + ntfs_debug("At found_free_rec."); + err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to allocate bit in mft bitmap."); + if (!base_ni || base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); + goto err_out; + } + ntfs_debug("Set bit 0x%llx in mft bitmap.", (long long)bit); +have_alloc_rec: + /* + * The mft bitmap is now uptodate. Deal with mft data attribute now. + * Note, we keep hold of the mft bitmap lock for writing until all + * modifications to the mft data attribute are complete, too, as they + * will impact decisions for mft bitmap and mft record allocation done + * by a parallel allocation and if the lock is not maintained a + * parallel allocation could allocate the same mft record as this one. + */ + ll = (bit + 1) << vol->mft_record_size_bits; + read_lock_irqsave(&mft_ni->size_lock, flags); + old_data_initialized = mft_ni->initialized_size; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + if (ll <= old_data_initialized) { + ntfs_debug("Allocated mft record already initialized."); + goto mft_rec_already_initialized; + } + ntfs_debug("Initializing allocated mft record."); + /* + * The mft record is outside the initialized data. Extend the mft data + * attribute until it covers the allocated record. The loop is only + * actually traversed more than once when a freshly formatted volume is + * first written to so it optimizes away nicely in the common case. + */ + if (!base_ni || base_ni->mft_no != FILE_MFT) { + read_lock_irqsave(&mft_ni->size_lock, flags); + ntfs_debug("Status of mft data before extension: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + mft_ni->allocated_size, i_size_read(vol->mft_ino), + mft_ni->initialized_size); + while (ll > mft_ni->allocated_size) { + read_unlock_irqrestore(&mft_ni->size_lock, flags); + err = ntfs_mft_data_extend_allocation_nolock(vol); + if (err == -EAGAIN) + err = ntfs_mft_data_extend_allocation_nolock(vol); + + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to extend mft data allocation."); + goto undo_mftbmp_alloc_nolock; + } + read_lock_irqsave(&mft_ni->size_lock, flags); + ntfs_debug("Status of mft data after allocation extension: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + mft_ni->allocated_size, i_size_read(vol->mft_ino), + mft_ni->initialized_size); + } + read_unlock_irqrestore(&mft_ni->size_lock, flags); + } else if (ll > mft_ni->allocated_size) { + err = -ENOSPC; + goto undo_mftbmp_alloc_nolock; + } + /* + * Extend mft data initialized size (and data size of course) to reach + * the allocated mft record, formatting the mft records allong the way. + * Note: We only modify the struct ntfs_inode structure as that is all that is + * needed by ntfs_mft_record_format(). We will update the attribute + * record itself in one fell swoop later on. + */ + write_lock_irqsave(&mft_ni->size_lock, flags); + old_data_initialized = mft_ni->initialized_size; + old_data_size = vol->mft_ino->i_size; + while (ll > mft_ni->initialized_size) { + s64 new_initialized_size, mft_no; + + new_initialized_size = mft_ni->initialized_size + + vol->mft_record_size; + mft_no = mft_ni->initialized_size >> vol->mft_record_size_bits; + if (new_initialized_size > i_size_read(vol->mft_ino)) + i_size_write(vol->mft_ino, new_initialized_size); + write_unlock_irqrestore(&mft_ni->size_lock, flags); + ntfs_debug("Initializing mft record 0x%llx.", + (long long)mft_no); + err = ntfs_mft_record_format(vol, mft_no); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to format mft record."); + goto undo_data_init; + } + write_lock_irqsave(&mft_ni->size_lock, flags); + mft_ni->initialized_size = new_initialized_size; + } + write_unlock_irqrestore(&mft_ni->size_lock, flags); + record_formatted = true; + /* Update the mft data attribute record to reflect the new sizes. */ + m = map_mft_record(mft_ni); + if (IS_ERR(m)) { + ntfs_error(vol->sb, "Failed to map mft record."); + err = PTR_ERR(m); + goto undo_data_init; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, m); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + err = -ENOMEM; + unmap_mft_record(mft_ni); + goto undo_data_init; + } + err = ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to find first attribute extent of mft data attribute."); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + goto undo_data_init; + } + a = ctx->attr; + read_lock_irqsave(&mft_ni->size_lock, flags); + a->data.non_resident.initialized_size = + cpu_to_le64(mft_ni->initialized_size); + a->data.non_resident.data_size = + cpu_to_le64(i_size_read(vol->mft_ino)); + read_unlock_irqrestore(&mft_ni->size_lock, flags); + /* Ensure the changes make it to disk. */ + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + read_lock_irqsave(&mft_ni->size_lock, flags); + ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", + mft_ni->allocated_size, i_size_read(vol->mft_ino), + mft_ni->initialized_size); + WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size); + WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino)); + read_unlock_irqrestore(&mft_ni->size_lock, flags); +mft_rec_already_initialized: + /* + * We can finally drop the mft bitmap lock as the mft data attribute + * has been fully updated. The only disparity left is that the + * allocated mft record still needs to be marked as in use to match the + * set bit in the mft bitmap but this is actually not a problem since + * this mft record is not referenced from anywhere yet and the fact + * that it is allocated in the mft bitmap means that no-one will try to + * allocate it either. + */ + if (!base_ni || base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); + /* + * We now have allocated and initialized the mft record. Calculate the + * index of and the offset within the page cache page the record is in. + */ + index = NTFS_MFT_NR_TO_PIDX(vol, bit); + ofs = NTFS_MFT_NR_TO_POFS(vol, bit); + /* Read, map, and pin the folio containing the mft record. */ + folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Failed to map page containing allocated mft record 0x%llx.", + bit); + err = PTR_ERR(folio); + goto undo_mftbmp_alloc; + } + folio_lock(folio); + folio_clear_uptodate(folio); + m = (struct mft_record *)((u8 *)kmap_local_folio(folio, 0) + ofs); + /* If we just formatted the mft record no need to do it again. */ + if (!record_formatted) { + /* Sanity check that the mft record is really not in use. */ + if (ntfs_is_file_record(m->magic) && + (m->flags & MFT_RECORD_IN_USE)) { + ntfs_warning(vol->sb, + "Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.", + bit); + folio_mark_uptodate(folio); + folio_unlock(folio); + kunmap_local(m); + folio_put(folio); + NVolSetErrors(vol); + goto search_free_rec; + } + /* + * We need to (re-)format the mft record, preserving the + * sequence number if it is not zero as well as the update + * sequence number if it is not zero or -1 (0xffff). This + * means we do not need to care whether or not something went + * wrong with the previous mft record. + */ + seq_no = m->sequence_number; + usn = *(__le16 *)((u8 *)m + le16_to_cpu(m->usa_ofs)); + err = ntfs_mft_record_layout(vol, bit, m); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to layout allocated mft record 0x%llx.", + bit); + folio_mark_uptodate(folio); + folio_unlock(folio); + kunmap_local(m); + folio_put(folio); + goto undo_mftbmp_alloc; + } + if (seq_no) + m->sequence_number = seq_no; + if (usn && le16_to_cpu(usn) != 0xffff) + *(__le16 *)((u8 *)m + le16_to_cpu(m->usa_ofs)) = usn; + pre_write_mst_fixup((struct ntfs_record *)m, vol->mft_record_size); + } + /* Set the mft record itself in use. */ + m->flags |= MFT_RECORD_IN_USE; + if (S_ISDIR(mode)) + m->flags |= MFT_RECORD_IS_DIRECTORY; + folio_mark_uptodate(folio); + if (base_ni) { + struct mft_record *m_tmp; + + /* + * Setup the base mft record in the extent mft record. This + * completes initialization of the allocated extent mft record + * and we can simply use it with map_extent_mft_record(). + */ + m->base_mft_record = MK_LE_MREF(base_ni->mft_no, + base_ni->seq_no); + /* + * Allocate an extent inode structure for the new mft record, + * attach it to the base inode @base_ni and map, pin, and lock + * its, i.e. the allocated, mft record. + */ + m_tmp = map_extent_mft_record(base_ni, + MK_MREF(bit, le16_to_cpu(m->sequence_number)), + ni); + if (IS_ERR(m_tmp)) { + ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.", + bit); + err = PTR_ERR(m_tmp); + /* Set the mft record itself not in use. */ + m->flags &= cpu_to_le16( + ~le16_to_cpu(MFT_RECORD_IN_USE)); + /* Make sure the mft record is written out to disk. */ + ntfs_mft_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(m); + folio_put(folio); + goto undo_mftbmp_alloc; + } + + /* + * Make sure the allocated mft record is written out to disk. + * No need to set the inode dirty because the caller is going + * to do that anyway after finishing with the new extent mft + * record (e.g. at a minimum a new attribute will be added to + * the mft record. + */ + ntfs_mft_mark_dirty(folio); + folio_unlock(folio); + /* + * Need to unmap the page since map_extent_mft_record() mapped + * it as well so we have it mapped twice at the moment. + */ + kunmap_local(m); + folio_put(folio); + } else { + /* + * Manually map, pin, and lock the mft record as we already + * have its page mapped and it is very easy to do. + */ + (*ni)->seq_no = le16_to_cpu(m->sequence_number); + /* + * Make sure the allocated mft record is written out to disk. + * NOTE: We do not set the ntfs inode dirty because this would + * fail in ntfs_write_inode() because the inode does not have a + * standard information attribute yet. Also, there is no need + * to set the inode dirty because the caller is going to do + * that anyway after finishing with the new mft record (e.g. at + * a minimum some new attributes will be added to the mft + * record. + */ + + (*ni)->mrec = kmalloc(vol->mft_record_size, GFP_NOFS); + if (!(*ni)->mrec) { + folio_unlock(folio); + kunmap_local(m); + folio_put(folio); + goto undo_mftbmp_alloc; + } + + memcpy((*ni)->mrec, m, vol->mft_record_size); + post_read_mst_fixup((struct ntfs_record *)(*ni)->mrec, vol->mft_record_size); + ntfs_mft_mark_dirty(folio); + folio_unlock(folio); + (*ni)->folio = folio; + (*ni)->folio_ofs = ofs; + atomic_inc(&(*ni)->count); + /* Update the default mft allocation position. */ + vol->mft_data_pos = bit + 1; + } + if (!base_ni || base_ni->mft_no != FILE_MFT) + mutex_unlock(&mft_ni->mrec_lock); + memalloc_nofs_restore(memalloc_flags); + + /* + * Return the opened, allocated inode of the allocated mft record as + * well as the mapped, pinned, and locked mft record. + */ + ntfs_debug("Returning opened, allocated %sinode 0x%llx.", + base_ni ? "extent " : "", bit); + (*ni)->mft_no = bit; + if (ni_mrec) + *ni_mrec = (*ni)->mrec; + ntfs_dec_free_mft_records(vol, 1); + return 0; +undo_data_init: + write_lock_irqsave(&mft_ni->size_lock, flags); + mft_ni->initialized_size = old_data_initialized; + i_size_write(vol->mft_ino, old_data_size); + write_unlock_irqrestore(&mft_ni->size_lock, flags); + goto undo_mftbmp_alloc_nolock; +undo_mftbmp_alloc: + if (!base_ni || base_ni->mft_no != FILE_MFT) + down_write(&vol->mftbmp_lock); +undo_mftbmp_alloc_nolock: + if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { + ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es); + NVolSetErrors(vol); + } + if (!base_ni || base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); +err_out: + if (!base_ni || base_ni->mft_no != FILE_MFT) + mutex_unlock(&mft_ni->mrec_lock); + memalloc_nofs_restore(memalloc_flags); + return err; +max_err_out: + ntfs_warning(vol->sb, + "Cannot allocate mft record because the maximum number of inodes (2^32) has already been reached."); + if (!base_ni || base_ni->mft_no != FILE_MFT) { + up_write(&vol->mftbmp_lock); + mutex_unlock(&mft_ni->mrec_lock); + } + memalloc_nofs_restore(memalloc_flags); + return -ENOSPC; +} + +/* + * ntfs_mft_record_free - free an mft record on an ntfs volume + * @vol: volume on which to free the mft record + * @ni: open ntfs inode of the mft record to free + * + * Free the mft record of the open inode @ni on the mounted ntfs volume @vol. + * Note that this function calls ntfs_inode_close() internally and hence you + * cannot use the pointer @ni any more after this function returns success. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) +{ + u64 mft_no; + int err; + u16 seq_no; + __le16 old_seq_no; + struct mft_record *ni_mrec; + unsigned int memalloc_flags; + struct ntfs_inode *base_ni; + + if (!vol || !ni) + return -EINVAL; + + ntfs_debug("Entering for inode 0x%llx.\n", (long long)ni->mft_no); + + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) + return -EIO; + + /* Cache the mft reference for later. */ + mft_no = ni->mft_no; + + /* Mark the mft record as not in use. */ + ni_mrec->flags &= ~MFT_RECORD_IN_USE; + + /* Increment the sequence number, skipping zero, if it is not zero. */ + old_seq_no = ni_mrec->sequence_number; + seq_no = le16_to_cpu(old_seq_no); + if (seq_no == 0xffff) + seq_no = 1; + else if (seq_no) + seq_no++; + ni_mrec->sequence_number = cpu_to_le16(seq_no); + + down_read(&NTFS_I(vol->mft_ino)->runlist.lock); + err = ntfs_get_block_mft_record(NTFS_I(vol->mft_ino), ni); + up_read(&NTFS_I(vol->mft_ino)->runlist.lock); + if (err) { + unmap_mft_record(ni); + return err; + } + + /* + * Set the ntfs inode dirty and write it out. We do not need to worry + * about the base inode here since whatever caused the extent mft + * record to be freed is guaranteed to do it already. + */ + NInoSetDirty(ni); + err = write_mft_record(ni, ni_mrec, 0); + if (err) + goto sync_rollback; + + if (likely(ni->nr_extents >= 0)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + + /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ + memalloc_flags = memalloc_nofs_save(); + if (base_ni->mft_no != FILE_MFT) + down_write(&vol->mftbmp_lock); + err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); + memalloc_nofs_restore(memalloc_flags); + if (err) + goto bitmap_rollback; + + unmap_mft_record(ni); + ntfs_inc_free_mft_records(vol, 1); + return 0; + + /* Rollback what we did... */ +bitmap_rollback: + memalloc_flags = memalloc_nofs_save(); + if (base_ni->mft_no != FILE_MFT) + down_write(&vol->mftbmp_lock); + if (ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no)) + ntfs_error(vol->sb, "ntfs_bitmap_set_bit failed in bitmap_rollback\n"); + if (base_ni->mft_no != FILE_MFT) + up_write(&vol->mftbmp_lock); + memalloc_nofs_restore(memalloc_flags); +sync_rollback: + ntfs_error(vol->sb, + "Eeek! Rollback failed in %s. Leaving inconsistent metadata!\n", __func__); + ni_mrec->flags |= MFT_RECORD_IN_USE; + ni_mrec->sequence_number = old_seq_no; + NInoSetDirty(ni); + write_mft_record(ni, ni_mrec, 0); + unmap_mft_record(ni); + return err; +} + +static s64 lcn_from_index(struct ntfs_volume *vol, struct ntfs_inode *ni, + unsigned long index) +{ + s64 vcn; + s64 lcn; + + vcn = ntfs_pidx_to_cluster(vol, index); + + down_read(&ni->runlist.lock); + lcn = ntfs_attr_vcn_to_lcn_nolock(ni, vcn, false); + up_read(&ni->runlist.lock); + + return lcn; +} + +/* + * ntfs_write_mft_block - Write back a folio containing MFT records + * @folio: The folio to write back (contains one or more MFT records) + * @wbc: Writeback control structure + * + * This function is called as part of the address_space_operations + * .writepages implementation for the $MFT inode (or $MFTMirr). + * It handles writing one folio (normally 4KiB page) worth of MFT records + * to the underlying block device. + * + * Return: 0 on success, or -errno on error. + */ +static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *wbc) +{ + struct address_space *mapping = folio->mapping; + struct inode *vi = mapping->host; + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_volume *vol = ni->vol; + u8 *kaddr; + struct ntfs_inode **locked_nis __free(kfree) = kmalloc_array(PAGE_SIZE / NTFS_BLOCK_SIZE, + sizeof(struct ntfs_inode *), GFP_NOFS); + int nr_locked_nis = 0, err = 0, mft_ofs, prev_mft_ofs; + struct inode **ref_inos __free(kfree) = kmalloc_array(PAGE_SIZE / NTFS_BLOCK_SIZE, + sizeof(struct inode *), GFP_NOFS); + int nr_ref_inos = 0; + struct bio *bio = NULL; + u64 mft_no; + struct ntfs_inode *tni; + s64 lcn; + s64 vcn = ntfs_pidx_to_cluster(vol, folio->index); + s64 end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); + unsigned int folio_sz; + struct runlist_element *rl; + loff_t i_size = i_size_read(vi); + + ntfs_debug("Entering for inode 0x%llx, attribute type 0x%x, folio index 0x%lx.", + ni->mft_no, ni->type, folio->index); + + if (!locked_nis || !ref_inos) + return -ENOMEM; + + /* We have to zero every time due to mmap-at-end-of-file. */ + if (folio->index >= (i_size >> folio_shift(folio))) + /* The page straddles i_size. */ + folio_zero_segment(folio, + offset_in_folio(folio, i_size), + folio_size(folio)); + + lcn = lcn_from_index(vol, ni, folio->index); + if (lcn <= LCN_HOLE) { + folio_start_writeback(folio); + folio_unlock(folio); + folio_end_writeback(folio); + return -EIO; + } + + /* Map folio so we can access its contents. */ + kaddr = kmap_local_folio(folio, 0); + /* Clear the page uptodate flag whilst the mst fixups are applied. */ + folio_clear_uptodate(folio); + + for (mft_ofs = 0; mft_ofs < PAGE_SIZE && vcn < end_vcn; + mft_ofs += vol->mft_record_size) { + /* Get the mft record number. */ + mft_no = (((s64)folio->index << PAGE_SHIFT) + mft_ofs) >> + vol->mft_record_size_bits; + vcn = ntfs_mft_no_to_cluster(vol, mft_no); + /* Check whether to write this mft record. */ + tni = NULL; + if (ntfs_may_write_mft_record(vol, mft_no, + (struct mft_record *)(kaddr + mft_ofs), + &tni, &ref_inos[nr_ref_inos])) { + unsigned int mft_record_off = 0; + s64 vcn_off = vcn; + + /* + * Skip $MFT extent mft records and let them being written + * by writeback to avioid deadlocks. the $MFT runlist + * lock must be taken before $MFT extent mrec_lock is taken. + */ + if (tni && tni->nr_extents < 0 && + tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino)) { + mutex_unlock(&tni->mrec_lock); + atomic_dec(&tni->count); + iput(vol->mft_ino); + continue; + } + + /* + * The record should be written. If a locked ntfs + * inode was returned, add it to the array of locked + * ntfs inodes. + */ + if (tni) + locked_nis[nr_locked_nis++] = tni; + else if (ref_inos[nr_ref_inos]) + nr_ref_inos++; + + if (bio && (mft_ofs != prev_mft_ofs + vol->mft_record_size)) { +flush_bio: + bio->bi_end_io = ntfs_bio_end_io; + submit_bio(bio); + bio = NULL; + } + + if (vol->cluster_size < folio_size(folio)) { + down_write(&ni->runlist.lock); + rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn); + up_write(&ni->runlist.lock); + if (IS_ERR(rl) || lcn < 0) { + err = -EIO; + goto unm_done; + } + + if (bio && + (bio_end_sector(bio) >> (vol->cluster_size_bits - 9)) != + lcn) { + bio->bi_end_io = ntfs_bio_end_io; + submit_bio(bio); + bio = NULL; + } + } + + if (!bio) { + unsigned int off; + + off = ((mft_no << vol->mft_record_size_bits) + + mft_record_off) & vol->cluster_size_mask; + + bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, + GFP_NOIO); + bio->bi_iter.bi_sector = + ntfs_bytes_to_sector(vol, + ntfs_cluster_to_bytes(vol, lcn) + off); + } + + if (vol->cluster_size == NTFS_BLOCK_SIZE && + (mft_record_off || + rl->length - (vcn_off - rl->vcn) == 1 || + mft_ofs + NTFS_BLOCK_SIZE >= PAGE_SIZE)) + folio_sz = NTFS_BLOCK_SIZE; + else + folio_sz = vol->mft_record_size; + if (!bio_add_folio(bio, folio, folio_sz, + mft_ofs + mft_record_off)) { + err = -EIO; + bio_put(bio); + goto unm_done; + } + mft_record_off += folio_sz; + + if (mft_record_off != vol->mft_record_size) { + vcn_off++; + goto flush_bio; + } + prev_mft_ofs = mft_ofs; + + if (mft_no < vol->mftmirr_size) + ntfs_sync_mft_mirror(vol, mft_no, + (struct mft_record *)(kaddr + mft_ofs)); + } else if (ref_inos[nr_ref_inos]) + nr_ref_inos++; + } + + if (bio) { + bio->bi_end_io = ntfs_bio_end_io; + submit_bio(bio); + } +unm_done: + folio_mark_uptodate(folio); + kunmap_local(kaddr); + + folio_start_writeback(folio); + folio_unlock(folio); + folio_end_writeback(folio); + + /* Unlock any locked inodes. */ + while (nr_locked_nis-- > 0) { + struct ntfs_inode *base_tni; + + tni = locked_nis[nr_locked_nis]; + mutex_unlock(&tni->mrec_lock); + + /* Get the base inode. */ + mutex_lock(&tni->extent_lock); + if (tni->nr_extents >= 0) + base_tni = tni; + else + base_tni = tni->ext.base_ntfs_ino; + mutex_unlock(&tni->extent_lock); + ntfs_debug("Unlocking %s inode 0x%llx.", + tni == base_tni ? "base" : "extent", + tni->mft_no); + atomic_dec(&tni->count); + iput(VFS_I(base_tni)); + } + + /* Dropping deferred references */ + while (nr_ref_inos-- > 0) { + if (ref_inos[nr_ref_inos]) + iput(ref_inos[nr_ref_inos]); + } + + if (unlikely(err && err != -ENOMEM)) + NVolSetErrors(vol); + if (likely(!err)) + ntfs_debug("Done."); + return err; +} + +/* + * ntfs_mft_writepages - Write back dirty folios for the $MFT inode + * @mapping: address space of the $MFT inode + * @wbc: writeback control + * + * Writeback iterator for MFT records. Iterates over dirty folios and + * delegates actual writing to ntfs_write_mft_block() for each folio. + * Called from the address_space_operations .writepages vector of the + * $MFT inode. + * + * Returns 0 on success, or the first error encountered. + */ +int ntfs_mft_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + struct folio *folio = NULL; + int error; + + if (NVolShutdown(NTFS_I(mapping->host)->vol)) + return -EIO; + + while ((folio = writeback_iter(mapping, wbc, folio, &error))) + error = ntfs_write_mft_block(folio, wbc); + return error; +} + +void ntfs_mft_mark_dirty(struct folio *folio) +{ + iomap_dirty_folio(folio->mapping, folio); +} diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h new file mode 100644 index 000000000000..75a51a98d0f6 --- /dev/null +++ b/fs/ntfs/mft.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for mft record handling in NTFS Linux kernel driver. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_MFT_H +#define _LINUX_NTFS_MFT_H + +#include +#include + +#include "inode.h" + +struct mft_record *map_mft_record(struct ntfs_inode *ni); +void unmap_mft_record(struct ntfs_inode *ni); +struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref, + struct ntfs_inode **ntfs_ino); + +static inline void unmap_extent_mft_record(struct ntfs_inode *ni) +{ + unmap_mft_record(ni); +} + +void __mark_mft_record_dirty(struct ntfs_inode *ni); + +/* + * mark_mft_record_dirty - set the mft record and the page containing it dirty + * @ni: ntfs inode describing the mapped mft record + * + * Set the mapped (extent) mft record of the (base or extent) ntfs inode @ni, + * as well as the page containing the mft record, dirty. Also, mark the base + * vfs inode dirty. This ensures that any changes to the mft record are + * written out to disk. + * + * NOTE: Do not do anything if the mft record is already marked dirty. + */ +static inline void mark_mft_record_dirty(struct ntfs_inode *ni) +{ + if (!NInoTestSetDirty(ni)) + __mark_mft_record_dirty(ni); +} + +int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, + struct mft_record *m); +int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int sync); + +/* + * write_mft_record - write out a mapped (extent) mft record + * @ni: ntfs inode describing the mapped (extent) mft record + * @m: mapped (extent) mft record to write + * @sync: if true, wait for i/o completion + * + * This is just a wrapper for write_mft_record_nolock() (see mft.c), which + * locks the page for the duration of the write. This ensures that there are + * no race conditions between writing the mft record via the dirty inode code + * paths and via the page cache write back code paths or between writing + * neighbouring mft records residing in the same page. + * + * Locking the page also serializes us against ->read_folio() if the page is not + * uptodate. + * + * On success, clean the mft record and return 0. On error, leave the mft + * record dirty and return -errno. + */ +static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m, int sync) +{ + struct folio *folio = ni->folio; + int err; + + folio_lock(folio); + err = write_mft_record_nolock(ni, m, sync); + folio_unlock(folio); + + return err; +} + +int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, + struct ntfs_inode **ni, struct ntfs_inode *base_ni, + struct mft_record **ni_mrec); +int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni); +int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref, + const s64 count, struct mft_record *b); +int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m, + u64 mft_no); +int ntfs_mft_writepages(struct address_space *mapping, + struct writeback_control *wbc); +void ntfs_mft_mark_dirty(struct folio *folio); + +#endif /* _LINUX_NTFS_MFT_H */ diff --git a/fs/ntfs/mst.c b/fs/ntfs/mst.c new file mode 100644 index 000000000000..7f9faad924ad --- /dev/null +++ b/fs/ntfs/mst.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS multi sector transfer protection handling code. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + */ + +#include + +#include "ntfs.h" + +/* + * post_read_mst_fixup - deprotect multi sector transfer protected data + * @b: pointer to the data to deprotect + * @size: size in bytes of @b + * + * Perform the necessary post read multi sector transfer fixup and detect the + * presence of incomplete multi sector transfers. - In that case, overwrite the + * magic of the ntfs record header being processed with "BAAD" (in memory only!) + * and abort processing. + * + * Return 0 on success and -EINVAL on error ("BAAD" magic will be present). + * + * NOTE: We consider the absence / invalidity of an update sequence array to + * mean that the structure is not protected at all and hence doesn't need to + * be fixed up. Thus, we return success and not failure in this case. This is + * in contrast to pre_write_mst_fixup(), see below. + */ +int post_read_mst_fixup(struct ntfs_record *b, const u32 size) +{ + u16 usa_ofs, usa_count, usn; + u16 *usa_pos, *data_pos; + + /* Setup the variables. */ + usa_ofs = le16_to_cpu(b->usa_ofs); + /* Decrement usa_count to get number of fixups. */ + usa_count = le16_to_cpu(b->usa_count) - 1; + /* Size and alignment checks. */ + if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 || + usa_ofs + (usa_count * 2) > size || + (size >> NTFS_BLOCK_SIZE_BITS) != usa_count) + return 0; + /* Position of usn in update sequence array. */ + usa_pos = (u16 *)b + usa_ofs/sizeof(u16); + /* + * The update sequence number which has to be equal to each of the + * u16 values before they are fixed up. Note no need to care for + * endianness since we are comparing and moving data for on disk + * structures which means the data is consistent. - If it is + * consistenty the wrong endianness it doesn't make any difference. + */ + usn = *usa_pos; + /* + * Position in protected data of first u16 that needs fixing up. + */ + data_pos = (u16 *)b + NTFS_BLOCK_SIZE / sizeof(u16) - 1; + /* + * Check for incomplete multi sector transfer(s). + */ + while (usa_count--) { + if (*data_pos != usn) { + struct mft_record *m = (struct mft_record *)b; + + pr_err_ratelimited("ntfs: Incomplete multi sector transfer detected! (Record magic : 0x%x, mft number : 0x%x, base mft number : 0x%lx, mft in use : %d, data : 0x%x, usn 0x%x)\n", + le32_to_cpu(m->magic), le32_to_cpu(m->mft_record_number), + MREF_LE(m->base_mft_record), m->flags & MFT_RECORD_IN_USE, + *data_pos, usn); + /* + * Incomplete multi sector transfer detected! )-: + * Set the magic to "BAAD" and return failure. + * Note that magic_BAAD is already converted to le32. + */ + b->magic = magic_BAAD; + return -EINVAL; + } + data_pos += NTFS_BLOCK_SIZE / sizeof(u16); + } + /* Re-setup the variables. */ + usa_count = le16_to_cpu(b->usa_count) - 1; + data_pos = (u16 *)b + NTFS_BLOCK_SIZE / sizeof(u16) - 1; + /* Fixup all sectors. */ + while (usa_count--) { + /* + * Increment position in usa and restore original data from + * the usa into the data buffer. + */ + *data_pos = *(++usa_pos); + /* Increment position in data as well. */ + data_pos += NTFS_BLOCK_SIZE/sizeof(u16); + } + return 0; +} + +/* + * pre_write_mst_fixup - apply multi sector transfer protection + * @b: pointer to the data to protect + * @size: size in bytes of @b + * + * Perform the necessary pre write multi sector transfer fixup on the data + * pointer to by @b of @size. + * + * Return 0 if fixup applied (success) or -EINVAL if no fixup was performed + * (assumed not needed). This is in contrast to post_read_mst_fixup() above. + * + * NOTE: We consider the absence / invalidity of an update sequence array to + * mean that the structure is not subject to protection and hence doesn't need + * to be fixed up. This means that you have to create a valid update sequence + * array header in the ntfs record before calling this function, otherwise it + * will fail (the header needs to contain the position of the update sequence + * array together with the number of elements in the array). You also need to + * initialise the update sequence number before calling this function + * otherwise a random word will be used (whatever was in the record at that + * position at that time). + */ +int pre_write_mst_fixup(struct ntfs_record *b, const u32 size) +{ + __le16 *usa_pos, *data_pos; + u16 usa_ofs, usa_count, usn; + __le16 le_usn; + + /* Sanity check + only fixup if it makes sense. */ + if (!b || ntfs_is_baad_record(b->magic) || + ntfs_is_hole_record(b->magic)) + return -EINVAL; + /* Setup the variables. */ + usa_ofs = le16_to_cpu(b->usa_ofs); + /* Decrement usa_count to get number of fixups. */ + usa_count = le16_to_cpu(b->usa_count) - 1; + /* Size and alignment checks. */ + if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 || + usa_ofs + (usa_count * 2) > size || + (size >> NTFS_BLOCK_SIZE_BITS) != usa_count) + return -EINVAL; + /* Position of usn in update sequence array. */ + usa_pos = (__le16 *)((u8 *)b + usa_ofs); + /* + * Cyclically increment the update sequence number + * (skipping 0 and -1, i.e. 0xffff). + */ + usn = le16_to_cpup(usa_pos) + 1; + if (usn == 0xffff || !usn) + usn = 1; + le_usn = cpu_to_le16(usn); + *usa_pos = le_usn; + /* Position in data of first u16 that needs fixing up. */ + data_pos = (__le16 *)b + NTFS_BLOCK_SIZE/sizeof(__le16) - 1; + /* Fixup all sectors. */ + while (usa_count--) { + /* + * Increment the position in the usa and save the + * original data from the data buffer into the usa. + */ + *(++usa_pos) = *data_pos; + /* Apply fixup to data. */ + *data_pos = le_usn; + /* Increment position in data as well. */ + data_pos += NTFS_BLOCK_SIZE / sizeof(__le16); + } + return 0; +} + +/* + * post_write_mst_fixup - fast deprotect multi sector transfer protected data + * @b: pointer to the data to deprotect + * + * Perform the necessary post write multi sector transfer fixup, not checking + * for any errors, because we assume we have just used pre_write_mst_fixup(), + * thus the data will be fine or we would never have gotten here. + */ +void post_write_mst_fixup(struct ntfs_record *b) +{ + __le16 *usa_pos, *data_pos; + + u16 usa_ofs = le16_to_cpu(b->usa_ofs); + u16 usa_count = le16_to_cpu(b->usa_count) - 1; + + /* Position of usn in update sequence array. */ + usa_pos = (__le16 *)b + usa_ofs/sizeof(__le16); + + /* Position in protected data of first u16 that needs fixing up. */ + data_pos = (__le16 *)b + NTFS_BLOCK_SIZE/sizeof(__le16) - 1; + + /* Fixup all sectors. */ + while (usa_count--) { + /* + * Increment position in usa and restore original data from + * the usa into the data buffer. + */ + *data_pos = *(++usa_pos); + + /* Increment position in data as well. */ + data_pos += NTFS_BLOCK_SIZE/sizeof(__le16); + } +} diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c new file mode 100644 index 000000000000..10894de519c3 --- /dev/null +++ b/fs/ntfs/namei.c @@ -0,0 +1,1684 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel directory inode operations. + * + * Copyright (c) 2001-2006 Anton Altaparmakov + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include +#include + +#include "ntfs.h" +#include "time.h" +#include "index.h" +#include "reparse.h" +#include "object_id.h" +#include "ea.h" + +static const __le16 aux_name_le[3] = { + cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X') +}; + +static const __le16 con_name_le[3] = { + cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N') +}; + +static const __le16 com_name_le[3] = { + cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M') +}; + +static const __le16 lpt_name_le[3] = { + cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T') +}; + +static const __le16 nul_name_le[3] = { + cpu_to_le16('N'), cpu_to_le16('U'), cpu_to_le16('L') +}; + +static const __le16 prn_name_le[3] = { + cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N') +}; + +static inline int ntfs_check_bad_char(const __le16 *wc, unsigned int wc_len) +{ + int i; + + for (i = 0; i < wc_len; i++) { + u16 c = le16_to_cpu(wc[i]); + + if (c < 0x0020 || + c == 0x0022 || c == 0x002A || c == 0x002F || + c == 0x003A || c == 0x003C || c == 0x003E || + c == 0x003F || c == 0x005C || c == 0x007C) + return -EINVAL; + } + + return 0; +} + +static int ntfs_check_bad_windows_name(struct ntfs_volume *vol, + const __le16 *wc, + unsigned int wc_len) +{ + if (ntfs_check_bad_char(wc, wc_len)) + return -EINVAL; + + if (!NVolCheckWindowsNames(vol)) + return 0; + + /* Check for trailing space or dot. */ + if (wc_len > 0 && + (wc[wc_len - 1] == cpu_to_le16(' ') || + wc[wc_len - 1] == cpu_to_le16('.'))) + return -EINVAL; + + if (wc_len == 3 || (wc_len > 3 && wc[3] == cpu_to_le16('.'))) { + __le16 *upcase = vol->upcase; + u32 size = vol->upcase_len; + + if (ntfs_are_names_equal(wc, 3, aux_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, con_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, nul_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, prn_name_le, 3, IGNORE_CASE, upcase, size)) + return -EINVAL; + } + + if (wc_len == 4 || (wc_len > 4 && wc[4] == cpu_to_le16('.'))) { + __le16 *upcase = vol->upcase; + u32 size = vol->upcase_len, port; + + if (ntfs_are_names_equal(wc, 3, com_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, lpt_name_le, 3, IGNORE_CASE, upcase, size)) { + port = le16_to_cpu(wc[3]); + if (port >= '1' && port <= '9') + return -EINVAL; + } + } + return 0; +} + +/* + * ntfs_lookup - find the inode represented by a dentry in a directory inode + * @dir_ino: directory inode in which to look for the inode + * @dent: dentry representing the inode to look for + * @flags: lookup flags + * + * In short, ntfs_lookup() looks for the inode represented by the dentry @dent + * in the directory inode @dir_ino and if found attaches the inode to the + * dentry @dent. + * + * In more detail, the dentry @dent specifies which inode to look for by + * supplying the name of the inode in @dent->d_name.name. ntfs_lookup() + * converts the name to Unicode and walks the contents of the directory inode + * @dir_ino looking for the converted Unicode name. If the name is found in the + * directory, the corresponding inode is loaded by calling ntfs_iget() on its + * inode number and the inode is associated with the dentry @dent via a call to + * d_splice_alias(). + * + * If the name is not found in the directory, a NULL inode is inserted into the + * dentry @dent via a call to d_add(). The dentry is then termed a negative + * dentry. + * + * Only if an actual error occurs, do we return an error via ERR_PTR(). + * + * In order to handle the case insensitivity issues of NTFS with regards to the + * dcache and the dcache requiring only one dentry per directory, we deal with + * dentry aliases that only differ in case in ->ntfs_lookup() while maintaining + * a case sensitive dcache. This means that we get the full benefit of dcache + * speed when the file/directory is looked up with the same case as returned by + * ->ntfs_readdir() but that a lookup for any other case (or for the short file + * name) will not find anything in dcache and will enter ->ntfs_lookup() + * instead, where we search the directory for a fully matching file name + * (including case) and if that is not found, we search for a file name that + * matches with different case and if that has non-POSIX semantics we return + * that. We actually do only one search (case sensitive) and keep tabs on + * whether we have found a case insensitive match in the process. + * + * To simplify matters for us, we do not treat the short vs long filenames as + * two hard links but instead if the lookup matches a short filename, we + * return the dentry for the corresponding long filename instead. + * + * There are three cases we need to distinguish here: + * + * 1) @dent perfectly matches (i.e. including case) a directory entry with a + * file name in the WIN32 or POSIX namespaces. In this case + * ntfs_lookup_inode_by_name() will return with name set to NULL and we + * just d_splice_alias() @dent. + * 2) @dent matches (not including case) a directory entry with a file name in + * the WIN32 namespace. In this case ntfs_lookup_inode_by_name() will return + * with name set to point to a kmalloc()ed ntfs_name structure containing + * the properly cased little endian Unicode name. We convert the name to the + * current NLS code page, search if a dentry with this name already exists + * and if so return that instead of @dent. At this point things are + * complicated by the possibility of 'disconnected' dentries due to NFS + * which we deal with appropriately (see the code comments). The VFS will + * then destroy the old @dent and use the one we returned. If a dentry is + * not found, we allocate a new one, d_splice_alias() it, and return it as + * above. + * 3) @dent matches either perfectly or not (i.e. we don't care about case) a + * directory entry with a file name in the DOS namespace. In this case + * ntfs_lookup_inode_by_name() will return with name set to point to a + * kmalloc()ed ntfs_name structure containing the mft reference (cpu endian) + * of the inode. We use the mft reference to read the inode and to find the + * file name in the WIN32 namespace corresponding to the matched short file + * name. We then convert the name to the current NLS code page, and proceed + * searching for a dentry with this name, etc, as in case 2), above. + * + * Locking: Caller must hold i_mutex on the directory. + */ +static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent, + unsigned int flags) +{ + struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb); + struct inode *dent_inode; + __le16 *uname; + struct ntfs_name *name = NULL; + u64 mref; + unsigned long dent_ino; + int uname_len; + + ntfs_debug("Looking up %pd in directory inode 0x%llx.", + dent, NTFS_I(dir_ino)->mft_no); + /* Convert the name of the dentry to Unicode. */ + uname_len = ntfs_nlstoucs(vol, dent->d_name.name, dent->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_debug("Failed to convert name to Unicode."); + return ERR_PTR(uname_len); + } + mutex_lock(&NTFS_I(dir_ino)->mrec_lock); + mref = ntfs_lookup_inode_by_name(NTFS_I(dir_ino), uname, uname_len, + &name); + mutex_unlock(&NTFS_I(dir_ino)->mrec_lock); + kmem_cache_free(ntfs_name_cache, uname); + if (!IS_ERR_MREF(mref)) { + dent_ino = MREF(mref); + ntfs_debug("Found inode 0x%lx. Calling ntfs_iget.", dent_ino); + dent_inode = ntfs_iget(vol->sb, dent_ino); + if (!IS_ERR(dent_inode)) { + /* Consistency check. */ + if (MSEQNO(mref) == NTFS_I(dent_inode)->seq_no || + dent_ino == FILE_MFT) { + /* Perfect WIN32/POSIX match. -- Case 1. */ + if (!name) { + ntfs_debug("Done. (Case 1.)"); + return d_splice_alias(dent_inode, dent); + } + /* + * We are too indented. Handle imperfect + * matches and short file names further below. + */ + goto handle_name; + } + ntfs_error(vol->sb, + "Found stale reference to inode 0x%lx (reference sequence number = 0x%x, inode sequence number = 0x%x), returning -EIO. Run chkdsk.", + dent_ino, MSEQNO(mref), + NTFS_I(dent_inode)->seq_no); + iput(dent_inode); + dent_inode = ERR_PTR(-EIO); + } else + ntfs_error(vol->sb, "ntfs_iget(0x%lx) failed with error code %li.", + dent_ino, PTR_ERR(dent_inode)); + kfree(name); + /* Return the error code. */ + return ERR_CAST(dent_inode); + } + kfree(name); + /* It is guaranteed that @name is no longer allocated at this point. */ + if (MREF_ERR(mref) == -ENOENT) { + ntfs_debug("Entry was not found, adding negative dentry."); + /* The dcache will handle negative entries. */ + d_add(dent, NULL); + ntfs_debug("Done."); + return NULL; + } + ntfs_error(vol->sb, "ntfs_lookup_ino_by_name() failed with error code %i.", + -MREF_ERR(mref)); + return ERR_PTR(MREF_ERR(mref)); +handle_name: + { + struct mft_record *m; + struct ntfs_attr_search_ctx *ctx; + struct ntfs_inode *ni = NTFS_I(dent_inode); + int err; + struct qstr nls_name; + + nls_name.name = NULL; + if (name->type != FILE_NAME_DOS) { /* Case 2. */ + ntfs_debug("Case 2."); + nls_name.len = (unsigned int)ntfs_ucstonls(vol, + (__le16 *)&name->name, name->len, + (unsigned char **)&nls_name.name, 0); + kfree(name); + } else /* if (name->type == FILE_NAME_DOS) */ { /* Case 3. */ + struct file_name_attr *fn; + + ntfs_debug("Case 3."); + kfree(name); + + /* Find the WIN32 name corresponding to the matched DOS name. */ + ni = NTFS_I(dent_inode); + m = map_mft_record(ni); + if (IS_ERR(m)) { + err = PTR_ERR(m); + m = NULL; + ctx = NULL; + goto err_out; + } + ctx = ntfs_attr_get_search_ctx(ni, m); + if (unlikely(!ctx)) { + err = -ENOMEM; + goto err_out; + } + do { + struct attr_record *a; + + err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, + NULL, 0, ctx); + if (unlikely(err)) { + ntfs_error(vol->sb, + "Inode corrupt: No WIN32 namespace counterpart to DOS file name. Run chkdsk."); + if (err == -ENOENT) + err = -EIO; + goto err_out; + } + /* Consistency checks. */ + a = ctx->attr; + if (a->non_resident || a->flags) + goto eio_err_out; + fn = (struct file_name_attr *)((u8 *)ctx->attr + le16_to_cpu( + ctx->attr->data.resident.value_offset)); + } while (fn->file_name_type != FILE_NAME_WIN32); + + /* Convert the found WIN32 name to current NLS code page. */ + nls_name.len = (unsigned int)ntfs_ucstonls(vol, + (__le16 *)&fn->file_name, fn->file_name_length, + (unsigned char **)&nls_name.name, 0); + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + } + m = NULL; + ctx = NULL; + + /* Check if a conversion error occurred. */ + if ((int)nls_name.len < 0) { + err = (int)nls_name.len; + goto err_out; + } + nls_name.hash = full_name_hash(dent, nls_name.name, nls_name.len); + + dent = d_add_ci(dent, dent_inode, &nls_name); + kfree(nls_name.name); + return dent; + +eio_err_out: + ntfs_error(vol->sb, "Illegal file name attribute. Run chkdsk."); + err = -EIO; +err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(ni); + iput(dent_inode); + ntfs_error(vol->sb, "Failed, returning error code %i.", err); + return ERR_PTR(err); + } +} + +static int ntfs_sd_add_everyone(struct ntfs_inode *ni) +{ + struct security_descriptor_relative *sd; + struct ntfs_acl *acl; + struct ntfs_ace *ace; + struct ntfs_sid *sid; + int ret, sd_len; + + /* Create SECURITY_DESCRIPTOR attribute (everyone has full access). */ + /* + * Calculate security descriptor length. We have 2 sub-authorities in + * owner and group SIDs, So add 8 bytes to every SID. + */ + sd_len = sizeof(struct security_descriptor_relative) + 2 * + (sizeof(struct ntfs_sid) + 8) + sizeof(struct ntfs_acl) + + sizeof(struct ntfs_ace) + 4; + sd = kmalloc(sd_len, GFP_NOFS); + if (!sd) + return -1; + + sd->revision = 1; + sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE; + + sid = (struct ntfs_sid *)((u8 *)sd + sizeof(struct security_descriptor_relative)); + sid->revision = 1; + sid->sub_authority_count = 2; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] = 5; + sd->owner = cpu_to_le32((u8 *)sid - (u8 *)sd); + + sid = (struct ntfs_sid *)((u8 *)sid + sizeof(struct ntfs_sid) + 8); + sid->revision = 1; + sid->sub_authority_count = 2; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] = 5; + sd->group = cpu_to_le32((u8 *)sid - (u8 *)sd); + + acl = (struct ntfs_acl *)((u8 *)sid + sizeof(struct ntfs_sid) + 8); + acl->revision = 2; + acl->size = cpu_to_le16(sizeof(struct ntfs_acl) + sizeof(struct ntfs_ace) + 4); + acl->ace_count = cpu_to_le16(1); + sd->dacl = cpu_to_le32((u8 *)acl - (u8 *)sd); + + ace = (struct ntfs_ace *)((u8 *)acl + sizeof(struct ntfs_acl)); + ace->type = ACCESS_ALLOWED_ACE_TYPE; + ace->flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; + ace->size = cpu_to_le16(sizeof(struct ntfs_ace) + 4); + ace->mask = cpu_to_le32(0x1f01ff); + ace->sid.revision = 1; + ace->sid.sub_authority_count = 1; + ace->sid.sub_authority[0] = 0; + ace->sid.identifier_authority.value[5] = 1; + + ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8 *)sd, + sd_len); + if (ret) + ntfs_error(ni->vol->sb, "Failed to add SECURITY_DESCRIPTOR\n"); + + kfree(sd); + return ret; +} + +static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dir, + __le16 *name, u8 name_len, mode_t mode, dev_t dev, + __le16 *target, int target_len) +{ + struct ntfs_inode *dir_ni = NTFS_I(dir); + struct ntfs_volume *vol = dir_ni->vol; + struct ntfs_inode *ni; + bool rollback_data = false, rollback_sd = false, rollback_reparse = false; + struct file_name_attr *fn = NULL; + struct standard_information *si = NULL; + int err = 0, fn_len, si_len; + struct inode *vi; + struct mft_record *ni_mrec, *dni_mrec; + struct super_block *sb = dir_ni->vol->sb; + __le64 parent_mft_ref; + u64 child_mft_ref; + __le16 ea_size; + + vi = new_inode(vol->sb); + if (!vi) + return ERR_PTR(-ENOMEM); + + ntfs_init_big_inode(vi); + ni = NTFS_I(vi); + ni->vol = dir_ni->vol; + ni->name_len = 0; + ni->name = NULL; + + /* + * Set the appropriate mode, attribute type, and name. For + * directories, also setup the index values to the defaults. + */ + if (S_ISDIR(mode)) { + mode &= ~vol->dmask; + + NInoSetMstProtected(ni); + ni->itype.index.block_size = 4096; + ni->itype.index.block_size_bits = ntfs_ffs(4096) - 1; + ni->itype.index.collation_rule = COLLATION_FILE_NAME; + if (vol->cluster_size <= ni->itype.index.block_size) { + ni->itype.index.vcn_size = vol->cluster_size; + ni->itype.index.vcn_size_bits = + vol->cluster_size_bits; + } else { + ni->itype.index.vcn_size = vol->sector_size; + ni->itype.index.vcn_size_bits = + vol->sector_size_bits; + } + } else { + mode &= ~vol->fmask; + } + + if (IS_RDONLY(vi)) + mode &= ~0222; + + inode_init_owner(idmap, vi, dir, mode); + + mode = vi->i_mode; + +#ifdef CONFIG_NTFS_FS_POSIX_ACL + if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) { + err = ntfs_init_acl(idmap, vi, dir); + if (err) + goto err_out; + } else +#endif + { + vi->i_flags |= S_NOSEC; + } + + if (uid_valid(vol->uid)) + vi->i_uid = vol->uid; + + if (gid_valid(vol->gid)) + vi->i_gid = vol->gid; + + /* + * Set the file size to 0, the ntfs inode sizes are set to 0 by + * the call to ntfs_init_big_inode() below. + */ + vi->i_size = 0; + vi->i_blocks = 0; + + inode_inc_iversion(vi); + + simple_inode_init_ts(vi); + ni->i_crtime = inode_get_ctime(vi); + + inode_set_mtime_to_ts(dir, ni->i_crtime); + inode_set_ctime_to_ts(dir, ni->i_crtime); + mark_inode_dirty(dir); + + err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL, + &ni_mrec); + if (err) { + iput(vi); + return ERR_PTR(err); + } + + /* + * Prevent iget and writeback from finding this inode. + * Caller must call d_instantiate_new instead of d_instantiate. + */ + spin_lock(&vi->i_lock); + inode_state_set(vi, I_NEW | I_CREATING); + spin_unlock(&vi->i_lock); + + /* Add the inode to the inode hash for the superblock. */ + vi->i_ino = (unsigned long)ni->mft_no; + inode_set_iversion(vi, 1); + insert_inode_hash(vi); + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + if (NInoBeingDeleted(dir_ni)) { + err = -ENOENT; + goto err_out; + } + + dni_mrec = map_mft_record(dir_ni); + if (IS_ERR(dni_mrec)) { + ntfs_error(dir_ni->vol->sb, "failed to map mft record for file 0x%llx.\n", + dir_ni->mft_no); + err = -EIO; + goto err_out; + } + parent_mft_ref = MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dni_mrec->sequence_number)); + unmap_mft_record(dir_ni); + + /* + * Create STANDARD_INFORMATION attribute. Write STANDARD_INFORMATION + * version 1.2, windows will upgrade it to version 3 if needed. + */ + si_len = offsetof(struct standard_information, file_attributes) + + sizeof(__le32) + 12; + si = kzalloc(si_len, GFP_NOFS); + if (!si) { + err = -ENOMEM; + goto err_out; + } + + si->creation_time = si->last_data_change_time = utc2ntfs(ni->i_crtime); + si->last_mft_change_time = si->last_access_time = si->creation_time; + + if (!S_ISREG(mode) && !S_ISDIR(mode)) + si->file_attributes = FILE_ATTR_SYSTEM; + + /* Add STANDARD_INFORMATION to inode. */ + err = ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, (u8 *)si, + si_len); + if (err) { + ntfs_error(sb, "Failed to add STANDARD_INFORMATION attribute.\n"); + goto err_out; + } + + err = ntfs_sd_add_everyone(ni); + if (err) + goto err_out; + rollback_sd = true; + + if (S_ISDIR(mode)) { + struct index_root *ir = NULL; + struct index_entry *ie; + int ir_len, index_len; + + /* Create struct index_root attribute. */ + index_len = sizeof(struct index_header) + sizeof(struct index_entry_header); + ir_len = offsetof(struct index_root, index) + index_len; + ir = kzalloc(ir_len, GFP_NOFS); + if (!ir) { + err = -ENOMEM; + goto err_out; + } + ir->type = AT_FILE_NAME; + ir->collation_rule = COLLATION_FILE_NAME; + ir->index_block_size = cpu_to_le32(ni->vol->index_record_size); + if (ni->vol->cluster_size <= ni->vol->index_record_size) + ir->clusters_per_index_block = + NTFS_B_TO_CLU(vol, ni->vol->index_record_size); + else + ir->clusters_per_index_block = + ni->vol->index_record_size >> ni->vol->sector_size_bits; + ir->index.entries_offset = cpu_to_le32(sizeof(struct index_header)); + ir->index.index_length = cpu_to_le32(index_len); + ir->index.allocated_size = cpu_to_le32(index_len); + ie = (struct index_entry *)((u8 *)ir + sizeof(struct index_root)); + ie->length = cpu_to_le16(sizeof(struct index_entry_header)); + ie->key_length = 0; + ie->flags = INDEX_ENTRY_END; + + /* Add struct index_root attribute to inode. */ + err = ntfs_attr_add(ni, AT_INDEX_ROOT, I30, 4, (u8 *)ir, ir_len); + if (err) { + kfree(ir); + ntfs_error(vi->i_sb, "Failed to add struct index_root attribute.\n"); + goto err_out; + } + kfree(ir); + err = ntfs_attr_open(ni, AT_INDEX_ROOT, I30, 4); + if (err) + goto err_out; + } else { + /* Add DATA attribute to inode. */ + err = ntfs_attr_add(ni, AT_DATA, AT_UNNAMED, 0, NULL, 0); + if (err) { + ntfs_error(dir_ni->vol->sb, "Failed to add DATA attribute.\n"); + goto err_out; + } + rollback_data = true; + + err = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (err) + goto err_out; + + if (S_ISLNK(mode)) { + err = ntfs_reparse_set_wsl_symlink(ni, target, target_len); + if (!err) + rollback_reparse = true; + } else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISSOCK(mode) || + S_ISFIFO(mode)) { + si->file_attributes = FILE_ATTRIBUTE_RECALL_ON_OPEN; + ni->flags = FILE_ATTRIBUTE_RECALL_ON_OPEN; + err = ntfs_reparse_set_wsl_not_symlink(ni, mode); + if (!err) + rollback_reparse = true; + } + if (err) + goto err_out; + } + + err = ntfs_ea_set_wsl_inode(vi, dev, &ea_size, + NTFS_EA_UID | NTFS_EA_GID | NTFS_EA_MODE); + if (err) + goto err_out; + + /* Create FILE_NAME attribute. */ + fn_len = sizeof(struct file_name_attr) + name_len * sizeof(__le16); + fn = kzalloc(fn_len, GFP_NOFS); + if (!fn) { + err = -ENOMEM; + goto err_out; + } + + fn->file_attributes |= ni->flags; + fn->parent_directory = parent_mft_ref; + fn->file_name_length = name_len; + fn->file_name_type = FILE_NAME_POSIX; + fn->type.ea.packed_ea_size = ea_size; + if (S_ISDIR(mode)) { + fn->file_attributes = FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT; + fn->allocated_size = fn->data_size = 0; + } else { + fn->data_size = cpu_to_le64(ni->data_size); + fn->allocated_size = cpu_to_le64(ni->allocated_size); + } + if (!S_ISREG(mode) && !S_ISDIR(mode)) { + fn->file_attributes = FILE_ATTR_SYSTEM; + if (rollback_reparse) + fn->file_attributes |= FILE_ATTR_REPARSE_POINT; + } + if (NVolHideDotFiles(vol) && name_len > 0 && name[0] == cpu_to_le16('.')) + fn->file_attributes |= FILE_ATTR_HIDDEN; + fn->creation_time = fn->last_data_change_time = utc2ntfs(ni->i_crtime); + fn->last_mft_change_time = fn->last_access_time = fn->creation_time; + memcpy(fn->file_name, name, name_len * sizeof(__le16)); + + /* Add FILE_NAME attribute to inode. */ + err = ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8 *)fn, fn_len); + if (err) { + ntfs_error(sb, "Failed to add FILE_NAME attribute.\n"); + goto err_out; + } + + child_mft_ref = MK_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number)); + /* Set hard links count and directory flag. */ + ni_mrec->link_count = cpu_to_le16(1); + mark_mft_record_dirty(ni); + + /* Add FILE_NAME attribute to index. */ + err = ntfs_index_add_filename(dir_ni, fn, child_mft_ref); + if (err) { + ntfs_debug("Failed to add entry to the index"); + goto err_out; + } + + unmap_mft_record(ni); + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + + ni->flags = fn->file_attributes; + /* Set the sequence number. */ + vi->i_generation = ni->seq_no; + set_nlink(vi, 1); + ntfs_set_vfs_operations(vi, mode, dev); + + /* Done! */ + kfree(fn); + kfree(si); + ntfs_debug("Done.\n"); + return ni; + +err_out: + if (rollback_sd) + ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); + + if (rollback_data) + ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0); + + if (rollback_reparse) + ntfs_delete_reparse_index(ni); + /* + * Free extent MFT records (should not exist any with current + * ntfs_create implementation, but for any case if something will be + * changed in the future). + */ + while (ni->nr_extents != 0) { + int err2; + + err2 = ntfs_mft_record_free(ni->vol, *(ni->ext.extent_ntfs_inos)); + if (err2) + ntfs_error(sb, + "Failed to free extent MFT record. Leaving inconsistent metadata.\n"); + ntfs_inode_close(*(ni->ext.extent_ntfs_inos)); + } + if (ntfs_mft_record_free(ni->vol, ni)) + ntfs_error(sb, + "Failed to free MFT record. Leaving inconsistent metadata. Run chkdsk.\n"); + unmap_mft_record(ni); + kfree(fn); + kfree(si); + + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + + remove_inode_hash(vi); + discard_new_inode(vi); + return ERR_PTR(err); +} + +static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +{ + struct ntfs_volume *vol = NTFS_SB(dir->i_sb); + struct ntfs_inode *ni; + __le16 *uname; + int uname_len, err; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(vol->sb, "Failed to convert name to unicode."); + return uname_len; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFREG | mode, 0, NULL, 0); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) + return PTR_ERR(ni); + + d_instantiate_new(dentry, VFS_I(ni)); + + return 0; +} + +static int ntfs_check_unlinkable_dir(struct ntfs_attr_search_ctx *ctx, struct file_name_attr *fn) +{ + int link_count; + int ret; + struct ntfs_inode *ni = ctx->base_ntfs_ino ? ctx->base_ntfs_ino : ctx->ntfs_ino; + struct mft_record *ni_mrec = ctx->base_mrec ? ctx->base_mrec : ctx->mrec; + + ret = ntfs_check_empty_dir(ni, ni_mrec); + if (!ret || ret != -ENOTEMPTY) + return ret; + + link_count = le16_to_cpu(ni_mrec->link_count); + /* + * Directory is non-empty, so we can unlink only if there is more than + * one "real" hard link, i.e. links aren't different DOS and WIN32 names + */ + if ((link_count == 1) || + (link_count == 2 && fn->file_name_type == FILE_NAME_DOS)) { + ret = -ENOTEMPTY; + ntfs_debug("Non-empty directory without hard links\n"); + goto no_hardlink; + } + + ret = 0; +no_hardlink: + return ret; +} + +static int ntfs_test_inode_attr(struct inode *vi, void *data) +{ + struct ntfs_inode *ni = NTFS_I(vi); + u64 mft_no = (u64)(uintptr_t)data; + + if (ni->mft_no != mft_no) + return 0; + if (NInoAttr(ni) || ni->nr_extents == -1) + return 1; + else + return 0; +} + +/* + * ntfs_delete - delete file or directory from ntfs volume + * @ni: ntfs inode for object to delte + * @dir_ni: ntfs inode for directory in which delete object + * @name: unicode name of the object to delete + * @name_len: length of the name in unicode characters + * @need_lock: whether mrec lock is needed or not + * + * Delete the specified name from the directory index @dir_ni and decrement + * the link count of the target inode @ni. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, + __le16 *name, u8 name_len, bool need_lock) +{ + struct ntfs_attr_search_ctx *actx = NULL; + struct file_name_attr *fn = NULL; + bool looking_for_dos_name = false, looking_for_win32_name = false; + bool case_sensitive_match = true; + int err = 0; + struct mft_record *ni_mrec; + struct super_block *sb; + bool link_count_zero = false; + + ntfs_debug("Entering.\n"); + + if (need_lock == true) { + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + } + + sb = dir_ni->vol->sb; + + if (ni->nr_extents == -1) + ni = ni->ext.base_ntfs_ino; + if (dir_ni->nr_extents == -1) + dir_ni = dir_ni->ext.base_ntfs_ino; + /* + * Search for FILE_NAME attribute with such name. If it's in POSIX or + * WIN32_AND_DOS namespace, then simply remove it from index and inode. + * If filename in DOS or in WIN32 namespace, then remove DOS name first, + * only then remove WIN32 name. + */ + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + ntfs_error(sb, "%s, Failed to get search context", __func__); + if (need_lock) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + return -ENOMEM; + } +search: + while ((err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE, + 0, NULL, 0, actx)) == 0) { +#ifdef DEBUG + unsigned char *s; +#endif + bool case_sensitive = IGNORE_CASE; + + fn = (struct file_name_attr *)((u8 *)actx->attr + + le16_to_cpu(actx->attr->data.resident.value_offset)); +#ifdef DEBUG + s = ntfs_attr_name_get(ni->vol, fn->file_name, fn->file_name_length); + ntfs_debug("name: '%s' type: %d dos: %d win32: %d case: %d\n", + s, fn->file_name_type, + looking_for_dos_name, looking_for_win32_name, + case_sensitive_match); + ntfs_attr_name_free(&s); +#endif + if (looking_for_dos_name) { + if (fn->file_name_type == FILE_NAME_DOS) + break; + continue; + } + if (looking_for_win32_name) { + if (fn->file_name_type == FILE_NAME_WIN32) + break; + continue; + } + + /* Ignore hard links from other directories */ + if (dir_ni->mft_no != MREF_LE(fn->parent_directory)) { + ntfs_debug("MFT record numbers don't match (%llu != %lu)\n", + dir_ni->mft_no, + MREF_LE(fn->parent_directory)); + continue; + } + + if (fn->file_name_type == FILE_NAME_POSIX || case_sensitive_match) + case_sensitive = CASE_SENSITIVE; + + if (ntfs_names_are_equal(fn->file_name, fn->file_name_length, + name, name_len, case_sensitive, + ni->vol->upcase, ni->vol->upcase_len)) { + if (fn->file_name_type == FILE_NAME_WIN32) { + looking_for_dos_name = true; + ntfs_attr_reinit_search_ctx(actx); + continue; + } + if (fn->file_name_type == FILE_NAME_DOS) + looking_for_dos_name = true; + break; + } + } + if (err) { + /* + * If case sensitive search failed, then try once again + * ignoring case. + */ + if (err == -ENOENT && case_sensitive_match) { + case_sensitive_match = false; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + goto err_out; + } + + err = ntfs_check_unlinkable_dir(actx, fn); + if (err) + goto err_out; + + err = ntfs_index_remove(dir_ni, fn, le32_to_cpu(actx->attr->data.resident.value_length)); + if (err) + goto err_out; + + err = ntfs_attr_record_rm(actx); + if (err) + goto err_out; + + ni_mrec = actx->base_mrec ? actx->base_mrec : actx->mrec; + ni_mrec->link_count = cpu_to_le16(le16_to_cpu(ni_mrec->link_count) - 1); + drop_nlink(VFS_I(ni)); + + mark_mft_record_dirty(ni); + if (looking_for_dos_name) { + looking_for_dos_name = false; + looking_for_win32_name = true; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (ni_mrec->link_count == 0) { + NInoSetBeingDeleted(ni); + ntfs_delete_reparse_index(ni); + ntfs_delete_object_id_index(ni); + link_count_zero = true; + } + + ntfs_attr_put_search_ctx(actx); + if (need_lock == true) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (link_count_zero == true) { + struct inode *attr_vi; + + while ((attr_vi = ilookup5(sb, ni->mft_no, ntfs_test_inode_attr, + (void *)(uintptr_t)ni->mft_no)) != NULL) { + clear_nlink(attr_vi); + iput(attr_vi); + } + } + ntfs_debug("Done.\n"); + return 0; +err_out: + ntfs_attr_put_search_ctx(actx); + if (need_lock) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + return err; +} + +static int ntfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *vi = dentry->d_inode; + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni = NTFS_I(vi); + __le16 *uname = NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + err = ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); + if (err) + goto out; + + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); + inode_set_ctime_to_ts(vi, inode_get_ctime(dir)); + if (vi->i_nlink) + mark_inode_dirty(vi); +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni; + __le16 *uname; + int uname_len; + + if (NVolShutdown(vol)) + return ERR_PTR(-EIO); + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return ERR_PTR(-ENOMEM); + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return ERR_PTR(err); + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFDIR | mode, 0, NULL, 0); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) { + err = PTR_ERR(ni); + return ERR_PTR(err); + } + + d_instantiate_new(dentry, VFS_I(ni)); + return NULL; +} + +static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *vi = dentry->d_inode; + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni; + __le16 *uname = NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + ni = NTFS_I(vi); + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + err = ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); + if (err) + goto out; + + inode_set_mtime_to_ts(vi, inode_set_atime_to_ts(vi, current_time(vi))); +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +/* + * __ntfs_link - create hard link for file or directory + * @ni: ntfs inode for object to create hard link + * @dir_ni: ntfs inode for directory in which new link should be placed + * @name: unicode name of the new link + * @name_len: length of the name in unicode characters + * + * Create a new hard link. This involves adding an entry to the directory + * index and adding a new FILE_NAME attribute to the target inode. + * + * Return 0 on success and -errno on error. + */ +static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, + __le16 *name, u8 name_len) +{ + struct super_block *sb; + struct inode *vi = VFS_I(ni); + struct file_name_attr *fn = NULL; + int fn_len, err = 0; + struct mft_record *dir_mrec = NULL, *ni_mrec = NULL; + + ntfs_debug("Entering.\n"); + + sb = dir_ni->vol->sb; + if (NInoBeingDeleted(dir_ni) || NInoBeingDeleted(ni)) + return -ENOENT; + + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto err_out; + } + + if (le16_to_cpu(ni_mrec->link_count) == 0) { + err = -ENOENT; + goto err_out; + } + + /* Create FILE_NAME attribute. */ + fn_len = sizeof(struct file_name_attr) + name_len * sizeof(__le16); + + fn = kzalloc(fn_len, GFP_NOFS); + if (!fn) { + err = -ENOMEM; + goto err_out; + } + + dir_mrec = map_mft_record(dir_ni); + if (IS_ERR(dir_mrec)) { + err = -EIO; + goto err_out; + } + + fn->parent_directory = MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dir_mrec->sequence_number)); + unmap_mft_record(dir_ni); + fn->file_name_length = name_len; + fn->file_name_type = FILE_NAME_POSIX; + fn->file_attributes = ni->flags; + if (ni_mrec->flags & MFT_RECORD_IS_DIRECTORY) { + fn->file_attributes |= FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT; + fn->allocated_size = fn->data_size = 0; + } else { + if (NInoSparse(ni) || NInoCompressed(ni)) + fn->allocated_size = + cpu_to_le64(ni->itype.compressed.size); + else + fn->allocated_size = cpu_to_le64(ni->allocated_size); + fn->data_size = cpu_to_le64(ni->data_size); + } + if (NVolHideDotFiles(dir_ni->vol) && name_len > 0 && name[0] == cpu_to_le16('.')) + fn->file_attributes |= FILE_ATTR_HIDDEN; + + fn->creation_time = utc2ntfs(ni->i_crtime); + fn->last_data_change_time = utc2ntfs(inode_get_mtime(vi)); + fn->last_mft_change_time = utc2ntfs(inode_get_ctime(vi)); + fn->last_access_time = utc2ntfs(inode_get_atime(vi)); + memcpy(fn->file_name, name, name_len * sizeof(__le16)); + + /* Add FILE_NAME attribute to index. */ + err = ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number))); + if (err) { + ntfs_error(sb, "Failed to add filename to the index"); + goto err_out; + } + /* Add FILE_NAME attribute to inode. */ + err = ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8 *)fn, fn_len); + if (err) { + ntfs_error(sb, "Failed to add FILE_NAME attribute.\n"); + /* Try to remove just added attribute from index. */ + if (ntfs_index_remove(dir_ni, fn, fn_len)) + goto rollback_failed; + goto err_out; + } + /* Increment hard links count. */ + ni_mrec->link_count = cpu_to_le16(le16_to_cpu(ni_mrec->link_count) + 1); + inc_nlink(VFS_I(ni)); + + /* Done! */ + mark_mft_record_dirty(ni); + kfree(fn); + unmap_mft_record(ni); + + ntfs_debug("Done.\n"); + + return 0; +rollback_failed: + ntfs_error(sb, "Rollback failed. Leaving inconsistent metadata.\n"); +err_out: + kfree(fn); + if (!IS_ERR_OR_NULL(ni_mrec)) + unmap_mft_record(ni); + return err; +} + +static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) +{ + struct inode *old_inode, *new_inode = NULL; + int err = 0; + int is_dir; + struct super_block *sb = old_dir->i_sb; + __le16 *uname_new = NULL; + __le16 *uname_old = NULL; + int new_name_len; + int old_name_len; + struct ntfs_volume *vol = NTFS_SB(sb); + struct ntfs_inode *old_ni, *new_ni = NULL; + struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir); + + if (NVolShutdown(old_dir_ni->vol)) + return -EIO; + + if (flags & (RENAME_EXCHANGE | RENAME_WHITEOUT)) + return -EINVAL; + + new_name_len = ntfs_nlstoucs(NTFS_I(new_dir)->vol, new_dentry->d_name.name, + new_dentry->d_name.len, &uname_new, + NTFS_MAX_NAME_LEN); + if (new_name_len < 0) { + if (new_name_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname_new, new_name_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname_new); + return err; + } + + old_name_len = ntfs_nlstoucs(NTFS_I(old_dir)->vol, old_dentry->d_name.name, + old_dentry->d_name.len, &uname_old, + NTFS_MAX_NAME_LEN); + if (old_name_len < 0) { + kmem_cache_free(ntfs_name_cache, uname_new); + if (old_name_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + old_inode = old_dentry->d_inode; + new_inode = new_dentry->d_inode; + old_ni = NTFS_I(old_inode); + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + + if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) { + err = -ENOENT; + goto unlock_old; + } + + is_dir = S_ISDIR(old_inode->i_mode); + + if (new_inode) { + new_ni = NTFS_I(new_inode); + mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2); + if (old_dir != new_dir) { + mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); + if (NInoBeingDeleted(new_dir_ni)) { + err = -ENOENT; + goto err_out; + } + } + + if (NInoBeingDeleted(new_ni)) { + err = -ENOENT; + goto err_out; + } + + if (is_dir) { + struct mft_record *ni_mrec; + + ni_mrec = map_mft_record(NTFS_I(new_inode)); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto err_out; + } + err = ntfs_check_empty_dir(NTFS_I(new_inode), ni_mrec); + unmap_mft_record(NTFS_I(new_inode)); + if (err) + goto err_out; + } + + err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false); + if (err) + goto err_out; + } else { + if (old_dir != new_dir) { + mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); + if (NInoBeingDeleted(new_dir_ni)) { + err = -ENOENT; + goto err_out; + } + } + } + + err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len); + if (err) + goto err_out; + + err = ntfs_delete(old_ni, old_dir_ni, uname_old, old_name_len, false); + if (err) { + int err2; + + ntfs_error(sb, "Failed to delete old ntfs inode(%llu) in old dir, err : %d\n", + old_ni->mft_no, err); + err2 = ntfs_delete(old_ni, new_dir_ni, uname_new, new_name_len, false); + if (err2) + ntfs_error(sb, "Failed to delete old ntfs inode in new dir, err : %d\n", + err2); + goto err_out; + } + + simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry); + mark_inode_dirty(old_inode); + mark_inode_dirty(old_dir); + if (old_dir != new_dir) + mark_inode_dirty(new_dir); + if (new_inode) + mark_inode_dirty(old_inode); + + inode_inc_iversion(new_dir); + +err_out: + if (old_dir != new_dir) + mutex_unlock(&new_dir_ni->mrec_lock); + if (new_inode) + mutex_unlock(&new_ni->mrec_lock); + +unlock_old: + mutex_unlock(&old_dir_ni->mrec_lock); + mutex_unlock(&old_ni->mrec_lock); + if (uname_new) + kmem_cache_free(ntfs_name_cache, uname_new); + if (uname_old) + kmem_cache_free(ntfs_name_cache, uname_old); + + return err; +} + +static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) +{ + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + struct inode *vi; + int err = 0; + struct ntfs_inode *ni; + __le16 *usrc; + __le16 *utarget; + int usrc_len; + int utarget_len; + int symlen = strlen(symname); + + if (NVolShutdown(vol)) + return -EIO; + + usrc_len = ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &usrc, NTFS_MAX_NAME_LEN); + if (usrc_len < 0) { + if (usrc_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + err = -ENOMEM; + goto out; + } + + err = ntfs_check_bad_windows_name(vol, usrc, usrc_len); + if (err) { + kmem_cache_free(ntfs_name_cache, usrc); + goto out; + } + + utarget_len = ntfs_nlstoucs(vol, symname, symlen, &utarget, + PATH_MAX); + if (utarget_len < 0) { + if (utarget_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert target name to Unicode."); + err = -ENOMEM; + kmem_cache_free(ntfs_name_cache, usrc); + goto out; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni = __ntfs_create(idmap, dir, usrc, usrc_len, S_IFLNK | 0777, 0, + utarget, utarget_len); + kmem_cache_free(ntfs_name_cache, usrc); + kvfree(utarget); + if (IS_ERR(ni)) { + err = PTR_ERR(ni); + goto out; + } + + vi = VFS_I(ni); + vi->i_size = symlen; + d_instantiate_new(dentry, vi); +out: + return err; +} + +static int ntfs_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t rdev) +{ + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni; + __le16 *uname = NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + switch (mode & S_IFMT) { + case S_IFCHR: + case S_IFBLK: + ni = __ntfs_create(idmap, dir, uname, uname_len, + mode, rdev, NULL, 0); + break; + default: + ni = __ntfs_create(idmap, dir, uname, uname_len, + mode, 0, NULL, 0); + } + + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) { + err = PTR_ERR(ni); + goto out; + } + + d_instantiate_new(dentry, VFS_I(ni)); +out: + return err; +} + +static int ntfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *vi = old_dentry->d_inode; + struct super_block *sb = vi->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + __le16 *uname = NULL; + int uname_len; + int err; + struct ntfs_inode *ni = NTFS_I(vi), *dir_ni = NTFS_I(dir); + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + err = -ENOMEM; + goto out; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ihold(vi); + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + err = __ntfs_link(NTFS_I(vi), NTFS_I(dir), uname, uname_len); + if (err) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + iput(vi); + pr_err("failed to create link, err = %d\n", err); + goto out; + } + + inode_inc_iversion(dir); + simple_inode_init_ts(dir); + + inode_inc_iversion(vi); + simple_inode_init_ts(vi); + + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + d_instantiate(dentry, vi); + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + +out: + kfree(uname); + return err; +} + +/* + * Inode operations for directories. + */ +const struct inode_operations ntfs_dir_inode_ops = { + .lookup = ntfs_lookup, /* VFS: Lookup directory. */ + .create = ntfs_create, + .unlink = ntfs_unlink, + .mkdir = ntfs_mkdir, + .rmdir = ntfs_rmdir, + .rename = ntfs_rename, + .get_acl = ntfs_get_acl, + .set_acl = ntfs_set_acl, + .listxattr = ntfs_listxattr, + .setattr = ntfs_setattr, + .getattr = ntfs_getattr, + .symlink = ntfs_symlink, + .mknod = ntfs_mknod, + .link = ntfs_link, +}; + +/* + * ntfs_get_parent - find the dentry of the parent of a given directory dentry + * @child_dent: dentry of the directory whose parent directory to find + * + * Find the dentry for the parent directory of the directory specified by the + * dentry @child_dent. This function is called from + * fs/exportfs/expfs.c::find_exported_dentry() which in turn is called from the + * default ->decode_fh() which is export_decode_fh() in the same file. + * + * Note: ntfs_get_parent() is called with @d_inode(child_dent)->i_mutex down. + * + * Return the dentry of the parent directory on success or the error code on + * error (IS_ERR() is true). + */ +static struct dentry *ntfs_get_parent(struct dentry *child_dent) +{ + struct inode *vi = d_inode(child_dent); + struct ntfs_inode *ni = NTFS_I(vi); + struct mft_record *mrec; + struct ntfs_attr_search_ctx *ctx; + struct attr_record *attr; + struct file_name_attr *fn; + unsigned long parent_ino; + int err; + + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + /* Get the mft record of the inode belonging to the child dentry. */ + mrec = map_mft_record(ni); + if (IS_ERR(mrec)) + return ERR_CAST(mrec); + /* Find the first file name attribute in the mft record. */ + ctx = ntfs_attr_get_search_ctx(ni, mrec); + if (unlikely(!ctx)) { + unmap_mft_record(ni); + return ERR_PTR(-ENOMEM); + } +try_next: + err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (unlikely(err)) { + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + if (err == -ENOENT) + ntfs_error(vi->i_sb, + "Inode 0x%llx does not have a file name attribute. Run chkdsk.", + ni->mft_no); + return ERR_PTR(err); + } + attr = ctx->attr; + if (unlikely(attr->non_resident)) + goto try_next; + fn = (struct file_name_attr *)((u8 *)attr + + le16_to_cpu(attr->data.resident.value_offset)); + if (unlikely((u8 *)fn + le32_to_cpu(attr->data.resident.value_length) > + (u8 *)attr + le32_to_cpu(attr->length))) + goto try_next; + /* Get the inode number of the parent directory. */ + parent_ino = MREF_LE(fn->parent_directory); + /* Release the search context and the mft record of the child. */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + + return d_obtain_alias(ntfs_iget(vi->i_sb, parent_ino)); +} + +static struct inode *ntfs_nfs_get_inode(struct super_block *sb, + u64 ino, u32 generation) +{ + struct inode *inode; + + inode = ntfs_iget(sb, ino); + if (!IS_ERR(inode)) { + if (inode->i_generation != generation) { + iput(inode); + inode = ERR_PTR(-ESTALE); + } + } + + return inode; +} + +static struct dentry *ntfs_fh_to_dentry(struct super_block *sb, struct fid *fid, + int fh_len, int fh_type) +{ + return generic_fh_to_dentry(sb, fid, fh_len, fh_type, + ntfs_nfs_get_inode); +} + +static struct dentry *ntfs_fh_to_parent(struct super_block *sb, struct fid *fid, + int fh_len, int fh_type) +{ + return generic_fh_to_parent(sb, fid, fh_len, fh_type, + ntfs_nfs_get_inode); +} + +/* + * Export operations allowing NFS exporting of mounted NTFS partitions. + */ +const struct export_operations ntfs_export_ops = { + .encode_fh = generic_encode_ino32_fh, + .get_parent = ntfs_get_parent, /* Find the parent of a given directory. */ + .fh_to_dentry = ntfs_fh_to_dentry, + .fh_to_parent = ntfs_fh_to_parent, +}; diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h new file mode 100644 index 000000000000..45064dbcc2e4 --- /dev/null +++ b/fs/ntfs/ntfs.h @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for NTFS Linux kernel driver. + * + * Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc. + * Copyright (C) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_H +#define _LINUX_NTFS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "volume.h" +#include "layout.h" +#include "inode.h" + +#ifdef pr_fmt +#undef pr_fmt +#endif + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/* + * Default pre-allocation size is optimize runlist merge overhead + * with small chunk size. + */ +#define NTFS_DEF_PREALLOC_SIZE 65536 + +/* + * The log2 of the standard number of clusters per compression block. + * A value of 4 corresponds to 16 clusters (1 << 4), which is the + * default chunk size used by NTFS LZNT1 compression. + */ +#define STANDARD_COMPRESSION_UNIT 4 + +/* + * The maximum cluster size (4KB) allowed for compression to be enabled. + * By design, NTFS does not support compression on volumes where the + * cluster size exceeds 4096 bytes. + */ +#define MAX_COMPRESSION_CLUSTER_SIZE 4096 + +#define NTFS_B_TO_CLU(vol, b) ((b) >> (vol)->cluster_size_bits) +#define NTFS_CLU_TO_B(vol, clu) ((u64)(clu) << (vol)->cluster_size_bits) +#define NTFS_B_TO_CLU_OFS(vol, clu) ((u64)(clu) & (vol)->cluster_size_mask) + +#define NTFS_MFT_NR_TO_CLU(vol, mft_no) (((u64)mft_no << (vol)->mft_record_size_bits) >> \ + (vol)->cluster_size_bits) +#define NTFS_MFT_NR_TO_PIDX(vol, mft_no) (mft_no >> (PAGE_SHIFT - \ + (vol)->mft_record_size_bits)) +#define NTFS_MFT_NR_TO_POFS(vol, mft_no) (((u64)mft_no << (vol)->mft_record_size_bits) & \ + ~PAGE_MASK) + +#define NTFS_PIDX_TO_BLK(vol, idx) (((u64)idx << PAGE_SHIFT) >> \ + ((vol)->sb)->s_blocksize_bits) +#define NTFS_PIDX_TO_CLU(vol, idx) (((u64)idx << PAGE_SHIFT) >> \ + (vol)->cluster_size_bits) +#define NTFS_CLU_TO_PIDX(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) >> \ + PAGE_SHIFT) +#define NTFS_CLU_TO_POFS(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) & \ + ~PAGE_MASK) + +#define NTFS_B_TO_SECTOR(vol, b) ((b) >> ((vol)->sb)->s_blocksize_bits) + +enum { + NTFS_BLOCK_SIZE = 512, + NTFS_BLOCK_SIZE_BITS = 9, + NTFS_SB_MAGIC = 0x5346544e, /* 'NTFS' */ + NTFS_MAX_NAME_LEN = 255, + NTFS_MAX_LABEL_LEN = 128, +}; + +enum { + CASE_SENSITIVE = 0, + IGNORE_CASE = 1, +}; + +/* + * Conversion helpers for NTFS units. + */ + +/* Convert bytes to cluster count */ +static inline u64 ntfs_bytes_to_cluster(const struct ntfs_volume *vol, + s64 bytes) +{ + return bytes >> vol->cluster_size_bits; +} + +/* Convert cluster count to bytes */ +static inline u64 ntfs_cluster_to_bytes(const struct ntfs_volume *vol, + u64 clusters) +{ + return clusters << vol->cluster_size_bits; +} + +/* Get the byte offset within a cluster from a linear byte address */ +static inline u64 ntfs_bytes_to_cluster_off(const struct ntfs_volume *vol, + u64 bytes) +{ + return bytes & vol->cluster_size_mask; +} + +/* Calculate the physical cluster number containing a specific MFT record. */ +static inline u64 ntfs_mft_no_to_cluster(const struct ntfs_volume *vol, + unsigned long mft_no) +{ + return ((u64)mft_no << vol->mft_record_size_bits) >> + vol->cluster_size_bits; +} + +/* Calculate the folio index where the MFT record resides. */ +static inline pgoff_t ntfs_mft_no_to_pidx(const struct ntfs_volume *vol, + unsigned long mft_no) +{ + return mft_no >> (PAGE_SHIFT - vol->mft_record_size_bits); +} + +/* Calculate the byte offset within a folio for an MFT record. */ +static inline u64 ntfs_mft_no_to_poff(const struct ntfs_volume *vol, + unsigned long mft_no) +{ + return ((u64)mft_no << vol->mft_record_size_bits) & ~PAGE_MASK; +} + +/* Convert folio index to cluster number. */ +static inline u64 ntfs_pidx_to_cluster(const struct ntfs_volume *vol, + pgoff_t idx) +{ + return ((u64)idx << PAGE_SHIFT) >> vol->cluster_size_bits; +} + +/* Convert cluster number to folio index. */ +static inline pgoff_t ntfs_cluster_to_pidx(const struct ntfs_volume *vol, + u64 clu) +{ + return (clu << vol->cluster_size_bits) >> PAGE_SHIFT; +} + +/* Get the byte offset within a folio from a cluster number */ +static inline u64 ntfs_cluster_to_poff(const struct ntfs_volume *vol, + u64 clu) +{ + return (clu << vol->cluster_size_bits) & ~PAGE_MASK; +} + +/* Convert byte offset to sector (block) number. */ +static inline sector_t ntfs_bytes_to_sector(const struct ntfs_volume *vol, + u64 bytes) +{ + return bytes >> vol->sb->s_blocksize_bits; +} + +/* Global variables. */ + +/* Slab caches (from super.c). */ +extern struct kmem_cache *ntfs_name_cache; +extern struct kmem_cache *ntfs_inode_cache; +extern struct kmem_cache *ntfs_big_inode_cache; +extern struct kmem_cache *ntfs_attr_ctx_cache; +extern struct kmem_cache *ntfs_index_ctx_cache; + +/* The various operations structs defined throughout the driver files. */ +extern const struct address_space_operations ntfs_aops; +extern const struct address_space_operations ntfs_mft_aops; + +extern const struct file_operations ntfs_file_ops; +extern const struct inode_operations ntfs_file_inode_ops; +extern const struct inode_operations ntfs_symlink_inode_operations; +extern const struct inode_operations ntfs_special_inode_operations; + +extern const struct file_operations ntfs_dir_ops; +extern const struct inode_operations ntfs_dir_inode_ops; + +extern const struct file_operations ntfs_empty_file_ops; +extern const struct inode_operations ntfs_empty_inode_ops; + +extern const struct export_operations ntfs_export_ops; + +/* + * NTFS_SB - return the ntfs volume given a vfs super block + * @sb: VFS super block + * + * NTFS_SB() returns the ntfs volume associated with the VFS super block @sb. + */ +static inline struct ntfs_volume *NTFS_SB(struct super_block *sb) +{ + return sb->s_fs_info; +} + +/* Declarations of functions and global variables. */ + +/* From fs/ntfs/compress.c */ +int ntfs_read_compressed_block(struct folio *folio); +int allocate_compression_buffers(void); +void free_compression_buffers(void); +int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, + struct iov_iter *from); + +/* From fs/ntfs/super.c */ +#define default_upcase_len 0x10000 +extern struct mutex ntfs_lock; + +struct option_t { + int val; + char *str; +}; +extern const struct option_t on_errors_arr[]; +int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags); +int ntfs_clear_volume_flags(struct ntfs_volume *vol, __le16 flags); +int ntfs_write_volume_label(struct ntfs_volume *vol, char *label); + +/* From fs/ntfs/mst.c */ +int post_read_mst_fixup(struct ntfs_record *b, const u32 size); +int pre_write_mst_fixup(struct ntfs_record *b, const u32 size); +void post_write_mst_fixup(struct ntfs_record *b); + +/* From fs/ntfs/unistr.c */ +bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len, + const __le16 *s2, size_t s2_len, + const u32 ic, + const __le16 *upcase, const u32 upcase_size); +int ntfs_collate_names(const __le16 *name1, const u32 name1_len, + const __le16 *name2, const u32 name2_len, + const int err_val, const u32 ic, + const __le16 *upcase, const u32 upcase_len); +int ntfs_ucsncmp(const __le16 *s1, const __le16 *s2, size_t n); +int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n, + const __le16 *upcase, const u32 upcase_size); +int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1, + const struct file_name_attr *file_name_attr2, + const int err_val, const u32 ic, + const __le16 *upcase, const u32 upcase_len); +int ntfs_nlstoucs(const struct ntfs_volume *vol, const char *ins, + const int ins_len, __le16 **outs, int max_name_len); +int ntfs_ucstonls(const struct ntfs_volume *vol, const __le16 *ins, + const int ins_len, unsigned char **outs, int outs_len); +__le16 *ntfs_ucsndup(const __le16 *s, u32 maxlen); +bool ntfs_names_are_equal(const __le16 *s1, size_t s1_len, + const __le16 *s2, size_t s2_len, + const u32 ic, + const __le16 *upcase, const u32 upcase_size); +int ntfs_force_shutdown(struct super_block *sb, u32 flags); +long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); +#ifdef CONFIG_COMPAT +long ntfs_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); +#endif + +/* From fs/ntfs/upcase.c */ +__le16 *generate_default_upcase(void); + +static inline int ntfs_ffs(int x) +{ + int r = 1; + + if (!x) + return 0; + if (!(x & 0xffff)) { + x >>= 16; + r += 16; + } + if (!(x & 0xff)) { + x >>= 8; + r += 8; + } + if (!(x & 0xf)) { + x >>= 4; + r += 4; + } + if (!(x & 3)) { + x >>= 2; + r += 2; + } + if (!(x & 1)) + r += 1; + return r; +} + +/* From fs/ntfs/bdev-io.c */ +int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t size); +int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size); + +#endif /* _LINUX_NTFS_H */ diff --git a/fs/ntfs/object_id.c b/fs/ntfs/object_id.c new file mode 100644 index 000000000000..89002c3759df --- /dev/null +++ b/fs/ntfs/object_id.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pocessing of object ids + * + * Part of this file is based on code from the NTFS-3G. + * + * Copyright (c) 2009-2019 Jean-Pierre Andre + * Copyright (c) 2026 LG Electronics Co., Ltd. + */ + +#include "ntfs.h" +#include "index.h" +#include "object_id.h" + +struct object_id_index_key { + union { + u32 alignment; + struct guid guid; + } object_id; +} __packed; + +struct object_id_index_data { + __le64 file_id; + struct guid birth_volume_id; + struct guid birth_object_id; + struct guid domain_id; +} __packed; + +/* Index entry in $Extend/$ObjId */ +struct object_id_index { + struct index_entry_header header; + struct object_id_index_key key; + struct object_id_index_data data; +} __packed; + +__le16 objid_index_name[] = {cpu_to_le16('$'), cpu_to_le16('O'), 0}; + +/* + * open_object_id_index - Open the $Extend/$ObjId file and its index + * @vol: NTFS volume structure + * + * Opens the $ObjId system file and retrieves its index context. + * + * Return: The index context if opened successfully, or NULL if an error + * occurred. + */ +static struct ntfs_index_context *open_object_id_index(struct ntfs_volume *vol) +{ + struct inode *dir_vi, *vi; + struct ntfs_inode *dir_ni; + struct ntfs_index_context *xo = NULL; + struct ntfs_name *name = NULL; + u64 mref; + int uname_len; + __le16 *uname; + + uname_len = ntfs_nlstoucs(vol, "$ObjId", 6, &uname, + NTFS_MAX_NAME_LEN); + if (uname_len < 0) + return NULL; + + /* do not use path_name_to inode - could reopen root */ + dir_vi = ntfs_iget(vol->sb, FILE_Extend); + if (IS_ERR(dir_vi)) { + kmem_cache_free(ntfs_name_cache, uname); + return NULL; + } + dir_ni = NTFS_I(dir_vi); + + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT); + mref = ntfs_lookup_inode_by_name(dir_ni, uname, uname_len, &name); + mutex_unlock(&dir_ni->mrec_lock); + kfree(name); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR_MREF(mref)) + goto put_dir_vi; + + vi = ntfs_iget(vol->sb, MREF(mref)); + if (IS_ERR(vi)) + goto put_dir_vi; + + xo = ntfs_index_ctx_get(NTFS_I(vi), objid_index_name, 2); + if (!xo) + iput(vi); +put_dir_vi: + iput(dir_vi); + return xo; +} + + +/* + * remove_object_id_index - Remove an object id index entry if attribute present + * @ni: NTFS inode structure containing the attribute + * @xo: Index context for the object id index + * + * Reads the existing object ID attribute and removes it from the index. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int remove_object_id_index(struct ntfs_inode *ni, struct ntfs_index_context *xo) +{ + struct object_id_index_key key = {0}; + s64 size; + + if (ni->data_size == 0) + return -ENODATA; + + /* read the existing object id attribute */ + size = ntfs_inode_attr_pread(VFS_I(ni), 0, sizeof(struct guid), + (char *)&key); + if (size != sizeof(struct guid)) + return -ENODATA; + + if (!ntfs_index_lookup(&key, sizeof(struct object_id_index_key), xo)) + return ntfs_index_rm(xo); + + return 0; +} + +/* + * ntfs_delete_object_id_index - Delete an object_id index entry + * @ni: NTFS inode structure + * + * Opens the object ID index and removes the entry corresponding to the inode. + * + * Return: 0 on success, or a negative error code on failure. + */ +int ntfs_delete_object_id_index(struct ntfs_inode *ni) +{ + struct ntfs_index_context *xo; + struct ntfs_inode *xoni; + struct inode *attr_vi; + int ret = 0; + + attr_vi = ntfs_attr_iget(VFS_I(ni), AT_OBJECT_ID, AT_UNNAMED, 0); + if (IS_ERR(attr_vi)) + return PTR_ERR(attr_vi); + + /* + * read the existing object id and un-index it + */ + xo = open_object_id_index(ni->vol); + if (xo) { + xoni = xo->idx_ni; + mutex_lock_nested(&xoni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT); + ret = remove_object_id_index(NTFS_I(attr_vi), xo); + if (!ret) { + ntfs_index_entry_mark_dirty(xo); + mark_mft_record_dirty(xoni); + } + ntfs_index_ctx_put(xo); + mutex_unlock(&xoni->mrec_lock); + iput(VFS_I(xoni)); + } + + iput(attr_vi); + return ret; +} diff --git a/fs/ntfs/object_id.h b/fs/ntfs/object_id.h new file mode 100644 index 000000000000..e4b8e529d5bc --- /dev/null +++ b/fs/ntfs/object_id.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2008-2021 Jean-Pierre Andre + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_OBJECT_ID_H +#define _LINUX_NTFS_OBJECT_ID_H + +extern __le16 objid_index_name[]; + +int ntfs_delete_object_id_index(struct ntfs_inode *ni); + +#endif /* _LINUX_NTFS_OBJECT_ID_H */ diff --git a/fs/ntfs/quota.c b/fs/ntfs/quota.c new file mode 100644 index 000000000000..b443243b58fb --- /dev/null +++ b/fs/ntfs/quota.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel quota ($Quota) handling. + * + * Copyright (c) 2004 Anton Altaparmakov + */ + +#include "index.h" +#include "quota.h" +#include "debug.h" +#include "ntfs.h" + +/* + * ntfs_mark_quotas_out_of_date - mark the quotas out of date on an ntfs volume + * @vol: ntfs volume on which to mark the quotas out of date + * + * Mark the quotas out of date on the ntfs volume @vol and return 'true' on + * success and 'false' on error. + */ +bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol) +{ + struct ntfs_index_context *ictx; + struct quota_control_entry *qce; + const __le32 qid = QUOTA_DEFAULTS_ID; + int err; + + ntfs_debug("Entering."); + if (NVolQuotaOutOfDate(vol)) + goto done; + if (!vol->quota_ino || !vol->quota_q_ino) { + ntfs_error(vol->sb, "Quota inodes are not open."); + return false; + } + inode_lock(vol->quota_q_ino); + ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), I30, 4); + if (!ictx) { + ntfs_error(vol->sb, "Failed to get index context."); + goto err_out; + } + err = ntfs_index_lookup(&qid, sizeof(qid), ictx); + if (err) { + if (err == -ENOENT) + ntfs_error(vol->sb, "Quota defaults entry is not present."); + else + ntfs_error(vol->sb, "Lookup of quota defaults entry failed."); + goto err_out; + } + if (ictx->data_len < offsetof(struct quota_control_entry, sid)) { + ntfs_error(vol->sb, "Quota defaults entry size is invalid. Run chkdsk."); + goto err_out; + } + qce = (struct quota_control_entry *)ictx->data; + if (le32_to_cpu(qce->version) != QUOTA_VERSION) { + ntfs_error(vol->sb, + "Quota defaults entry version 0x%x is not supported.", + le32_to_cpu(qce->version)); + goto err_out; + } + ntfs_debug("Quota defaults flags = 0x%x.", le32_to_cpu(qce->flags)); + /* If quotas are already marked out of date, no need to do anything. */ + if (qce->flags & QUOTA_FLAG_OUT_OF_DATE) + goto set_done; + /* + * If quota tracking is neither requested, nor enabled and there are no + * pending deletes, no need to mark the quotas out of date. + */ + if (!(qce->flags & (QUOTA_FLAG_TRACKING_ENABLED | + QUOTA_FLAG_TRACKING_REQUESTED | + QUOTA_FLAG_PENDING_DELETES))) + goto set_done; + /* + * Set the QUOTA_FLAG_OUT_OF_DATE bit thus marking quotas out of date. + * This is verified on WinXP to be sufficient to cause windows to + * rescan the volume on boot and update all quota entries. + */ + qce->flags |= QUOTA_FLAG_OUT_OF_DATE; + /* Ensure the modified flags are written to disk. */ + ntfs_index_entry_mark_dirty(ictx); +set_done: + ntfs_index_ctx_put(ictx); + inode_unlock(vol->quota_q_ino); + /* + * We set the flag so we do not try to mark the quotas out of date + * again on remount. + */ + NVolSetQuotaOutOfDate(vol); +done: + ntfs_debug("Done."); + return true; +err_out: + if (ictx) + ntfs_index_ctx_put(ictx); + inode_unlock(vol->quota_q_ino); + return false; +} diff --git a/fs/ntfs/quota.h b/fs/ntfs/quota.h new file mode 100644 index 000000000000..4b7322661a32 --- /dev/null +++ b/fs/ntfs/quota.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for NTFS kernel quota ($Quota) handling. + * + * Copyright (c) 2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_QUOTA_H +#define _LINUX_NTFS_QUOTA_H + +#include "volume.h" + +bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol); + +#endif /* _LINUX_NTFS_QUOTA_H */ diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c new file mode 100644 index 000000000000..8f60ec6f66c1 --- /dev/null +++ b/fs/ntfs/reparse.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Processing of reparse points + * + * Part of this file is based on code from the NTFS-3G. + * + * Copyright (c) 2008-2021 Jean-Pierre Andre + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include "ntfs.h" +#include "layout.h" +#include "attrib.h" +#include "inode.h" +#include "dir.h" +#include "volume.h" +#include "mft.h" +#include "index.h" +#include "lcnalloc.h" +#include "reparse.h" + +struct wsl_link_reparse_data { + __le32 type; + char link[]; +}; + +/* Index entry in $Extend/$Reparse */ +struct reparse_index { + struct index_entry_header header; + struct reparse_index_key key; + __le32 filling; +}; + +__le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0}; + + +/* + * Check if the reparse point attribute buffer is valid. + * Returns true if valid, false otherwise. + */ +static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni, + const struct reparse_point *reparse_attr, size_t size) +{ + size_t expected; + + if (!ni || !reparse_attr) + return false; + + /* Minimum size must cover reparse_point header */ + if (size < sizeof(struct reparse_point)) + return false; + + /* Reserved zero tag is invalid */ + if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO) + return false; + + /* Calculate expected total size */ + expected = sizeof(struct reparse_point) + + le16_to_cpu(reparse_attr->reparse_data_length); + + /* Add GUID size for non-Microsoft tags */ + if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT)) + expected += sizeof(struct guid); + + /* Buffer must exactly match the expected size */ + return expected == size; +} + +/* + * Do some sanity checks on reparse data + * + * Microsoft reparse points have an 8-byte header whereas + * non-Microsoft reparse points have a 24-byte header. In each case, + * 'reparse_data_length' must equal the number of non-header bytes. + * + * If the reparse data looks like a junction point or symbolic + * link, more checks can be done. + */ +static bool valid_reparse_data(struct ntfs_inode *ni, + const struct reparse_point *reparse_attr, size_t size) +{ + const struct wsl_link_reparse_data *wsl_reparse_data = + (const struct wsl_link_reparse_data *)reparse_attr->reparse_data; + unsigned int data_len = le16_to_cpu(reparse_attr->reparse_data_length); + + if (ntfs_is_valid_reparse_buffer(ni, reparse_attr, size) == false) + return false; + + switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_LX_SYMLINK: + if (data_len <= sizeof(wsl_reparse_data->type) || + wsl_reparse_data->type != cpu_to_le32(2)) + return false; + break; + case IO_REPARSE_TAG_AF_UNIX: + case IO_REPARSE_TAG_LX_FIFO: + case IO_REPARSE_TAG_LX_CHR: + case IO_REPARSE_TAG_LX_BLK: + if (data_len || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) + return false; + } + + return true; +} + +static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr) +{ + unsigned int mode = 0; + + switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_SYMLINK: + case IO_REPARSE_TAG_LX_SYMLINK: + mode = S_IFLNK; + break; + case IO_REPARSE_TAG_AF_UNIX: + mode = S_IFSOCK; + break; + case IO_REPARSE_TAG_LX_FIFO: + mode = S_IFIFO; + break; + case IO_REPARSE_TAG_LX_CHR: + mode = S_IFCHR; + break; + case IO_REPARSE_TAG_LX_BLK: + mode = S_IFBLK; + } + + return mode; +} + +/* + * Get the target for symbolic link + */ +unsigned int ntfs_make_symlink(struct ntfs_inode *ni) +{ + s64 attr_size = 0; + unsigned int lth; + struct reparse_point *reparse_attr; + struct wsl_link_reparse_data *wsl_link_data; + unsigned int mode = 0; + + reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0, + &attr_size); + if (reparse_attr && attr_size && + valid_reparse_data(ni, reparse_attr, attr_size)) { + switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_LX_SYMLINK: + wsl_link_data = + (struct wsl_link_reparse_data *)reparse_attr->reparse_data; + if (wsl_link_data->type == cpu_to_le32(2)) { + lth = le16_to_cpu(reparse_attr->reparse_data_length) - + sizeof(wsl_link_data->type); + ni->target = kvzalloc(lth + 1, GFP_NOFS); + if (ni->target) { + memcpy(ni->target, wsl_link_data->link, lth); + ni->target[lth] = 0; + mode = ntfs_reparse_tag_mode(reparse_attr); + } + } + break; + default: + mode = ntfs_reparse_tag_mode(reparse_attr); + } + } else + ni->flags &= ~FILE_ATTR_REPARSE_POINT; + + if (reparse_attr) + kvfree(reparse_attr); + + return mode; +} + +unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref) +{ + s64 attr_size = 0; + struct reparse_point *reparse_attr; + unsigned int dt_type = DT_UNKNOWN; + struct inode *vi; + + vi = ntfs_iget(vol->sb, mref); + if (IS_ERR(vi)) + return PTR_ERR(vi); + + reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi), + AT_REPARSE_POINT, NULL, 0, &attr_size); + + if (reparse_attr && attr_size) { + switch (reparse_attr->reparse_tag) { + case IO_REPARSE_TAG_SYMLINK: + case IO_REPARSE_TAG_LX_SYMLINK: + dt_type = DT_LNK; + break; + case IO_REPARSE_TAG_AF_UNIX: + dt_type = DT_SOCK; + break; + case IO_REPARSE_TAG_LX_FIFO: + dt_type = DT_FIFO; + break; + case IO_REPARSE_TAG_LX_CHR: + dt_type = DT_CHR; + break; + case IO_REPARSE_TAG_LX_BLK: + dt_type = DT_BLK; + } + } + + if (reparse_attr) + kvfree(reparse_attr); + + iput(vi); + return dt_type; +} + +/* + * Set the index for new reparse data + */ +static int set_reparse_index(struct ntfs_inode *ni, struct ntfs_index_context *xr, + __le32 reparse_tag) +{ + struct reparse_index indx; + u64 file_id_cpu; + __le64 file_id; + + file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no); + file_id = cpu_to_le64(file_id_cpu); + indx.header.data.vi.data_offset = + cpu_to_le16(sizeof(struct index_entry_header) + sizeof(struct reparse_index_key)); + indx.header.data.vi.data_length = 0; + indx.header.data.vi.reservedV = 0; + indx.header.length = cpu_to_le16(sizeof(struct reparse_index)); + indx.header.key_length = cpu_to_le16(sizeof(struct reparse_index_key)); + indx.header.flags = 0; + indx.header.reserved = 0; + indx.key.reparse_tag = reparse_tag; + /* danger on processors which require proper alignment! */ + memcpy(&indx.key.file_id, &file_id, 8); + indx.filling = 0; + ntfs_index_ctx_reinit(xr); + + return ntfs_ie_add(xr, (struct index_entry *)&indx); +} + +/* + * Remove a reparse data index entry if attribute present + */ +static int remove_reparse_index(struct inode *rp, struct ntfs_index_context *xr, + __le32 *preparse_tag) +{ + struct reparse_index_key key; + u64 file_id_cpu; + __le64 file_id; + s64 size; + struct ntfs_inode *ni = NTFS_I(rp); + int err = 0, ret = ni->data_size; + + if (ni->data_size == 0) + return 0; + + /* read the existing reparse_tag */ + size = ntfs_inode_attr_pread(rp, 0, 4, (char *)preparse_tag); + if (size != 4) + return -ENODATA; + + file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no); + file_id = cpu_to_le64(file_id_cpu); + key.reparse_tag = *preparse_tag; + /* danger on processors which require proper alignment! */ + memcpy(&key.file_id, &file_id, 8); + if (!ntfs_index_lookup(&key, sizeof(struct reparse_index_key), xr)) { + err = ntfs_index_rm(xr); + if (err) + ret = err; + } + return ret; +} + +/* + * Open the $Extend/$Reparse file and its index + */ +static struct ntfs_index_context *open_reparse_index(struct ntfs_volume *vol) +{ + struct ntfs_index_context *xr = NULL; + u64 mref; + __le16 *uname; + struct ntfs_name *name = NULL; + int uname_len; + struct inode *vi, *dir_vi; + + /* do not use path_name_to inode - could reopen root */ + dir_vi = ntfs_iget(vol->sb, FILE_Extend); + if (IS_ERR(dir_vi)) + return NULL; + + uname_len = ntfs_nlstoucs(vol, "$Reparse", 8, &uname, + NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + iput(dir_vi); + return NULL; + } + + mutex_lock_nested(&NTFS_I(dir_vi)->mrec_lock, NTFS_EXTEND_MUTEX_PARENT); + mref = ntfs_lookup_inode_by_name(NTFS_I(dir_vi), uname, uname_len, + &name); + mutex_unlock(&NTFS_I(dir_vi)->mrec_lock); + kfree(name); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR_MREF(mref)) + goto put_dir_vi; + + vi = ntfs_iget(vol->sb, MREF(mref)); + if (IS_ERR(vi)) + goto put_dir_vi; + + xr = ntfs_index_ctx_get(NTFS_I(vi), reparse_index_name, 2); + if (!xr) + iput(vi); +put_dir_vi: + iput(dir_vi); + return xr; +} + + +/* + * Update the reparse data and index + * + * The reparse data attribute should have been created, and + * an existing index is expected if there is an existing value. + * + */ +static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context *xr, + char *value, size_t size) +{ + struct inode *rp_inode; + int err = 0; + s64 written; + int oldsize; + __le32 reparse_tag; + struct ntfs_inode *rp_ni; + + rp_inode = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0); + if (IS_ERR(rp_inode)) + return -EINVAL; + rp_ni = NTFS_I(rp_inode); + + /* remove the existing reparse data */ + oldsize = remove_reparse_index(rp_inode, xr, &reparse_tag); + if (oldsize < 0) { + err = oldsize; + goto put_rp_inode; + } + + /* overwrite value if any */ + written = ntfs_inode_attr_pwrite(rp_inode, 0, size, value, false); + if (written != size) { + ntfs_error(ni->vol->sb, "Failed to update reparse data\n"); + err = -EIO; + goto put_rp_inode; + } + + if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) && + oldsize > 0) { + /* + * If cannot index, try to remove the reparse + * data and log the error. There will be an + * inconsistency if removal fails. + */ + ntfs_attr_rm(rp_ni); + ntfs_error(ni->vol->sb, + "Failed to index reparse data. Possible corruption.\n"); + } + + mark_mft_record_dirty(ni); +put_rp_inode: + iput(rp_inode); + + return err; +} + +/* + * Delete a reparse index entry + */ +int ntfs_delete_reparse_index(struct ntfs_inode *ni) +{ + struct inode *vi; + struct ntfs_index_context *xr; + struct ntfs_inode *xrni; + __le32 reparse_tag; + int err = 0; + + if (!(ni->flags & FILE_ATTR_REPARSE_POINT)) + return 0; + + vi = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0); + if (IS_ERR(vi)) + return PTR_ERR(vi); + + /* + * read the existing reparse data (the tag is enough) + * and un-index it + */ + xr = open_reparse_index(ni->vol); + if (xr) { + xrni = xr->idx_ni; + mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT); + err = remove_reparse_index(vi, xr, &reparse_tag); + if (err < 0) { + ntfs_index_ctx_put(xr); + mutex_unlock(&xrni->mrec_lock); + iput(VFS_I(xrni)); + goto out; + } + mark_mft_record_dirty(xrni); + ntfs_index_ctx_put(xr); + mutex_unlock(&xrni->mrec_lock); + iput(VFS_I(xrni)); + } + + ni->flags &= ~FILE_ATTR_REPARSE_POINT; + NInoSetFileNameDirty(ni); + mark_mft_record_dirty(ni); + +out: + iput(vi); + return err; +} + +/* + * Set the reparse data from an extended attribute + */ +static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t size) +{ + int err = 0; + struct ntfs_inode *xrni; + struct ntfs_index_context *xr; + + if (!ni) + return -EINVAL; + + /* + * reparse data compatibily with EA is not checked + * any more, it is required by Windows 10, but may + * lead to problems with earlier versions. + */ + if (valid_reparse_data(ni, (const struct reparse_point *)value, size) == false) + return -EINVAL; + + xr = open_reparse_index(ni->vol); + if (!xr) + return -EINVAL; + xrni = xr->idx_ni; + + if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) { + struct reparse_point rp = {0, }; + + /* + * no reparse data attribute : add one, + * apparently, this does not feed the new value in + * Note : NTFS version must be >= 3 + */ + if (ni->vol->major_ver < 3) { + err = -EOPNOTSUPP; + ntfs_index_ctx_put(xr); + goto out; + } + + err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, (u8 *)&rp, sizeof(rp)); + if (err) { + ntfs_index_ctx_put(xr); + goto out; + } + ni->flags |= FILE_ATTR_REPARSE_POINT; + NInoSetFileNameDirty(ni); + mark_mft_record_dirty(ni); + } + + /* update value and index */ + mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT); + err = update_reparse_data(ni, xr, value, size); + if (err) { + ni->flags &= ~FILE_ATTR_REPARSE_POINT; + NInoSetFileNameDirty(ni); + mark_mft_record_dirty(ni); + } + ntfs_index_ctx_put(xr); + mutex_unlock(&xrni->mrec_lock); + +out: + if (!err) + mark_mft_record_dirty(xrni); + iput(VFS_I(xrni)); + + return err; +} + +/* + * Set reparse data for a WSL type symlink + */ +int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni, + const __le16 *target, int target_len) +{ + int err = 0; + int len; + int reparse_len; + unsigned char *utarget = NULL; + struct reparse_point *reparse; + struct wsl_link_reparse_data *data; + + utarget = (char *)NULL; + len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0); + if (len <= 0) + return -EINVAL; + + reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + len; + reparse = kvzalloc(reparse_len, GFP_NOFS); + if (!reparse) { + err = -ENOMEM; + kvfree(utarget); + } else { + data = (struct wsl_link_reparse_data *)reparse->reparse_data; + reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; + reparse->reparse_data_length = + cpu_to_le16(sizeof(data->type) + len); + reparse->reserved = 0; + data->type = cpu_to_le32(2); + memcpy(data->link, utarget, len); + err = ntfs_set_ntfs_reparse_data(ni, + (char *)reparse, reparse_len); + kvfree(reparse); + if (!err) + ni->target = utarget; + } + return err; +} + +/* + * Set reparse data for a WSL special file other than a symlink + * (socket, fifo, character or block device) + */ +int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode) +{ + int err; + int len; + int reparse_len; + __le32 reparse_tag; + struct reparse_point *reparse; + + len = 0; + if (S_ISSOCK(mode)) + reparse_tag = IO_REPARSE_TAG_AF_UNIX; + else if (S_ISFIFO(mode)) + reparse_tag = IO_REPARSE_TAG_LX_FIFO; + else if (S_ISCHR(mode)) + reparse_tag = IO_REPARSE_TAG_LX_CHR; + else if (S_ISBLK(mode)) + reparse_tag = IO_REPARSE_TAG_LX_BLK; + else + return -EOPNOTSUPP; + + reparse_len = sizeof(struct reparse_point) + len; + reparse = kvzalloc(reparse_len, GFP_NOFS); + if (!reparse) + err = -ENOMEM; + else { + reparse->reparse_tag = reparse_tag; + reparse->reparse_data_length = cpu_to_le16(len); + reparse->reserved = cpu_to_le16(0); + err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, + reparse_len); + kvfree(reparse); + } + + return err; +} diff --git a/fs/ntfs/reparse.h b/fs/ntfs/reparse.h new file mode 100644 index 000000000000..28da40257f2a --- /dev/null +++ b/fs/ntfs/reparse.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2008-2021 Jean-Pierre Andre + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_REPARSE_H +#define _LINUX_NTFS_REPARSE_H + +extern __le16 reparse_index_name[]; + +unsigned int ntfs_make_symlink(struct ntfs_inode *ni); +unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref); +int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni, + const __le16 *target, int target_len); +int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode); +int ntfs_delete_reparse_index(struct ntfs_inode *ni); +int ntfs_remove_ntfs_reparse_data(struct ntfs_inode *ni); + +#endif /* _LINUX_NTFS_REPARSE_H */ diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c new file mode 100644 index 000000000000..b213b4976d2b --- /dev/null +++ b/fs/ntfs/runlist.c @@ -0,0 +1,2066 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS runlist handling code. + * + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + * + * Part of this file is based on code from the NTFS-3G. + * and is copyrighted by the respective authors below: + * Copyright (c) 2002-2005 Anton Altaparmakov + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2002-2008 Szabolcs Szakacsits + * Copyright (c) 2004 Yura Pakhuchiy + * Copyright (c) 2007-2022 Jean-Pierre Andre + */ + +#include "ntfs.h" +#include "attrib.h" + +/* + * ntfs_rl_mm - runlist memmove + * @base: base runlist array + * @dst: destination index in @base + * @src: source index in @base + * @size: number of elements to move + * + * It is up to the caller to serialize access to the runlist @base. + */ +static inline void ntfs_rl_mm(struct runlist_element *base, int dst, int src, int size) +{ + if (likely((dst != src) && (size > 0))) + memmove(base + dst, base + src, size * sizeof(*base)); +} + +/* + * ntfs_rl_mc - runlist memory copy + * @dstbase: destination runlist array + * @dst: destination index in @dstbase + * @srcbase: source runlist array + * @src: source index in @srcbase + * @size: number of elements to copy + * + * It is up to the caller to serialize access to the runlists @dstbase and + * @srcbase. + */ +static inline void ntfs_rl_mc(struct runlist_element *dstbase, int dst, + struct runlist_element *srcbase, int src, int size) +{ + if (likely(size > 0)) + memcpy(dstbase + dst, srcbase + src, size * sizeof(*dstbase)); +} + +/* + * ntfs_rl_realloc - Reallocate memory for runlists + * @rl: original runlist + * @old_size: number of runlist elements in the original runlist @rl + * @new_size: number of runlist elements we need space for + * + * As the runlists grow, more memory will be required. To prevent the + * kernel having to allocate and reallocate large numbers of small bits of + * memory, this function returns an entire page of memory. + * + * It is up to the caller to serialize access to the runlist @rl. + * + * N.B. If the new allocation doesn't require a different number of pages in + * memory, the function will return the original pointer. + * + * On success, return a pointer to the newly allocated, or recycled, memory. + * On error, return -errno. + */ +struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl, + int old_size, int new_size) +{ + struct runlist_element *new_rl; + + old_size = old_size * sizeof(*rl); + new_size = new_size * sizeof(*rl); + if (old_size == new_size) + return rl; + + new_rl = kvzalloc(new_size, GFP_NOFS); + if (unlikely(!new_rl)) + return ERR_PTR(-ENOMEM); + + if (likely(rl != NULL)) { + if (unlikely(old_size > new_size)) + old_size = new_size; + memcpy(new_rl, rl, old_size); + kvfree(rl); + } + return new_rl; +} + +/* + * ntfs_rl_realloc_nofail - Reallocate memory for runlists + * @rl: original runlist + * @old_size: number of runlist elements in the original runlist @rl + * @new_size: number of runlist elements we need space for + * + * As the runlists grow, more memory will be required. To prevent the + * kernel having to allocate and reallocate large numbers of small bits of + * memory, this function returns an entire page of memory. + * + * This function guarantees that the allocation will succeed. It will sleep + * for as long as it takes to complete the allocation. + * + * It is up to the caller to serialize access to the runlist @rl. + * + * N.B. If the new allocation doesn't require a different number of pages in + * memory, the function will return the original pointer. + * + * On success, return a pointer to the newly allocated, or recycled, memory. + * On error, return -errno. + */ +static inline struct runlist_element *ntfs_rl_realloc_nofail(struct runlist_element *rl, + int old_size, int new_size) +{ + struct runlist_element *new_rl; + + old_size = old_size * sizeof(*rl); + new_size = new_size * sizeof(*rl); + if (old_size == new_size) + return rl; + + new_rl = kvmalloc(new_size, GFP_NOFS | __GFP_NOFAIL); + if (likely(rl != NULL)) { + if (unlikely(old_size > new_size)) + old_size = new_size; + memcpy(new_rl, rl, old_size); + kvfree(rl); + } + return new_rl; +} + +/* + * ntfs_are_rl_mergeable - test if two runlists can be joined together + * @dst: original runlist + * @src: new runlist to test for mergeability with @dst + * + * Test if two runlists can be joined together. For this, their VCNs and LCNs + * must be adjacent. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * Return: true Success, the runlists can be merged. + * false Failure, the runlists cannot be merged. + */ +static inline bool ntfs_are_rl_mergeable(struct runlist_element *dst, + struct runlist_element *src) +{ + /* We can merge unmapped regions even if they are misaligned. */ + if ((dst->lcn == LCN_RL_NOT_MAPPED) && (src->lcn == LCN_RL_NOT_MAPPED)) + return true; + /* If the runs are misaligned, we cannot merge them. */ + if ((dst->vcn + dst->length) != src->vcn) + return false; + /* If both runs are non-sparse and contiguous, we can merge them. */ + if ((dst->lcn >= 0) && (src->lcn >= 0) && + ((dst->lcn + dst->length) == src->lcn)) + return true; + /* If we are merging two holes, we can merge them. */ + if ((dst->lcn == LCN_HOLE) && (src->lcn == LCN_HOLE)) + return true; + /* If we are merging two dealloc, we can merge them. */ + if ((dst->lcn == LCN_DELALLOC) && (src->lcn == LCN_DELALLOC)) + return true; + /* Cannot merge. */ + return false; +} + +/* + * __ntfs_rl_merge - merge two runlists without testing if they can be merged + * @dst: original, destination runlist + * @src: new runlist to merge with @dst + * + * Merge the two runlists, writing into the destination runlist @dst. The + * caller must make sure the runlists can be merged or this will corrupt the + * destination runlist. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + */ +static inline void __ntfs_rl_merge(struct runlist_element *dst, struct runlist_element *src) +{ + dst->length += src->length; +} + +/* + * ntfs_rl_append - append a runlist after a given element + * @dst: destination runlist to append to + * @dsize: number of elements in @dst + * @src: source runlist to append from + * @ssize: number of elements in @src + * @loc: index in @dst after which to append @src + * @new_size: on success, set to the new combined size + * + * Append the runlist @src after element @loc in @dst. Merge the right end of + * the new runlist, if necessary. Adjust the size of the hole before the + * appended runlist. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. + */ +static inline struct runlist_element *ntfs_rl_append(struct runlist_element *dst, + int dsize, struct runlist_element *src, int ssize, int loc, + size_t *new_size) +{ + bool right = false; /* Right end of @src needs merging. */ + int marker; /* End of the inserted runs. */ + + /* First, check if the right hand end needs merging. */ + if ((loc + 1) < dsize) + right = ntfs_are_rl_mergeable(src + ssize - 1, dst + loc + 1); + + /* Space required: @dst size + @src size, less one if we merged. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - right); + if (IS_ERR(dst)) + return dst; + + *new_size = dsize + ssize - right; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* First, merge the right hand end, if necessary. */ + if (right) + __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + + /* First run after the @src runs that have been inserted. */ + marker = loc + ssize + 1; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, marker, loc + 1 + right, dsize - (loc + 1 + right)); + ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + + /* Adjust the size of the preceding hole. */ + dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; + + /* We may have changed the length of the file, so fix the end marker */ + if (dst[marker].lcn == LCN_ENOENT) + dst[marker].vcn = dst[marker - 1].vcn + dst[marker - 1].length; + + return dst; +} + +/* + * ntfs_rl_insert - insert a runlist into another + * @dst: destination runlist to insert into + * @dsize: number of elements in @dst + * @src: source runlist to insert from + * @ssize: number of elements in @src + * @loc: index in @dst at which to insert @src + * @new_size: on success, set to the new combined size + * + * Insert the runlist @src before element @loc in the runlist @dst. Merge the + * left end of the new runlist, if necessary. Adjust the size of the hole + * after the inserted runlist. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. + */ +static inline struct runlist_element *ntfs_rl_insert(struct runlist_element *dst, + int dsize, struct runlist_element *src, int ssize, int loc, + size_t *new_size) +{ + bool left = false; /* Left end of @src needs merging. */ + bool disc = false; /* Discontinuity between @dst and @src. */ + int marker; /* End of the inserted runs. */ + + /* + * disc => Discontinuity between the end of @dst and the start of @src. + * This means we might need to insert a "not mapped" run. + */ + if (loc == 0) + disc = (src[0].vcn > 0); + else { + s64 merged_length; + + left = ntfs_are_rl_mergeable(dst + loc - 1, src); + + merged_length = dst[loc - 1].length; + if (left) + merged_length += src->length; + + disc = (src[0].vcn > dst[loc - 1].vcn + merged_length); + } + /* + * Space required: @dst size + @src size, less one if we merged, plus + * one if there was a discontinuity. + */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - left + disc); + if (IS_ERR(dst)) + return dst; + + *new_size = dsize + ssize - left + disc; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlist. + */ + if (left) + __ntfs_rl_merge(dst + loc - 1, src); + /* + * First run after the @src runs that have been inserted. + * Nominally, @marker equals @loc + @ssize, i.e. location + number of + * runs in @src. However, if @left, then the first run in @src has + * been merged with one in @dst. And if @disc, then @dst and @src do + * not meet and we need an extra run to fill the gap. + */ + marker = loc + ssize - left + disc; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, marker, loc, dsize - loc); + ntfs_rl_mc(dst, loc + disc, src, left, ssize - left); + + /* Adjust the VCN of the first run after the insertion... */ + dst[marker].vcn = dst[marker - 1].vcn + dst[marker - 1].length; + /* ... and the length. */ + if (dst[marker].lcn == LCN_HOLE || dst[marker].lcn == LCN_RL_NOT_MAPPED || + dst[marker].lcn == LCN_DELALLOC) + dst[marker].length = dst[marker + 1].vcn - dst[marker].vcn; + + /* Writing beyond the end of the file and there is a discontinuity. */ + if (disc) { + if (loc > 0) { + dst[loc].vcn = dst[loc - 1].vcn + dst[loc - 1].length; + dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; + } else { + dst[loc].vcn = 0; + dst[loc].length = dst[loc + 1].vcn; + } + dst[loc].lcn = LCN_RL_NOT_MAPPED; + } + return dst; +} + +/* + * ntfs_rl_replace - overwrite a runlist element with another runlist + * @dst: destination runlist to replace in + * @dsize: number of elements in @dst + * @src: source runlist to replace with + * @ssize: number of elements in @src + * @loc: index in @dst to replace + * @new_size: on success, set to the new combined size + * + * Replace the runlist element @dst at @loc with @src. Merge the left and + * right ends of the inserted runlist, if necessary. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. + */ +static inline struct runlist_element *ntfs_rl_replace(struct runlist_element *dst, + int dsize, struct runlist_element *src, int ssize, int loc, + size_t *new_size) +{ + int delta; + bool left = false; /* Left end of @src needs merging. */ + bool right = false; /* Right end of @src needs merging. */ + int tail; /* Start of tail of @dst. */ + int marker; /* End of the inserted runs. */ + + /* First, see if the left and right ends need merging. */ + if ((loc + 1) < dsize) + right = ntfs_are_rl_mergeable(src + ssize - 1, dst + loc + 1); + if (loc > 0) + left = ntfs_are_rl_mergeable(dst + loc - 1, src); + /* + * Allocate some space. We will need less if the left, right, or both + * ends get merged. The -1 accounts for the run being replaced. + */ + delta = ssize - 1 - left - right; + if (delta > 0) { + dst = ntfs_rl_realloc(dst, dsize, dsize + delta); + if (IS_ERR(dst)) + return dst; + } + + *new_size = dsize + delta; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* First, merge the left and right ends, if necessary. */ + if (right) + __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + if (left) + __ntfs_rl_merge(dst + loc - 1, src); + /* + * Offset of the tail of @dst. This needs to be moved out of the way + * to make space for the runs to be copied from @src, i.e. the first + * run of the tail of @dst. + * Nominally, @tail equals @loc + 1, i.e. location, skipping the + * replaced run. However, if @right, then one of @dst's runs is + * already merged into @src. + */ + tail = loc + right + 1; + /* + * First run after the @src runs that have been inserted, i.e. where + * the tail of @dst needs to be moved to. + * Nominally, @marker equals @loc + @ssize, i.e. location + number of + * runs in @src. However, if @left, then the first run in @src has + * been merged with one in @dst. + */ + marker = loc + ssize - left; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, marker, tail, dsize - tail); + ntfs_rl_mc(dst, loc, src, left, ssize - left); + + /* We may have changed the length of the file, so fix the end marker. */ + if (dsize - tail > 0 && dst[marker].lcn == LCN_ENOENT) + dst[marker].vcn = dst[marker - 1].vcn + dst[marker - 1].length; + return dst; +} + +/* + * ntfs_rl_split - insert a runlist into the centre of a hole + * @dst: destination runlist with a hole + * @dsize: number of elements in @dst + * @src: source runlist to insert + * @ssize: number of elements in @src + * @loc: index in @dst of the hole to split + * @new_size: on success, set to the new combined size + * + * Split the runlist @dst at @loc into two and insert @new in between the two + * fragments. No merging of runlists is necessary. Adjust the size of the + * holes either side. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. + */ +static inline struct runlist_element *ntfs_rl_split(struct runlist_element *dst, int dsize, + struct runlist_element *src, int ssize, int loc, + size_t *new_size) +{ + /* Space required: @dst size + @src size + one new hole. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize + 1); + if (IS_ERR(dst)) + return dst; + + *new_size = dsize + ssize + 1; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, loc + 1 + ssize, loc, dsize - loc); + ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + + /* Adjust the size of the holes either size of @src. */ + dst[loc].length = dst[loc+1].vcn - dst[loc].vcn; + dst[loc+ssize+1].vcn = dst[loc+ssize].vcn + dst[loc+ssize].length; + dst[loc+ssize+1].length = dst[loc+ssize+2].vcn - dst[loc+ssize+1].vcn; + + return dst; +} + +/* + * ntfs_runlists_merge - merge two runlists into one + * @d_runlist: destination runlist structure to merge into + * @srl: source runlist to merge from + * @s_rl_count: number of elements in @srl (0 to auto-detect) + * @new_rl_count: on success, set to the new combined runlist size + * + * First we sanity check the two runlists @srl and @drl to make sure that they + * are sensible and can be merged. The runlist @srl must be either after the + * runlist @drl or completely within a hole (or unmapped region) in @drl. + * + * It is up to the caller to serialize access to the runlists @drl and @srl. + * + * Merging of runlists is necessary in two cases: + * 1. When attribute lists are used and a further extent is being mapped. + * 2. When new clusters are allocated to fill a hole or extend a file. + * + * There are four possible ways @srl can be merged. It can: + * - be inserted at the beginning of a hole, + * - split the hole in two and be inserted between the two fragments, + * - be appended at the end of a hole, or it can + * - replace the whole hole. + * It can also be appended to the end of the runlist, which is just a variant + * of the insert case. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @drl and @srl are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. + */ +struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, + struct runlist_element *srl, size_t s_rl_count, + size_t *new_rl_count) +{ + int di, si; /* Current index into @[ds]rl. */ + int sstart; /* First index with lcn > LCN_RL_NOT_MAPPED. */ + int dins; /* Index into @drl at which to insert @srl. */ + int dend, send; /* Last index into @[ds]rl. */ + int dfinal, sfinal; /* The last index into @[ds]rl with lcn >= LCN_HOLE. */ + int marker = 0; + s64 marker_vcn = 0; + struct runlist_element *drl = d_runlist->rl, *rl; + +#ifdef DEBUG + ntfs_debug("dst:"); + ntfs_debug_dump_runlist(drl); + ntfs_debug("src:"); + ntfs_debug_dump_runlist(srl); +#endif + + /* Check for silly calling... */ + if (unlikely(!srl)) + return drl; + if (IS_ERR(srl) || IS_ERR(drl)) + return ERR_PTR(-EINVAL); + + if (s_rl_count == 0) { + for (; srl[s_rl_count].length; s_rl_count++) + ; + s_rl_count++; + } + + /* Check for the case where the first mapping is being done now. */ + if (unlikely(!drl)) { + drl = srl; + /* Complete the source runlist if necessary. */ + if (unlikely(drl[0].vcn)) { + /* Scan to the end of the source runlist. */ + drl = ntfs_rl_realloc(drl, s_rl_count, s_rl_count + 1); + if (IS_ERR(drl)) + return drl; + /* Insert start element at the front of the runlist. */ + ntfs_rl_mm(drl, 1, 0, s_rl_count); + drl[0].vcn = 0; + drl[0].lcn = LCN_RL_NOT_MAPPED; + drl[0].length = drl[1].vcn; + s_rl_count++; + } + + *new_rl_count = s_rl_count; + goto finished; + } + + if (d_runlist->count < 1 || s_rl_count < 2) + return ERR_PTR(-EINVAL); + + si = di = 0; + + /* Skip any unmapped start element(s) in the source runlist. */ + while (srl[si].length && srl[si].lcn < LCN_HOLE) + si++; + + /* Can't have an entirely unmapped source runlist. */ + WARN_ON(!srl[si].length); + + /* Record the starting points. */ + sstart = si; + + /* + * Skip forward in @drl until we reach the position where @srl needs to + * be inserted. If we reach the end of @drl, @srl just needs to be + * appended to @drl. + */ + rl = __ntfs_attr_find_vcn_nolock(d_runlist, srl[sstart].vcn); + if (IS_ERR(rl)) + di = (int)d_runlist->count - 1; + else + di = (int)(rl - d_runlist->rl); + dins = di; + + /* Sanity check for illegal overlaps. */ + if ((drl[di].vcn == srl[si].vcn) && (drl[di].lcn >= 0) && + (srl[si].lcn >= 0)) { + ntfs_error(NULL, "Run lists overlap. Cannot merge!"); + return ERR_PTR(-ERANGE); + } + + /* Scan to the end of both runlists in order to know their sizes. */ + send = (int)s_rl_count - 1; + dend = (int)d_runlist->count - 1; + + if (srl[send].lcn == LCN_ENOENT) + marker_vcn = srl[marker = send].vcn; + + /* Scan to the last element with lcn >= LCN_HOLE. */ + for (sfinal = send; sfinal >= 0 && srl[sfinal].lcn < LCN_HOLE; sfinal--) + ; + for (dfinal = dend; dfinal >= 0 && drl[dfinal].lcn < LCN_HOLE; dfinal--) + ; + + { + bool start; + bool finish; + int ds = dend + 1; /* Number of elements in drl & srl */ + int ss = sfinal - sstart + 1; + + start = ((drl[dins].lcn < LCN_RL_NOT_MAPPED) || /* End of file */ + (drl[dins].vcn == srl[sstart].vcn)); /* Start of hole */ + finish = ((drl[dins].lcn >= LCN_RL_NOT_MAPPED) && /* End of file */ + ((drl[dins].vcn + drl[dins].length) <= /* End of hole */ + (srl[send - 1].vcn + srl[send - 1].length))); + + /* Or we will lose an end marker. */ + if (finish && !drl[dins].length) + ss++; + if (marker && (drl[dins].vcn + drl[dins].length > srl[send - 1].vcn)) + finish = false; + + if (start) { + if (finish) + drl = ntfs_rl_replace(drl, ds, srl + sstart, ss, dins, new_rl_count); + else + drl = ntfs_rl_insert(drl, ds, srl + sstart, ss, dins, new_rl_count); + } else { + if (finish) + drl = ntfs_rl_append(drl, ds, srl + sstart, ss, dins, new_rl_count); + else + drl = ntfs_rl_split(drl, ds, srl + sstart, ss, dins, new_rl_count); + } + if (IS_ERR(drl)) { + ntfs_error(NULL, "Merge failed."); + return drl; + } + kvfree(srl); + if (marker) { + ntfs_debug("Triggering marker code."); + for (ds = dend; drl[ds].length; ds++) + ; + /* We only need to care if @srl ended after @drl. */ + if (drl[ds].vcn <= marker_vcn) { + int slots = 0; + + if (drl[ds].vcn == marker_vcn) { + ntfs_debug("Old marker = 0x%llx, replacing with LCN_ENOENT.", + drl[ds].lcn); + drl[ds].lcn = LCN_ENOENT; + goto finished; + } + /* + * We need to create an unmapped runlist element in + * @drl or extend an existing one before adding the + * ENOENT terminator. + */ + if (drl[ds].lcn == LCN_ENOENT) { + ds--; + slots = 1; + } + if (drl[ds].lcn != LCN_RL_NOT_MAPPED) { + /* Add an unmapped runlist element. */ + if (!slots) { + drl = ntfs_rl_realloc_nofail(drl, ds, + ds + 2); + slots = 2; + *new_rl_count += 2; + } + ds++; + /* Need to set vcn if it isn't set already. */ + if (slots != 1) + drl[ds].vcn = drl[ds - 1].vcn + + drl[ds - 1].length; + drl[ds].lcn = LCN_RL_NOT_MAPPED; + /* We now used up a slot. */ + slots--; + } + drl[ds].length = marker_vcn - drl[ds].vcn; + /* Finally add the ENOENT terminator. */ + ds++; + if (!slots) { + drl = ntfs_rl_realloc_nofail(drl, ds, ds + 1); + *new_rl_count += 1; + } + drl[ds].vcn = marker_vcn; + drl[ds].lcn = LCN_ENOENT; + drl[ds].length = (s64)0; + } + } + } + +finished: + /* The merge was completed successfully. */ + ntfs_debug("Merged runlist:"); + ntfs_debug_dump_runlist(drl); + return drl; +} + +/* + * ntfs_mapping_pairs_decompress - convert mapping pairs array to runlist + * @vol: ntfs volume + * @attr: attribute record whose mapping pairs to decompress + * @old_runlist: optional runlist to merge the decompressed runlist into + * @new_rl_count: on success, set to the new runlist size + * + * It is up to the caller to serialize access to the runlist @old_rl. + * + * Decompress the attribute @attr's mapping pairs array into a runlist. On + * success, return the decompressed runlist. + * + * If @old_rl is not NULL, decompressed runlist is inserted into the + * appropriate place in @old_rl and the resultant, combined runlist is + * returned. The original @old_rl is deallocated. + * + * On error, return -errno. @old_rl is left unmodified in that case. + */ +struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *vol, + const struct attr_record *attr, struct runlist *old_runlist, + size_t *new_rl_count) +{ + s64 vcn; /* Current vcn. */ + s64 lcn; /* Current lcn. */ + s64 deltaxcn; /* Change in [vl]cn. */ + struct runlist_element *rl, *new_rl; /* The output runlist. */ + u8 *buf; /* Current position in mapping pairs array. */ + u8 *attr_end; /* End of attribute. */ + int rlsize; /* Size of runlist buffer. */ + u16 rlpos; /* Current runlist position in units of struct runlist_elements. */ + u8 b; /* Current byte offset in buf. */ + +#ifdef DEBUG + /* Make sure attr exists and is non-resident. */ + if (!attr || !attr->non_resident) { + ntfs_error(vol->sb, "Invalid arguments."); + return ERR_PTR(-EINVAL); + } +#endif + /* Start at vcn = lowest_vcn and lcn 0. */ + vcn = le64_to_cpu(attr->data.non_resident.lowest_vcn); + lcn = 0; + /* Get start of the mapping pairs array. */ + buf = (u8 *)attr + + le16_to_cpu(attr->data.non_resident.mapping_pairs_offset); + attr_end = (u8 *)attr + le32_to_cpu(attr->length); + if (unlikely(buf < (u8 *)attr || buf > attr_end)) { + ntfs_error(vol->sb, "Corrupt attribute."); + return ERR_PTR(-EIO); + } + + /* Current position in runlist array. */ + rlpos = 0; + /* Allocate first page and set current runlist size to one page. */ + rl = kvzalloc(rlsize = PAGE_SIZE, GFP_NOFS); + if (unlikely(!rl)) + return ERR_PTR(-ENOMEM); + /* Insert unmapped starting element if necessary. */ + if (vcn) { + rl->vcn = 0; + rl->lcn = LCN_RL_NOT_MAPPED; + rl->length = vcn; + rlpos++; + } + while (buf < attr_end && *buf) { + /* + * Allocate more memory if needed, including space for the + * not-mapped and terminator elements. kvzalloc() + * operates on whole pages only. + */ + if (((rlpos + 3) * sizeof(*rl)) > rlsize) { + struct runlist_element *rl2; + + rl2 = kvzalloc(rlsize + PAGE_SIZE, GFP_NOFS); + if (unlikely(!rl2)) { + kvfree(rl); + return ERR_PTR(-ENOMEM); + } + memcpy(rl2, rl, rlsize); + kvfree(rl); + rl = rl2; + rlsize += PAGE_SIZE; + } + /* Enter the current vcn into the current runlist element. */ + rl[rlpos].vcn = vcn; + /* + * Get the change in vcn, i.e. the run length in clusters. + * Doing it this way ensures that we signextend negative values. + * A negative run length doesn't make any sense, but hey, I + * didn't make up the NTFS specs and Windows NT4 treats the run + * length as a signed value so that's how it is... + */ + b = *buf & 0xf; + if (b) { + if (unlikely(buf + b > attr_end)) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + } else { /* The length entry is compulsory. */ + ntfs_error(vol->sb, "Missing length entry in mapping pairs array."); + deltaxcn = (s64)-1; + } + /* + * Assume a negative length to indicate data corruption and + * hence clean-up and return NULL. + */ + if (unlikely(deltaxcn < 0)) { + ntfs_error(vol->sb, "Invalid length in mapping pairs array."); + goto err_out; + } + /* + * Enter the current run length into the current runlist + * element. + */ + rl[rlpos].length = deltaxcn; + /* Increment the current vcn by the current run length. */ + vcn += deltaxcn; + /* + * There might be no lcn change at all, as is the case for + * sparse clusters on NTFS 3.0+, in which case we set the lcn + * to LCN_HOLE. + */ + if (!(*buf & 0xf0)) + rl[rlpos].lcn = LCN_HOLE; + else { + /* Get the lcn change which really can be negative. */ + u8 b2 = *buf & 0xf; + + b = b2 + ((*buf >> 4) & 0xf); + if (buf + b > attr_end) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b > b2; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + /* Change the current lcn to its new value. */ + lcn += deltaxcn; +#ifdef DEBUG + /* + * On NTFS 1.2-, apparently can have lcn == -1 to + * indicate a hole. But we haven't verified ourselves + * whether it is really the lcn or the deltaxcn that is + * -1. So if either is found give us a message so we + * can investigate it further! + */ + if (vol->major_ver < 3) { + if (unlikely(deltaxcn == -1)) + ntfs_error(vol->sb, "lcn delta == -1"); + if (unlikely(lcn == -1)) + ntfs_error(vol->sb, "lcn == -1"); + } +#endif + /* Check lcn is not below -1. */ + if (unlikely(lcn < -1)) { + ntfs_error(vol->sb, "Invalid s64 < -1 in mapping pairs array."); + goto err_out; + } + + /* chkdsk accepts zero-sized runs only for holes */ + if ((lcn != -1) && !rl[rlpos].length) { + ntfs_error(vol->sb, + "Invalid zero-sized data run(lcn : %lld).\n", + lcn); + goto err_out; + } + + /* Enter the current lcn into the runlist element. */ + rl[rlpos].lcn = lcn; + } + /* Get to the next runlist element, skipping zero-sized holes */ + if (rl[rlpos].length) + rlpos++; + /* Increment the buffer position to the next mapping pair. */ + buf += (*buf & 0xf) + ((*buf >> 4) & 0xf) + 1; + } + if (unlikely(buf >= attr_end)) + goto io_error; + /* + * If there is a highest_vcn specified, it must be equal to the final + * vcn in the runlist - 1, or something has gone badly wrong. + */ + deltaxcn = le64_to_cpu(attr->data.non_resident.highest_vcn); + if (unlikely(deltaxcn && vcn - 1 != deltaxcn)) { +mpa_err: + ntfs_error(vol->sb, "Corrupt mapping pairs array in non-resident attribute."); + goto err_out; + } + /* Setup not mapped runlist element if this is the base extent. */ + if (!attr->data.non_resident.lowest_vcn) { + s64 max_cluster; + + max_cluster = ((le64_to_cpu(attr->data.non_resident.allocated_size) + + vol->cluster_size - 1) >> + vol->cluster_size_bits) - 1; + /* + * A highest_vcn of zero means this is a single extent + * attribute so simply terminate the runlist with LCN_ENOENT). + */ + if (deltaxcn) { + /* + * If there is a difference between the highest_vcn and + * the highest cluster, the runlist is either corrupt + * or, more likely, there are more extents following + * this one. + */ + if (deltaxcn < max_cluster) { + ntfs_debug("More extents to follow; deltaxcn = 0x%llx, max_cluster = 0x%llx", + deltaxcn, max_cluster); + rl[rlpos].vcn = vcn; + vcn += rl[rlpos].length = max_cluster - + deltaxcn; + rl[rlpos].lcn = LCN_RL_NOT_MAPPED; + rlpos++; + } else if (unlikely(deltaxcn > max_cluster)) { + ntfs_error(vol->sb, + "Corrupt attribute. deltaxcn = 0x%llx, max_cluster = 0x%llx", + deltaxcn, max_cluster); + goto mpa_err; + } + } + rl[rlpos].lcn = LCN_ENOENT; + } else /* Not the base extent. There may be more extents to follow. */ + rl[rlpos].lcn = LCN_RL_NOT_MAPPED; + + /* Setup terminating runlist element. */ + rl[rlpos].vcn = vcn; + rl[rlpos].length = (s64)0; + /* If no existing runlist was specified, we are done. */ + if (!old_runlist || !old_runlist->rl) { + *new_rl_count = rlpos + 1; + ntfs_debug("Mapping pairs array successfully decompressed:"); + ntfs_debug_dump_runlist(rl); + return rl; + } + /* Now combine the new and old runlists checking for overlaps. */ + new_rl = ntfs_runlists_merge(old_runlist, rl, rlpos + 1, new_rl_count); + if (!IS_ERR(new_rl)) + return new_rl; + kvfree(rl); + ntfs_error(vol->sb, "Failed to merge runlists."); + return new_rl; +io_error: + ntfs_error(vol->sb, "Corrupt attribute."); +err_out: + kvfree(rl); + return ERR_PTR(-EIO); +} + +/* + * ntfs_rl_vcn_to_lcn - convert a vcn into a lcn given a runlist + * @rl: runlist to use for conversion + * @vcn: vcn to convert + * + * Convert the virtual cluster number @vcn of an attribute into a logical + * cluster number (lcn) of a device using the runlist @rl to map vcns to their + * corresponding lcns. + * + * It is up to the caller to serialize access to the runlist @rl. + * + * Since lcns must be >= 0, we use negative return codes with special meaning: + * + * Return code Meaning / Description + * ================================================== + * LCN_HOLE Hole / not allocated on disk. + * LCN_RL_NOT_MAPPED This is part of the runlist which has not been + * inserted into the runlist yet. + * LCN_ENOENT There is no such vcn in the attribute. + * + * Locking: - The caller must have locked the runlist (for reading or writing). + * - This function does not touch the lock, nor does it modify the + * runlist. + */ +s64 ntfs_rl_vcn_to_lcn(const struct runlist_element *rl, const s64 vcn) +{ + int i; + + /* + * If rl is NULL, assume that we have found an unmapped runlist. The + * caller can then attempt to map it and fail appropriately if + * necessary. + */ + if (unlikely(!rl)) + return LCN_RL_NOT_MAPPED; + + /* Catch out of lower bounds vcn. */ + if (unlikely(vcn < rl[0].vcn)) + return LCN_ENOENT; + + for (i = 0; likely(rl[i].length); i++) { + if (vcn < rl[i+1].vcn) { + if (likely(rl[i].lcn >= 0)) + return rl[i].lcn + (vcn - rl[i].vcn); + return rl[i].lcn; + } + } + /* + * The terminator element is setup to the correct value, i.e. one of + * LCN_HOLE, LCN_RL_NOT_MAPPED, or LCN_ENOENT. + */ + if (likely(rl[i].lcn < 0)) + return rl[i].lcn; + /* Just in case... We could replace this with BUG() some day. */ + return LCN_ENOENT; +} + +/* + * ntfs_rl_find_vcn_nolock - find a vcn in a runlist + * @rl: runlist to search + * @vcn: vcn to find + * + * Find the virtual cluster number @vcn in the runlist @rl and return the + * address of the runlist element containing the @vcn on success. + * + * Return NULL if @rl is NULL or @vcn is in an unmapped part/out of bounds of + * the runlist. + * + * Locking: The runlist must be locked on entry. + */ +struct runlist_element *ntfs_rl_find_vcn_nolock(struct runlist_element *rl, const s64 vcn) +{ + if (unlikely(!rl || vcn < rl[0].vcn)) + return NULL; + while (likely(rl->length)) { + if (unlikely(vcn < rl[1].vcn)) { + if (likely(rl->lcn >= LCN_HOLE)) + return rl; + return NULL; + } + rl++; + } + if (likely(rl->lcn == LCN_ENOENT)) + return rl; + return NULL; +} + +/* + * ntfs_get_nr_significant_bytes - get number of bytes needed to store a number + * @n: number for which to get the number of bytes for + * + * Return the number of bytes required to store @n unambiguously as + * a signed number. + * + * This is used in the context of the mapping pairs array to determine how + * many bytes will be needed in the array to store a given logical cluster + * number (lcn) or a specific run length. + * + * Return the number of bytes written. This function cannot fail. + */ +static inline int ntfs_get_nr_significant_bytes(const s64 n) +{ + s64 l = n; + int i; + s8 j; + + i = 0; + do { + l >>= 8; + i++; + } while (l != 0 && l != -1); + j = (n >> 8 * (i - 1)) & 0xff; + /* If the sign bit is wrong, we need an extra byte. */ + if ((n < 0 && j >= 0) || (n > 0 && j < 0)) + i++; + return i; +} + +/* + * ntfs_get_size_for_mapping_pairs - get bytes needed for mapping pairs array + * @vol: ntfs volume + * @rl: runlist to calculate the mapping pairs array size for + * @first_vcn: first vcn which to include in the mapping pairs array + * @last_vcn: last vcn which to include in the mapping pairs array + * @max_mp_size: maximum size to return (0 or less means unlimited) + * + * Walk the locked runlist @rl and calculate the size in bytes of the mapping + * pairs array corresponding to the runlist @rl, starting at vcn @first_vcn and + * finishing with vcn @last_vcn. + * + * A @last_vcn of -1 means end of runlist and in that case the size of the + * mapping pairs array corresponding to the runlist starting at vcn @first_vcn + * and finishing at the end of the runlist is determined. + * + * This for example allows us to allocate a buffer of the right size when + * building the mapping pairs array. + * + * If @rl is NULL, just return 1 (for the single terminator byte). + * + * Return the calculated size in bytes on success. On error, return -errno. + */ +int ntfs_get_size_for_mapping_pairs(const struct ntfs_volume *vol, + const struct runlist_element *rl, const s64 first_vcn, + const s64 last_vcn, int max_mp_size) +{ + s64 prev_lcn; + int rls; + bool the_end = false; + + if (first_vcn < 0 || last_vcn < -1) + return -EINVAL; + + if (last_vcn >= 0 && first_vcn > last_vcn) + return -EINVAL; + + if (!rl) { + WARN_ON(first_vcn); + WARN_ON(last_vcn > 0); + return 1; + } + if (max_mp_size <= 0) + max_mp_size = INT_MAX; + /* Skip to runlist element containing @first_vcn. */ + while (rl->length && first_vcn >= rl[1].vcn) + rl++; + if (unlikely((!rl->length && first_vcn > rl->vcn) || + first_vcn < rl->vcn)) + return -EINVAL; + prev_lcn = 0; + /* Always need the termining zero byte. */ + rls = 1; + /* Do the first partial run if present. */ + if (first_vcn > rl->vcn) { + s64 delta, length = rl->length; + + /* We know rl->length != 0 already. */ + if (unlikely(length < 0 || rl->lcn < LCN_HOLE)) + goto err_out; + /* + * If @stop_vcn is given and finishes inside this run, cap the + * run length. + */ + if (unlikely(last_vcn >= 0 && rl[1].vcn > last_vcn)) { + s64 s1 = last_vcn + 1; + + if (unlikely(rl[1].vcn > s1)) + length = s1 - rl->vcn; + the_end = true; + } + delta = first_vcn - rl->vcn; + /* Header byte + length. */ + rls += 1 + ntfs_get_nr_significant_bytes(length - delta); + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just store the lcn. + * Note: this assumes that on NTFS 1.2-, holes are stored with + * an lcn of -1 and not a delta_lcn of -1 (unless both are -1). + */ + if (likely(rl->lcn >= 0 || vol->major_ver < 3)) { + prev_lcn = rl->lcn; + if (likely(rl->lcn >= 0)) + prev_lcn += delta; + /* Change in lcn. */ + rls += ntfs_get_nr_significant_bytes(prev_lcn); + } + /* Go to next runlist element. */ + rl++; + } + /* Do the full runs. */ + for (; rl->length && !the_end; rl++) { + s64 length = rl->length; + + if (unlikely(length < 0 || rl->lcn < LCN_HOLE)) + goto err_out; + /* + * If @stop_vcn is given and finishes inside this run, cap the + * run length. + */ + if (unlikely(last_vcn >= 0 && rl[1].vcn > last_vcn)) { + s64 s1 = last_vcn + 1; + + if (unlikely(rl[1].vcn > s1)) + length = s1 - rl->vcn; + the_end = true; + } + /* Header byte + length. */ + rls += 1 + ntfs_get_nr_significant_bytes(length); + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just store the lcn. + * Note: this assumes that on NTFS 1.2-, holes are stored with + * an lcn of -1 and not a delta_lcn of -1 (unless both are -1). + */ + if (likely(rl->lcn >= 0 || vol->major_ver < 3)) { + /* Change in lcn. */ + rls += ntfs_get_nr_significant_bytes(rl->lcn - + prev_lcn); + prev_lcn = rl->lcn; + } + + if (rls > max_mp_size) + break; + } + return rls; +err_out: + if (rl->lcn == LCN_RL_NOT_MAPPED) + rls = -EINVAL; + else + rls = -EIO; + return rls; +} + +/* + * ntfs_write_significant_bytes - write the significant bytes of a number + * @dst: destination buffer to write to + * @dst_max: pointer to last byte of destination buffer for bounds checking + * @n: number whose significant bytes to write + * + * Store in @dst, the minimum bytes of the number @n which are required to + * identify @n unambiguously as a signed number, taking care not to exceed + * @dest_max, the maximum position within @dst to which we are allowed to + * write. + * + * This is used when building the mapping pairs array of a runlist to compress + * a given logical cluster number (lcn) or a specific run length to the minimum + * size possible. + * + * Return the number of bytes written on success. On error, i.e. the + * destination buffer @dst is too small, return -ENOSPC. + */ +static inline int ntfs_write_significant_bytes(s8 *dst, const s8 *dst_max, + const s64 n) +{ + s64 l = n; + int i; + s8 j; + + i = 0; + do { + if (unlikely(dst > dst_max)) + goto err_out; + *dst++ = l & 0xffll; + l >>= 8; + i++; + } while (l != 0 && l != -1); + j = (n >> 8 * (i - 1)) & 0xff; + /* If the sign bit is wrong, we need an extra byte. */ + if (n < 0 && j >= 0) { + if (unlikely(dst > dst_max)) + goto err_out; + i++; + *dst = (s8)-1; + } else if (n > 0 && j < 0) { + if (unlikely(dst > dst_max)) + goto err_out; + i++; + *dst = (s8)0; + } + return i; +err_out: + return -ENOSPC; +} + +/* + * ntfs_mapping_pairs_build - build the mapping pairs array from a runlist + * @vol: ntfs volume + * @dst: destination buffer to build mapping pairs array into + * @dst_len: size of @dst in bytes + * @rl: runlist to build the mapping pairs array from + * @first_vcn: first vcn which to include in the mapping pairs array + * @last_vcn: last vcn which to include in the mapping pairs array + * @stop_vcn: on return, set to the first vcn outside the destination buffer + * @stop_rl: on return, set to the runlist element where encoding stopped + * @de_cluster_count: on return, set to the number of clusters encoded + * + * Create the mapping pairs array from the locked runlist @rl, starting at vcn + * @first_vcn and finishing with vcn @last_vcn and save the array in @dst. + * @dst_len is the size of @dst in bytes and it should be at least equal to the + * value obtained by calling ntfs_get_size_for_mapping_pairs(). + * + * A @last_vcn of -1 means end of runlist and in that case the mapping pairs + * array corresponding to the runlist starting at vcn @first_vcn and finishing + * at the end of the runlist is created. + * + * If @rl is NULL, just write a single terminator byte to @dst. + * + * On success or -ENOSPC error, if @stop_vcn is not NULL, *@stop_vcn is set to + * the first vcn outside the destination buffer. Note that on error, @dst has + * been filled with all the mapping pairs that will fit, thus it can be treated + * as partial success, in that a new attribute extent needs to be created or + * the next extent has to be used and the mapping pairs build has to be + * continued with @first_vcn set to *@stop_vcn. + * + * Return 0 on success and -errno on error. The following error codes are + * defined: + * -EINVAL - Run list contains unmapped elements. Make sure to only pass + * fully mapped runlists to this function. + * -EIO - The runlist is corrupt. + * -ENOSPC - The destination buffer is too small. + * + * Locking: @rl must be locked on entry (either for reading or writing), it + * remains locked throughout, and is left locked upon return. + */ +int ntfs_mapping_pairs_build(const struct ntfs_volume *vol, s8 *dst, + const int dst_len, const struct runlist_element *rl, + const s64 first_vcn, const s64 last_vcn, s64 *const stop_vcn, + struct runlist_element **stop_rl, unsigned int *de_cluster_count) +{ + s64 prev_lcn; + s8 *dst_max, *dst_next; + int err = -ENOSPC; + bool the_end = false; + s8 len_len, lcn_len; + unsigned int de_cnt = 0; + + if (first_vcn < 0 || last_vcn < -1 || dst_len < 1) + return -EINVAL; + if (last_vcn >= 0 && first_vcn > last_vcn) + return -EINVAL; + + if (!rl) { + WARN_ON(first_vcn || last_vcn > 0); + if (stop_vcn) + *stop_vcn = 0; + /* Terminator byte. */ + *dst = 0; + return 0; + } + /* Skip to runlist element containing @first_vcn. */ + while (rl->length && first_vcn >= rl[1].vcn) + rl++; + if (unlikely((!rl->length && first_vcn > rl->vcn) || + first_vcn < rl->vcn)) + return -EINVAL; + /* + * @dst_max is used for bounds checking in + * ntfs_write_significant_bytes(). + */ + dst_max = dst + dst_len - 1; + prev_lcn = 0; + /* Do the first partial run if present. */ + if (first_vcn > rl->vcn) { + s64 delta, length = rl->length; + + /* We know rl->length != 0 already. */ + if (unlikely(length < 0 || rl->lcn < LCN_HOLE)) + goto err_out; + /* + * If @stop_vcn is given and finishes inside this run, cap the + * run length. + */ + if (unlikely(last_vcn >= 0 && rl[1].vcn > last_vcn)) { + s64 s1 = last_vcn + 1; + + if (unlikely(rl[1].vcn > s1)) + length = s1 - rl->vcn; + the_end = true; + } + delta = first_vcn - rl->vcn; + /* Write length. */ + len_len = ntfs_write_significant_bytes(dst + 1, dst_max, + length - delta); + if (unlikely(len_len < 0)) + goto size_err; + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just write the lcn + * change. + */ + if (likely(rl->lcn >= 0 || vol->major_ver < 3)) { + prev_lcn = rl->lcn; + if (likely(rl->lcn >= 0)) + prev_lcn += delta; + /* Write change in lcn. */ + lcn_len = ntfs_write_significant_bytes(dst + 1 + + len_len, dst_max, prev_lcn); + if (unlikely(lcn_len < 0)) + goto size_err; + } else + lcn_len = 0; + dst_next = dst + len_len + lcn_len + 1; + if (unlikely(dst_next > dst_max)) + goto size_err; + /* Update header byte. */ + *dst = lcn_len << 4 | len_len; + /* Position at next mapping pairs array element. */ + dst = dst_next; + /* Go to next runlist element. */ + rl++; + } + /* Do the full runs. */ + for (; rl->length && !the_end; rl++) { + s64 length = rl->length; + + if (unlikely(length < 0 || rl->lcn < LCN_HOLE)) + goto err_out; + /* + * If @stop_vcn is given and finishes inside this run, cap the + * run length. + */ + if (unlikely(last_vcn >= 0 && rl[1].vcn > last_vcn)) { + s64 s1 = last_vcn + 1; + + if (unlikely(rl[1].vcn > s1)) + length = s1 - rl->vcn; + the_end = true; + } + /* Write length. */ + len_len = ntfs_write_significant_bytes(dst + 1, dst_max, + length); + if (unlikely(len_len < 0)) + goto size_err; + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just write the lcn + * change. + */ + if (likely(rl->lcn >= 0 || vol->major_ver < 3)) { + /* Write change in lcn. */ + lcn_len = ntfs_write_significant_bytes(dst + 1 + + len_len, dst_max, rl->lcn - prev_lcn); + if (unlikely(lcn_len < 0)) + goto size_err; + prev_lcn = rl->lcn; + } else { + if (rl->lcn == LCN_DELALLOC) + de_cnt += rl->length; + lcn_len = 0; + } + dst_next = dst + len_len + lcn_len + 1; + if (unlikely(dst_next > dst_max)) + goto size_err; + /* Update header byte. */ + *dst = lcn_len << 4 | len_len; + /* Position at next mapping pairs array element. */ + dst = dst_next; + } + /* Success. */ + if (de_cluster_count) + *de_cluster_count = de_cnt; + err = 0; +size_err: + /* Set stop vcn. */ + if (stop_vcn) + *stop_vcn = rl->vcn; + if (stop_rl) + *stop_rl = (struct runlist_element *)rl; + /* Add terminator byte. */ + *dst = 0; + return err; +err_out: + if (rl->lcn == LCN_RL_NOT_MAPPED) + err = -EINVAL; + else + err = -EIO; + return err; +} + +/* + * ntfs_rl_truncate_nolock - truncate a runlist starting at a specified vcn + * @vol: ntfs volume (needed for error output) + * @runlist: runlist to truncate + * @new_length: the new length of the runlist in VCNs + * + * Truncate the runlist described by @runlist as well as the memory buffer + * holding the runlist elements to a length of @new_length VCNs. + * + * If @new_length lies within the runlist, the runlist elements with VCNs of + * @new_length and above are discarded. As a special case if @new_length is + * zero, the runlist is discarded and set to NULL. + * + * If @new_length lies beyond the runlist, a sparse runlist element is added to + * the end of the runlist @runlist or if the last runlist element is a sparse + * one already, this is extended. + * + * Note, no checking is done for unmapped runlist elements. It is assumed that + * the caller has mapped any elements that need to be mapped already. + * + * Return 0 on success and -errno on error. + * + * Locking: The caller must hold @runlist->lock for writing. + */ +int ntfs_rl_truncate_nolock(const struct ntfs_volume *vol, struct runlist *const runlist, + const s64 new_length) +{ + struct runlist_element *rl; + int old_size; + + ntfs_debug("Entering for new_length 0x%llx.", (long long)new_length); + + if (!runlist || new_length < 0) + return -EINVAL; + + rl = runlist->rl; + if (new_length < rl->vcn) + return -EINVAL; + + /* Find @new_length in the runlist. */ + while (likely(rl->length && new_length >= rl[1].vcn)) + rl++; + /* + * If not at the end of the runlist we need to shrink it. + * If at the end of the runlist we need to expand it. + */ + if (rl->length) { + struct runlist_element *trl; + bool is_end; + + ntfs_debug("Shrinking runlist."); + /* Determine the runlist size. */ + trl = rl + 1; + while (likely(trl->length)) + trl++; + old_size = trl - runlist->rl + 1; + /* Truncate the run. */ + rl->length = new_length - rl->vcn; + /* + * If a run was partially truncated, make the following runlist + * element a terminator. + */ + is_end = false; + if (rl->length) { + rl++; + if (!rl->length) + is_end = true; + rl->vcn = new_length; + rl->length = 0; + } + rl->lcn = LCN_ENOENT; + runlist->count = rl - runlist->rl + 1; + /* Reallocate memory if necessary. */ + if (!is_end) { + int new_size = rl - runlist->rl + 1; + + rl = ntfs_rl_realloc(runlist->rl, old_size, new_size); + if (IS_ERR(rl)) + ntfs_warning(vol->sb, + "Failed to shrink runlist buffer. This just wastes a bit of memory temporarily so we ignore it and return success."); + else + runlist->rl = rl; + } + } else if (likely(/* !rl->length && */ new_length > rl->vcn)) { + ntfs_debug("Expanding runlist."); + /* + * If there is a previous runlist element and it is a sparse + * one, extend it. Otherwise need to add a new, sparse runlist + * element. + */ + if ((rl > runlist->rl) && ((rl - 1)->lcn == LCN_HOLE)) + (rl - 1)->length = new_length - (rl - 1)->vcn; + else { + /* Determine the runlist size. */ + old_size = rl - runlist->rl + 1; + /* Reallocate memory if necessary. */ + rl = ntfs_rl_realloc(runlist->rl, old_size, + old_size + 1); + if (IS_ERR(rl)) { + ntfs_error(vol->sb, "Failed to expand runlist buffer, aborting."); + return PTR_ERR(rl); + } + runlist->rl = rl; + /* + * Set @rl to the same runlist element in the new + * runlist as before in the old runlist. + */ + rl += old_size - 1; + /* Add a new, sparse runlist element. */ + rl->lcn = LCN_HOLE; + rl->length = new_length - rl->vcn; + /* Add a new terminator runlist element. */ + rl++; + rl->length = 0; + runlist->count = old_size + 1; + } + rl->vcn = new_length; + rl->lcn = LCN_ENOENT; + } else /* if (unlikely(!rl->length && new_length == rl->vcn)) */ { + /* Runlist already has same size as requested. */ + rl->lcn = LCN_ENOENT; + } + ntfs_debug("Done."); + return 0; +} + +/* + * ntfs_rl_sparse - check whether runlist have sparse regions or not. + * @rl: runlist to check + * + * Return 1 if have, 0 if not, -errno on error. + */ +int ntfs_rl_sparse(struct runlist_element *rl) +{ + struct runlist_element *rlc; + + if (!rl) + return -EINVAL; + + for (rlc = rl; rlc->length; rlc++) + if (rlc->lcn < 0) { + if (rlc->lcn != LCN_HOLE && rlc->lcn != LCN_DELALLOC) { + pr_err("%s: bad runlist\n", __func__); + return -EINVAL; + } + return 1; + } + return 0; +} + +/* + * ntfs_rl_get_compressed_size - calculate length of non sparse regions + * @vol: ntfs volume (need for cluster size) + * @rl: runlist to calculate for + * + * Return compressed size or -errno on error. + */ +s64 ntfs_rl_get_compressed_size(struct ntfs_volume *vol, struct runlist_element *rl) +{ + struct runlist_element *rlc; + s64 ret = 0; + + if (!rl) + return -EINVAL; + + for (rlc = rl; rlc->length; rlc++) { + if (rlc->lcn < 0) { + if (rlc->lcn != LCN_HOLE && rlc->lcn != LCN_DELALLOC) { + ntfs_error(vol->sb, "%s: bad runlist, rlc->lcn : %lld", + __func__, rlc->lcn); + return -EINVAL; + } + } else + ret += rlc->length; + } + return NTFS_CLU_TO_B(vol, ret); +} + +static inline bool ntfs_rle_lcn_contiguous(struct runlist_element *left_rle, + struct runlist_element *right_rle) +{ + if (left_rle->lcn > LCN_HOLE && + left_rle->lcn + left_rle->length == right_rle->lcn) + return true; + else if (left_rle->lcn == LCN_HOLE && right_rle->lcn == LCN_HOLE) + return true; + else + return false; +} + +static inline bool ntfs_rle_contain(struct runlist_element *rle, s64 vcn) +{ + if (rle->length > 0 && + vcn >= rle->vcn && vcn < rle->vcn + rle->length) + return true; + else + return false; +} + +struct runlist_element *ntfs_rl_insert_range(struct runlist_element *dst_rl, int dst_cnt, + struct runlist_element *src_rl, int src_cnt, + size_t *new_rl_cnt) +{ + struct runlist_element *i_rl, *new_rl, *src_rl_origin = src_rl; + struct runlist_element dst_rl_split; + s64 start_vcn; + int new_1st_cnt, new_2nd_cnt, new_3rd_cnt, new_cnt; + + if (!dst_rl || !src_rl || !new_rl_cnt) + return ERR_PTR(-EINVAL); + if (dst_cnt <= 0 || src_cnt <= 0) + return ERR_PTR(-EINVAL); + if (!(dst_rl[dst_cnt - 1].lcn == LCN_ENOENT && + dst_rl[dst_cnt - 1].length == 0) || + src_rl[src_cnt - 1].lcn < LCN_HOLE) + return ERR_PTR(-EINVAL); + + start_vcn = src_rl[0].vcn; + + i_rl = ntfs_rl_find_vcn_nolock(dst_rl, start_vcn); + if (!i_rl || + (i_rl->lcn == LCN_ENOENT && i_rl->vcn != start_vcn) || + (i_rl->lcn != LCN_ENOENT && !ntfs_rle_contain(i_rl, start_vcn))) + return ERR_PTR(-EINVAL); + + new_1st_cnt = (int)(i_rl - dst_rl); + if (new_1st_cnt > dst_cnt) + return ERR_PTR(-EINVAL); + new_3rd_cnt = dst_cnt - new_1st_cnt; + if (new_3rd_cnt < 1) + return ERR_PTR(-EINVAL); + + if (i_rl[0].vcn != start_vcn) { + if (i_rl[0].lcn == LCN_HOLE && src_rl[0].lcn == LCN_HOLE) + goto merge_src_rle; + + /* split @i_rl[0] and create @dst_rl_split */ + dst_rl_split.vcn = i_rl[0].vcn; + dst_rl_split.length = start_vcn - i_rl[0].vcn; + dst_rl_split.lcn = i_rl[0].lcn; + + i_rl[0].vcn = start_vcn; + i_rl[0].length -= dst_rl_split.length; + i_rl[0].lcn += dst_rl_split.length; + } else { + struct runlist_element *dst_rle, *src_rle; +merge_src_rle: + + /* not split @i_rl[0] */ + dst_rl_split.lcn = LCN_ENOENT; + + /* merge @src_rl's first run and @i_rl[0]'s left run if possible */ + dst_rle = &dst_rl[new_1st_cnt - 1]; + src_rle = &src_rl[0]; + if (new_1st_cnt > 0 && ntfs_rle_lcn_contiguous(dst_rle, src_rle)) { + WARN_ON(dst_rle->vcn + dst_rle->length != src_rle->vcn); + dst_rle->length += src_rle->length; + src_rl++; + src_cnt--; + } else { + /* merge @src_rl's last run and @i_rl[0]'s right if possible */ + dst_rle = &dst_rl[new_1st_cnt]; + src_rle = &src_rl[src_cnt - 1]; + + if (ntfs_rle_lcn_contiguous(dst_rle, src_rle)) { + dst_rle->length += src_rle->length; + src_cnt--; + } + } + } + + new_2nd_cnt = src_cnt; + new_cnt = new_1st_cnt + new_2nd_cnt + new_3rd_cnt; + new_cnt += dst_rl_split.lcn >= LCN_HOLE ? 1 : 0; + new_rl = kvcalloc(new_cnt, sizeof(*new_rl), GFP_NOFS); + if (!new_rl) + return ERR_PTR(-ENOMEM); + + /* Copy the @dst_rl's first half to @new_rl */ + ntfs_rl_mc(new_rl, 0, dst_rl, 0, new_1st_cnt); + if (dst_rl_split.lcn >= LCN_HOLE) { + ntfs_rl_mc(new_rl, new_1st_cnt, &dst_rl_split, 0, 1); + new_1st_cnt++; + } + /* Copy the @src_rl to @new_rl */ + ntfs_rl_mc(new_rl, new_1st_cnt, src_rl, 0, new_2nd_cnt); + /* Copy the @dst_rl's second half to @new_rl */ + if (new_3rd_cnt >= 1) { + struct runlist_element *rl, *rl_3rd; + int dst_1st_cnt = dst_rl_split.lcn >= LCN_HOLE ? + new_1st_cnt - 1 : new_1st_cnt; + + ntfs_rl_mc(new_rl, new_1st_cnt + new_2nd_cnt, + dst_rl, dst_1st_cnt, new_3rd_cnt); + /* Update vcn of the @dst_rl's second half runs to reflect + * appended @src_rl. + */ + if (new_1st_cnt + new_2nd_cnt == 0) { + rl_3rd = &new_rl[new_1st_cnt + new_2nd_cnt + 1]; + rl = &new_rl[new_1st_cnt + new_2nd_cnt]; + } else { + rl_3rd = &new_rl[new_1st_cnt + new_2nd_cnt]; + rl = &new_rl[new_1st_cnt + new_2nd_cnt - 1]; + } + do { + rl_3rd->vcn = rl->vcn + rl->length; + if (rl_3rd->length <= 0) + break; + rl = rl_3rd; + rl_3rd++; + } while (1); + } + *new_rl_cnt = new_1st_cnt + new_2nd_cnt + new_3rd_cnt; + + kvfree(dst_rl); + kvfree(src_rl_origin); + return new_rl; +} + +struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int dst_cnt, + s64 start_vcn, s64 len, + struct runlist_element **punch_rl, + size_t *new_rl_cnt) +{ + struct runlist_element *s_rl, *e_rl, *new_rl, *dst_3rd_rl, hole_rl[1]; + s64 end_vcn; + int new_1st_cnt, dst_3rd_cnt, new_cnt, punch_cnt, merge_cnt; + bool begin_split, end_split, one_split_3; + + if (dst_cnt < 2 || + !(dst_rl[dst_cnt - 1].lcn == LCN_ENOENT && + dst_rl[dst_cnt - 1].length == 0)) + return ERR_PTR(-EINVAL); + + end_vcn = min(start_vcn + len - 1, + dst_rl[dst_cnt - 2].vcn + dst_rl[dst_cnt - 2].length - 1); + + s_rl = ntfs_rl_find_vcn_nolock(dst_rl, start_vcn); + if (!s_rl || + s_rl->lcn <= LCN_ENOENT || + !ntfs_rle_contain(s_rl, start_vcn)) + return ERR_PTR(-EINVAL); + + begin_split = s_rl->vcn != start_vcn ? true : false; + + e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn); + if (!e_rl || + e_rl->lcn <= LCN_ENOENT || + !ntfs_rle_contain(e_rl, end_vcn)) + return ERR_PTR(-EINVAL); + + end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false; + + /* @s_rl has to be split into left, punched hole, and right */ + one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false; + + punch_cnt = (int)(e_rl - s_rl) + 1; + + *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element), + GFP_NOFS); + if (!*punch_rl) + return ERR_PTR(-ENOMEM); + + new_cnt = dst_cnt - (int)(e_rl - s_rl + 1) + 3; + new_rl = kvcalloc(new_cnt, sizeof(struct runlist_element), GFP_NOFS); + if (!new_rl) { + kvfree(*punch_rl); + *punch_rl = NULL; + return ERR_PTR(-ENOMEM); + } + + new_1st_cnt = (int)(s_rl - dst_rl) + 1; + ntfs_rl_mc(*punch_rl, 0, dst_rl, new_1st_cnt - 1, punch_cnt); + + (*punch_rl)[punch_cnt].lcn = LCN_ENOENT; + (*punch_rl)[punch_cnt].length = 0; + + if (!begin_split) + new_1st_cnt--; + dst_3rd_rl = e_rl; + dst_3rd_cnt = (int)(&dst_rl[dst_cnt - 1] - e_rl) + 1; + if (!end_split) { + dst_3rd_rl++; + dst_3rd_cnt--; + } + + /* Copy the 1st part of @dst_rl into @new_rl */ + ntfs_rl_mc(new_rl, 0, dst_rl, 0, new_1st_cnt); + if (begin_split) { + /* the @e_rl has to be splited and copied into the last of @new_rl + * and the first of @punch_rl + */ + s64 first_cnt = start_vcn - dst_rl[new_1st_cnt - 1].vcn; + + if (new_1st_cnt) + new_rl[new_1st_cnt - 1].length = first_cnt; + + (*punch_rl)[0].vcn = start_vcn; + (*punch_rl)[0].length -= first_cnt; + if ((*punch_rl)[0].lcn > LCN_HOLE) + (*punch_rl)[0].lcn += first_cnt; + } + + /* Copy a hole into @new_rl */ + hole_rl[0].vcn = start_vcn; + hole_rl[0].length = (s64)len; + hole_rl[0].lcn = LCN_HOLE; + ntfs_rl_mc(new_rl, new_1st_cnt, hole_rl, 0, 1); + + /* Copy the 3rd part of @dst_rl into @new_rl */ + ntfs_rl_mc(new_rl, new_1st_cnt + 1, dst_3rd_rl, 0, dst_3rd_cnt); + if (end_split) { + /* the @e_rl has to be splited and copied into the first of + * @new_rl and the last of @punch_rl + */ + s64 first_cnt = end_vcn - dst_3rd_rl[0].vcn + 1; + + new_rl[new_1st_cnt + 1].vcn = end_vcn + 1; + new_rl[new_1st_cnt + 1].length -= first_cnt; + if (new_rl[new_1st_cnt + 1].lcn > LCN_HOLE) + new_rl[new_1st_cnt + 1].lcn += first_cnt; + + if (one_split_3) + (*punch_rl)[punch_cnt - 1].length -= + new_rl[new_1st_cnt + 1].length; + else + (*punch_rl)[punch_cnt - 1].length = first_cnt; + } + + /* Merge left and hole, or hole and right in @new_rl, if left or right + * consists of holes. + */ + merge_cnt = 0; + if (new_1st_cnt > 0 && new_rl[new_1st_cnt - 1].lcn == LCN_HOLE) { + /* Merge right and hole */ + s_rl = &new_rl[new_1st_cnt - 1]; + s_rl->length += s_rl[1].length; + merge_cnt = 1; + /* Merge left and right */ + if (new_1st_cnt + 1 < new_cnt && + new_rl[new_1st_cnt + 1].lcn == LCN_HOLE) { + s_rl->length += s_rl[2].length; + merge_cnt++; + } + } else if (new_1st_cnt + 1 < new_cnt && + new_rl[new_1st_cnt + 1].lcn == LCN_HOLE) { + /* Merge left and hole */ + s_rl = &new_rl[new_1st_cnt]; + s_rl->length += s_rl[1].length; + merge_cnt = 1; + } + if (merge_cnt) { + struct runlist_element *d_rl, *src_rl; + + d_rl = s_rl + 1; + src_rl = s_rl + 1 + merge_cnt; + ntfs_rl_mm(new_rl, (int)(d_rl - new_rl), (int)(src_rl - new_rl), + (int)(&new_rl[new_cnt - 1] - src_rl) + 1); + } + + (*punch_rl)[punch_cnt].vcn = (*punch_rl)[punch_cnt - 1].vcn + + (*punch_rl)[punch_cnt - 1].length; + + /* punch_cnt elements of dst are replaced with one hole */ + *new_rl_cnt = dst_cnt - (punch_cnt - (int)begin_split - (int)end_split) + + 1 - merge_cnt; + kvfree(dst_rl); + return new_rl; +} + +struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, int dst_cnt, + s64 start_vcn, s64 len, + struct runlist_element **punch_rl, + size_t *new_rl_cnt) +{ + struct runlist_element *s_rl, *e_rl, *new_rl, *dst_3rd_rl; + s64 end_vcn; + int new_1st_cnt, dst_3rd_cnt, new_cnt, punch_cnt, merge_cnt, i; + bool begin_split, end_split, one_split_3; + + if (dst_cnt < 2 || + !(dst_rl[dst_cnt - 1].lcn == LCN_ENOENT && + dst_rl[dst_cnt - 1].length == 0)) + return ERR_PTR(-EINVAL); + + end_vcn = min(start_vcn + len - 1, + dst_rl[dst_cnt - 1].vcn - 1); + + s_rl = ntfs_rl_find_vcn_nolock(dst_rl, start_vcn); + if (!s_rl || + s_rl->lcn <= LCN_ENOENT || + !ntfs_rle_contain(s_rl, start_vcn)) + return ERR_PTR(-EINVAL); + + begin_split = s_rl->vcn != start_vcn ? true : false; + + e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn); + if (!e_rl || + e_rl->lcn <= LCN_ENOENT || + !ntfs_rle_contain(e_rl, end_vcn)) + return ERR_PTR(-EINVAL); + + end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false; + + /* @s_rl has to be split into left, collapsed, and right */ + one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false; + + punch_cnt = (int)(e_rl - s_rl) + 1; + *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element), + GFP_NOFS); + if (!*punch_rl) + return ERR_PTR(-ENOMEM); + + new_cnt = dst_cnt - (int)(e_rl - s_rl + 1) + 3; + new_rl = kvcalloc(new_cnt, sizeof(struct runlist_element), GFP_NOFS); + if (!new_rl) { + kvfree(*punch_rl); + *punch_rl = NULL; + return ERR_PTR(-ENOMEM); + } + + new_1st_cnt = (int)(s_rl - dst_rl) + 1; + ntfs_rl_mc(*punch_rl, 0, dst_rl, new_1st_cnt - 1, punch_cnt); + (*punch_rl)[punch_cnt].lcn = LCN_ENOENT; + (*punch_rl)[punch_cnt].length = 0; + + if (!begin_split) + new_1st_cnt--; + dst_3rd_rl = e_rl; + dst_3rd_cnt = (int)(&dst_rl[dst_cnt - 1] - e_rl) + 1; + if (!end_split) { + dst_3rd_rl++; + dst_3rd_cnt--; + } + + /* Copy the 1st part of @dst_rl into @new_rl */ + ntfs_rl_mc(new_rl, 0, dst_rl, 0, new_1st_cnt); + if (begin_split) { + /* the @e_rl has to be splited and copied into the last of @new_rl + * and the first of @punch_rl + */ + s64 first_cnt = start_vcn - dst_rl[new_1st_cnt - 1].vcn; + + new_rl[new_1st_cnt - 1].length = first_cnt; + + (*punch_rl)[0].vcn = start_vcn; + (*punch_rl)[0].length -= first_cnt; + if ((*punch_rl)[0].lcn > LCN_HOLE) + (*punch_rl)[0].lcn += first_cnt; + } + + /* Copy the 3rd part of @dst_rl into @new_rl */ + ntfs_rl_mc(new_rl, new_1st_cnt, dst_3rd_rl, 0, dst_3rd_cnt); + if (end_split) { + /* the @e_rl has to be splited and copied into the first of + * @new_rl and the last of @punch_rl + */ + s64 first_cnt = end_vcn - dst_3rd_rl[0].vcn + 1; + + new_rl[new_1st_cnt].vcn = end_vcn + 1; + new_rl[new_1st_cnt].length -= first_cnt; + if (new_rl[new_1st_cnt].lcn > LCN_HOLE) + new_rl[new_1st_cnt].lcn += first_cnt; + + if (one_split_3) + (*punch_rl)[punch_cnt - 1].length -= + new_rl[new_1st_cnt].length; + else + (*punch_rl)[punch_cnt - 1].length = first_cnt; + } + + /* Adjust vcn */ + if (new_1st_cnt == 0) + new_rl[new_1st_cnt].vcn = 0; + for (i = new_1st_cnt == 0 ? 1 : new_1st_cnt; new_rl[i].length; i++) + new_rl[i].vcn = new_rl[i - 1].vcn + new_rl[i - 1].length; + new_rl[i].vcn = new_rl[i - 1].vcn + new_rl[i - 1].length; + + /* Merge left and hole, or hole and right in @new_rl, if left or right + * consists of holes. + */ + merge_cnt = 0; + i = new_1st_cnt == 0 ? 1 : new_1st_cnt; + if (ntfs_rle_lcn_contiguous(&new_rl[i - 1], &new_rl[i])) { + /* Merge right and left */ + s_rl = &new_rl[new_1st_cnt - 1]; + s_rl->length += s_rl[1].length; + merge_cnt = 1; + } + if (merge_cnt) { + struct runlist_element *d_rl, *src_rl; + + d_rl = s_rl + 1; + src_rl = s_rl + 1 + merge_cnt; + ntfs_rl_mm(new_rl, (int)(d_rl - new_rl), (int)(src_rl - new_rl), + (int)(&new_rl[new_cnt - 1] - src_rl) + 1); + } + + (*punch_rl)[punch_cnt].vcn = (*punch_rl)[punch_cnt - 1].vcn + + (*punch_rl)[punch_cnt - 1].length; + + /* punch_cnt elements of dst are extracted */ + *new_rl_cnt = dst_cnt - (punch_cnt - (int)begin_split - (int)end_split) - + merge_cnt; + + kvfree(dst_rl); + return new_rl; +} diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h new file mode 100644 index 000000000000..cc92e09fbf68 --- /dev/null +++ b/fs/ntfs/runlist.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for runlist handling in NTFS Linux kernel driver. + * + * Copyright (c) 2001-2005 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_RUNLIST_H +#define _LINUX_NTFS_RUNLIST_H + +#include "volume.h" + +/* + * runlist_element - in memory vcn to lcn mapping array element + * @vcn: starting vcn of the current array element + * @lcn: starting lcn of the current array element + * @length: length in clusters of the current array element + * + * The last vcn (in fact the last vcn + 1) is reached when length == 0. + * + * When lcn == -1 this means that the count vcns starting at vcn are not + * physically allocated (i.e. this is a hole / data is sparse). + * + * In memory vcn to lcn mapping structure element. + * @vcn: vcn = Starting virtual cluster number. + * @lcn: lcn = Starting logical cluster number. + * @length: Run length in clusters. + */ +struct runlist_element { + s64 vcn; + s64 lcn; + s64 length; +}; + +/* + * runlist - in memory vcn to lcn mapping array including a read/write lock + * @rl: pointer to an array of runlist elements + * @lock: read/write spinlock for serializing access to @rl + * @rl_hint: hint/cache pointing to the last accessed runlist element + */ +struct runlist { + struct runlist_element *rl; + struct rw_semaphore lock; + size_t count; + int rl_hint; +}; + +static inline void ntfs_init_runlist(struct runlist *rl) +{ + rl->rl = NULL; + init_rwsem(&rl->lock); + rl->count = 0; + rl->rl_hint = -1; +} + +enum { + LCN_DELALLOC = -1, + LCN_HOLE = -2, + LCN_RL_NOT_MAPPED = -3, + LCN_ENOENT = -4, + LCN_ENOMEM = -5, + LCN_EIO = -6, + LCN_EINVAL = -7, +}; + +struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, + struct runlist_element *srl, size_t s_rl_count, + size_t *new_rl_count); +struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *vol, + const struct attr_record *attr, struct runlist *old_runlist, + size_t *new_rl_count); +s64 ntfs_rl_vcn_to_lcn(const struct runlist_element *rl, const s64 vcn); +struct runlist_element *ntfs_rl_find_vcn_nolock(struct runlist_element *rl, const s64 vcn); +int ntfs_get_size_for_mapping_pairs(const struct ntfs_volume *vol, + const struct runlist_element *rl, const s64 first_vcn, + const s64 last_vcn, int max_mp_size); +int ntfs_mapping_pairs_build(const struct ntfs_volume *vol, s8 *dst, + const int dst_len, const struct runlist_element *rl, + const s64 first_vcn, const s64 last_vcn, s64 *const stop_vcn, + struct runlist_element **stop_rl, unsigned int *de_cluster_count); +int ntfs_rl_truncate_nolock(const struct ntfs_volume *vol, + struct runlist *const runlist, const s64 new_length); +int ntfs_rl_sparse(struct runlist_element *rl); +s64 ntfs_rl_get_compressed_size(struct ntfs_volume *vol, struct runlist_element *rl); +struct runlist_element *ntfs_rl_insert_range(struct runlist_element *dst_rl, int dst_cnt, + struct runlist_element *src_rl, int src_cnt, size_t *new_cnt); +struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int dst_cnt, + s64 start_vcn, s64 len, struct runlist_element **punch_rl, + size_t *new_rl_cnt); +struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, int dst_cnt, + s64 start_vcn, s64 len, struct runlist_element **punch_rl, + size_t *new_rl_cnt); +struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl, int old_size, + int new_size); +#endif /* _LINUX_NTFS_RUNLIST_H */ diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c new file mode 100644 index 000000000000..22dc7865eca7 --- /dev/null +++ b/fs/ntfs/super.c @@ -0,0 +1,2765 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel super block handling. + * + * Copyright (c) 2001-2012 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2001,2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include /* For bdev_logical_block_size(). */ +#include +#include +#include +#include +#include +#include + +#include "sysctl.h" +#include "logfile.h" +#include "quota.h" +#include "index.h" +#include "ntfs.h" +#include "ea.h" +#include "volume.h" + +/* A global default upcase table and a corresponding reference count. */ +static __le16 *default_upcase; +static unsigned long ntfs_nr_upcase_users; + +static struct workqueue_struct *ntfs_wq; + +/* Error constants/strings used in inode.c::ntfs_show_options(). */ +enum { + /* One of these must be present, default is ON_ERRORS_CONTINUE. */ + ON_ERRORS_PANIC = 0x01, + ON_ERRORS_REMOUNT_RO = 0x02, + ON_ERRORS_CONTINUE = 0x04, +}; + +static const struct constant_table ntfs_param_enums[] = { + { "panic", ON_ERRORS_PANIC }, + { "remount-ro", ON_ERRORS_REMOUNT_RO }, + { "continue", ON_ERRORS_CONTINUE }, + {} +}; + +enum { + Opt_uid, + Opt_gid, + Opt_umask, + Opt_dmask, + Opt_fmask, + Opt_errors, + Opt_nls, + Opt_charset, + Opt_show_sys_files, + Opt_show_meta, + Opt_case_sensitive, + Opt_disable_sparse, + Opt_sparse, + Opt_mft_zone_multiplier, + Opt_preallocated_size, + Opt_sys_immutable, + Opt_nohidden, + Opt_hide_dot_files, + Opt_check_windows_names, + Opt_acl, + Opt_discard, + Opt_nocase, +}; + +static const struct fs_parameter_spec ntfs_parameters[] = { + fsparam_u32("uid", Opt_uid), + fsparam_u32("gid", Opt_gid), + fsparam_u32oct("umask", Opt_umask), + fsparam_u32oct("dmask", Opt_dmask), + fsparam_u32oct("fmask", Opt_fmask), + fsparam_string("nls", Opt_nls), + fsparam_string("iocharset", Opt_charset), + fsparam_enum("errors", Opt_errors, ntfs_param_enums), + fsparam_flag("show_sys_files", Opt_show_sys_files), + fsparam_flag("showmeta", Opt_show_meta), + fsparam_flag("case_sensitive", Opt_case_sensitive), + fsparam_flag("disable_sparse", Opt_disable_sparse), + fsparam_s32("mft_zone_multiplier", Opt_mft_zone_multiplier), + fsparam_u64("preallocated_size", Opt_preallocated_size), + fsparam_flag("sys_immutable", Opt_sys_immutable), + fsparam_flag("nohidden", Opt_nohidden), + fsparam_flag("hide_dot_files", Opt_hide_dot_files), + fsparam_flag("windows_names", Opt_check_windows_names), + fsparam_flag("acl", Opt_acl), + fsparam_flag("discard", Opt_discard), + fsparam_flag("sparse", Opt_sparse), + fsparam_flag("nocase", Opt_nocase), + {} +}; + +static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct ntfs_volume *vol = fc->s_fs_info; + struct fs_parse_result result; + int opt; + + opt = fs_parse(fc, ntfs_parameters, param, &result); + if (opt < 0) + return opt; + + switch (opt) { + case Opt_uid: + vol->uid = make_kuid(current_user_ns(), result.uint_32); + break; + case Opt_gid: + vol->gid = make_kgid(current_user_ns(), result.uint_32); + break; + case Opt_umask: + vol->fmask = vol->dmask = result.uint_32; + break; + case Opt_dmask: + vol->dmask = result.uint_32; + break; + case Opt_fmask: + vol->fmask = result.uint_32; + break; + case Opt_errors: + vol->on_errors = result.uint_32; + break; + case Opt_nls: + case Opt_charset: + if (vol->nls_map) + unload_nls(vol->nls_map); + vol->nls_map = load_nls(param->string); + if (!vol->nls_map) { + ntfs_error(vol->sb, "Failed to load NLS table '%s'.", + param->string); + return -EINVAL; + } + break; + case Opt_mft_zone_multiplier: + if (vol->mft_zone_multiplier && vol->mft_zone_multiplier != + result.int_32) { + ntfs_error(vol->sb, "Cannot change mft_zone_multiplier on remount."); + return -EINVAL; + } + if (result.int_32 < 1 || result.int_32 > 4) { + ntfs_error(vol->sb, + "Invalid mft_zone_multiplier. Using default value, i.e. 1."); + vol->mft_zone_multiplier = 1; + } else + vol->mft_zone_multiplier = result.int_32; + break; + case Opt_show_sys_files: + case Opt_show_meta: + if (result.boolean) + NVolSetShowSystemFiles(vol); + else + NVolClearShowSystemFiles(vol); + break; + case Opt_case_sensitive: + if (result.boolean) + NVolSetCaseSensitive(vol); + else + NVolClearCaseSensitive(vol); + break; + case Opt_nocase: + if (result.boolean) + NVolClearCaseSensitive(vol); + else + NVolSetCaseSensitive(vol); + break; + case Opt_preallocated_size: + vol->preallocated_size = (loff_t)result.uint_64; + break; + case Opt_sys_immutable: + if (result.boolean) + NVolSetSysImmutable(vol); + else + NVolClearSysImmutable(vol); + break; + case Opt_nohidden: + if (result.boolean) + NVolClearShowHiddenFiles(vol); + else + NVolSetShowHiddenFiles(vol); + break; + case Opt_hide_dot_files: + if (result.boolean) + NVolSetHideDotFiles(vol); + else + NVolClearHideDotFiles(vol); + break; + case Opt_check_windows_names: + if (result.boolean) + NVolSetCheckWindowsNames(vol); + else + NVolClearCheckWindowsNames(vol); + break; + case Opt_acl: +#ifdef CONFIG_NTFS_FS_POSIX_ACL + if (result.boolean) + fc->sb_flags |= SB_POSIXACL; + else + fc->sb_flags &= ~SB_POSIXACL; + break; +#else + return -EINVAL; +#endif + case Opt_discard: + if (result.boolean) + NVolSetDiscard(vol); + else + NVolClearDiscard(vol); + break; + case Opt_disable_sparse: + if (result.boolean) + NVolSetDisableSparse(vol); + else + NVolClearDisableSparse(vol); + break; + case Opt_sparse: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ntfs_reconfigure(struct fs_context *fc) +{ + struct super_block *sb = fc->root->d_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + + ntfs_debug("Entering with remount"); + + sync_filesystem(sb); + + /* + * For the read-write compiled driver, if we are remounting read-write, + * make sure there are no volume errors and that no unsupported volume + * flags are set. Also, empty the logfile journal as it would become + * stale as soon as something is written to the volume and mark the + * volume dirty so that chkdsk is run if the volume is not umounted + * cleanly. Finally, mark the quotas out of date so Windows rescans + * the volume on boot and updates them. + * + * When remounting read-only, mark the volume clean if no volume errors + * have occurred. + */ + if (sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY)) { + static const char *es = ". Cannot remount read-write."; + + /* Remounting read-write. */ + if (NVolErrors(vol)) { + ntfs_error(sb, "Volume has errors and is read-only%s", + es); + return -EROFS; + } + if (vol->vol_flags & VOLUME_IS_DIRTY) { + ntfs_error(sb, "Volume is dirty and read-only%s", es); + return -EROFS; + } + if (vol->vol_flags & VOLUME_MODIFIED_BY_CHKDSK) { + ntfs_error(sb, "Volume has been modified by chkdsk and is read-only%s", es); + return -EROFS; + } + if (vol->vol_flags & VOLUME_MUST_MOUNT_RO_MASK) { + ntfs_error(sb, "Volume has unsupported flags set (0x%x) and is read-only%s", + le16_to_cpu(vol->vol_flags), es); + return -EROFS; + } + if (vol->logfile_ino && !ntfs_empty_logfile(vol->logfile_ino)) { + ntfs_error(sb, "Failed to empty journal LogFile%s", + es); + NVolSetErrors(vol); + return -EROFS; + } + if (!ntfs_mark_quotas_out_of_date(vol)) { + ntfs_error(sb, "Failed to mark quotas out of date%s", + es); + NVolSetErrors(vol); + return -EROFS; + } + } else if (!sb_rdonly(sb) && (fc->sb_flags & SB_RDONLY)) { + /* Remounting read-only. */ + if (!NVolErrors(vol)) { + if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) + ntfs_warning(sb, + "Failed to clear dirty bit in volume information flags. Run chkdsk."); + } + } + + ntfs_debug("Done."); + return 0; +} + +const struct option_t on_errors_arr[] = { + { ON_ERRORS_PANIC, "panic" }, + { ON_ERRORS_REMOUNT_RO, "remount-ro", }, + { ON_ERRORS_CONTINUE, "continue", }, + { 0, NULL } +}; + +void ntfs_handle_error(struct super_block *sb) +{ + struct ntfs_volume *vol = NTFS_SB(sb); + + if (sb_rdonly(sb)) + return; + + if (vol->on_errors == ON_ERRORS_REMOUNT_RO) { + sb->s_flags |= SB_RDONLY; + pr_crit("(device %s): Filesystem has been set read-only\n", + sb->s_id); + } else if (vol->on_errors == ON_ERRORS_PANIC) { + panic("ntfs: (device %s): panic from previous error\n", + sb->s_id); + } else if (vol->on_errors == ON_ERRORS_CONTINUE) { + if (errseq_check(&sb->s_wb_err, vol->wb_err) == -ENODEV) { + NVolSetShutdown(vol); + vol->wb_err = sb->s_wb_err; + } + } +} + +/* + * ntfs_write_volume_flags - write new flags to the volume information flags + * @vol: ntfs volume on which to modify the flags + * @flags: new flags value for the volume information flags + * + * Internal function. You probably want to use ntfs_{set,clear}_volume_flags() + * instead (see below). + * + * Replace the volume information flags on the volume @vol with the value + * supplied in @flags. Note, this overwrites the volume information flags, so + * make sure to combine the flags you want to modify with the old flags and use + * the result when calling ntfs_write_volume_flags(). + * + * Return 0 on success and -errno on error. + */ +static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags) +{ + struct ntfs_inode *ni = NTFS_I(vol->vol_ino); + struct volume_information *vi; + struct ntfs_attr_search_ctx *ctx; + int err; + + ntfs_debug("Entering, old flags = 0x%x, new flags = 0x%x.", + le16_to_cpu(vol->vol_flags), le16_to_cpu(flags)); + mutex_lock(&ni->mrec_lock); + if (vol->vol_flags == flags) + goto done; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = -ENOMEM; + goto put_unm_err_out; + } + + err = ntfs_attr_lookup(AT_VOLUME_INFORMATION, NULL, 0, 0, 0, NULL, 0, + ctx); + if (err) + goto put_unm_err_out; + + vi = (struct volume_information *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + vol->vol_flags = vi->flags = flags; + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); +done: + mutex_unlock(&ni->mrec_lock); + ntfs_debug("Done."); + return 0; +put_unm_err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&ni->mrec_lock); + ntfs_error(vol->sb, "Failed with error code %i.", -err); + return err; +} + +/* + * ntfs_set_volume_flags - set bits in the volume information flags + * @vol: ntfs volume on which to modify the flags + * @flags: flags to set on the volume + * + * Set the bits in @flags in the volume information flags on the volume @vol. + * + * Return 0 on success and -errno on error. + */ +int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags) +{ + flags &= VOLUME_FLAGS_MASK; + return ntfs_write_volume_flags(vol, vol->vol_flags | flags); +} + +/* + * ntfs_clear_volume_flags - clear bits in the volume information flags + * @vol: ntfs volume on which to modify the flags + * @flags: flags to clear on the volume + * + * Clear the bits in @flags in the volume information flags on the volume @vol. + * + * Return 0 on success and -errno on error. + */ +int ntfs_clear_volume_flags(struct ntfs_volume *vol, __le16 flags) +{ + flags &= VOLUME_FLAGS_MASK; + flags = vol->vol_flags & cpu_to_le16(~le16_to_cpu(flags)); + return ntfs_write_volume_flags(vol, flags); +} + +int ntfs_write_volume_label(struct ntfs_volume *vol, char *label) +{ + struct ntfs_inode *vol_ni = NTFS_I(vol->vol_ino); + struct ntfs_attr_search_ctx *ctx; + __le16 *uname; + int uname_len, ret; + + uname_len = ntfs_nlstoucs(vol, label, strlen(label), + &uname, FSLABEL_MAX); + if (uname_len < 0) { + ntfs_error(vol->sb, + "Failed to convert volume label '%s' to Unicode.", + label); + return uname_len; + } + + if (uname_len > NTFS_MAX_LABEL_LEN) { + ntfs_error(vol->sb, + "Volume label is too long (max %d characters).", + NTFS_MAX_LABEL_LEN); + kvfree(uname); + return -EINVAL; + } + + mutex_lock(&vol_ni->mrec_lock); + ctx = ntfs_attr_get_search_ctx(vol_ni, NULL); + if (!ctx) { + ret = -ENOMEM; + goto out; + } + + if (!ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0, + ctx)) + ntfs_attr_record_rm(ctx); + ntfs_attr_put_search_ctx(ctx); + + ret = ntfs_resident_attr_record_add(vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0, + (u8 *)uname, uname_len * sizeof(__le16), 0); +out: + mutex_unlock(&vol_ni->mrec_lock); + kvfree(uname); + mark_inode_dirty_sync(vol->vol_ino); + + if (ret >= 0) { + kfree(vol->volume_label); + vol->volume_label = kstrdup(label, GFP_KERNEL); + ret = 0; + } + return ret; +} + +/* + * is_boot_sector_ntfs - check whether a boot sector is a valid NTFS boot sector + * @sb: Super block of the device to which @b belongs. + * @b: Boot sector of device @sb to check. + * @silent: If 'true', all output will be silenced. + * + * is_boot_sector_ntfs() checks whether the boot sector @b is a valid NTFS boot + * sector. Returns 'true' if it is valid and 'false' if not. + * + * @sb is only needed for warning/error output, i.e. it can be NULL when silent + * is 'true'. + */ +static bool is_boot_sector_ntfs(const struct super_block *sb, + const struct ntfs_boot_sector *b, const bool silent) +{ + /* + * Check that checksum == sum of u32 values from b to the checksum + * field. If checksum is zero, no checking is done. We will work when + * the checksum test fails, since some utilities update the boot sector + * ignoring the checksum which leaves the checksum out-of-date. We + * report a warning if this is the case. + */ + if ((void *)b < (void *)&b->checksum && b->checksum && !silent) { + __le32 *u; + u32 i; + + for (i = 0, u = (__le32 *)b; u < (__le32 *)(&b->checksum); ++u) + i += le32_to_cpup(u); + if (le32_to_cpu(b->checksum) != i) + ntfs_warning(sb, "Invalid boot sector checksum."); + } + /* Check OEMidentifier is "NTFS " */ + if (b->oem_id != magicNTFS) + goto not_ntfs; + /* Check bytes per sector value is between 256 and 4096. */ + if (le16_to_cpu(b->bpb.bytes_per_sector) < 0x100 || + le16_to_cpu(b->bpb.bytes_per_sector) > 0x1000) + goto not_ntfs; + /* + * Check sectors per cluster value is valid and the cluster size + * is not above the maximum (2MB). + */ + if (b->bpb.sectors_per_cluster > 0x80 && + b->bpb.sectors_per_cluster < 0xf4) + goto not_ntfs; + + /* Check reserved/unused fields are really zero. */ + if (le16_to_cpu(b->bpb.reserved_sectors) || + le16_to_cpu(b->bpb.root_entries) || + le16_to_cpu(b->bpb.sectors) || + le16_to_cpu(b->bpb.sectors_per_fat) || + le32_to_cpu(b->bpb.large_sectors) || b->bpb.fats) + goto not_ntfs; + /* Check clusters per file mft record value is valid. */ + if ((u8)b->clusters_per_mft_record < 0xe1 || + (u8)b->clusters_per_mft_record > 0xf7) + switch (b->clusters_per_mft_record) { + case 1: case 2: case 4: case 8: case 16: case 32: case 64: + break; + default: + goto not_ntfs; + } + /* Check clusters per index block value is valid. */ + if ((u8)b->clusters_per_index_record < 0xe1 || + (u8)b->clusters_per_index_record > 0xf7) + switch (b->clusters_per_index_record) { + case 1: case 2: case 4: case 8: case 16: case 32: case 64: + break; + default: + goto not_ntfs; + } + /* + * Check for valid end of sector marker. We will work without it, but + * many BIOSes will refuse to boot from a bootsector if the magic is + * incorrect, so we emit a warning. + */ + if (!silent && b->end_of_sector_marker != cpu_to_le16(0xaa55)) + ntfs_warning(sb, "Invalid end of sector marker."); + return true; +not_ntfs: + return false; +} + +/* + * read_ntfs_boot_sector - read the NTFS boot sector of a device + * @sb: super block of device to read the boot sector from + * @silent: if true, suppress all output + * + * Reads the boot sector from the device and validates it. + */ +static char *read_ntfs_boot_sector(struct super_block *sb, + const int silent) +{ + char *boot_sector; + + boot_sector = kzalloc(PAGE_SIZE, GFP_NOFS); + if (!boot_sector) + return NULL; + + if (ntfs_bdev_read(sb->s_bdev, boot_sector, 0, PAGE_SIZE)) { + if (!silent) + ntfs_error(sb, "Unable to read primary boot sector."); + kfree(boot_sector); + return NULL; + } + + if (!is_boot_sector_ntfs(sb, (struct ntfs_boot_sector *)boot_sector, + silent)) { + if (!silent) + ntfs_error(sb, "Primary boot sector is invalid."); + kfree(boot_sector); + return NULL; + } + + return boot_sector; +} + +/* + * parse_ntfs_boot_sector - parse the boot sector and store the data in @vol + * @vol: volume structure to initialise with data from boot sector + * @b: boot sector to parse + * + * Parse the ntfs boot sector @b and store all imporant information therein in + * the ntfs super block @vol. Return 'true' on success and 'false' on error. + */ +static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, + const struct ntfs_boot_sector *b) +{ + unsigned int sectors_per_cluster, sectors_per_cluster_bits, nr_hidden_sects; + int clusters_per_mft_record, clusters_per_index_record; + s64 ll; + + vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector); + vol->sector_size_bits = ffs(vol->sector_size) - 1; + ntfs_debug("vol->sector_size = %i (0x%x)", vol->sector_size, + vol->sector_size); + ntfs_debug("vol->sector_size_bits = %i (0x%x)", vol->sector_size_bits, + vol->sector_size_bits); + if (vol->sector_size < vol->sb->s_blocksize) { + ntfs_error(vol->sb, + "Sector size (%i) is smaller than the device block size (%lu). This is not supported.", + vol->sector_size, vol->sb->s_blocksize); + return false; + } + + if (b->bpb.sectors_per_cluster >= 0xf4) + sectors_per_cluster = 1U << -(s8)b->bpb.sectors_per_cluster; + else + sectors_per_cluster = b->bpb.sectors_per_cluster; + ntfs_debug("sectors_per_cluster = 0x%x", b->bpb.sectors_per_cluster); + sectors_per_cluster_bits = ffs(sectors_per_cluster) - 1; + ntfs_debug("sectors_per_cluster_bits = 0x%x", + sectors_per_cluster_bits); + nr_hidden_sects = le32_to_cpu(b->bpb.hidden_sectors); + ntfs_debug("number of hidden sectors = 0x%x", nr_hidden_sects); + vol->cluster_size = vol->sector_size << sectors_per_cluster_bits; + vol->cluster_size_mask = vol->cluster_size - 1; + vol->cluster_size_bits = ffs(vol->cluster_size) - 1; + ntfs_debug("vol->cluster_size = %i (0x%x)", vol->cluster_size, + vol->cluster_size); + ntfs_debug("vol->cluster_size_mask = 0x%x", vol->cluster_size_mask); + ntfs_debug("vol->cluster_size_bits = %i", vol->cluster_size_bits); + if (vol->cluster_size < vol->sector_size) { + ntfs_error(vol->sb, + "Cluster size (%i) is smaller than the sector size (%i). This is not supported.", + vol->cluster_size, vol->sector_size); + return false; + } + clusters_per_mft_record = b->clusters_per_mft_record; + ntfs_debug("clusters_per_mft_record = %i (0x%x)", + clusters_per_mft_record, clusters_per_mft_record); + if (clusters_per_mft_record > 0) + vol->mft_record_size = vol->cluster_size << + (ffs(clusters_per_mft_record) - 1); + else + /* + * When mft_record_size < cluster_size, clusters_per_mft_record + * = -log2(mft_record_size) bytes. mft_record_size normaly is + * 1024 bytes, which is encoded as 0xF6 (-10 in decimal). + */ + vol->mft_record_size = 1 << -clusters_per_mft_record; + vol->mft_record_size_mask = vol->mft_record_size - 1; + vol->mft_record_size_bits = ffs(vol->mft_record_size) - 1; + ntfs_debug("vol->mft_record_size = %i (0x%x)", vol->mft_record_size, + vol->mft_record_size); + ntfs_debug("vol->mft_record_size_mask = 0x%x", + vol->mft_record_size_mask); + ntfs_debug("vol->mft_record_size_bits = %i (0x%x)", + vol->mft_record_size_bits, vol->mft_record_size_bits); + /* + * We cannot support mft record sizes above the PAGE_SIZE since + * we store $MFT/$DATA, the table of mft records in the page cache. + */ + if (vol->mft_record_size > PAGE_SIZE) { + ntfs_error(vol->sb, + "Mft record size (%i) exceeds the PAGE_SIZE on your system (%lu). This is not supported.", + vol->mft_record_size, PAGE_SIZE); + return false; + } + /* We cannot support mft record sizes below the sector size. */ + if (vol->mft_record_size < vol->sector_size) { + ntfs_warning(vol->sb, "Mft record size (%i) is smaller than the sector size (%i).", + vol->mft_record_size, vol->sector_size); + } + clusters_per_index_record = b->clusters_per_index_record; + ntfs_debug("clusters_per_index_record = %i (0x%x)", + clusters_per_index_record, clusters_per_index_record); + if (clusters_per_index_record > 0) + vol->index_record_size = vol->cluster_size << + (ffs(clusters_per_index_record) - 1); + else + /* + * When index_record_size < cluster_size, + * clusters_per_index_record = -log2(index_record_size) bytes. + * index_record_size normaly equals 4096 bytes, which is + * encoded as 0xF4 (-12 in decimal). + */ + vol->index_record_size = 1 << -clusters_per_index_record; + vol->index_record_size_mask = vol->index_record_size - 1; + vol->index_record_size_bits = ffs(vol->index_record_size) - 1; + ntfs_debug("vol->index_record_size = %i (0x%x)", + vol->index_record_size, vol->index_record_size); + ntfs_debug("vol->index_record_size_mask = 0x%x", + vol->index_record_size_mask); + ntfs_debug("vol->index_record_size_bits = %i (0x%x)", + vol->index_record_size_bits, + vol->index_record_size_bits); + /* We cannot support index record sizes below the sector size. */ + if (vol->index_record_size < vol->sector_size) { + ntfs_error(vol->sb, + "Index record size (%i) is smaller than the sector size (%i). This is not supported.", + vol->index_record_size, vol->sector_size); + return false; + } + /* + * Get the size of the volume in clusters and check for 64-bit-ness. + * Windows currently only uses 32 bits to save the clusters so we do + * the same as it is much faster on 32-bit CPUs. + */ + ll = le64_to_cpu(b->number_of_sectors) >> sectors_per_cluster_bits; + if ((u64)ll >= 1ULL << 32) { + ntfs_error(vol->sb, "Cannot handle 64-bit clusters."); + return false; + } + vol->nr_clusters = ll; + ntfs_debug("vol->nr_clusters = 0x%llx", vol->nr_clusters); + ll = le64_to_cpu(b->mft_lcn); + if (ll >= vol->nr_clusters) { + ntfs_error(vol->sb, "MFT LCN (%lli, 0x%llx) is beyond end of volume. Weird.", + ll, ll); + return false; + } + vol->mft_lcn = ll; + ntfs_debug("vol->mft_lcn = 0x%llx", vol->mft_lcn); + ll = le64_to_cpu(b->mftmirr_lcn); + if (ll >= vol->nr_clusters) { + ntfs_error(vol->sb, "MFTMirr LCN (%lli, 0x%llx) is beyond end of volume. Weird.", + ll, ll); + return false; + } + vol->mftmirr_lcn = ll; + ntfs_debug("vol->mftmirr_lcn = 0x%llx", vol->mftmirr_lcn); + /* + * Work out the size of the mft mirror in number of mft records. If the + * cluster size is less than or equal to the size taken by four mft + * records, the mft mirror stores the first four mft records. If the + * cluster size is bigger than the size taken by four mft records, the + * mft mirror contains as many mft records as will fit into one + * cluster. + */ + if (vol->cluster_size <= (4 << vol->mft_record_size_bits)) + vol->mftmirr_size = 4; + else + vol->mftmirr_size = vol->cluster_size >> + vol->mft_record_size_bits; + ntfs_debug("vol->mftmirr_size = %i", vol->mftmirr_size); + vol->serial_no = le64_to_cpu(b->volume_serial_number); + ntfs_debug("vol->serial_no = 0x%llx", vol->serial_no); + + vol->sparse_compression_unit = 4; + if (vol->cluster_size > 4096) { + switch (vol->cluster_size) { + case 65536: + vol->sparse_compression_unit = 0; + break; + case 32768: + vol->sparse_compression_unit = 1; + break; + case 16384: + vol->sparse_compression_unit = 2; + break; + case 8192: + vol->sparse_compression_unit = 3; + break; + } + } + + return true; +} + +/* + * ntfs_setup_allocators - initialize the cluster and mft allocators + * @vol: volume structure for which to setup the allocators + * + * Setup the cluster (lcn) and mft allocators to the starting values. + */ +static void ntfs_setup_allocators(struct ntfs_volume *vol) +{ + s64 mft_zone_size, mft_lcn; + + ntfs_debug("vol->mft_zone_multiplier = 0x%x", + vol->mft_zone_multiplier); + /* Determine the size of the MFT zone. */ + mft_zone_size = vol->nr_clusters; + switch (vol->mft_zone_multiplier) { /* % of volume size in clusters */ + case 4: + mft_zone_size >>= 1; /* 50% */ + break; + case 3: + mft_zone_size = (mft_zone_size + + (mft_zone_size >> 1)) >> 2; /* 37.5% */ + break; + case 2: + mft_zone_size >>= 2; /* 25% */ + break; + /* case 1: */ + default: + mft_zone_size >>= 3; /* 12.5% */ + break; + } + /* Setup the mft zone. */ + vol->mft_zone_start = vol->mft_zone_pos = vol->mft_lcn; + ntfs_debug("vol->mft_zone_pos = 0x%llx", vol->mft_zone_pos); + /* + * Calculate the mft_lcn for an unmodified NTFS volume (see mkntfs + * source) and if the actual mft_lcn is in the expected place or even + * further to the front of the volume, extend the mft_zone to cover the + * beginning of the volume as well. This is in order to protect the + * area reserved for the mft bitmap as well within the mft_zone itself. + * On non-standard volumes we do not protect it as the overhead would + * be higher than the speed increase we would get by doing it. + */ + mft_lcn = NTFS_B_TO_CLU(vol, 8192 + 2 * vol->cluster_size - 1); + if (mft_lcn * vol->cluster_size < 16 * 1024) + mft_lcn = (16 * 1024 + vol->cluster_size - 1) >> + vol->cluster_size_bits; + if (vol->mft_zone_start <= mft_lcn) + vol->mft_zone_start = 0; + ntfs_debug("vol->mft_zone_start = 0x%llx", vol->mft_zone_start); + /* + * Need to cap the mft zone on non-standard volumes so that it does + * not point outside the boundaries of the volume. We do this by + * halving the zone size until we are inside the volume. + */ + vol->mft_zone_end = vol->mft_lcn + mft_zone_size; + while (vol->mft_zone_end >= vol->nr_clusters) { + mft_zone_size >>= 1; + vol->mft_zone_end = vol->mft_lcn + mft_zone_size; + } + ntfs_debug("vol->mft_zone_end = 0x%llx", vol->mft_zone_end); + /* + * Set the current position within each data zone to the start of the + * respective zone. + */ + vol->data1_zone_pos = vol->mft_zone_end; + ntfs_debug("vol->data1_zone_pos = 0x%llx", vol->data1_zone_pos); + vol->data2_zone_pos = 0; + ntfs_debug("vol->data2_zone_pos = 0x%llx", vol->data2_zone_pos); + + /* Set the mft data allocation position to mft record 24. */ + vol->mft_data_pos = 24; + ntfs_debug("vol->mft_data_pos = 0x%llx", vol->mft_data_pos); +} + +static struct lock_class_key mftmirr_runlist_lock_key, + mftmirr_mrec_lock_key; +/* + * load_and_init_mft_mirror - load and setup the mft mirror inode for a volume + * @vol: ntfs super block describing device whose mft mirror to load + * + * Return 'true' on success or 'false' on error. + */ +static bool load_and_init_mft_mirror(struct ntfs_volume *vol) +{ + struct inode *tmp_ino; + struct ntfs_inode *tmp_ni; + + ntfs_debug("Entering."); + /* Get mft mirror inode. */ + tmp_ino = ntfs_iget(vol->sb, FILE_MFTMirr); + if (IS_ERR(tmp_ino)) { + if (!IS_ERR(tmp_ino)) + iput(tmp_ino); + /* Caller will display error message. */ + return false; + } + lockdep_set_class(&NTFS_I(tmp_ino)->runlist.lock, + &mftmirr_runlist_lock_key); + lockdep_set_class(&NTFS_I(tmp_ino)->mrec_lock, + &mftmirr_mrec_lock_key); + /* + * Re-initialize some specifics about $MFTMirr's inode as + * ntfs_read_inode() will have set up the default ones. + */ + /* Set uid and gid to root. */ + tmp_ino->i_uid = GLOBAL_ROOT_UID; + tmp_ino->i_gid = GLOBAL_ROOT_GID; + /* Regular file. No access for anyone. */ + tmp_ino->i_mode = S_IFREG; + /* No VFS initiated operations allowed for $MFTMirr. */ + tmp_ino->i_op = &ntfs_empty_inode_ops; + tmp_ino->i_fop = &ntfs_empty_file_ops; + /* Put in our special address space operations. */ + tmp_ino->i_mapping->a_ops = &ntfs_aops; + tmp_ni = NTFS_I(tmp_ino); + /* The $MFTMirr, like the $MFT is multi sector transfer protected. */ + NInoSetMstProtected(tmp_ni); + NInoSetSparseDisabled(tmp_ni); + /* + * Set up our little cheat allowing us to reuse the async read io + * completion handler for directories. + */ + tmp_ni->itype.index.block_size = vol->mft_record_size; + tmp_ni->itype.index.block_size_bits = vol->mft_record_size_bits; + vol->mftmirr_ino = tmp_ino; + ntfs_debug("Done."); + return true; +} + +/* + * check_mft_mirror - compare contents of the mft mirror with the mft + * @vol: ntfs super block describing device whose mft mirror to check + * + * Return 'true' on success or 'false' on error. + * + * Note, this function also results in the mft mirror runlist being completely + * mapped into memory. The mft mirror write code requires this and will BUG() + * should it find an unmapped runlist element. + */ +static bool check_mft_mirror(struct ntfs_volume *vol) +{ + struct super_block *sb = vol->sb; + struct ntfs_inode *mirr_ni; + struct folio *mft_folio = NULL, *mirr_folio = NULL; + u8 *kmft = NULL, *kmirr = NULL; + struct runlist_element *rl, rl2[2]; + pgoff_t index; + int mrecs_per_page, i; + + ntfs_debug("Entering."); + /* Compare contents of $MFT and $MFTMirr. */ + mrecs_per_page = PAGE_SIZE / vol->mft_record_size; + index = i = 0; + do { + u32 bytes; + + /* Switch pages if necessary. */ + if (!(i % mrecs_per_page)) { + if (index) { + kunmap_local(kmirr); + folio_put(mirr_folio); + kunmap_local(kmft); + folio_put(mft_folio); + } + /* Get the $MFT page. */ + mft_folio = read_mapping_folio(vol->mft_ino->i_mapping, + index, NULL); + if (IS_ERR(mft_folio)) { + ntfs_error(sb, "Failed to read $MFT."); + return false; + } + kmft = kmap_local_folio(mft_folio, 0); + /* Get the $MFTMirr page. */ + mirr_folio = read_mapping_folio(vol->mftmirr_ino->i_mapping, + index, NULL); + if (IS_ERR(mirr_folio)) { + ntfs_error(sb, "Failed to read $MFTMirr."); + goto mft_unmap_out; + } + kmirr = kmap_local_folio(mirr_folio, 0); + ++index; + } + + /* Do not check the record if it is not in use. */ + if (((struct mft_record *)kmft)->flags & MFT_RECORD_IN_USE) { + /* Make sure the record is ok. */ + if (ntfs_is_baad_recordp((__le32 *)kmft)) { + ntfs_error(sb, + "Incomplete multi sector transfer detected in mft record %i.", + i); +mm_unmap_out: + kunmap_local(kmirr); + folio_put(mirr_folio); +mft_unmap_out: + kunmap_local(kmft); + folio_put(mft_folio); + return false; + } + } + /* Do not check the mirror record if it is not in use. */ + if (((struct mft_record *)kmirr)->flags & MFT_RECORD_IN_USE) { + if (ntfs_is_baad_recordp((__le32 *)kmirr)) { + ntfs_error(sb, + "Incomplete multi sector transfer detected in mft mirror record %i.", + i); + goto mm_unmap_out; + } + } + /* Get the amount of data in the current record. */ + bytes = le32_to_cpu(((struct mft_record *)kmft)->bytes_in_use); + if (bytes < sizeof(struct mft_record_old) || + bytes > vol->mft_record_size || + ntfs_is_baad_recordp((__le32 *)kmft)) { + bytes = le32_to_cpu(((struct mft_record *)kmirr)->bytes_in_use); + if (bytes < sizeof(struct mft_record_old) || + bytes > vol->mft_record_size || + ntfs_is_baad_recordp((__le32 *)kmirr)) + bytes = vol->mft_record_size; + } + kmft += vol->mft_record_size; + kmirr += vol->mft_record_size; + } while (++i < vol->mftmirr_size); + /* Release the last folios. */ + kunmap_local(kmirr); + folio_put(mirr_folio); + kunmap_local(kmft); + folio_put(mft_folio); + + /* Construct the mft mirror runlist by hand. */ + rl2[0].vcn = 0; + rl2[0].lcn = vol->mftmirr_lcn; + rl2[0].length = NTFS_B_TO_CLU(vol, vol->mftmirr_size * vol->mft_record_size + + vol->cluster_size - 1); + rl2[1].vcn = rl2[0].length; + rl2[1].lcn = LCN_ENOENT; + rl2[1].length = 0; + /* + * Because we have just read all of the mft mirror, we know we have + * mapped the full runlist for it. + */ + mirr_ni = NTFS_I(vol->mftmirr_ino); + down_read(&mirr_ni->runlist.lock); + rl = mirr_ni->runlist.rl; + /* Compare the two runlists. They must be identical. */ + i = 0; + do { + if (rl2[i].vcn != rl[i].vcn || rl2[i].lcn != rl[i].lcn || + rl2[i].length != rl[i].length) { + ntfs_error(sb, "$MFTMirr location mismatch. Run chkdsk."); + up_read(&mirr_ni->runlist.lock); + return false; + } + } while (rl2[i++].length); + up_read(&mirr_ni->runlist.lock); + ntfs_debug("Done."); + return true; +} + +/* + * load_and_check_logfile - load and check the logfile inode for a volume + * @vol: ntfs volume to load the logfile for + * @rp: on success, set to the restart page header + * + * Return 0 on success or errno on error. + */ +static int load_and_check_logfile(struct ntfs_volume *vol, + struct restart_page_header **rp) +{ + struct inode *tmp_ino; + int err = 0; + + ntfs_debug("Entering."); + tmp_ino = ntfs_iget(vol->sb, FILE_LogFile); + if (IS_ERR(tmp_ino)) { + if (!IS_ERR(tmp_ino)) + iput(tmp_ino); + /* Caller will display error message. */ + return -ENOENT; + } + if (!ntfs_check_logfile(tmp_ino, rp)) + err = -EINVAL; + NInoSetSparseDisabled(NTFS_I(tmp_ino)); + vol->logfile_ino = tmp_ino; + ntfs_debug("Done."); + return err; +} + +#define NTFS_HIBERFIL_HEADER_SIZE 4096 + +/* + * check_windows_hibernation_status - check if Windows is suspended on a volume + * @vol: ntfs super block of device to check + * + * Check if Windows is hibernated on the ntfs volume @vol. This is done by + * looking for the file hiberfil.sys in the root directory of the volume. If + * the file is not present Windows is definitely not suspended. + * + * If hiberfil.sys exists and is less than 4kiB in size it means Windows is + * definitely suspended (this volume is not the system volume). Caveat: on a + * system with many volumes it is possible that the < 4kiB check is bogus but + * for now this should do fine. + * + * If hiberfil.sys exists and is larger than 4kiB in size, we need to read the + * hiberfil header (which is the first 4kiB). If this begins with "hibr", + * Windows is definitely suspended. If it is completely full of zeroes, + * Windows is definitely not hibernated. Any other case is treated as if + * Windows is suspended. This caters for the above mentioned caveat of a + * system with many volumes where no "hibr" magic would be present and there is + * no zero header. + * + * Return 0 if Windows is not hibernated on the volume, >0 if Windows is + * hibernated on the volume, and -errno on error. + */ +static int check_windows_hibernation_status(struct ntfs_volume *vol) +{ + static const __le16 hiberfil[13] = { cpu_to_le16('h'), + cpu_to_le16('i'), cpu_to_le16('b'), + cpu_to_le16('e'), cpu_to_le16('r'), + cpu_to_le16('f'), cpu_to_le16('i'), + cpu_to_le16('l'), cpu_to_le16('.'), + cpu_to_le16('s'), cpu_to_le16('y'), + cpu_to_le16('s'), 0 }; + u64 mref; + struct inode *vi; + struct folio *folio; + u32 *kaddr, *kend, *start_addr = NULL; + struct ntfs_name *name = NULL; + int ret = 1; + + ntfs_debug("Entering."); + /* + * Find the inode number for the hibernation file by looking up the + * filename hiberfil.sys in the root directory. + */ + inode_lock(vol->root_ino); + mref = ntfs_lookup_inode_by_name(NTFS_I(vol->root_ino), hiberfil, 12, + &name); + inode_unlock(vol->root_ino); + kfree(name); + if (IS_ERR_MREF(mref)) { + ret = MREF_ERR(mref); + /* If the file does not exist, Windows is not hibernated. */ + if (ret == -ENOENT) { + ntfs_debug("hiberfil.sys not present. Windows is not hibernated on the volume."); + return 0; + } + /* A real error occurred. */ + ntfs_error(vol->sb, "Failed to find inode number for hiberfil.sys."); + return ret; + } + /* Get the inode. */ + vi = ntfs_iget(vol->sb, MREF(mref)); + if (IS_ERR(vi)) { + if (!IS_ERR(vi)) + iput(vi); + ntfs_error(vol->sb, "Failed to load hiberfil.sys."); + return IS_ERR(vi) ? PTR_ERR(vi) : -EIO; + } + if (unlikely(i_size_read(vi) < NTFS_HIBERFIL_HEADER_SIZE)) { + ntfs_debug("hiberfil.sys is smaller than 4kiB (0x%llx). Windows is hibernated on the volume. This is not the system volume.", + i_size_read(vi)); + goto iput_out; + } + + folio = read_mapping_folio(vi->i_mapping, 0, NULL); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Failed to read from hiberfil.sys."); + ret = PTR_ERR(folio); + goto iput_out; + } + start_addr = (u32 *)kmap_local_folio(folio, 0); + kaddr = start_addr; + if (*(__le32 *)kaddr == cpu_to_le32(0x72626968)/*'hibr'*/) { + ntfs_debug("Magic \"hibr\" found in hiberfil.sys. Windows is hibernated on the volume. This is the system volume."); + goto unm_iput_out; + } + kend = kaddr + NTFS_HIBERFIL_HEADER_SIZE/sizeof(*kaddr); + do { + if (unlikely(*kaddr)) { + ntfs_debug("hiberfil.sys is larger than 4kiB (0x%llx), does not contain the \"hibr\" magic, and does not have a zero header. Windows is hibernated on the volume. This is not the system volume.", + i_size_read(vi)); + goto unm_iput_out; + } + } while (++kaddr < kend); + ntfs_debug("hiberfil.sys contains a zero header. Windows is not hibernated on the volume. This is the system volume."); + ret = 0; +unm_iput_out: + kunmap_local(start_addr); + folio_put(folio); +iput_out: + iput(vi); + return ret; +} + +/* + * load_and_init_quota - load and setup the quota file for a volume if present + * @vol: ntfs super block describing device whose quota file to load + * + * Return 'true' on success or 'false' on error. If $Quota is not present, we + * leave vol->quota_ino as NULL and return success. + */ +static bool load_and_init_quota(struct ntfs_volume *vol) +{ + static const __le16 Quota[7] = { cpu_to_le16('$'), + cpu_to_le16('Q'), cpu_to_le16('u'), + cpu_to_le16('o'), cpu_to_le16('t'), + cpu_to_le16('a'), 0 }; + static __le16 Q[3] = { cpu_to_le16('$'), + cpu_to_le16('Q'), 0 }; + struct ntfs_name *name = NULL; + u64 mref; + struct inode *tmp_ino; + + ntfs_debug("Entering."); + /* + * Find the inode number for the quota file by looking up the filename + * $Quota in the extended system files directory $Extend. + */ + inode_lock(vol->extend_ino); + mref = ntfs_lookup_inode_by_name(NTFS_I(vol->extend_ino), Quota, 6, + &name); + inode_unlock(vol->extend_ino); + kfree(name); + if (IS_ERR_MREF(mref)) { + /* + * If the file does not exist, quotas are disabled and have + * never been enabled on this volume, just return success. + */ + if (MREF_ERR(mref) == -ENOENT) { + ntfs_debug("$Quota not present. Volume does not have quotas enabled."); + /* + * No need to try to set quotas out of date if they are + * not enabled. + */ + NVolSetQuotaOutOfDate(vol); + return true; + } + /* A real error occurred. */ + ntfs_error(vol->sb, "Failed to find inode number for $Quota."); + return false; + } + /* Get the inode. */ + tmp_ino = ntfs_iget(vol->sb, MREF(mref)); + if (IS_ERR(tmp_ino)) { + if (!IS_ERR(tmp_ino)) + iput(tmp_ino); + ntfs_error(vol->sb, "Failed to load $Quota."); + return false; + } + vol->quota_ino = tmp_ino; + /* Get the $Q index allocation attribute. */ + tmp_ino = ntfs_index_iget(vol->quota_ino, Q, 2); + if (IS_ERR(tmp_ino)) { + ntfs_error(vol->sb, "Failed to load $Quota/$Q index."); + return false; + } + vol->quota_q_ino = tmp_ino; + ntfs_debug("Done."); + return true; +} + +/* + * load_and_init_attrdef - load the attribute definitions table for a volume + * @vol: ntfs super block describing device whose attrdef to load + * + * Return 'true' on success or 'false' on error. + */ +static bool load_and_init_attrdef(struct ntfs_volume *vol) +{ + loff_t i_size; + struct super_block *sb = vol->sb; + struct inode *ino; + struct folio *folio; + u8 *addr; + pgoff_t index, max_index; + unsigned int size; + + ntfs_debug("Entering."); + /* Read attrdef table and setup vol->attrdef and vol->attrdef_size. */ + ino = ntfs_iget(sb, FILE_AttrDef); + if (IS_ERR(ino)) { + if (!IS_ERR(ino)) + iput(ino); + goto failed; + } + NInoSetSparseDisabled(NTFS_I(ino)); + /* The size of FILE_AttrDef must be above 0 and fit inside 31 bits. */ + i_size = i_size_read(ino); + if (i_size <= 0 || i_size > 0x7fffffff) + goto iput_failed; + vol->attrdef = kvzalloc(i_size, GFP_NOFS); + if (!vol->attrdef) + goto iput_failed; + index = 0; + max_index = i_size >> PAGE_SHIFT; + size = PAGE_SIZE; + while (index < max_index) { + /* Read the attrdef table and copy it into the linear buffer. */ +read_partial_attrdef_page: + folio = read_mapping_folio(ino->i_mapping, index, NULL); + if (IS_ERR(folio)) + goto free_iput_failed; + addr = kmap_local_folio(folio, 0); + memcpy((u8 *)vol->attrdef + (index++ << PAGE_SHIFT), + addr, size); + kunmap_local(addr); + folio_put(folio); + } + if (size == PAGE_SIZE) { + size = i_size & ~PAGE_MASK; + if (size) + goto read_partial_attrdef_page; + } + vol->attrdef_size = i_size; + ntfs_debug("Read %llu bytes from $AttrDef.", i_size); + iput(ino); + return true; +free_iput_failed: + kvfree(vol->attrdef); + vol->attrdef = NULL; +iput_failed: + iput(ino); +failed: + ntfs_error(sb, "Failed to initialize attribute definition table."); + return false; +} + +/* + * load_and_init_upcase - load the upcase table for an ntfs volume + * @vol: ntfs super block describing device whose upcase to load + * + * Return 'true' on success or 'false' on error. + */ +static bool load_and_init_upcase(struct ntfs_volume *vol) +{ + loff_t i_size; + struct super_block *sb = vol->sb; + struct inode *ino; + struct folio *folio; + u8 *addr; + pgoff_t index, max_index; + unsigned int size; + int i, max; + + ntfs_debug("Entering."); + /* Read upcase table and setup vol->upcase and vol->upcase_len. */ + ino = ntfs_iget(sb, FILE_UpCase); + if (IS_ERR(ino)) { + if (!IS_ERR(ino)) + iput(ino); + goto upcase_failed; + } + /* + * The upcase size must not be above 64k Unicode characters, must not + * be zero and must be a multiple of sizeof(__le16). + */ + i_size = i_size_read(ino); + if (!i_size || i_size & (sizeof(__le16) - 1) || + i_size > 64ULL * 1024 * sizeof(__le16)) + goto iput_upcase_failed; + vol->upcase = kvzalloc(i_size, GFP_NOFS); + if (!vol->upcase) + goto iput_upcase_failed; + index = 0; + max_index = i_size >> PAGE_SHIFT; + size = PAGE_SIZE; + while (index < max_index) { + /* Read the upcase table and copy it into the linear buffer. */ +read_partial_upcase_page: + folio = read_mapping_folio(ino->i_mapping, index, NULL); + if (IS_ERR(folio)) + goto iput_upcase_failed; + addr = kmap_local_folio(folio, 0); + memcpy((char *)vol->upcase + (index++ << PAGE_SHIFT), + addr, size); + kunmap_local(addr); + folio_put(folio); + } + if (size == PAGE_SIZE) { + size = i_size & ~PAGE_MASK; + if (size) + goto read_partial_upcase_page; + } + vol->upcase_len = i_size >> sizeof(unsigned char); + ntfs_debug("Read %llu bytes from $UpCase (expected %zu bytes).", + i_size, 64 * 1024 * sizeof(__le16)); + iput(ino); + mutex_lock(&ntfs_lock); + if (!default_upcase) { + ntfs_debug("Using volume specified $UpCase since default is not present."); + mutex_unlock(&ntfs_lock); + return true; + } + max = default_upcase_len; + if (max > vol->upcase_len) + max = vol->upcase_len; + for (i = 0; i < max; i++) + if (vol->upcase[i] != default_upcase[i]) + break; + if (i == max) { + kvfree(vol->upcase); + vol->upcase = default_upcase; + vol->upcase_len = max; + ntfs_nr_upcase_users++; + mutex_unlock(&ntfs_lock); + ntfs_debug("Volume specified $UpCase matches default. Using default."); + return true; + } + mutex_unlock(&ntfs_lock); + ntfs_debug("Using volume specified $UpCase since it does not match the default."); + return true; +iput_upcase_failed: + iput(ino); + kvfree(vol->upcase); + vol->upcase = NULL; +upcase_failed: + mutex_lock(&ntfs_lock); + if (default_upcase) { + vol->upcase = default_upcase; + vol->upcase_len = default_upcase_len; + ntfs_nr_upcase_users++; + mutex_unlock(&ntfs_lock); + ntfs_error(sb, "Failed to load $UpCase from the volume. Using default."); + return true; + } + mutex_unlock(&ntfs_lock); + ntfs_error(sb, "Failed to initialize upcase table."); + return false; +} + +/* + * The lcn and mft bitmap inodes are NTFS-internal inodes with + * their own special locking rules: + */ +static struct lock_class_key + lcnbmp_runlist_lock_key, lcnbmp_mrec_lock_key, + mftbmp_runlist_lock_key, mftbmp_mrec_lock_key; + +/* + * load_system_files - open the system files using normal functions + * @vol: ntfs super block describing device whose system files to load + * + * Open the system files with normal access functions and complete setting up + * the ntfs super block @vol. + * + * Return 'true' on success or 'false' on error. + */ +static bool load_system_files(struct ntfs_volume *vol) +{ + struct super_block *sb = vol->sb; + struct mft_record *m; + struct volume_information *vi; + struct ntfs_attr_search_ctx *ctx; + struct restart_page_header *rp; + int err; + + ntfs_debug("Entering."); + /* Get mft mirror inode compare the contents of $MFT and $MFTMirr. */ + if (!load_and_init_mft_mirror(vol) || !check_mft_mirror(vol)) { + /* If a read-write mount, convert it to a read-only mount. */ + if (!sb_rdonly(sb) && vol->on_errors == ON_ERRORS_REMOUNT_RO) { + static const char *es1 = "Failed to load $MFTMirr"; + static const char *es2 = "$MFTMirr does not match $MFT"; + static const char *es3 = ". Run ntfsck and/or chkdsk."; + + sb->s_flags |= SB_RDONLY; + ntfs_error(sb, "%s. Mounting read-only%s", + !vol->mftmirr_ino ? es1 : es2, es3); + } + NVolSetErrors(vol); + } + /* Get mft bitmap attribute inode. */ + vol->mftbmp_ino = ntfs_attr_iget(vol->mft_ino, AT_BITMAP, NULL, 0); + if (IS_ERR(vol->mftbmp_ino)) { + ntfs_error(sb, "Failed to load $MFT/$BITMAP attribute."); + goto iput_mirr_err_out; + } + lockdep_set_class(&NTFS_I(vol->mftbmp_ino)->runlist.lock, + &mftbmp_runlist_lock_key); + lockdep_set_class(&NTFS_I(vol->mftbmp_ino)->mrec_lock, + &mftbmp_mrec_lock_key); + /* Read upcase table and setup @vol->upcase and @vol->upcase_len. */ + if (!load_and_init_upcase(vol)) + goto iput_mftbmp_err_out; + /* + * Read attribute definitions table and setup @vol->attrdef and + * @vol->attrdef_size. + */ + if (!load_and_init_attrdef(vol)) + goto iput_upcase_err_out; + /* + * Get the cluster allocation bitmap inode and verify the size, no + * need for any locking at this stage as we are already running + * exclusively as we are mount in progress task. + */ + vol->lcnbmp_ino = ntfs_iget(sb, FILE_Bitmap); + if (IS_ERR(vol->lcnbmp_ino)) { + if (!IS_ERR(vol->lcnbmp_ino)) + iput(vol->lcnbmp_ino); + goto bitmap_failed; + } + lockdep_set_class(&NTFS_I(vol->lcnbmp_ino)->runlist.lock, + &lcnbmp_runlist_lock_key); + lockdep_set_class(&NTFS_I(vol->lcnbmp_ino)->mrec_lock, + &lcnbmp_mrec_lock_key); + + NInoSetSparseDisabled(NTFS_I(vol->lcnbmp_ino)); + if ((vol->nr_clusters + 7) >> 3 > i_size_read(vol->lcnbmp_ino)) { + iput(vol->lcnbmp_ino); +bitmap_failed: + ntfs_error(sb, "Failed to load $Bitmap."); + goto iput_attrdef_err_out; + } + /* + * Get the volume inode and setup our cache of the volume flags and + * version. + */ + vol->vol_ino = ntfs_iget(sb, FILE_Volume); + if (IS_ERR(vol->vol_ino)) { + if (!IS_ERR(vol->vol_ino)) + iput(vol->vol_ino); +volume_failed: + ntfs_error(sb, "Failed to load $Volume."); + goto iput_lcnbmp_err_out; + } + m = map_mft_record(NTFS_I(vol->vol_ino)); + if (IS_ERR(m)) { +iput_volume_failed: + iput(vol->vol_ino); + goto volume_failed; + } + + ctx = ntfs_attr_get_search_ctx(NTFS_I(vol->vol_ino), m); + if (!ctx) { + ntfs_error(sb, "Failed to get attribute search context."); + goto get_ctx_vol_failed; + } + + if (!ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0, ctx) && + !ctx->attr->non_resident && + !(ctx->attr->flags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) && + le32_to_cpu(ctx->attr->data.resident.value_length) > 0) { + err = ntfs_ucstonls(vol, (__le16 *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)), + le32_to_cpu(ctx->attr->data.resident.value_length) / 2, + &vol->volume_label, NTFS_MAX_LABEL_LEN); + if (err < 0) + vol->volume_label = NULL; + } + + if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, NULL, 0, 0, 0, NULL, 0, + ctx) || ctx->attr->non_resident || ctx->attr->flags) { + ntfs_attr_put_search_ctx(ctx); +get_ctx_vol_failed: + unmap_mft_record(NTFS_I(vol->vol_ino)); + goto iput_volume_failed; + } + vi = (struct volume_information *)((char *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + /* Copy the volume flags and version to the struct ntfs_volume structure. */ + vol->vol_flags = vi->flags; + vol->major_ver = vi->major_ver; + vol->minor_ver = vi->minor_ver; + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(NTFS_I(vol->vol_ino)); + pr_info("volume version %i.%i, dev %s, cluster size %d\n", + vol->major_ver, vol->minor_ver, sb->s_id, vol->cluster_size); + + /* Make sure that no unsupported volume flags are set. */ + if (vol->vol_flags & VOLUME_MUST_MOUNT_RO_MASK) { + static const char *es1a = "Volume is dirty"; + static const char *es1b = "Volume has been modified by chkdsk"; + static const char *es1c = "Volume has unsupported flags set"; + static const char *es2a = ". Run chkdsk and mount in Windows."; + static const char *es2b = ". Mount in Windows."; + const char *es1, *es2; + + es2 = es2a; + if (vol->vol_flags & VOLUME_IS_DIRTY) + es1 = es1a; + else if (vol->vol_flags & VOLUME_MODIFIED_BY_CHKDSK) { + es1 = es1b; + es2 = es2b; + } else { + es1 = es1c; + ntfs_warning(sb, "Unsupported volume flags 0x%x encountered.", + (unsigned int)le16_to_cpu(vol->vol_flags)); + } + /* If a read-write mount, convert it to a read-only mount. */ + if (!sb_rdonly(sb) && vol->on_errors == ON_ERRORS_REMOUNT_RO) { + sb->s_flags |= SB_RDONLY; + ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); + } + /* + * Do not set NVolErrors() because ntfs_remount() re-checks the + * flags which we need to do in case any flags have changed. + */ + } + /* + * Get the inode for the logfile, check it and determine if the volume + * was shutdown cleanly. + */ + rp = NULL; + err = load_and_check_logfile(vol, &rp); + if (err) { + /* If a read-write mount, convert it to a read-only mount. */ + if (!sb_rdonly(sb) && vol->on_errors == ON_ERRORS_REMOUNT_RO) { + sb->s_flags |= SB_RDONLY; + ntfs_error(sb, "Failed to load LogFile. Mounting read-only."); + } + NVolSetErrors(vol); + } + + kvfree(rp); + /* Get the root directory inode so we can do path lookups. */ + vol->root_ino = ntfs_iget(sb, FILE_root); + if (IS_ERR(vol->root_ino)) { + if (!IS_ERR(vol->root_ino)) + iput(vol->root_ino); + ntfs_error(sb, "Failed to load root directory."); + goto iput_logfile_err_out; + } + /* + * Check if Windows is suspended to disk on the target volume. If it + * is hibernated, we must not write *anything* to the disk so set + * NVolErrors() without setting the dirty volume flag and mount + * read-only. This will prevent read-write remounting and it will also + * prevent all writes. + */ + err = check_windows_hibernation_status(vol); + if (unlikely(err)) { + static const char *es1a = "Failed to determine if Windows is hibernated"; + static const char *es1b = "Windows is hibernated"; + static const char *es2 = ". Run chkdsk."; + const char *es1; + + es1 = err < 0 ? es1a : es1b; + /* If a read-write mount, convert it to a read-only mount. */ + if (!sb_rdonly(sb) && vol->on_errors == ON_ERRORS_REMOUNT_RO) { + sb->s_flags |= SB_RDONLY; + ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); + } + NVolSetErrors(vol); + } + + /* If (still) a read-write mount, empty the logfile. */ + if (!sb_rdonly(sb) && + vol->logfile_ino && !ntfs_empty_logfile(vol->logfile_ino) && + vol->on_errors == ON_ERRORS_REMOUNT_RO) { + static const char *es1 = "Failed to empty LogFile"; + static const char *es2 = ". Mount in Windows."; + + /* Convert to a read-only mount. */ + ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); + sb->s_flags |= SB_RDONLY; + NVolSetErrors(vol); + } + /* If on NTFS versions before 3.0, we are done. */ + if (unlikely(vol->major_ver < 3)) + return true; + /* NTFS 3.0+ specific initialization. */ + /* Get the security descriptors inode. */ + vol->secure_ino = ntfs_iget(sb, FILE_Secure); + if (IS_ERR(vol->secure_ino)) { + if (!IS_ERR(vol->secure_ino)) + iput(vol->secure_ino); + ntfs_error(sb, "Failed to load $Secure."); + goto iput_root_err_out; + } + /* Get the extended system files' directory inode. */ + vol->extend_ino = ntfs_iget(sb, FILE_Extend); + if (IS_ERR(vol->extend_ino) || + !S_ISDIR(vol->extend_ino->i_mode)) { + if (!IS_ERR(vol->extend_ino)) + iput(vol->extend_ino); + ntfs_error(sb, "Failed to load $Extend."); + goto iput_sec_err_out; + } + /* Find the quota file, load it if present, and set it up. */ + if (!load_and_init_quota(vol) && + vol->on_errors == ON_ERRORS_REMOUNT_RO) { + static const char *es1 = "Failed to load $Quota"; + static const char *es2 = ". Run chkdsk."; + + sb->s_flags |= SB_RDONLY; + ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); + /* This will prevent a read-write remount. */ + NVolSetErrors(vol); + } + + return true; + +iput_sec_err_out: + iput(vol->secure_ino); +iput_root_err_out: + iput(vol->root_ino); +iput_logfile_err_out: + if (vol->logfile_ino) + iput(vol->logfile_ino); + iput(vol->vol_ino); +iput_lcnbmp_err_out: + iput(vol->lcnbmp_ino); +iput_attrdef_err_out: + vol->attrdef_size = 0; + if (vol->attrdef) { + kvfree(vol->attrdef); + vol->attrdef = NULL; + } +iput_upcase_err_out: + vol->upcase_len = 0; + mutex_lock(&ntfs_lock); + if (vol->upcase == default_upcase) { + ntfs_nr_upcase_users--; + vol->upcase = NULL; + } + mutex_unlock(&ntfs_lock); + if (vol->upcase) { + kvfree(vol->upcase); + vol->upcase = NULL; + } +iput_mftbmp_err_out: + iput(vol->mftbmp_ino); +iput_mirr_err_out: + iput(vol->mftmirr_ino); + return false; +} + +static void ntfs_volume_free(struct ntfs_volume *vol) +{ + /* Throw away the table of attribute definitions. */ + vol->attrdef_size = 0; + if (vol->attrdef) { + kvfree(vol->attrdef); + vol->attrdef = NULL; + } + vol->upcase_len = 0; + /* + * Destroy the global default upcase table if necessary. Also decrease + * the number of upcase users if we are a user. + */ + mutex_lock(&ntfs_lock); + if (vol->upcase == default_upcase) { + ntfs_nr_upcase_users--; + vol->upcase = NULL; + } + + if (!ntfs_nr_upcase_users && default_upcase) { + kvfree(default_upcase); + default_upcase = NULL; + } + + free_compression_buffers(); + + mutex_unlock(&ntfs_lock); + if (vol->upcase) { + kvfree(vol->upcase); + vol->upcase = NULL; + } + + unload_nls(vol->nls_map); + + if (vol->lcn_empty_bits_per_page) + kvfree(vol->lcn_empty_bits_per_page); + kfree(vol->volume_label); + kfree(vol); +} + +/* + * ntfs_put_super - called by the vfs to unmount a volume + * @sb: vfs superblock of volume to unmount + */ +static void ntfs_put_super(struct super_block *sb) +{ + struct ntfs_volume *vol = NTFS_SB(sb); + + pr_info("Entering %s, dev %s\n", __func__, sb->s_id); + + cancel_work_sync(&vol->precalc_work); + + /* + * Commit all inodes while they are still open in case some of them + * cause others to be dirtied. + */ + ntfs_commit_inode(vol->vol_ino); + + /* NTFS 3.0+ specific. */ + if (vol->major_ver >= 3) { + if (vol->quota_q_ino) + ntfs_commit_inode(vol->quota_q_ino); + if (vol->quota_ino) + ntfs_commit_inode(vol->quota_ino); + if (vol->extend_ino) + ntfs_commit_inode(vol->extend_ino); + if (vol->secure_ino) + ntfs_commit_inode(vol->secure_ino); + } + + ntfs_commit_inode(vol->root_ino); + + ntfs_commit_inode(vol->lcnbmp_ino); + + /* + * the GFP_NOFS scope is not needed because ntfs_commit_inode + * does nothing + */ + ntfs_commit_inode(vol->mftbmp_ino); + + if (vol->logfile_ino) + ntfs_commit_inode(vol->logfile_ino); + + if (vol->mftmirr_ino) + ntfs_commit_inode(vol->mftmirr_ino); + ntfs_commit_inode(vol->mft_ino); + + /* + * If a read-write mount and no volume errors have occurred, mark the + * volume clean. Also, re-commit all affected inodes. + */ + if (!sb_rdonly(sb)) { + if (!NVolErrors(vol)) { + if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) + ntfs_warning(sb, + "Failed to clear dirty bit in volume information flags. Run chkdsk."); + ntfs_commit_inode(vol->vol_ino); + ntfs_commit_inode(vol->root_ino); + if (vol->mftmirr_ino) + ntfs_commit_inode(vol->mftmirr_ino); + ntfs_commit_inode(vol->mft_ino); + } else { + ntfs_warning(sb, + "Volume has errors. Leaving volume marked dirty. Run chkdsk."); + } + } + + iput(vol->vol_ino); + vol->vol_ino = NULL; + + /* NTFS 3.0+ specific clean up. */ + if (vol->major_ver >= 3) { + if (vol->quota_q_ino) { + iput(vol->quota_q_ino); + vol->quota_q_ino = NULL; + } + if (vol->quota_ino) { + iput(vol->quota_ino); + vol->quota_ino = NULL; + } + if (vol->extend_ino) { + iput(vol->extend_ino); + vol->extend_ino = NULL; + } + if (vol->secure_ino) { + iput(vol->secure_ino); + vol->secure_ino = NULL; + } + } + + iput(vol->root_ino); + vol->root_ino = NULL; + + iput(vol->lcnbmp_ino); + vol->lcnbmp_ino = NULL; + + iput(vol->mftbmp_ino); + vol->mftbmp_ino = NULL; + + if (vol->logfile_ino) { + iput(vol->logfile_ino); + vol->logfile_ino = NULL; + } + if (vol->mftmirr_ino) { + /* Re-commit the mft mirror and mft just in case. */ + ntfs_commit_inode(vol->mftmirr_ino); + ntfs_commit_inode(vol->mft_ino); + iput(vol->mftmirr_ino); + vol->mftmirr_ino = NULL; + } + /* + * We should have no dirty inodes left, due to + * mft.c::ntfs_mft_writepage() cleaning all the dirty pages as + * the underlying mft records are written out and cleaned. + */ + ntfs_commit_inode(vol->mft_ino); + write_inode_now(vol->mft_ino, 1); + + iput(vol->mft_ino); + vol->mft_ino = NULL; + blkdev_issue_flush(sb->s_bdev); + + ntfs_volume_free(vol); +} + +int ntfs_force_shutdown(struct super_block *sb, u32 flags) +{ + struct ntfs_volume *vol = NTFS_SB(sb); + int ret; + + if (NVolShutdown(vol)) + return 0; + + switch (flags) { + case FS_SHUTDOWN_FLAGS_DEFAULT: + case FS_SHUTDOWN_FLAGS_LOGFLUSH: + ret = bdev_freeze(sb->s_bdev); + if (ret) + return ret; + bdev_thaw(sb->s_bdev); + NVolSetShutdown(vol); + break; + case FS_SHUTDOWN_FLAGS_NOLOGFLUSH: + NVolSetShutdown(vol); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void ntfs_shutdown(struct super_block *sb) +{ + ntfs_force_shutdown(sb, FS_SHUTDOWN_FLAGS_NOLOGFLUSH); + +} + +static int ntfs_sync_fs(struct super_block *sb, int wait) +{ + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + + if (NVolShutdown(vol)) + return -EIO; + + if (!wait) + return 0; + + /* If there are some dirty buffers in the bdev inode */ + if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) { + ntfs_warning(sb, "Failed to clear dirty bit in volume information flags. Run chkdsk."); + err = -EIO; + } + sync_inodes_sb(sb); + sync_blockdev(sb->s_bdev); + blkdev_issue_flush(sb->s_bdev); + return err; +} + +/* + * get_nr_free_clusters - return the number of free clusters on a volume + * @vol: ntfs volume for which to obtain free cluster count + * + * Calculate the number of free clusters on the mounted NTFS volume @vol. We + * actually calculate the number of clusters in use instead because this + * allows us to not care about partial pages as these will be just zero filled + * and hence not be counted as allocated clusters. + * + * The only particularity is that clusters beyond the end of the logical ntfs + * volume will be marked as allocated to prevent errors which means we have to + * discount those at the end. This is important as the cluster bitmap always + * has a size in multiples of 8 bytes, i.e. up to 63 clusters could be outside + * the logical volume and marked in use when they are not as they do not exist. + * + * If any pages cannot be read we assume all clusters in the erroring pages are + * in use. This means we return an underestimate on errors which is better than + * an overestimate. + */ +s64 get_nr_free_clusters(struct ntfs_volume *vol) +{ + s64 nr_free = vol->nr_clusters; + u32 nr_used; + struct address_space *mapping = vol->lcnbmp_ino->i_mapping; + struct folio *folio; + pgoff_t index, max_index; + struct file_ra_state *ra; + + ntfs_debug("Entering."); + /* Serialize accesses to the cluster bitmap. */ + + if (NVolFreeClusterKnown(vol)) + return atomic64_read(&vol->free_clusters); + + ra = kzalloc(sizeof(*ra), GFP_NOFS); + if (!ra) + return 0; + + file_ra_state_init(ra, mapping); + + /* + * Convert the number of bits into bytes rounded up, then convert into + * multiples of PAGE_SIZE, rounding up so that if we have one + * full and one partial page max_index = 2. + */ + max_index = (((vol->nr_clusters + 7) >> 3) + PAGE_SIZE - 1) >> + PAGE_SHIFT; + /* Use multiples of 4 bytes, thus max_size is PAGE_SIZE / 4. */ + ntfs_debug("Reading $Bitmap, max_index = 0x%lx, max_size = 0x%lx.", + max_index, PAGE_SIZE / 4); + for (index = 0; index < max_index; index++) { + unsigned long *kaddr; + + /* + * Get folio from page cache, getting it from backing store + * if necessary, and increment the use count. + */ + folio = ntfs_get_locked_folio(mapping, index, max_index, ra); + + /* Ignore pages which errored synchronously. */ + if (IS_ERR(folio)) { + ntfs_debug("Skipping page (index 0x%lx).", index); + nr_free -= PAGE_SIZE * 8; + vol->lcn_empty_bits_per_page[index] = 0; + continue; + } + + kaddr = kmap_local_folio(folio, 0); + /* + * Subtract the number of set bits. If this + * is the last page and it is partial we don't really care as + * it just means we do a little extra work but it won't affect + * the result as all out of range bytes are set to zero by + * ntfs_readpage(). + */ + nr_used = bitmap_weight(kaddr, PAGE_SIZE * BITS_PER_BYTE); + nr_free -= nr_used; + vol->lcn_empty_bits_per_page[index] = PAGE_SIZE * BITS_PER_BYTE - nr_used; + kunmap_local(kaddr); + folio_unlock(folio); + folio_put(folio); + } + ntfs_debug("Finished reading $Bitmap, last index = 0x%lx.", index - 1); + /* + * Fixup for eventual bits outside logical ntfs volume (see function + * description above). + */ + if (vol->nr_clusters & 63) + nr_free += 64 - (vol->nr_clusters & 63); + + /* If errors occurred we may well have gone below zero, fix this. */ + if (nr_free < 0) + nr_free = 0; + else + atomic64_set(&vol->free_clusters, nr_free); + + kfree(ra); + NVolSetFreeClusterKnown(vol); + wake_up_all(&vol->free_waitq); + ntfs_debug("Exiting."); + return nr_free; +} + +/* + * @nr_clusters is the number of clusters requested for allocation. + * + * Return the number of clusters available for allocation within + * the range of @nr_clusters, which is counts that considered + * for delayed allocation. + */ +s64 ntfs_available_clusters_count(struct ntfs_volume *vol, s64 nr_clusters) +{ + s64 free_clusters; + + /* wait event */ + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + + free_clusters = atomic64_read(&vol->free_clusters) - + atomic64_read(&vol->dirty_clusters); + if (free_clusters <= 0) + return -ENOSPC; + else if (free_clusters < nr_clusters) + nr_clusters = free_clusters; + + return nr_clusters; +} + +/* + * __get_nr_free_mft_records - return the number of free inodes on a volume + * @vol: ntfs volume for which to obtain free inode count + * @nr_free: number of mft records in filesystem + * @max_index: maximum number of pages containing set bits + * + * Calculate the number of free mft records (inodes) on the mounted NTFS + * volume @vol. We actually calculate the number of mft records in use instead + * because this allows us to not care about partial pages as these will be just + * zero filled and hence not be counted as allocated mft record. + * + * If any pages cannot be read we assume all mft records in the erroring pages + * are in use. This means we return an underestimate on errors which is better + * than an overestimate. + * + * NOTE: Caller must hold mftbmp_lock rw_semaphore for reading or writing. + */ +static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, + s64 nr_free, const pgoff_t max_index) +{ + struct address_space *mapping = vol->mftbmp_ino->i_mapping; + struct folio *folio; + pgoff_t index; + struct file_ra_state *ra; + + ntfs_debug("Entering."); + + ra = kzalloc(sizeof(*ra), GFP_NOFS); + if (!ra) + return 0; + + file_ra_state_init(ra, mapping); + + /* Use multiples of 4 bytes, thus max_size is PAGE_SIZE / 4. */ + ntfs_debug("Reading $MFT/$BITMAP, max_index = 0x%lx, max_size = 0x%lx.", + max_index, PAGE_SIZE / 4); + for (index = 0; index < max_index; index++) { + unsigned long *kaddr; + + /* + * Get folio from page cache, getting it from backing store + * if necessary, and increment the use count. + */ + folio = ntfs_get_locked_folio(mapping, index, max_index, ra); + + /* Ignore pages which errored synchronously. */ + if (IS_ERR(folio)) { + ntfs_debug("read_mapping_page() error. Skipping page (index 0x%lx).", + index); + nr_free -= PAGE_SIZE * 8; + continue; + } + + kaddr = kmap_local_folio(folio, 0); + /* + * Subtract the number of set bits. If this + * is the last page and it is partial we don't really care as + * it just means we do a little extra work but it won't affect + * the result as all out of range bytes are set to zero by + * ntfs_readpage(). + */ + nr_free -= bitmap_weight(kaddr, + PAGE_SIZE * BITS_PER_BYTE); + kunmap_local(kaddr); + folio_unlock(folio); + folio_put(folio); + } + ntfs_debug("Finished reading $MFT/$BITMAP, last index = 0x%lx.", + index - 1); + /* If errors occurred we may well have gone below zero, fix this. */ + if (nr_free < 0) + nr_free = 0; + else + atomic64_set(&vol->free_mft_records, nr_free); + + kfree(ra); + ntfs_debug("Exiting."); + return nr_free; +} + +/* + * ntfs_statfs - return information about mounted NTFS volume + * @dentry: dentry from mounted volume + * @sfs: statfs structure in which to return the information + * + * Return information about the mounted NTFS volume @dentry in the statfs structure + * pointed to by @sfs (this is initialized with zeros before ntfs_statfs is + * called). We interpret the values to be correct of the moment in time at + * which we are called. Most values are variable otherwise and this isn't just + * the free values but the totals as well. For example we can increase the + * total number of file nodes if we run out and we can keep doing this until + * there is no more space on the volume left at all. + * + * Called from vfs_statfs which is used to handle the statfs, fstatfs, and + * ustat system calls. + * + * Return 0 on success or -errno on error. + */ +static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs) +{ + struct super_block *sb = dentry->d_sb; + s64 size; + struct ntfs_volume *vol = NTFS_SB(sb); + struct ntfs_inode *mft_ni = NTFS_I(vol->mft_ino); + unsigned long flags; + + ntfs_debug("Entering."); + /* Type of filesystem. */ + sfs->f_type = NTFS_SB_MAGIC; + /* Optimal transfer block size. */ + sfs->f_bsize = vol->cluster_size; + /* Fundamental file system block size, used as the unit. */ + sfs->f_frsize = vol->cluster_size; + + /* + * Total data blocks in filesystem in units of f_bsize and since + * inodes are also stored in data blocs ($MFT is a file) this is just + * the total clusters. + */ + sfs->f_blocks = vol->nr_clusters; + + /* wait event */ + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + + /* Free data blocks in filesystem in units of f_bsize. */ + size = atomic64_read(&vol->free_clusters) - + atomic64_read(&vol->dirty_clusters); + if (size < 0LL) + size = 0LL; + + /* Free blocks avail to non-superuser, same as above on NTFS. */ + sfs->f_bavail = sfs->f_bfree = size; + + /* Number of inodes in filesystem (at this point in time). */ + read_lock_irqsave(&mft_ni->size_lock, flags); + sfs->f_files = i_size_read(vol->mft_ino) >> vol->mft_record_size_bits; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + + /* Free inodes in fs (based on current total count). */ + sfs->f_ffree = atomic64_read(&vol->free_mft_records); + + /* + * File system id. This is extremely *nix flavour dependent and even + * within Linux itself all fs do their own thing. I interpret this to + * mean a unique id associated with the mounted fs and not the id + * associated with the filesystem driver, the latter is already given + * by the filesystem type in sfs->f_type. Thus we use the 64-bit + * volume serial number splitting it into two 32-bit parts. We enter + * the least significant 32-bits in f_fsid[0] and the most significant + * 32-bits in f_fsid[1]. + */ + sfs->f_fsid = u64_to_fsid(vol->serial_no); + /* Maximum length of filenames. */ + sfs->f_namelen = NTFS_MAX_NAME_LEN; + + return 0; +} + +static int ntfs_write_inode(struct inode *vi, struct writeback_control *wbc) +{ + return __ntfs_write_inode(vi, wbc->sync_mode == WB_SYNC_ALL); +} + +/* + * The complete super operations. + */ +static const struct super_operations ntfs_sops = { + .alloc_inode = ntfs_alloc_big_inode, /* VFS: Allocate new inode. */ + .free_inode = ntfs_free_big_inode, /* VFS: Deallocate inode. */ + .drop_inode = ntfs_drop_big_inode, + .write_inode = ntfs_write_inode, /* VFS: Write dirty inode to disk. */ + .put_super = ntfs_put_super, /* Syscall: umount. */ + .shutdown = ntfs_shutdown, + .sync_fs = ntfs_sync_fs, /* Syscall: sync. */ + .statfs = ntfs_statfs, /* Syscall: statfs */ + .evict_inode = ntfs_evict_big_inode, + .show_options = ntfs_show_options, /* Show mount options in proc. */ +}; + +static void precalc_free_clusters(struct work_struct *work) +{ + struct ntfs_volume *vol = container_of(work, struct ntfs_volume, precalc_work); + s64 nr_free; + + nr_free = get_nr_free_clusters(vol); + + ntfs_debug("pre-calculate free clusters(%lld) using workqueue", + nr_free); +} + +static struct lock_class_key ntfs_mft_inval_lock_key; + +/* + * ntfs_fill_super - mount an ntfs filesystem + * @sb: super block of the device to mount + * @fc: filesystem context containing mount options + * + * ntfs_fill_super() is called by the VFS to mount the device described by @sb + * with the mount otions in @data with the NTFS filesystem. + * + * If @silent is true, remain silent even if errors are detected. This is used + * during bootup, when the kernel tries to mount the root filesystem with all + * registered filesystems one after the other until one succeeds. This implies + * that all filesystems except the correct one will quite correctly and + * expectedly return an error, but nobody wants to see error messages when in + * fact this is what is supposed to happen. + */ +static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + char *boot; + struct inode *tmp_ino; + int blocksize, result; + pgoff_t lcn_bit_pages; + struct ntfs_volume *vol = NTFS_SB(sb); + int silent = fc->sb_flags & SB_SILENT; + + vol->sb = sb; + + /* + * We do a pretty difficult piece of bootstrap by reading the + * MFT (and other metadata) from disk into memory. We'll only + * release this metadata during umount, so the locking patterns + * observed during bootstrap do not count. So turn off the + * observation of locking patterns (strictly for this context + * only) while mounting NTFS. [The validator is still active + * otherwise, even for this context: it will for example record + * lock class registrations.] + */ + lockdep_off(); + ntfs_debug("Entering."); + + if (vol->nls_map && !strcmp(vol->nls_map->charset, "utf8")) + vol->nls_utf8 = true; + if (NVolDisableSparse(vol)) + vol->preallocated_size = 0; + + if (NVolDiscard(vol) && !bdev_max_discard_sectors(sb->s_bdev)) { + ntfs_warning( + sb, + "Discard requested but device does not support discard. Discard disabled."); + NVolClearDiscard(vol); + } + + /* We support sector sizes up to the PAGE_SIZE. */ + if (bdev_logical_block_size(sb->s_bdev) > PAGE_SIZE) { + if (!silent) + ntfs_error(sb, + "Device has unsupported sector size (%i). The maximum supported sector size on this architecture is %lu bytes.", + bdev_logical_block_size(sb->s_bdev), + PAGE_SIZE); + goto err_out_now; + } + + /* + * Setup the device access block size to NTFS_BLOCK_SIZE or the hard + * sector size, whichever is bigger. + */ + blocksize = sb_min_blocksize(sb, NTFS_BLOCK_SIZE); + if (blocksize < NTFS_BLOCK_SIZE) { + if (!silent) + ntfs_error(sb, "Unable to set device block size."); + goto err_out_now; + } + + ntfs_debug("Set device block size to %i bytes (block size bits %i).", + blocksize, sb->s_blocksize_bits); + /* Determine the size of the device in units of block_size bytes. */ + if (!bdev_nr_bytes(sb->s_bdev)) { + if (!silent) + ntfs_error(sb, "Unable to determine device size."); + goto err_out_now; + } + vol->nr_blocks = bdev_nr_bytes(sb->s_bdev) >> + sb->s_blocksize_bits; + /* Read the boot sector and return unlocked buffer head to it. */ + boot = read_ntfs_boot_sector(sb, silent); + if (!boot) { + if (!silent) + ntfs_error(sb, "Not an NTFS volume."); + goto err_out_now; + } + /* + * Extract the data from the boot sector and setup the ntfs volume + * using it. + */ + result = parse_ntfs_boot_sector(vol, (struct ntfs_boot_sector *)boot); + kfree(boot); + if (!result) { + if (!silent) + ntfs_error(sb, "Unsupported NTFS filesystem."); + goto err_out_now; + } + + if (vol->sector_size > blocksize) { + blocksize = sb_set_blocksize(sb, vol->sector_size); + if (blocksize != vol->sector_size) { + if (!silent) + ntfs_error(sb, + "Unable to set device block size to sector size (%i).", + vol->sector_size); + goto err_out_now; + } + vol->nr_blocks = bdev_nr_bytes(sb->s_bdev) >> + sb->s_blocksize_bits; + ntfs_debug("Changed device block size to %i bytes (block size bits %i) to match volume sector size.", + blocksize, sb->s_blocksize_bits); + } + /* Initialize the cluster and mft allocators. */ + ntfs_setup_allocators(vol); + /* Setup remaining fields in the super block. */ + sb->s_magic = NTFS_SB_MAGIC; + /* + * Ntfs allows 63 bits for the file size, i.e. correct would be: + * sb->s_maxbytes = ~0ULL >> 1; + * But the kernel uses a long as the page cache page index which on + * 32-bit architectures is only 32-bits. MAX_LFS_FILESIZE is kernel + * defined to the maximum the page cache page index can cope with + * without overflowing the index or to 2^63 - 1, whichever is smaller. + */ + sb->s_maxbytes = MAX_LFS_FILESIZE; + /* Ntfs measures time in 100ns intervals. */ + sb->s_time_gran = 100; + + sb->s_xattr = ntfs_xattr_handlers; + /* + * Now load the metadata required for the page cache and our address + * space operations to function. We do this by setting up a specialised + * read_inode method and then just calling the normal iget() to obtain + * the inode for $MFT which is sufficient to allow our normal inode + * operations and associated address space operations to function. + */ + sb->s_op = &ntfs_sops; + tmp_ino = new_inode(sb); + if (!tmp_ino) { + if (!silent) + ntfs_error(sb, "Failed to load essential metadata."); + goto err_out_now; + } + + tmp_ino->i_ino = FILE_MFT; + insert_inode_hash(tmp_ino); + if (ntfs_read_inode_mount(tmp_ino) < 0) { + if (!silent) + ntfs_error(sb, "Failed to load essential metadata."); + goto iput_tmp_ino_err_out_now; + } + lockdep_set_class(&tmp_ino->i_mapping->invalidate_lock, + &ntfs_mft_inval_lock_key); + + mutex_lock(&ntfs_lock); + + /* + * Generate the global default upcase table if necessary. Also + * temporarily increment the number of upcase users to avoid race + * conditions with concurrent (u)mounts. + */ + if (!default_upcase) + default_upcase = generate_default_upcase(); + ntfs_nr_upcase_users++; + mutex_unlock(&ntfs_lock); + + lcn_bit_pages = (((vol->nr_clusters + 7) >> 3) + PAGE_SIZE - 1) >> PAGE_SHIFT; + vol->lcn_empty_bits_per_page = kvmalloc_array(lcn_bit_pages, sizeof(unsigned int), + GFP_KERNEL); + if (!vol->lcn_empty_bits_per_page) { + ntfs_error(sb, + "Unable to allocate pages for storing LCN empty bit counts\n"); + goto unl_upcase_iput_tmp_ino_err_out_now; + } + + /* + * From now on, ignore @silent parameter. If we fail below this line, + * it will be due to a corrupt fs or a system error, so we report it. + */ + /* + * Open the system files with normal access functions and complete + * setting up the ntfs super block. + */ + if (!load_system_files(vol)) { + ntfs_error(sb, "Failed to load system files."); + goto unl_upcase_iput_tmp_ino_err_out_now; + } + + /* We grab a reference, simulating an ntfs_iget(). */ + ihold(vol->root_ino); + sb->s_root = d_make_root(vol->root_ino); + if (sb->s_root) { + s64 nr_records; + + ntfs_debug("Exiting, status successful."); + + /* Release the default upcase if it has no users. */ + mutex_lock(&ntfs_lock); + if (!--ntfs_nr_upcase_users && default_upcase) { + kvfree(default_upcase); + default_upcase = NULL; + } + mutex_unlock(&ntfs_lock); + sb->s_export_op = &ntfs_export_ops; + lockdep_on(); + + nr_records = __get_nr_free_mft_records(vol, + i_size_read(vol->mft_ino) >> vol->mft_record_size_bits, + ((((NTFS_I(vol->mft_ino)->initialized_size >> + vol->mft_record_size_bits) + + 7) >> 3) + PAGE_SIZE - 1) >> PAGE_SHIFT); + ntfs_debug("Free mft records(%lld)", nr_records); + + init_waitqueue_head(&vol->free_waitq); + INIT_WORK(&vol->precalc_work, precalc_free_clusters); + queue_work(ntfs_wq, &vol->precalc_work); + return 0; + } + ntfs_error(sb, "Failed to allocate root directory."); + /* Clean up after the successful load_system_files() call from above. */ + iput(vol->vol_ino); + vol->vol_ino = NULL; + /* NTFS 3.0+ specific clean up. */ + if (vol->major_ver >= 3) { + if (vol->quota_q_ino) { + iput(vol->quota_q_ino); + vol->quota_q_ino = NULL; + } + if (vol->quota_ino) { + iput(vol->quota_ino); + vol->quota_ino = NULL; + } + if (vol->extend_ino) { + iput(vol->extend_ino); + vol->extend_ino = NULL; + } + if (vol->secure_ino) { + iput(vol->secure_ino); + vol->secure_ino = NULL; + } + } + iput(vol->root_ino); + vol->root_ino = NULL; + iput(vol->lcnbmp_ino); + vol->lcnbmp_ino = NULL; + iput(vol->mftbmp_ino); + vol->mftbmp_ino = NULL; + if (vol->logfile_ino) { + iput(vol->logfile_ino); + vol->logfile_ino = NULL; + } + if (vol->mftmirr_ino) { + iput(vol->mftmirr_ino); + vol->mftmirr_ino = NULL; + } + /* Throw away the table of attribute definitions. */ + vol->attrdef_size = 0; + if (vol->attrdef) { + kvfree(vol->attrdef); + vol->attrdef = NULL; + } + vol->upcase_len = 0; + mutex_lock(&ntfs_lock); + if (vol->upcase == default_upcase) { + ntfs_nr_upcase_users--; + vol->upcase = NULL; + } + mutex_unlock(&ntfs_lock); + if (vol->upcase) { + kvfree(vol->upcase); + vol->upcase = NULL; + } + if (vol->nls_map) { + unload_nls(vol->nls_map); + vol->nls_map = NULL; + } + /* Error exit code path. */ +unl_upcase_iput_tmp_ino_err_out_now: + if (vol->lcn_empty_bits_per_page) + kvfree(vol->lcn_empty_bits_per_page); + /* + * Decrease the number of upcase users and destroy the global default + * upcase table if necessary. + */ + mutex_lock(&ntfs_lock); + if (!--ntfs_nr_upcase_users && default_upcase) { + kvfree(default_upcase); + default_upcase = NULL; + } + + mutex_unlock(&ntfs_lock); +iput_tmp_ino_err_out_now: + iput(tmp_ino); + if (vol->mft_ino && vol->mft_ino != tmp_ino) + iput(vol->mft_ino); + vol->mft_ino = NULL; + /* Errors at this stage are irrelevant. */ +err_out_now: + sb->s_fs_info = NULL; + kfree(vol); + ntfs_debug("Failed, returning -EINVAL."); + lockdep_on(); + return -EINVAL; +} + +/* + * This is a slab cache to optimize allocations and deallocations of Unicode + * strings of the maximum length allowed by NTFS, which is NTFS_MAX_NAME_LEN + * (255) Unicode characters + a terminating NULL Unicode character. + */ +struct kmem_cache *ntfs_name_cache; + +/* Slab caches for efficient allocation/deallocation of inodes. */ +struct kmem_cache *ntfs_inode_cache; +struct kmem_cache *ntfs_big_inode_cache; + +/* Init once constructor for the inode slab cache. */ +static void ntfs_big_inode_init_once(void *foo) +{ + struct ntfs_inode *ni = foo; + + inode_init_once(VFS_I(ni)); +} + +/* + * Slab caches to optimize allocations and deallocations of attribute search + * contexts and index contexts, respectively. + */ +struct kmem_cache *ntfs_attr_ctx_cache; +struct kmem_cache *ntfs_index_ctx_cache; + +/* Driver wide mutex. */ +DEFINE_MUTEX(ntfs_lock); + +static int ntfs_get_tree(struct fs_context *fc) +{ + return get_tree_bdev(fc, ntfs_fill_super); +} + +static void ntfs_free_fs_context(struct fs_context *fc) +{ + struct ntfs_volume *vol = fc->s_fs_info; + + if (vol) + ntfs_volume_free(vol); +} + +static const struct fs_context_operations ntfs_context_ops = { + .parse_param = ntfs_parse_param, + .get_tree = ntfs_get_tree, + .free = ntfs_free_fs_context, + .reconfigure = ntfs_reconfigure, +}; + +static int ntfs_init_fs_context(struct fs_context *fc) +{ + struct ntfs_volume *vol; + + /* Allocate a new struct ntfs_volume and place it in sb->s_fs_info. */ + vol = kmalloc(sizeof(struct ntfs_volume), GFP_NOFS); + if (!vol) + return -ENOMEM; + + /* Initialize struct ntfs_volume structure. */ + *vol = (struct ntfs_volume) { + .uid = INVALID_UID, + .gid = INVALID_GID, + .fmask = 0, + .dmask = 0, + .mft_zone_multiplier = 1, + .on_errors = ON_ERRORS_CONTINUE, + .nls_map = load_nls_default(), + .preallocated_size = NTFS_DEF_PREALLOC_SIZE, + }; + + NVolSetShowHiddenFiles(vol); + NVolSetCaseSensitive(vol); + init_rwsem(&vol->mftbmp_lock); + init_rwsem(&vol->lcnbmp_lock); + + fc->s_fs_info = vol; + fc->ops = &ntfs_context_ops; + return 0; +} + +static struct file_system_type ntfs_fs_type = { + .owner = THIS_MODULE, + .name = "ntfs", + .init_fs_context = ntfs_init_fs_context, + .parameters = ntfs_parameters, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, +}; +MODULE_ALIAS_FS("ntfs"); + +static int ntfs_workqueue_init(void) +{ + ntfs_wq = alloc_workqueue("ntfs-bg-io", 0, 0); + if (!ntfs_wq) + return -ENOMEM; + return 0; +} + +static void ntfs_workqueue_destroy(void) +{ + destroy_workqueue(ntfs_wq); + ntfs_wq = NULL; +} + +/* Stable names for the slab caches. */ +static const char ntfs_index_ctx_cache_name[] = "ntfs_index_ctx_cache"; +static const char ntfs_attr_ctx_cache_name[] = "ntfs_attr_ctx_cache"; +static const char ntfs_name_cache_name[] = "ntfs_name_cache"; +static const char ntfs_inode_cache_name[] = "ntfs_inode_cache"; +static const char ntfs_big_inode_cache_name[] = "ntfs_big_inode_cache"; + +static int __init init_ntfs_fs(void) +{ + int err = 0; + + err = ntfs_workqueue_init(); + if (err) { + pr_crit("Failed to register workqueue!\n"); + return err; + } + + ntfs_index_ctx_cache = kmem_cache_create(ntfs_index_ctx_cache_name, + sizeof(struct ntfs_index_context), 0 /* offset */, + SLAB_HWCACHE_ALIGN, NULL /* ctor */); + if (!ntfs_index_ctx_cache) { + pr_crit("Failed to create %s!\n", ntfs_index_ctx_cache_name); + goto ictx_err_out; + } + ntfs_attr_ctx_cache = kmem_cache_create(ntfs_attr_ctx_cache_name, + sizeof(struct ntfs_attr_search_ctx), 0 /* offset */, + SLAB_HWCACHE_ALIGN, NULL /* ctor */); + if (!ntfs_attr_ctx_cache) { + pr_crit("NTFS: Failed to create %s!\n", + ntfs_attr_ctx_cache_name); + goto actx_err_out; + } + + ntfs_name_cache = kmem_cache_create(ntfs_name_cache_name, + (NTFS_MAX_NAME_LEN+2) * sizeof(__le16), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!ntfs_name_cache) { + pr_crit("Failed to create %s!\n", ntfs_name_cache_name); + goto name_err_out; + } + + ntfs_inode_cache = kmem_cache_create(ntfs_inode_cache_name, + sizeof(struct ntfs_inode), 0, SLAB_RECLAIM_ACCOUNT, NULL); + if (!ntfs_inode_cache) { + pr_crit("Failed to create %s!\n", ntfs_inode_cache_name); + goto inode_err_out; + } + + ntfs_big_inode_cache = kmem_cache_create(ntfs_big_inode_cache_name, + sizeof(struct big_ntfs_inode), 0, SLAB_HWCACHE_ALIGN | + SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT, + ntfs_big_inode_init_once); + if (!ntfs_big_inode_cache) { + pr_crit("Failed to create %s!\n", ntfs_big_inode_cache_name); + goto big_inode_err_out; + } + + /* Register the ntfs sysctls. */ + err = ntfs_sysctl(1); + if (err) { + pr_crit("Failed to register NTFS sysctls!\n"); + goto sysctl_err_out; + } + + err = register_filesystem(&ntfs_fs_type); + if (!err) { + ntfs_debug("NTFS driver registered successfully."); + return 0; /* Success! */ + } + pr_crit("Failed to register NTFS filesystem driver!\n"); + + /* Unregister the ntfs sysctls. */ + ntfs_sysctl(0); +sysctl_err_out: + kmem_cache_destroy(ntfs_big_inode_cache); +big_inode_err_out: + kmem_cache_destroy(ntfs_inode_cache); +inode_err_out: + kmem_cache_destroy(ntfs_name_cache); +name_err_out: + kmem_cache_destroy(ntfs_attr_ctx_cache); +actx_err_out: + kmem_cache_destroy(ntfs_index_ctx_cache); +ictx_err_out: + if (!err) { + pr_crit("Aborting NTFS filesystem driver registration...\n"); + err = -ENOMEM; + } + return err; +} + +static void __exit exit_ntfs_fs(void) +{ + ntfs_debug("Unregistering NTFS driver."); + + unregister_filesystem(&ntfs_fs_type); + + /* + * Make sure all delayed rcu free inodes are flushed before we + * destroy cache. + */ + rcu_barrier(); + kmem_cache_destroy(ntfs_big_inode_cache); + kmem_cache_destroy(ntfs_inode_cache); + kmem_cache_destroy(ntfs_name_cache); + kmem_cache_destroy(ntfs_attr_ctx_cache); + kmem_cache_destroy(ntfs_index_ctx_cache); + ntfs_workqueue_destroy(); + /* Unregister the ntfs sysctls. */ + ntfs_sysctl(0); +} + +module_init(init_ntfs_fs); +module_exit(exit_ntfs_fs); + +MODULE_AUTHOR("Anton Altaparmakov "); /* Original read-only NTFS driver */ +MODULE_AUTHOR("Namjae Jeon "); /* Add write, iomap and various features */ +MODULE_DESCRIPTION("NTFS read-write filesystem driver"); +MODULE_LICENSE("GPL"); +#ifdef DEBUG +module_param(debug_msgs, uint, 0); +MODULE_PARM_DESC(debug_msgs, "Enable debug messages."); +#endif diff --git a/fs/ntfs/sysctl.c b/fs/ntfs/sysctl.c new file mode 100644 index 000000000000..8c5c1b7cd483 --- /dev/null +++ b/fs/ntfs/sysctl.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Code for sysctl handling in NTFS Linux kernel driver. + * + * Copyright (C) 1997 Martin von Löwis, Régis Duchesne + * Copyright (c) 2002-2005 Anton Altaparmakov + */ + +#ifdef DEBUG + +#include + +#ifdef CONFIG_SYSCTL + +#include +#include + +#include "sysctl.h" +#include "debug.h" + +/* Definition of the ntfs sysctl. */ +static const struct ctl_table ntfs_sysctls[] = { + { + .procname = "ntfs-debug", + .data = &debug_msgs, /* Data pointer and size. */ + .maxlen = sizeof(debug_msgs), + .mode = 0644, /* Mode, proc handler. */ + .proc_handler = proc_dointvec + }, +}; + +/* Storage for the sysctls header. */ +static struct ctl_table_header *sysctls_root_table; + +/* + * ntfs_sysctl - add or remove the debug sysctl + * @add: add (1) or remove (0) the sysctl + * + * Add or remove the debug sysctl. Return 0 on success or -errno on error. + */ +int ntfs_sysctl(int add) +{ + if (add) { + sysctls_root_table = register_sysctl("fs/ntfs", ntfs_sysctls); + if (!sysctls_root_table) + return -ENOMEM; + } else { + unregister_sysctl_table(sysctls_root_table); + sysctls_root_table = NULL; + } + return 0; +} +#endif /* CONFIG_SYSCTL */ +#endif /* DEBUG */ diff --git a/fs/ntfs/sysctl.h b/fs/ntfs/sysctl.h new file mode 100644 index 000000000000..037127c994bf --- /dev/null +++ b/fs/ntfs/sysctl.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for sysctl handling in NTFS Linux kernel driver. + * + * Copyright (C) 1997 Martin von Löwis, Régis Duchesne + * Copyright (c) 2002-2004 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_SYSCTL_H +#define _LINUX_NTFS_SYSCTL_H + + +#if defined(DEBUG) && defined(CONFIG_SYSCTL) + +int ntfs_sysctl(int add); + +#else + +/* Just return success. */ +static inline int ntfs_sysctl(int add) +{ + return 0; +} + +#endif /* DEBUG && CONFIG_SYSCTL */ +#endif /* _LINUX_NTFS_SYSCTL_H */ diff --git a/fs/ntfs/time.h b/fs/ntfs/time.h new file mode 100644 index 000000000000..aeed195bdcda --- /dev/null +++ b/fs/ntfs/time.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * NTFS time conversion functions. + * + * Copyright (c) 2001-2005 Anton Altaparmakov + */ + +#ifndef _LINUX_NTFS_TIME_H +#define _LINUX_NTFS_TIME_H + +#include +#include /* For do_div(). */ + +#define NTFS_TIME_OFFSET ((s64)(369 * 365 + 89) * 24 * 3600) + +/* + * utc2ntfs - convert Linux UTC time to NTFS time + * @ts: Linux UTC time to convert to NTFS time + * + * Convert the Linux UTC time @ts to its corresponding NTFS time and return + * that in little endian format. + * + * Linux stores time in a struct timespec64 consisting of a time64_t tv_sec + * and a long tv_nsec where tv_sec is the number of 1-second intervals since + * 1st January 1970, 00:00:00 UTC and tv_nsec is the number of 1-nano-second + * intervals since the value of tv_sec. + * + * NTFS uses Microsoft's standard time format which is stored in a s64 and is + * measured as the number of 100-nano-second intervals since 1st January 1601, + * 00:00:00 UTC. + */ +static inline __le64 utc2ntfs(const struct timespec64 ts) +{ + /* + * Convert the seconds to 100ns intervals, add the nano-seconds + * converted to 100ns intervals, and then add the NTFS time offset. + */ + return cpu_to_le64((u64)(ts.tv_sec + NTFS_TIME_OFFSET) * 10000000 + + ts.tv_nsec / 100); +} + +/* + * get_current_ntfs_time - get the current time in little endian NTFS format + * + * Get the current time from the Linux kernel, convert it to its corresponding + * NTFS time and return that in little endian format. + */ +static inline __le64 get_current_ntfs_time(void) +{ + struct timespec64 ts; + + ktime_get_coarse_real_ts64(&ts); + return utc2ntfs(ts); +} + +/* + * ntfs2utc - convert NTFS time to Linux time + * @time: NTFS time (little endian) to convert to Linux UTC + * + * Convert the little endian NTFS time @time to its corresponding Linux UTC + * time and return that in cpu format. + * + * Linux stores time in a struct timespec64 consisting of a time64_t tv_sec + * and a long tv_nsec where tv_sec is the number of 1-second intervals since + * 1st January 1970, 00:00:00 UTC and tv_nsec is the number of 1-nano-second + * intervals since the value of tv_sec. + * + * NTFS uses Microsoft's standard time format which is stored in a s64 and is + * measured as the number of 100 nano-second intervals since 1st January 1601, + * 00:00:00 UTC. + */ +static inline struct timespec64 ntfs2utc(const __le64 time) +{ + struct timespec64 ts; + s32 t32; + + /* Subtract the NTFS time offset. */ + s64 t = le64_to_cpu(time) - NTFS_TIME_OFFSET * 10000000; + /* + * Convert the time to 1-second intervals and the remainder to + * 1-nano-second intervals. + */ + ts.tv_sec = div_s64_rem(t, 10000000, &t32); + ts.tv_nsec = t32 * 100; + return ts; +} +#endif /* _LINUX_NTFS_TIME_H */ diff --git a/fs/ntfs/unistr.c b/fs/ntfs/unistr.c new file mode 100644 index 000000000000..7f11a2825527 --- /dev/null +++ b/fs/ntfs/unistr.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS Unicode string handling. + * + * Copyright (c) 2001-2006 Anton Altaparmakov + */ + +#include "ntfs.h" + +/* + * IMPORTANT + * ========= + * + * All these routines assume that the Unicode characters are in little endian + * encoding inside the strings!!! + */ + +/* + * This is used by the name collation functions to quickly determine what + * characters are (in)valid. + */ +static const u8 legal_ansi_char_array[0x40] = { + 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + + 0x17, 0x07, 0x18, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x18, 0x16, 0x16, 0x17, 0x07, 0x00, + + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x04, 0x16, 0x18, 0x16, 0x18, 0x18, +}; + +/* + * ntfs_are_names_equal - compare two Unicode names for equality + * @s1: name to compare to @s2 + * @s1_len: length in Unicode characters of @s1 + * @s2: name to compare to @s1 + * @s2_len: length in Unicode characters of @s2 + * @ic: ignore case bool + * @upcase: upcase table (only if @ic == IGNORE_CASE) + * @upcase_size: length in Unicode characters of @upcase (if present) + * + * Compare the names @s1 and @s2 and return 'true' (1) if the names are + * identical, or 'false' (0) if they are not identical. If @ic is IGNORE_CASE, + * the @upcase table is used to performa a case insensitive comparison. + */ +bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len, + const __le16 *s2, size_t s2_len, const u32 ic, + const __le16 *upcase, const u32 upcase_size) +{ + if (s1_len != s2_len) + return false; + if (ic == CASE_SENSITIVE) + return !ntfs_ucsncmp(s1, s2, s1_len); + return !ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size); +} + +/* + * ntfs_collate_names - collate two Unicode names + * @name1: first Unicode name to compare + * @name1_len: first Unicode name length + * @name2: second Unicode name to compare + * @name2_len: second Unicode name length + * @err_val: if @name1 contains an invalid character return this value + * @ic: either CASE_SENSITIVE or IGNORE_CASE + * @upcase: upcase table (ignored if @ic is CASE_SENSITIVE) + * @upcase_len: upcase table size (ignored if @ic is CASE_SENSITIVE) + * + * ntfs_collate_names collates two Unicode names and returns: + * + * -1 if the first name collates before the second one, + * 0 if the names match, + * 1 if the second name collates before the first one, or + * @err_val if an invalid character is found in @name1 during the comparison. + * + * The following characters are considered invalid: '"', '*', '<', '>' and '?'. + */ +int ntfs_collate_names(const __le16 *name1, const u32 name1_len, + const __le16 *name2, const u32 name2_len, + const int err_val, const u32 ic, + const __le16 *upcase, const u32 upcase_len) +{ + u32 cnt, min_len; + u16 c1, c2; + + min_len = name1_len; + if (name1_len > name2_len) + min_len = name2_len; + for (cnt = 0; cnt < min_len; ++cnt) { + c1 = le16_to_cpu(*name1++); + c2 = le16_to_cpu(*name2++); + if (ic) { + if (c1 < upcase_len) + c1 = le16_to_cpu(upcase[c1]); + if (c2 < upcase_len) + c2 = le16_to_cpu(upcase[c2]); + } + if (c1 < 64 && legal_ansi_char_array[c1] & 8) + return err_val; + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + } + if (name1_len < name2_len) + return -1; + if (name1_len == name2_len) + return 0; + /* name1_len > name2_len */ + c1 = le16_to_cpu(*name1); + if (c1 < 64 && legal_ansi_char_array[c1] & 8) + return err_val; + return 1; +} + +/* + * ntfs_ucsncmp - compare two little endian Unicode strings + * @s1: first string + * @s2: second string + * @n: maximum unicode characters to compare + * + * Compare the first @n characters of the Unicode strings @s1 and @s2, + * The strings in little endian format and appropriate le16_to_cpu() + * conversion is performed on non-little endian machines. + * + * The function returns an integer less than, equal to, or greater than zero + * if @s1 (or the first @n Unicode characters thereof) is found, respectively, + * to be less than, to match, or be greater than @s2. + */ +int ntfs_ucsncmp(const __le16 *s1, const __le16 *s2, size_t n) +{ + u16 c1, c2; + size_t i; + + for (i = 0; i < n; ++i) { + c1 = le16_to_cpu(s1[i]); + c2 = le16_to_cpu(s2[i]); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (!c1) + break; + } + return 0; +} + +/* + * ntfs_ucsncasecmp - compare two little endian Unicode strings, ignoring case + * @s1: first string + * @s2: second string + * @n: maximum unicode characters to compare + * @upcase: upcase table + * @upcase_size: upcase table size in Unicode characters + * + * Compare the first @n characters of the Unicode strings @s1 and @s2, + * ignoring case. The strings in little endian format and appropriate + * le16_to_cpu() conversion is performed on non-little endian machines. + * + * Each character is uppercased using the @upcase table before the comparison. + * + * The function returns an integer less than, equal to, or greater than zero + * if @s1 (or the first @n Unicode characters thereof) is found, respectively, + * to be less than, to match, or be greater than @s2. + */ +int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n, + const __le16 *upcase, const u32 upcase_size) +{ + size_t i; + u16 c1, c2; + + for (i = 0; i < n; ++i) { + c1 = le16_to_cpu(s1[i]); + if (c1 < upcase_size) + c1 = le16_to_cpu(upcase[c1]); + c2 = le16_to_cpu(s2[i]); + if (c2 < upcase_size) + c2 = le16_to_cpu(upcase[c2]); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (!c1) + break; + } + return 0; +} + +int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1, + const struct file_name_attr *file_name_attr2, + const int err_val, const u32 ic, + const __le16 *upcase, const u32 upcase_len) +{ + return ntfs_collate_names((__le16 *)&file_name_attr1->file_name, + file_name_attr1->file_name_length, + (__le16 *)&file_name_attr2->file_name, + file_name_attr2->file_name_length, + err_val, ic, upcase, upcase_len); +} + +/* + * ntfs_nlstoucs - convert NLS string to little endian Unicode string + * @vol: ntfs volume which we are working with + * @ins: input NLS string buffer + * @ins_len: length of input string in bytes + * @outs: on return contains the allocated output Unicode string buffer + * @max_name_len: maximum number of Unicode characters allowed for the output name + * + * Convert the input string @ins, which is in whatever format the loaded NLS + * map dictates, into a little endian, 2-byte Unicode string. + * + * This function allocates the string and the caller is responsible for + * calling kmem_cache_free(ntfs_name_cache, *@outs); when finished with it. + * + * On success the function returns the number of Unicode characters written to + * the output string *@outs (>= 0), not counting the terminating Unicode NULL + * character. *@outs is set to the allocated output string buffer. + * + * On error, a negative number corresponding to the error code is returned. In + * that case the output string is not allocated. Both *@outs and *@outs_len + * are then undefined. + * + * This might look a bit odd due to fast path optimization... + */ +int ntfs_nlstoucs(const struct ntfs_volume *vol, const char *ins, + const int ins_len, __le16 **outs, int max_name_len) +{ + struct nls_table *nls = vol->nls_map; + __le16 *ucs; + wchar_t wc; + int i, o, wc_len; + + /* We do not trust outside sources. */ + if (likely(ins)) { + if (max_name_len > NTFS_MAX_NAME_LEN) + ucs = kvmalloc((max_name_len + 2) * sizeof(__le16), + GFP_NOFS | __GFP_ZERO); + else + ucs = kmem_cache_alloc(ntfs_name_cache, GFP_NOFS); + if (likely(ucs)) { + if (vol->nls_utf8) { + o = utf8s_to_utf16s(ins, ins_len, + UTF16_LITTLE_ENDIAN, + (wchar_t *)ucs, + max_name_len + 2); + if (o < 0 || o > max_name_len) { + wc_len = o; + goto name_err; + } + } else { + for (i = o = 0; i < ins_len; i += wc_len) { + wc_len = nls->char2uni(ins + i, ins_len - i, + &wc); + if (likely(wc_len >= 0 && + o < max_name_len)) { + if (likely(wc)) { + ucs[o++] = cpu_to_le16(wc); + continue; + } /* else if (!wc) */ + break; + } + + goto name_err; + } + } + ucs[o] = 0; + *outs = ucs; + return o; + } /* else if (!ucs) */ + ntfs_debug("Failed to allocate buffer for converted name from ntfs_name_cache."); + return -ENOMEM; + } /* else if (!ins) */ + ntfs_error(vol->sb, "Received NULL pointer."); + return -EINVAL; +name_err: + if (max_name_len > NTFS_MAX_NAME_LEN) + kvfree(ucs); + else + kmem_cache_free(ntfs_name_cache, ucs); + if (wc_len < 0) { + ntfs_debug("Name using character set %s contains characters that cannot be converted to Unicode.", + nls->charset); + i = -EILSEQ; + } else { + ntfs_debug("Name is too long (maximum length for a name on NTFS is %d Unicode characters.", + max_name_len); + i = -ENAMETOOLONG; + } + return i; +} + +/* + * ntfs_ucstonls - convert little endian Unicode string to NLS string + * @vol: ntfs volume which we are working with + * @ins: input Unicode string buffer + * @ins_len: length of input string in Unicode characters + * @outs: on return contains the (allocated) output NLS string buffer + * @outs_len: length of output string buffer in bytes + * + * Convert the input little endian, 2-byte Unicode string @ins, of length + * @ins_len into the string format dictated by the loaded NLS. + * + * If *@outs is NULL, this function allocates the string and the caller is + * responsible for calling kfree(*@outs); when finished with it. In this case + * @outs_len is ignored and can be 0. + * + * On success the function returns the number of bytes written to the output + * string *@outs (>= 0), not counting the terminating NULL byte. If the output + * string buffer was allocated, *@outs is set to it. + * + * On error, a negative number corresponding to the error code is returned. In + * that case the output string is not allocated. The contents of *@outs are + * then undefined. + * + * This might look a bit odd due to fast path optimization... + */ +int ntfs_ucstonls(const struct ntfs_volume *vol, const __le16 *ins, + const int ins_len, unsigned char **outs, int outs_len) +{ + struct nls_table *nls = vol->nls_map; + unsigned char *ns; + int i, o, ns_len, wc; + + /* We don't trust outside sources. */ + if (ins) { + ns = *outs; + ns_len = outs_len; + if (ns && !ns_len) { + wc = -ENAMETOOLONG; + goto conversion_err; + } + if (!ns) { + ns_len = ins_len * NLS_MAX_CHARSET_SIZE; + ns = kmalloc(ns_len + 1, GFP_NOFS); + if (!ns) + goto mem_err_out; + } + + if (vol->nls_utf8) { + o = utf16s_to_utf8s((const wchar_t *)ins, ins_len, + UTF16_LITTLE_ENDIAN, ns, ns_len); + if (o >= ns_len) { + wc = -ENAMETOOLONG; + goto conversion_err; + } + goto done; + } + + for (i = o = 0; i < ins_len; i++) { +retry: + wc = nls->uni2char(le16_to_cpu(ins[i]), ns + o, + ns_len - o); + if (wc > 0) { + o += wc; + continue; + } else if (!wc) + break; + else if (wc == -ENAMETOOLONG && ns != *outs) { + unsigned char *tc; + /* Grow in multiples of 64 bytes. */ + tc = kmalloc((ns_len + 64) & + ~63, GFP_NOFS); + if (tc) { + memcpy(tc, ns, ns_len); + ns_len = ((ns_len + 64) & ~63) - 1; + kfree(ns); + ns = tc; + goto retry; + } /* No memory so goto conversion_error; */ + } /* wc < 0, real error. */ + goto conversion_err; + } +done: + ns[o] = 0; + *outs = ns; + return o; + } /* else (!ins) */ + ntfs_error(vol->sb, "Received NULL pointer."); + return -EINVAL; +conversion_err: + ntfs_error(vol->sb, + "Unicode name contains characters that cannot be converted to character set %s. You might want to try to use the mount option nls=utf8.", + nls->charset); + if (ns != *outs) + kfree(ns); + if (wc != -ENAMETOOLONG) + wc = -EILSEQ; + return wc; +mem_err_out: + ntfs_error(vol->sb, "Failed to allocate name!"); + return -ENOMEM; +} + +/* + * ntfs_ucsnlen - determine the length of a little endian Unicode string + * @s: pointer to Unicode string + * @maxlen: maximum length of string @s + * + * Return the number of Unicode characters in the little endian Unicode + * string @s up to a maximum of maxlen Unicode characters, not including + * the terminating (__le16)'\0'. If there is no (__le16)'\0' between @s + * and @s + @maxlen, @maxlen is returned. + * + * This function never looks beyond @s + @maxlen. + */ +static u32 ntfs_ucsnlen(const __le16 *s, u32 maxlen) +{ + u32 i; + + for (i = 0; i < maxlen; i++) { + if (!le16_to_cpu(s[i])) + break; + } + return i; +} + +/* + * ntfs_ucsndup - duplicate little endian Unicode string + * @s: pointer to Unicode string + * @maxlen: maximum length of string @s + * + * Return a pointer to a new little endian Unicode string which is a duplicate + * of the string s. Memory for the new string is obtained with kmalloc, + * and can be freed with kfree. + * + * A maximum of @maxlen Unicode characters are copied and a terminating + * (__le16)'\0' little endian Unicode character is added. + * + * This function never looks beyond @s + @maxlen. + * + * Return a pointer to the new little endian Unicode string on success and NULL + * on failure with errno set to the error code. + */ +__le16 *ntfs_ucsndup(const __le16 *s, u32 maxlen) +{ + __le16 *dst; + u32 len; + + len = ntfs_ucsnlen(s, maxlen); + dst = kmalloc((len + 1) * sizeof(__le16), GFP_NOFS); + if (dst) { + memcpy(dst, s, len * sizeof(__le16)); + dst[len] = cpu_to_le16(L'\0'); + } + return dst; +} + +/* + * ntfs_names_are_equal - compare two Unicode names for equality + * @s1: name to compare to @s2 + * @s1_len: length in Unicode characters of @s1 + * @s2: name to compare to @s1 + * @s2_len: length in Unicode characters of @s2 + * @ic: ignore case bool + * @upcase: upcase table (only if @ic == IGNORE_CASE) + * @upcase_size: length in Unicode characters of @upcase (if present) + * + * Compare the names @s1 and @s2 and return TRUE (1) if the names are + * identical, or FALSE (0) if they are not identical. If @ic is IGNORE_CASE, + * the @upcase table is used to perform a case insensitive comparison. + */ +bool ntfs_names_are_equal(const __le16 *s1, size_t s1_len, + const __le16 *s2, size_t s2_len, + const u32 ic, + const __le16 *upcase, const u32 upcase_size) +{ + if (s1_len != s2_len) + return false; + if (!s1_len) + return true; + if (ic == CASE_SENSITIVE) + return ntfs_ucsncmp(s1, s2, s1_len) ? false : true; + return ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size) ? false : true; +} diff --git a/fs/ntfs/upcase.c b/fs/ntfs/upcase.c new file mode 100644 index 000000000000..4b954470883f --- /dev/null +++ b/fs/ntfs/upcase.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generate the full NTFS Unicode upcase table in little endian. + * + * Copyright (c) 2001 Richard Russon + * Copyright (c) 2001-2006 Anton Altaparmakov + */ + +#include "ntfs.h" + +__le16 *generate_default_upcase(void) +{ + static const int uc_run_table[][3] = { /* Start, End, Add */ + {0x0061, 0x007B, -32}, {0x0451, 0x045D, -80}, {0x1F70, 0x1F72, 74}, + {0x00E0, 0x00F7, -32}, {0x045E, 0x0460, -80}, {0x1F72, 0x1F76, 86}, + {0x00F8, 0x00FF, -32}, {0x0561, 0x0587, -48}, {0x1F76, 0x1F78, 100}, + {0x0256, 0x0258, -205}, {0x1F00, 0x1F08, 8}, {0x1F78, 0x1F7A, 128}, + {0x028A, 0x028C, -217}, {0x1F10, 0x1F16, 8}, {0x1F7A, 0x1F7C, 112}, + {0x03AC, 0x03AD, -38}, {0x1F20, 0x1F28, 8}, {0x1F7C, 0x1F7E, 126}, + {0x03AD, 0x03B0, -37}, {0x1F30, 0x1F38, 8}, {0x1FB0, 0x1FB2, 8}, + {0x03B1, 0x03C2, -32}, {0x1F40, 0x1F46, 8}, {0x1FD0, 0x1FD2, 8}, + {0x03C2, 0x03C3, -31}, {0x1F51, 0x1F52, 8}, {0x1FE0, 0x1FE2, 8}, + {0x03C3, 0x03CC, -32}, {0x1F53, 0x1F54, 8}, {0x1FE5, 0x1FE6, 7}, + {0x03CC, 0x03CD, -64}, {0x1F55, 0x1F56, 8}, {0x2170, 0x2180, -16}, + {0x03CD, 0x03CF, -63}, {0x1F57, 0x1F58, 8}, {0x24D0, 0x24EA, -26}, + {0x0430, 0x0450, -32}, {0x1F60, 0x1F68, 8}, {0xFF41, 0xFF5B, -32}, + {0} + }; + + static const int uc_dup_table[][2] = { /* Start, End */ + {0x0100, 0x012F}, {0x01A0, 0x01A6}, {0x03E2, 0x03EF}, {0x04CB, 0x04CC}, + {0x0132, 0x0137}, {0x01B3, 0x01B7}, {0x0460, 0x0481}, {0x04D0, 0x04EB}, + {0x0139, 0x0149}, {0x01CD, 0x01DD}, {0x0490, 0x04BF}, {0x04EE, 0x04F5}, + {0x014A, 0x0178}, {0x01DE, 0x01EF}, {0x04BF, 0x04BF}, {0x04F8, 0x04F9}, + {0x0179, 0x017E}, {0x01F4, 0x01F5}, {0x04C1, 0x04C4}, {0x1E00, 0x1E95}, + {0x018B, 0x018B}, {0x01FA, 0x0218}, {0x04C7, 0x04C8}, {0x1EA0, 0x1EF9}, + {0} + }; + + static const int uc_word_table[][2] = { /* Offset, Value */ + {0x00FF, 0x0178}, {0x01AD, 0x01AC}, {0x01F3, 0x01F1}, {0x0269, 0x0196}, + {0x0183, 0x0182}, {0x01B0, 0x01AF}, {0x0253, 0x0181}, {0x026F, 0x019C}, + {0x0185, 0x0184}, {0x01B9, 0x01B8}, {0x0254, 0x0186}, {0x0272, 0x019D}, + {0x0188, 0x0187}, {0x01BD, 0x01BC}, {0x0259, 0x018F}, {0x0275, 0x019F}, + {0x018C, 0x018B}, {0x01C6, 0x01C4}, {0x025B, 0x0190}, {0x0283, 0x01A9}, + {0x0192, 0x0191}, {0x01C9, 0x01C7}, {0x0260, 0x0193}, {0x0288, 0x01AE}, + {0x0199, 0x0198}, {0x01CC, 0x01CA}, {0x0263, 0x0194}, {0x0292, 0x01B7}, + {0x01A8, 0x01A7}, {0x01DD, 0x018E}, {0x0268, 0x0197}, + {0} + }; + + int i, r; + __le16 *uc; + + uc = kvcalloc(default_upcase_len, sizeof(__le16), GFP_NOFS); + if (!uc) + return uc; + /* Generate the little endian Unicode upcase table used by ntfs. */ + for (i = 0; i < default_upcase_len; i++) + uc[i] = cpu_to_le16(i); + for (r = 0; uc_run_table[r][0]; r++) + for (i = uc_run_table[r][0]; i < uc_run_table[r][1]; i++) + le16_add_cpu(&uc[i], uc_run_table[r][2]); + for (r = 0; uc_dup_table[r][0]; r++) + for (i = uc_dup_table[r][0]; i < uc_dup_table[r][1]; i += 2) + le16_add_cpu(&uc[i + 1], -1); + for (r = 0; uc_word_table[r][0]; r++) + uc[uc_word_table[r][0]] = cpu_to_le16(uc_word_table[r][1]); + return uc; +} diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h new file mode 100644 index 000000000000..af41427ec622 --- /dev/null +++ b/fs/ntfs/volume.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Defines for volume structures in NTFS Linux kernel driver. + * + * Copyright (c) 2001-2006 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#ifndef _LINUX_NTFS_VOLUME_H +#define _LINUX_NTFS_VOLUME_H + +#include +#include +#include +#include +#include +#include + +#include "layout.h" + +#define NTFS_VOL_UID BIT(1) +#define NTFS_VOL_GID BIT(2) + +/* + * The NTFS in memory super block structure. + * + * @sb: Pointer back to the super_block. + * @nr_blocks: Number of sb->s_blocksize bytes sized blocks on the device. + * @flags: Miscellaneous flags, see below. + * @uid: uid that files will be mounted as. + * @gid: gid that files will be mounted as. + * @fmask: The mask for file permissions. + * @dmask: The mask for directory permissions. + * @mft_zone_multiplier: Initial mft zone multiplier. + * @on_errors: What to do on filesystem errors. + * @wb_err: Writeback error tracking. + * @sector_size: in bytes + * @sector_size_bits: log2(sector_size) + * @cluster_size: in bytes + * @cluster_size_mask: cluster_size - 1 + * @cluster_size_bits: log2(cluster_size) + * @mft_record_size: in bytes + * @mft_record_size_mask: mft_record_size - 1 + * @mft_record_size_bits: log2(mft_record_size) + * @index_record_size: in bytes + * @index_record_size_mask: index_record_size - 1 + * @index_record_size_bits: log2(index_record_size) + * @nr_clusters: Volume size in clusters == number of bits in lcn bitmap. + * @mft_lcn: Cluster location of mft data. + * @mftmirr_lcn: Cluster location of copy of mft. + * @serial_no: The volume serial number. + * @upcase_len: Number of entries in upcase[]. + * @upcase: The upcase table. + * @attrdef_size: Size of the attribute definition table in bytes. + * @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef. + * @mft_data_pos: Mft record number at which to allocate the next mft record. + * @mft_zone_start: First cluster of the mft zone. + * @mft_zone_end: First cluster beyond the mft zone. + * @mft_zone_pos: Current position in the mft zone. + * @data1_zone_pos: Current position in the first data zone. + * @data2_zone_pos: Current position in the second data zone. + * @mft_ino: The VFS inode of $MFT. + * @mftbmp_ino: Attribute inode for $MFT/$BITMAP. + * @mftbmp_lock: Lock for serializing accesses to the mft record bitmap. + * @mftmirr_ino: The VFS inode of $MFTMirr. + * @mftmirr_size: Size of mft mirror in mft records. + * @logfile_ino: The VFS inode of LogFile. + * @lcnbmp_ino: The VFS inode of $Bitmap. + * @lcnbmp_lock: Lock for serializing accesses to the cluster bitmap + * @vol_ino: The VFS inode of $Volume. + * @vol_flags: Volume flags. + * @major_ver: Ntfs major version of volume. + * @minor_ver: Ntfs minor version of volume. + * @volume_label: volume label. + * @root_ino: The VFS inode of the root directory. + * @secure_ino: The VFS inode of $Secure (NTFS3.0+ only, otherwise NULL). + * @extend_ino: The VFS inode of $Extend (NTFS3.0+ only, otherwise NULL). + * @quota_ino: The VFS inode of $Quota. + * @quota_q_ino: Attribute inode for $Quota/$Q. + * @nls_map: NLS (National Language Support) table. + * @nls_utf8: NLS table for UTF-8. + * @free_waitq: Wait queue for threads waiting for free clusters or MFT records. + * @free_clusters: Track the number of free clusters. + * @free_mft_records: Track the free mft records. + * @dirty_clusters: Number of clusters that are dirty. + * @sparse_compression_unit: Size of compression/sparse unit in clusters. + * @lcn_empty_bits_per_page: Number of empty bits per page in the LCN bitmap. + * @precalc_work: Work structure for background pre-calculation tasks. + * @preallocated_size: reallocation size (in bytes). + */ +struct ntfs_volume { + struct super_block *sb; + s64 nr_blocks; + unsigned long flags; + kuid_t uid; + kgid_t gid; + umode_t fmask; + umode_t dmask; + u8 mft_zone_multiplier; + u8 on_errors; + errseq_t wb_err; + u16 sector_size; + u8 sector_size_bits; + u32 cluster_size; + u32 cluster_size_mask; + u8 cluster_size_bits; + u32 mft_record_size; + u32 mft_record_size_mask; + u8 mft_record_size_bits; + u32 index_record_size; + u32 index_record_size_mask; + u8 index_record_size_bits; + s64 nr_clusters; + s64 mft_lcn; + s64 mftmirr_lcn; + u64 serial_no; + u32 upcase_len; + __le16 *upcase; + s32 attrdef_size; + struct attr_def *attrdef; + s64 mft_data_pos; + s64 mft_zone_start; + s64 mft_zone_end; + s64 mft_zone_pos; + s64 data1_zone_pos; + s64 data2_zone_pos; + struct inode *mft_ino; + struct inode *mftbmp_ino; + struct rw_semaphore mftbmp_lock; + struct inode *mftmirr_ino; + int mftmirr_size; + struct inode *logfile_ino; + struct inode *lcnbmp_ino; + struct rw_semaphore lcnbmp_lock; + struct inode *vol_ino; + __le16 vol_flags; + u8 major_ver; + u8 minor_ver; + unsigned char *volume_label; + struct inode *root_ino; + struct inode *secure_ino; + struct inode *extend_ino; + struct inode *quota_ino; + struct inode *quota_q_ino; + struct nls_table *nls_map; + bool nls_utf8; + wait_queue_head_t free_waitq; + atomic64_t free_clusters; + atomic64_t free_mft_records; + atomic64_t dirty_clusters; + u8 sparse_compression_unit; + unsigned int *lcn_empty_bits_per_page; + struct work_struct precalc_work; + loff_t preallocated_size; +}; + +/* + * Defined bits for the flags field in the ntfs_volume structure. + * + * NV_Errors Volume has errors, prevent remount rw. + * NV_ShowSystemFiles Return system files in ntfs_readdir(). + * NV_CaseSensitive Treat file names as case sensitive and + * create filenames in the POSIX namespace. + * Otherwise be case insensitive but still + * create file names in POSIX namespace. + * NV_LogFileEmpty LogFile journal is empty. + * NV_QuotaOutOfDate Quota is out of date. + * NV_UsnJrnlStamped UsnJrnl has been stamped. + * NV_ReadOnly Volume is mounted read-only. + * NV_Compression Volume supports compression. + * NV_FreeClusterKnown Free cluster count is known and up-to-date. + * NV_Shutdown Volume is in shutdown state + * NV_SysImmutable Protect system files from deletion. + * NV_ShowHiddenFiles Return hidden files in ntfs_readdir(). + * NV_HideDotFiles Hide names beginning with a dot ("."). + * NV_CheckWindowsNames Refuse creation/rename of files with + * Windows-reserved names (CON, AUX, NUL, COM1, + * LPT1, etc.) or invalid characters. + * + * NV_Discard Issue discard/TRIM commands for freed clusters. + * NV_DisableSparse Disable creation of sparse regions. + */ +enum { + NV_Errors, + NV_ShowSystemFiles, + NV_CaseSensitive, + NV_LogFileEmpty, + NV_QuotaOutOfDate, + NV_UsnJrnlStamped, + NV_ReadOnly, + NV_Compression, + NV_FreeClusterKnown, + NV_Shutdown, + NV_SysImmutable, + NV_ShowHiddenFiles, + NV_HideDotFiles, + NV_CheckWindowsNames, + NV_Discard, + NV_DisableSparse, +}; + +/* + * Macro tricks to expand the NVolFoo(), NVolSetFoo(), and NVolClearFoo() + * functions. + */ +#define DEFINE_NVOL_BIT_OPS(flag) \ +static inline int NVol##flag(struct ntfs_volume *vol) \ +{ \ + return test_bit(NV_##flag, &(vol)->flags); \ +} \ +static inline void NVolSet##flag(struct ntfs_volume *vol) \ +{ \ + set_bit(NV_##flag, &(vol)->flags); \ +} \ +static inline void NVolClear##flag(struct ntfs_volume *vol) \ +{ \ + clear_bit(NV_##flag, &(vol)->flags); \ +} + +/* Emit the ntfs volume bitops functions. */ +DEFINE_NVOL_BIT_OPS(Errors) +DEFINE_NVOL_BIT_OPS(ShowSystemFiles) +DEFINE_NVOL_BIT_OPS(CaseSensitive) +DEFINE_NVOL_BIT_OPS(LogFileEmpty) +DEFINE_NVOL_BIT_OPS(QuotaOutOfDate) +DEFINE_NVOL_BIT_OPS(UsnJrnlStamped) +DEFINE_NVOL_BIT_OPS(ReadOnly) +DEFINE_NVOL_BIT_OPS(Compression) +DEFINE_NVOL_BIT_OPS(FreeClusterKnown) +DEFINE_NVOL_BIT_OPS(Shutdown) +DEFINE_NVOL_BIT_OPS(SysImmutable) +DEFINE_NVOL_BIT_OPS(ShowHiddenFiles) +DEFINE_NVOL_BIT_OPS(HideDotFiles) +DEFINE_NVOL_BIT_OPS(CheckWindowsNames) +DEFINE_NVOL_BIT_OPS(Discard) +DEFINE_NVOL_BIT_OPS(DisableSparse) + +static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr) +{ + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + atomic64_add(nr, &vol->free_clusters); +} + +static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr) +{ + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + atomic64_sub(nr, &vol->free_clusters); +} + +static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr) +{ + if (!NVolFreeClusterKnown(vol)) + return; + + atomic64_add(nr, &vol->free_mft_records); +} + +static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr) +{ + if (!NVolFreeClusterKnown(vol)) + return; + + atomic64_sub(nr, &vol->free_mft_records); +} + +static inline void ntfs_set_lcn_empty_bits(struct ntfs_volume *vol, unsigned long index, + u8 val, unsigned int count) +{ + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + + if (val) + vol->lcn_empty_bits_per_page[index] -= count; + else + vol->lcn_empty_bits_per_page[index] += count; +} + +static __always_inline void ntfs_hold_dirty_clusters(struct ntfs_volume *vol, s64 nr_clusters) +{ + atomic64_add(nr_clusters, &vol->dirty_clusters); +} + +static __always_inline void ntfs_release_dirty_clusters(struct ntfs_volume *vol, s64 nr_clusters) +{ + if (atomic64_read(&vol->dirty_clusters) < nr_clusters) + atomic64_set(&vol->dirty_clusters, 0); + else + atomic64_sub(nr_clusters, &vol->dirty_clusters); +} + +s64 ntfs_available_clusters_count(struct ntfs_volume *vol, s64 nr_clusters); +s64 get_nr_free_clusters(struct ntfs_volume *vol); +#endif /* _LINUX_NTFS_VOLUME_H */ diff --git a/fs/ntfs3/Kconfig b/fs/ntfs3/Kconfig index 7bc31d69f680..876dbc613ae6 100644 --- a/fs/ntfs3/Kconfig +++ b/fs/ntfs3/Kconfig @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config NTFS3_FS tristate "NTFS Read-Write file system support" + depends on !NTFS_FS || m select BUFFER_HEAD select NLS select LEGACY_DIRECT_IO @@ -46,12 +47,3 @@ config NTFS3_FS_POSIX_ACL NOTE: this is linux only feature. Windows will ignore these ACLs. If you don't know what Access Control Lists are, say N. - -config NTFS_FS - tristate "NTFS file system support" - select NTFS3_FS - select BUFFER_HEAD - select NLS - help - This config option is here only for backward compatibility. NTFS - filesystem is now handled by the NTFS3 driver. diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index 4652a56ad105..d99ab086ef6f 100644 --- a/fs/ntfs3/dir.c +++ b/fs/ntfs3/dir.c @@ -676,14 +676,4 @@ const struct file_operations ntfs_dir_operations = { #endif .setlease = generic_setlease, }; - -#if IS_ENABLED(CONFIG_NTFS_FS) -const struct file_operations ntfs_legacy_dir_operations = { - .llseek = generic_file_llseek, - .read = generic_read_dir, - .iterate_shared = ntfs_readdir, - .open = ntfs_file_open, - .setlease = generic_setlease, -}; -#endif // clang-format on diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index fb07afef84d0..b041639ab406 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -1566,15 +1566,4 @@ const struct file_operations ntfs_file_operations = { .release = ntfs_file_release, .setlease = generic_setlease, }; - -#if IS_ENABLED(CONFIG_NTFS_FS) -const struct file_operations ntfs_legacy_file_operations = { - .llseek = generic_file_llseek, - .read_iter = ntfs_file_read_iter, - .splice_read = ntfs_file_splice_read, - .open = ntfs_file_open, - .release = ntfs_file_release, - .setlease = generic_setlease, -}; -#endif // clang-format on diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 7f2ed0f26686..a5d9e375a289 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -443,9 +443,7 @@ end_enum: * Usually a hard links to directories are disabled. */ inode->i_op = &ntfs_dir_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_dir_operations : - &ntfs_dir_operations; + inode->i_fop = &ntfs_dir_operations; ni->i_valid = 0; } else if (S_ISLNK(mode)) { ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY; @@ -455,9 +453,7 @@ end_enum: } else if (S_ISREG(mode)) { ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY; inode->i_op = &ntfs_file_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_file_operations : - &ntfs_file_operations; + inode->i_fop = &ntfs_file_operations; inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr : &ntfs_aops; if (ino != MFT_REC_MFT) @@ -1601,9 +1597,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, if (S_ISDIR(mode)) { inode->i_op = &ntfs_dir_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_dir_operations : - &ntfs_dir_operations; + inode->i_fop = &ntfs_dir_operations; } else if (S_ISLNK(mode)) { inode->i_op = &ntfs_link_inode_operations; inode->i_fop = NULL; @@ -1612,9 +1606,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, inode_nohighmem(inode); } else if (S_ISREG(mode)) { inode->i_op = &ntfs_file_inode_operations; - inode->i_fop = unlikely(is_legacy_ntfs(sb)) ? - &ntfs_legacy_file_operations : - &ntfs_file_operations; + inode->i_fop = &ntfs_file_operations; inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr : &ntfs_aops; init_rwsem(&ni->file.run_lock); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index daf5a1f47275..56e608de4f70 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -530,7 +530,6 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni, struct ntfs_fnd *fnd); bool dir_is_empty(struct inode *dir); extern const struct file_operations ntfs_dir_operations; -extern const struct file_operations ntfs_legacy_dir_operations; /* Globals from file.c */ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, @@ -546,7 +545,6 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg); extern const struct inode_operations ntfs_special_inode_operations; extern const struct inode_operations ntfs_file_inode_operations; extern const struct file_operations ntfs_file_operations; -extern const struct file_operations ntfs_legacy_file_operations; /* Globals from frecord.c */ void ni_remove_mi(struct ntfs_inode *ni, struct mft_inode *mi); @@ -1250,13 +1248,4 @@ static inline void le64_sub_cpu(__le64 *var, u64 val) *var = cpu_to_le64(le64_to_cpu(*var) - val); } -#if IS_ENABLED(CONFIG_NTFS_FS) -bool is_legacy_ntfs(struct super_block *sb); -#else -static inline bool is_legacy_ntfs(struct super_block *sb) -{ - return false; -} -#endif - #endif /* _LINUX_NTFS3_NTFS_FS_H */ diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index 51aa008e126a..6588caa03632 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -434,12 +434,6 @@ static int ntfs_fs_reconfigure(struct fs_context *fc) struct ntfs_mount_options *new_opts = fc->fs_private; int ro_rw; - /* If ntfs3 is used as legacy ntfs enforce read-only mode. */ - if (is_legacy_ntfs(sb)) { - fc->sb_flags |= SB_RDONLY; - goto out; - } - ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY); if (ro_rw && (sbi->flags & NTFS_FLAGS_NEED_REPLAY)) { errorf(fc, @@ -466,7 +460,6 @@ static int ntfs_fs_reconfigure(struct fs_context *fc) return -EINVAL; } -out: sync_filesystem(sb); swap(sbi->options, fc->fs_private); @@ -1699,8 +1692,6 @@ load_root: ntfs_create_procdir(sb); - if (is_legacy_ntfs(sb)) - sb->s_flags |= SB_RDONLY; return 0; put_inode_out: @@ -1823,7 +1814,7 @@ static const struct fs_context_operations ntfs_context_ops = { * This will called when mount/remount. We will first initialize * options so that if remount we can use just that. */ -static int __ntfs_init_fs_context(struct fs_context *fc) +static int ntfs_init_fs_context(struct fs_context *fc) { struct ntfs_mount_options *opts; struct ntfs_sb_info *sbi; @@ -1877,11 +1868,6 @@ free_opts: return -ENOMEM; } -static int ntfs_init_fs_context(struct fs_context *fc) -{ - return __ntfs_init_fs_context(fc); -} - static void ntfs3_kill_sb(struct super_block *sb) { struct ntfs_sb_info *sbi = sb->s_fs_info; @@ -1903,47 +1889,6 @@ static struct file_system_type ntfs_fs_type = { .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, }; -#if IS_ENABLED(CONFIG_NTFS_FS) -static int ntfs_legacy_init_fs_context(struct fs_context *fc) -{ - int ret; - - ret = __ntfs_init_fs_context(fc); - /* If ntfs3 is used as legacy ntfs enforce read-only mode. */ - fc->sb_flags |= SB_RDONLY; - return ret; -} - -static struct file_system_type ntfs_legacy_fs_type = { - .owner = THIS_MODULE, - .name = "ntfs", - .init_fs_context = ntfs_legacy_init_fs_context, - .parameters = ntfs_fs_parameters, - .kill_sb = ntfs3_kill_sb, - .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, -}; -MODULE_ALIAS_FS("ntfs"); - -static inline void register_as_ntfs_legacy(void) -{ - int err = register_filesystem(&ntfs_legacy_fs_type); - if (err) - pr_warn("ntfs3: Failed to register legacy ntfs filesystem driver: %d\n", err); -} - -static inline void unregister_as_ntfs_legacy(void) -{ - unregister_filesystem(&ntfs_legacy_fs_type); -} -bool is_legacy_ntfs(struct super_block *sb) -{ - return sb->s_type == &ntfs_legacy_fs_type; -} -#else -static inline void register_as_ntfs_legacy(void) {} -static inline void unregister_as_ntfs_legacy(void) {} -#endif - // clang-format on static int __init init_ntfs_fs(void) @@ -1972,7 +1917,6 @@ static int __init init_ntfs_fs(void) goto out1; } - register_as_ntfs_legacy(); err = register_filesystem(&ntfs_fs_type); if (err) goto out; @@ -1992,7 +1936,6 @@ static void __exit exit_ntfs_fs(void) rcu_barrier(); kmem_cache_destroy(ntfs_inode_cachep); unregister_filesystem(&ntfs_fs_type); - unregister_as_ntfs_legacy(); ntfs3_exit_bitmap(); ntfs_remove_proc_root(); } diff --git a/fs/ocfs2/journal.c b/fs/ocfs2/journal.c index 4c86a9d46870..f9bf3bac085d 100644 --- a/fs/ocfs2/journal.c +++ b/fs/ocfs2/journal.c @@ -899,8 +899,13 @@ bail: static int ocfs2_journal_submit_inode_data_buffers(struct jbd2_inode *jinode) { - return filemap_fdatawrite_range(jinode->i_vfs_inode->i_mapping, - jinode->i_dirty_start, jinode->i_dirty_end); + struct address_space *mapping = jinode->i_vfs_inode->i_mapping; + loff_t range_start, range_end; + + if (!jbd2_jinode_get_dirty_range(jinode, &range_start, &range_end)) + return 0; + + return filemap_fdatawrite_range(mapping, range_start, range_end); } int ocfs2_journal_init(struct ocfs2_super *osb, int *dirty) diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 9e8a2a9e5229..7143b64b5b25 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -224,6 +224,8 @@ static void orangefs_readahead(struct readahead_control *rac) loff_t new_start = readahead_pos(rac); int ret; size_t new_len = 0; + size_t this_size; + size_t remaining; loff_t bytes_remaining = inode->i_size - readahead_pos(rac); loff_t pages_remaining = bytes_remaining / PAGE_SIZE; @@ -239,17 +241,33 @@ static void orangefs_readahead(struct readahead_control *rac) offset = readahead_pos(rac); i_pages = &rac->mapping->i_pages; - iov_iter_xarray(&iter, ITER_DEST, i_pages, offset, readahead_length(rac)); + iov_iter_xarray(&iter, ITER_DEST, i_pages, + offset, readahead_length(rac)); - /* read in the pages. */ - if ((ret = wait_for_direct_io(ORANGEFS_IO_READ, inode, - &offset, &iter, readahead_length(rac), - inode->i_size, NULL, NULL, rac->file)) < 0) - gossip_debug(GOSSIP_FILE_DEBUG, - "%s: wait_for_direct_io failed. \n", __func__); - else - ret = 0; + remaining = readahead_length(rac); + while (remaining) { + if (remaining > 4194304) + this_size = 4194304; + else + this_size = remaining; + /* read in the pages. */ + if ((ret = wait_for_direct_io(ORANGEFS_IO_READ, inode, + &offset, &iter, this_size, + inode->i_size, NULL, NULL, rac->file)) < 0) { + gossip_debug(GOSSIP_FILE_DEBUG, + "%s: wait_for_direct_io failed. :%d: \n", + __func__, ret); + goto cleanup; + } else { + ret = 0; + } + + remaining -= this_size; + offset += this_size; + } + +cleanup: /* clean up. */ while ((folio = readahead_folio(rac))) { if (!ret) diff --git a/fs/orangefs/orangefs-bufmap.c b/fs/orangefs/orangefs-bufmap.c index 5dd2708cce94..a557e1cf7436 100644 --- a/fs/orangefs/orangefs-bufmap.c +++ b/fs/orangefs/orangefs-bufmap.c @@ -139,9 +139,15 @@ static int get(struct slot_map *m) /* used to describe mapped buffers */ struct orangefs_bufmap_desc { void __user *uaddr; /* user space address pointer */ - struct page **page_array; /* array of mapped pages */ - int array_count; /* size of above arrays */ - struct list_head list_link; + struct folio **folio_array; + /* + * folio_offsets could be needed when userspace sets custom + * sizes in user_desc, or when folios aren't all backed by + * 2MB THPs. + */ + size_t *folio_offsets; + int folio_count; + bool is_two_2mib_chunks; }; static struct orangefs_bufmap { @@ -150,8 +156,10 @@ static struct orangefs_bufmap { int desc_count; int total_size; int page_count; + int folio_count; struct page **page_array; + struct folio **folio_array; struct orangefs_bufmap_desc *desc_array; /* array to track usage of buffer descriptors */ @@ -174,6 +182,17 @@ orangefs_bufmap_unmap(struct orangefs_bufmap *bufmap) static void orangefs_bufmap_free(struct orangefs_bufmap *bufmap) { + int i; + + if (!bufmap) + return; + + for (i = 0; i < bufmap->desc_count; i++) { + kfree(bufmap->desc_array[i].folio_array); + kfree(bufmap->desc_array[i].folio_offsets); + bufmap->desc_array[i].folio_array = NULL; + bufmap->desc_array[i].folio_offsets = NULL; + } kfree(bufmap->page_array); kfree(bufmap->desc_array); bitmap_free(bufmap->buffer_index_array); @@ -213,8 +232,10 @@ orangefs_bufmap_alloc(struct ORANGEFS_dev_map_desc *user_desc) bufmap->desc_count = user_desc->count; bufmap->desc_size = user_desc->size; bufmap->desc_shift = ilog2(bufmap->desc_size); + bufmap->page_count = bufmap->total_size / PAGE_SIZE; - bufmap->buffer_index_array = bitmap_zalloc(bufmap->desc_count, GFP_KERNEL); + bufmap->buffer_index_array = + bitmap_zalloc(bufmap->desc_count, GFP_KERNEL); if (!bufmap->buffer_index_array) goto out_free_bufmap; @@ -223,16 +244,21 @@ orangefs_bufmap_alloc(struct ORANGEFS_dev_map_desc *user_desc) if (!bufmap->desc_array) goto out_free_index_array; - bufmap->page_count = bufmap->total_size / PAGE_SIZE; - /* allocate storage to track our page mappings */ bufmap->page_array = kzalloc_objs(struct page *, bufmap->page_count); if (!bufmap->page_array) goto out_free_desc_array; + /* allocate folio array. */ + bufmap->folio_array = kzalloc_objs(struct folio *, bufmap->page_count); + if (!bufmap->folio_array) + goto out_free_page_array; + return bufmap; +out_free_page_array: + kfree(bufmap->page_array); out_free_desc_array: kfree(bufmap->desc_array); out_free_index_array: @@ -243,16 +269,65 @@ out: return NULL; } -static int -orangefs_bufmap_map(struct orangefs_bufmap *bufmap, - struct ORANGEFS_dev_map_desc *user_desc) +static int orangefs_bufmap_group_folios(struct orangefs_bufmap *bufmap) +{ + int i = 0; + int f = 0; + int k; + int num_pages; + struct page *page; + struct folio *folio; + + while (i < bufmap->page_count) { + page = bufmap->page_array[i]; + folio = page_folio(page); + num_pages = folio_nr_pages(folio); + gossip_debug(GOSSIP_BUFMAP_DEBUG, + "%s: i:%d: num_pages:%d: \n", __func__, i, num_pages); + + for (k = 1; k < num_pages; k++) { + if (bufmap->page_array[i + k] != folio_page(folio, k)) { + gossip_err("%s: bad match, i:%d: k:%d:\n", + __func__, i, k); + return -EINVAL; + } + } + + bufmap->folio_array[f++] = folio; + i += num_pages; + } + + bufmap->folio_count = f; + pr_info("%s: Grouped %d folios from %d pages.\n", + __func__, + bufmap->folio_count, + bufmap->page_count); + return 0; +} + +static int orangefs_bufmap_map(struct orangefs_bufmap *bufmap, + struct ORANGEFS_dev_map_desc *user_desc) { int pages_per_desc = bufmap->desc_size / PAGE_SIZE; - int offset = 0, ret, i; + int ret; + int i; + int j; + int current_folio; + int desc_pages_needed; + int desc_folio_count; + int remaining_pages; + int need_avail_min; + int pages_assigned_to_this_desc; + int allocated_descs = 0; + size_t current_offset; + size_t adjust_offset; + struct folio *folio; /* map the pages */ ret = pin_user_pages_fast((unsigned long)user_desc->ptr, - bufmap->page_count, FOLL_WRITE, bufmap->page_array); + bufmap->page_count, + FOLL_WRITE, + bufmap->page_array); if (ret < 0) return ret; @@ -260,7 +335,6 @@ orangefs_bufmap_map(struct orangefs_bufmap *bufmap, if (ret != bufmap->page_count) { gossip_err("orangefs error: asked for %d pages, only got %d.\n", bufmap->page_count, ret); - for (i = 0; i < ret; i++) unpin_user_page(bufmap->page_array[i]); return -ENOMEM; @@ -275,16 +349,120 @@ orangefs_bufmap_map(struct orangefs_bufmap *bufmap, for (i = 0; i < bufmap->page_count; i++) flush_dcache_page(bufmap->page_array[i]); - /* build a list of available descriptors */ - for (offset = 0, i = 0; i < bufmap->desc_count; i++) { - bufmap->desc_array[i].page_array = &bufmap->page_array[offset]; - bufmap->desc_array[i].array_count = pages_per_desc; + /* + * Group pages into folios. + */ + ret = orangefs_bufmap_group_folios(bufmap); + if (ret) + goto unpin; + + pr_info("%s: desc_size=%d bytes (%d pages per desc), total folios=%d\n", + __func__, bufmap->desc_size, pages_per_desc, + bufmap->folio_count); + + current_folio = 0; + remaining_pages = 0; + current_offset = 0; + for (i = 0; i < bufmap->desc_count; i++) { + desc_pages_needed = pages_per_desc; + desc_folio_count = 0; + pages_assigned_to_this_desc = 0; + bufmap->desc_array[i].is_two_2mib_chunks = false; + + /* + * We hope there was enough memory that each desc is + * covered by two THPs/folios, if not we want to keep on + * working even if there's only one page per folio. + */ + bufmap->desc_array[i].folio_array = + kzalloc_objs(struct folio *, pages_per_desc); + if (!bufmap->desc_array[i].folio_array) { + ret = -ENOMEM; + goto unpin; + } + + bufmap->desc_array[i].folio_offsets = + kzalloc_objs(size_t, pages_per_desc); + if (!bufmap->desc_array[i].folio_offsets) { + ret = -ENOMEM; + kfree(bufmap->desc_array[i].folio_array); + bufmap->desc_array[i].folio_array = NULL; + goto unpin; + } + bufmap->desc_array[i].uaddr = - (user_desc->ptr + (i * pages_per_desc * PAGE_SIZE)); - offset += pages_per_desc; + user_desc->ptr + (size_t)i * bufmap->desc_size; + + /* + * Accumulate folios until desc is full. + */ + while (desc_pages_needed > 0) { + if (remaining_pages == 0) { + /* shouldn't happen. */ + if (current_folio >= bufmap->folio_count) { + ret = -EINVAL; + goto unpin; + } + folio = bufmap->folio_array[current_folio++]; + remaining_pages = folio_nr_pages(folio); + current_offset = 0; + } else { + folio = bufmap->folio_array[current_folio - 1]; + } + + need_avail_min = + min(desc_pages_needed, remaining_pages); + adjust_offset = need_avail_min * PAGE_SIZE; + + bufmap->desc_array[i].folio_array[desc_folio_count] = + folio; + bufmap->desc_array[i].folio_offsets[desc_folio_count] = + current_offset; + desc_folio_count++; + pages_assigned_to_this_desc += need_avail_min; + desc_pages_needed -= need_avail_min; + remaining_pages -= need_avail_min; + current_offset += adjust_offset; + } + + /* Detect optimal case: two 2MiB folios per 4MiB slot. */ + if (desc_folio_count == 2 && + folio_nr_pages(bufmap->desc_array[i].folio_array[0]) == 512 && + folio_nr_pages(bufmap->desc_array[i].folio_array[1]) == 512) { + bufmap->desc_array[i].is_two_2mib_chunks = true; + gossip_debug(GOSSIP_BUFMAP_DEBUG, "%s: descriptor :%d: " + "optimal folio/page ratio.\n", __func__, i); + } + + bufmap->desc_array[i].folio_count = desc_folio_count; + gossip_debug(GOSSIP_BUFMAP_DEBUG, + " descriptor %d: folio_count=%d, " + "pages_assigned=%d (should be %d)\n", + i, desc_folio_count, pages_assigned_to_this_desc, + pages_per_desc); + + allocated_descs = i + 1; } return 0; +unpin: + /* + * rollback any allocations we got so far... + * Memory pressure, like in generic/340, led me + * to write the rollback this way. + */ + for (j = 0; j < allocated_descs; j++) { + if (bufmap->desc_array[j].folio_array) { + kfree(bufmap->desc_array[j].folio_array); + bufmap->desc_array[j].folio_array = NULL; + } + if (bufmap->desc_array[j].folio_offsets) { + kfree(bufmap->desc_array[j].folio_offsets); + bufmap->desc_array[j].folio_offsets = NULL; + } + } + unpin_user_pages(bufmap->page_array, bufmap->page_count); + return ret; } /* @@ -292,6 +470,8 @@ orangefs_bufmap_map(struct orangefs_bufmap *bufmap, * * initializes the mapped buffer interface * + * user_desc is the parameters provided by userspace for the bufmap. + * * returns 0 on success, -errno on failure */ int orangefs_bufmap_initialize(struct ORANGEFS_dev_map_desc *user_desc) @@ -300,8 +480,8 @@ int orangefs_bufmap_initialize(struct ORANGEFS_dev_map_desc *user_desc) int ret = -EINVAL; gossip_debug(GOSSIP_BUFMAP_DEBUG, - "orangefs_bufmap_initialize: called (ptr (" - "%p) sz (%d) cnt(%d).\n", + "%s: called (ptr (" "%p) sz (%d) cnt(%d).\n", + __func__, user_desc->ptr, user_desc->size, user_desc->count); @@ -371,7 +551,7 @@ int orangefs_bufmap_initialize(struct ORANGEFS_dev_map_desc *user_desc) spin_unlock(&orangefs_bufmap_lock); gossip_debug(GOSSIP_BUFMAP_DEBUG, - "orangefs_bufmap_initialize: exiting normally\n"); + "%s: exiting normally\n", __func__); return 0; out_unmap_bufmap: @@ -471,22 +651,89 @@ int orangefs_bufmap_copy_from_iovec(struct iov_iter *iter, size_t size) { struct orangefs_bufmap_desc *to; - int i; - - gossip_debug(GOSSIP_BUFMAP_DEBUG, - "%s: buffer_index:%d: size:%zu:\n", - __func__, buffer_index, size); + size_t remaining = size; + int folio_index = 0; + struct folio *folio; + size_t folio_offset; + size_t folio_avail; + size_t copy_amount; + size_t copied; + void *kaddr; + size_t half; + size_t first; + size_t second; to = &__orangefs_bufmap->desc_array[buffer_index]; - for (i = 0; size; i++) { - struct page *page = to->page_array[i]; - size_t n = size; - if (n > PAGE_SIZE) - n = PAGE_SIZE; - if (copy_page_from_iter(page, 0, n, iter) != n) + + /* shouldn't happen... */ + if (size > 4194304) + pr_info("%s: size:%zu\n", __func__, size); + + gossip_debug(GOSSIP_BUFMAP_DEBUG, + "%s: buffer_index:%d size:%zu folio_count:%d\n", + __func__, + buffer_index, + size, + to->folio_count); + + /* Fast path: exactly two 2 MiB folios */ + if (to->is_two_2mib_chunks && size <= 4194304) { + gossip_debug(GOSSIP_BUFMAP_DEBUG, + "%s: fastpath hit.\n", __func__); + half = 2097152; /* 2 MiB */ + first = min(size, half); + second = (size > half) ? size - half : 0; + + /* First 2 MiB chunk */ + kaddr = kmap_local_folio(to->folio_array[0], 0); + copied = copy_from_iter(kaddr, first, iter); + kunmap_local(kaddr); + if (copied != first) return -EFAULT; - size -= n; + + if (second == 0) + return 0; + + /* Second 2 MiB chunk */ + kaddr = kmap_local_folio(to->folio_array[1], 0); + copied = copy_from_iter(kaddr, second, iter); + kunmap_local(kaddr); + if (copied != second) + return -EFAULT; + + return 0; } + + while (remaining > 0) { + + if (unlikely(folio_index >= to->folio_count || + to->folio_array[folio_index] == NULL)) { + gossip_err("%s: " + "folio_index:%d: >= folio_count:%d: " + "(size %zu, buffer %d)\n", + __func__, + folio_index, + to->folio_count, + size, + buffer_index); + return -EFAULT; + } + + folio = to->folio_array[folio_index]; + folio_offset = to->folio_offsets[folio_index]; + folio_avail = folio_nr_pages(folio) * PAGE_SIZE - folio_offset; + copy_amount = min(remaining, folio_avail); + kaddr = kmap_local_folio(folio, folio_offset); + copied = copy_from_iter(kaddr, copy_amount, iter); + kunmap_local(kaddr); + + if (copied != copy_amount) + return -EFAULT; + + remaining -= copied; + folio_index++; + } + return 0; } @@ -499,23 +746,91 @@ int orangefs_bufmap_copy_to_iovec(struct iov_iter *iter, size_t size) { struct orangefs_bufmap_desc *from; - int i; + size_t remaining = size; + int folio_index = 0; + struct folio *folio; + size_t folio_offset; + size_t folio_avail; + size_t copy_amount; + size_t copied; + void *kaddr; + size_t half; + size_t first; + size_t second; from = &__orangefs_bufmap->desc_array[buffer_index]; + + /* shouldn't happen... */ + if (size > 4194304) + pr_info("%s: size:%zu\n", __func__, size); + gossip_debug(GOSSIP_BUFMAP_DEBUG, - "%s: buffer_index:%d: size:%zu:\n", - __func__, buffer_index, size); + "%s: buffer_index:%d size:%zu folio_count:%d\n", + __func__, + buffer_index, + size, + from->folio_count); + /* Fast path: exactly two 2 MiB folios */ + if (from->is_two_2mib_chunks && size <= 4194304) { + gossip_debug(GOSSIP_BUFMAP_DEBUG, + "%s: fastpath hit.\n", __func__); + half = 2097152; /* 2 MiB */ + first = min(size, half); + second = (size > half) ? size - half : 0; + void *kaddr; + size_t copied; - for (i = 0; size; i++) { - struct page *page = from->page_array[i]; - size_t n = size; - if (n > PAGE_SIZE) - n = PAGE_SIZE; - n = copy_page_to_iter(page, 0, n, iter); - if (!n) + /* First 2 MiB chunk */ + kaddr = kmap_local_folio(from->folio_array[0], 0); + copied = copy_to_iter(kaddr, first, iter); + kunmap_local(kaddr); + if (copied != first) return -EFAULT; - size -= n; + + if (second == 0) + return 0; + + /* Second 2 MiB chunk */ + kaddr = kmap_local_folio(from->folio_array[1], 0); + copied = copy_to_iter(kaddr, second, iter); + kunmap_local(kaddr); + if (copied != second) + return -EFAULT; + + return 0; } + + while (remaining > 0) { + + if (unlikely(folio_index >= from->folio_count || + from->folio_array[folio_index] == NULL)) { + gossip_err("%s: " + "folio_index:%d: >= folio_count:%d: " + "(size %zu, buffer %d)\n", + __func__, + folio_index, + from->folio_count, + size, + buffer_index); + return -EFAULT; + } + + folio = from->folio_array[folio_index]; + folio_offset = from->folio_offsets[folio_index]; + folio_avail = folio_nr_pages(folio) * PAGE_SIZE - folio_offset; + copy_amount = min(remaining, folio_avail); + + kaddr = kmap_local_folio(folio, folio_offset); + copied = copy_to_iter(kaddr, copy_amount, iter); + kunmap_local(kaddr); + + if (copied != copy_amount) + return -EFAULT; + + remaining -= copied; + folio_index++; + } + return 0; } diff --git a/fs/orangefs/orangefs-cache.c b/fs/orangefs/orangefs-cache.c index e75e173a9186..0bdb99e89744 100644 --- a/fs/orangefs/orangefs-cache.c +++ b/fs/orangefs/orangefs-cache.c @@ -19,10 +19,14 @@ static struct kmem_cache *op_cache; int op_cache_initialize(void) { - op_cache = kmem_cache_create("orangefs_op_cache", + op_cache = kmem_cache_create_usercopy("orangefs_op_cache", sizeof(struct orangefs_kernel_op_s), 0, 0, + offsetof(struct orangefs_kernel_op_s, tag), + offsetof(struct orangefs_kernel_op_s, upcall) + + sizeof(struct orangefs_upcall_s) - + offsetof(struct orangefs_kernel_op_s, tag), NULL); if (!op_cache) { diff --git a/fs/orangefs/orangefs-debugfs.c b/fs/orangefs/orangefs-debugfs.c index e82b934ed074..69bd73a2b556 100644 --- a/fs/orangefs/orangefs-debugfs.c +++ b/fs/orangefs/orangefs-debugfs.c @@ -238,12 +238,10 @@ void orangefs_debugfs_init(int debug_mask) static void orangefs_kernel_debug_init(void) { static char k_buffer[ORANGEFS_MAX_DEBUG_STRING_LEN] = { }; - size_t len = strlen(kernel_debug_string); + size_t len = + strscpy(k_buffer, kernel_debug_string, sizeof(k_buffer) - 1); - gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: start\n", __func__); - - if (len + 1 < ORANGEFS_MAX_DEBUG_STRING_LEN) { - memcpy(k_buffer, kernel_debug_string, len); + if (len > 0) { k_buffer[len] = '\n'; k_buffer[len + 1] = '\0'; } else { @@ -339,12 +337,10 @@ static int help_show(struct seq_file *m, void *v) static void orangefs_client_debug_init(void) { static char c_buffer[ORANGEFS_MAX_DEBUG_STRING_LEN] = { }; - size_t len = strlen(client_debug_string); + size_t len = + strscpy(c_buffer, client_debug_string, sizeof(c_buffer) - 1); - gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: start\n", __func__); - - if (len + 1 < ORANGEFS_MAX_DEBUG_STRING_LEN) { - memcpy(c_buffer, client_debug_string, len); + if (len > 0) { c_buffer[len] = '\n'; c_buffer[len + 1] = '\0'; } else { @@ -443,7 +439,7 @@ static ssize_t orangefs_debug_write(struct file *file, count = ORANGEFS_MAX_DEBUG_STRING_LEN; } - buf = memdup_user_nul(ubuf, count - 1); + buf = memdup_user_nul(ubuf, count); if (IS_ERR(buf)) { gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: memdup_user_nul failed!\n", @@ -452,6 +448,7 @@ static ssize_t orangefs_debug_write(struct file *file, buf = NULL; goto out; } + strim(buf); /* * Map the keyword string from userspace into a valid debug mask. @@ -873,9 +870,10 @@ out: */ static void debug_string_to_mask(char *debug_string, void *mask, int type) { - char *unchecked_keyword; int i; char *strsep_fodder = kstrdup(debug_string, GFP_KERNEL); + char *trimmed; + char *token; char *original_pointer; int element_count = 0; struct client_debug_mask *c_mask = NULL; @@ -893,18 +891,17 @@ static void debug_string_to_mask(char *debug_string, void *mask, int type) } original_pointer = strsep_fodder; - while ((unchecked_keyword = strsep(&strsep_fodder, ","))) - if (strlen(unchecked_keyword)) { + while ((token = strsep(&strsep_fodder, ",")) != NULL) { + trimmed = strim(token); + if (*trimmed) { for (i = 0; i < element_count; i++) if (type) - do_c_mask(i, - unchecked_keyword, - &c_mask); + do_c_mask(i, trimmed, &c_mask); else - do_k_mask(i, - unchecked_keyword, - &k_mask); + do_k_mask(i, trimmed, &k_mask); + } + } kfree(original_pointer); } diff --git a/fs/orangefs/xattr.c b/fs/orangefs/xattr.c index 1b372189cd10..b6d116302de4 100644 --- a/fs/orangefs/xattr.c +++ b/fs/orangefs/xattr.c @@ -188,6 +188,10 @@ ssize_t orangefs_inode_getxattr(struct inode *inode, const char *name, * Length returned includes null terminator. */ length = new_op->downcall.resp.getxattr.val_sz; + if (length < 0 || length > ORANGEFS_MAX_XATTR_VALUELEN) { + ret = -EIO; + goto out_release_op; + } /* * Just return the length of the queried attribute. diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h new file mode 100644 index 000000000000..fa7269154620 --- /dev/null +++ b/include/cxl/cxl.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright(c) 2020 Intel Corporation. */ +/* Copyright(c) 2026 Advanced Micro Devices, Inc. */ + +#ifndef __CXL_CXL_H__ +#define __CXL_CXL_H__ + +#include +#include +#include + +/** + * enum cxl_devtype - delineate type-2 from a generic type-3 device + * @CXL_DEVTYPE_DEVMEM: Vendor specific CXL Type-2 device implementing HDM-D or + * HDM-DB, no requirement that this device implements a + * mailbox, or other memory-device-standard manageability + * flows. + * @CXL_DEVTYPE_CLASSMEM: Common class definition of a CXL Type-3 device with + * HDM-H and class-mandatory memory device registers + */ +enum cxl_devtype { + CXL_DEVTYPE_DEVMEM, + CXL_DEVTYPE_CLASSMEM, +}; + +struct device; + +/* + * Using struct_group() allows for per register-block-type helper routines, + * without requiring block-type agnostic code to include the prefix. + */ +struct cxl_regs { + /* + * Common set of CXL Component register block base pointers + * @hdm_decoder: CXL 2.0 8.2.5.12 CXL HDM Decoder Capability Structure + * @ras: CXL 2.0 8.2.5.9 CXL RAS Capability Structure + */ + struct_group_tagged(cxl_component_regs, component, + void __iomem *hdm_decoder; + void __iomem *ras; + ); + /* + * Common set of CXL Device register block base pointers + * @status: CXL 2.0 8.2.8.3 Device Status Registers + * @mbox: CXL 2.0 8.2.8.4 Mailbox Registers + * @memdev: CXL 2.0 8.2.8.5 Memory Device Registers + */ + struct_group_tagged(cxl_device_regs, device_regs, + void __iomem *status, *mbox, *memdev; + ); + + struct_group_tagged(cxl_pmu_regs, pmu_regs, + void __iomem *pmu; + ); + + /* + * RCH downstream port specific RAS register + * @aer: CXL 3.0 8.2.1.1 RCH Downstream Port RCRB + */ + struct_group_tagged(cxl_rch_regs, rch_regs, + void __iomem *dport_aer; + ); + + /* + * RCD upstream port specific PCIe cap register + * @pcie_cap: CXL 3.0 8.2.1.2 RCD Upstream Port RCRB + */ + struct_group_tagged(cxl_rcd_regs, rcd_regs, + void __iomem *rcd_pcie_cap; + ); +}; + +struct cxl_reg_map { + bool valid; + int id; + unsigned long offset; + unsigned long size; +}; + +struct cxl_component_reg_map { + struct cxl_reg_map hdm_decoder; + struct cxl_reg_map ras; +}; + +struct cxl_device_reg_map { + struct cxl_reg_map status; + struct cxl_reg_map mbox; + struct cxl_reg_map memdev; +}; + +struct cxl_pmu_reg_map { + struct cxl_reg_map pmu; +}; + +/** + * struct cxl_register_map - DVSEC harvested register block mapping parameters + * @host: device for devm operations and logging + * @base: virtual base of the register-block-BAR + @block_offset + * @resource: physical resource base of the register block + * @max_size: maximum mapping size to perform register search + * @reg_type: see enum cxl_regloc_type + * @component_map: cxl_reg_map for component registers + * @device_map: cxl_reg_maps for device registers + * @pmu_map: cxl_reg_maps for CXL Performance Monitoring Units + */ +struct cxl_register_map { + struct device *host; + void __iomem *base; + resource_size_t resource; + resource_size_t max_size; + u8 reg_type; + union { + struct cxl_component_reg_map component_map; + struct cxl_device_reg_map device_map; + struct cxl_pmu_reg_map pmu_map; + }; +}; + +/** + * struct cxl_dpa_perf - DPA performance property entry + * @dpa_range: range for DPA address + * @coord: QoS performance data (i.e. latency, bandwidth) + * @cdat_coord: raw QoS performance data from CDAT + * @qos_class: QoS Class cookies + */ +struct cxl_dpa_perf { + struct range dpa_range; + struct access_coordinate coord[ACCESS_COORDINATE_MAX]; + struct access_coordinate cdat_coord[ACCESS_COORDINATE_MAX]; + int qos_class; +}; + +enum cxl_partition_mode { + CXL_PARTMODE_RAM, + CXL_PARTMODE_PMEM, +}; + +/** + * struct cxl_dpa_partition - DPA partition descriptor + * @res: shortcut to the partition in the DPA resource tree (cxlds->dpa_res) + * @perf: performance attributes of the partition from CDAT + * @mode: operation mode for the DPA capacity, e.g. ram, pmem, dynamic... + */ +struct cxl_dpa_partition { + struct resource res; + struct cxl_dpa_perf perf; + enum cxl_partition_mode mode; +}; + +#define CXL_NR_PARTITIONS_MAX 2 + +/** + * struct cxl_dev_state - The driver device state + * + * cxl_dev_state represents the CXL driver/device state. It provides an + * interface to mailbox commands as well as some cached data about the device. + * Currently only memory devices are represented. + * + * @dev: The device associated with this CXL state + * @cxlmd: The device representing the CXL.mem capabilities of @dev + * @reg_map: component and ras register mapping parameters + * @regs: Parsed register blocks + * @cxl_dvsec: Offset to the PCIe device DVSEC + * @rcd: operating in RCD mode (CXL 3.0 9.11.8 CXL Devices Attached to an RCH) + * @media_ready: Indicate whether the device media is usable + * @dpa_res: Overall DPA resource tree for the device + * @part: DPA partition array + * @nr_partitions: Number of DPA partitions + * @serial: PCIe Device Serial Number + * @type: Generic Memory Class device or Vendor Specific Memory device + * @cxl_mbox: CXL mailbox context + * @cxlfs: CXL features context + */ +struct cxl_dev_state { + /* public for Type2 drivers */ + struct device *dev; + struct cxl_memdev *cxlmd; + + /* private for Type2 drivers */ + struct cxl_register_map reg_map; + struct cxl_device_regs regs; + int cxl_dvsec; + bool rcd; + bool media_ready; + struct resource dpa_res; + struct cxl_dpa_partition part[CXL_NR_PARTITIONS_MAX]; + unsigned int nr_partitions; + u64 serial; + enum cxl_devtype type; + struct cxl_mailbox cxl_mbox; +#ifdef CONFIG_CXL_FEATURES + struct cxl_features_state *cxlfs; +#endif +}; + +struct cxl_dev_state *_devm_cxl_dev_state_create(struct device *dev, + enum cxl_devtype type, + u64 serial, u16 dvsec, + size_t size, bool has_mbox); + +/** + * cxl_dev_state_create - safely create and cast a cxl dev state embedded in a + * driver specific struct. + * + * @parent: device behind the request + * @type: CXL device type + * @serial: device identification + * @dvsec: dvsec capability offset + * @drv_struct: driver struct embedding a cxl_dev_state struct + * @member: name of the struct cxl_dev_state member in drv_struct + * @mbox: true if mailbox supported + * + * Returns a pointer to the drv_struct allocated and embedding a cxl_dev_state + * struct initialized. + * + * Introduced for Type2 driver support. + */ +#define devm_cxl_dev_state_create(parent, type, serial, dvsec, drv_struct, member, mbox) \ + ({ \ + static_assert(__same_type(struct cxl_dev_state, \ + ((drv_struct *)NULL)->member)); \ + static_assert(offsetof(drv_struct, member) == 0); \ + (drv_struct *)_devm_cxl_dev_state_create(parent, type, serial, dvsec, \ + sizeof(drv_struct), mbox); \ + }) +#endif /* __CXL_CXL_H__ */ diff --git a/include/linux/bootmem_info.h b/include/linux/bootmem_info.h index 4c506e76a808..492ceeb1cdf8 100644 --- a/include/linux/bootmem_info.h +++ b/include/linux/bootmem_info.h @@ -44,10 +44,6 @@ static inline void free_bootmem_page(struct page *page) { enum bootmem_type type = bootmem_type(page); - /* - * The reserve_bootmem_region sets the reserved flag on bootmem - * pages. - */ VM_BUG_ON_PAGE(page_ref_count(page) != 2, page); if (type == SECTION_INFO || type == MIX_SECTION_INFO) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 0136a108d083..b4b703c90ca9 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1541,6 +1541,8 @@ bool bpf_has_frame_pointer(unsigned long ip); int bpf_jit_charge_modmem(u32 size); void bpf_jit_uncharge_modmem(u32 size); bool bpf_prog_has_trampoline(const struct bpf_prog *prog); +bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog, + int insn_idx); #else static inline int bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr, diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 53e8664cb566..b148f816f25b 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -630,16 +630,17 @@ struct bpf_insn_aux_data { /* below fields are initialized once */ unsigned int orig_idx; /* original instruction index */ - bool jmp_point; - bool prune_point; + u32 jmp_point:1; + u32 prune_point:1; /* ensure we check state equivalence and save state checkpoint and * this instruction, regardless of any heuristics */ - bool force_checkpoint; + u32 force_checkpoint:1; /* true if instruction is a call to a helper function that * accepts callback function as a parameter. */ - bool calls_callback; + u32 calls_callback:1; + u32 indirect_target:1; /* if it is an indirect jump target */ /* * CFG strongly connected component this instruction belongs to, * zero if it is a singleton SCC. diff --git a/include/linux/filter.h b/include/linux/filter.h index f552170eacf4..1ec6d5ba64cc 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -1108,6 +1108,8 @@ sk_filter_reason(struct sock *sk, struct sk_buff *skb) return sk_filter_trim_cap(sk, skb, 1); } +struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp, + int *err); struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err); void bpf_prog_free(struct bpf_prog *fp); @@ -1153,7 +1155,7 @@ u64 __bpf_call_base(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5); ((u64 (*)(u64, u64, u64, u64, u64, const struct bpf_insn *)) \ (void *)__bpf_call_base) -struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog); +struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog); void bpf_jit_compile(struct bpf_prog *prog); bool bpf_jit_needs_zext(void); bool bpf_jit_inlines_helper_call(s32 imm); @@ -1184,6 +1186,31 @@ static inline bool bpf_dump_raw_ok(const struct cred *cred) struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off, const struct bpf_insn *patch, u32 len); + +#ifdef CONFIG_BPF_SYSCALL +struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, + const struct bpf_insn *patch, u32 len); +struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env); +void bpf_restore_insn_aux_data(struct bpf_verifier_env *env, + struct bpf_insn_aux_data *orig_insn_aux); +#else +static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, + const struct bpf_insn *patch, u32 len) +{ + return ERR_PTR(-ENOTSUPP); +} + +static inline struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env) +{ + return NULL; +} + +static inline void bpf_restore_insn_aux_data(struct bpf_verifier_env *env, + struct bpf_insn_aux_data *orig_insn_aux) +{ +} +#endif /* CONFIG_BPF_SYSCALL */ + int bpf_remove_insns(struct bpf_prog *prog, u32 off, u32 cnt); static inline bool xdp_return_frame_no_direct(void) @@ -1310,9 +1337,14 @@ int bpf_jit_get_func_addr(const struct bpf_prog *prog, const char *bpf_jit_get_prog_name(struct bpf_prog *prog); -struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *fp); +struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog); void bpf_jit_prog_release_other(struct bpf_prog *fp, struct bpf_prog *fp_other); +static inline bool bpf_prog_need_blind(const struct bpf_prog *prog) +{ + return prog->blinding_requested && !prog->blinded; +} + static inline void bpf_jit_dump(unsigned int flen, unsigned int proglen, u32 pass, void *image) { @@ -1451,6 +1483,20 @@ static inline void bpf_prog_kallsyms_del(struct bpf_prog *fp) { } +static inline bool bpf_prog_need_blind(const struct bpf_prog *prog) +{ + return false; +} + +static inline +struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog) +{ + return prog; +} + +static inline void bpf_jit_prog_release_other(struct bpf_prog *fp, struct bpf_prog *fp_other) +{ +} #endif /* CONFIG_BPF_JIT */ void bpf_prog_kallsyms_del_all(struct bpf_prog *fp); diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h index a53a00d36228..7e785aa6d35d 100644 --- a/include/linux/jbd2.h +++ b/include/linux/jbd2.h @@ -429,22 +429,46 @@ struct jbd2_inode { unsigned long i_flags; /** - * @i_dirty_start: + * @i_dirty_start_page: + * + * Dirty range start in PAGE_SIZE units. + * + * The dirty range is empty if @i_dirty_start_page is greater than or + * equal to @i_dirty_end_page. * - * Offset in bytes where the dirty range for this inode starts. * [j_list_lock] */ - loff_t i_dirty_start; + pgoff_t i_dirty_start_page; /** - * @i_dirty_end: + * @i_dirty_end_page: * - * Inclusive offset in bytes where the dirty range for this inode - * ends. [j_list_lock] + * Dirty range end in PAGE_SIZE units (exclusive). + * + * [j_list_lock] */ - loff_t i_dirty_end; + pgoff_t i_dirty_end_page; }; +/* + * Lockless readers treat start_page >= end_page as an empty range. + * Writers publish a new non-empty range by storing i_dirty_end_page before + * i_dirty_start_page. + */ +static inline bool jbd2_jinode_get_dirty_range(const struct jbd2_inode *jinode, + loff_t *start, loff_t *end) +{ + pgoff_t start_page = READ_ONCE(jinode->i_dirty_start_page); + pgoff_t end_page = READ_ONCE(jinode->i_dirty_end_page); + + if (start_page >= end_page) + return false; + + *start = (loff_t)start_page << PAGE_SHIFT; + *end = ((loff_t)end_page << PAGE_SHIFT) - 1; + return true; +} + struct jbd2_revoke_table_s; /** diff --git a/include/linux/memblock.h b/include/linux/memblock.h index 9eac4f268359..b0f750d22a7b 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -173,8 +173,6 @@ void __next_mem_range_rev(u64 *idx, int nid, enum memblock_flags flags, struct memblock_type *type_b, phys_addr_t *out_start, phys_addr_t *out_end, int *out_nid); -void memblock_free_late(phys_addr_t base, phys_addr_t size); - #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP static inline void __next_physmem_range(u64 *idx, struct memblock_type *type, phys_addr_t *out_start, diff --git a/include/linux/mm.h b/include/linux/mm.h index 8260e28205e9..255e0f50ea32 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3928,9 +3928,6 @@ extern unsigned long free_reserved_area(void *start, void *end, extern void adjust_managed_page_count(struct page *page, long count); -extern void reserve_bootmem_region(phys_addr_t start, - phys_addr_t end, int nid); - /* Free the reserved page into the buddy system, so it gets managed. */ void free_reserved_page(struct page *page); diff --git a/include/linux/mtd/concat.h b/include/linux/mtd/concat.h index d6f653e07426..f8d4d6ac1fc1 100644 --- a/include/linux/mtd/concat.h +++ b/include/linux/mtd/concat.h @@ -9,6 +9,18 @@ #define MTD_CONCAT_H +/* + * Our storage structure: + * Subdev points to an array of pointers to struct mtd_info objects + * which is allocated along with this structure + * + */ +struct mtd_concat { + struct mtd_info mtd; + int num_subdev; + struct mtd_info *subdev[]; +}; + struct mtd_info *mtd_concat_create( struct mtd_info *subdev[], /* subdevices to concatenate */ int num_devs, /* number of subdevices */ @@ -16,5 +28,54 @@ struct mtd_info *mtd_concat_create( void mtd_concat_destroy(struct mtd_info *mtd); -#endif +/** + * mtd_virt_concat_node_create - Create a component for concatenation + * + * Returns a positive number representing the no. of devices found for + * concatenation, or a negative error code. + * + * List all the devices for concatenations found in DT and create a + * component for concatenation. + */ +int mtd_virt_concat_node_create(void); +/** + * mtd_virt_concat_add - add mtd_info object to the list of subdevices for concatenation + * @mtd: pointer to new MTD device info structure + * + * Returns true if the mtd_info object is added successfully else returns false. + * + * The mtd_info object is added to the list of subdevices for concatenation. + * It returns true if a match is found, and false if all subdevices have + * already been added or if the mtd_info object does not match any of the + * intended MTD devices. + */ +bool mtd_virt_concat_add(struct mtd_info *mtd); + +/** + * mtd_virt_concat_create_join - Create and register the concatenated MTD device + * + * Returns 0 on succes, or a negative error code. + * + * Creates and registers the concatenated MTD device + */ +int mtd_virt_concat_create_join(void); + +/** + * mtd_virt_concat_destroy - Remove the concat that includes a specific mtd device + * as one of its components. + * @mtd: pointer to MTD device info structure. + * + * Returns 0 on succes, or a negative error code. + * + * If the mtd_info object is part of a concatenated device, all other MTD devices + * within that concat are registered individually. The concatenated device is then + * removed, along with its concatenation component. + * + */ +int mtd_virt_concat_destroy(struct mtd_info *mtd); + +void mtd_virt_concat_destroy_joins(void); +void mtd_virt_concat_destroy_items(void); + +#endif diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 6a024cf1c53a..58abd306ebe3 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -477,8 +477,9 @@ struct spinand_ecc_info { const struct mtd_ooblayout_ops *ooblayout; }; -#define SPINAND_HAS_QE_BIT BIT(0) -#define SPINAND_HAS_CR_FEAT_BIT BIT(1) +/* SPI NAND flags */ +#define SPINAND_HAS_QE_BIT BIT(0) +#define SPINAND_HAS_CR_FEAT_BIT BIT(1) #define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2) #define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3) #define SPINAND_NO_RAW_ACCESS BIT(4) diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 70b2b661f42c..13f71202845e 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -657,4 +657,16 @@ struct procmap_query { __u64 build_id_addr; /* in */ }; +/* + * Shutdown the filesystem. + */ +#define FS_IOC_SHUTDOWN _IOR('X', 125, __u32) + +/* + * Flags for FS_IOC_SHUTDOWN + */ +#define FS_SHUTDOWN_FLAGS_DEFAULT 0x0 +#define FS_SHUTDOWN_FLAGS_LOGFLUSH 0x1 /* flush log but not data*/ +#define FS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2 /* don't flush log nor data */ + #endif /* _UAPI_LINUX_FS_H */ diff --git a/init/initramfs.c b/init/initramfs.c index d29fe0a5ccfc..58db15fb18fd 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -652,13 +652,6 @@ disable: void __weak __init free_initrd_mem(unsigned long start, unsigned long end) { -#ifdef CONFIG_ARCH_KEEP_MEMBLOCK - unsigned long aligned_start = ALIGN_DOWN(start, PAGE_SIZE); - unsigned long aligned_end = ALIGN(end, PAGE_SIZE); - - memblock_free((void *)aligned_start, aligned_end - aligned_start); -#endif - free_reserved_area((void *)start, (void *)end, POISON_FREE_INITMEM, "initrd"); } diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c index 08d008cc471e..802656c6fd3c 100644 --- a/kernel/bpf/arena.c +++ b/kernel/bpf/arena.c @@ -341,6 +341,16 @@ static void arena_vm_open(struct vm_area_struct *vma) refcount_inc(&vml->mmap_count); } +static int arena_vm_may_split(struct vm_area_struct *vma, unsigned long addr) +{ + return -EINVAL; +} + +static int arena_vm_mremap(struct vm_area_struct *vma) +{ + return -EINVAL; +} + static void arena_vm_close(struct vm_area_struct *vma) { struct bpf_map *map = vma->vm_file->private_data; @@ -417,6 +427,8 @@ out_unlock_sigsegv: static const struct vm_operations_struct arena_vm_ops = { .open = arena_vm_open, + .may_split = arena_vm_may_split, + .mremap = arena_vm_mremap, .close = arena_vm_close, .fault = arena_vm_fault, }; @@ -486,10 +498,11 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma) arena->user_vm_end = vma->vm_end; /* * bpf_map_mmap() checks that it's being mmaped as VM_SHARED and - * clears VM_MAYEXEC. Set VM_DONTEXPAND as well to avoid - * potential change of user_vm_start. + * clears VM_MAYEXEC. Set VM_DONTEXPAND to avoid potential change + * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from + * being copied into the child process on fork. */ - vm_flags_set(vma, VM_DONTEXPAND); + vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY); vma->vm_ops = &arena_vm_ops; return 0; } @@ -549,6 +562,10 @@ static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt u32 uaddr32; int ret, i; + if (node_id != NUMA_NO_NODE && + ((unsigned int)node_id >= nr_node_ids || !node_online(node_id))) + return 0; + if (page_cnt > page_cnt_max) return 0; diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c index 05b366b821c3..521cb9d7e8c7 100644 --- a/kernel/bpf/bpf_struct_ops.c +++ b/kernel/bpf/bpf_struct_ops.c @@ -811,9 +811,6 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key, goto reset_unlock; } - /* Poison pointer on error instead of return for backward compatibility */ - bpf_prog_assoc_struct_ops(prog, &st_map->map); - link = kzalloc_obj(*link, GFP_USER); if (!link) { bpf_prog_put(prog); @@ -824,6 +821,9 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key, &bpf_struct_ops_link_lops, prog, prog->expected_attach_type); *plink++ = &link->link; + /* Poison pointer on error instead of return for backward compatibility */ + bpf_prog_assoc_struct_ops(prog, &st_map->map); + ksym = kzalloc_obj(*ksym, GFP_USER); if (!ksym) { err = -ENOMEM; @@ -906,6 +906,7 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key, reset_unlock: bpf_struct_ops_map_free_ksyms(st_map); bpf_struct_ops_map_free_image(st_map); + bpf_struct_ops_map_dissoc_progs(st_map); bpf_struct_ops_map_put_progs(st_map); memset(uvalue, 0, map->value_size); memset(kvalue, 0, map->value_size); diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 066b86e7233c..8b018ff48875 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -1491,24 +1491,11 @@ void bpf_jit_prog_release_other(struct bpf_prog *fp, struct bpf_prog *fp_other) bpf_prog_clone_free(fp_other); } -static void adjust_insn_arrays(struct bpf_prog *prog, u32 off, u32 len) -{ -#ifdef CONFIG_BPF_SYSCALL - struct bpf_map *map; - int i; - - if (len <= 1) - return; - - for (i = 0; i < prog->aux->used_map_cnt; i++) { - map = prog->aux->used_maps[i]; - if (map->map_type == BPF_MAP_TYPE_INSN_ARRAY) - bpf_insn_array_adjust(map, off, len); - } -#endif -} - -struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *prog) +/* + * Now this function is used only to blind the main prog and must be invoked only when + * bpf_prog_need_blind() returns true. + */ +struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog) { struct bpf_insn insn_buff[16], aux[2]; struct bpf_prog *clone, *tmp; @@ -1516,13 +1503,17 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *prog) struct bpf_insn *insn; int i, rewritten; - if (!prog->blinding_requested || prog->blinded) - return prog; + if (WARN_ON_ONCE(env && env->prog != prog)) + return ERR_PTR(-EINVAL); clone = bpf_prog_clone_create(prog, GFP_USER); if (!clone) return ERR_PTR(-ENOMEM); + /* make sure bpf_patch_insn_data() patches the correct prog */ + if (env) + env->prog = clone; + insn_cnt = clone->len; insn = clone->insnsi; @@ -1550,21 +1541,28 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *prog) if (!rewritten) continue; - tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten); - if (IS_ERR(tmp)) { + if (env) + tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten); + else + tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten); + + if (IS_ERR_OR_NULL(tmp)) { + if (env) + /* restore the original prog */ + env->prog = prog; /* Patching may have repointed aux->prog during * realloc from the original one, so we need to * fix it up here on error. */ bpf_jit_prog_release_other(prog, clone); - return tmp; + return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM); } clone = tmp; insn_delta = rewritten - 1; - /* Instructions arrays must be updated using absolute xlated offsets */ - adjust_insn_arrays(clone, prog->aux->subprog_start + i, rewritten); + if (env) + env->prog = clone; /* Walk new program and skip insns we just inserted. */ insn = clone->insnsi + i + insn_delta; @@ -1575,6 +1573,15 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *prog) clone->blinded = 1; return clone; } + +bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog, + int insn_idx) +{ + if (!env) + return false; + insn_idx += prog->aux->subprog_start; + return env->insn_aux_data[insn_idx].indirect_target; +} #endif /* CONFIG_BPF_JIT */ /* Base function for offset calculation. Needs to go into .text section, @@ -2533,18 +2540,55 @@ static bool bpf_prog_select_interpreter(struct bpf_prog *fp) return select_interpreter; } -/** - * bpf_prog_select_runtime - select exec runtime for BPF program - * @fp: bpf_prog populated with BPF program - * @err: pointer to error variable - * - * Try to JIT eBPF program, if JIT is not available, use interpreter. - * The BPF program will be executed via bpf_prog_run() function. - * - * Return: the &fp argument along with &err set to 0 for success or - * a negative errno code on failure - */ -struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err) +static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) +{ +#ifdef CONFIG_BPF_JIT + struct bpf_prog *orig_prog; + struct bpf_insn_aux_data *orig_insn_aux; + + if (!bpf_prog_need_blind(prog)) + return bpf_int_jit_compile(env, prog); + + if (env) { + /* + * If env is not NULL, we are called from the end of bpf_check(), at this + * point, only insn_aux_data is used after failure, so it should be restored + * on failure. + */ + orig_insn_aux = bpf_dup_insn_aux_data(env); + if (!orig_insn_aux) + return prog; + } + + orig_prog = prog; + prog = bpf_jit_blind_constants(env, prog); + /* + * If blinding was requested and we failed during blinding, we must fall + * back to the interpreter. + */ + if (IS_ERR(prog)) + goto out_restore; + + prog = bpf_int_jit_compile(env, prog); + if (prog->jited) { + bpf_jit_prog_release_other(prog, orig_prog); + if (env) + vfree(orig_insn_aux); + return prog; + } + + bpf_jit_prog_release_other(orig_prog, prog); + +out_restore: + prog = orig_prog; + if (env) + bpf_restore_insn_aux_data(env, orig_insn_aux); +#endif + return prog; +} + +struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp, + int *err) { /* In case of BPF to BPF calls, verifier did all the prep * work with regards to JITing, etc. @@ -2572,7 +2616,7 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err) if (*err) return fp; - fp = bpf_int_jit_compile(fp); + fp = bpf_prog_jit_compile(env, fp); bpf_prog_jit_attempt_done(fp); if (!fp->jited && jit_needed) { *err = -ENOTSUPP; @@ -2598,6 +2642,22 @@ finalize: return fp; } + +/** + * bpf_prog_select_runtime - select exec runtime for BPF program + * @fp: bpf_prog populated with BPF program + * @err: pointer to error variable + * + * Try to JIT eBPF program, if JIT is not available, use interpreter. + * The BPF program will be executed via bpf_prog_run() function. + * + * Return: the &fp argument along with &err set to 0 for success or + * a negative errno code on failure + */ +struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err) +{ + return __bpf_prog_select_runtime(NULL, fp, err); +} EXPORT_SYMBOL_GPL(bpf_prog_select_runtime); static unsigned int __bpf_prog_ret1(const void *ctx, @@ -3085,7 +3145,7 @@ const struct bpf_func_proto bpf_tail_call_proto = { * It is encouraged to implement bpf_int_jit_compile() instead, so that * eBPF and implicitly also cBPF can get JITed! */ -struct bpf_prog * __weak bpf_int_jit_compile(struct bpf_prog *prog) +struct bpf_prog * __weak bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog) { return prog; } diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index 67c9b28767e1..fba9e8c00878 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -183,6 +183,18 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env, data[i].seen = old_seen; data[i].zext_dst = insn_has_def32(insn + i); } + + /* + * The indirect_target flag of the original instruction was moved to the last of the + * new instructions by the above memmove and memset, but the indirect jump target is + * actually the first instruction, so move it back. This also matches with the behavior + * of bpf_insn_array_adjust(), which preserves xlated_off to point to the first new + * instruction. + */ + if (data[off + cnt - 1].indirect_target) { + data[off].indirect_target = 1; + data[off + cnt - 1].indirect_target = 0; + } } static void adjust_subprog_starts(struct bpf_verifier_env *env, u32 off, u32 len) @@ -232,8 +244,8 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) } } -static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, - const struct bpf_insn *patch, u32 len) +struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, + const struct bpf_insn *patch, u32 len) { struct bpf_prog *new_prog; struct bpf_insn_aux_data *new_data = NULL; @@ -973,7 +985,47 @@ patch_insn_buf: return 0; } -int bpf_jit_subprogs(struct bpf_verifier_env *env) +static u32 *bpf_dup_subprog_starts(struct bpf_verifier_env *env) +{ + u32 *starts = NULL; + + starts = kvmalloc_objs(u32, env->subprog_cnt, GFP_KERNEL_ACCOUNT); + if (starts) { + for (int i = 0; i < env->subprog_cnt; i++) + starts[i] = env->subprog_info[i].start; + } + return starts; +} + +static void bpf_restore_subprog_starts(struct bpf_verifier_env *env, u32 *orig_starts) +{ + for (int i = 0; i < env->subprog_cnt; i++) + env->subprog_info[i].start = orig_starts[i]; + /* restore the start of fake 'exit' subprog as well */ + env->subprog_info[env->subprog_cnt].start = env->prog->len; +} + +struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env) +{ + size_t size; + void *new_aux; + + size = array_size(sizeof(struct bpf_insn_aux_data), env->prog->len); + new_aux = __vmalloc(size, GFP_KERNEL_ACCOUNT); + if (new_aux) + memcpy(new_aux, env->insn_aux_data, size); + return new_aux; +} + +void bpf_restore_insn_aux_data(struct bpf_verifier_env *env, + struct bpf_insn_aux_data *orig_insn_aux) +{ + /* the expanded elements are zero-filled, so no special handling is required */ + vfree(env->insn_aux_data); + env->insn_aux_data = orig_insn_aux; +} + +static int jit_subprogs(struct bpf_verifier_env *env) { struct bpf_prog *prog = env->prog, **func, *tmp; int i, j, subprog_start, subprog_end = 0, len, subprog; @@ -981,10 +1033,6 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) struct bpf_insn *insn; void *old_bpf_func; int err, num_exentries; - int old_len, subprog_start_adjustment = 0; - - if (env->subprog_cnt <= 1) - return 0; for (i = 0, insn = prog->insnsi; i < prog->len; i++, insn++) { if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn)) @@ -1053,10 +1101,11 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) goto out_free; func[i]->is_func = 1; func[i]->sleepable = prog->sleepable; + func[i]->blinded = prog->blinded; func[i]->aux->func_idx = i; /* Below members will be freed only at prog->aux */ func[i]->aux->btf = prog->aux->btf; - func[i]->aux->subprog_start = subprog_start + subprog_start_adjustment; + func[i]->aux->subprog_start = subprog_start; func[i]->aux->func_info = prog->aux->func_info; func[i]->aux->func_info_cnt = prog->aux->func_info_cnt; func[i]->aux->poke_tab = prog->aux->poke_tab; @@ -1110,17 +1159,10 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) func[i]->aux->exception_cb = env->subprog_info[i].is_exception_cb; func[i]->aux->changes_pkt_data = env->subprog_info[i].changes_pkt_data; func[i]->aux->might_sleep = env->subprog_info[i].might_sleep; + func[i]->aux->token = prog->aux->token; if (!i) func[i]->aux->exception_boundary = env->seen_exception; - - /* - * To properly pass the absolute subprog start to jit - * all instruction adjustments should be accumulated - */ - old_len = func[i]->len; - func[i] = bpf_int_jit_compile(func[i]); - subprog_start_adjustment += func[i]->len - old_len; - + func[i] = bpf_int_jit_compile(env, func[i]); if (!func[i]->jited) { err = -ENOTSUPP; goto out_free; @@ -1164,7 +1206,7 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) } for (i = 0; i < env->subprog_cnt; i++) { old_bpf_func = func[i]->bpf_func; - tmp = bpf_int_jit_compile(func[i]); + tmp = bpf_int_jit_compile(env, func[i]); if (tmp != func[i] || func[i]->bpf_func != old_bpf_func) { verbose(env, "JIT doesn't support bpf-to-bpf calls\n"); err = -ENOTSUPP; @@ -1246,16 +1288,87 @@ out_free: } kfree(func); out_undo_insn: + bpf_prog_jit_attempt_done(prog); + return err; +} + +int bpf_jit_subprogs(struct bpf_verifier_env *env) +{ + int err, i; + bool blinded = false; + struct bpf_insn *insn; + struct bpf_prog *prog, *orig_prog; + struct bpf_insn_aux_data *orig_insn_aux; + u32 *orig_subprog_starts; + + if (env->subprog_cnt <= 1) + return 0; + + prog = orig_prog = env->prog; + if (bpf_prog_need_blind(prog)) { + orig_insn_aux = bpf_dup_insn_aux_data(env); + if (!orig_insn_aux) { + err = -ENOMEM; + goto out_cleanup; + } + orig_subprog_starts = bpf_dup_subprog_starts(env); + if (!orig_subprog_starts) { + vfree(orig_insn_aux); + err = -ENOMEM; + goto out_cleanup; + } + prog = bpf_jit_blind_constants(env, prog); + if (IS_ERR(prog)) { + err = -ENOMEM; + prog = orig_prog; + goto out_restore; + } + blinded = true; + } + + err = jit_subprogs(env); + if (err) + goto out_jit_err; + + if (blinded) { + bpf_jit_prog_release_other(prog, orig_prog); + kvfree(orig_subprog_starts); + vfree(orig_insn_aux); + } + + return 0; + +out_jit_err: + if (blinded) { + bpf_jit_prog_release_other(orig_prog, prog); + /* roll back to the clean original prog */ + prog = env->prog = orig_prog; + goto out_restore; + } else { + if (err != -EFAULT) { + /* + * We will fall back to interpreter mode when err is not -EFAULT, before + * that, insn->off and insn->imm should be restored to their original + * values since they were modified by jit_subprogs. + */ + for (i = 0, insn = prog->insnsi; i < prog->len; i++, insn++) { + if (!bpf_pseudo_call(insn)) + continue; + insn->off = 0; + insn->imm = env->insn_aux_data[i].call_imm; + } + } + goto out_cleanup; + } + +out_restore: + bpf_restore_subprog_starts(env, orig_subprog_starts); + bpf_restore_insn_aux_data(env, orig_insn_aux); + kvfree(orig_subprog_starts); +out_cleanup: /* cleanup main prog to be interpreted */ prog->jit_requested = 0; prog->blinding_requested = 0; - for (i = 0, insn = prog->insnsi; i < prog->len; i++, insn++) { - if (!bpf_pseudo_call(insn)) - continue; - insn->off = 0; - insn->imm = env->insn_aux_data[i].call_imm; - } - bpf_prog_jit_attempt_done(prog); return err; } diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index bb95e287b0dc..2bb60200c266 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -845,7 +845,13 @@ int bpf_bprintf_prepare(const char *fmt, u32 fmt_size, const u64 *raw_args, data->buf = buffers->buf; for (i = 0; i < fmt_size; i++) { - if ((!isprint(fmt[i]) && !isspace(fmt[i])) || !isascii(fmt[i])) { + unsigned char c = fmt[i]; + + /* + * Permit bytes >= 0x80 in plain text so UTF-8 literals can pass + * through unchanged, while still rejecting ASCII control bytes. + */ + if (isascii(c) && !isprint(c) && !isspace(c)) { err = -EINVAL; goto out; } @@ -867,6 +873,15 @@ int bpf_bprintf_prepare(const char *fmt, u32 fmt_size, const u64 *raw_args, * always access fmt[i + 1], in the worst case it will be a 0 */ i++; + c = fmt[i]; + /* + * The format parser below only understands ASCII conversion + * specifiers and modifiers, so reject non-ASCII after '%'. + */ + if (!isascii(c)) { + err = -EINVAL; + goto out; + } /* skip optional "[0 +-][num]" width formatting field */ while (fmt[i] == '0' || fmt[i] == '+' || fmt[i] == '-' || diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c index 1fb4c511db5a..332e6e003f27 100644 --- a/kernel/bpf/liveness.c +++ b/kernel/bpf/liveness.c @@ -574,7 +574,7 @@ static int print_instances(struct bpf_verifier_env *env) * * precise {frame=N, off=V} -- known absolute frame index and byte offset * | - * offset-imprecise {frame=N, off=OFF_IMPRECISE} + * offset-imprecise {frame=N, cnt=0} * | -- known frame identity, unknown offset * fully-imprecise {frame=ARG_IMPRECISE, mask=bitmask} * -- unknown frame identity; .mask is a @@ -607,8 +607,6 @@ enum arg_track_state { ARG_IMPRECISE = -3, /* lost identity; .mask is arg bitmask */ }; -#define OFF_IMPRECISE S16_MIN /* arg identity known but offset unknown */ - /* Track callee stack slots fp-8 through fp-512 (64 slots of 8 bytes each) */ #define MAX_ARG_SPILL_SLOTS 64 @@ -622,28 +620,6 @@ static bool arg_is_fp(const struct arg_track *at) return at->frame >= 0 || at->frame == ARG_IMPRECISE; } -/* - * Clear all tracked callee stack slots overlapping the byte range - * [off, off+sz-1] where off is a negative FP-relative offset. - */ -static void clear_overlapping_stack_slots(struct arg_track *at_stack, s16 off, u32 sz) -{ - struct arg_track none = { .frame = ARG_NONE }; - - if (off == OFF_IMPRECISE) { - for (int i = 0; i < MAX_ARG_SPILL_SLOTS; i++) - at_stack[i] = none; - return; - } - for (int i = 0; i < MAX_ARG_SPILL_SLOTS; i++) { - int slot_start = -((i + 1) * 8); - int slot_end = slot_start + 8; - - if (slot_start < off + (int)sz && slot_end > off) - at_stack[i] = none; - } -} - static void verbose_arg_track(struct bpf_verifier_env *env, struct arg_track *at) { int i; @@ -863,16 +839,13 @@ static void arg_track_alu64(struct arg_track *dst, const struct arg_track *src) *dst = arg_join_imprecise(*dst, *src); } -static s16 arg_add(s16 off, s64 delta) +static bool arg_add(s16 off, s64 delta, s16 *out) { - s64 res; + s16 d = delta; - if (off == OFF_IMPRECISE) - return OFF_IMPRECISE; - res = (s64)off + delta; - if (res < S16_MIN + 1 || res > S16_MAX) - return OFF_IMPRECISE; - return res; + if (d != delta) + return true; + return check_add_overflow(off, d, out); } static void arg_padd(struct arg_track *at, s64 delta) @@ -882,9 +855,9 @@ static void arg_padd(struct arg_track *at, s64 delta) if (at->off_cnt == 0) return; for (i = 0; i < at->off_cnt; i++) { - s16 new_off = arg_add(at->off[i], delta); + s16 new_off; - if (new_off == OFF_IMPRECISE) { + if (arg_add(at->off[i], delta, &new_off)) { at->off_cnt = 0; return; } @@ -899,8 +872,6 @@ static void arg_padd(struct arg_track *at, s64 delta) */ static int fp_off_to_slot(s16 off) { - if (off == OFF_IMPRECISE) - return -1; if (off >= 0 || off < -(int)(MAX_ARG_SPILL_SLOTS * 8)) return -1; if (off % 8) @@ -930,9 +901,11 @@ static struct arg_track fill_from_stack(struct bpf_insn *insn, return imp; for (i = 0; i < cnt; i++) { - s16 fp_off = arg_add(at_out[reg].off[i], insn->off); - int slot = fp_off_to_slot(fp_off); + s16 fp_off, slot; + if (arg_add(at_out[reg].off[i], insn->off, &fp_off)) + return imp; + slot = fp_off_to_slot(fp_off); if (slot < 0) return imp; result = __arg_track_join(result, at_stack_out[slot]); @@ -968,9 +941,12 @@ static void spill_to_stack(struct bpf_insn *insn, struct arg_track *at_out, return; } for (i = 0; i < cnt; i++) { - s16 fp_off = arg_add(at_out[reg].off[i], insn->off); - int slot = fp_off_to_slot(fp_off); + s16 fp_off; + int slot; + if (arg_add(at_out[reg].off[i], insn->off, &fp_off)) + continue; + slot = fp_off_to_slot(fp_off); if (slot < 0) continue; if (cnt == 1) @@ -980,6 +956,32 @@ static void spill_to_stack(struct bpf_insn *insn, struct arg_track *at_out, } } +/* + * Clear all tracked callee stack slots overlapping the byte range + * [off, off+sz-1] where off is a negative FP-relative offset. + */ +static void clear_overlapping_stack_slots(struct arg_track *at_stack, s16 off, u32 sz, int cnt) +{ + struct arg_track none = { .frame = ARG_NONE }; + + if (cnt == 0) { + for (int i = 0; i < MAX_ARG_SPILL_SLOTS; i++) + at_stack[i] = __arg_track_join(at_stack[i], none); + return; + } + for (int i = 0; i < MAX_ARG_SPILL_SLOTS; i++) { + int slot_start = -((i + 1) * 8); + int slot_end = slot_start + 8; + + if (slot_start < off + (int)sz && slot_end > off) { + if (cnt == 1) + at_stack[i] = none; + else + at_stack[i] = __arg_track_join(at_stack[i], none); + } + } +} + /* * Clear stack slots overlapping all possible FP offsets in @reg. */ @@ -990,18 +992,22 @@ static void clear_stack_for_all_offs(struct bpf_insn *insn, int cnt, i; if (reg == BPF_REG_FP) { - clear_overlapping_stack_slots(at_stack_out, insn->off, sz); + clear_overlapping_stack_slots(at_stack_out, insn->off, sz, 1); return; } cnt = at_out[reg].off_cnt; if (cnt == 0) { - clear_overlapping_stack_slots(at_stack_out, OFF_IMPRECISE, sz); + clear_overlapping_stack_slots(at_stack_out, 0, sz, cnt); return; } for (i = 0; i < cnt; i++) { - s16 fp_off = arg_add(at_out[reg].off[i], insn->off); + s16 fp_off; - clear_overlapping_stack_slots(at_stack_out, fp_off, sz); + if (arg_add(at_out[reg].off[i], insn->off, &fp_off)) { + clear_overlapping_stack_slots(at_stack_out, 0, sz, 0); + break; + } + clear_overlapping_stack_slots(at_stack_out, fp_off, sz, cnt); } } @@ -1042,6 +1048,12 @@ static void arg_track_log(struct bpf_verifier_env *env, struct bpf_insn *insn, i verbose(env, "\n"); } +static bool can_be_local_fp(int depth, int regno, struct arg_track *at) +{ + return regno == BPF_REG_FP || at->frame == depth || + (at->frame == ARG_IMPRECISE && (at->mask & BIT(depth))); +} + /* * Pure dataflow transfer function for arg_track state. * Updates at_out[] based on how the instruction modifies registers. @@ -1111,8 +1123,7 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn, at_out[r] = none; } else if (class == BPF_LDX) { u32 sz = bpf_size_to_bytes(BPF_SIZE(insn->code)); - bool src_is_local_fp = insn->src_reg == BPF_REG_FP || src->frame == depth || - (src->frame == ARG_IMPRECISE && (src->mask & BIT(depth))); + bool src_is_local_fp = can_be_local_fp(depth, insn->src_reg, src); /* * Reload from callee stack: if src is current-frame FP-derived @@ -1147,7 +1158,7 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn, bool dst_is_local_fp; /* Track spills to current-frame FP-derived callee stack */ - dst_is_local_fp = insn->dst_reg == BPF_REG_FP || dst->frame == depth; + dst_is_local_fp = can_be_local_fp(depth, insn->dst_reg, dst); if (dst_is_local_fp && BPF_MODE(insn->code) == BPF_MEM) spill_to_stack(insn, at_out, insn->dst_reg, at_stack_out, src, sz); @@ -1166,7 +1177,7 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn, } } else if (class == BPF_ST && BPF_MODE(insn->code) == BPF_MEM) { u32 sz = bpf_size_to_bytes(BPF_SIZE(insn->code)); - bool dst_is_local_fp = insn->dst_reg == BPF_REG_FP || dst->frame == depth; + bool dst_is_local_fp = can_be_local_fp(depth, insn->dst_reg, dst); /* BPF_ST to FP-derived dst: clear overlapping stack slots */ if (dst_is_local_fp) @@ -1316,8 +1327,7 @@ static int record_load_store_access(struct bpf_verifier_env *env, resolved.off_cnt = ptr->off_cnt; resolved.frame = ptr->frame; for (oi = 0; oi < ptr->off_cnt; oi++) { - resolved.off[oi] = arg_add(ptr->off[oi], insn->off); - if (resolved.off[oi] == OFF_IMPRECISE) { + if (arg_add(ptr->off[oi], insn->off, &resolved.off[oi])) { resolved.off_cnt = 0; break; } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 17667416600b..b297df056b92 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3085,10 +3085,6 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) if (err < 0) goto free_used_maps; - prog = bpf_prog_select_runtime(prog, &err); - if (err < 0) - goto free_used_maps; - err = bpf_prog_mark_insn_arrays_ready(prog); if (err < 0) goto free_used_maps; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9e4980128151..69d75515ed3f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3497,6 +3497,11 @@ static int insn_stack_access_flags(int frameno, int spi) return INSN_F_STACK_ACCESS | (spi << INSN_F_SPI_SHIFT) | frameno; } +static void mark_indirect_target(struct bpf_verifier_env *env, int idx) +{ + env->insn_aux_data[idx].indirect_target = true; +} + #define LR_FRAMENO_BITS 3 #define LR_SPI_BITS 6 #define LR_ENTRY_BITS (LR_SPI_BITS + LR_FRAMENO_BITS + 1) @@ -4544,6 +4549,9 @@ static int map_kptr_match_type(struct bpf_verifier_env *env, int perm_flags; const char *reg_name = ""; + if (base_type(reg->type) != PTR_TO_BTF_ID) + goto bad_type; + if (btf_is_kernel(reg->btf)) { perm_flags = PTR_MAYBE_NULL | PTR_TRUSTED | MEM_RCU; @@ -4556,7 +4564,7 @@ static int map_kptr_match_type(struct bpf_verifier_env *env, perm_flags |= MEM_PERCPU; } - if (base_type(reg->type) != PTR_TO_BTF_ID || (type_flag(reg->type) & ~perm_flags)) + if (type_flag(reg->type) & ~perm_flags) goto bad_type; /* We need to verify reg->type and reg->btf, before accessing reg->btf */ @@ -17545,12 +17553,14 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in } for (i = 0; i < n - 1; i++) { + mark_indirect_target(env, env->gotox_tmp_buf->items[i]); other_branch = push_stack(env, env->gotox_tmp_buf->items[i], env->insn_idx, env->cur_state->speculative); if (IS_ERR(other_branch)) return PTR_ERR(other_branch); } env->insn_idx = env->gotox_tmp_buf->items[n-1]; + mark_indirect_target(env, env->insn_idx); return INSN_IDX_UPDATED; } @@ -20155,6 +20165,14 @@ skip_full_check: adjust_btf_func(env); + /* extension progs temporarily inherit the attach_type of their targets + for verification purposes, so set it back to zero before returning + */ + if (env->prog->type == BPF_PROG_TYPE_EXT) + env->prog->expected_attach_type = 0; + + env->prog = __bpf_prog_select_runtime(env, env->prog, &ret); + err_release_maps: if (ret) release_insn_arrays(env); @@ -20166,12 +20184,6 @@ err_release_maps: if (!env->prog->aux->used_btfs) release_btfs(env); - /* extension progs temporarily inherit the attach_type of their targets - for verification purposes, so set it back to zero before returning - */ - if (env->prog->type == BPF_PROG_TYPE_EXT) - env->prog->expected_attach_type = 0; - *prog = env->prog; module_put(env->attach_btf_mod); diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index b525398f570c..e3ea524b0eeb 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -547,10 +547,10 @@ void __init swiotlb_exit(void) free_pages(tbl_vaddr, get_order(tbl_size)); free_pages((unsigned long)mem->slots, get_order(slots_size)); } else { - memblock_free_late(__pa(mem->areas), + memblock_free(mem->areas, array_size(sizeof(*mem->areas), mem->nareas)); - memblock_free_late(mem->start, tbl_size); - memblock_free_late(__pa(mem->slots), slots_size); + memblock_phys_free(mem->start, tbl_size); + memblock_free(mem->slots, slots_size); } memset(mem, 0, sizeof(*mem)); diff --git a/lib/bootconfig.c b/lib/bootconfig.c index 01966ab9a8b5..c470b93d5dbc 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -66,7 +66,7 @@ static inline void __init xbc_free_mem(void *addr, size_t size, bool early) if (early) memblock_free(addr, size); else if (addr) - memblock_free_late(__pa(addr), size); + memblock_free(addr, size); } #else /* !__KERNEL__ */ diff --git a/mm/internal.h b/mm/internal.h index c693646e5b3f..41398ecb2201 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1322,7 +1322,17 @@ static inline void vunmap_range_noflush(unsigned long start, unsigned long end) #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT DECLARE_STATIC_KEY_TRUE(deferred_pages); +static inline bool deferred_pages_enabled(void) +{ + return static_branch_unlikely(&deferred_pages); +} + bool __init deferred_grow_zone(struct zone *zone, unsigned int order); +#else +static inline bool deferred_pages_enabled(void) +{ + return false; +} #endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */ void init_deferred_page(unsigned long pfn, int nid); diff --git a/mm/kfence/core.c b/mm/kfence/core.c index 9eba46212edf..655dc5ce3240 100644 --- a/mm/kfence/core.c +++ b/mm/kfence/core.c @@ -736,10 +736,10 @@ static bool __init kfence_init_pool_early(void) * fails for the first page, and therefore expect addr==__kfence_pool in * most failure cases. */ - memblock_free_late(__pa(addr), KFENCE_POOL_SIZE - (addr - (unsigned long)__kfence_pool)); + memblock_free((void *)addr, KFENCE_POOL_SIZE - (addr - (unsigned long)__kfence_pool)); __kfence_pool = NULL; - memblock_free_late(__pa(kfence_metadata_init), KFENCE_METADATA_SIZE); + memblock_free(kfence_metadata_init, KFENCE_METADATA_SIZE); kfence_metadata_init = NULL; return false; diff --git a/mm/memblock.c b/mm/memblock.c index 2505ce8b319c..5629844b2804 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -17,6 +17,7 @@ #include #include #include +#include #ifdef CONFIG_KEXEC_HANDOVER #include @@ -384,26 +385,27 @@ static void __init_memblock memblock_remove_region(struct memblock_type *type, u */ void __init memblock_discard(void) { - phys_addr_t addr, size; + phys_addr_t size; + void *addr; if (memblock.reserved.regions != memblock_reserved_init_regions) { - addr = __pa(memblock.reserved.regions); + addr = memblock.reserved.regions; size = PAGE_ALIGN(sizeof(struct memblock_region) * memblock.reserved.max); if (memblock_reserved_in_slab) - kfree(memblock.reserved.regions); + kfree(addr); else - memblock_free_late(addr, size); + memblock_free(addr, size); } if (memblock.memory.regions != memblock_memory_init_regions) { - addr = __pa(memblock.memory.regions); + addr = memblock.memory.regions; size = PAGE_ALIGN(sizeof(struct memblock_region) * memblock.memory.max); if (memblock_memory_in_slab) - kfree(memblock.memory.regions); + kfree(addr); else - memblock_free_late(addr, size); + memblock_free(addr, size); } memblock_memory = NULL; @@ -893,13 +895,81 @@ int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size) return memblock_remove_range(&memblock.memory, base, size); } +static unsigned long __free_reserved_area(phys_addr_t start, phys_addr_t end, + int poison) +{ + unsigned long pages = 0, pfn; + + if (deferred_pages_enabled()) { + WARN(1, "Cannot free reserved memory because of deferred initialization of the memory map"); + return 0; + } + + for_each_valid_pfn(pfn, PFN_UP(start), PFN_DOWN(end)) { + struct page *page = pfn_to_page(pfn); + void *direct_map_addr; + + /* + * 'direct_map_addr' might be different from the kernel virtual + * address because some architectures use aliases. + * Going via physical address, pfn_to_page() and page_address() + * ensures that we get a _writeable_ alias for the memset(). + */ + direct_map_addr = page_address(page); + /* + * Perform a kasan-unchecked memset() since this memory + * has not been initialized. + */ + direct_map_addr = kasan_reset_tag(direct_map_addr); + if ((unsigned int)poison <= 0xFF) + memset(direct_map_addr, poison, PAGE_SIZE); + + free_reserved_page(page); + pages++; + } + return pages; +} + +unsigned long free_reserved_area(void *start, void *end, int poison, const char *s) +{ + phys_addr_t start_pa, end_pa; + unsigned long pages; + + /* + * end is the first address past the region and it may be beyond what + * __pa() or __pa_symbol() can handle. + * Use the address included in the range for the conversion and add back + * 1 afterwards. + */ + if (__is_kernel((unsigned long)start)) { + start_pa = __pa_symbol(start); + end_pa = __pa_symbol(end - 1) + 1; + } else { + start_pa = __pa(start); + end_pa = __pa(end - 1) + 1; + } + + if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK)) { + if (start_pa < end_pa) + memblock_remove_range(&memblock.reserved, + start_pa, end_pa - start_pa); + } + + pages = __free_reserved_area(start_pa, end_pa, poison); + if (pages && s) + pr_info("Freeing %s memory: %ldK\n", s, K(pages)); + + return pages; +} + /** * memblock_free - free boot memory allocation * @ptr: starting address of the boot memory allocation * @size: size of the boot memory block in bytes * * Free boot memory block previously allocated by memblock_alloc_xx() API. - * The freeing memory will not be released to the buddy allocator. + * If called after the buddy allocator is available, the memory is released to + * the buddy allocator. */ void __init_memblock memblock_free(void *ptr, size_t size) { @@ -913,17 +983,24 @@ void __init_memblock memblock_free(void *ptr, size_t size) * @size: size of the boot memory block in bytes * * Free boot memory block previously allocated by memblock_phys_alloc_xx() API. - * The freeing memory will not be released to the buddy allocator. + * If called after the buddy allocator is available, the memory is released to + * the buddy allocator. */ int __init_memblock memblock_phys_free(phys_addr_t base, phys_addr_t size) { phys_addr_t end = base + size - 1; + int ret; memblock_dbg("%s: [%pa-%pa] %pS\n", __func__, &base, &end, (void *)_RET_IP_); kmemleak_free_part_phys(base, size); - return memblock_remove_range(&memblock.reserved, base, size); + ret = memblock_remove_range(&memblock.reserved, base, size); + + if (slab_is_available()) + __free_reserved_area(base, base + size, -1); + + return ret; } int __init_memblock __memblock_reserve(phys_addr_t base, phys_addr_t size, @@ -973,7 +1050,7 @@ __init void memmap_init_kho_scratch_pages(void) /* * Initialize struct pages for free scratch memory. * The struct pages for reserved scratch memory will be set up in - * reserve_bootmem_region() + * memmap_init_reserved_pages() */ __for_each_mem_range(i, &memblock.memory, NULL, NUMA_NO_NODE, MEMBLOCK_KHO_SCRATCH, &start, &end, &nid) { @@ -1766,32 +1843,6 @@ void *__init __memblock_alloc_or_panic(phys_addr_t size, phys_addr_t align, return addr; } -/** - * memblock_free_late - free pages directly to buddy allocator - * @base: phys starting address of the boot memory block - * @size: size of the boot memory block in bytes - * - * This is only useful when the memblock allocator has already been torn - * down, but we are still initializing the system. Pages are released directly - * to the buddy allocator. - */ -void __init memblock_free_late(phys_addr_t base, phys_addr_t size) -{ - phys_addr_t cursor, end; - - end = base + size - 1; - memblock_dbg("%s: [%pa-%pa] %pS\n", - __func__, &base, &end, (void *)_RET_IP_); - kmemleak_free_part_phys(base, size); - cursor = PFN_UP(base); - end = PFN_DOWN(base + size); - - for (; cursor < end; cursor++) { - memblock_free_pages(cursor, 0); - totalram_pages_inc(); - } -} - /* * Remaining API functions */ @@ -2255,6 +2306,31 @@ static unsigned long __init __free_memory_core(phys_addr_t start, return end_pfn - start_pfn; } +/* + * Initialised pages do not have PageReserved set. This function is called + * for each reserved range and marks the pages PageReserved. + * When deferred initialization of struct pages is enabled it also ensures + * that struct pages are properly initialised. + */ +static void __init memmap_init_reserved_range(phys_addr_t start, + phys_addr_t end, int nid) +{ + unsigned long pfn; + + for_each_valid_pfn(pfn, PFN_DOWN(start), PFN_UP(end)) { + struct page *page = pfn_to_page(pfn); + + init_deferred_page(pfn, nid); + + /* + * no need for atomic set_bit because the struct + * page is not visible yet so nobody should + * access it yet. + */ + __SetPageReserved(page); + } +} + static void __init memmap_init_reserved_pages(void) { struct memblock_region *region; @@ -2274,7 +2350,7 @@ repeat: end = start + region->size; if (memblock_is_nomap(region)) - reserve_bootmem_region(start, end, nid); + memmap_init_reserved_range(start, end, nid); memblock_set_node(start, region->size, &memblock.reserved, nid); } @@ -2299,7 +2375,7 @@ repeat: if (!numa_valid_node(nid)) nid = early_pfn_to_nid(PFN_DOWN(start)); - reserve_bootmem_region(start, end, nid); + memmap_init_reserved_range(start, end, nid); } } } @@ -2449,7 +2525,7 @@ int reserve_mem_release_by_name(const char *name) return 0; start = phys_to_virt(map->start); - end = start + map->size - 1; + end = start + map->size; snprintf(buf, sizeof(buf), "reserve_mem:%s", name); free_reserved_area(start, end, 0, buf); map->size = 0; @@ -2657,23 +2733,25 @@ static int __init reserve_mem(char *p) int len; if (!p) - return -EINVAL; + goto err_param; /* Check if there's room for more reserved memory */ - if (reserved_mem_count >= RESERVE_MEM_MAX_ENTRIES) + if (reserved_mem_count >= RESERVE_MEM_MAX_ENTRIES) { + pr_err("reserve_mem: no more room for reserved memory\n"); return -EBUSY; + } oldp = p; size = memparse(p, &p); if (!size || p == oldp) - return -EINVAL; + goto err_param; if (*p != ':') - return -EINVAL; + goto err_param; align = memparse(p+1, &p); if (*p != ':') - return -EINVAL; + goto err_param; /* * memblock_phys_alloc() doesn't like a zero size align, @@ -2687,7 +2765,7 @@ static int __init reserve_mem(char *p) /* name needs to have length but not too big */ if (!len || len >= RESERVE_MEM_NAME_SIZE) - return -EINVAL; + goto err_param; /* Make sure that name has text */ for (p = name; *p; p++) { @@ -2695,11 +2773,13 @@ static int __init reserve_mem(char *p) break; } if (!*p) - return -EINVAL; + goto err_param; /* Make sure the name is not already used */ - if (reserve_mem_find_by_name(name, &start, &tmp)) + if (reserve_mem_find_by_name(name, &start, &tmp)) { + pr_err("reserve_mem: name \"%s\" was already used\n", name); return -EBUSY; + } /* Pick previous allocations up from KHO if available */ if (reserve_mem_kho_revive(name, size, align)) @@ -2707,16 +2787,22 @@ static int __init reserve_mem(char *p) /* TODO: Allocation must be outside of scratch region */ start = memblock_phys_alloc(size, align); - if (!start) + if (!start) { + pr_err("reserve_mem: memblock allocation failed\n"); return -ENOMEM; + } reserved_mem_add(start, size, name); return 1; +err_param: + pr_err("reserve_mem: empty or malformed parameter\n"); + return -EINVAL; } __setup("reserve_mem=", reserve_mem); -#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_ARCH_KEEP_MEMBLOCK) +#ifdef CONFIG_DEBUG_FS +#ifdef CONFIG_ARCH_KEEP_MEMBLOCK static const char * const flagname[] = { [ilog2(MEMBLOCK_HOTPLUG)] = "HOTPLUG", [ilog2(MEMBLOCK_MIRROR)] = "MIRROR", @@ -2763,10 +2849,8 @@ static int memblock_debug_show(struct seq_file *m, void *private) } DEFINE_SHOW_ATTRIBUTE(memblock_debug); -static int __init memblock_init_debugfs(void) +static inline void memblock_debugfs_expose_arrays(struct dentry *root) { - struct dentry *root = debugfs_create_dir("memblock", NULL); - debugfs_create_file("memory", 0444, root, &memblock.memory, &memblock_debug_fops); debugfs_create_file("reserved", 0444, root, @@ -2775,7 +2859,48 @@ static int __init memblock_init_debugfs(void) debugfs_create_file("physmem", 0444, root, &physmem, &memblock_debug_fops); #endif +} +#else + +static inline void memblock_debugfs_expose_arrays(struct dentry *root) { } + +#endif /* CONFIG_ARCH_KEEP_MEMBLOCK */ + +static int memblock_reserve_mem_show(struct seq_file *m, void *private) +{ + struct reserve_mem_table *map; + char txtsz[16]; + + guard(mutex)(&reserve_mem_lock); + for (int i = 0; i < reserved_mem_count; i++) { + map = &reserved_mem_table[i]; + if (!map->size) + continue; + + memset(txtsz, 0, sizeof(txtsz)); + string_get_size(map->size, 1, STRING_UNITS_2, txtsz, sizeof(txtsz)); + seq_printf(m, "%s\t\t(%s)\n", map->name, txtsz); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(memblock_reserve_mem); + +static int __init memblock_init_debugfs(void) +{ + struct dentry *root; + + if (!IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK) && !reserved_mem_count) + return 0; + + root = debugfs_create_dir("memblock", NULL); + + if (reserved_mem_count) + debugfs_create_file("reserve_mem_param", 0444, root, NULL, + &memblock_reserve_mem_fops); + + memblock_debugfs_expose_arrays(root); return 0; } __initcall(memblock_init_debugfs); diff --git a/mm/mm_init.c b/mm/mm_init.c index 79f93f2a90cf..f9f8e1af921c 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -783,31 +783,6 @@ void __meminit init_deferred_page(unsigned long pfn, int nid) __init_deferred_page(pfn, nid); } -/* - * Initialised pages do not have PageReserved set. This function is - * called for each range allocated by the bootmem allocator and - * marks the pages PageReserved. The remaining valid pages are later - * sent to the buddy page allocator. - */ -void __meminit reserve_bootmem_region(phys_addr_t start, - phys_addr_t end, int nid) -{ - unsigned long pfn; - - for_each_valid_pfn(pfn, PFN_DOWN(start), PFN_UP(end)) { - struct page *page = pfn_to_page(pfn); - - __init_deferred_page(pfn, nid); - - /* - * no need for atomic set_bit because the struct - * page is not visible yet so nobody should - * access it yet. - */ - __SetPageReserved(page); - } -} - /* If zone is ZONE_MOVABLE but memory is mirrored, it is an overlapped init */ static bool __meminit overlap_memmap_init(unsigned long zone, unsigned long *pfn) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 62b4d041e893..aa7bf3610b5a 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -297,11 +297,6 @@ int page_group_by_mobility_disabled __read_mostly; */ DEFINE_STATIC_KEY_TRUE(deferred_pages); -static inline bool deferred_pages_enabled(void) -{ - return static_branch_unlikely(&deferred_pages); -} - /* * deferred_grow_zone() is __init, but it is called from * get_page_from_freelist() during early boot until deferred_pages permanently @@ -314,11 +309,6 @@ _deferred_grow_zone(struct zone *zone, unsigned int order) return deferred_grow_zone(zone, order); } #else -static inline bool deferred_pages_enabled(void) -{ - return false; -} - static inline bool _deferred_grow_zone(struct zone *zone, unsigned int order) { return false; @@ -6211,42 +6201,6 @@ void adjust_managed_page_count(struct page *page, long count) } EXPORT_SYMBOL(adjust_managed_page_count); -unsigned long free_reserved_area(void *start, void *end, int poison, const char *s) -{ - void *pos; - unsigned long pages = 0; - - start = (void *)PAGE_ALIGN((unsigned long)start); - end = (void *)((unsigned long)end & PAGE_MASK); - for (pos = start; pos < end; pos += PAGE_SIZE, pages++) { - struct page *page = virt_to_page(pos); - void *direct_map_addr; - - /* - * 'direct_map_addr' might be different from 'pos' - * because some architectures' virt_to_page() - * work with aliases. Getting the direct map - * address ensures that we get a _writeable_ - * alias for the memset(). - */ - direct_map_addr = page_address(page); - /* - * Perform a kasan-unchecked memset() since this memory - * has not been initialized. - */ - direct_map_addr = kasan_reset_tag(direct_map_addr); - if ((unsigned int)poison <= 0xFF) - memset(direct_map_addr, poison, PAGE_SIZE); - - free_reserved_page(page); - } - - if (pages && s) - pr_info("Freeing %s memory: %ldK\n", s, K(pages)); - - return pages; -} - void free_reserved_page(struct page *page) { clear_page_tag_ref(page); diff --git a/net/core/filter.c b/net/core/filter.c index fcfcb72663ca..5fa9189eb772 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -503,7 +503,7 @@ static bool convert_bpf_ld_abs(struct sock_filter *fp, struct bpf_insn **insnp) ((unaligned_ok && offset >= 0) || (!unaligned_ok && offset >= 0 && offset + ip_align >= 0 && - offset + ip_align % size == 0))) { + (offset + ip_align) % size == 0))) { bool ldx_off_ok = offset <= S16_MAX; *insn++ = BPF_MOV64_REG(BPF_REG_TMP, BPF_REG_H); diff --git a/net/core/sock_map.c b/net/core/sock_map.c index b0e96337a269..02a68be3002a 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -530,7 +530,7 @@ static bool sock_map_redirect_allowed(const struct sock *sk) if (sk_is_tcp(sk)) return sk->sk_state != TCP_LISTEN; else - return sk->sk_state == TCP_ESTABLISHED; + return READ_ONCE(sk->sk_state) == TCP_ESTABLISHED; } static bool sock_map_sk_is_suitable(const struct sock *sk) @@ -543,7 +543,7 @@ static bool sock_map_sk_state_allowed(const struct sock *sk) if (sk_is_tcp(sk)) return (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_LISTEN); if (sk_is_stream_unix(sk)) - return (1 << sk->sk_state) & TCPF_ESTABLISHED; + return (1 << READ_ONCE(sk->sk_state)) & TCPF_ESTABLISHED; if (sk_is_vsock(sk) && (sk->sk_type == SOCK_STREAM || sk->sk_type == SOCK_SEQPACKET)) return (1 << sk->sk_state) & TCPF_ESTABLISHED; diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 4c4a8d23ddd2..f668ff107722 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -3735,15 +3735,15 @@ static int bpf_iter_unix_seq_show(struct seq_file *seq, void *v) struct bpf_prog *prog; struct sock *sk = v; uid_t uid; - bool slow; int ret; if (v == SEQ_START_TOKEN) return 0; - slow = lock_sock_fast(sk); + lock_sock(sk); + unix_state_lock(sk); - if (unlikely(sk_unhashed(sk))) { + if (unlikely(sock_flag(sk, SOCK_DEAD))) { ret = SEQ_SKIP; goto unlock; } @@ -3753,7 +3753,8 @@ static int bpf_iter_unix_seq_show(struct seq_file *seq, void *v) prog = bpf_iter_get_info(&meta, false); ret = unix_prog_seq_show(prog, &meta, v, uid); unlock: - unlock_sock_fast(sk, slow); + unix_state_unlock(sk); + release_sock(sk); return ret; } diff --git a/net/unix/unix_bpf.c b/net/unix/unix_bpf.c index d14cd5454a8d..f86ff19e9764 100644 --- a/net/unix/unix_bpf.c +++ b/net/unix/unix_bpf.c @@ -185,6 +185,9 @@ int unix_stream_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool r */ if (!psock->sk_pair) { sk_pair = unix_peer(sk); + if (unlikely(!sk_pair)) + return -EINVAL; + sock_hold(sk_pair); psock->sk_pair = sk_pair; } diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile index 99bdc8a6d26e..704c687ed3ad 100644 --- a/tools/build/feature/Makefile +++ b/tools/build/feature/Makefile @@ -104,12 +104,18 @@ else endif endif +ifeq ($(findstring -static,${LDFLAGS}),-static) + PKG_CONFIG += --static +endif + all: $(FILES) __BUILD = $(CC) $(CFLAGS) -MD -Wall -Werror -o $@ $(patsubst %.bin,%.c,$(@F)) $(LDFLAGS) BUILD = $(__BUILD) > $(@:.bin=.make.output) 2>&1 BUILD_BFD = $(BUILD) -DPACKAGE='"perf"' -lbfd -ldl - BUILD_ALL = $(BUILD) -fstack-protector-all -O2 -D_FORTIFY_SOURCE=2 -ldw -lelf -lnuma -lelf -lslang $(FLAGS_PERL_EMBED) $(FLAGS_PYTHON_EMBED) -ldl -lz -llzma -lzstd -lssl + BUILD_ALL = $(BUILD) -fstack-protector-all -O2 -D_FORTIFY_SOURCE=2 -ldw -lelf -lnuma -lelf -lslang \ + $(FLAGS_PERL_EMBED) $(FLAGS_PYTHON_EMBED) -ldl -lz -llzma -lzstd \ + $(shell $(PKG_CONFIG) --libs --cflags openssl 2>/dev/null) __BUILDXX = $(CXX) $(CXXFLAGS) -MD -Wall -Werror -o $@ $(patsubst %.bin,%.cpp,$(@F)) $(LDFLAGS) BUILDXX = $(__BUILDXX) > $(@:.bin=.make.output) 2>&1 @@ -388,7 +394,7 @@ $(OUTPUT)test-libpfm4.bin: $(BUILD) -lpfm $(OUTPUT)test-libopenssl.bin: - $(BUILD) -lssl + $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags openssl 2>/dev/null) $(OUTPUT)test-bpftool-skeletons.bin: $(SYSTEM_BPFTOOL) version | grep '^features:.*skeletons' \ diff --git a/tools/include/linux/mm.h b/tools/include/linux/mm.h index 028f3faf46e7..84b5954f66c3 100644 --- a/tools/include/linux/mm.h +++ b/tools/include/linux/mm.h @@ -17,6 +17,7 @@ #define __va(x) ((void *)((unsigned long)(x))) #define __pa(x) ((unsigned long)(x)) +#define __pa_symbol(x) ((unsigned long)(x)) #define pfn_to_page(pfn) ((void *)((pfn) * PAGE_SIZE)) @@ -32,8 +33,6 @@ static inline phys_addr_t virt_to_phys(volatile void *address) return (phys_addr_t)address; } -void reserve_bootmem_region(phys_addr_t start, phys_addr_t end, int nid); - static inline void totalram_pages_inc(void) { } diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 8b0c3246097f..3a80a018fc7d 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -5852,11 +5852,12 @@ static int load_module_btfs(struct bpf_object *obj) info.name = ptr_to_u64(name); info.name_len = sizeof(name); + btf = NULL; err = bpf_btf_get_info_by_fd(fd, &info, &len); if (err) { err = -errno; pr_warn("failed to get BTF object #%d info: %s\n", id, errstr(err)); - goto err_out; + break; } /* ignore non-module BTFs */ @@ -5870,15 +5871,15 @@ static int load_module_btfs(struct bpf_object *obj) if (err) { pr_warn("failed to load module [%s]'s BTF object #%d: %s\n", name, id, errstr(err)); - goto err_out; + break; } err = libbpf_ensure_mem((void **)&obj->btf_modules, &obj->btf_module_cap, sizeof(*obj->btf_modules), obj->btf_module_cnt + 1); if (err) - goto err_out; + break; - mod_btf = &obj->btf_modules[obj->btf_module_cnt++]; + mod_btf = &obj->btf_modules[obj->btf_module_cnt]; mod_btf->btf = btf; mod_btf->id = id; @@ -5886,16 +5887,16 @@ static int load_module_btfs(struct bpf_object *obj) mod_btf->name = strdup(name); if (!mod_btf->name) { err = -ENOMEM; - goto err_out; + break; } - continue; - -err_out: - close(fd); - return err; + obj->btf_module_cnt++; } - return 0; + if (err) { + btf__free(btf); + close(fd); + } + return err; } static struct bpf_core_cand_list * diff --git a/tools/lib/perf/cpumap.c b/tools/lib/perf/cpumap.c index 4160e7d2e120..e51b0490ad57 100644 --- a/tools/lib/perf/cpumap.c +++ b/tools/lib/perf/cpumap.c @@ -15,12 +15,12 @@ #define MAX_NR_CPUS 4096 -void perf_cpu_map__set_nr(struct perf_cpu_map *map, int nr_cpus) +void perf_cpu_map__set_nr(struct perf_cpu_map *map, unsigned int nr_cpus) { RC_CHK_ACCESS(map)->nr = nr_cpus; } -struct perf_cpu_map *perf_cpu_map__alloc(int nr_cpus) +struct perf_cpu_map *perf_cpu_map__alloc(unsigned int nr_cpus) { RC_STRUCT(perf_cpu_map) *cpus; struct perf_cpu_map *result; @@ -78,7 +78,7 @@ void perf_cpu_map__put(struct perf_cpu_map *map) static struct perf_cpu_map *cpu_map__new_sysconf(void) { struct perf_cpu_map *cpus; - int nr_cpus, nr_cpus_conf; + long nr_cpus, nr_cpus_conf; nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (nr_cpus < 0) @@ -86,15 +86,13 @@ static struct perf_cpu_map *cpu_map__new_sysconf(void) nr_cpus_conf = sysconf(_SC_NPROCESSORS_CONF); if (nr_cpus != nr_cpus_conf) { - pr_warning("Number of online CPUs (%d) differs from the number configured (%d) the CPU map will only cover the first %d CPUs.", + pr_warning("Number of online CPUs (%ld) differs from the number configured (%ld) the CPU map will only cover the first %ld CPUs.", nr_cpus, nr_cpus_conf, nr_cpus); } cpus = perf_cpu_map__alloc(nr_cpus); if (cpus != NULL) { - int i; - - for (i = 0; i < nr_cpus; ++i) + for (long i = 0; i < nr_cpus; ++i) RC_CHK_ACCESS(cpus)->map[i].cpu = i; } @@ -132,23 +130,23 @@ static int cmp_cpu(const void *a, const void *b) return cpu_a->cpu - cpu_b->cpu; } -static struct perf_cpu __perf_cpu_map__cpu(const struct perf_cpu_map *cpus, int idx) +static struct perf_cpu __perf_cpu_map__cpu(const struct perf_cpu_map *cpus, unsigned int idx) { return RC_CHK_ACCESS(cpus)->map[idx]; } -static struct perf_cpu_map *cpu_map__trim_new(int nr_cpus, const struct perf_cpu *tmp_cpus) +static struct perf_cpu_map *cpu_map__trim_new(unsigned int nr_cpus, const struct perf_cpu *tmp_cpus) { size_t payload_size = nr_cpus * sizeof(struct perf_cpu); struct perf_cpu_map *cpus = perf_cpu_map__alloc(nr_cpus); - int i, j; if (cpus != NULL) { + unsigned int j = 0; + memcpy(RC_CHK_ACCESS(cpus)->map, tmp_cpus, payload_size); qsort(RC_CHK_ACCESS(cpus)->map, nr_cpus, sizeof(struct perf_cpu), cmp_cpu); /* Remove dups */ - j = 0; - for (i = 0; i < nr_cpus; i++) { + for (unsigned int i = 0; i < nr_cpus; i++) { if (i == 0 || __perf_cpu_map__cpu(cpus, i).cpu != __perf_cpu_map__cpu(cpus, i - 1).cpu) { @@ -167,9 +165,8 @@ struct perf_cpu_map *perf_cpu_map__new(const char *cpu_list) struct perf_cpu_map *cpus = NULL; unsigned long start_cpu, end_cpu = 0; char *p = NULL; - int i, nr_cpus = 0; + unsigned int nr_cpus = 0, max_entries = 0; struct perf_cpu *tmp_cpus = NULL, *tmp; - int max_entries = 0; if (!cpu_list) return perf_cpu_map__new_online_cpus(); @@ -208,9 +205,10 @@ struct perf_cpu_map *perf_cpu_map__new(const char *cpu_list) for (; start_cpu <= end_cpu; start_cpu++) { /* check for duplicates */ - for (i = 0; i < nr_cpus; i++) + for (unsigned int i = 0; i < nr_cpus; i++) { if (tmp_cpus[i].cpu == (int16_t)start_cpu) goto invalid; + } if (nr_cpus == max_entries) { max_entries += max(end_cpu - start_cpu + 1, 16UL); @@ -252,12 +250,12 @@ struct perf_cpu_map *perf_cpu_map__new_int(int cpu) return cpus; } -static int __perf_cpu_map__nr(const struct perf_cpu_map *cpus) +static unsigned int __perf_cpu_map__nr(const struct perf_cpu_map *cpus) { return RC_CHK_ACCESS(cpus)->nr; } -struct perf_cpu perf_cpu_map__cpu(const struct perf_cpu_map *cpus, int idx) +struct perf_cpu perf_cpu_map__cpu(const struct perf_cpu_map *cpus, unsigned int idx) { struct perf_cpu result = { .cpu = -1 @@ -269,7 +267,7 @@ struct perf_cpu perf_cpu_map__cpu(const struct perf_cpu_map *cpus, int idx) return result; } -int perf_cpu_map__nr(const struct perf_cpu_map *cpus) +unsigned int perf_cpu_map__nr(const struct perf_cpu_map *cpus) { return cpus ? __perf_cpu_map__nr(cpus) : 1; } @@ -294,7 +292,7 @@ bool perf_cpu_map__is_empty(const struct perf_cpu_map *map) int perf_cpu_map__idx(const struct perf_cpu_map *cpus, struct perf_cpu cpu) { - int low, high; + unsigned int low, high; if (!cpus) return -1; @@ -324,7 +322,7 @@ bool perf_cpu_map__has(const struct perf_cpu_map *cpus, struct perf_cpu cpu) bool perf_cpu_map__equal(const struct perf_cpu_map *lhs, const struct perf_cpu_map *rhs) { - int nr; + unsigned int nr; if (lhs == rhs) return true; @@ -336,7 +334,7 @@ bool perf_cpu_map__equal(const struct perf_cpu_map *lhs, const struct perf_cpu_m if (nr != __perf_cpu_map__nr(rhs)) return false; - for (int idx = 0; idx < nr; idx++) { + for (unsigned int idx = 0; idx < nr; idx++) { if (__perf_cpu_map__cpu(lhs, idx).cpu != __perf_cpu_map__cpu(rhs, idx).cpu) return false; } @@ -353,7 +351,7 @@ struct perf_cpu perf_cpu_map__min(const struct perf_cpu_map *map) struct perf_cpu cpu, result = { .cpu = -1 }; - int idx; + unsigned int idx; perf_cpu_map__for_each_cpu_skip_any(cpu, idx, map) { result = cpu; @@ -384,7 +382,7 @@ bool perf_cpu_map__is_subset(const struct perf_cpu_map *a, const struct perf_cpu if (!a || __perf_cpu_map__nr(b) > __perf_cpu_map__nr(a)) return false; - for (int i = 0, j = 0; i < __perf_cpu_map__nr(a); i++) { + for (unsigned int i = 0, j = 0; i < __perf_cpu_map__nr(a); i++) { if (__perf_cpu_map__cpu(a, i).cpu > __perf_cpu_map__cpu(b, j).cpu) return false; if (__perf_cpu_map__cpu(a, i).cpu == __perf_cpu_map__cpu(b, j).cpu) { @@ -410,8 +408,7 @@ bool perf_cpu_map__is_subset(const struct perf_cpu_map *a, const struct perf_cpu int perf_cpu_map__merge(struct perf_cpu_map **orig, struct perf_cpu_map *other) { struct perf_cpu *tmp_cpus; - int tmp_len; - int i, j, k; + unsigned int tmp_len, i, j, k; struct perf_cpu_map *merged; if (perf_cpu_map__is_subset(*orig, other)) @@ -455,7 +452,7 @@ int perf_cpu_map__merge(struct perf_cpu_map **orig, struct perf_cpu_map *other) struct perf_cpu_map *perf_cpu_map__intersect(struct perf_cpu_map *orig, struct perf_cpu_map *other) { - int i, j, k; + unsigned int i, j, k; struct perf_cpu_map *merged; if (perf_cpu_map__is_subset(other, orig)) diff --git a/tools/lib/perf/evsel.c b/tools/lib/perf/evsel.c index 13a307fc75ae..f747c0bc692d 100644 --- a/tools/lib/perf/evsel.c +++ b/tools/lib/perf/evsel.c @@ -127,7 +127,8 @@ int perf_evsel__open(struct perf_evsel *evsel, struct perf_cpu_map *cpus, struct perf_thread_map *threads) { struct perf_cpu cpu; - int idx, thread, err = 0; + unsigned int idx; + int thread, err = 0; if (cpus == NULL) { static struct perf_cpu_map *empty_cpu_map; @@ -460,7 +461,7 @@ int perf_evsel__enable_cpu(struct perf_evsel *evsel, int cpu_map_idx) int perf_evsel__enable_thread(struct perf_evsel *evsel, int thread) { struct perf_cpu cpu __maybe_unused; - int idx; + unsigned int idx; int err; perf_cpu_map__for_each_cpu(cpu, idx, evsel->cpus) { @@ -499,12 +500,13 @@ int perf_evsel__disable(struct perf_evsel *evsel) int perf_evsel__apply_filter(struct perf_evsel *evsel, const char *filter) { - int err = 0, i; + int err = 0; - for (i = 0; i < perf_cpu_map__nr(evsel->cpus) && !err; i++) + for (unsigned int i = 0; i < perf_cpu_map__nr(evsel->cpus) && !err; i++) { err = perf_evsel__run_ioctl(evsel, PERF_EVENT_IOC_SET_FILTER, (void *)filter, i); + } return err; } diff --git a/tools/lib/perf/include/internal/cpumap.h b/tools/lib/perf/include/internal/cpumap.h index e2be2d17c32b..c19678188b17 100644 --- a/tools/lib/perf/include/internal/cpumap.h +++ b/tools/lib/perf/include/internal/cpumap.h @@ -16,16 +16,16 @@ DECLARE_RC_STRUCT(perf_cpu_map) { refcount_t refcnt; /** Length of the map array. */ - int nr; + unsigned int nr; /** The CPU values. */ struct perf_cpu map[]; }; -struct perf_cpu_map *perf_cpu_map__alloc(int nr_cpus); +struct perf_cpu_map *perf_cpu_map__alloc(unsigned int nr_cpus); int perf_cpu_map__idx(const struct perf_cpu_map *cpus, struct perf_cpu cpu); bool perf_cpu_map__is_subset(const struct perf_cpu_map *a, const struct perf_cpu_map *b); -void perf_cpu_map__set_nr(struct perf_cpu_map *map, int nr_cpus); +void perf_cpu_map__set_nr(struct perf_cpu_map *map, unsigned int nr_cpus); static inline refcount_t *perf_cpu_map__refcnt(struct perf_cpu_map *map) { diff --git a/tools/lib/perf/include/perf/cpumap.h b/tools/lib/perf/include/perf/cpumap.h index 58cc5c5fa47c..a1dd25db65b6 100644 --- a/tools/lib/perf/include/perf/cpumap.h +++ b/tools/lib/perf/include/perf/cpumap.h @@ -49,7 +49,7 @@ LIBPERF_API void perf_cpu_map__put(struct perf_cpu_map *map); * perf_cpu_map__cpu - get the CPU value at the given index. Returns -1 if index * is invalid. */ -LIBPERF_API struct perf_cpu perf_cpu_map__cpu(const struct perf_cpu_map *cpus, int idx); +LIBPERF_API struct perf_cpu perf_cpu_map__cpu(const struct perf_cpu_map *cpus, unsigned int idx); /** * perf_cpu_map__nr - for an empty map returns 1, as perf_cpu_map__cpu returns a * cpu of -1 for an invalid index, this makes an empty map @@ -57,7 +57,7 @@ LIBPERF_API struct perf_cpu perf_cpu_map__cpu(const struct perf_cpu_map *cpus, i * the result is the number CPUs in the map plus one if the * "any CPU"/dummy value is present. */ -LIBPERF_API int perf_cpu_map__nr(const struct perf_cpu_map *cpus); +LIBPERF_API unsigned int perf_cpu_map__nr(const struct perf_cpu_map *cpus); /** * perf_cpu_map__has_any_cpu_or_is_empty - is map either empty or has the "any CPU"/dummy value. */ diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt index 547f1a268018..a688738809c4 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -110,8 +110,11 @@ include::itrace.txt[] Interleave source code with assembly code. Enabled by default, disable with --no-source. ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. -M:: --disassembler-style=:: Set disassembler style for objdump. diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt index 1160224cb718..c5913cf59c98 100644 --- a/tools/perf/Documentation/perf-bench.txt +++ b/tools/perf/Documentation/perf-bench.txt @@ -274,6 +274,10 @@ Repeat mmap() invocation this number of times. --cycles:: Use perf's cpu-cycles event instead of gettimeofday syscall. +-t:: +--threads=:: +Create multiple threads to call mmap/munmap concurrently. + SUITES FOR 'numa' ~~~~~~~~~~~~~~~~~ *mem*:: diff --git a/tools/perf/Documentation/perf-config.txt b/tools/perf/Documentation/perf-config.txt index 642d1c490d9e..9b223f892829 100644 --- a/tools/perf/Documentation/perf-config.txt +++ b/tools/perf/Documentation/perf-config.txt @@ -210,6 +210,12 @@ core.*:: Sets a timeout (in milliseconds) for parsing /proc//maps files. Can be overridden by the --proc-map-timeout option on supported subcommands. The default timeout is 500ms. + addr2line-disable-warn:: + When set to 'true' disable all warnings from 'addr2line' output. + Default setting is 'false' to show these warnings. + addr2line-timeout:: + Sets a timeout (in milliseconds) for parsing 'addr2line' + output. The default timeout is 5s. tui.*, gtk.*:: Subcommands that can be configured here are 'top', 'report' and 'annotate'. diff --git a/tools/perf/Documentation/perf-diff.txt b/tools/perf/Documentation/perf-diff.txt index 58efab72d2e5..8e4a3f093135 100644 --- a/tools/perf/Documentation/perf-diff.txt +++ b/tools/perf/Documentation/perf-diff.txt @@ -81,8 +81,11 @@ OPTIONS --force:: Don't do ownership validation. ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. -b:: --baseline-only:: diff --git a/tools/perf/Documentation/perf-kwork.txt b/tools/perf/Documentation/perf-kwork.txt index 21e607669d78..5c33a1fb2ffe 100644 --- a/tools/perf/Documentation/perf-kwork.txt +++ b/tools/perf/Documentation/perf-kwork.txt @@ -169,8 +169,11 @@ OPTIONS for 'perf kwork timehist' --max-stack:: Maximum number of functions to display in backtrace, default 5. ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. --time:: Only analyze samples within given time window: ,. Times diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 5c43a6edc0e5..2e5790325430 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -50,6 +50,12 @@ OPTIONS --source=PATH:: Specify path to kernel source. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. + -v:: --verbose:: Be more verbose (show parsed arguments, etc). diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index acef3ff4178e..22f87eaa3279 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -88,7 +88,7 @@ OPTIONS Sort histogram entries by given key(s) - multiple keys can be specified in CSV format. Following sort keys are available: pid, comm, dso, symbol, parent, cpu, socket, srcline, weight, - local_weight, cgroup_id, addr. + local_weight, cgroup_id, addr, comm_nodigit. Each key has following meaning: @@ -136,13 +136,17 @@ OPTIONS - addr: (Full) virtual address of the sampled instruction - retire_lat: On X86, this reports pipeline stall of this instruction compared to the previous instruction in cycles. And currently supported only on X86 - - simd: Flags describing a SIMD operation. "e" for empty Arm SVE predicate. "p" for partial Arm SVE predicate + - simd: Flags describing a SIMD operation. The architecture type can be Arm's + ASE (Advanced SIMD extension), SVE, SME. It provides an extra tag for + predicate: "e" for empty predicate, "p" for partial predicate, "d" for + predicate disabled, and "f" for full predicate. - type: Data type of sample memory access. - typeoff: Offset in the data type of sample memory access. - symoff: Offset in the symbol. - weight1: Average value of event specific weight (1st field of weight_struct). - weight2: Average value of event specific weight (2nd field of weight_struct). - weight3: Average value of event specific weight (3rd field of weight_struct). + - comm_nodigit: same as comm, with numbers replaced by "" By default, overhead, comm, dso and symbol keys are used. (i.e. --sort overhead,comm,dso,symbol). @@ -368,8 +372,11 @@ OPTIONS --force:: Don't do ownership validation. ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. -C:: --cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt index 4d9981609c04..a4221398e5e0 100644 --- a/tools/perf/Documentation/perf-sched.txt +++ b/tools/perf/Documentation/perf-sched.txt @@ -437,8 +437,11 @@ OPTIONS for 'perf sched timehist' Show all scheduling events followed by a summary by thread with min, max, and average run times (in sec) and relative stddev. ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. -V:: --cpu-visual:: diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt index ddf92f9c7821..200ea25891d8 100644 --- a/tools/perf/Documentation/perf-script.txt +++ b/tools/perf/Documentation/perf-script.txt @@ -307,8 +307,11 @@ OPTIONS --kallsyms=:: kallsyms pathname ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. -G:: --hide-call-graph:: diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt index 7cccc3a847d1..b72a29c9223c 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -578,6 +578,10 @@ $ perf config stat.no-csv-summary=true Only enable events on applying cpu with this type for hybrid platform (e.g. core or atom)" +--pmu-filter:: +Only enable events on applying pmu with specified for multiple +pmus with same type (e.g. hisi_sicl2_cpa0 or hisi_sicl0_cpa0) + EXAMPLES -------- diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt index ef2281c56743..bacc5df3c400 100644 --- a/tools/perf/Documentation/perf-timechart.txt +++ b/tools/perf/Documentation/perf-timechart.txt @@ -53,8 +53,11 @@ TIMECHART OPTIONS -f:: --force:: Don't complain, do it. ---symfs=:: - Look for files with symbols relative to this directory. +--symfs=:: + Look for files with symbols relative to this directory. The optional + layout can be 'hierarchy' (default, matches full path) or 'flat' + (only matches base name). This is useful when debug files are stored + in a flat directory structure. -n:: --proc-num:: Print task info for at least given number of tasks. diff --git a/tools/perf/Documentation/tips.txt b/tools/perf/Documentation/tips.txt index 3fee9b2a88ea..ebf12a8c5db5 100644 --- a/tools/perf/Documentation/tips.txt +++ b/tools/perf/Documentation/tips.txt @@ -11,7 +11,7 @@ Search options using a keyword: perf report -h Use parent filter to see specific call path: perf report -p List events using substring match: perf list To see list of saved events and attributes: perf evlist -v -Use --symfs if your symbol files are in non-standard locations +Use --symfs [,layout] if your symbol files are in non-standard locations. To see callchains in a more compact form: perf report -g folded To see call chains by final symbol taking CPU time (bottom up) use perf report -G Show individual samples with: perf script diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 15fbba9f4ca8..333ddd0e4bd8 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -349,6 +349,7 @@ CORE_CFLAGS += -fno-omit-frame-pointer CORE_CFLAGS += -Wall CORE_CFLAGS += -Wextra CORE_CFLAGS += -std=gnu11 +CORE_CFLAGS += -funsigned-char CXXFLAGS += -std=gnu++17 -fno-exceptions -fno-rtti CXXFLAGS += -Wall diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index f7b936deeaa2..cee19c923c06 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -547,7 +547,7 @@ $(clone_flags_array): $(beauty_uapi_linux_dir)/sched.h $(clone_flags_tbl) $(Q)$(SHELL) '$(clone_flags_tbl)' $(beauty_uapi_linux_dir) > $@ drm_ioctl_array := $(beauty_ioctl_outdir)/drm_ioctl_array.c -drm_hdr_dir := $(srctree)/tools/include/uapi/drm +drm_hdr_dir := $(srctree)/tools/perf/trace/beauty/include/uapi/drm drm_ioctl_tbl := $(srctree)/tools/perf/trace/beauty/drm_ioctl.sh $(drm_ioctl_array): $(drm_hdr_dir)/drm.h $(drm_hdr_dir)/i915_drm.h $(drm_ioctl_tbl) @@ -556,8 +556,8 @@ $(drm_ioctl_array): $(drm_hdr_dir)/drm.h $(drm_hdr_dir)/i915_drm.h $(drm_ioctl_t fadvise_advice_array := $(beauty_outdir)/fadvise_advice_array.c fadvise_advice_tbl := $(srctree)/tools/perf/trace/beauty/fadvise.sh -$(fadvise_advice_array): $(linux_uapi_dir)/in.h $(fadvise_advice_tbl) - $(Q)$(SHELL) '$(fadvise_advice_tbl)' $(linux_uapi_dir) > $@ +$(fadvise_advice_array): $(beauty_uapi_linux_dir)/fadvise.h $(fadvise_advice_tbl) + $(Q)$(SHELL) '$(fadvise_advice_tbl)' $(beauty_uapi_linux_dir) > $@ fsmount_arrays := $(beauty_outdir)/fsmount_arrays.c fsmount_tbls := $(srctree)/tools/perf/trace/beauty/fsmount.sh diff --git a/tools/perf/arch/arm/util/auxtrace.c b/tools/perf/arch/arm/util/auxtrace.c index eb6404267f17..27bb14c8b880 100644 --- a/tools/perf/arch/arm/util/auxtrace.c +++ b/tools/perf/arch/arm/util/auxtrace.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include "../../../util/auxtrace.h" @@ -27,7 +27,7 @@ static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err) /* arm_spe_xxxxxxxxx\0 */ char arm_spe_pmu_name[sizeof(ARM_SPE_PMU_NAME) + 10]; - arm_spe_pmus = zalloc(sizeof(struct perf_pmu *) * nr_cpus); + arm_spe_pmus = calloc(nr_cpus, sizeof(struct perf_pmu *)); if (!arm_spe_pmus) { pr_err("spes alloc failed\n"); *err = -ENOMEM; @@ -79,7 +79,7 @@ static struct perf_pmu **find_all_hisi_ptt_pmus(int *nr_ptts, int *err) if (!(*nr_ptts)) goto out; - hisi_ptt_pmus = zalloc(sizeof(struct perf_pmu *) * (*nr_ptts)); + hisi_ptt_pmus = calloc((*nr_ptts), sizeof(struct perf_pmu *)); if (!hisi_ptt_pmus) { pr_err("hisi_ptt alloc failed\n"); *err = -ENOMEM; diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c index 4418d21708d6..b7a839de8707 100644 --- a/tools/perf/arch/arm/util/cs-etm.c +++ b/tools/perf/arch/arm/util/cs-etm.c @@ -197,7 +197,8 @@ static struct perf_pmu *cs_etm_get_pmu(struct auxtrace_record *itr) static int cs_etm_validate_config(struct perf_pmu *cs_etm_pmu, struct evsel *evsel) { - int idx, err = 0; + unsigned int idx; + int err = 0; struct perf_cpu_map *event_cpus = evsel->evlist->core.user_requested_cpus; struct perf_cpu_map *intersect_cpus; struct perf_cpu cpu; @@ -546,7 +547,7 @@ static size_t cs_etm_info_priv_size(struct auxtrace_record *itr, struct evlist *evlist) { - int idx; + unsigned int idx; int etmv3 = 0, etmv4 = 0, ete = 0; struct perf_cpu_map *event_cpus = evlist->core.user_requested_cpus; struct perf_cpu_map *intersect_cpus; @@ -783,7 +784,7 @@ static int cs_etm_info_fill(struct auxtrace_record *itr, struct perf_record_auxtrace_info *info, size_t priv_size) { - int i; + unsigned int i; u32 offset; u64 nr_cpu, type; struct perf_cpu_map *cpu_map; diff --git a/tools/perf/arch/arm64/util/arm-spe.c b/tools/perf/arch/arm64/util/arm-spe.c index 17ced7bbbdda..f00d72d087fc 100644 --- a/tools/perf/arch/arm64/util/arm-spe.c +++ b/tools/perf/arch/arm64/util/arm-spe.c @@ -144,7 +144,8 @@ static int arm_spe_info_fill(struct auxtrace_record *itr, struct perf_record_auxtrace_info *auxtrace_info, size_t priv_size) { - int i, ret; + unsigned int i; + int ret; size_t offset; struct arm_spe_recording *sper = container_of(itr, struct arm_spe_recording, itr); diff --git a/tools/perf/arch/arm64/util/header.c b/tools/perf/arch/arm64/util/header.c index cbc0ba101636..95e71c4f6c78 100644 --- a/tools/perf/arch/arm64/util/header.c +++ b/tools/perf/arch/arm64/util/header.c @@ -43,7 +43,7 @@ static int _get_cpuid(char *buf, size_t sz, struct perf_cpu cpu) int get_cpuid(char *buf, size_t sz, struct perf_cpu cpu) { struct perf_cpu_map *cpus; - int idx; + unsigned int idx; if (cpu.cpu != -1) return _get_cpuid(buf, sz, cpu); diff --git a/tools/perf/arch/common.c b/tools/perf/arch/common.c index 4908d54dd33b..21836f70f231 100644 --- a/tools/perf/arch/common.c +++ b/tools/perf/arch/common.c @@ -9,14 +9,14 @@ #include "../util/debug.h" #include -const char *const arc_triplets[] = { +static const char *const arc_triplets[] = { "arc-linux-", "arc-snps-linux-uclibc-", "arc-snps-linux-gnu-", NULL }; -const char *const arm_triplets[] = { +static const char *const arm_triplets[] = { "arm-eabi-", "arm-linux-androideabi-", "arm-unknown-linux-", @@ -28,13 +28,13 @@ const char *const arm_triplets[] = { NULL }; -const char *const arm64_triplets[] = { +static const char *const arm64_triplets[] = { "aarch64-linux-android-", "aarch64-linux-gnu-", NULL }; -const char *const powerpc_triplets[] = { +static const char *const powerpc_triplets[] = { "powerpc-unknown-linux-gnu-", "powerpc-linux-gnu-", "powerpc64-unknown-linux-gnu-", @@ -43,40 +43,40 @@ const char *const powerpc_triplets[] = { NULL }; -const char *const riscv32_triplets[] = { +static const char *const riscv32_triplets[] = { "riscv32-unknown-linux-gnu-", "riscv32-linux-android-", "riscv32-linux-gnu-", NULL }; -const char *const riscv64_triplets[] = { +static const char *const riscv64_triplets[] = { "riscv64-unknown-linux-gnu-", "riscv64-linux-android-", "riscv64-linux-gnu-", NULL }; -const char *const s390_triplets[] = { +static const char *const s390_triplets[] = { "s390-ibm-linux-", "s390x-linux-gnu-", NULL }; -const char *const sh_triplets[] = { +static const char *const sh_triplets[] = { "sh-unknown-linux-gnu-", "sh-linux-gnu-", NULL }; -const char *const sparc_triplets[] = { +static const char *const sparc_triplets[] = { "sparc-unknown-linux-gnu-", "sparc64-unknown-linux-gnu-", "sparc64-linux-gnu-", NULL }; -const char *const x86_triplets[] = { +static const char *const x86_triplets[] = { "x86_64-pc-linux-gnu-", "x86_64-unknown-linux-gnu-", "i686-pc-linux-gnu-", @@ -90,7 +90,7 @@ const char *const x86_triplets[] = { NULL }; -const char *const mips_triplets[] = { +static const char *const mips_triplets[] = { "mips-unknown-linux-gnu-", "mipsel-linux-android-", "mips-linux-gnu-", diff --git a/tools/perf/arch/loongarch/util/Build b/tools/perf/arch/loongarch/util/Build index 3ad73d0289f3..8d91e78d31c9 100644 --- a/tools/perf/arch/loongarch/util/Build +++ b/tools/perf/arch/loongarch/util/Build @@ -1,4 +1,3 @@ perf-util-y += header.o perf-util-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o -perf-util-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o diff --git a/tools/perf/arch/powerpc/util/auxtrace.c b/tools/perf/arch/powerpc/util/auxtrace.c index 292ea335e4ff..e39deff6c857 100644 --- a/tools/perf/arch/powerpc/util/auxtrace.c +++ b/tools/perf/arch/powerpc/util/auxtrace.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "../../util/evlist.h" #include "../../util/debug.h" diff --git a/tools/perf/arch/sh/include/dwarf-regs-table.h b/tools/perf/arch/sh/include/dwarf-regs-table.h index 900e69619970..b5974a090fb4 100644 --- a/tools/perf/arch/sh/include/dwarf-regs-table.h +++ b/tools/perf/arch/sh/include/dwarf-regs-table.h @@ -2,7 +2,7 @@ #ifdef DEFINE_DWARF_REGSTR_TABLE /* This is included in perf/util/dwarf-regs.c */ -const char * const sh_regstr_tbl[] = { +static const char * const sh_regstr_tbl[] = { "r0", "r1", "r2", diff --git a/tools/perf/arch/x86/tests/amd-ibs-period.c b/tools/perf/arch/x86/tests/amd-ibs-period.c index 223e059e04de..cee9e11c05e0 100644 --- a/tools/perf/arch/x86/tests/amd-ibs-period.c +++ b/tools/perf/arch/x86/tests/amd-ibs-period.c @@ -8,7 +8,6 @@ #include "arch-tests.h" #include "linux/perf_event.h" -#include "linux/zalloc.h" #include "tests/tests.h" #include "../perf-sys.h" #include "pmu.h" @@ -60,7 +59,7 @@ static int dummy_workload_1(unsigned long count) 0xcc, /* int 3 */ }; - p = zalloc(2 * page_size); + p = calloc(2, page_size); if (!p) { printf("malloc() failed. %m"); return 1; diff --git a/tools/perf/arch/x86/tests/dwarf-unwind.c b/tools/perf/arch/x86/tests/dwarf-unwind.c index e91a73d09cec..99d2b7ed016f 100644 --- a/tools/perf/arch/x86/tests/dwarf-unwind.c +++ b/tools/perf/arch/x86/tests/dwarf-unwind.c @@ -54,22 +54,13 @@ int test__arch_unwind_sample(struct perf_sample *sample, struct thread *thread) { struct regs_dump *regs = perf_sample__user_regs(sample); - u64 *buf; + u64 *buf = calloc(PERF_REGS_MAX, sizeof(u64)); - buf = malloc(sizeof(u64) * PERF_REGS_MAX); if (!buf) { pr_debug("failed to allocate sample uregs data\n"); return -1; } -#ifdef MEMORY_SANITIZER - /* - * Assignments to buf in the assembly function perf_regs_load aren't - * seen by memory sanitizer. Zero the memory to convince memory - * sanitizer the memory is initialized. - */ - memset(buf, 0, sizeof(u64) * PERF_REGS_MAX); -#endif perf_regs_load(buf); regs->abi = PERF_SAMPLE_REGS_ABI; regs->regs = buf; diff --git a/tools/perf/arch/x86/util/pmu.c b/tools/perf/arch/x86/util/pmu.c index a3f96221758d..7c9d238922a6 100644 --- a/tools/perf/arch/x86/util/pmu.c +++ b/tools/perf/arch/x86/util/pmu.c @@ -5,8 +5,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -71,11 +71,6 @@ static int snc_nodes_per_l3_cache(void) return snc_nodes; } -static bool starts_with(const char *str, const char *prefix) -{ - return !strncmp(prefix, str, strlen(prefix)); -} - static int num_chas(void) { static bool checked_chas; @@ -93,7 +88,7 @@ static int num_chas(void) while ((dent = io_dir__readdir(&dir)) != NULL) { /* Note, dent->d_type will be DT_LNK and so isn't a useful filter. */ - if (starts_with(dent->d_name, "uncore_cha_")) + if (strstarts(dent->d_name, "uncore_cha_")) num_chas++; } close(fd); @@ -225,7 +220,8 @@ static void gnr_uncore_cha_imc_adjust_cpumask_for_snc(struct perf_pmu *pmu, bool static struct perf_cpu_map *cha_adjusted[MAX_SNCS]; static struct perf_cpu_map *imc_adjusted[MAX_SNCS]; struct perf_cpu_map **adjusted = cha ? cha_adjusted : imc_adjusted; - int idx, pmu_snc, cpu_adjust; + unsigned int idx; + int pmu_snc, cpu_adjust; struct perf_cpu cpu; bool alloc; @@ -305,9 +301,9 @@ void perf_pmu__arch_init(struct perf_pmu *pmu) else pmu->mem_events = perf_mem_events_intel; } else if (x86__is_intel_graniterapids()) { - if (starts_with(pmu->name, "uncore_cha_")) + if (strstarts(pmu->name, "uncore_cha_")) gnr_uncore_cha_imc_adjust_cpumask_for_snc(pmu, /*cha=*/true); - else if (starts_with(pmu->name, "uncore_imc_")) + else if (strstarts(pmu->name, "uncore_imc_")) gnr_uncore_cha_imc_adjust_cpumask_for_snc(pmu, /*cha=*/false); } } diff --git a/tools/perf/bench/breakpoint.c b/tools/perf/bench/breakpoint.c index dfd18f5db97d..1b7cd4481bd2 100644 --- a/tools/perf/bench/breakpoint.c +++ b/tools/perf/bench/breakpoint.c @@ -16,7 +16,7 @@ #include "bench.h" #include "futex.h" -struct { +static struct { unsigned int nbreakpoints; unsigned int nparallel; unsigned int nthreads; @@ -173,7 +173,7 @@ int bench_breakpoint_thread(int argc, const char **argv) return 0; } -struct { +static struct { unsigned int npassive; unsigned int nactive; } enable_params = { diff --git a/tools/perf/bench/mem-functions.c b/tools/perf/bench/mem-functions.c index 2908a3a796c9..5ede52853953 100644 --- a/tools/perf/bench/mem-functions.c +++ b/tools/perf/bench/mem-functions.c @@ -7,13 +7,14 @@ * Written by Hitoshi Mitake */ -#include "debug.h" +#include "bench.h" #include "../perf-sys.h" #include -#include "../util/header.h" -#include "../util/cloexec.h" -#include "../util/string2.h" -#include "bench.h" +#include "util/cloexec.h" +#include "util/debug.h" +#include "util/header.h" +#include "util/stat.h" +#include "util/string2.h" #include "mem-memcpy-arch.h" #include "mem-memset-arch.h" @@ -26,6 +27,7 @@ #include #include #include +#include #define K 1024 @@ -41,6 +43,7 @@ static unsigned int nr_loops = 1; static bool use_cycles; static int cycles_fd; static unsigned int seed; +static unsigned int nr_threads = 1; static const struct option bench_common_options[] = { OPT_STRING('s', "size", &size_str, "1MB", @@ -121,6 +124,8 @@ static struct perf_event_attr cycle_attr = { .config = PERF_COUNT_HW_CPU_CYCLES }; +static struct stats stats; + static int init_cycles(void) { cycles_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, perf_event_open_cloexec_flag()); @@ -174,18 +179,18 @@ static void clock_accum(union bench_clock *a, union bench_clock *b) static double timeval2double(struct timeval *ts) { - return (double)ts->tv_sec + (double)ts->tv_usec / (double)USEC_PER_SEC; + return ((double)ts->tv_sec + (double)ts->tv_usec / (double)USEC_PER_SEC) / nr_threads; } #define print_bps(x) do { \ if (x < K) \ - printf(" %14lf bytes/sec\n", x); \ + printf(" %14lf bytes/sec", x); \ else if (x < K * K) \ - printf(" %14lfd KB/sec\n", x / K); \ + printf(" %14lfd KB/sec", x / K); \ else if (x < K * K * K) \ - printf(" %14lf MB/sec\n", x / K / K); \ + printf(" %14lf MB/sec", x / K / K); \ else \ - printf(" %14lf GB/sec\n", x / K / K / K); \ + printf(" %14lf GB/sec", x / K / K / K); \ } while (0) static void __bench_mem_function(struct bench_mem_info *info, struct bench_params *p, @@ -196,6 +201,7 @@ static void __bench_mem_function(struct bench_mem_info *info, struct bench_param union bench_clock rt = { 0 }; void *src = NULL, *dst = NULL; + init_stats(&stats); printf("# function '%s' (%s)\n", r->name, r->desc); if (r->fn.init && r->fn.init(info, p, &src, &dst)) @@ -210,11 +216,16 @@ static void __bench_mem_function(struct bench_mem_info *info, struct bench_param switch (bench_format) { case BENCH_FORMAT_DEFAULT: if (use_cycles) { - printf(" %14lf cycles/byte\n", (double)rt.cycles/(double)p->size_total); + printf(" %14lf cycles/byte", (double)rt.cycles/(double)p->size_total); } else { result_bps = (double)p->size_total/timeval2double(&rt.tv); print_bps(result_bps); } + if (nr_threads > 1) { + printf("/thread\t( +- %6.2f%% )", + rel_stddev_stats(stddev_stats(&stats), avg_stats(&stats))); + } + printf("\n"); break; case BENCH_FORMAT_SIMPLE: @@ -388,7 +399,7 @@ static void mem_free(struct bench_mem_info *info __maybe_unused, *dst = *src = NULL; } -struct function memcpy_functions[] = { +static struct function memcpy_functions[] = { { .name = "default", .desc = "Default memcpy() provided by glibc", .fn.init = mem_alloc, @@ -494,16 +505,27 @@ static void mmap_page_touch(void *dst, size_t size, unsigned int page_shift, boo } } -static int do_mmap(const struct function *r, struct bench_params *p, - void *src __maybe_unused, void *dst __maybe_unused, - union bench_clock *accum) +struct mmap_data { + pthread_t id; + const struct function *func; + struct bench_params *params; + union bench_clock result; + unsigned int seed; + int error; +}; + +static void *do_mmap_thread(void *arg) { + struct mmap_data *data = arg; + const struct function *r = data->func; + struct bench_params *p = data->params; union bench_clock start, end, diff; mmap_op_t fn = r->fn.mmap_op; bool populate = strcmp(r->name, "populate") == 0; + void *dst; - if (p->seed) - srand(p->seed); + if (data->seed) + srand(data->seed); for (unsigned int i = 0; i < p->nr_loops; i++) { clock_get(&start); @@ -514,16 +536,59 @@ static int do_mmap(const struct function *r, struct bench_params *p, fn(dst, p->size, p->page_shift, p->seed); clock_get(&end); diff = clock_diff(&start, &end); - clock_accum(accum, &diff); + clock_accum(&data->result, &diff); bench_munmap(dst, p->size); } - return 0; + return data; out: - printf("# Memory allocation failed - maybe size (%s) %s?\n", size_str, - p->page_shift != PAGE_SHIFT_4KB ? "has insufficient hugepages" : "is too large"); - return -1; + data->error = -ENOMEM; + return NULL; +} + +static int do_mmap(const struct function *r, struct bench_params *p, + void *src __maybe_unused, void *dst __maybe_unused, + union bench_clock *accum) +{ + struct mmap_data *data; + int error = 0; + + data = calloc(nr_threads, sizeof(*data)); + if (!data) { + printf("# Failed to allocate thread resources\n"); + return -1; + } + + for (unsigned int i = 0; i < nr_threads; i++) { + data[i].func = r; + data[i].params = p; + if (p->seed) + data[i].seed = p->seed + i; + + if (pthread_create(&data[i].id, NULL, do_mmap_thread, &data[i]) < 0) + data[i].error = -errno; + } + + for (unsigned int i = 0; i < nr_threads; i++) { + union bench_clock *t = &data[i].result; + + pthread_join(data[i].id, NULL); + + clock_accum(accum, t); + if (use_cycles) + update_stats(&stats, t->cycles); + else + update_stats(&stats, t->tv.tv_sec * 1e6 + t->tv.tv_usec); + error |= data[i].error; + } + free(data); + + if (error) { + printf("# Memory allocation failed - maybe size (%s) %s?\n", size_str, + p->page_shift != PAGE_SHIFT_4KB ? "has insufficient hugepages" : "is too large"); + } + return error ? -1 : 0; } static const char * const bench_mem_mmap_usage[] = { @@ -548,6 +613,8 @@ int bench_mem_mmap(int argc, const char **argv) static const struct option bench_mmap_options[] = { OPT_UINTEGER('r', "randomize", &seed, "Seed to randomize page access offset."), + OPT_UINTEGER('t', "threads", &nr_threads, + "Number of threads to run concurrently (default: 1)."), OPT_PARENT(bench_common_options), OPT_END() }; diff --git a/tools/perf/bench/numa.c b/tools/perf/bench/numa.c index 19be2aaf4dc0..42d7afc03f9b 100644 --- a/tools/perf/bench/numa.c +++ b/tools/perf/bench/numa.c @@ -32,7 +32,6 @@ #include #include #include -#include #include "../util/header.h" #include "../util/mutex.h" @@ -166,7 +165,7 @@ static struct global_info *g = NULL; static int parse_cpus_opt(const struct option *opt, const char *arg, int unset); static int parse_nodes_opt(const struct option *opt, const char *arg, int unset); -struct params p0; +static struct params p0; static const struct option options[] = { OPT_INTEGER('p', "nr_proc" , &p0.nr_proc, "number of processes"), @@ -980,10 +979,8 @@ static int count_process_nodes(int process_nr) int nodes; int n, t; - node_present = (char *)malloc(g->p.nr_nodes * sizeof(char)); + node_present = calloc(g->p.nr_nodes, sizeof(char)); BUG_ON(!node_present); - for (nodes = 0; nodes < g->p.nr_nodes; nodes++) - node_present[nodes] = 0; for (t = 0; t < g->p.nr_threads; t++) { struct thread_data *td; @@ -1090,10 +1087,8 @@ static void calc_convergence(double runtime_ns_max, double *convergence) if (!g->p.show_convergence && !g->p.measure_convergence) return; - nodes = (int *)malloc(g->p.nr_nodes * sizeof(int)); + nodes = calloc(g->p.nr_nodes, sizeof(int)); BUG_ON(!nodes); - for (node = 0; node < g->p.nr_nodes; node++) - nodes[node] = 0; loops_done_min = -1; loops_done_max = 0; @@ -1423,7 +1418,7 @@ static void worker_process(int process_nr) bind_to_memnode(td->bind_node); bind_to_cpumask(td->bind_cpumask); - pthreads = zalloc(g->p.nr_threads * sizeof(pthread_t)); + pthreads = calloc(g->p.nr_threads, sizeof(pthread_t)); process_data = setup_private_data(g->p.bytes_process); if (g->p.show_details >= 3) { @@ -1629,7 +1624,7 @@ static int __bench_numa(const char *name) if (init()) return -1; - pids = zalloc(g->p.nr_proc * sizeof(*pids)); + pids = calloc(g->p.nr_proc, sizeof(*pids)); pid = -1; if (g->p.serialize_startup) { diff --git a/tools/perf/bench/sched-messaging.c b/tools/perf/bench/sched-messaging.c index 93dcd9dba3d0..4fb6657fc826 100644 --- a/tools/perf/bench/sched-messaging.c +++ b/tools/perf/bench/sched-messaging.c @@ -301,7 +301,7 @@ int bench_sched_messaging(int argc, const char **argv) argc = parse_options(argc, argv, options, bench_sched_message_usage, 0); - worker_tab = malloc(num_fds * 2 * num_groups * sizeof(union messaging_worker)); + worker_tab = calloc(num_fds * 2 * num_groups, sizeof(union messaging_worker)); if (!worker_tab) err(EXIT_FAILURE, "main:malloc()"); diff --git a/tools/perf/bench/uprobe.c b/tools/perf/bench/uprobe.c index c4dac868f1ee..89697ff788ef 100644 --- a/tools/perf/bench/uprobe.c +++ b/tools/perf/bench/uprobe.c @@ -58,7 +58,7 @@ static const char * const bench_uprobe_usage[] = { goto cleanup; \ } -struct bench_uprobe_bpf *skel; +static struct bench_uprobe_bpf *skel; static int bench_uprobe__setup_bpf_skel(enum bench_uprobe bench) { diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 9c27bb30b708..5e57b78548f4 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -13,7 +13,6 @@ #include #include "util/cache.h" #include -#include #include "util/symbol.h" #include "util/debug.h" @@ -313,15 +312,6 @@ out_put: return ret; } -static int process_feature_event(const struct perf_tool *tool __maybe_unused, - struct perf_session *session, - union perf_event *event) -{ - if (event->feat.feat_id < HEADER_LAST_FEATURE) - return perf_event__process_feature(session, event); - return 0; -} - static int hist_entry__stdio_annotate(struct hist_entry *he, struct evsel *evsel, struct perf_annotate *ann) @@ -744,8 +734,7 @@ int cmd_annotate(int argc, const char **argv) &annotate.group_set, "Show event group information together"), OPT_STRING('C', "cpu", &annotate.cpu_list, "cpu", "list of cpus to profile"), - OPT_CALLBACK(0, "symfs", NULL, "directory", - "Look for files with symbols relative to this directory", + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, symbol__config_symfs), OPT_BOOLEAN(0, "source", &annotate_opts.annotate_src, "Interleave source code with assembly code (default)"), @@ -876,7 +865,7 @@ int cmd_annotate(int argc, const char **argv) annotate.tool.id_index = perf_event__process_id_index; annotate.tool.auxtrace_info = perf_event__process_auxtrace_info; annotate.tool.auxtrace = perf_event__process_auxtrace; - annotate.tool.feature = process_feature_event; + annotate.tool.feature = perf_event__process_feature; annotate.tool.ordering_requires_timestamps = true; annotate.session = perf_session__new(&data, &annotate.tool); diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c index 02dea1b88228..02d47913cc6a 100644 --- a/tools/perf/builtin-bench.c +++ b/tools/perf/builtin-bench.c @@ -37,14 +37,14 @@ struct bench { }; #ifdef HAVE_LIBNUMA_SUPPORT -static struct bench numa_benchmarks[] = { +static const struct bench numa_benchmarks[] = { { "mem", "Benchmark for NUMA workloads", bench_numa }, { "all", "Run all NUMA benchmarks", NULL }, { NULL, NULL, NULL } }; #endif -static struct bench sched_benchmarks[] = { +static const struct bench sched_benchmarks[] = { { "messaging", "Benchmark for scheduling and IPC", bench_sched_messaging }, { "pipe", "Benchmark for pipe() between two processes", bench_sched_pipe }, { "seccomp-notify", "Benchmark for seccomp user notify", bench_sched_seccomp_notify}, @@ -52,7 +52,7 @@ static struct bench sched_benchmarks[] = { { NULL, NULL, NULL } }; -static struct bench syscall_benchmarks[] = { +static const struct bench syscall_benchmarks[] = { { "basic", "Benchmark for basic getppid(2) calls", bench_syscall_basic }, { "getpgid", "Benchmark for getpgid(2) calls", bench_syscall_getpgid }, { "fork", "Benchmark for fork(2) calls", bench_syscall_fork }, @@ -61,7 +61,7 @@ static struct bench syscall_benchmarks[] = { { NULL, NULL, NULL }, }; -static struct bench mem_benchmarks[] = { +static const struct bench mem_benchmarks[] = { { "memcpy", "Benchmark for memcpy() functions", bench_mem_memcpy }, { "memset", "Benchmark for memset() functions", bench_mem_memset }, { "find_bit", "Benchmark for find_bit() functions", bench_mem_find_bit }, @@ -70,7 +70,7 @@ static struct bench mem_benchmarks[] = { { NULL, NULL, NULL } }; -static struct bench futex_benchmarks[] = { +static const struct bench futex_benchmarks[] = { { "hash", "Benchmark for futex hash table", bench_futex_hash }, { "wake", "Benchmark for futex wake calls", bench_futex_wake }, { "wake-parallel", "Benchmark for parallel futex wake calls", bench_futex_wake_parallel }, @@ -82,7 +82,7 @@ static struct bench futex_benchmarks[] = { }; #ifdef HAVE_EVENTFD_SUPPORT -static struct bench epoll_benchmarks[] = { +static const struct bench epoll_benchmarks[] = { { "wait", "Benchmark epoll concurrent epoll_waits", bench_epoll_wait }, { "ctl", "Benchmark epoll concurrent epoll_ctls", bench_epoll_ctl }, { "all", "Run all futex benchmarks", NULL }, @@ -90,7 +90,7 @@ static struct bench epoll_benchmarks[] = { }; #endif // HAVE_EVENTFD_SUPPORT -static struct bench internals_benchmarks[] = { +static const struct bench internals_benchmarks[] = { { "synthesize", "Benchmark perf event synthesis", bench_synthesize }, { "kallsyms-parse", "Benchmark kallsyms parsing", bench_kallsyms_parse }, { "inject-build-id", "Benchmark build-id injection", bench_inject_build_id }, @@ -99,14 +99,14 @@ static struct bench internals_benchmarks[] = { { NULL, NULL, NULL } }; -static struct bench breakpoint_benchmarks[] = { +static const struct bench breakpoint_benchmarks[] = { { "thread", "Benchmark thread start/finish with breakpoints", bench_breakpoint_thread}, { "enable", "Benchmark breakpoint enable/disable", bench_breakpoint_enable}, { "all", "Run all breakpoint benchmarks", NULL}, { NULL, NULL, NULL }, }; -static struct bench uprobe_benchmarks[] = { +static const struct bench uprobe_benchmarks[] = { { "baseline", "Baseline libc usleep(1000) call", bench_uprobe_baseline, }, { "empty", "Attach empty BPF prog to uprobe on usleep, system wide", bench_uprobe_empty, }, { "trace_printk", "Attach trace_printk BPF prog to uprobe on usleep syswide", bench_uprobe_trace_printk, }, @@ -116,12 +116,12 @@ static struct bench uprobe_benchmarks[] = { }; struct collection { - const char *name; - const char *summary; - struct bench *benchmarks; + const char *name; + const char *summary; + const struct bench *benchmarks; }; -static struct collection collections[] = { +static const struct collection collections[] = { { "sched", "Scheduler and IPC benchmarks", sched_benchmarks }, { "syscall", "System call benchmarks", syscall_benchmarks }, { "mem", "Memory access benchmarks", mem_benchmarks }, @@ -147,9 +147,9 @@ static struct collection collections[] = { #define for_each_bench(coll, bench) \ for (bench = coll->benchmarks; bench && bench->name; bench++) -static void dump_benchmarks(struct collection *coll) +static void dump_benchmarks(const struct collection *coll) { - struct bench *bench; + const struct bench *bench; printf("\n # List of available benchmarks for collection '%s':\n\n", coll->name); @@ -178,7 +178,7 @@ static const char * const bench_usage[] = { static void print_usage(void) { - struct collection *coll; + const struct collection *coll; int i; printf("Usage: \n"); @@ -234,9 +234,9 @@ static int run_bench(const char *coll_name, const char *bench_name, bench_fn_t f return ret; } -static void run_collection(struct collection *coll) +static void run_collection(const struct collection *coll) { - struct bench *bench; + const struct bench *bench; const char *argv[2]; argv[1] = NULL; @@ -260,7 +260,7 @@ static void run_collection(struct collection *coll) static void run_all_collections(void) { - struct collection *coll; + const struct collection *coll; for_each_collection(coll) run_collection(coll); @@ -268,7 +268,7 @@ static void run_all_collections(void) int cmd_bench(int argc, const char **argv) { - struct collection *coll; + const struct collection *coll; int ret = 0; /* Unbuffered output */ @@ -306,7 +306,7 @@ int cmd_bench(int argc, const char **argv) } for_each_collection(coll) { - struct bench *bench; + const struct bench *bench; if (strcmp(coll->name, argv[0])) continue; diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c index d390ae4e3ec8..72a7802775ee 100644 --- a/tools/perf/builtin-c2c.c +++ b/tools/perf/builtin-c2c.c @@ -155,7 +155,7 @@ static void *c2c_he_zalloc(size_t size) if (!c2c_he->nodeset) goto out_free; - c2c_he->node_stats = zalloc(c2c.nodes_cnt * sizeof(*c2c_he->node_stats)); + c2c_he->node_stats = calloc(c2c.nodes_cnt, sizeof(*c2c_he->node_stats)); if (!c2c_he->node_stats) goto out_free; @@ -2310,7 +2310,6 @@ static int setup_nodes(struct perf_session *session) { struct numa_node *n; unsigned long **nodes; - int node, idx; struct perf_cpu cpu; int *cpu2node; struct perf_env *env = perf_session__env(session); @@ -2325,24 +2324,25 @@ static int setup_nodes(struct perf_session *session) if (!n) return -EINVAL; - nodes = zalloc(sizeof(unsigned long *) * c2c.nodes_cnt); + nodes = calloc(c2c.nodes_cnt, sizeof(unsigned long *)); if (!nodes) return -ENOMEM; c2c.nodes = nodes; - cpu2node = zalloc(sizeof(int) * c2c.cpus_cnt); + cpu2node = calloc(c2c.cpus_cnt, sizeof(int)); if (!cpu2node) return -ENOMEM; - for (idx = 0; idx < c2c.cpus_cnt; idx++) + for (int idx = 0; idx < c2c.cpus_cnt; idx++) cpu2node[idx] = -1; c2c.cpu2node = cpu2node; - for (node = 0; node < c2c.nodes_cnt; node++) { + for (int node = 0; node < c2c.nodes_cnt; node++) { struct perf_cpu_map *map = n[node].map; unsigned long *set; + unsigned int idx; set = bitmap_zalloc(c2c.cpus_cnt); if (!set) @@ -2892,9 +2892,10 @@ static int ui_quirks(void) #define CALLCHAIN_DEFAULT_OPT "graph,0.5,caller,function,percent" -const char callchain_help[] = "Display call graph (stack chain/backtrace):\n\n" - CALLCHAIN_REPORT_HELP - "\n\t\t\t\tDefault: " CALLCHAIN_DEFAULT_OPT; +static const char callchain_help[] = + "Display call graph (stack chain/backtrace):\n\n" + CALLCHAIN_REPORT_HELP + "\n\t\t\t\tDefault: " CALLCHAIN_DEFAULT_OPT; static int parse_callchain_opt(const struct option *opt, const char *arg, int unset) diff --git a/tools/perf/builtin-config.c b/tools/perf/builtin-config.c index 45b5312fbe83..237600643bbd 100644 --- a/tools/perf/builtin-config.c +++ b/tools/perf/builtin-config.c @@ -23,7 +23,7 @@ static const char * const config_usage[] = { NULL }; -enum actions { +static enum actions { ACTION_LIST = 1 } actions; diff --git a/tools/perf/builtin-daemon.c b/tools/perf/builtin-daemon.c index 33473e071392..c4632577d129 100644 --- a/tools/perf/builtin-daemon.c +++ b/tools/perf/builtin-daemon.c @@ -1016,7 +1016,7 @@ static int setup_config_changes(struct daemon *daemon) { char *basen = strdup(daemon->config_real); char *dirn = strdup(daemon->config_real); - char *base, *dir; + const char *base, *dir; int fd, wd = -1; if (!dirn || !basen) @@ -1029,7 +1029,7 @@ static int setup_config_changes(struct daemon *daemon) } dir = dirname(dirn); - base = basename(basen); + base = perf_basename(basen); pr_debug("config file: %s, dir: %s\n", base, dir); wd = inotify_add_watch(fd, dir, IN_CLOSE_WRITE); diff --git a/tools/perf/builtin-data.c b/tools/perf/builtin-data.c index 85f59886b5cf..4c08ccb8c06b 100644 --- a/tools/perf/builtin-data.c +++ b/tools/perf/builtin-data.c @@ -28,15 +28,15 @@ static const char *data_usage[] = { NULL }; -const char *to_json; -const char *to_ctf; -struct perf_data_convert_opts opts = { +static const char *to_json; +static const char *to_ctf; +static struct perf_data_convert_opts opts = { .force = false, .all = false, .time_str = NULL, }; -const struct option data_options[] = { +static const struct option data_options[] = { OPT_INCR('v', "verbose", &verbose, "be more verbose"), OPT_STRING('i', "input", &input_name, "file", "input file name"), OPT_STRING(0, "to-json", &to_json, NULL, "Convert to JSON format"), diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index 59bf1f72d12e..1b3df868849a 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -113,7 +113,7 @@ enum { COMPUTE_STREAM, /* After COMPUTE_MAX to avoid use current compute arrays */ }; -const char *compute_names[COMPUTE_MAX] = { +static const char *compute_names[COMPUTE_MAX] = { [COMPUTE_DELTA] = "delta", [COMPUTE_DELTA_ABS] = "delta-abs", [COMPUTE_RATIO] = "ratio", @@ -382,7 +382,7 @@ static void block_hist_free(void *he) free(bh); } -struct hist_entry_ops block_hist_ops = { +static struct hist_entry_ops block_hist_ops = { .new = block_hist_zalloc, .free = block_hist_free, }; @@ -1280,8 +1280,7 @@ static const struct option options[] = { OPT_STRING_NOEMPTY('t', "field-separator", &symbol_conf.field_sep, "separator", "separator for columns, no spaces will be added between " "columns '.' is reserved."), - OPT_CALLBACK(0, "symfs", NULL, "directory", - "Look for files with symbols relative to this directory", + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, symbol__config_symfs), OPT_UINTEGER('o', "order", &sort_compute, "Specify compute sorting."), OPT_CALLBACK(0, "percentage", NULL, "relative|absolute", @@ -1353,7 +1352,7 @@ static int cycles_printf(struct hist_entry *he, struct hist_entry *pair, /* * Avoid printing the warning "addr2line_init failed for ..." */ - symbol_conf.disable_add2line_warn = true; + symbol_conf.addr2line_disable_warn = true; bi = block_he->block_info; @@ -1892,7 +1891,7 @@ static int data_init(int argc, const char **argv) return -EINVAL; } - data__files = zalloc(sizeof(*data__files) * data__files_cnt); + data__files = calloc(data__files_cnt, sizeof(*data__files)); if (!data__files) return -ENOMEM; @@ -1987,7 +1986,7 @@ int cmd_diff(int argc, const char **argv) if (compute == COMPUTE_STREAM) { symbol_conf.show_branchflag_count = true; - symbol_conf.disable_add2line_warn = true; + symbol_conf.addr2line_disable_warn = true; callchain_param.mode = CHAIN_FLAT; callchain_param.key = CCKEY_SRCLINE; callchain_param.branch_callstack = 1; diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c index 4cc33452d79b..8a7dbfb14535 100644 --- a/tools/perf/builtin-ftrace.c +++ b/tools/perf/builtin-ftrace.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "debug.h" diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 5b29f4296861..f174bc69cec4 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -133,7 +133,7 @@ struct perf_inject { struct perf_file_section secs[HEADER_FEAT_BITS]; struct guest_session guest_session; struct strlist *known_build_ids; - const struct evsel *mmap_evsel; + struct evsel *mmap_evsel; struct ip_callchain *raw_callchain; }; @@ -270,9 +270,8 @@ static s64 perf_event__repipe_auxtrace(const struct perf_tool *tool, inject->have_auxtrace = true; if (!inject->output.is_pipe) { - off_t offset; + off_t offset = perf_data__seek(&inject->output, 0, SEEK_CUR); - offset = lseek(inject->output.file.fd, 0, SEEK_CUR); if (offset == -1) return -errno; ret = auxtrace_index__auxtrace_event(&session->auxtrace_index, @@ -519,7 +518,7 @@ static struct dso *findnew_dso(int pid, int tid, const char *filename, * processing mmap events. If not stashed, search the evlist for the first mmap * gathering event. */ -static const struct evsel *inject__mmap_evsel(struct perf_inject *inject) +static struct evsel *inject__mmap_evsel(struct perf_inject *inject) { struct evsel *pos; @@ -1023,7 +1022,6 @@ int perf_event__inject_buildid(const struct perf_tool *tool, union perf_event *e sample__for_each_callchain_node(thread, evsel, sample, PERF_MAX_STACK_DEPTH, /*symbols=*/false, mark_dso_hit_callback, &args); - thread__put(thread); repipe: perf_event__repipe(tool, event, sample, machine); @@ -1087,6 +1085,7 @@ static int perf_inject__sched_stat(const struct perf_tool *tool, struct perf_sample sample_sw; struct perf_inject *inject = container_of(tool, struct perf_inject, tool); u32 pid = evsel__intval(evsel, sample, "pid"); + int ret; list_for_each_entry(ent, &inject->samples, node) { if (pid == ent->tid) @@ -1103,7 +1102,9 @@ found: perf_event__synthesize_sample(event_sw, evsel->core.attr.sample_type, evsel->core.attr.read_format, &sample_sw); build_id__mark_dso_hit(tool, event_sw, &sample_sw, evsel, machine); - return perf_event__repipe(tool, event_sw, &sample_sw, machine); + ret = perf_event__repipe(tool, event_sw, &sample_sw, machine); + perf_sample__exit(&sample_sw); + return ret; } #endif @@ -1429,6 +1430,7 @@ static int synthesize_build_id(struct perf_inject *inject, struct dso *dso, pid_ { struct machine *machine = perf_session__findnew_machine(inject->session, machine_pid); struct perf_sample synth_sample = { + .evsel = inject__mmap_evsel(inject), .pid = -1, .tid = -1, .time = -1, @@ -1648,6 +1650,7 @@ static int guest_session__fetch(struct guest_session *gs) size_t hdr_sz = sizeof(*hdr); ssize_t ret; + perf_sample__init(&gs->ev.sample, /*all=*/false); buf = gs->ev.event_buf; if (!buf) { buf = malloc(PERF_SAMPLE_MAX_SIZE); @@ -1745,18 +1748,24 @@ static int guest_session__inject_events(struct guest_session *gs, u64 timestamp) if (!gs->fetched) { ret = guest_session__fetch(gs); if (ret) - return ret; + break; gs->fetched = true; } ev = gs->ev.event; sample = &gs->ev.sample; - if (!ev->header.size) - return 0; /* EOF */ - - if (sample->time > timestamp) - return 0; + if (!ev->header.size) { + /* EOF */ + perf_sample__exit(&gs->ev.sample); + gs->fetched = false; + ret = 0; + break; + } + if (sample->time > timestamp) { + ret = 0; + break; + } /* Change cpumode to guest */ cpumode = ev->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; @@ -1779,12 +1788,14 @@ static int guest_session__inject_events(struct guest_session *gs, u64 timestamp) if (id_hdr_size & 7) { pr_err("Bad id_hdr_size %u\n", id_hdr_size); - return -EINVAL; + ret = -EINVAL; + break; } if (ev->header.size & 7) { pr_err("Bad event size %u\n", ev->header.size); - return -EINVAL; + ret = -EINVAL; + break; } /* Remove guest id sample */ @@ -1792,14 +1803,16 @@ static int guest_session__inject_events(struct guest_session *gs, u64 timestamp) if (ev->header.size & 7) { pr_err("Bad raw event size %u\n", ev->header.size); - return -EINVAL; + ret = -EINVAL; + break; } guest_id = guest_session__lookup_id(gs, id); if (!guest_id) { pr_err("Guest event with unknown id %llu\n", (unsigned long long)id); - return -EINVAL; + ret = -EINVAL; + break; } /* Change to host ID to avoid conflicting ID values */ @@ -1819,19 +1832,28 @@ static int guest_session__inject_events(struct guest_session *gs, u64 timestamp) /* New id sample with new ID and CPU */ ret = evlist__append_id_sample(inject->session->evlist, ev, sample); if (ret) - return ret; + break; if (ev->header.size & 7) { pr_err("Bad new event size %u\n", ev->header.size); - return -EINVAL; + ret = -EINVAL; + break; } - gs->fetched = false; - ret = output_bytes(inject, ev, ev->header.size); if (ret) - return ret; + break; + + /* Reset for next guest session event fetch. */ + perf_sample__exit(sample); + gs->fetched = false; } + if (ret && gs->fetched) { + /* Clear saved sample state on error. */ + perf_sample__exit(&gs->ev.sample); + gs->fetched = false; + } + return ret; } static int guest_session__flush_events(struct guest_session *gs) @@ -2134,6 +2156,7 @@ static bool keep_feat(struct perf_inject *inject, int feat) case HEADER_HYBRID_TOPOLOGY: case HEADER_PMU_CAPS: case HEADER_CPU_DOMAIN_INFO: + case HEADER_CLN_SIZE: return true; /* Information that can be updated */ case HEADER_BUILD_ID: @@ -2479,12 +2502,12 @@ int cmd_inject(int argc, const char **argv) .output = { .path = "-", .mode = PERF_DATA_MODE_WRITE, - .use_stdio = true, + .file.use_stdio = true, }, }; struct perf_data data = { .mode = PERF_DATA_MODE_READ, - .use_stdio = true, + .file.use_stdio = true, }; int ret; const char *known_build_ids = NULL; diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 7929a5fa5f46..9c64a0d74823 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -82,7 +82,7 @@ static unsigned long nr_allocs, nr_cross_allocs; /* filters for controlling start and stop of time of analysis */ static struct perf_time_interval ptime; -const char *time_str; +static const char *time_str; static int insert_alloc_stat(unsigned long call_site, unsigned long ptr, int bytes_req, int bytes_alloc, int cpu) diff --git a/tools/perf/builtin-kwork.c b/tools/perf/builtin-kwork.c index 7f3068264568..9d3a4c779a41 100644 --- a/tools/perf/builtin-kwork.c +++ b/tools/perf/builtin-kwork.c @@ -985,7 +985,7 @@ static int process_irq_handler_exit_event(const struct perf_tool *tool, return 0; } -const struct evsel_str_handler irq_tp_handlers[] = { +static const struct evsel_str_handler irq_tp_handlers[] = { { "irq:irq_handler_entry", process_irq_handler_entry_event, }, { "irq:irq_handler_exit", process_irq_handler_exit_event, }, }; @@ -1080,7 +1080,7 @@ static int process_softirq_exit_event(const struct perf_tool *tool, return 0; } -const struct evsel_str_handler softirq_tp_handlers[] = { +static const struct evsel_str_handler softirq_tp_handlers[] = { { "irq:softirq_raise", process_softirq_raise_event, }, { "irq:softirq_entry", process_softirq_entry_event, }, { "irq:softirq_exit", process_softirq_exit_event, }, @@ -1211,7 +1211,7 @@ static int process_workqueue_execute_end_event(const struct perf_tool *tool, return 0; } -const struct evsel_str_handler workqueue_tp_handlers[] = { +static const struct evsel_str_handler workqueue_tp_handlers[] = { { "workqueue:workqueue_activate_work", process_workqueue_activate_work_event, }, { "workqueue:workqueue_execute_start", process_workqueue_execute_start_event, }, { "workqueue:workqueue_execute_end", process_workqueue_execute_end_event, }, @@ -1281,7 +1281,7 @@ static int process_sched_switch_event(const struct perf_tool *tool, return 0; } -const struct evsel_str_handler sched_tp_handlers[] = { +static const struct evsel_str_handler sched_tp_handlers[] = { { "sched:sched_switch", process_sched_switch_event, }, }; @@ -1561,13 +1561,13 @@ static void print_bad_events(struct perf_kwork *kwork) } } -const char *graph_load = "||||||||||||||||||||||||||||||||||||||||||||||||"; -const char *graph_idle = " "; static void top_print_per_cpu_load(struct perf_kwork *kwork) { int i, load_width; u64 total, load, load_ratio; struct kwork_top_stat *stat = &kwork->top_stat; + const char *graph_load = "||||||||||||||||||||||||||||||||||||||||||||||||"; + const char *graph_idle = " "; for (i = 0; i < MAX_NR_CPUS; i++) { total = stat->cpus_runtime[i].total; @@ -2208,7 +2208,7 @@ static int perf_kwork__top(struct perf_kwork *kwork) struct __top_cpus_runtime *cpus_runtime; int ret = 0; - cpus_runtime = zalloc(sizeof(struct __top_cpus_runtime) * (MAX_NR_CPUS + 1)); + cpus_runtime = calloc(MAX_NR_CPUS + 1, sizeof(struct __top_cpus_runtime)); if (!cpus_runtime) return -1; @@ -2423,8 +2423,8 @@ int cmd_kwork(int argc, const char **argv) "Display call chains if present"), OPT_UINTEGER(0, "max-stack", &kwork.max_stack, "Maximum number of functions to display backtrace."), - OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", - "Look for files with symbols relative to this directory"), + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, + symbol__config_symfs), OPT_STRING(0, "time", &kwork.time_str, "str", "Time span for analysis (start,stop)"), OPT_STRING('C', "cpu", &kwork.cpu_list, "cpu", diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index e8962c985d34..5585aeb97684 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -2250,7 +2250,7 @@ static int parse_map_entry(const struct option *opt, const char *str, static int parse_max_stack(const struct option *opt, const char *str, int unset __maybe_unused) { - unsigned long *len = (unsigned long *)opt->value; + int *len = opt->value; long val; char *endptr; diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index 1b4ba85ee019..a67b565278ae 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -597,8 +597,8 @@ __cmd_probe(int argc, const char **argv) OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, "Enable kernel symbol demangling"), OPT_BOOLEAN(0, "cache", &probe_conf.cache, "Manipulate probe cache"), - OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", - "Look for files with symbols relative to this directory"), + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, + symbol__config_symfs), OPT_CALLBACK(0, "target-ns", NULL, "pid", "target pid for namespace contexts", opt_set_target_ns), OPT_BOOLEAN(0, "bootconfig", &probe_conf.bootconfig, diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 60d764068302..4a5eba498c02 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -40,7 +40,6 @@ #include "util/perf_api_probe.h" #include "util/trigger.h" #include "util/perf-hooks.h" -#include "util/cpu-set-sched.h" #include "util/synthetic-events.h" #include "util/time-utils.h" #include "util/units.h" @@ -56,6 +55,7 @@ #include "asm/bug.h" #include "perf.h" #include "cputopo.h" +#include "dwarf-regs.h" #include #include @@ -453,7 +453,7 @@ static int record__aio_pushfn(struct mmap *map, void *to, void *buf, size_t size static int record__aio_push(struct record *rec, struct mmap *map, off_t *off) { int ret, idx; - int trace_fd = rec->session->data->file.fd; + int trace_fd = perf_data__fd(rec->session->data); struct record_aio aio = { .rec = rec, .size = 0 }; /* @@ -1070,12 +1070,12 @@ static int record__thread_data_init_maps(struct record_thread *thread_data, stru thread_data->nr_mmaps = bitmap_weight(thread_data->mask->maps.bits, thread_data->mask->maps.nbits); if (mmap) { - thread_data->maps = zalloc(thread_data->nr_mmaps * sizeof(struct mmap *)); + thread_data->maps = calloc(thread_data->nr_mmaps, sizeof(struct mmap *)); if (!thread_data->maps) return -ENOMEM; } if (overwrite_mmap) { - thread_data->overwrite_maps = zalloc(thread_data->nr_mmaps * sizeof(struct mmap *)); + thread_data->overwrite_maps = calloc(thread_data->nr_mmaps, sizeof(struct mmap *)); if (!thread_data->overwrite_maps) { zfree(&thread_data->maps); return -ENOMEM; @@ -1220,7 +1220,7 @@ static int record__alloc_thread_data(struct record *rec, struct evlist *evlist) int t, ret; struct record_thread *thread_data; - rec->thread_data = zalloc(rec->nr_threads * sizeof(*(rec->thread_data))); + rec->thread_data = calloc(rec->nr_threads, sizeof(*(rec->thread_data))); if (!rec->thread_data) { pr_err("Failed to allocate thread data\n"); return -ENOMEM; @@ -1640,7 +1640,7 @@ static int record__mmap_read_evlist(struct record *rec, struct evlist *evlist, int rc = 0; int nr_mmaps; struct mmap **maps; - int trace_fd = rec->data.file.fd; + int trace_fd = perf_data__fd(&rec->data); off_t off = 0; if (!evlist) @@ -1845,10 +1845,12 @@ record__finish_output(struct record *rec) } rec->session->header.data_size += rec->bytes_written; - data->file.size = lseek(perf_data__fd(data), 0, SEEK_CUR); + data->file.size = perf_data__seek(data, 0, SEEK_CUR); if (record__threads_enabled(rec)) { - for (i = 0; i < data->dir.nr; i++) - data->dir.files[i].size = lseek(data->dir.files[i].fd, 0, SEEK_CUR); + for (i = 0; i < data->dir.nr; i++) { + data->dir.files[i].size = + perf_data_file__seek(&data->dir.files[i], 0, SEEK_CUR); + } } /* Buildid scanning disabled or build ID in kernel and synthesized map events. */ @@ -2976,65 +2978,32 @@ out_delete_session: return status; } -static void callchain_debug(struct callchain_param *callchain) -{ - static const char *str[CALLCHAIN_MAX] = { "NONE", "FP", "DWARF", "LBR" }; - - pr_debug("callchain: type %s\n", str[callchain->record_mode]); - - if (callchain->record_mode == CALLCHAIN_DWARF) - pr_debug("callchain: stack dump size %d\n", - callchain->dump_size); -} - -int record_opts__parse_callchain(struct record_opts *record, - struct callchain_param *callchain, - const char *arg, bool unset) -{ - int ret; - callchain->enabled = !unset; - - /* --no-call-graph */ - if (unset) { - callchain->record_mode = CALLCHAIN_NONE; - pr_debug("callchain: disabled\n"); - return 0; - } - - ret = parse_callchain_record_opt(arg, callchain); - if (!ret) { - /* Enable data address sampling for DWARF unwind. */ - if (callchain->record_mode == CALLCHAIN_DWARF && - !record->record_data_mmap_set) - record->record_data_mmap = true; - callchain_debug(callchain); - } - - return ret; -} - -int record_parse_callchain_opt(const struct option *opt, +static int record_parse_callchain_opt(const struct option *opt, const char *arg, int unset) { return record_opts__parse_callchain(opt->value, &callchain_param, arg, unset); } -int record_callchain_opt(const struct option *opt, - const char *arg __maybe_unused, - int unset __maybe_unused) +static int record_callchain_opt(const struct option *opt, + const char *arg __maybe_unused, + int unset) { - struct callchain_param *callchain = opt->value; + /* + * The -g option only sets the callchain if not already configured by + * .perfconfig. It does, however, enable it. + */ + if (callchain_param.record_mode != CALLCHAIN_NONE) { + callchain_param.enabled = true; + return 0; + } - callchain->enabled = true; - - if (callchain->record_mode == CALLCHAIN_NONE) - callchain->record_mode = CALLCHAIN_FP; - - callchain_debug(callchain); - return 0; + return record_opts__parse_callchain(opt->value, &callchain_param, + EM_HOST != EM_S390 ? "fp" : "dwarf", + unset); } + static int perf_record_config(const char *var, const char *value, void *cb) { struct record *rec = cb; @@ -3526,7 +3495,7 @@ static struct option __record_options[] = { OPT_CALLBACK(0, "mmap-flush", &record.opts, "number", "Minimal number of bytes that is extracted from mmap data pages (default: 1)", record__mmap_flush_parse), - OPT_CALLBACK_NOOPT('g', NULL, &callchain_param, + OPT_CALLBACK_NOOPT('g', NULL, &record.opts, NULL, "enables call-graph recording" , &record_callchain_opt), OPT_CALLBACK(0, "call-graph", &record.opts, @@ -3696,7 +3665,7 @@ struct option *record_options = __record_options; static int record__mmap_cpu_mask_init(struct mmap_cpu_mask *mask, struct perf_cpu_map *cpus) { struct perf_cpu cpu; - int idx; + unsigned int idx; if (cpu_map__is_dummy(cpus)) return 0; @@ -3743,7 +3712,7 @@ static int record__alloc_thread_masks(struct record *rec, int nr_threads, int nr { int t, ret; - rec->thread_masks = zalloc(nr_threads * sizeof(*(rec->thread_masks))); + rec->thread_masks = calloc(nr_threads, sizeof(*(rec->thread_masks))); if (!rec->thread_masks) { pr_err("Failed to allocate thread masks\n"); return -ENOMEM; @@ -3953,7 +3922,7 @@ static int record__init_thread_numa_masks(struct record *rec, struct perf_cpu_ma return -ENOMEM; } - spec = zalloc(topo->nr * sizeof(char *)); + spec = calloc(topo->nr, sizeof(char *)); if (!spec) { pr_err("Failed to allocate NUMA spec\n"); ret = -ENOMEM; @@ -4131,8 +4100,11 @@ int cmd_record(int argc, const char **argv) perf_debuginfod_setup(&record.debuginfod); - /* Make system wide (-a) the default target. */ - if (!argc && target__none(&rec->opts.target)) + /* + * Use system wide (-a) for the default target (ie. when no + * workload). User ID filtering also implies system-wide. + */ + if ((!argc && target__none(&rec->opts.target)) || rec->uid_str) rec->opts.target.system_wide = true; if (nr_cgroups && !rec->opts.target.system_wide) { @@ -4310,7 +4282,8 @@ int cmd_record(int argc, const char **argv) record.opts.tail_synthesize = true; if (rec->evlist->core.nr_entries == 0) { - struct evlist *def_evlist = evlist__new_default(); + struct evlist *def_evlist = evlist__new_default(&rec->opts.target, + callchain_param.enabled); if (!def_evlist) goto out; @@ -4339,9 +4312,6 @@ int cmd_record(int argc, const char **argv) err = parse_uid_filter(rec->evlist, uid); if (err) goto out; - - /* User ID filtering implies system wide. */ - rec->opts.target.system_wide = true; } /* Enable ignoring missing threads when -p option is defined. */ diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 3b81f4b3dc49..95c0bdba6b11 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -245,25 +245,20 @@ static int process_feature_event(const struct perf_tool *tool, union perf_event *event) { struct report *rep = container_of(tool, struct report, tool); + int ret = perf_event__process_feature(tool, session, event); - if (event->feat.feat_id < HEADER_LAST_FEATURE) - return perf_event__process_feature(session, event); + if (ret == 0 && event->header.size == sizeof(struct perf_record_header_feature) && + (int)event->feat.feat_id >= session->header.last_feat) { + /* + * (feat_id = HEADER_LAST_FEATURE) is the end marker which means + * all features are received. + */ + if (rep->header_only) + session_done = 1; - if (event->feat.feat_id != HEADER_LAST_FEATURE) { - pr_err("failed: wrong feature ID: %" PRI_lu64 "\n", - event->feat.feat_id); - return -1; - } else if (rep->header_only) { - session_done = 1; + setup_forced_leader(rep, session->evlist); } - - /* - * (feat_id = HEADER_LAST_FEATURE) is the end marker which - * means all features are received, now we can force the - * group if needed. - */ - setup_forced_leader(rep, session->evlist); - return 0; + return ret; } static int process_sample_event(const struct perf_tool *tool, @@ -1416,8 +1411,7 @@ int cmd_report(int argc, const char **argv) "columns '.' is reserved."), OPT_BOOLEAN('U', "hide-unresolved", &symbol_conf.hide_unresolved, "Only display entries resolved to a symbol"), - OPT_CALLBACK(0, "symfs", NULL, "directory", - "Look for files with symbols relative to this directory", + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, symbol__config_symfs), OPT_STRING('C', "cpu", &report.cpu_list, "cpu", "list of cpus to profile"), diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index 3f509cfdd58c..555247568e7a 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -2405,7 +2405,7 @@ static int init_idle_threads(int ncpu) { int i, ret; - idle_threads = zalloc(ncpu * sizeof(struct thread *)); + idle_threads = calloc(ncpu, sizeof(struct thread *)); if (!idle_threads) return -ENOMEM; @@ -3483,7 +3483,7 @@ static int setup_cpus_switch_event(struct perf_sched *sched) if (!sched->cpu_last_switched) return -1; - sched->curr_pid = malloc(MAX_CPUS * sizeof(*(sched->curr_pid))); + sched->curr_pid = calloc(MAX_CPUS, sizeof(*(sched->curr_pid))); if (!sched->curr_pid) { zfree(&sched->cpu_last_switched); return -1; @@ -3559,7 +3559,7 @@ static int setup_map_cpus(struct perf_sched *sched) sched->max_cpu.cpu = sysconf(_SC_NPROCESSORS_CONF); if (sched->map.comp) { - sched->map.comp_cpus = zalloc(sched->max_cpu.cpu * sizeof(int)); + sched->map.comp_cpus = calloc(sched->max_cpu.cpu, sizeof(int)); if (!sched->map.comp_cpus) return -1; } @@ -4879,8 +4879,8 @@ int cmd_sched(int argc, const char **argv) "Display call chains if present (default on)"), OPT_UINTEGER(0, "max-stack", &sched.max_stack, "Maximum number of functions to display backtrace."), - OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", - "Look for files with symbols relative to this directory"), + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, + symbol__config_symfs), OPT_BOOLEAN('s', "summary", &sched.summary_only, "Show only syscall summary with statistics"), OPT_BOOLEAN('S', "with-summary", &sched.summary, @@ -4955,6 +4955,7 @@ int cmd_sched(int argc, const char **argv) .switch_event = replay_switch_event, .fork_event = replay_fork_event, }; + struct trace_sched_handler stats_ops = {}; int ret; perf_tool__init(&sched.tool, /*ordered_events=*/true); @@ -5037,6 +5038,7 @@ int cmd_sched(int argc, const char **argv) } else if (!strcmp(argv[0], "stats")) { const char *const stats_subcommands[] = {"record", "report", NULL}; + sched.tp_handler = &stats_ops; argc = parse_options_subcommand(argc, argv, stats_options, stats_subcommands, stats_usage, diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 7c743a303507..c8ac9f01a36b 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -166,7 +166,7 @@ struct perf_script { int range_num; }; -struct output_option { +static struct output_option { const char *str; enum perf_output_field field; } all_output_options[] = { @@ -1271,11 +1271,11 @@ static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en, if (PRINT_FIELD(BRCNTR)) { struct evsel *pos = evsel__leader(evsel); - unsigned int i = 0, j, num, mask, width; + unsigned int i = 0, j, num, mask, width, numprinted = 0; perf_env__find_br_cntr_info(evsel__env(evsel), NULL, &width); mask = (1L << width) - 1; - printed += fprintf(fp, "br_cntr: "); + printed += fprintf(fp, "\t# br_cntr: "); evlist__for_each_entry_from(evsel->evlist, pos) { if (!(pos->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS)) continue; @@ -1283,16 +1283,20 @@ static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en, break; num = (br_cntr >> (i++ * width)) & mask; + numprinted += num; if (!verbose) { for (j = 0; j < num; j++) printed += fprintf(fp, "%s", pos->abbr_name); } else printed += fprintf(fp, "%s %d ", pos->name, num); } - printed += fprintf(fp, "\t"); + if (numprinted == 0 && !verbose) + printed += fprintf(fp, "-"); + printed += fprintf(fp, " "); } - printed += fprintf(fp, "#%s%s%s%s", + printed += fprintf(fp, "%s%s%s%s%s", + !PRINT_FIELD(BRCNTR) ? "#" : "", en->flags.predicted ? " PRED" : "", en->flags.mispred ? " MISPRED" : "", en->flags.in_tx ? " INTX" : "", @@ -2568,7 +2572,6 @@ static struct scripting_ops *scripting_ops; static void __process_stat(struct evsel *counter, u64 tstamp) { int nthreads = perf_thread_map__nr(counter->core.threads); - int idx, thread; struct perf_cpu cpu; static int header_printed; @@ -2578,7 +2581,9 @@ static void __process_stat(struct evsel *counter, u64 tstamp) header_printed = 1; } - for (thread = 0; thread < nthreads; thread++) { + for (int thread = 0; thread < nthreads; thread++) { + unsigned int idx; + perf_cpu_map__for_each_cpu(cpu, idx, evsel__cpus(counter)) { struct perf_counts_values *counts; @@ -2905,8 +2910,12 @@ static int print_event_with_time(const struct perf_tool *tool, thread = machine__findnew_thread(machine, pid, tid); if (evsel) { + struct evsel *saved_evsel = sample->evsel; + + sample->evsel = evsel; perf_sample__fprintf_start(script, sample, thread, evsel, event->header.type, stdout); + sample->evsel = saved_evsel; } perf_event__fprintf(event, machine, stdout); @@ -3814,7 +3823,7 @@ out: static int have_cmd(int argc, const char **argv) { - char **__argv = malloc(sizeof(const char *) * argc); + char **__argv = calloc(argc, sizeof(const char *)); if (!__argv) { pr_err("malloc failed\n"); @@ -3939,15 +3948,6 @@ int process_cpu_map_event(const struct perf_tool *tool, return set_maps(script); } -static int process_feature_event(const struct perf_tool *tool __maybe_unused, - struct perf_session *session, - union perf_event *event) -{ - if (event->feat.feat_id < HEADER_LAST_FEATURE) - return perf_event__process_feature(session, event); - return 0; -} - static int perf_script__process_auxtrace_info(const struct perf_tool *tool, struct perf_session *session, union perf_event *event) @@ -4074,8 +4074,7 @@ int cmd_script(int argc, const char **argv) "file", "kallsyms pathname"), OPT_BOOLEAN('G', "hide-call-graph", &no_callchain, "When printing symbols do not display call chain"), - OPT_CALLBACK(0, "symfs", NULL, "directory", - "Look for files with symbols relative to this directory", + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, symbol__config_symfs), OPT_CALLBACK('F', "fields", NULL, "str", "comma separated output fields prepend with 'type:'. " @@ -4313,7 +4312,7 @@ int cmd_script(int argc, const char **argv) } } - __argv = malloc((argc + 6) * sizeof(const char *)); + __argv = calloc(argc + 6, sizeof(const char *)); if (!__argv) { pr_err("malloc failed\n"); err = -ENOMEM; @@ -4339,7 +4338,7 @@ int cmd_script(int argc, const char **argv) dup2(live_pipe[0], 0); close(live_pipe[1]); - __argv = malloc((argc + 4) * sizeof(const char *)); + __argv = calloc(argc + 4, sizeof(const char *)); if (!__argv) { pr_err("malloc failed\n"); err = -ENOMEM; @@ -4377,7 +4376,7 @@ script_found: } } - __argv = malloc((argc + 2) * sizeof(const char *)); + __argv = calloc(argc + 2, sizeof(const char *)); if (!__argv) { pr_err("malloc failed\n"); err = -ENOMEM; @@ -4423,7 +4422,7 @@ script_found: #ifdef HAVE_LIBTRACEEVENT script.tool.tracing_data = perf_event__process_tracing_data; #endif - script.tool.feature = process_feature_event; + script.tool.feature = perf_event__process_feature; script.tool.build_id = perf_event__process_build_id; script.tool.id_index = perf_event__process_id_index; script.tool.auxtrace_info = perf_script__process_auxtrace_info; diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 73c2ba7e3076..99d7db372b48 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -164,7 +164,7 @@ struct opt_aggr_mode { }; /* Turn command line option into most generic aggregation mode setting. */ -static enum aggr_mode opt_aggr_mode_to_aggr_mode(struct opt_aggr_mode *opt_mode) +static enum aggr_mode opt_aggr_mode_to_aggr_mode(const struct opt_aggr_mode *opt_mode) { enum aggr_mode mode = AGGR_GLOBAL; @@ -410,7 +410,7 @@ static int read_tool_counters(void) struct evsel *counter; evlist__for_each_entry(evsel_list, counter) { - int idx; + unsigned int idx; if (!evsel__is_tool(counter)) continue; @@ -1214,13 +1214,28 @@ static int parse_cputype(const struct option *opt, return 0; } +static int parse_pmu_filter(const struct option *opt, + const char *str, + int unset __maybe_unused) +{ + struct evlist *evlist = *(struct evlist **)opt->value; + + if (!list_empty(&evlist->core.entries)) { + fprintf(stderr, "Must define pmu-filter before events/metrics\n"); + return -1; + } + + parse_events_option_args.pmu_filter = str; + return 0; +} + static int parse_cache_level(const struct option *opt, const char *str, int unset __maybe_unused) { int level; - struct opt_aggr_mode *opt_aggr_mode = (struct opt_aggr_mode *)opt->value; - u32 *aggr_level = (u32 *)opt->data; + bool *per_cache = opt->value; + u32 *aggr_level = opt->data; /* * If no string is specified, aggregate based on the topology of @@ -1258,7 +1273,7 @@ static int parse_cache_level(const struct option *opt, return -EINVAL; } out: - opt_aggr_mode->cache = true; + *per_cache = true; *aggr_level = level; return 0; } @@ -1917,25 +1932,33 @@ static int default_evlist_evsel_cmp(void *priv __maybe_unused, const struct evsel *lhs = container_of(lhs_core, struct evsel, core); const struct perf_evsel *rhs_core = container_of(r, struct perf_evsel, node); const struct evsel *rhs = container_of(rhs_core, struct evsel, core); + const struct evsel *lhs_leader = evsel__leader(lhs); + const struct evsel *rhs_leader = evsel__leader(rhs); - if (evsel__leader(lhs) == evsel__leader(rhs)) { + if (lhs_leader == rhs_leader) { /* Within the same group, respect the original order. */ return lhs_core->idx - rhs_core->idx; } - /* Sort default metrics evsels first, and default show events before those. */ - if (lhs->default_metricgroup != rhs->default_metricgroup) - return lhs->default_metricgroup ? -1 : 1; + /* + * Compare using leader's attributes so that all members of a group + * stay together. This ensures leaders are opened before their members. + */ - if (lhs->default_show_events != rhs->default_show_events) - return lhs->default_show_events ? -1 : 1; + /* Sort default metrics evsels first, and default show events before those. */ + if (lhs_leader->default_metricgroup != rhs_leader->default_metricgroup) + return lhs_leader->default_metricgroup ? -1 : 1; + + if (lhs_leader->default_show_events != rhs_leader->default_show_events) + return lhs_leader->default_show_events ? -1 : 1; /* Sort by PMU type (prefers legacy types first). */ - if (lhs->pmu != rhs->pmu) - return lhs->pmu->type - rhs->pmu->type; + if (lhs_leader->pmu != rhs_leader->pmu) + return lhs_leader->pmu->type - rhs_leader->pmu->type; - /* Sort by name. */ - return strcmp(evsel__name((struct evsel *)lhs), evsel__name((struct evsel *)rhs)); + /* Sort by leader's name. */ + return strcmp(evsel__name((struct evsel *)lhs_leader), + evsel__name((struct evsel *)rhs_leader)); } /* @@ -2305,24 +2328,23 @@ static struct perf_stat perf_stat = { static int __cmd_report(int argc, const char **argv) { struct perf_session *session; + struct opt_aggr_mode opt_mode = {}; const struct option options[] = { OPT_STRING('i', "input", &input_name, "file", "input file name"), - OPT_SET_UINT(0, "per-socket", &perf_stat.aggr_mode, - "aggregate counts per processor socket", AGGR_SOCKET), - OPT_SET_UINT(0, "per-die", &perf_stat.aggr_mode, - "aggregate counts per processor die", AGGR_DIE), - OPT_SET_UINT(0, "per-cluster", &perf_stat.aggr_mode, - "aggregate counts perf processor cluster", AGGR_CLUSTER), - OPT_CALLBACK_OPTARG(0, "per-cache", &perf_stat.aggr_mode, &perf_stat.aggr_level, - "cache level", - "aggregate count at this cache level (Default: LLC)", + OPT_BOOLEAN(0, "per-thread", &opt_mode.thread, "aggregate counts per thread"), + OPT_BOOLEAN(0, "per-socket", &opt_mode.socket, + "aggregate counts per processor socket"), + OPT_BOOLEAN(0, "per-die", &opt_mode.die, "aggregate counts per processor die"), + OPT_BOOLEAN(0, "per-cluster", &opt_mode.cluster, + "aggregate counts per processor cluster"), + OPT_CALLBACK_OPTARG(0, "per-cache", &opt_mode.cache, &perf_stat.aggr_level, + "cache level", "aggregate count at this cache level (Default: LLC)", parse_cache_level), - OPT_SET_UINT(0, "per-core", &perf_stat.aggr_mode, - "aggregate counts per physical processor core", AGGR_CORE), - OPT_SET_UINT(0, "per-node", &perf_stat.aggr_mode, - "aggregate counts per numa node", AGGR_NODE), - OPT_SET_UINT('A', "no-aggr", &perf_stat.aggr_mode, - "disable CPU count aggregation", AGGR_NONE), + OPT_BOOLEAN(0, "per-core", &opt_mode.core, + "aggregate counts per physical processor core"), + OPT_BOOLEAN(0, "per-node", &opt_mode.node, "aggregate counts per numa node"), + OPT_BOOLEAN('A', "no-aggr", &opt_mode.no_aggr, + "disable aggregation across CPUs or PMUs"), OPT_END() }; struct stat st; @@ -2330,6 +2352,10 @@ static int __cmd_report(int argc, const char **argv) argc = parse_options(argc, argv, options, stat_report_usage, 0); + perf_stat.aggr_mode = opt_aggr_mode_to_aggr_mode(&opt_mode); + if (perf_stat.aggr_mode == AGGR_GLOBAL) + perf_stat.aggr_mode = AGGR_UNSET; /* No option found so leave unset. */ + if (!input_name || !strlen(input_name)) { if (!fstat(STDIN_FILENO, &st) && S_ISFIFO(st.st_mode)) input_name = "-"; @@ -2506,7 +2532,7 @@ int cmd_stat(int argc, const char **argv) OPT_BOOLEAN(0, "per-die", &opt_mode.die, "aggregate counts per processor die"), OPT_BOOLEAN(0, "per-cluster", &opt_mode.cluster, "aggregate counts per processor cluster"), - OPT_CALLBACK_OPTARG(0, "per-cache", &opt_mode, &stat_config.aggr_level, + OPT_CALLBACK_OPTARG(0, "per-cache", &opt_mode.cache, &stat_config.aggr_level, "cache level", "aggregate count at this cache level (Default: LLC)", parse_cache_level), OPT_BOOLEAN(0, "per-core", &opt_mode.core, @@ -2561,6 +2587,10 @@ int cmd_stat(int argc, const char **argv) "Only enable events on applying cpu with this type " "for hybrid platform (e.g. core or atom)", parse_cputype), + OPT_CALLBACK(0, "pmu-filter", &evsel_list, "pmu", + "Only enable events on applying pmu with specified " + "for multiple pmus with same type(e.g. hisi_sicl2_cpa0 or hisi_sicl0_cpa0)", + parse_pmu_filter), #ifdef HAVE_LIBPFM OPT_CALLBACK(0, "pfm-events", &evsel_list, "event", "libpfm4 event selector. use 'perf list' to list available events", @@ -2744,7 +2774,7 @@ int cmd_stat(int argc, const char **argv) } if (stat_config.walltime_run_table) { - stat_config.walltime_run = zalloc(stat_config.run_count * sizeof(stat_config.walltime_run[0])); + stat_config.walltime_run = calloc(stat_config.run_count, sizeof(stat_config.walltime_run[0])); if (!stat_config.walltime_run) { pr_err("failed to setup -r option"); goto out; diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index f8b49d69e9a5..28f33e39895d 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -1951,8 +1951,7 @@ int cmd_timechart(int argc, const char **argv) OPT_CALLBACK('p', "process", NULL, "process", "process selector. Pass a pid or process name.", parse_process), - OPT_CALLBACK(0, "symfs", NULL, "directory", - "Look for files with symbols relative to this directory", + OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP, symbol__config_symfs), OPT_INTEGER('n', "proc-num", &tchart.proc_num, "min. number of tasks to print"), diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index 710604c4f6f6..f6eb543de537 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -56,6 +56,7 @@ #include "util/debug.h" #include "util/ordered-events.h" #include "util/pfm.h" +#include "dwarf-regs.h" #include #include @@ -1386,13 +1387,6 @@ out_join_thread: return ret; } -static int -callchain_opt(const struct option *opt, const char *arg, int unset) -{ - symbol_conf.use_callchain = true; - return record_callchain_opt(opt, arg, unset); -} - static int parse_callchain_opt(const struct option *opt, const char *arg, int unset) { @@ -1413,6 +1407,24 @@ parse_callchain_opt(const struct option *opt, const char *arg, int unset) return parse_callchain_top_opt(arg); } +static int +callchain_opt(const struct option *opt, const char *arg __maybe_unused, int unset) +{ + struct callchain_param *callchain = opt->value; + + /* + * The -g option only sets the callchain if not already configured by + * .perfconfig. It does, however, enable it. + */ + if (callchain->record_mode != CALLCHAIN_NONE) { + callchain->enabled = true; + return 0; + } + + return parse_callchain_opt(opt, EM_HOST != EM_S390 ? "fp" : "dwarf", unset); +} + + static int perf_top_config(const char *var, const char *value, void *cb __maybe_unused) { if (!strcmp(var, "top.call-graph")) { @@ -1437,11 +1449,10 @@ parse_percent_limit(const struct option *opt, const char *arg, return 0; } -const char top_callchain_help[] = CALLCHAIN_RECORD_HELP CALLCHAIN_REPORT_HELP - "\n\t\t\t\tDefault: fp,graph,0.5,caller,function"; - int cmd_top(int argc, const char **argv) { + static const char top_callchain_help[] = CALLCHAIN_RECORD_HELP CALLCHAIN_REPORT_HELP + "\n\t\t\t\tDefault: fp,graph,0.5,caller,function"; char errbuf[BUFSIZ]; struct perf_top top = { .count_filter = 5, @@ -1694,8 +1705,17 @@ int cmd_top(int argc, const char **argv) if (annotate_check_args() < 0) goto out_delete_evlist; + status = target__validate(target); + if (status) { + target__strerror(target, status, errbuf, BUFSIZ); + ui__warning("%s\n", errbuf); + } + + if (target__none(target)) + target->system_wide = true; + if (!top.evlist->core.nr_entries) { - struct evlist *def_evlist = evlist__new_default(); + struct evlist *def_evlist = evlist__new_default(target, callchain_param.enabled); if (!def_evlist) goto out_delete_evlist; @@ -1788,12 +1808,6 @@ int cmd_top(int argc, const char **argv) goto out_delete_evlist; } - status = target__validate(target); - if (status) { - target__strerror(target, status, errbuf, BUFSIZ); - ui__warning("%s\n", errbuf); - } - if (top.uid_str) { uid_t uid = parse_uid(top.uid_str); @@ -1807,9 +1821,6 @@ int cmd_top(int argc, const char **argv) goto out_delete_evlist; } - if (target__none(target)) - target->system_wide = true; - if (evlist__create_maps(top.evlist, target) < 0) { ui__error("Couldn't create thread/CPU maps: %s\n", errno == ENOENT ? "No such process" : str_error_r(errno, errbuf, sizeof(errbuf))); diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index 311d9da9896a..e58c49d047a2 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -21,7 +21,6 @@ #include #include #endif -#include "util/bpf_map.h" #include "util/rlimit.h" #include "builtin.h" #include "util/cgroup.h" @@ -1565,7 +1564,9 @@ static bool syscall_id_equal(long key1, long key2, void *ctx __maybe_unused) static struct hashmap *alloc_syscall_stats(void) { - return hashmap__new(syscall_id_hash, syscall_id_equal, NULL); + struct hashmap *result = hashmap__new(syscall_id_hash, syscall_id_equal, NULL); + + return IS_ERR(result) ? NULL : result; } static void delete_syscall_stats(struct hashmap *syscall_stats) @@ -1573,7 +1574,7 @@ static void delete_syscall_stats(struct hashmap *syscall_stats) struct hashmap_entry *pos; size_t bkt; - if (syscall_stats == NULL) + if (!syscall_stats) return; hashmap__for_each_entry(syscall_stats, pos, bkt) @@ -1589,7 +1590,7 @@ static struct thread_trace *thread_trace__new(struct trace *trace) ttrace->files.max = -1; if (trace->summary) { ttrace->syscall_stats = alloc_syscall_stats(); - if (IS_ERR(ttrace->syscall_stats)) + if (!ttrace->syscall_stats) zfree(&ttrace); } } @@ -2003,9 +2004,13 @@ static int trace__symbols_init(struct trace *trace, int argc, const char **argv, if (err < 0) goto out; + if (trace->summary_only && trace->summary_mode != SUMMARY__BY_THREAD) + goto out; + err = __machine__synthesize_threads(trace->host, &trace->tool, &trace->opts.target, evlist->core.threads, trace__tool_process, - /*needs_mmap=*/callchain_param.enabled, + /*needs_mmap=*/callchain_param.enabled && + !trace->summary_only, /*mmap_data=*/false, /*nr_threads_synthesize=*/1); out: @@ -2264,9 +2269,7 @@ static int trace__validate_ev_qualifier(struct trace *trace) struct str_node *pos; size_t nr_used = 0, nr_allocated = strlist__nr_entries(trace->ev_qualifier); - trace->ev_qualifier_ids.entries = malloc(nr_allocated * - sizeof(trace->ev_qualifier_ids.entries[0])); - + trace->ev_qualifier_ids.entries = calloc(nr_allocated, sizeof(trace->ev_qualifier_ids.entries[0])); if (trace->ev_qualifier_ids.entries == NULL) { fputs("Error:\tNot enough memory for allocating events qualifier ids\n", trace->output); @@ -2955,7 +2958,7 @@ static int trace__sys_exit(struct trace *trace, struct evsel *evsel, ++trace->stats.vfs_getname; } - if (ttrace->entry_time) { + if (ttrace->entry_time && sample->time >= ttrace->entry_time) { duration = sample->time - ttrace->entry_time; if (trace__filter_duration(trace, duration)) goto out; @@ -4464,7 +4467,7 @@ create_maps: if (trace->summary_mode == SUMMARY__BY_TOTAL && !trace->summary_bpf) { trace->syscall_stats = alloc_syscall_stats(); - if (IS_ERR(trace->syscall_stats)) + if (!trace->syscall_stats) goto out_delete_evlist; } @@ -4771,7 +4774,7 @@ static int trace__replay(struct trace *trace) if (trace->summary_mode == SUMMARY__BY_TOTAL) { trace->syscall_stats = alloc_syscall_stats(); - if (IS_ERR(trace->syscall_stats)) + if (!trace->syscall_stats) goto out; } @@ -5299,6 +5302,13 @@ static int trace__parse_summary_mode(const struct option *opt, const char *str, return 0; } +static int trace_parse_callchain_opt(const struct option *opt, + const char *arg, + int unset) +{ + return record_opts__parse_callchain(opt->value, &callchain_param, arg, unset); +} + static int trace__config(const char *var, const char *value, void *arg) { struct trace *trace = arg; @@ -5446,7 +5456,7 @@ int cmd_trace(int argc, const char **argv) OPT_BOOLEAN('f', "force", &trace.force, "don't complain, do it"), OPT_CALLBACK(0, "call-graph", &trace.opts, "record_mode[,record_size]", record_callchain_help, - &record_parse_callchain_opt), + &trace_parse_callchain_opt), OPT_BOOLEAN(0, "libtraceevent_print", &trace.libtraceevent_print, "Use libtraceevent to print the tracepoint arguments."), OPT_BOOLEAN(0, "kernel-syscall-graph", &trace.kernel_syscallchains, diff --git a/tools/perf/check-headers.sh b/tools/perf/check-headers.sh index 31826621eebd..531c0e0e84df 100755 --- a/tools/perf/check-headers.sh +++ b/tools/perf/check-headers.sh @@ -6,10 +6,7 @@ NC='\033[0m' # No Color declare -a FILES=( "include/uapi/linux/const.h" - "include/uapi/drm/drm.h" - "include/uapi/drm/i915_drm.h" "include/uapi/linux/bits.h" - "include/uapi/linux/fadvise.h" "include/uapi/linux/fscrypt.h" "include/uapi/linux/genetlink.h" "include/uapi/linux/if_addr.h" @@ -90,7 +87,10 @@ declare -a SYNC_CHECK_FILES=( declare -a BEAUTY_FILES=( "arch/x86/include/asm/irq_vectors.h" "arch/x86/include/uapi/asm/prctl.h" + "include/uapi/drm/drm.h" + "include/uapi/drm/i915_drm.h" "include/linux/socket.h" + "include/uapi/linux/fadvise.h" "include/uapi/linux/fcntl.h" "include/uapi/linux/fs.h" "include/uapi/linux/mount.h" diff --git a/tools/perf/jvmti/libjvmti.c b/tools/perf/jvmti/libjvmti.c index 87bfd4781003..d3dc53010e76 100644 --- a/tools/perf/jvmti/libjvmti.c +++ b/tools/perf/jvmti/libjvmti.c @@ -98,7 +98,7 @@ get_line_numbers(jvmtiEnv *jvmti, const void *compile_info, jvmti_line_info_t ** /* * Phase 2 -- allocate big enough line table */ - *tab = malloc(nr_total * sizeof(**tab)); + *tab = calloc(nr_total, sizeof(**tab)); if (!*tab) return JVMTI_ERROR_OUT_OF_MEMORY; @@ -262,11 +262,10 @@ compiled_method_load_cb(jvmtiEnv *jvmti, } nr_lines = 0; } else if (nr_lines > 0) { - line_file_names = malloc(sizeof(char*) * nr_lines); + line_file_names = calloc(nr_lines, sizeof(char *)); if (!line_file_names) { warnx("jvmti: cannot allocate space for line table method names"); } else { - memset(line_file_names, 0, sizeof(char*) * nr_lines); ret = fill_source_filenames(jvmti, nr_lines, line_tab, line_file_names); if (ret != JVMTI_ERROR_NONE) { warnx("jvmti: fill_source_filenames failed"); diff --git a/tools/perf/perf.c b/tools/perf/perf.c index f475a8664ffc..1f51e8de6b1b 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -48,7 +48,7 @@ struct cmd_struct { int option; }; -static struct cmd_struct commands[] = { +static const struct cmd_struct commands[] = { { "archive", NULL, 0 }, { "buildid-cache", cmd_buildid_cache, 0 }, { "buildid-list", cmd_buildid_list, 0 }, @@ -178,7 +178,7 @@ static int set_debug_file(const char *path) return 0; } -struct option options[] = { +static const struct option options[] = { OPT_ARGUMENT("help", "help"), OPT_ARGUMENT("version", "version"), OPT_ARGUMENT("exec-path", "exec-path"), @@ -280,7 +280,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) unsigned int i; for (i = 0; i < ARRAY_SIZE(commands); i++) { - struct cmd_struct *p = commands+i; + const struct cmd_struct *p = commands + i; printf("%s ", p->cmd); } putchar('\n'); @@ -289,7 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) unsigned int i; for (i = 0; i < ARRAY_SIZE(options)-1; i++) { - struct option *p = options+i; + const struct option *p = options + i; printf("--%s ", p->long_name); } putchar('\n'); @@ -331,7 +331,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) #define RUN_SETUP (1<<0) #define USE_PAGER (1<<1) -static int run_builtin(struct cmd_struct *p, int argc, const char **argv) +static int run_builtin(const struct cmd_struct *p, int argc, const char **argv) { int status; struct stat st; @@ -390,7 +390,7 @@ static void handle_internal_command(int argc, const char **argv) } for (i = 0; i < ARRAY_SIZE(commands); i++) { - struct cmd_struct *p = commands+i; + const struct cmd_struct *p = commands+i; if (p->fn == NULL) continue; if (strcmp(p->cmd, cmd)) diff --git a/tools/perf/pmu-events/Build b/tools/perf/pmu-events/Build index dc5f94862a3b..dc1df2d57ddc 100644 --- a/tools/perf/pmu-events/Build +++ b/tools/perf/pmu-events/Build @@ -211,10 +211,10 @@ ifneq ($(strip $(ORPHAN_FILES)),) # Message for $(call echo-cmd,rm). Generally cleaning files isn't part # of a build step. -quiet_cmd_rm = RM $^ +quiet_cmd_rm = RM ...$(words $^) orphan file(s)... +# The list of files can be long. Use xargs to prevent issues. prune_orphans: $(ORPHAN_FILES) - # The list of files can be long. Use xargs to prevent issues. $(Q)$(call echo-cmd,rm)echo "$^" | xargs rm -f JEVENTS_DEPS += prune_orphans diff --git a/tools/perf/pmu-events/arch/arm64/common-and-microarch.json b/tools/perf/pmu-events/arch/arm64/common-and-microarch.json index 468cb085d879..144325d87be4 100644 --- a/tools/perf/pmu-events/arch/arm64/common-and-microarch.json +++ b/tools/perf/pmu-events/arch/arm64/common-and-microarch.json @@ -1512,11 +1512,26 @@ "EventName": "L2D_CACHE_REFILL_PRFM", "BriefDescription": "Level 2 data cache refill, software preload" }, + { + "EventCode": "0x8150", + "EventName": "L3D_CACHE_RW", + "BriefDescription": "Level 3 data cache demand access." + }, + { + "EventCode": "0x8151", + "EventName": "L3D_CACHE_PRFM", + "BriefDescription": "Level 3 data cache software prefetch" + }, { "EventCode": "0x8152", "EventName": "L3D_CACHE_MISS", "BriefDescription": "Level 3 data cache demand access miss" }, + { + "EventCode": "0x8153", + "EventName": "L3D_CACHE_REFILL_PRFM", + "BriefDescription": "Level 3 data cache refill, software prefetch." + }, { "EventCode": "0x8154", "EventName": "L1D_CACHE_HWPRF", @@ -1527,6 +1542,11 @@ "EventName": "L2D_CACHE_HWPRF", "BriefDescription": "Level 2 data cache hardware prefetch." }, + { + "EventCode": "0x8156", + "EventName": "L3D_CACHE_HWPRF", + "BriefDescription": "Level 3 data cache hardware prefetch." + }, { "EventCode": "0x8158", "EventName": "STALL_FRONTEND_MEMBOUND", @@ -1682,6 +1702,11 @@ "EventName": "L2D_CACHE_REFILL_HWPRF", "BriefDescription": "Level 2 data cache refill, hardware prefetch." }, + { + "EventCode": "0x81BE", + "EventName": "L3D_CACHE_REFILL_HWPRF", + "BriefDescription": "Level 3 data cache refill, hardware prefetch." + }, { "EventCode": "0x81C0", "EventName": "L1I_CACHE_HIT_RD", @@ -1712,11 +1737,31 @@ "EventName": "L1I_CACHE_HIT_RD_FPRFM", "BriefDescription": "Level 1 instruction cache demand fetch first hit, fetched by software preload" }, + { + "EventCode": "0x81DC", + "EventName": "L1D_CACHE_HIT_RW_FPRFM", + "BriefDescription": "Level 1 data cache demand access first hit, fetched by software prefetch." + }, { "EventCode": "0x81E0", "EventName": "L1I_CACHE_HIT_RD_FHWPRF", "BriefDescription": "Level 1 instruction cache demand fetch first hit, fetched by hardware prefetcher" }, + { + "EventCode": "0x81EC", + "EventName": "L1D_CACHE_HIT_RW_FHWPRF", + "BriefDescription": "Level 1 data cache demand access first hit, fetched by hardware prefetcher." + }, + { + "EventCode": "0x81F0", + "EventName": "L1I_CACHE_HIT_RD_FPRF", + "BriefDescription": "Level 1 instruction cache demand fetch first hit, fetched by prefetch." + }, + { + "EventCode": "0x81FC", + "EventName": "L1D_CACHE_HIT_RW_FPRF", + "BriefDescription": "Level 1 data cache demand access first hit, fetched by prefetch." + }, { "EventCode": "0x8200", "EventName": "L1I_CACHE_HIT", @@ -1767,11 +1812,26 @@ "EventName": "L1I_LFB_HIT_RD_FPRFM", "BriefDescription": "Level 1 instruction cache demand fetch line-fill buffer first hit, recently fetched by software preload" }, + { + "EventCode": "0x825C", + "EventName": "L1D_LFB_HIT_RW_FPRFM", + "BriefDescription": "Level 1 data cache demand access line-fill buffer first hit, recently fetched by software prefetch." + }, { "EventCode": "0x8260", "EventName": "L1I_LFB_HIT_RD_FHWPRF", "BriefDescription": "Level 1 instruction cache demand fetch line-fill buffer first hit, recently fetched by hardware prefetcher" }, + { + "EventCode": "0x826C", + "EventName": "L1D_LFB_HIT_RW_FHWPRF", + "BriefDescription": "Level 1 data cache demand access line-fill buffer first hit, recently fetched by hardware prefetcher." + }, + { + "EventCode": "0x827C", + "EventName": "L1D_LFB_HIT_RW_FPRF", + "BriefDescription": "Level 1 data cache demand access line-fill buffer first hit, recently fetched by prefetch." + }, { "EventCode": "0x8280", "EventName": "L1I_CACHE_PRF", @@ -1807,6 +1867,11 @@ "EventName": "LL_CACHE_REFILL", "BriefDescription": "Last level cache refill" }, + { + "EventCode": "0x828E", + "EventName": "L3D_CACHE_REFILL_PRF", + "BriefDescription": "Level 3 data cache refill, prefetch." + }, { "EventCode": "0x8320", "EventName": "L1D_CACHE_REFILL_PERCYC", @@ -1872,6 +1937,16 @@ "EventName": "FP_FP8_MIN_SPEC", "BriefDescription": "Floating-point operation speculatively_executed, smallest type is 8-bit floating-point." }, + { + "EventCode": "0x8480", + "EventName": "FP_SP_FIXED_MIN_OPS_SPEC", + "BriefDescription": "Non-scalable element arithmetic operations speculatively executed, smallest type is single-precision floating-point." + }, + { + "EventCode": "0x8482", + "EventName": "FP_HP_FIXED_MIN_OPS_SPEC", + "BriefDescription": "Non-scalable element arithmetic operations speculatively executed, smallest type is half-precision floating-point." + }, { "EventCode": "0x8483", "EventName": "FP_BF16_FIXED_MIN_OPS_SPEC", @@ -1882,6 +1957,16 @@ "EventName": "FP_FP8_FIXED_MIN_OPS_SPEC", "BriefDescription": "Non-scalable element arithmetic operations speculatively executed, smallest type is 8-bit floating-point." }, + { + "EventCode": "0x8488", + "EventName": "FP_SP_SCALE_MIN_OPS_SPEC", + "BriefDescription": "Scalable element arithmetic operations speculatively executed, smallest type is single-precision floating-point." + }, + { + "EventCode": "0x848A", + "EventName": "FP_HP_SCALE_MIN_OPS_SPEC", + "BriefDescription": "Scalable element arithmetic operations speculatively executed, smallest type is half-precision floating-point." + }, { "EventCode": "0x848B", "EventName": "FP_BF16_SCALE_MIN_OPS_SPEC", diff --git a/tools/perf/pmu-events/arch/arm64/mapfile.csv b/tools/perf/pmu-events/arch/arm64/mapfile.csv index bb3fa8a33496..7f0eaa702048 100644 --- a/tools/perf/pmu-events/arch/arm64/mapfile.csv +++ b/tools/perf/pmu-events/arch/arm64/mapfile.csv @@ -46,3 +46,4 @@ 0x00000000500f0000,v1,ampere/emag,core 0x00000000c00fac30,v1,ampere/ampereone,core 0x00000000c00fac40,v1,ampere/ampereonex,core +0x000000004e0f0100,v1,nvidia/t410,core diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/branch.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/branch.json new file mode 100644 index 000000000000..ef4effc00ec3 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/branch.json @@ -0,0 +1,45 @@ +[ + { + "ArchStdEvent": "BR_MIS_PRED", + "PublicDescription": "This event counts branches which are speculatively executed and mispredicted." + }, + { + "ArchStdEvent": "BR_PRED", + "PublicDescription": "This event counts all speculatively executed branches." + }, + { + "EventCode": "0x017e", + "EventName": "BR_PRED_BTB_CTX_UPDATE", + "PublicDescription": "Branch context table update." + }, + { + "EventCode": "0x0188", + "EventName": "BR_MIS_PRED_DIR_RESOLVED", + "PublicDescription": "Number of branch misprediction due to direction misprediction." + }, + { + "EventCode": "0x0189", + "EventName": "BR_MIS_PRED_DIR_UNCOND_RESOLVED", + "PublicDescription": "Number of branch misprediction due to direction misprediction for unconditional branches." + }, + { + "EventCode": "0x018a", + "EventName": "BR_MIS_PRED_DIR_UNCOND_DIRECT_RESOLVED", + "PublicDescription": "Number of branch misprediction due to direction misprediction for unconditional direct branches." + }, + { + "EventCode": "0x018b", + "EventName": "BR_PRED_MULTI_RESOLVED", + "PublicDescription": "Number of resolved branch which made prediction by polymorphic indirect predictor." + }, + { + "EventCode": "0x018c", + "EventName": "BR_MIS_PRED_MULTI_RESOLVED", + "PublicDescription": "Number of branch misprediction which made prediction by polymorphic indirect predictor." + }, + { + "EventCode": "0x01e4", + "EventName": "BR_RGN_RECLAIM", + "PublicDescription": "This event counts the Indirect predictor entries flushed by region reclamation." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/brbe.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/brbe.json new file mode 100644 index 000000000000..9c315b2d7046 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/brbe.json @@ -0,0 +1,6 @@ +[ + { + "ArchStdEvent": "BRB_FILTRATE", + "PublicDescription": "This event counts each valid branch record captured in the branch record buffer. Branch records that are not captured because they are removed by filtering are not counted." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/bus.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/bus.json new file mode 100644 index 000000000000..5bb8de617c68 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/bus.json @@ -0,0 +1,48 @@ +[ + { + "ArchStdEvent": "BUS_ACCESS", + "PublicDescription": "This event counts the number of data-beat accesses between the CPU and the external bus. This count includes accesses due to read, write, and snoop. Each beat of data is counted individually." + }, + { + "ArchStdEvent": "BUS_CYCLES", + "PublicDescription": "This event counts bus cycles in the CPU. Bus cycles represent a clock cycle in which a transaction could be sent or received on the interface from the CPU to the external bus. Since that interface is driven at the same clock speed as the CPU, this event increments at the rate of CPU clock. Regardless of the WFE/WFI state of the PE, this event increments on each processor clock." + }, + { + "ArchStdEvent": "BUS_ACCESS_RD", + "PublicDescription": "This event counts memory Read transactions seen on the external bus. Each beat of data is counted individually." + }, + { + "ArchStdEvent": "BUS_ACCESS_WR", + "PublicDescription": "This event counts memory Write transactions seen on the external bus. Each beat of data is counted individually." + }, + { + "EventCode": "0x0154", + "EventName": "BUS_REQUEST_REQ", + "PublicDescription": "Bus request, request." + }, + { + "EventCode": "0x0155", + "EventName": "BUS_REQUEST_RETRY", + "PublicDescription": "Bus request, retry." + }, + { + "EventCode": "0x0198", + "EventName": "L2_CHI_CBUSY0", + "PublicDescription": "Number of RXDAT or RXRSP response received width CBusy of 0." + }, + { + "EventCode": "0x0199", + "EventName": "L2_CHI_CBUSY1", + "PublicDescription": "Number of RXDAT or RXRSP response received width CBusy of 1." + }, + { + "EventCode": "0x019a", + "EventName": "L2_CHI_CBUSY2", + "PublicDescription": "Number of RXDAT or RXRSP response received width CBusy of 2." + }, + { + "EventCode": "0x019b", + "EventName": "L2_CHI_CBUSY3", + "PublicDescription": "Number of RXDAT or RXRSP response received width CBusy of 3." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/exception.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/exception.json new file mode 100644 index 000000000000..ecd996c3610b --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/exception.json @@ -0,0 +1,62 @@ +[ + { + "ArchStdEvent": "EXC_TAKEN", + "PublicDescription": "This event counts any taken architecturally visible exceptions such as IRQ, FIQ, SError, and other synchronous exceptions. Exceptions are counted whether or not they are taken locally." + }, + { + "ArchStdEvent": "EXC_RETURN", + "PublicDescription": "This event counts any architecturally executed exception return instructions. For example: AArch64: ERET." + }, + { + "ArchStdEvent": "EXC_UNDEF", + "PublicDescription": "This event counts the number of synchronous exceptions which are taken locally that are due to attempting to execute an instruction that is UNDEFINED.\nAttempting to execute instruction bit patterns that have not been allocated.\nAttempting to execute instructions when they are disabled.\nAttempting to execute instructions at an inappropriate Exception level.\nAttempting to execute an instruction when the value of PSTATE.IL is 1." + }, + { + "ArchStdEvent": "EXC_SVC", + "PublicDescription": "This event counts SVC exceptions taken locally." + }, + { + "ArchStdEvent": "EXC_PABORT", + "PublicDescription": "This event counts synchronous exceptions that are taken locally and caused by Instruction Aborts." + }, + { + "ArchStdEvent": "EXC_DABORT", + "PublicDescription": "This event counts exceptions that are taken locally and are caused by data aborts or SErrors. Conditions that could cause those exceptions are attempting to read or write memory where the MMU generates a fault, attempting to read or write memory with a misaligned address, Interrupts from the nSEI inputs and internally generated SErrors." + }, + { + "ArchStdEvent": "EXC_IRQ", + "PublicDescription": "This event counts IRQ exceptions including the virtual IRQs that are taken locally." + }, + { + "ArchStdEvent": "EXC_FIQ", + "PublicDescription": "This event counts FIQ exceptions including the virtual FIQs that are taken locally." + }, + { + "ArchStdEvent": "EXC_SMC", + "PublicDescription": "This event counts SMC exceptions taken to EL3." + }, + { + "ArchStdEvent": "EXC_HVC", + "PublicDescription": "This event counts HVC exceptions taken to EL2." + }, + { + "ArchStdEvent": "EXC_TRAP_PABORT", + "PublicDescription": "This event counts exceptions which are traps not taken locally and are caused by Instruction Aborts. For example, attempting to execute an instruction with a misaligned PC." + }, + { + "ArchStdEvent": "EXC_TRAP_DABORT", + "PublicDescription": "This event counts exceptions which are traps not taken locally and are caused by Data Aborts or SError Interrupts. Conditions that could cause those exceptions are:\n* Attempting to read or write memory where the MMU generates a fault,\n* Attempting to read or write memory with a misaligned address,\n* Interrupts from the SEI input,\n* Internally generated SErrors." + }, + { + "ArchStdEvent": "EXC_TRAP_OTHER", + "PublicDescription": "This event counts the number of synchronous trap exceptions which are not taken locally and are not SVC, SMC, HVC, Data Aborts, Instruction Aborts, or Interrupts." + }, + { + "ArchStdEvent": "EXC_TRAP_IRQ", + "PublicDescription": "This event counts IRQ exceptions including the virtual IRQs that are not taken locally." + }, + { + "ArchStdEvent": "EXC_TRAP_FIQ", + "PublicDescription": "This event counts FIQs which are not taken locally but taken from EL0, EL1, or EL2 to EL3 (which would be the normal behavior for FIQs when not executing in EL3)." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/fp_operation.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/fp_operation.json new file mode 100644 index 000000000000..3588e130781d --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/fp_operation.json @@ -0,0 +1,78 @@ +[ + { + "ArchStdEvent": "FP_HP_SPEC", + "PublicDescription": "This event counts speculatively executed half precision floating point operations." + }, + { + "ArchStdEvent": "FP_SP_SPEC", + "PublicDescription": "This event counts speculatively executed single precision floating point operations." + }, + { + "ArchStdEvent": "FP_DP_SPEC", + "PublicDescription": "This event counts speculatively executed double precision floating point operations." + }, + { + "ArchStdEvent": "FP_SCALE_OPS_SPEC", + "PublicDescription": "This event counts speculatively executed scalable single precision floating point operations." + }, + { + "ArchStdEvent": "FP_FIXED_OPS_SPEC", + "PublicDescription": "This event counts speculatively executed non-scalable single precision floating point operations." + }, + { + "ArchStdEvent": "FP_HP_SCALE_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the largest type was half-precision floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the counter to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_HP_FIXED_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the largest type was half-precision floating-point, where v is the number of arithmetic operations carried out by the operation or which instruction causes the event to increment.\nThis event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_SP_SCALE_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the largest type was single-precision floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_SP_FIXED_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the largest type was single-precision floating-point, where v is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_DP_SCALE_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the largest type was double-precision floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_DP_FIXED_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the largest type was double-precision floating-point, where v is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_SP_FIXED_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the smallest type was single-precision floating-point, where v is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_HP_FIXED_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the smallest type was half-precision floating-point, where v is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_BF16_FIXED_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the smallest type was BFloat16 floating-point. Where v is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment. This event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_FP8_FIXED_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed non-scalable element arithmetic operation, due to an instruction where the smallest type was 8-bit floating-point, where v is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_SCALE_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_SP_SCALE_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the smallest type was single-precision floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_HP_SCALE_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the smallest type was half-precision floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_BF16_SCALE_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the smallest type was BFloat16 floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + }, + { + "ArchStdEvent": "FP_FP8_SCALE_MIN_OPS_SPEC", + "PublicDescription": "This event increments by v for each speculatively executed scalable element arithmetic operation, due to an instruction where the smallest type was 8-bit floating-point, where v is a value such that (v*(VL/128)) is the number of arithmetic operations carried out by the operation or instruction which causes the event to increment.\nThis event does not count operations that are counted by FP_FIXED_OPS_SPEC or FP_SCALE2_OPS_SPEC." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/general.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/general.json new file mode 100644 index 000000000000..bd9c248387aa --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/general.json @@ -0,0 +1,15 @@ +[ + { + "ArchStdEvent": "CPU_CYCLES", + "PublicDescription": "This event counts CPU clock cycles when the PE is not in WFE/WFI. The clock measured by this event is defined as the physical clock driving the CPU logic." + }, + { + "ArchStdEvent": "CNT_CYCLES", + "PublicDescription": "This event increments at a constant frequency equal to the rate of increment of the System Counter, CNTPCT_EL0.\nThis event does not increment when the PE is in WFE/WFI." + }, + { + "EventCode": "0x01e1", + "EventName": "CPU_SLOT", + "PublicDescription": "Entitled CPU slots.\nThis event counts the number of slots. When in ST mode, this event shall increment by PMMIR_EL1.SLOTS quantities, and when in SMT partitioned resource mode (regardless of in WFI state or otherwise), this event is incremented by PMMIR_EL1.SLOTS/2 quantities." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/l1d_cache.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/l1d_cache.json new file mode 100644 index 000000000000..ed6f764eff24 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/l1d_cache.json @@ -0,0 +1,122 @@ +[ + { + "ArchStdEvent": "L1D_CACHE_REFILL", + "PublicDescription": "This event counts L1 D-cache refills caused by speculatively executed load or store operations, preload instructions, or hardware cache prefetching that missed in the L1 D-cache. This event only counts one event per cache line.\nSince the caches are Write-back only for this processor, there are no Write-through cache accesses." + }, + { + "ArchStdEvent": "L1D_CACHE", + "PublicDescription": "This event counts L1 D-cache accesses from any load/store operations, software preload, or hardware prefetch operations. Atomic operations that resolve in the CPU's caches (near atomic operations) count as both a write access and read access. Each access to a cache line is counted including the multiple accesses caused by single instructions such as LDM or STM. Each access to other L1 data or unified memory structures, for example refill buffers, write buffers, and write-back buffers, are also counted.\nThis event counts the sum of the following events:\nL1D_CACHE_RD,\nL1D_CACHE_WR,\nL1D_CACHE_PRFM, and\nL1D_CACHE_HWPRF." + }, + { + "ArchStdEvent": "L1D_CACHE_WB", + "PublicDescription": "This event counts write-backs of dirty data from the L1 D-cache to the L2 cache. This occurs when either a dirty cache line is evicted from L1 D-cache and allocated in the L2 cache or dirty data is written to the L2 and possibly to the next level of cache. This event counts both victim cache line evictions and cache write-backs from snoops or cache maintenance operations. The following cache operations are not counted:\n* Invalidations which do not result in data being transferred out of the L1 (such as evictions of clean data),\n* Full line writes which write to L2 without writing L1, such as write streaming mode.\nThis event is the sum of the following events:\nL1D_CACHE_WB_CLEAN and\nL1D_CACHE_WB_VICTIM." + }, + { + "ArchStdEvent": "L1D_CACHE_LMISS_RD", + "PublicDescription": "This event counts cache line refills into the L1 D-cache from any memory Read operations, that incurred additional latency.\nCounts same as L1D_CACHE_REFILL_RD on this CPU." + }, + { + "ArchStdEvent": "L1D_CACHE_RD", + "PublicDescription": "This event counts L1 D-cache accesses from any Load operation. Atomic Load operations that resolve in the CPU's caches count as both a write access and read access." + }, + { + "ArchStdEvent": "L1D_CACHE_WR", + "PublicDescription": "This event counts L1 D-cache accesses generated by Store operations. This event also counts accesses caused by a DC ZVA (D-cache zero, specified by virtual address) instruction. Near atomic operations that resolve in the CPU's caches count as a write access and read access.\nThis event is a subset of the L1D_CACHE event, except this event only counts memory Write operations." + }, + { + "ArchStdEvent": "L1D_CACHE_REFILL_RD", + "PublicDescription": "This event counts L1 D-cache refills caused by speculatively executed Load instructions where the memory Read operation misses in the L1 D-cache. This event only counts one event per cache line.\nThis event is a subset of the L1D_CACHE_REFILL event, but only counts memory Read operations. This event does not count reads caused by cache maintenance operations or preload instructions." + }, + { + "ArchStdEvent": "L1D_CACHE_REFILL_WR", + "PublicDescription": "This event counts L1 D-cache refills caused by speculatively executed Store instructions where the memory Write operation misses in the L1 D-cache. This event only counts one event per cache line.\nThis event is a subset of the L1D_CACHE_REFILL event, but only counts memory Write operations." + }, + { + "ArchStdEvent": "L1D_CACHE_REFILL_INNER", + "PublicDescription": "This event counts L1 D-cache refills (L1D_CACHE_REFILL) where the cache line data came from caches inside the immediate Cluster of the Core (L2 cache)." + }, + { + "ArchStdEvent": "L1D_CACHE_REFILL_OUTER", + "PublicDescription": "This event counts L1 D-cache refills (L1D_CACHE_REFILL) for which the cache line data came from outside the immediate Cluster of the Core, like an SLC in the system interconnect or DRAM or remote socket." + }, + { + "ArchStdEvent": "L1D_CACHE_WB_VICTIM", + "PublicDescription": "This event counts dirty cache line evictions from the L1 D-cache caused by a new cache line allocation. This event does not count evictions caused by cache maintenance operations.\nThis event is a subset of the L1D_CACHE_WB event, but only counts write-backs that are a result of the line being allocated for an access made by the CPU." + }, + { + "ArchStdEvent": "L1D_CACHE_WB_CLEAN", + "PublicDescription": "This event counts write-backs from the L1 D-cache that are a result of a coherency operation made by another CPU. Event counts include cache maintenance operations.\nThis event is a subset of the L1D_CACHE_WB event." + }, + { + "ArchStdEvent": "L1D_CACHE_INVAL", + "PublicDescription": "This event counts each explicit invalidation of a cache line in the L1 D-cache caused by:\n* Cache Maintenance Operations (CMO) that operate by a virtual address.\n* Broadcast cache coherency operations from another CPU in the system.\nThis event does not count for the following conditions:\n* A cache refill invalidates a cache line.\n* A CMO which is executed on that CPU and invalidates a cache line specified by Set/Way.\nNote that CMOs that operate by Set/Way cannot be broadcast from one CPU to another." + }, + { + "ArchStdEvent": "L1D_CACHE_RW", + "PublicDescription": "This event counts L1 data demand cache accesses from any Load or Store operation. Near atomic operations that resolve in the CPU's caches count as both a write access and read access.\nThis event is implemented as L1D_CACHE_RD + L1D_CACHE_WR" + }, + { + "ArchStdEvent": "L1D_CACHE_PRFM", + "PublicDescription": "This event counts L1 D-cache accesses from software preload or prefetch instructions." + }, + { + "ArchStdEvent": "L1D_CACHE_MISS", + "PublicDescription": "This event counts each demand access counted by L1D_CACHE_RW that misses in the L1 Data or unified cache, causing an access to outside of the L1 caches of this PE." + }, + { + "ArchStdEvent": "L1D_CACHE_REFILL_PRFM", + "PublicDescription": "This event counts L1 D-cache refills where the cache line access was generated by software preload or prefetch instructions." + }, + { + "ArchStdEvent": "L1D_CACHE_HWPRF", + "PublicDescription": "This event counts L1 D-cache accesses from any Load/Store operations generated by the hardware prefetcher." + }, + { + "ArchStdEvent": "L1D_CACHE_REFILL_HWPRF", + "PublicDescription": "This event counts each hardware prefetch access counted by L1D_CACHE_HWPRF that causes a refill of the L1 D-cache from outside of the L1 D-cache." + }, + { + "ArchStdEvent": "L1D_CACHE_HIT_RW_FPRFM", + "PublicDescription": "This event counts each demand access first hit counted by L1D_CACHE_HIT_RW_FPRF where the cache line was fetched in response to a prefetch instruction. That is, the L1D_CACHE_REFILL_PRFM event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "ArchStdEvent": "L1D_CACHE_HIT_RW_FHWPRF", + "PublicDescription": "This event counts each demand access first hit counted by L1D_CACHE_HIT_RW_FPRF where the cache line was fetched by a hardware prefetcher. That is, the L1D_CACHE_REFILL_HWPRF Event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "ArchStdEvent": "L1D_CACHE_HIT_RW_FPRF", + "PublicDescription": "This event counts each demand access first hit counted by L1D_CACHE_HIT_RW where the cache line was fetched in response to a prefetch instruction or by a hardware prefetcher. That is, the L1D_CACHE_REFILL_PRF event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "ArchStdEvent": "L1D_LFB_HIT_RW_FPRFM", + "PublicDescription": "This event counts each demand access line-fill buffer first hit counted by L1D_LFB_HIT_RW_FPRF where the cache line was fetched in response to a prefetch instruction. That is, the access hits a cache line that is in the process of being loaded into the L1 D-cache, and so does not generate a new refill, but has to wait for the previous refill to complete, and the L1D_CACHE_REFILL_PRFM event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "ArchStdEvent": "L1D_LFB_HIT_RW_FHWPRF", + "PublicDescription": "This event counts each demand access line-fill buffer first hit counted by L1D_LFB_HIT_RW_FPRF, where the cache line was fetched by a hardware prefetcher. That is, the access hits a cache line that is in the process of being loaded into the L1 D-cache, and so does not generate a new refill, but has to wait for the previous refill to complete, and the L1D_CACHE_REFILL_HWPRF Event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "ArchStdEvent": "L1D_LFB_HIT_RW_FPRF", + "PublicDescription": "This event counts each demand access line-fill buffer first hit counted by L1D_LFB_HIT_RW where the cache line was fetched in response to a prefetch instruction or by a hardware prefetcher. That is, the access hits a cache line that is in the process of being loaded into the L1 D-cache, and so does not generate a new refill, but has to wait for the previous refill to complete, and the L1D_CACHE_REFILL_PRF event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "EventCode": "0x01f5", + "EventName": "L1D_CACHE_REFILL_RW", + "PublicDescription": "L1 D-cache refill, demand Read and Write. This event counts demand Read and Write accesses that causes a refill of the L1 D-cache of this PE, from outside of this cache." + }, + { + "EventCode": "0x0204", + "EventName": "L1D_CACHE_REFILL_OUTER_LLC", + "PublicDescription": "This event counts L1D_CACHE_REFILL from L3 D-cache." + }, + { + "EventCode": "0x0205", + "EventName": "L1D_CACHE_REFILL_OUTER_DRAM", + "PublicDescription": "This event counts L1D_CACHE_REFILL from local memory." + }, + { + "EventCode": "0x0206", + "EventName": "L1D_CACHE_REFILL_OUTER_REMOTE", + "PublicDescription": "This event counts L1D_CACHE_REFILL from a remote memory." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/l1i_cache.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/l1i_cache.json new file mode 100644 index 000000000000..952454004d98 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/l1i_cache.json @@ -0,0 +1,114 @@ +[ + { + "ArchStdEvent": "L1I_CACHE_REFILL", + "PublicDescription": "This event counts cache line refills in the L1 I-cache caused by a missed instruction fetch (demand, hardware prefetch, and software preload accesses). Instruction fetches may include accessing multiple instructions, but the single cache line allocation is counted once." + }, + { + "ArchStdEvent": "L1I_CACHE", + "PublicDescription": "This event counts instruction fetches (demand, hardware prefetch, and software preload accesses) which access the L1 Instruction Cache. Instruction Cache accesses caused by cache maintenance operations are not counted." + }, + { + "ArchStdEvent": "L1I_CACHE_LMISS", + "PublicDescription": "This event counts cache line refills into the L1 I-cache, that incurred additional latency.\nCounts the same as L1I_CACHE_REFILL in this CPU." + }, + { + "ArchStdEvent": "L1I_CACHE_RD", + "PublicDescription": "This event counts demand instruction fetches which access the L1 I-cache." + }, + { + "ArchStdEvent": "L1I_CACHE_PRFM", + "PublicDescription": "This event counts instruction fetches generated by software preload or prefetch instructions which access the L1 I-cache." + }, + { + "ArchStdEvent": "L1I_CACHE_HWPRF", + "PublicDescription": "This event counts instruction fetches which access the L1 I-cache generated by the hardware prefetcher." + }, + { + "ArchStdEvent": "L1I_CACHE_REFILL_PRFM", + "PublicDescription": "This event counts cache line refills in the L1 I-cache caused by a missed instruction fetch generated by software preload or prefetch instructions. Instruction fetches may include accessing multiple instructions, but the single cache line allocation is counted once." + }, + { + "ArchStdEvent": "L1I_CACHE_REFILL_HWPRF", + "PublicDescription": "This event counts each hardware prefetch access counted by L1I_CACHE_HWPRF that causes a refill of the Level 1I-cache from outside of the L1 I-cache." + }, + { + "ArchStdEvent": "L1I_CACHE_HIT_RD", + "PublicDescription": "This event counts demand instruction fetches that access the L1 I-cache and hit in the L1 I-cache." + }, + { + "ArchStdEvent": "L1I_CACHE_HIT_RD_FPRF", + "PublicDescription": "This event counts each demand fetch first hit counted by L1I_CACHE_HIT_RD where the cache line was fetched in response to a software preload or by a hardware prefetcher. That is, the L1I_CACHE_REFILL_PRF event was generated when the cache line was fetched into the cache.\nOnly the first hit by a demand access is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "ArchStdEvent": "L1I_CACHE_HIT", + "PublicDescription": "This event counts instruction fetches that access the L1 I-cache (demand, hardware prefetch, and software preload accesses) and hit in the L1 I-cache. I-cache accesses caused by cache maintenance operations are not counted." + }, + { + "ArchStdEvent": "L1I_CACHE_HIT_PRFM", + "PublicDescription": "This event counts instruction fetches generated by software preload or prefetch instructions that access the L1 I-cache and hit in the L1 I-cache." + }, + { + "ArchStdEvent": "L1I_LFB_HIT_RD", + "PublicDescription": "This event counts demand instruction fetches that access the L1 I-cache and hit in a line that is in the process of being loaded into the L1 I-cache." + }, + { + "EventCode": "0x0174", + "EventName": "L1I_HWPRF_REQ_DROP", + "PublicDescription": "L1 I-cache hardware prefetch dropped." + }, + { + "EventCode": "0x01e3", + "EventName": "L1I_CACHE_REFILL_RD", + "PublicDescription": "L1 I-cache refill, Read.\nThis event counts demand instruction fetch that causes a refill of the L1 I-cache of this PE, from outside of this cache." + }, + { + "EventCode": "0x01ea", + "EventName": "L1I_CFC_ENTRIES", + "PublicDescription": "This event counts the CFC (Cache Fill Control) entries.\nThe CFC is the fill buffer for I-cache." + }, + { + "EventCode": "0x01ef", + "EventName": "L1I_CACHE_INVAL", + "PublicDescription": "L1 I-cache invalidate.\nThis event counts each explicit invalidation of a cache line in the L1 I-cache caused by:\n* Broadcast cache coherency operations from another CPU in the system.\n* Invalidation dues to capacity eviction in L2 D-cache.\nThis event does not count for the following conditions:\n* A cache refill invalidates a cache line.\n* A CMO which is executed on that CPU Core and invalidates a cache line specified by Set/Way.\n* Cache Maintenance Operations (CMO) that operate by a virtual address.\nNote that\n* CMOs that operate by Set/Way cannot be broadcast from one CPU Core to another.\n* The CMO is treated as No-op for the purposes of L1 I-cache line invalidation, as this Core implements fully coherent I-cache." + }, + { + "EventCode": "0x0212", + "EventName": "L1I_CACHE_HIT_HWPRF", + "PublicDescription": "This event counts each hardware prefetch access that hits an L1 I-cache." + }, + { + "EventCode": "0x0215", + "EventName": "L1I_LFB_HIT", + "PublicDescription": "L1 Line fill buffer hit.\nThis event counts each Demand or software preload or hardware prefetch induced instruction fetch that hits an L1 I-cache line that is in the process of being loaded into the L1 instruction cache, and so does not generate a new refill, but has to wait for the previous refill to complete." + }, + { + "EventCode": "0x0216", + "EventName": "L1I_LFB_HIT_PRFM", + "PublicDescription": "This event counts each software prefetch access that hits a cache line that is in the process of being loaded into the L1 instruction cache, and so does not generate a new refill, but has to wait for the previous refill to complete." + }, + { + "EventCode": "0x0219", + "EventName": "L1I_LFB_HIT_HWPRF", + "PublicDescription": "This event counts each hardware prefetch access that hits a cache line that is in the process of being loaded into the L1 instruction cache, and so does not generate a new refill, but has to wait for the previous refill to complete." + }, + { + "EventCode": "0x0221", + "EventName": "L1I_PRFM_REQ", + "PublicDescription": "L1 I-cache software prefetch requests." + }, + { + "EventCode": "0x0222", + "EventName": "L1I_HWPRF_REQ", + "PublicDescription": "L1 I-cache hardware prefetch requests." + }, + { + "EventCode": "0x0228", + "EventName": "L1I_CACHE_HIT_PRFM_FPRF", + "PublicDescription": "L1 I-cache software prefetch access first hit, fetched by hardware or software prefetch.\nThis event counts each software preload access first hit where the cache line was fetched in response to a hardware prefetcher or software preload instruction.\nOnly the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "EventCode": "0x022a", + "EventName": "L1I_CACHE_HIT_HWPRF_FPRF", + "PublicDescription": "L1 I-cache hardware prefetch access first hit, fetched by hardware or software prefetch.\nThis event counts each hardware prefetch access first hit where the cache line was fetched in response to a hardware or prefetch instruction.\nOnly the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/l2d_cache.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/l2d_cache.json new file mode 100644 index 000000000000..66f21a94381e --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/l2d_cache.json @@ -0,0 +1,134 @@ +[ + { + "ArchStdEvent": "L2D_CACHE", + "PublicDescription": "This event counts accesses to the L2 cache due to data accesses. L2 cache is a unified cache for data and instruction accesses. Accesses are for misses in the L1 D-cache or translation resolutions due to accesses. This event also counts write-back of dirty data from L1 D-cache to the L2 cache.\nI-cache accesses are included in this event. This event is the sum of the following events:\nL2D_CACHE_RD,\nL2D_CACHE_WR,\nL2D_CACHE_PRFM, and\nL2D_CACHE_HWPRF." + }, + { + "ArchStdEvent": "L2D_CACHE_REFILL", + "PublicDescription": "This event counts cache line refills into the L2 cache. L2 cache is a unified cache for data and instruction accesses. Accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nI-cache refills are included in this event. This event is the sum of the following events:\nL2D_CACHE_REFILL_RD,\nL2D_CACHE_REFILL_WR,\nL2D_CACHE_REFILL_HWPRF, and\nL2D_CACHE_REFILL_PRFM." + }, + { + "ArchStdEvent": "L2D_CACHE_WB", + "PublicDescription": "This event counts write-backs of data from the L2 cache to outside the CPU. This includes snoops to the L2 (from other CPUs) which return data even if the snoops cause an invalidation. L2 cache line invalidations which do not write data outside the CPU and snoops which return data from an L1 cache are not counted. Data would not be written outside the cache when invalidating a clean cache line.\nThis event is the sum of the following events:\nL2D_CACHE_WB_VICTIM and\nL2D_CACHE_WB_CLEAN." + }, + { + "ArchStdEvent": "L2D_CACHE_RD", + "PublicDescription": "This event counts L2 D-cache accesses due to memory Read operations. L2 cache is a unified cache for data and instruction accesses, accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nI-cache accesses are included in this event. This event is a subset of the L2D_CACHE event, but this event only counts memory Read operations." + }, + { + "ArchStdEvent": "L2D_CACHE_WR", + "PublicDescription": "This event counts L2 cache accesses due to memory Write operations. L2 cache is a unified cache for data and instruction accesses, accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nThis event is a subset of the L2D_CACHE event, but this event only counts memory Write operations." + }, + { + "ArchStdEvent": "L2D_CACHE_REFILL_RD", + "PublicDescription": "This event counts refills for memory accesses due to memory Read operation counted by L2D_CACHE_RD. L2 cache is a unified cache for data and instruction accesses, accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nThis CPU includes I-cache refills in this counter as an L2I equivalent event was not implemented. This event is a subset of the L2D_CACHE_REFILL event. This event does not count L2 refills caused by stashes into L2.\nThis count includes demand requests that encounter an L2 prefetch request or an L2 software prefetch request to the same cache line, which is still pending in the L2 LFB." + }, + { + "ArchStdEvent": "L2D_CACHE_REFILL_WR", + "PublicDescription": "This event counts refills for memory accesses due to memory Write operation counted by L2D_CACHE_WR. L2 cache is a unified cache for data and instruction accesses, accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nThis count includes demand requests that encounter an L2 prefetch request or an L2 software prefetch request to the same cache line, which is still pending in the L2 LFB." + }, + { + "ArchStdEvent": "L2D_CACHE_WB_VICTIM", + "PublicDescription": "This event counts evictions from the L2 cache because of a line being allocated into the L2 cache.\nThis event is a subset of the L2D_CACHE_WB event." + }, + { + "ArchStdEvent": "L2D_CACHE_WB_CLEAN", + "PublicDescription": "This event counts write-backs from the L2 cache that are a result of any of the following:\n* Cache maintenance operations,\n* Snoop responses, or\n* Direct cache transfers to another CPU due to a forwarding snoop request.\nThis event is a subset of the L2D_CACHE_WB event." + }, + { + "ArchStdEvent": "L2D_CACHE_INVAL", + "PublicDescription": "This event counts each explicit invalidation of a cache line in the L2 cache by cache maintenance operations that operate by a virtual address, or by external coherency operations. This event does not count if either:\n* A cache refill invalidates a cache line, or\n* A cache Maintenance Operation (CMO), which invalidates a cache line specified by Set/Way,\nis executed on that CPU.\nCMOs that operate by Set/Way cannot be broadcast from one CPU to another." + }, + { + "ArchStdEvent": "L2D_CACHE_LMISS_RD", + "PublicDescription": "This event counts cache line refills into the L2 unified cache from any memory Read operations that incurred additional latency.\nCounts the same as L2D_CACHE_REFILL_RD in this CPU" + }, + { + "ArchStdEvent": "L2D_CACHE_RW", + "PublicDescription": "This event counts L2 cache demand accesses from any Load/Store operations. L2 cache is a unified cache for data and instruction accesses, accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nI-cache accesses are included in this event.\nThis event is the sum of the following events:\nL2D_CACHE_RD and\nL2D_CACHE_WR." + }, + { + "ArchStdEvent": "L2D_CACHE_PRFM", + "PublicDescription": "This event counts L2 D-cache accesses generated by software preload or prefetch instructions with target = L1/L2/L3 cache.\nNote that a software preload or prefetch instructions with (target = L1/L2/L3) that hits in L1D will not result in an L2 D-cache access. Therefore, such a software preload or prefetch instructions will not be counted by this event." + }, + { + "ArchStdEvent": "L2D_CACHE_MISS", + "PublicDescription": "This event counts cache line misses in the L2 cache. L2 cache is a unified cache for data and instruction accesses. Accesses are for misses in the L1 D-cache or translation resolutions due to accesses.\nThis event counts the same as L2D_CACHE_REFILL_RD in this CPU." + }, + { + "ArchStdEvent": "L2D_CACHE_REFILL_PRFM", + "PublicDescription": "This event counts refills due to accesses generated as a result of software preload or prefetch instructions as counted by L2D_CACHE_PRFM. I-cache refills are included in this event." + }, + { + "ArchStdEvent": "L2D_CACHE_HWPRF", + "PublicDescription": "This event counts the L2 D-cache access caused by L1 or L2 hardware prefetcher." + }, + { + "ArchStdEvent": "L2D_CACHE_REFILL_HWPRF", + "PublicDescription": "This event counts each hardware prefetch access counted by L2D_CACHE_HWPRF that causes a refill of the L2 cache, or any L1 Data, or Instruction cache of this PE, from outside of those caches.\nThis does not include prefetch requests pending waiting for a refill in LFB and a new demand request to the same cache line hitting the LFB entry. All such refills are counted as L2D_LFB_HIT_RWL1PRF_FHWPRF." + }, + { + "ArchStdEvent": "L2D_CACHE_REFILL_PRF", + "PublicDescription": "This event counts each access to L2 Cache due to a prefetch instruction, or hardware prefetch that causes a refill of the L2 or any Level 1, from outside of those caches." + }, + { + "EventCode": "0x0108", + "EventName": "L2D_CACHE_IF_REFILL", + "PublicDescription": "L2 D-cache refill, instruction fetch.\nThis event counts demand instruction fetch that causes a refill of the L2 cache or L1 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x0109", + "EventName": "L2D_CACHE_TBW_REFILL", + "PublicDescription": "L2 D-cache refill, Page table walk.\nThis event counts demand translation table walk that causes a refill of the L2 cache or L1 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x010a", + "EventName": "L2D_CACHE_PF_REFILL", + "PublicDescription": "L2 D-cache refill, prefetch.\nThis event counts L1 or L2 hardware or software prefetch accesses that causes a refill of the L2 cache or L1 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x010b", + "EventName": "L2D_LFB_HIT_RWL1PRF_FHWPRF", + "PublicDescription": "L2 line fill buffer demand Read, demand Write or L1 prefetch first hit, fetched by hardware prefetch.\nThis event counts each of the following access that hit the line-fill buffer when the same cache line is already being fetched due to an L2 hardware prefetcher.\n* Demand Read or Write\n* L1I-HWPRF\n* L1D-HWPRF\n* L1I PRFM\n* L1D PRFM\nThese accesses hit a cache line that is currently being loaded into the L2 cache as a result of a hardware prefetcher to the same line. Consequently, this access does not initiate a new refill but waits for the completion of the previous refill.\nOnly the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "EventCode": "0x0179", + "EventName": "L2D_CACHE_HIT_RWL1PRF_FHWPRF", + "PublicDescription": "L2 D-cache demand Read, demand Write and L1 prefetch hit, fetched by hardware prefetch. This event counts each demand Read, demand Write and L1 hardware or software prefetch request that hit an L2 D-cache line that was refilled into L2 D-cache in response to an L2 hardware prefetch. Only the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "EventCode": "0x01b8", + "EventName": "L2D_CACHE_L1PRF", + "PublicDescription": "L2 D-cache access, L1 hardware or software prefetch. This event counts L1 Hardware or software prefetch access to L2 D-cache." + }, + { + "EventCode": "0x01b9", + "EventName": "L2D_CACHE_REFILL_L1PRF", + "PublicDescription": "L2 D-cache refill, L1 hardware or software prefetch.\nThis event counts each access counted by L2D_CACHE_L1PRF that causes a refill of the L2 cache or any L1 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x0201", + "EventName": "L2D_CACHE_BACKSNOOP_L1D_VIRT_ALIASING", + "PublicDescription": "This event counts when the L2 D-cache sends an invalidating back-snoop to the L1 D for an access initiated by the L1 D, where the corresponding line is already present in the L1 D-cache.\nThe L2 D-cache line tags the PE that refilled the line. It also retains specific bits of the VA to identify virtually aliased addresses.\nThe L1 D request requiring a back-snoop can originate either from the same PE that refilled the L2 D line or from a different PE. In either case, this event only counts those back snoop where the requested VA mismatch the VA stored in the L2 D tag.\nThis event is counted only by PE that initiated the original request necessitating a back-snoop.\nNote : The L1 D is VIPT, it identifies this access as a miss. Conversely, as L2 is PIPT, it identifies this as a hit. L2 D utilizes the back-snoop mechanism to refill L1 D with the snooped data." + }, + { + "EventCode": "0x0208", + "EventName": "L2D_CACHE_RWL1PRF", + "PublicDescription": "L2 D-cache access, demand Read, demand Write or L1 hardware or software prefetch.\nThis event counts each access to L2 D-cache due to the following:\n* Demand Read or Write.\n* L1 Hardware or software prefetch." + }, + { + "EventCode": "0x020a", + "EventName": "L2D_CACHE_REFILL_RWL1PRF", + "PublicDescription": "L2 D-cache refill, demand Read, demand Write or L1 hardware or software prefetch.\nThis event counts each access counted by L2D_CACHE_RWL1PRF that causes a refill of the L2 cache, or any L1 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x020c", + "EventName": "L2D_CACHE_HIT_RWL1PRF_FPRFM", + "PublicDescription": "L2 D-cache demand Read, demand Write and L1 prefetch hit, fetched by software prefetch.\nThis event counts each demand Read, demand Write and L1 hardware or software prefetch request that hit an L2 D-cache line that was refilled into L2 D-cache in response to an L2 software prefetch. Only the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "EventCode": "0x020e", + "EventName": "L2D_CACHE_HIT_RWL1PRF_FPRF", + "PublicDescription": "L2 D-cache demand Read, demand Write and L1 prefetch hit, fetched by software or hardware prefetch.\nThis event counts each demand Read, demand Write and L1 hardware or software prefetch request that hit an L2 D-cache line that was refilled into L2 D-cache in response to an L2 hardware prefetch or software prefetch. Only the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/ll_cache.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/ll_cache.json new file mode 100644 index 000000000000..851d0a70de9c --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/ll_cache.json @@ -0,0 +1,107 @@ +[ + { + "ArchStdEvent": "L3D_CACHE_ALLOCATE", + "PublicDescription": "This event counts each memory Write operation that writes an entire line into the L3 data without fetching data from outside the L3 Data. These are allocations of cache lines in the L3 Data that are not refills counted by\nL3D_CACHE_REFILL. For example:\nA Write-back of an entire cache line from an L2 cache to the L3 D-cache.\n* A Write of an entire cache line from a coalescing Write buffer.\n* An operation such as DC ZVA.\nThis counter does not count writes that write an entire line to beyond level 3. Thus this counter does not count the streaming writes to beyond L3 cache." + }, + { + "ArchStdEvent": "L3D_CACHE_REFILL", + "PublicDescription": "This event counts each access counted by L3D_CACHE that causes a refill of the L3 Data, or any L1 Data, instruction or L2 cache of this PE, from outside of those caches. This includes the refill due to hardware prefetch and software prefetch accesses.\nThis event is a sum of L3D_CACHE_MISS, L3D_CACHE_REFILL_PRFM and L3D_CACHE_REFILL_HWPRF event.\nA refill includes any access that causes data to be fetched from outside of the L1 to L3 caches, even if the data is ultimately not allocated into the L3 D-cache." + }, + { + "ArchStdEvent": "L3D_CACHE", + "PublicDescription": "This event counts each memory Read operation or memory Write operation that causes a cache access to the Level 3.\nThis event is a sum of the following Events:\n* L3D_CACHE_RD(0x00a0)\n* L3D_CACHE_ALLOCATE(0x0029)\n* L3D_CACHE_PRFM(0x8151)\n* L3D_CACHE_HWPRF(0x8156)\n* L2D_CACHE_WB(0x0018)" + }, + { + "ArchStdEvent": "LL_CACHE_RD", + "PublicDescription": "This is an alias to the event L3D_CACHE_RD (0x00a0)." + }, + { + "ArchStdEvent": "LL_CACHE_MISS_RD", + "PublicDescription": "This is an alias to the event L3D_CACHE_REFILL_RD (0x00a2)." + }, + { + "ArchStdEvent": "L3D_CACHE_RD", + "PublicDescription": "This event counts each Memory Read operation to L3 D-cache from instruction fetch, Load/Store, and MMU translation table accesses. This does not include hardware prefetcher or PRFM instruction accesses. This include L1 and L2 prefetcher accesses to L3 D-cache." + }, + { + "ArchStdEvent": "L3D_CACHE_REFILL_RD", + "PublicDescription": "This event counts each access counted by both L3D_CACHE_RD and L3D_CACHE_REFILL. That is, every refill of the L3 cache counted by L3D_CACHE_REFILL that is caused by a Memory Read operation.\nThe L3D_CACHE_MISS(0x8152), L3D_CACHE_REFILL_RD (0x00a2) and L3D_CACHE_LMISS_RD(0x400b) count the same event in the hardware." + }, + { + "ArchStdEvent": "L3D_CACHE_LMISS_RD", + "PublicDescription": "This event counts each memory Read operation to the L3 cache counted by L3D_CACHE that incurs additional latency because it returns data from outside of the L1 to L3 caches.\nThe L3D_CACHE_MISS(0x8152), L3D_CACHE_REFILL_RD (0x00a2) and L3D_CACHE_LMISS_RD(0x400b) count the same event in the hardware." + }, + { + "ArchStdEvent": "L3D_CACHE_RW", + "PublicDescription": "This event counts each access counted by L3D_CACHE that is due to a demand memory Read operation or demand memory Write operation.\nThis event is a sum of L3D_CACHE_RD(0x00a0), L3D_CACHE_ALLOCATE(0x0029) and L2D_CACHE_WB(0x0018).\nNote that this counter does not count that writes an entire line to beyond level 3. Thus this counter does not count the streaming Writes to beyond L3 cache." + }, + { + "ArchStdEvent": "L3D_CACHE_PRFM", + "PublicDescription": "This event counts each access counted by L3D_CACHE that is due to a prefetch instruction. This includes L3 Data accesses due to the L1, L2, or L3 prefetch instruction." + }, + { + "ArchStdEvent": "L3D_CACHE_MISS", + "PublicDescription": "This event counts each demand Read access counted by L3D_CACHE_RD that misses in the L1 to L3 Data, causing an access to outside of the L3 cache.\nThe L3D_CACHE_MISS(0x8152), L3D_CACHE_REFILL_RD (0x00a2) and L3D_CACHE_LMISS_RD(0x400b) count the same event in the hardware." + }, + { + "ArchStdEvent": "L3D_CACHE_REFILL_PRFM", + "PublicDescription": "This event counts each access counted by L3D_CACHE_PRFM that causes a refill of the L3 cache, or any L1 or L2 Data, from outside of those caches." + }, + { + "ArchStdEvent": "L3D_CACHE_HWPRF", + "PublicDescription": "This event counts each access to L3 cache that is due to a hardware prefetcher. This includes L3D accesses due to the Level-1 or Level-2 or Level-3 hardware prefetcher." + }, + { + "ArchStdEvent": "L3D_CACHE_REFILL_HWPRF", + "PublicDescription": "This event counts each hardware prefetch counted by L3D_CACHE_HWPRF that causes a refill of the L3 Data or unified cache, or any L1 or L2 Data, Instruction, or unified cache of this PE, from outside of those caches." + }, + { + "ArchStdEvent": "L3D_CACHE_REFILL_PRF", + "PublicDescription": "This event counts each access to L3 cache due to a prefetch instruction, or hardware prefetch that causes a refill of the L3 Data, or any L1 or L2 Data, from outside of those caches." + }, + { + "EventCode": "0x01e8", + "EventName": "L3D_CACHE_RWL1PRFL2PRF", + "PublicDescription": "L3 cache access, demand Read, demand Write, L1 hardware or software prefetch or L2 hardware or software prefetch.\nThis event counts each access to L3 D-cache due to the following:\n* Demand Read or Write.\n* L1 Hardware or software prefetch.\n* L2 Hardware or software prefetch." + }, + { + "EventCode": "0x01e9", + "EventName": "L3D_CACHE_REFILL_RWL1PRFL2PRF", + "PublicDescription": "L3 cache refill, demand Read, demand Write, L1 hardware or software prefetch or L2 hardware or software prefetch.\nThis event counts each access counted by L3D_CACHE_RWL1PRFL2PRF that causes a refill of the L3 cache, or any L1 or L2 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x01f6", + "EventName": "L3D_CACHE_REFILL_L2PRF", + "PublicDescription": "This event counts each access counted by L3D_CACHE_L2PRF that causes a refill of the L3 cache, or any L1 or L2 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x01f7", + "EventName": "L3D_CACHE_HIT_RWL1PRFL2PRF_FPRF", + "PublicDescription": "L3 cache demand Read, demand Write, L1 prefetch L2 prefetch first hit, fetched by software or hardware prefetch.\nThis event counts each demand Read, demand Write, L1 hardware or software prefetch request and L2 hardware or software prefetch that hit an L3 D-cache line that was refilled into L3 D-cache in response to an L3 hardware prefetch or software prefetch. Only the first hit is counted. After this event is generated for a cache line, the event is not generated again for the same cache line while it remains in the cache." + }, + { + "EventCode": "0x0225", + "EventName": "L3D_CACHE_REFILL_IF", + "PublicDescription": "L3 cache refill, instruction fetch.\nThis event counts demand instruction fetch that causes a refill of the L3 cache, or any L1 or L2 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x0226", + "EventName": "L3D_CACHE_REFILL_MM", + "PublicDescription": "L3 cache refill, translation table walk access.\nThis event counts demand translation table access that causes a refill of the L3 cache, or any L1 or L2 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x0227", + "EventName": "L3D_CACHE_REFILL_L1PRF", + "PublicDescription": "This event counts each access counted by L3D_CACHE_L1PRF that causes a refill of the L3 cache, or any L1 or L2 cache of this PE, from outside of those caches." + }, + { + "EventCode": "0x022c", + "EventName": "L3D_CACHE_L1PRF", + "PublicDescription": "This event counts the L3 D-cache access due to L1 hardware prefetch or software prefetch request.\nThe L1 hardware prefetch or software prefetch requests that miss the L1I, L1D and L2 D-cache are counted by this counter" + }, + { + "EventCode": "0x022d", + "EventName": "L3D_CACHE_L2PRF", + "PublicDescription": "This event counts the L3 D-cache access due to L2 hardware prefetch or software prefetch request.\nThe L2 hardware prefetch or software prefetch requests that miss the L2 D-cache are counted by this counter" + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/memory.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/memory.json new file mode 100644 index 000000000000..becd2d90bf39 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/memory.json @@ -0,0 +1,46 @@ +[ + { + "ArchStdEvent": "MEM_ACCESS", + "PublicDescription": "This event counts memory accesses issued by the CPU load/store unit, where those accesses are issued due to load or store operations. This event counts memory accesses regardless of whether the data is received from any level of cache hierarchy or external memory. If memory accesses are broken up into smaller transactions than what were specified in the load or store instructions, then the event counts those smaller memory transactions.\nMemory accesses generated by the following instructions or activity are not counted: instruction fetches, cache maintenance instructions, translation table walks or prefetches, memory prefetch operations. This event counts the sum of the following events:\nMEM_ACCESS_RD and\nMEM_ACCESS_WR." + }, + { + "ArchStdEvent": "MEMORY_ERROR", + "PublicDescription": "This event counts any detected correctable or uncorrectable physical memory errors (ECC or parity) in protected CPU RAMs. On the Core, this event counts errors in the caches (including data and tag RAMs). Any detected memory error (from either a speculative and abandoned access, or an architecturally executed access) is counted.\nNote that errors are only detected when the actual protected memory is accessed by an operation." + }, + { + "ArchStdEvent": "REMOTE_ACCESS", + "PublicDescription": "This event counts each external bus read access that causes an access to a remote device. That is, a socket that does not contain the PE." + }, + { + "ArchStdEvent": "MEM_ACCESS_RD", + "PublicDescription": "This event counts memory accesses issued by the CPU due to Load operations. This event counts any memory Load access, no matter whether the data is received from any level of cache hierarchy or external memory. This event also counts atomic Load operations. If memory accesses are broken up by the Load/Store unit into smaller transactions that are issued by the bus interface, then the event counts those smaller transactions.\nThe following instructions are not counted:\n1) Instruction fetches,\n2) Cache maintenance instructions,\n3) Translation table walks or prefetches,\n4) Memory prefetch operations.\nThis event is a subset of the MEM_ACCESS event but the event only counts memory-Read operations." + }, + { + "ArchStdEvent": "MEM_ACCESS_WR", + "PublicDescription": "This event counts memory accesses issued by the CPU due to Store operations. This event counts any memory Store access, no matter whether the data is located in any level of cache or external memory. This event also counts atomic Load and Store operations. If memory accesses are broken up by the Load/Store unit into smaller transactions that are issued by the bus interface, then the event counts those smaller transactions." + }, + { + "ArchStdEvent": "LDST_ALIGN_LAT", + "PublicDescription": "This event counts the number of memory Read and Write accesses in a cycle that incurred additional latency due to the alignment of the address and the size of data being accessed, which results in a store crossing a single cache line.\nThis event is implemented as the sum of the following events on this CPU:\nLD_ALIGN_LAT and\nST_ALIGN_LAT." + }, + { + "ArchStdEvent": "LD_ALIGN_LAT", + "PublicDescription": "This event counts the number of memory Read accesses in a cycle that incurred additional latency due to the alignment of the address and size of data being accessed, which results in a load crossing a single cache line." + }, + { + "ArchStdEvent": "ST_ALIGN_LAT", + "PublicDescription": "This event counts the number of memory Write accesses in a cycle that incurred additional latency due to the alignment of the address and size of data being accessed." + }, + { + "ArchStdEvent": "INST_FETCH_PERCYC", + "PublicDescription": "This event counts number of instruction fetches outstanding per cycle, which will provide an average latency of instruction fetch." + }, + { + "ArchStdEvent": "MEM_ACCESS_RD_PERCYC", + "PublicDescription": "This event counts the number of outstanding Loads or memory Read accesses per cycle." + }, + { + "ArchStdEvent": "INST_FETCH", + "PublicDescription": "This event counts instruction memory accesses that the PE makes." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/metrics.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/metrics.json new file mode 100644 index 000000000000..b825ede03f54 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/metrics.json @@ -0,0 +1,722 @@ +[ + { + "MetricName": "backend_bound", + "MetricExpr": "100 * (STALL_SLOT_BACKEND / CPU_SLOT)", + "BriefDescription": "This metric is the percentage of total slots that were stalled due to resource constraints in the backend of the processor.", + "ScaleUnit": "1percent of slots", + "MetricGroup": "TopdownL1" + }, + { + "MetricName": "backend_busy_bound", + "MetricExpr": "100 * (STALL_BACKEND_BUSY / STALL_BACKEND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to issue queues being full to accept operations for execution.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_cache_l1d_bound", + "MetricExpr": "100 * (STALL_BACKEND_L1D / (STALL_BACKEND_L1D + STALL_BACKEND_MEM))", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to memory access latency issues caused by L1 D-cache misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_cache_l2d_bound", + "MetricExpr": "100 * (STALL_BACKEND_MEM / (STALL_BACKEND_L1D + STALL_BACKEND_MEM))", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to memory access latency issues caused by L2 D-cache misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_core_bound", + "MetricExpr": "100 * (STALL_BACKEND_CPUBOUND / STALL_BACKEND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to backend Core resource constraints not related to instruction fetch latency issues caused by memory access components.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_core_rename_bound", + "MetricExpr": "100 * (STALL_BACKEND_RENAME / STALL_BACKEND_CPUBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend as the rename unit registers are unavailable.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_mem_bound", + "MetricExpr": "100 * (STALL_BACKEND_MEMBOUND / STALL_BACKEND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to backend Core resource constraints related to memory access latency issues caused by memory access components.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_mem_cache_bound", + "MetricExpr": "100 * ((STALL_BACKEND_L1D + STALL_BACKEND_MEM) / STALL_BACKEND_MEMBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to memory latency issues caused by D-cache misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_mem_store_bound", + "MetricExpr": "100 * (STALL_BACKEND_ST / STALL_BACKEND_MEMBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to memory Write pending caused by Stores stalled in the pre-commit stage.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_mem_tlb_bound", + "MetricExpr": "100 * (STALL_BACKEND_TLB / STALL_BACKEND_MEMBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the backend due to memory access latency issues caused by Data TLB misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Backend" + }, + { + "MetricName": "backend_stalled_cycles", + "MetricExpr": "100 * (STALL_BACKEND / CPU_CYCLES)", + "BriefDescription": "This metric is the percentage of cycles that were stalled due to resource constraints in the backend unit of the processor.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Cycle_Accounting" + }, + { + "MetricName": "bad_speculation", + "MetricExpr": "100 - (frontend_bound + retiring + backend_bound)", + "BriefDescription": "This metric is the percentage of total slots that executed operations and didn't retire due to a pipeline flush. This indicates cycles that were utilized but inefficiently.", + "ScaleUnit": "1percent of slots", + "MetricGroup": "TopdownL1" + }, + { + "MetricName": "barrier_percentage", + "MetricExpr": "100 * ((ISB_SPEC + DSB_SPEC + DMB_SPEC) / INST_SPEC)", + "BriefDescription": "This metric measures instruction and data barrier operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "branch_direct_ratio", + "MetricExpr": "BR_IMMED_RETIRED / BR_RETIRED", + "BriefDescription": "This metric measures the ratio of direct branches retired to the total number of branches architecturally executed.", + "ScaleUnit": "1per branch", + "MetricGroup": "Branch_Effectiveness" + }, + { + "MetricName": "branch_indirect_ratio", + "MetricExpr": "BR_IND_RETIRED / BR_RETIRED", + "BriefDescription": "This metric measures the ratio of indirect branches retired, including function returns, to the total number of branches architecturally executed.", + "ScaleUnit": "1per branch", + "MetricGroup": "Branch_Effectiveness" + }, + { + "MetricName": "branch_misprediction_ratio", + "MetricExpr": "BR_MIS_PRED_RETIRED / BR_RETIRED", + "BriefDescription": "This metric measures the ratio of branches mispredicted to the total number of branches architecturally executed. This gives an indication of the effectiveness of the branch prediction unit.", + "ScaleUnit": "1per branch", + "MetricGroup": "Miss_Ratio;Branch_Effectiveness" + }, + { + "MetricName": "branch_mpki", + "MetricExpr": "1000 * (BR_MIS_PRED_RETIRED / INST_RETIRED)", + "BriefDescription": "This metric measures the number of branch mispredictions per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;Branch_Effectiveness" + }, + { + "MetricName": "branch_percentage", + "MetricExpr": "100 * ((BR_IMMED_SPEC + BR_INDIRECT_SPEC) / INST_SPEC)", + "BriefDescription": "This metric measures branch operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "branch_return_ratio", + "MetricExpr": "BR_RETURN_RETIRED / BR_RETIRED", + "BriefDescription": "This metric measures the ratio of branches retired that are function returns to the total number of branches architecturally executed.", + "ScaleUnit": "1per branch", + "MetricGroup": "Branch_Effectiveness" + }, + { + "MetricName": "bus_bandwidth", + "MetricExpr": "BUS_ACCESS * 32 / duration_time ", + "BriefDescription": "This metric measures the bus-bandwidth of the data transferred between this PE's L2 with unCore in the system.", + "ScaleUnit": "1Bytes/sec" + }, + { + "MetricName": "cpu_cycles_fraction_in_st_mode", + "MetricExpr": "((CPU_SLOT/CPU_CYCLES) - 5) / 5", + "BriefDescription": "This metric counts fraction of the CPU cycles spent in ST mode during program execution.", + "ScaleUnit": "1fraction of cycles", + "MetricGroup": "SMT" + }, + { + "MetricName": "cpu_cycles_in_smt_mode", + "MetricExpr": "(1 - cpu_cycles_fraction_in_st_mode) * CPU_CYCLES", + "BriefDescription": "This metric counts CPU cycles in SMT mode during program execution.", + "ScaleUnit": "1CPU cycles", + "MetricGroup": "SMT" + }, + { + "MetricName": "cpu_cycles_in_st_mode", + "MetricExpr": "cpu_cycles_fraction_in_st_mode * CPU_CYCLES", + "BriefDescription": "This metric counts CPU cycles in ST mode during program execution.", + "ScaleUnit": "1CPU cycles", + "MetricGroup": "SMT" + }, + { + "MetricName": "crypto_percentage", + "MetricExpr": "100 * (CRYPTO_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures crypto operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "dtlb_mpki", + "MetricExpr": "1000 * (DTLB_WALK / INST_RETIRED)", + "BriefDescription": "This metric measures the number of Data TLB Walks per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;DTLB_Effectiveness" + }, + { + "MetricName": "dtlb_walk_average_latency", + "MetricExpr": "DTLB_WALK_PERCYC / DTLB_WALK", + "BriefDescription": "This metric measures the average latency of Data TLB walks in CPU cycles.", + "ScaleUnit": "1CPU cycles", + "MetricGroup": "Average_Latency" + }, + { + "MetricName": "dtlb_walk_ratio", + "MetricExpr": "DTLB_WALK / L1D_TLB", + "BriefDescription": "This metric measures the ratio of Data TLB Walks to the total number of Data TLB accesses. This gives an indication of the effectiveness of the Data TLB accesses.", + "ScaleUnit": "1per TLB access", + "MetricGroup": "Miss_Ratio;DTLB_Effectiveness" + }, + { + "MetricName": "fp16_percentage", + "MetricExpr": "100 * (FP_HP_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures half-precision floating point operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "FP_Precision_Mix" + }, + { + "MetricName": "fp32_percentage", + "MetricExpr": "100 * (FP_SP_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures single-precision floating point operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "FP_Precision_Mix" + }, + { + "MetricName": "fp64_percentage", + "MetricExpr": "100 * (FP_DP_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures double-precision floating point operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "FP_Precision_Mix" + }, + { + "MetricName": "fp_ops_per_cycle", + "MetricExpr": "(FP_SCALE_OPS_SPEC + FP_FIXED_OPS_SPEC) / CPU_CYCLES", + "BriefDescription": "This metric measures floating point operations per cycle in any precision performed by any instruction. Operations are counted by computation and by vector lanes, fused computations such as multiply-add count as twice per vector lane for example.", + "ScaleUnit": "1operations per cycle", + "MetricGroup": "FP_Arithmetic_Intensity" + }, + { + "MetricName": "frontend_bound", + "MetricExpr": "100 * (STALL_SLOT_FRONTEND_WITHOUT_MISPRED / CPU_SLOT)", + "BriefDescription": "This metric is the percentage of total slots that were stalled due to resource constraints in the frontend of the processor.", + "ScaleUnit": "1percent of slots", + "MetricGroup": "TopdownL1" + }, + { + "MetricName": "frontend_cache_l1i_bound", + "MetricExpr": "100 * (STALL_FRONTEND_L1I / (STALL_FRONTEND_L1I + STALL_FRONTEND_MEM))", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend due to memory access latency issues caused by L1 I-cache misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_cache_l2i_bound", + "MetricExpr": "100 * (STALL_FRONTEND_MEM / (STALL_FRONTEND_L1I + STALL_FRONTEND_MEM))", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend due to memory access latency issues caused by L2 I-cache misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_core_bound", + "MetricExpr": "100 * (STALL_FRONTEND_CPUBOUND / STALL_FRONTEND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend due to frontend Core resource constraints not related to instruction fetch latency issues caused by memory access components.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_core_flow_bound", + "MetricExpr": "100 * (STALL_FRONTEND_FLOW / STALL_FRONTEND_CPUBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend as the decode unit is awaiting input from the branch prediction unit.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_core_flush_bound", + "MetricExpr": "100 * (STALL_FRONTEND_FLUSH / STALL_FRONTEND_CPUBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend as the processor is recovering from a pipeline flush caused by bad speculation or other machine resteers.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_mem_bound", + "MetricExpr": "100 * (STALL_FRONTEND_MEMBOUND / STALL_FRONTEND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend due to frontend Core resource constraints related to the instruction fetch latency issues caused by memory access components.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_mem_cache_bound", + "MetricExpr": "100 * ((STALL_FRONTEND_L1I + STALL_FRONTEND_MEM) / STALL_FRONTEND_MEMBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend due to instruction fetch latency issues caused by I-cache misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_mem_tlb_bound", + "MetricExpr": "100 * (STALL_FRONTEND_TLB / STALL_FRONTEND_MEMBOUND)", + "BriefDescription": "This metric is the percentage of total cycles stalled in the frontend due to instruction fetch latency issues caused by Instruction TLB misses.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Topdown_Frontend" + }, + { + "MetricName": "frontend_stalled_cycles", + "MetricExpr": "100 * (STALL_FRONTEND / CPU_CYCLES)", + "BriefDescription": "This metric is the percentage of cycles that were stalled due to resource constraints in the frontend unit of the processor.", + "ScaleUnit": "1percent of cycles", + "MetricGroup": "Cycle_Accounting" + }, + { + "MetricName": "instruction_fetch_average_latency", + "MetricExpr": "INST_FETCH_PERCYC / INST_FETCH", + "BriefDescription": "This metric measures the average latency of instruction fetches in CPU cycles.", + "ScaleUnit": "1CPU cycles", + "MetricGroup": "Average_Latency" + }, + { + "MetricName": "integer_dp_percentage", + "MetricExpr": "100 * (DP_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures scalar integer operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "ipc", + "MetricExpr": "INST_RETIRED / CPU_CYCLES", + "BriefDescription": "This metric measures the number of instructions retired per cycle.", + "ScaleUnit": "1per cycle", + "MetricGroup": "General" + }, + { + "MetricName": "itlb_mpki", + "MetricExpr": "1000 * (ITLB_WALK / INST_RETIRED)", + "BriefDescription": "This metric measures the number of instruction TLB Walks per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;ITLB_Effectiveness" + }, + { + "MetricName": "itlb_walk_average_latency", + "MetricExpr": "ITLB_WALK_PERCYC / ITLB_WALK", + "BriefDescription": "This metric measures the average latency of instruction TLB walks in CPU cycles.", + "ScaleUnit": "1CPU cycles", + "MetricGroup": "Average_Latency" + }, + { + "MetricName": "itlb_walk_ratio", + "MetricExpr": "ITLB_WALK / L1I_TLB", + "BriefDescription": "This metric measures the ratio of instruction TLB Walks to the total number of Instruction TLB accesses. This gives an indication of the effectiveness of the Instruction TLB accesses.", + "ScaleUnit": "1per TLB access", + "MetricGroup": "Miss_Ratio;ITLB_Effectiveness" + }, + { + "MetricName": "l1d_cache_miss_ratio", + "MetricExpr": "L1D_CACHE_REFILL / L1D_CACHE", + "BriefDescription": "This metric measures the ratio of L1 D-cache accesses missed to the total number of L1 D-cache accesses. This gives an indication of the effectiveness of the L1 D-cache.", + "ScaleUnit": "1per cache access", + "MetricGroup": "Miss_Ratio;L1D_Cache_Effectiveness" + }, + { + "MetricName": "l1d_cache_mpki", + "MetricExpr": "1000 * (L1D_CACHE_REFILL / INST_RETIRED)", + "BriefDescription": "This metric measures the number of L1 D-cache accesses missed per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;L1D_Cache_Effectiveness" + }, + { + "MetricName": "l1d_cache_rw_miss_ratio", + "MetricExpr": "l1d_demand_misses / l1d_demand_accesses", + "BriefDescription": "This metric measures the ratio of L1 D-cache Read accesses missed to the total number of L1 D-cache accesses. This gives an indication of the effectiveness of the L1 D-cache for demand Load or Store traffic.", + "ScaleUnit": "1per cache access", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1d_demand_accesses", + "MetricExpr": "L1D_CACHE_RW", + "BriefDescription": "This metric measures the count of L1 D-cache accesses incurred on Load or Store by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1d_demand_misses", + "MetricExpr": "L1D_CACHE_REFILL_RW", + "BriefDescription": "This metric measures the count of L1 D-cache misses incurred on a Load or Store by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1d_prf_accuracy", + "MetricExpr": "100 * (l1d_useful_prf / l1d_refilled_prf)", + "BriefDescription": "This metric measures the fraction of prefetched memory addresses that are used by the instruction stream.", + "ScaleUnit": "1percent of prefetch", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1d_prf_coverage", + "MetricExpr": "100 * (l1d_useful_prf / (l1d_demand_misses + l1d_refilled_prf))", + "BriefDescription": "This metric measures the baseline demand cache misses which the prefetcher brings into the cache.", + "ScaleUnit": "1percent of cache access", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1d_refilled_prf", + "MetricExpr": "L1D_CACHE_REFILL_HWPRF + L1D_CACHE_REFILL_PRFM + L1D_LFB_HIT_RW_FHWPRF + L1D_LFB_HIT_RW_FPRFM", + "BriefDescription": "This metric measures the count of cache lines refilled by L1 data prefetcher (hardware prefetches or software preload) into L1 D-cache.", + "ScaleUnit": "1count", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1d_tlb_miss_ratio", + "MetricExpr": "L1D_TLB_REFILL / L1D_TLB", + "BriefDescription": "This metric measures the ratio of L1 Data TLB accesses missed to the total number of L1 Data TLB accesses. This gives an indication of the effectiveness of the L1 Data TLB.", + "ScaleUnit": "1per TLB access", + "MetricGroup": "Miss_Ratio;DTLB_Effectiveness" + }, + { + "MetricName": "l1d_tlb_mpki", + "MetricExpr": "1000 * (L1D_TLB_REFILL / INST_RETIRED)", + "BriefDescription": "This metric measures the number of L1 Data TLB accesses missed per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;DTLB_Effectiveness" + }, + { + "MetricName": "l1d_useful_prf", + "MetricExpr": "L1D_CACHE_HIT_RW_FPRF + L1D_LFB_HIT_RW_FHWPRF + L1D_LFB_HIT_RW_FPRFM", + "BriefDescription": "This metric measures the count of cache lines refilled by L1 data prefetcher (hardware prefetches or software preload) into L1 D-cache which are further used by Load or Store from the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L1I_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_cache_miss_ratio", + "MetricExpr": "L1I_CACHE_REFILL / L1I_CACHE", + "BriefDescription": "This metric measures the ratio of L1 I-cache accesses missed to the total number of L1 I-cache accesses. This gives an indication of the effectiveness of the L1 I-cache.", + "ScaleUnit": "1per cache access", + "MetricGroup": "Miss_Ratio;L1I_Cache_Effectiveness" + }, + { + "MetricName": "l1i_cache_mpki", + "MetricExpr": "1000 * (L1I_CACHE_REFILL / INST_RETIRED)", + "BriefDescription": "This metric measures the number of L1 I-cache accesses missed per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;L1I_Cache_Effectiveness" + }, + { + "MetricName": "l1i_cache_rd_miss_ratio", + "MetricExpr": "l1i_demand_misses / l1i_demand_accesses", + "BriefDescription": "This metric measures the ratio of L1 I-cache Read accesses missed to the total number of L1 I-cache accesses. This gives an indication of the effectiveness of the L1 I-cache for demand instruction fetch traffic. Note that cache accesses in this cache are demand instruction fetch.", + "ScaleUnit": "1per cache access", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_demand_accesses", + "MetricExpr": "L1I_CACHE_RD", + "BriefDescription": "This metric measures the count of L1 I-cache accesses caused by an instruction fetch by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_demand_misses", + "MetricExpr": "L1I_CACHE_REFILL_RD", + "BriefDescription": "This metric measures the count of L1 I-cache misses caused by an instruction fetch by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_prf_accuracy", + "MetricExpr": "100 * (l1i_useful_prf / l1i_refilled_prf)", + "BriefDescription": "This metric measures the fraction of prefetched memory addresses that are used by the instruction stream.", + "ScaleUnit": "1percent of prefetch", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_prf_coverage", + "MetricExpr": "100 * (l1i_useful_prf / (l1i_demand_misses + l1i_refilled_prf))", + "BriefDescription": "This metric measures the baseline demand cache misses which the prefetcher brings into the cache.", + "ScaleUnit": "1percent of cache access", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_refilled_prf", + "MetricExpr": "L1I_CACHE_REFILL_HWPRF + L1I_CACHE_REFILL_PRFM", + "BriefDescription": "This metric measures the count of cache lines refilled by L1 instruction prefetcher (hardware prefetches or software preload) into L1 I-cache.", + "ScaleUnit": "1count", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l1i_tlb_miss_ratio", + "MetricExpr": "L1I_TLB_REFILL / L1I_TLB", + "BriefDescription": "This metric measures the ratio of L1 Instruction TLB accesses missed to the total number of L1 Instruction TLB accesses. This gives an indication of the effectiveness of the L1 Instruction TLB.", + "ScaleUnit": "1per TLB access", + "MetricGroup": "Miss_Ratio;ITLB_Effectiveness" + }, + { + "MetricName": "l1i_tlb_mpki", + "MetricExpr": "1000 * (L1I_TLB_REFILL / INST_RETIRED)", + "BriefDescription": "This metric measures the number of L1 Instruction TLB accesses missed per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;ITLB_Effectiveness" + }, + { + "MetricName": "l1i_useful_prf", + "MetricExpr": "L1I_CACHE_HIT_RD_FPRF", + "BriefDescription": "This metric measures the count of cache lines refilled by L1 instruction prefetcher (hardware prefetches or software preload) into L1 I-cache which are further used by instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L1D_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2_cache_miss_ratio", + "MetricExpr": "L2D_CACHE_REFILL / L2D_CACHE", + "BriefDescription": "This metric measures the ratio of L2 cache accesses missed to the total number of L2 cache accesses. This gives an indication of the effectiveness of the L2 cache, which is a unified cache that stores both data and instruction.\nNote that cache accesses in this cache are either data memory access or instruction fetch as this is a unified cache.", + "ScaleUnit": "1per cache access", + "MetricGroup": "Miss_Ratio;L2_Cache_Effectiveness" + }, + { + "MetricName": "l2_cache_mpki", + "MetricExpr": "1000 * (l2d_demand_misses / INST_RETIRED)", + "BriefDescription": "This metric measures the number of L2 unified cache accesses missed per thousand instructions executed.\nNote that cache accesses in this cache are either data memory access or instruction fetch as this is a unified cache.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;L2_Cache_Effectiveness" + }, + { + "MetricName": "l2_tlb_miss_ratio", + "MetricExpr": "L2D_TLB_REFILL / L2D_TLB", + "BriefDescription": "This metric measures the ratio of L2 unified TLB accesses missed to the total number of L2 unified TLB accesses.\nThis gives an indication of the effectiveness of the L2 TLB.", + "ScaleUnit": "1per TLB access", + "MetricGroup": "Miss_Ratio;ITLB_Effectiveness;DTLB_Effectiveness" + }, + { + "MetricName": "l2_tlb_mpki", + "MetricExpr": "1000 * (L2D_TLB_REFILL / INST_RETIRED)", + "BriefDescription": "This metric measures the number of L2 unified TLB accesses missed per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;ITLB_Effectiveness;DTLB_Effectiveness" + }, + { + "MetricName": "l2d_cache_rwl1prf_miss_ratio", + "MetricExpr": "l2d_demand_misses / l2d_demand_accesses", + "BriefDescription": "This metric measures the ratio of L2 D-cache Read accesses missed to the total number of L2 D-cache accesses.\nThis gives an indication of the effectiveness of the L2 D-cache for demand instruction fetch, Load, Store, or L1 prefetcher accesses traffic.", + "ScaleUnit": "1per cache access", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2d_demand_accesses", + "MetricExpr": "L2D_CACHE_RD + L2D_CACHE_WR + L2D_CACHE_L1PRF", + "BriefDescription": "This metric measures the count of L2 D-cache accesses incurred on an instruction fetch, Load, Store, or L1 prefetcher accesses by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2d_demand_misses", + "MetricExpr": "L2D_CACHE_REFILL_RD + L2D_CACHE_REFILL_WR + L2D_CACHE_REFILL_L1PRF", + "BriefDescription": "This metric measures the count of L2 D-cache misses incurred on an instruction fetch, Load, Store, or L1 prefetcher accesses by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2d_prf_accuracy", + "MetricExpr": "100 * (l2d_useful_prf / l2d_refilled_prf)", + "BriefDescription": "This metric measures the fraction of prefetched memory addresses that are used by the instruction stream.", + "ScaleUnit": "1percent of prefetch", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2d_prf_coverage", + "MetricExpr": "100 * (l2d_useful_prf / (l2d_demand_misses + l2d_refilled_prf))", + "BriefDescription": "This metric measures the baseline demand cache misses which the prefetcher brings into the cache.", + "ScaleUnit": "1percent of cache access", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2d_refilled_prf", + "MetricExpr": "(L2D_CACHE_REFILL_PRF - L2D_CACHE_REFILL_L1PRF) + L2D_LFB_HIT_RWL1PRF_FHWPRF", + "BriefDescription": "This metric measures the count of cache lines refilled by L2 data prefetcher (hardware prefetches or software preload) into L2 D-cache.", + "ScaleUnit": "1count", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l2d_useful_prf", + "MetricExpr": "L2D_CACHE_HIT_RWL1PRF_FPRF + L2D_LFB_HIT_RWL1PRF_FHWPRF", + "BriefDescription": "This metric measures the count of cache lines refilled by L2 data prefetcher (hardware prefetches or software preload) into L2 D-cache which are further used by instruction fetch, Load, Store, or L1 prefetcher accesses from the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L2_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_cache_rwl1prfl2prf_miss_ratio", + "MetricExpr": "l3d_demand_misses / l3d_demand_accesses", + "BriefDescription": "This metric measures the ratio of L3 D-cache Read accesses missed to the total number of L3 D-cache accesses. This gives an indication of the effectiveness of the L2 D-cache for demand instruction fetch, Load, Store, L1 prefetcher, or L2 prefetcher accesses traffic.", + "ScaleUnit": "1per cache access", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_demand_accesses", + "MetricExpr": "L3D_CACHE_RWL1PRFL2PRF", + "BriefDescription": "This metric measures the count of L3 D-cache accesses incurred on an instruction fetch, Load, Store, L1 prefetcher, or L2 prefetcher accesses by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_demand_misses", + "MetricExpr": "L3D_CACHE_REFILL_RWL1PRFL2PRF", + "BriefDescription": "This metric measures the count of L3 D-cache misses incurred on an instruction fetch, Load, Store, L1 prefetcher, or L2 prefetcher accesses by the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_prf_accuracy", + "MetricExpr": "100 * (l3d_useful_prf / l3d_refilled_prf)", + "BriefDescription": "This metric measures the fraction of prefetched memory addresses that are used by the instruction stream.", + "ScaleUnit": "1percent of prefetch", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_prf_coverage", + "MetricExpr": "100 * (l3d_useful_prf / (l3d_demand_misses + l3d_refilled_prf))", + "BriefDescription": "This metric measures the baseline demand cache misses which the prefetcher brings into the cache.", + "ScaleUnit": "1percent of cache access", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_refilled_prf", + "MetricExpr": "L3D_CACHE_REFILL_HWPRF + L3D_CACHE_REFILL_PRFM - L3D_CACHE_REFILL_L1PRF - L3D_CACHE_REFILL_L2PRF", + "BriefDescription": "This metric measures the count of cache lines refilled by L3 data prefetcher (hardware prefetches or software preload) into L3 D-cache.", + "ScaleUnit": "1count", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "l3d_useful_prf", + "MetricExpr": "L3D_CACHE_HIT_RWL1PRFL2PRF_FPRF", + "BriefDescription": "This metric measures the count of cache lines refilled by L3 data prefetcher (hardware prefetches or software preload) into L3 D-cache which are further used by instruction fetch, Load, Store, L1 prefetcher, or L2 prefetcher accesses from the instruction stream of the program.", + "ScaleUnit": "1count", + "MetricGroup": "L3_Prefetcher_Effectiveness" + }, + { + "MetricName": "ll_cache_read_hit_ratio", + "MetricExpr": "(LL_CACHE_RD - LL_CACHE_MISS_RD) / LL_CACHE_RD", + "BriefDescription": "This metric measures the ratio of last level cache Read accesses hit in the cache to the total number of last level cache accesses. This gives an indication of the effectiveness of the last level cache for Read traffic. Note that cache accesses in this cache are either data memory access or instruction fetch as this is a system level cache.", + "ScaleUnit": "1per cache access", + "MetricGroup": "LL_Cache_Effectiveness" + }, + { + "MetricName": "ll_cache_read_miss_ratio", + "MetricExpr": "LL_CACHE_MISS_RD / LL_CACHE_RD", + "BriefDescription": "This metric measures the ratio of last level cache Read accesses missed to the total number of last level cache accesses. This gives an indication of the effectiveness of the last level cache for Read traffic. Note that cache accesses in this cache are either data memory access or instruction fetch as this is a system level cache.", + "ScaleUnit": "1per cache access", + "MetricGroup": "Miss_Ratio;LL_Cache_Effectiveness" + }, + { + "MetricName": "ll_cache_read_mpki", + "MetricExpr": "1000 * (LL_CACHE_MISS_RD / INST_RETIRED)", + "BriefDescription": "This metric measures the number of last level cache Read accesses missed per thousand instructions executed.", + "ScaleUnit": "1MPKI", + "MetricGroup": "MPKI;LL_Cache_Effectiveness" + }, + { + "MetricName": "load_average_latency", + "MetricExpr": "MEM_ACCESS_RD_PERCYC / MEM_ACCESS", + "BriefDescription": "This metric measures the average latency of Load operations in CPU cycles.", + "ScaleUnit": "1CPU cycles", + "MetricGroup": "Average_Latency" + }, + { + "MetricName": "load_percentage", + "MetricExpr": "100 * (LD_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures Load operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "nonsve_fp_ops_per_cycle", + "MetricExpr": "FP_FIXED_OPS_SPEC / CPU_CYCLES", + "BriefDescription": "This metric measures floating point operations per cycle in any precision performed by an instruction that is not an SVE instruction. Operations are counted by computation and by vector lanes, fused computations such as multiply-add count as twice per vector lane for example.", + "ScaleUnit": "1operations per cycle", + "MetricGroup": "FP_Arithmetic_Intensity" + }, + { + "MetricName": "retiring", + "MetricExpr": "100 * ((OP_RETIRED/OP_SPEC) * (1 - (STALL_SLOT/CPU_SLOT)))", + "BriefDescription": "This metric is the percentage of total slots that retired operations, which indicates cycles that were utilized efficiently.", + "ScaleUnit": "1percent of slots", + "MetricGroup": "TopdownL1" + }, + { + "MetricName": "scalar_fp_percentage", + "MetricExpr": "100 * (VFP_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures scalar floating point operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "simd_percentage", + "MetricExpr": "100 * (ASE_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures advanced SIMD operations as a percentage of total operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "store_percentage", + "MetricExpr": "100 * (ST_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures Store operations as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "sve_all_percentage", + "MetricExpr": "100 * (SVE_INST_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures scalable vector operations, including Loads and Stores, as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "Operation_Mix" + }, + { + "MetricName": "sve_fp_ops_per_cycle", + "MetricExpr": "FP_SCALE_OPS_SPEC / CPU_CYCLES", + "BriefDescription": "This metric measures floating point operations per cycle in any precision performed by SVE instructions. Operations are counted by computation and by vector lanes, fused computations such as multiply-add count as twice per vector lane for example.", + "ScaleUnit": "1operations per cycle", + "MetricGroup": "FP_Arithmetic_Intensity" + }, + { + "MetricName": "sve_predicate_empty_percentage", + "MetricExpr": "100 * (SVE_PRED_EMPTY_SPEC / SVE_PRED_SPEC)", + "BriefDescription": "This metric measures scalable vector operations with no active predicates as a percentage of SVE predicated operations speculatively executed.", + "ScaleUnit": "1percent of SVE predicated operations", + "MetricGroup": "SVE_Effectiveness" + }, + { + "MetricName": "sve_predicate_full_percentage", + "MetricExpr": "100 * (SVE_PRED_FULL_SPEC / SVE_PRED_SPEC)", + "BriefDescription": "This metric measures scalable vector operations with all active predicates as a percentage of SVE predicated operations speculatively executed.", + "ScaleUnit": "1percent of SVE predicated operations", + "MetricGroup": "SVE_Effectiveness" + }, + { + "MetricName": "sve_predicate_partial_percentage", + "MetricExpr": "100 * (SVE_PRED_PARTIAL_SPEC / SVE_PRED_SPEC)", + "BriefDescription": "This metric measures scalable vector operations with at least one active predicates as a percentage of SVE predicated operations speculatively executed.", + "ScaleUnit": "1percent of SVE predicated operations", + "MetricGroup": "SVE_Effectiveness" + }, + { + "MetricName": "sve_predicate_percentage", + "MetricExpr": "100 * (SVE_PRED_SPEC / INST_SPEC)", + "BriefDescription": "This metric measures scalable vector operations with predicates as a percentage of operations speculatively executed.", + "ScaleUnit": "1percent of operations", + "MetricGroup": "SVE_Effectiveness" + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/misc.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/misc.json new file mode 100644 index 000000000000..8ff87d844e52 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/misc.json @@ -0,0 +1,642 @@ +[ + { + "ArchStdEvent": "SW_INCR", + "PublicDescription": "This event counts software writes to the PMSWINC_EL0 (software PMU increment) register. The PMSWINC_EL0 register is a manually updated counter for use by application software.\nThis event could be used to measure any user program event, such as accesses to a particular data structure (by writing to the PMSWINC_EL0 register each time the data structure is accessed).\nTo use the PMSWINC_EL0 register and event, developers must insert instructions that write to the PMSWINC_EL0 register into the source code.\nSince the SW_INCR event records writes to the PMSWINC_EL0 register, there is no need to do a Read/Increment/Write sequence to the PMSWINC_EL0 register." + }, + { + "ArchStdEvent": "TRB_WRAP", + "PublicDescription": "This event is generated each time the trace buffer current Write pointer is wrapped to the trace buffer base pointer." + }, + { + "ArchStdEvent": "TRCEXTOUT0", + "PublicDescription": "Trace unit external output 0." + }, + { + "ArchStdEvent": "TRCEXTOUT1", + "PublicDescription": "Trace unit external output 1." + }, + { + "ArchStdEvent": "TRCEXTOUT2", + "PublicDescription": "Trace unit external output 2." + }, + { + "ArchStdEvent": "TRCEXTOUT3", + "PublicDescription": "Trace unit external output 3." + }, + { + "ArchStdEvent": "CTI_TRIGOUT4", + "PublicDescription": "Cross-trigger Interface output trigger 4." + }, + { + "ArchStdEvent": "CTI_TRIGOUT5", + "PublicDescription": "Cross-trigger Interface output trigger 5." + }, + { + "ArchStdEvent": "CTI_TRIGOUT6", + "PublicDescription": "Cross-trigger Interface output trigger 6." + }, + { + "ArchStdEvent": "CTI_TRIGOUT7", + "PublicDescription": "Cross-trigger Interface output trigger 7." + }, + { + "EventCode": "0x00e1", + "EventName": "L1I_PRFM_REQ_DROP", + "PublicDescription": "L1 I-cache software prefetch dropped." + }, + { + "EventCode": "0x0100", + "EventName": "L1_PF_REFILL", + "PublicDescription": "L1 prefetch requests, refilled to L1 cache." + }, + { + "EventCode": "0x0120", + "EventName": "FLUSH", + "PublicDescription": "This event counts both the CT flush and BX flush. The BR_MIS_PRED counts the BX flushes. So the FLUSH-BR_MIS_PRED gives the CT flushes." + }, + { + "EventCode": "0x0121", + "EventName": "FLUSH_MEM", + "PublicDescription": "Flushes due to memory hazards. This only includes CT flushes." + }, + { + "EventCode": "0x0122", + "EventName": "FLUSH_BAD_BRANCH", + "PublicDescription": "Flushes due to bad predicted branch. This only includes CT flushes." + }, + { + "EventCode": "0x0123", + "EventName": "FLUSH_STDBYPASS", + "PublicDescription": "Flushes due to bad predecode. This only includes CT flushes." + }, + { + "EventCode": "0x0124", + "EventName": "FLUSH_ISB", + "PublicDescription": "Flushes due to ISB or similar side-effects. This only includes CT flushes." + }, + { + "EventCode": "0x0125", + "EventName": "FLUSH_OTHER", + "PublicDescription": "Flushes due to other hazards. This only includes CT flushes." + }, + { + "EventCode": "0x0126", + "EventName": "STORE_STREAM", + "PublicDescription": "Stored lines in streaming no-Write-allocate mode." + }, + { + "EventCode": "0x0127", + "EventName": "NUKE_RAR", + "PublicDescription": "Load/Store nuke due to Read-after-Read ordering hazard." + }, + { + "EventCode": "0x0128", + "EventName": "NUKE_RAW", + "PublicDescription": "Load/Store nuke due to Read-after-Write ordering hazard." + }, + { + "EventCode": "0x0129", + "EventName": "L1_PF_GEN_PAGE", + "PublicDescription": "Load/Store prefetch to L1 generated, Page mode." + }, + { + "EventCode": "0x012a", + "EventName": "L1_PF_GEN_STRIDE", + "PublicDescription": "Load/Store prefetch to L1 generated, stride mode." + }, + { + "EventCode": "0x012b", + "EventName": "L2_PF_GEN_LD", + "PublicDescription": "Load prefetch to L2 generated." + }, + { + "EventCode": "0x012d", + "EventName": "LS_PF_TRAIN_TABLE_ALLOC", + "PublicDescription": "LS prefetch train table entry allocated." + }, + { + "EventCode": "0x0130", + "EventName": "LS_PF_GEN_TABLE_ALLOC", + "PublicDescription": "This event counts the number of cycles with at least one table allocation, for L2 hardware prefetches (including the software PRFM instructions that are converted into hardware prefetches due to D-TLB miss).\nLS prefetch gen table allocation (for L2 prefetches)." + }, + { + "EventCode": "0x0131", + "EventName": "LS_PF_GEN_TABLE_ALLOC_PF_PEND", + "PublicDescription": "This event counts the number of cycles in which at least one hardware prefetch is dropped due to the inability to identify a victim when the generation table is full. The hardware prefetch considered here includes the software PRFM that is converted into hardware prefetches due to D-TLB miss." + }, + { + "EventCode": "0x0132", + "EventName": "TBW", + "PublicDescription": "Tablewalks." + }, + { + "EventCode": "0x0134", + "EventName": "S1L2_HIT", + "PublicDescription": "Translation cache hit on S1L2 walk cache entry." + }, + { + "EventCode": "0x0135", + "EventName": "S1L1_HIT", + "PublicDescription": "Translation cache hit on S1L1 walk cache entry." + }, + { + "EventCode": "0x0136", + "EventName": "S1L0_HIT", + "PublicDescription": "Translation cache hit on S1L0 walk cache entry." + }, + { + "EventCode": "0x0137", + "EventName": "S2L2_HIT", + "PublicDescription": "Translation cache hit for S2L2 IPA walk cache entry." + }, + { + "EventCode": "0x0138", + "EventName": "IPA_REQ", + "PublicDescription": "Translation cache lookups for IPA to PA entries." + }, + { + "EventCode": "0x0139", + "EventName": "IPA_REFILL", + "PublicDescription": "Translation cache refills for IPA to PA entries." + }, + { + "EventCode": "0x013a", + "EventName": "S1_FLT", + "PublicDescription": "Stage1 tablewalk fault." + }, + { + "EventCode": "0x013b", + "EventName": "S2_FLT", + "PublicDescription": "Stage2 tablewalk fault." + }, + { + "EventCode": "0x013c", + "EventName": "COLT_REFILL", + "PublicDescription": "Aggregated page refill." + }, + { + "EventCode": "0x0145", + "EventName": "L1_PF_HIT", + "PublicDescription": "L1 prefetch requests, hitting in L1 cache." + }, + { + "EventCode": "0x0146", + "EventName": "L1_PF", + "PublicDescription": "L1 prefetch requests." + }, + { + "EventCode": "0x0147", + "EventName": "CACHE_LS_REFILL", + "PublicDescription": "L2 D-cache refill, Load/Store." + }, + { + "EventCode": "0x0148", + "EventName": "CACHE_PF", + "PublicDescription": "L2 prefetch requests." + }, + { + "EventCode": "0x0149", + "EventName": "CACHE_PF_HIT", + "PublicDescription": "L2 prefetch requests, hitting in L2 cache." + }, + { + "EventCode": "0x0150", + "EventName": "UNUSED_PF", + "PublicDescription": "L2 unused prefetch." + }, + { + "EventCode": "0x0151", + "EventName": "PFT_SENT", + "PublicDescription": "L2 prefetch TGT sent.\nNote that PFT_SENT != PFT_USEFUL + PFT_DROP. There may be PFT_SENT for which the accesses resulted in a SLC hit." + }, + { + "EventCode": "0x0152", + "EventName": "PFT_USEFUL", + "PublicDescription": "L2 prefetch TGT useful." + }, + { + "EventCode": "0x0153", + "EventName": "PFT_DROP", + "PublicDescription": "L2 prefetch TGT dropped." + }, + { + "EventCode": "0x0162", + "EventName": "LRQ_FULL", + "PublicDescription": "This event counts the number of cycles the LRQ is full." + }, + { + "EventCode": "0x0163", + "EventName": "FETCH_FQ_EMPTY", + "PublicDescription": "Fetch Queue empty cycles." + }, + { + "EventCode": "0x0164", + "EventName": "FPG2", + "PublicDescription": "Forward progress guarantee. Medium range livelock triggered." + }, + { + "EventCode": "0x0165", + "EventName": "FPG", + "PublicDescription": "Forward progress guarantee. Tofu global livelock buster is triggered." + }, + { + "EventCode": "0x0172", + "EventName": "DEADBLOCK", + "PublicDescription": "Write-back evictions converted to dataless EVICT.\nThe victim line is deemed deadblock if the likeliness of a reuse is low. The Core uses dataless evict to evict a deadblock; and it uses an evict with data to evict an L2 line that is not a deadblock." + }, + { + "EventCode": "0x0173", + "EventName": "PF_PRQ_ALLOC_PF_PEND", + "PublicDescription": "L1 prefetch prq allocation (replacing pending)." + }, + { + "EventCode": "0x0178", + "EventName": "FETCH_ICACHE_INSTR", + "PublicDescription": "Instructions fetched from I-cache." + }, + { + "EventCode": "0x017b", + "EventName": "NEAR_CAS", + "PublicDescription": "Near atomics: compare and swap." + }, + { + "EventCode": "0x017c", + "EventName": "NEAR_CAS_PASS", + "PublicDescription": "Near atomics: compare and swap pass." + }, + { + "EventCode": "0x017d", + "EventName": "FAR_CAS", + "PublicDescription": "Far atomics: compare and swap." + }, + { + "EventCode": "0x0186", + "EventName": "L2_BTB_RELOAD_MAIN_BTB", + "PublicDescription": "Number of completed L1 BTB update initiated by L2 BTB hit which swap branch information between L1 BTB and L2 BTB." + }, + { + "EventCode": "0x018f", + "EventName": "L1_PF_GEN_MCMC", + "PublicDescription": "Load/Store prefetch to L1 generated, MCMC." + }, + { + "EventCode": "0x0190", + "EventName": "PF_MODE_0_CYCLES", + "PublicDescription": "Number of cycles in which the hardware prefetcher is in the most aggressive mode." + }, + { + "EventCode": "0x0191", + "EventName": "PF_MODE_1_CYCLES", + "PublicDescription": "Number of cycles in which the hardware prefetcher is in the more aggressive mode." + }, + { + "EventCode": "0x0192", + "EventName": "PF_MODE_2_CYCLES", + "PublicDescription": "Number of cycles in which the hardware prefetcher is in the less aggressive mode." + }, + { + "EventCode": "0x0193", + "EventName": "PF_MODE_3_CYCLES", + "PublicDescription": "Number of cycles in which the hardware prefetcher is in the most conservative mode." + }, + { + "EventCode": "0x0194", + "EventName": "TXREQ_LIMIT_MAX_CYCLES", + "PublicDescription": "Number of cycles in which the dynamic TXREQ limit is the L2_TQ_SIZE." + }, + { + "EventCode": "0x0195", + "EventName": "TXREQ_LIMIT_3QUARTER_CYCLES", + "PublicDescription": "Number of cycles in which the dynamic TXREQ limit is between 3/4 of the L2_TQ_SIZE and the L2_TQ_SIZE-1." + }, + { + "EventCode": "0x0196", + "EventName": "TXREQ_LIMIT_HALF_CYCLES", + "PublicDescription": "Number of cycles in which the dynamic TXREQ limit is between 1/2 of the L2_TQ_SIZE and 3/4 of the L2_TQ_SIZE." + }, + { + "EventCode": "0x0197", + "EventName": "TXREQ_LIMIT_1QUARTER_CYCLES", + "PublicDescription": "Number of cycles in which the dynamic TXREQ limit is between 1/4 of the L2_TQ_SIZE and 1/2 of the L2_TQ_SIZE." + }, + { + "EventCode": "0x019d", + "EventName": "PREFETCH_LATE_CMC", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by CMC prefetch request." + }, + { + "EventCode": "0x019e", + "EventName": "PREFETCH_LATE_BO", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by BO prefetch request." + }, + { + "EventCode": "0x019f", + "EventName": "PREFETCH_LATE_STRIDE", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by STRIDE prefetch request." + }, + { + "EventCode": "0x01a0", + "EventName": "PREFETCH_LATE_SPATIAL", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by SPATIAL prefetch request." + }, + { + "EventCode": "0x01a2", + "EventName": "PREFETCH_LATE_TBW", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by TBW prefetch request." + }, + { + "EventCode": "0x01a3", + "EventName": "PREFETCH_LATE_PAGE", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by PAGE prefetch request." + }, + { + "EventCode": "0x01a4", + "EventName": "PREFETCH_LATE_GSMS", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by GSMS prefetch request." + }, + { + "EventCode": "0x01a5", + "EventName": "PREFETCH_LATE_SIP_CONS", + "PublicDescription": "LS/readclean or LS/readunique lookup hit on TQ entry allocated by SIP_CONS prefetch request." + }, + { + "EventCode": "0x01a6", + "EventName": "PREFETCH_REFILL_CMC", + "PublicDescription": "PF/prefetch or PF/readclean request from CMC pf engine filled the L2 cache." + }, + { + "EventCode": "0x01a7", + "EventName": "PREFETCH_REFILL_BO", + "PublicDescription": "PF/prefetch or PF/readclean request from BO pf engine filled the L2 cache." + }, + { + "EventCode": "0x01a8", + "EventName": "PREFETCH_REFILL_STRIDE", + "PublicDescription": "PF/prefetch or PF/readclean request from STRIDE pf engine filled the L2 cache." + }, + { + "EventCode": "0x01a9", + "EventName": "PREFETCH_REFILL_SPATIAL", + "PublicDescription": "PF/prefetch or PF/readclean request from SPATIAL pf engine filled the L2 cache." + }, + { + "EventCode": "0x01ab", + "EventName": "PREFETCH_REFILL_TBW", + "PublicDescription": "PF/prefetch or PF/readclean request from TBW pf engine filled the L2 cache." + }, + { + "EventCode": "0x01ac", + "EventName": "PREFETCH_REFILL_PAGE", + "PublicDescription": "PF/prefetch or PF/readclean request from PAGE pf engine filled the L2 cache." + }, + { + "EventCode": "0x01ad", + "EventName": "PREFETCH_REFILL_GSMS", + "PublicDescription": "PF/prefetch or PF/readclean request from GSMS pf engine filled the L2 cache." + }, + { + "EventCode": "0x01ae", + "EventName": "PREFETCH_REFILL_SIP_CONS", + "PublicDescription": "PF/prefetch or PF/readclean request from SIP_CONS pf engine filled the L2 cache." + }, + { + "EventCode": "0x01af", + "EventName": "CACHE_HIT_LINE_PF_CMC", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by CMC prefetch request." + }, + { + "EventCode": "0x01b0", + "EventName": "CACHE_HIT_LINE_PF_BO", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by BO prefetch request." + }, + { + "EventCode": "0x01b1", + "EventName": "CACHE_HIT_LINE_PF_STRIDE", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by STRIDE prefetch request." + }, + { + "EventCode": "0x01b2", + "EventName": "CACHE_HIT_LINE_PF_SPATIAL", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by SPATIAL prefetch request." + }, + { + "EventCode": "0x01b4", + "EventName": "CACHE_HIT_LINE_PF_TBW", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by TBW prefetch request." + }, + { + "EventCode": "0x01b5", + "EventName": "CACHE_HIT_LINE_PF_PAGE", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by PAGE prefetch request." + }, + { + "EventCode": "0x01b6", + "EventName": "CACHE_HIT_LINE_PF_GSMS", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by GSMS prefetch request." + }, + { + "EventCode": "0x01b7", + "EventName": "CACHE_HIT_LINE_PF_SIP_CONS", + "PublicDescription": "LS/readclean or LS/readunique lookup hit in L2 cache on line filled by SIP_CONS prefetch request." + }, + { + "EventCode": "0x01ba", + "EventName": "PREFETCH_LATE_STORE_ISSUE", + "PublicDescription": "This event counts the number of demand requests that matches a Store-issue prefetcher's pending refill request. These are called late prefetch requests and are still counted as useful prefetcher requests for the sake of accuracy and coverage measurements." + }, + { + "EventCode": "0x01bb", + "EventName": "PREFETCH_LATE_STORE_STRIDE", + "PublicDescription": "This event counts the number of demand requests that matches a Store-stride prefetcher's pending refill request. These are called late prefetch requests and are still counted as useful prefetcher requests for the sake of accuracy and coverage measurements." + }, + { + "EventCode": "0x01bc", + "EventName": "PREFETCH_LATE_PC_OFFSET", + "PublicDescription": "This event counts the number of demand requests that matches a PC-offset prefetcher's pending refill request. These are called late prefetch requests and are still counted as useful prefetcher requests for the sake of accuracy and coverage measurements." + }, + { + "EventCode": "0x01bd", + "EventName": "PREFETCH_LATE_IFUPF", + "PublicDescription": "This event counts the number of demand requests that matches a IFU prefetcher's pending refill request. These are called late prefetch requests and are still counted as useful prefetcher requests for the sake of accuracy and coverage measurements." + }, + { + "EventCode": "0x01be", + "EventName": "PREFETCH_REFILL_STORE_ISSUE", + "PublicDescription": "This event counts the number of cache refills due to Store-Issue prefetcher." + }, + { + "EventCode": "0x01bf", + "EventName": "PREFETCH_REFILL_STORE_STRIDE", + "PublicDescription": "This event counts the number of cache refills due to Store-stride prefetcher." + }, + { + "EventCode": "0x01c0", + "EventName": "PREFETCH_REFILL_PC_OFFSET", + "PublicDescription": "This event counts the number of cache refills due to PC-offset prefetcher." + }, + { + "EventCode": "0x01c1", + "EventName": "PREFETCH_REFILL_IFUPF", + "PublicDescription": "This event counts the number of cache refills due to IFU prefetcher." + }, + { + "EventCode": "0x01c2", + "EventName": "CACHE_HIT_LINE_PF_STORE_ISSUE", + "PublicDescription": "This event counts the number of first hit to a cache line filled by Store-issue prefetcher." + }, + { + "EventCode": "0x01c3", + "EventName": "CACHE_HIT_LINE_PF_STORE_STRIDE", + "PublicDescription": "This event counts the number of first hit to a cache line filled by Store-stride prefetcher." + }, + { + "EventCode": "0x01c4", + "EventName": "CACHE_HIT_LINE_PF_PC_OFFSET", + "PublicDescription": "This event counts the number of first hit to a cache line filled by PC-offset prefetcher." + }, + { + "EventCode": "0x01c5", + "EventName": "CACHE_HIT_LINE_PF_IFUPF", + "PublicDescription": "This event counts the number of first hit to a cache line filled by IFU prefetcher." + }, + { + "EventCode": "0x01c6", + "EventName": "L2_PF_GEN_ST_ISSUE", + "PublicDescription": "Store-issue prefetch to L2 generated." + }, + { + "EventCode": "0x01c7", + "EventName": "L2_PF_GEN_ST_STRIDE", + "PublicDescription": "Store-stride prefetch to L2 generated" + }, + { + "EventCode": "0x01cb", + "EventName": "L2_TQ_OUTSTANDING", + "PublicDescription": "Outstanding tracker count, per cycle.\nThis event increments by the number of valid entries pertaining to this thread in the L2TQ, in each cycle.\nThis event can be used to calculate the occupancy of L2TQ by dividing this by the CPU_CYCLES event. The L2TQ queue tracks the outstanding Read, Write and Snoop transactions. The Read transaction and the Write transaction entries are attributable to PE, whereas the Snoop transactions are not always attributable to PE." + }, + { + "EventCode": "0x01cc", + "EventName": "TXREQ_LIMIT_COUNT_CYCLES", + "PublicDescription": "This event increments by the dynamic TXREQ value, in each cycle.\nThis is a companion event of TXREQ_LIMIT_MAX_CYCLES, TXREQ_LIMIT_3QUARTER_CYCLES, TXREQ_LIMIT_HALF_CYCLES, and TXREQ_LIMIT_1QUARTER_CYCLES." + }, + { + "EventCode": "0x01ce", + "EventName": "L3DPRFM_TO_L2PRQ_CONVERTED", + "PublicDescription": "This event counts the number of Converted-L3D-PRFMs. These are indeed L3D PRFM and activities around these PRFM are counted by the L3D_CACHE_PRFM, L3D_CACHE_REFILL_PRFM and L3D_CACHE_REFILL Events." + }, + { + "EventCode": "0x01d2", + "EventName": "DVM_TLBI_RCVD", + "PublicDescription": "This event counts the number of TLBI DVM message received over CHI interface, for *this* Core." + }, + { + "EventCode": "0x01d6", + "EventName": "DSB_COMMITING_LOCAL_TLBI", + "PublicDescription": "This event counts the number of DSB that are retired and committed at least one local TLBI instruction. This event increments no more than once (in a cycle) even if the DSB commits multiple local TLBI instruction." + }, + { + "EventCode": "0x01d7", + "EventName": "DSB_COMMITING_BROADCAST_TLBI", + "PublicDescription": "This event counts the number of DSB that are retired and committed at least one broadcast TLBI instruction. This event increments no more than once (in a cycle) even if the DSB commits multiple broadcast TLBI instruction." + }, + { + "EventCode": "0x01eb", + "EventName": "L1DPRFM_L2DPRFM_TO_L2PRQ_CONVERTED", + "PublicDescription": "This event counts the number of Converted-L1D-PRFMs and Converted-L2D-PRFM.\nActivities involving the Converted-L1D-PRFM are counted by the L1D_CACHE_PRFM. However they are *not* counted by the L1D_CACHE_REFILL_PRFM, and L1D_CACHE_REFILL, as these Converted-L1D-PRFM are treated as L2 D hardware prefetches. Activities around the Converted-L1D-PRFMs and Converted-L2D-PRFMs are counted by the L2D_CACHE_PRFM, L2D_CACHE_REFILL_PRFM and L2D_CACHE_REFILL Events." + }, + { + "EventCode": "0x01ec", + "EventName": "PREFETCH_LATE_CONVERTED_PRFM", + "PublicDescription": "This event counts the number of demand requests that matches a Converted-L1D-PRFM or Converted-L2D-PRFM pending refill request at L2 D-cache. These are called late prefetch requests and are still counted as useful prefetcher requests for the sake of accuracy and coverage measurements.\nNote that this event is not counted by the L2D_CACHE_HIT_RWL1PRF_LATE_HWPRF, though the Converted-L1D-PRFM or Converted-L2D-PRFM are replayed by the L2PRQ." + }, + { + "EventCode": "0x01ed", + "EventName": "PREFETCH_REFILL_CONVERTED_PRFM", + "PublicDescription": "This event counts the number of L2 D-cache refills due to Converted-L1D-PRFM or Converted-L2D-PRFM.\nNote : L2D_CACHE_REFILL_PRFM is inclusive of PREFETCH_REFILL_PRFM_CONVERTED, where both the PREFETCH_REFILL_PRFM_CONVERTED and the L2D_CACHE_REFILL_PRFM increment when L2 D-cache refills due to Converted-L1D-PRFM or Converted-L2D-PRFM." + }, + { + "EventCode": "0x01ee", + "EventName": "CACHE_HIT_LINE_PF_CONVERTED_PRFM", + "PublicDescription": "This event counts the number of first hit to a cache line filled by Converted-L1D-PRFM or Converted-L2D-PRFM.\nNote that L2D_CACHE_HIT_RWL1PRF_FPRFM is inclusive of CACHE_HIT_LINE_PF_CONVERTED_PRFM, where both the CACHE_HIT_LINE_PF_CONVERTED_PRFM and the L2D_CACHE_HIT_RWL1PRF_FPRFM increment on a first hit to L2 D-cache filled by Converted-L1D-PRFM or Converted-L2D-PRFM." + }, + { + "EventCode": "0x01f0", + "EventName": "TMS_ST_TO_SMT_LATENCY", + "PublicDescription": "This event counts the number of CPU cycles spent on TMS for ST-to-SMT switch.\nThis event is counted by both the threads - This event in both threads increment during TMS for ST-to-SMT switch." + }, + { + "EventCode": "0x01f1", + "EventName": "TMS_SMT_TO_ST_LATENCY", + "PublicDescription": "This event counts the number of CPU cycles spent on TMS for SMT-to-ST switch. The count also includes the CPU cycles spend due to an aborted SMT-to-ST TMS attempt.\nThis event is counted only by the thread that is not in WFI." + }, + { + "EventCode": "0x01f2", + "EventName": "TMS_ST_TO_SMT_COUNT", + "PublicDescription": "This event counts the number of completed TMS from ST-to-SMT.\nThis event is counted only by the active thread (the one that is not in WFI).\nNote: When an active thread enters the Debug state in ST-Full resource mode, it is switched to SMT mode. This is because the inactive thread cannot wake up while the other thread remains in the Debug state. To prEvent this issue, threads operating in ST-Full resource mode are transitioned to SMT mode upon entering Debug state. This event count will also reflect such switches from ST to SMT mode.\n(Also see the (NV_CPUACTLR14_EL1.chka_prEvent_st_tx_to_smt_when_tx_in_debug_state bit to disable this behavior.)" + }, + { + "EventCode": "0x01f3", + "EventName": "TMS_SMT_TO_ST_COUNT", + "PublicDescription": "This event counts the number of completed TMS from SMT-to-ST.\nThis event is counted only by the thread that is not in WFI." + }, + { + "EventCode": "0x01f4", + "EventName": "TMS_SMT_TO_ST_COUNT_ABRT", + "PublicDescription": "This event counts the number of aborted TMS from SMT-to-ST.\nThis event is counted only by the thread that is not in WFI." + }, + { + "EventCode": "0x0202", + "EventName": "L0I_CACHE_RD", + "PublicDescription": "This event counts the number of predict blocks serviced out of L0 I-cache.\nNote: The L0 I-cache performs at most 4 L0 I look-up in a cycle. Two of which are to service PB from L0 I. And the other two to refill L0 I-cache from L1 I. This event count only the L0 I-cache lookup pertaining to servicing the PB from L0 I." + }, + { + "EventCode": "0x0203", + "EventName": "L0I_CACHE_REFILL", + "PublicDescription": "This event counts the number of L0I cache refill from L1 I-cache." + }, + { + "EventCode": "0x0207", + "EventName": "INTR_LATENCY", + "PublicDescription": "This event counts the number of cycles elapsed between when an Interrupt is recognized (after masking) to when a uop associated with the first instruction in the destination exception level is allocated. If there is some other flush condition that pre-empts the Interrupt, then the cycles counted terminates early at the first instruction executed after that flush. In the event of dropped Interrupts (when an Interrupt is deasserted before it is taken), this counter measures the number of cycles that elapse from the moment an Interrupt is recognized (post-masking) until the Interrupt is dropped or deasserted.\nNote that\n* IESB(Implicit Error Synchronization Barrier) is an internal mop, so the latency of an implicit IESB mop executed before the Interrupt taken is included in the Interrupt latency count.\n* Nukes or TMS sequence within the window are also counted by the Interrupt latency Event.\n* A SMT to ST TMS will be aborted on detecting the wake condition for the WFI thread. The Interrupt latency count includes any additional penalty for an aborted TMS." + }, + { + "EventCode": "0x021c", + "EventName": "CWT_ALLOC_ENTRY", + "PublicDescription": "Cache Way Tracker Allocate entry." + }, + { + "EventCode": "0x021d", + "EventName": "CWT_ALLOC_LINE", + "PublicDescription": "Cache Way Tracker Allocate line." + }, + { + "EventCode": "0x021e", + "EventName": "CWT_HIT", + "PublicDescription": "Cache Way Tracker hit." + }, + { + "EventCode": "0x021f", + "EventName": "CWT_HIT_TAG", + "PublicDescription": "Cache Way Tracker hit when ITAG lookup suppressed." + }, + { + "EventCode": "0x0220", + "EventName": "CWT_REPLAY_TAG", + "PublicDescription": "Cache Way Tracker causes ITAG replay due to miss when ITAG lookup suppressed." + }, + { + "EventCode": "0x0250", + "EventName": "GPT_REQ", + "PublicDescription": "GPT lookup." + }, + { + "EventCode": "0x0251", + "EventName": "GPT_WC_HIT", + "PublicDescription": "GPT lookup hit in Walk cache." + }, + { + "EventCode": "0x0252", + "EventName": "GPT_PG_HIT", + "PublicDescription": "GPT lookup hit in TLB." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/retired.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/retired.json new file mode 100644 index 000000000000..34c7eefa66b0 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/retired.json @@ -0,0 +1,94 @@ +[ + { + "ArchStdEvent": "INST_RETIRED", + "PublicDescription": "This event counts instructions that have been architecturally executed." + }, + { + "ArchStdEvent": "CID_WRITE_RETIRED", + "PublicDescription": "This event counts architecturally executed writes to the CONTEXTIDR_EL1 register, which usually contains the kernel PID and can be output with hardware trace." + }, + { + "ArchStdEvent": "BR_IMMED_RETIRED", + "PublicDescription": "This event counts architecturally executed direct branches." + }, + { + "ArchStdEvent": "BR_RETURN_RETIRED", + "PublicDescription": "This event counts architecturally executed procedure returns." + }, + { + "ArchStdEvent": "TTBR_WRITE_RETIRED", + "PublicDescription": "This event counts architectural writes to TTBR0/1_EL1. If virtualization host extensions are enabled (by setting the HCR_EL2.E2H bit to 1), then accesses to TTBR0/1_EL1 that are redirected to TTBR0/1_EL2, or accesses to TTBR0/1_EL12, are counted. TTBRn registers are typically updated when the kernel is swapping user-space threads or applications." + }, + { + "ArchStdEvent": "BR_RETIRED", + "PublicDescription": "This event counts architecturally executed branches, whether the branch is taken or not. Instructions that explicitly write to the PC are also counted. Note that exception generating instructions, exception return instructions, and context synchronization instructions are not counted." + }, + { + "ArchStdEvent": "BR_MIS_PRED_RETIRED", + "PublicDescription": "This event counts branches counted by BR_RETIRED which were mispredicted and caused a pipeline flush." + }, + { + "ArchStdEvent": "OP_RETIRED", + "PublicDescription": "This event counts micro-operations that are architecturally executed. This is a count of number of micro-operations retired from the commit queue in a single cycle." + }, + { + "ArchStdEvent": "BR_INDNR_TAKEN_RETIRED", + "PublicDescription": "This event counts architecturally executed indirect branches excluding procedure returns that were taken." + }, + { + "ArchStdEvent": "BR_IMMED_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed direct branches that were correctly predicted." + }, + { + "ArchStdEvent": "BR_IMMED_MIS_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed direct branches that were mispredicted and caused a pipeline flush." + }, + { + "ArchStdEvent": "BR_IND_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed indirect branches including procedure returns that were correctly predicted." + }, + { + "ArchStdEvent": "BR_IND_MIS_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed indirect branches including procedure returns that were mispredicted and caused a pipeline flush." + }, + { + "ArchStdEvent": "BR_RETURN_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed procedure returns that were correctly predicted." + }, + { + "ArchStdEvent": "BR_RETURN_MIS_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed procedure returns that were mispredicted and caused a pipeline flush." + }, + { + "ArchStdEvent": "BR_INDNR_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed indirect branches excluding procedure returns that were correctly predicted." + }, + { + "ArchStdEvent": "BR_INDNR_MIS_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed indirect branches excluding procedure returns that were mispredicted and caused a pipeline flush." + }, + { + "ArchStdEvent": "BR_TAKEN_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed branches that were taken and were correctly predicted." + }, + { + "ArchStdEvent": "BR_TAKEN_MIS_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed branches that were taken and were mispredicted causing a pipeline flush." + }, + { + "ArchStdEvent": "BR_SKIP_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed branches that were not taken and were correctly predicted." + }, + { + "ArchStdEvent": "BR_SKIP_MIS_PRED_RETIRED", + "PublicDescription": "This event counts architecturally executed branches that were not taken and were mispredicted causing a pipeline flush." + }, + { + "ArchStdEvent": "BR_PRED_RETIRED", + "PublicDescription": "This event counts branch instructions counted by BR_RETIRED which were correctly predicted." + }, + { + "ArchStdEvent": "BR_IND_RETIRED", + "PublicDescription": "This event counts architecturally executed indirect branches including procedure returns." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/spe.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/spe.json new file mode 100644 index 000000000000..00d0c5051a48 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/spe.json @@ -0,0 +1,42 @@ +[ + { + "ArchStdEvent": "SAMPLE_POP", + "PublicDescription": "This event counts statistical profiling sample population, the count of all operations that could be sampled but may or may not be chosen for sampling." + }, + { + "ArchStdEvent": "SAMPLE_FEED", + "PublicDescription": "This event counts statistical profiling samples taken for sampling." + }, + { + "ArchStdEvent": "SAMPLE_FILTRATE", + "PublicDescription": "This event counts statistical profiling samples taken which are not removed by filtering." + }, + { + "ArchStdEvent": "SAMPLE_COLLISION", + "PublicDescription": "This event counts statistical profiling samples that have collided with a previous sample and so therefore not taken." + }, + { + "ArchStdEvent": "SAMPLE_FEED_BR", + "PublicDescription": "This event counts statistical profiling samples taken which are branches." + }, + { + "ArchStdEvent": "SAMPLE_FEED_LD", + "PublicDescription": "This event counts statistical profiling samples taken which are Loads or Load atomic operations." + }, + { + "ArchStdEvent": "SAMPLE_FEED_ST", + "PublicDescription": "This event counts statistical profiling samples taken which are Stores or Store atomic operations." + }, + { + "ArchStdEvent": "SAMPLE_FEED_OP", + "PublicDescription": "This event counts statistical profiling samples taken which are matching any operation type filters supported." + }, + { + "ArchStdEvent": "SAMPLE_FEED_EVENT", + "PublicDescription": "This event counts statistical profiling samples taken which are matching event packet filter constraints." + }, + { + "ArchStdEvent": "SAMPLE_FEED_LAT", + "PublicDescription": "This event counts statistical profiling samples taken which are exceeding minimum latency set by operation latency filter constraints." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/spec_operation.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/spec_operation.json new file mode 100644 index 000000000000..8bc802f5f350 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/spec_operation.json @@ -0,0 +1,230 @@ +[ + { + "ArchStdEvent": "INST_SPEC", + "PublicDescription": "This event counts operations that have been speculatively executed." + }, + { + "ArchStdEvent": "OP_SPEC", + "PublicDescription": "This event counts micro-operations speculatively executed. This is the count of the number of micro-operations dispatched in a cycle." + }, + { + "ArchStdEvent": "UNALIGNED_LD_SPEC", + "PublicDescription": "This event counts unaligned memory Read operations issued by the CPU. This event counts unaligned accesses (as defined by the actual instruction), even if they are subsequently issued as multiple aligned accesses.\nThis event does not count preload operations (PLD, PLI).\nThis event is a subset of the UNALIGNED_LDST_SPEC event." + }, + { + "ArchStdEvent": "UNALIGNED_ST_SPEC", + "PublicDescription": "This event counts unaligned memory Write operations issued by the CPU. This event counts unaligned accesses (as defined by the actual instruction), even if they are subsequently issued as multiple aligned accesses.\nThis event is a subset of the UNALIGNED_LDST_SPEC event." + }, + { + "ArchStdEvent": "UNALIGNED_LDST_SPEC", + "PublicDescription": "This event counts unaligned memory operations issued by the CPU. This event counts unaligned accesses (as defined by the actual instruction), even if they are subsequently issued as multiple aligned accesses.\nThis event is the sum of the following events:\nUNALIGNED_ST_SPEC and\nUNALIGNED_LD_SPEC." + }, + { + "ArchStdEvent": "LDREX_SPEC", + "PublicDescription": "This event counts Load-Exclusive operations that have been speculatively executed. For example: LDREX, LDX" + }, + { + "ArchStdEvent": "STREX_PASS_SPEC", + "PublicDescription": "This event counts Store-exclusive operations that have been speculatively executed and have successfully completed the Store operation." + }, + { + "ArchStdEvent": "STREX_FAIL_SPEC", + "PublicDescription": "This event counts Store-exclusive operations that have been speculatively executed and have not successfully completed the Store operation." + }, + { + "ArchStdEvent": "STREX_SPEC", + "PublicDescription": "This event counts Store-exclusive operations that have been speculatively executed.\nThis event is the sum of the following events:\nSTREX_PASS_SPEC and\nSTREX_FAIL_SPEC." + }, + { + "ArchStdEvent": "LD_SPEC", + "PublicDescription": "This event counts speculatively executed Load operations including Single Instruction Multiple Data (SIMD) Load operations." + }, + { + "ArchStdEvent": "ST_SPEC", + "PublicDescription": "This event counts speculatively executed Store operations including Single Instruction Multiple Data (SIMD) Store operations." + }, + { + "ArchStdEvent": "LDST_SPEC", + "PublicDescription": "This event counts Load and Store operations that have been speculatively executed." + }, + { + "ArchStdEvent": "DP_SPEC", + "PublicDescription": "This event counts speculatively executed logical or arithmetic instructions such as MOV/MVN operations." + }, + { + "ArchStdEvent": "ASE_SPEC", + "PublicDescription": "This event counts speculatively executed Advanced SIMD operations excluding Load, Store, and Move micro-operations that move data to or from SIMD (vector) registers." + }, + { + "ArchStdEvent": "VFP_SPEC", + "PublicDescription": "This event counts speculatively executed floating point operations. This event does not count operations that move data to or from floating point (vector) registers." + }, + { + "ArchStdEvent": "PC_WRITE_SPEC", + "PublicDescription": "This event counts speculatively executed operations which cause software changes of the PC. Those operations include all taken branch operations." + }, + { + "ArchStdEvent": "CRYPTO_SPEC", + "PublicDescription": "This event counts speculatively executed cryptographic operations except for PMULL and VMULL operations." + }, + { + "ArchStdEvent": "BR_IMMED_SPEC", + "PublicDescription": "This event counts direct branch operations which are speculatively executed." + }, + { + "ArchStdEvent": "BR_RETURN_SPEC", + "PublicDescription": "This event counts procedure return operations (RET, RETAA and RETAB) which are speculatively executed." + }, + { + "ArchStdEvent": "BR_INDIRECT_SPEC", + "PublicDescription": "This event counts indirect branch operations including procedure returns, which are speculatively executed. This includes operations that force a software change of the PC, other than exception-generating operations and direct branch instructions. Some examples of the instructions counted by this event include BR Xn, RET, etc." + }, + { + "ArchStdEvent": "ISB_SPEC", + "PublicDescription": "This event counts ISB operations that are executed." + }, + { + "ArchStdEvent": "DSB_SPEC", + "PublicDescription": "This event counts DSB operations that are speculatively issued to Load/Store unit in the CPU." + }, + { + "ArchStdEvent": "DMB_SPEC", + "PublicDescription": "This event counts DMB operations that are speculatively issued to the Load/Store unit in the CPU. This event does not count implied barriers from Load-acquire/Store-release operations." + }, + { + "ArchStdEvent": "CSDB_SPEC", + "PublicDescription": "This event counts CSDB operations that are speculatively issued to the Load/Store unit in the CPU. This event does not count implied barriers from Load-acquire/Store-release operations." + }, + { + "ArchStdEvent": "RC_LD_SPEC", + "PublicDescription": "This event counts any Load acquire operations that are speculatively executed. For example: LDAR, LDARH, LDARB" + }, + { + "ArchStdEvent": "RC_ST_SPEC", + "PublicDescription": "This event counts any Store release operations that are speculatively executed. For example: STLR, STLRH, STLRB" + }, + { + "ArchStdEvent": "SIMD_INST_SPEC", + "PublicDescription": "This event counts speculatively executed operations that are SIMD or SVE vector operations or Advanced SIMD non-scalar operations." + }, + { + "ArchStdEvent": "ASE_INST_SPEC", + "PublicDescription": "This event counts speculatively executed Advanced SIMD operations." + }, + { + "ArchStdEvent": "SVE_INST_SPEC", + "PublicDescription": "This event counts speculatively executed operations that are SVE operations." + }, + { + "ArchStdEvent": "INT_SPEC", + "PublicDescription": "This event counts speculatively executed integer arithmetic operations." + }, + { + "ArchStdEvent": "SVE_PRED_SPEC", + "PublicDescription": "This event counts speculatively executed predicated SVE operations.\nThis counter also counts SVE operation due to instruction with Governing predicate operand that determines the Active elements that do not write to any SVE Z vector destination register using either zeroing or merging predicate. Thus, the operations due to instructions such as INCP, DECP, UQINCP, UQDECP, SQINCP, SQDECP and PNEXT, are counted by the SVE_PRED_* events." + }, + { + "ArchStdEvent": "SVE_PRED_EMPTY_SPEC", + "PublicDescription": "This event counts speculatively executed predicated SVE operations with no active predicate elements.\nThis counter also counts SVE operation due to instruction with Governing predicate operand that determines the Active elements that do not write to any SVE Z vector destination register using either zeroing or merging predicate. Thus, the operations due to instructions such as INCP, DECP, UQINCP, UQDECP, SQINCP, SQDECP and PNEXT, are counted by the SVE_PRED_* events." + }, + { + "ArchStdEvent": "SVE_PRED_FULL_SPEC", + "PublicDescription": "This event counts speculatively executed predicated SVE operations with all predicate elements active.\nThis counter also counts SVE operation due to instruction with Governing predicate operand that determines the Active elements that do not write to any SVE Z vector destination register using either zeroing or merging predicate. Thus, the operations due to instructions such as INCP, DECP, UQINCP, UQDECP, SQINCP, SQDECP and PNEXT, are counted by the SVE_PRED_* events." + }, + { + "ArchStdEvent": "SVE_PRED_PARTIAL_SPEC", + "PublicDescription": "This event counts speculatively executed predicated SVE operations with at least one but not all active predicate elements.\nThis counter also counts SVE operation due to instruction with Governing predicate operand that determines the Active elements that do not write to any SVE Z vector destination register using either zeroing or merging predicate. Thus, the operations due to instructions such as INCP, DECP, UQINCP, UQDECP, SQINCP, SQDECP and PNEXT, are counted by the SVE_PRED_* events." + }, + { + "ArchStdEvent": "SVE_PRED_NOT_FULL_SPEC", + "PublicDescription": "This event counts speculatively executed predicated SVE operations with at least one non active predicate elements.\nThis counter also counts SVE operation due to instruction with Governing predicate operand that determines the Active elements that do not write to any SVE Z vector destination register using either zeroing or merging predicate. Thus, the operations due to instructions such as INCP, DECP, UQINCP, UQDECP, SQINCP, SQDECP and PNEXT, are counted by the SVE_PRED_* events." + }, + { + "ArchStdEvent": "PRF_SPEC", + "PublicDescription": "This event counts speculatively executed operations that prefetch memory. For example, Scalar: PRFM, SVE: PRFB, PRFD, PRFH, or PRFW." + }, + { + "ArchStdEvent": "SVE_LDFF_SPEC", + "PublicDescription": "This event counts speculatively executed SVE first fault or non-fault Load operations." + }, + { + "ArchStdEvent": "SVE_LDFF_FAULT_SPEC", + "PublicDescription": "This event counts speculatively executed SVE first fault or non-fault Load operations that clear at least one bit in the FFR." + }, + { + "ArchStdEvent": "ASE_SVE_INT8_SPEC", + "PublicDescription": "This event counts speculatively executed Advanced SIMD or SVE integer operations with the largest data type being an 8-bit integer." + }, + { + "ArchStdEvent": "ASE_SVE_INT16_SPEC", + "PublicDescription": "This event counts speculatively executed Advanced SIMD or SVE integer operations with the largest data type a 16-bit integer." + }, + { + "ArchStdEvent": "ASE_SVE_INT32_SPEC", + "PublicDescription": "This event counts speculatively executed Advanced SIMD or SVE integer operations with the largest data type a 32-bit integer." + }, + { + "ArchStdEvent": "ASE_SVE_INT64_SPEC", + "PublicDescription": "This event counts speculatively executed Advanced SIMD or SVE integer operations with the largest data type a 64-bit integer." + }, + { + "EventCode": "0x011d", + "EventName": "SPEC_RET_STACK_FULL", + "PublicDescription": "This event counts predict pipe stalls due to speculative return address predictor full." + }, + { + "EventCode": "0x011f", + "EventName": "MOPS_SPEC", + "PublicDescription": "Macro-ops speculatively decoded." + }, + { + "EventCode": "0x0180", + "EventName": "BR_SPEC_PRED_TAKEN", + "PublicDescription": "Number of predicted taken from branch predictor." + }, + { + "EventCode": "0x0181", + "EventName": "BR_SPEC_PRED_TAKEN_FROM_L2BTB", + "PublicDescription": "Number of predicted taken branch from L2 BTB." + }, + { + "EventCode": "0x0182", + "EventName": "BR_SPEC_PRED_TAKEN_MULTI", + "PublicDescription": "Number of predicted taken for polymorphic branch." + }, + { + "EventCode": "0x0185", + "EventName": "BR_SPEC_PRED_STATIC", + "PublicDescription": "Number of post fetch prediction." + }, + { + "EventCode": "0x01d0", + "EventName": "TLBI_LOCAL_SPEC", + "PublicDescription": "A non-broadcast TLBI instruction executed (Speculatively or otherwise) on *this* PE." + }, + { + "EventCode": "0x01d1", + "EventName": "TLBI_BROADCAST_SPEC", + "PublicDescription": "A broadcast TLBI instruction executed (Speculatively or otherwise) on *this* PE." + }, + { + "EventCode": "0x01e7", + "EventName": "BR_SPEC_PRED_ALN_REDIR", + "PublicDescription": "BPU predict pipe align redirect (either AL-APQ hit/miss)." + }, + { + "EventCode": "0x0200", + "EventName": "SIMD_CRYPTO_INST_SPEC", + "PublicDescription": "SIMD, SVE, and CRYPTO instructions speculatively decoded." + }, + { + "EventCode": "0x022e", + "EventName": "VPRED_LD_SPEC", + "PublicDescription": "This event counts the number of Speculatively-executed-Load operations with addresses produced by the value-prediction mechanism. The loaded data might be discarded if the predicted address differs from the actual address." + }, + { + "EventCode": "0x022f", + "EventName": "VPRED_LD_SPEC_MISMATCH", + "PublicDescription": "This event counts a subset of VPRED_LD_SPEC where the predicted Load address and the actual address mismatched." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/stall.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/stall.json new file mode 100644 index 000000000000..92d9e0866c24 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/stall.json @@ -0,0 +1,145 @@ +[ + { + "ArchStdEvent": "STALL_FRONTEND", + "PublicDescription": "This event counts cycles when frontend could not send any micro-operations to the rename stage because of frontend resource stalls caused by fetch memory latency or branch prediction flow stalls. STALL_FRONTEND_SLOTS counts SLOTS during the cycle when this event counts. STALL_SLOT_FRONTEND will count SLOTS when this event is counted on this CPU." + }, + { + "ArchStdEvent": "STALL_BACKEND", + "PublicDescription": "This event counts cycles whenever the rename unit is unable to send any micro-operations to the backend of the pipeline because of backend resource constraints. Backend resource constraints can include issue stage fullness, execution stage fullness, or other internal pipeline resource fullness. All the backend slots were empty during the cycle when this event counts." + }, + { + "ArchStdEvent": "STALL", + "PublicDescription": "This event counts cycles when no operations are sent to the rename unit from the frontend or from the rename unit to the backend for any reason (either frontend or backend stall). This event is the sum of the following events:\nSTALL_FRONTEND and\nSTALL_BACKEND." + }, + { + "ArchStdEvent": "STALL_SLOT_BACKEND", + "PublicDescription": "This event counts slots per cycle in which no operations are sent from the rename unit to the backend due to backend resource constraints. STALL_BACKEND counts during the cycle when STALL_SLOT_BACKEND counts at least 1. STALL_BACKEND counts during the cycle when STALL_SLOT_BACKEND is SLOTS." + }, + { + "ArchStdEvent": "STALL_SLOT_FRONTEND", + "PublicDescription": "This event counts slots per cycle in which no operations are sent to the rename unit from the frontend due to frontend resource constraints. STALL_FRONTEND counts during the cycle when STALL_SLOT_FRONTEND is SLOTS." + }, + { + "ArchStdEvent": "STALL_SLOT", + "PublicDescription": "This event counts slots per cycle in which no operations are sent to the rename unit from the frontend or from the rename unit to the backend for any reason (either frontend or backend stall).\nSTALL_SLOT is the sum of the following events:\nSTALL_SLOT_FRONTEND and\nSTALL_SLOT_BACKEND." + }, + { + "ArchStdEvent": "STALL_BACKEND_MEM", + "PublicDescription": "This event counts cycles when the backend is stalled because there is a pending demand Load request in progress in the last level Core cache.\nLast level cache in this CPU is Level 2, hence this event counts same as STALL_BACKEND_L2D." + }, + { + "ArchStdEvent": "STALL_FRONTEND_MEMBOUND", + "PublicDescription": "This event counts cycles when the frontend could not send any micro-operations to the rename stage due to resource constraints in the memory resources." + }, + { + "ArchStdEvent": "STALL_FRONTEND_L1I", + "PublicDescription": "This event counts cycles when the frontend is stalled because there is an instruction fetch request pending in the L1 I-cache." + }, + { + "ArchStdEvent": "STALL_FRONTEND_MEM", + "PublicDescription": "This event counts cycles when the frontend is stalled because there is an instruction fetch request pending in the last level Core cache.\nLast level cache in this CPU is Level 2, hence this event counts rather than STALL_FRONTEND_L2I." + }, + { + "ArchStdEvent": "STALL_FRONTEND_TLB", + "PublicDescription": "This event counts when the frontend is stalled on any TLB misses being handled. This event also counts the TLB accesses made by hardware prefetches." + }, + { + "ArchStdEvent": "STALL_FRONTEND_CPUBOUND", + "PublicDescription": "This event counts cycles when the frontend could not send any micro-operations to the rename stage due to resource constraints in the CPU resources excluding memory resources." + }, + { + "ArchStdEvent": "STALL_FRONTEND_FLOW", + "PublicDescription": "This event counts cycles when the frontend could not send any micro-operations to the rename stage due to resource constraints in the branch prediction unit." + }, + { + "ArchStdEvent": "STALL_FRONTEND_FLUSH", + "PublicDescription": "This event counts cycles when the frontend could not send any micro-operations to the rename stage as the frontend is recovering from a machine flush or resteer. Example scenarios that cause a flush include branch mispredictions, taken exceptions, microarchitectural flush etc." + }, + { + "ArchStdEvent": "STALL_BACKEND_MEMBOUND", + "PublicDescription": "This event counts cycles when the backend could not accept any micro-operations due to resource constraints in the memory resources." + }, + { + "ArchStdEvent": "STALL_BACKEND_L1D", + "PublicDescription": "This event counts cycles when the backend is stalled because there is a pending demand Load request in progress in the L1 D-cache." + }, + { + "ArchStdEvent": "STALL_BACKEND_TLB", + "PublicDescription": "This event counts cycles when the backend is stalled on any demand TLB misses being handled." + }, + { + "ArchStdEvent": "STALL_BACKEND_ST", + "PublicDescription": "This event counts cycles when the backend is stalled and there is a Store that has not reached the pre-commit stage." + }, + { + "ArchStdEvent": "STALL_BACKEND_CPUBOUND", + "PublicDescription": "This event counts cycles when the backend could not accept any micro-operations due to any resource constraints in the CPU excluding memory resources." + }, + { + "ArchStdEvent": "STALL_BACKEND_BUSY", + "PublicDescription": "This event counts cycles when the backend could not accept any micro-operations because the issue queues are full to take any operations for execution." + }, + { + "ArchStdEvent": "STALL_BACKEND_ILOCK", + "PublicDescription": "This event counts cycles when the backend could not accept any micro-operations due to resource constraints imposed by input dependency." + }, + { + "ArchStdEvent": "STALL_BACKEND_RENAME", + "PublicDescription": "This event counts cycles when backend is stalled even when operations are available from the frontend but at least one is not ready to be sent to the backend because no rename register is available." + }, + { + "EventCode": "0x0158", + "EventName": "FLAG_DISP_STALL", + "PublicDescription": "Rename stalled due to FRF(Flag register file) full." + }, + { + "EventCode": "0x0159", + "EventName": "GEN_DISP_STALL", + "PublicDescription": "Rename stalled due to GRF (General-purpose register file) full." + }, + { + "EventCode": "0x015a", + "EventName": "VEC_DISP_STALL", + "PublicDescription": "Rename stalled due to VRF (Vector register file) full." + }, + { + "EventCode": "0x015c", + "EventName": "SX_IQ_STALL", + "PublicDescription": "Dispatch stalled due to IQ full, SX." + }, + { + "EventCode": "0x015d", + "EventName": "MX_IQ_STALL", + "PublicDescription": "Dispatch stalled due to IQ full, MX." + }, + { + "EventCode": "0x015e", + "EventName": "LS_IQ_STALL", + "PublicDescription": "Dispatch stalled due to IQ full, LS." + }, + { + "EventCode": "0x015f", + "EventName": "VX_IQ_STALL", + "PublicDescription": "Dispatch stalled due to IQ full, VX." + }, + { + "EventCode": "0x0160", + "EventName": "MCQ_FULL_STALL", + "PublicDescription": "Dispatch stalled due to MCQ full." + }, + { + "EventCode": "0x01cf", + "EventName": "PRD_DISP_STALL", + "PublicDescription": "Rename stalled due to predicate registers (physical) are full." + }, + { + "EventCode": "0x01e0", + "EventName": "CSDB_STALL", + "PublicDescription": "Rename stalled due to CSDB." + }, + { + "EventCode": "0x01e2", + "EventName": "STALL_SLOT_FRONTEND_WITHOUT_MISPRED", + "PublicDescription": "Stall slot frontend during non-mispredicted branch.\nThis event counts the STALL_STOT_FRONTEND Events, except for the 4 cycles following a mispredicted branch Event or 4 cycles following a commit flush&restart Event." + } +] diff --git a/tools/perf/pmu-events/arch/arm64/nvidia/t410/tlb.json b/tools/perf/pmu-events/arch/arm64/nvidia/t410/tlb.json new file mode 100644 index 000000000000..18ec5c348c87 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/nvidia/t410/tlb.json @@ -0,0 +1,158 @@ +[ + { + "ArchStdEvent": "L1I_TLB_REFILL", + "PublicDescription": "This event counts L1 Instruction TLB refills from any instruction fetch (demand, hardware prefetch, and software preload accesses). If there are multiple misses in the TLB that are resolved by the refill, then this event only counts once. This event will not count if the translation table walk results in a fault (such as a translation or access fault), since there is no new translation created for the TLB." + }, + { + "ArchStdEvent": "L1D_TLB_REFILL", + "PublicDescription": "This event counts L1 Data TLB accesses that resulted in TLB refills. If there are multiple misses in the TLB that are resolved by the refill, then this event only counts once. This event counts for refills caused by preload instructions or hardware prefetch accesses. This event counts regardless of whether the miss hits in L2 or results in a translation table walk. This event will not count if the translation table walk results in a fault (such as a translation or access fault), since there is no new translation created for the TLB. This event will not count on an access from an AT (Address Translation) instruction.\nThis event counts the sum of the following events:\nL1D_TLB_REFILL_RD and\nL1D_TLB_REFILL_WR." + }, + { + "ArchStdEvent": "L1D_TLB", + "PublicDescription": "This event counts L1 Data TLB accesses caused by any memory Load or Store operation.\nNote that Load or Store instructions can be broken up into multiple memory operations.\nThis event does not count TLB maintenance operations." + }, + { + "ArchStdEvent": "L1I_TLB", + "PublicDescription": "This event counts L1 instruction TLB accesses (caused by demand or hardware prefetch or software preload accesses), whether the access hits or misses in the TLB. This event counts both demand accesses and prefetch or preload generated accesses.\nThis event is a superset of the L1I_TLB_REFILL event." + }, + { + "ArchStdEvent": "L2D_TLB_REFILL", + "PublicDescription": "This event counts L2 TLB refills caused by memory operations from both data and instruction fetch, except for those caused by TLB maintenance operations and hardware prefetches.\nThis event is the sum of the following events:\nL2D_TLB_REFILL_RD and\nL2D_TLB_REFILL_WR." + }, + { + "ArchStdEvent": "L2D_TLB", + "PublicDescription": "This event counts L2 TLB accesses except those caused by TLB maintenance operations.\nThis event is the sum of the following events:\nL2D_TLB_RD and\nL2D_TLB_WR." + }, + { + "ArchStdEvent": "DTLB_WALK", + "PublicDescription": "This event counts number of demand data translation table walks caused by a miss in the L2 TLB and performing at least one memory access. Translation table walks are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD. Note that partial translations that cause a translation table walk are also counted. Also note that this event counts walks triggered by software preloads, but not walks triggered by hardware prefetchers, and that this event does not count walks triggered by TLB maintenance operations.\nThis event does not include prefetches." + }, + { + "ArchStdEvent": "ITLB_WALK", + "PublicDescription": "This event counts number of instruction translation table walks caused by a miss in the L2 TLB and performing at least one memory access. Translation table walks are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD. Note that partial translations that cause a translation table walk are also counted. Also note that this event does not count walks triggered by TLB maintenance operations.\nThis event does not include prefetches." + }, + { + "ArchStdEvent": "L1D_TLB_REFILL_RD", + "PublicDescription": "This event counts L1 Data TLB refills caused by memory Read operations. If there are multiple misses in the TLB that are resolved by the refill, then this event only counts once. This event counts for refills caused by preload instructions or hardware prefetch accesses. This event counts regardless of whether the miss hits in L2 or results in a translation table walk. This event will not count if the translation table walk results in a fault (such as a translation or access fault), since there is no new translation created for the TLB. This event will not count on an access from an Address Translation (AT) instruction.\nThis event is a subset of the L1D_TLB_REFILL event." + }, + { + "ArchStdEvent": "L1D_TLB_REFILL_WR", + "PublicDescription": "This event counts L1 Data TLB refills caused by data side memory Write operations. If there are multiple misses in the TLB that are resolved by the refill, then this event only counts once. This event counts for refills caused by preload instructions or hardware prefetch accesses. This event counts regardless of whether the miss hits in L2 or results in a translation table walk. This event will not count if the table walk results in a fault (such as a translation or access fault), since there is no new translation created for the TLB. This event will not count with an access from an Address Translation (AT) instruction.\nThis event is a subset of the L1D_TLB_REFILL event." + }, + { + "ArchStdEvent": "L1D_TLB_RD", + "PublicDescription": "This event counts L1 Data TLB accesses caused by memory Read operations. This event counts whether the access hits or misses in the TLB. This event does not count TLB maintenance operations." + }, + { + "ArchStdEvent": "L1D_TLB_WR", + "PublicDescription": "This event counts any L1 Data side TLB accesses caused by memory Write operations. This event counts whether the access hits or misses in the TLB. This event does not count TLB maintenance operations." + }, + { + "ArchStdEvent": "L2D_TLB_REFILL_RD", + "PublicDescription": "This event counts L2 TLB refills caused by memory Read operations from both data and instruction fetch except for those caused by TLB maintenance operations or hardware prefetches.\nThis event is a subset of the L2D_TLB_REFILL event." + }, + { + "ArchStdEvent": "L2D_TLB_REFILL_WR", + "PublicDescription": "This event counts L2 TLB refills caused by memory Write operations from both data and instruction fetch except for those caused by TLB maintenance operations.\nThis event is a subset of the L2D_TLB_REFILL event." + }, + { + "ArchStdEvent": "L2D_TLB_RD", + "PublicDescription": "This event counts L2 TLB accesses caused by memory Read operations from both data and instruction fetch except for those caused by TLB maintenance operations.\nThis event is a subset of the L2D_TLB event." + }, + { + "ArchStdEvent": "L2D_TLB_WR", + "PublicDescription": "This event counts L2 TLB accesses caused by memory Write operations from both data and instruction fetch except for those caused by TLB maintenance operations.\nThis event is a subset of the L2D_TLB event." + }, + { + "ArchStdEvent": "DTLB_WALK_PERCYC", + "PublicDescription": "This event counts the number of data translation table walks in progress per cycle." + }, + { + "ArchStdEvent": "ITLB_WALK_PERCYC", + "PublicDescription": "This event counts the number of instruction translation table walks in progress per cycle." + }, + { + "ArchStdEvent": "L1D_TLB_RW", + "PublicDescription": "This event counts L1 Data TLB demand accesses caused by memory Read or Write operations. This event counts whether the access hits or misses in the TLB. This event does not count TLB maintenance operations." + }, + { + "ArchStdEvent": "L1I_TLB_RD", + "PublicDescription": "This event counts L1 Instruction TLB demand accesses whether the access hits or misses in the TLB." + }, + { + "ArchStdEvent": "L1D_TLB_PRFM", + "PublicDescription": "This event counts L1 Data TLB accesses generated by software prefetch or preload memory accesses. Load or Store instructions can be broken into multiple memory operations. This event does not count TLB maintenance operations." + }, + { + "ArchStdEvent": "L1I_TLB_PRFM", + "PublicDescription": "This event counts L1 Instruction TLB accesses generated by software preload or prefetch instructions. This event counts whether the access hits or misses in the TLB. This event does not count TLB maintenance operations." + }, + { + "ArchStdEvent": "DTLB_HWUPD", + "PublicDescription": "This event counts number of memory accesses triggered by a data translation table walk and performing an update of a translation table entry. Memory accesses are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD. Note that this event counts accesses triggered by software preloads, but not accesses triggered by hardware prefetchers." + }, + { + "ArchStdEvent": "ITLB_HWUPD", + "PublicDescription": "This event counts number of memory accesses triggered by an instruction translation table walk and performing an update of a translation table entry. Memory accesses are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD." + }, + { + "ArchStdEvent": "DTLB_STEP", + "PublicDescription": "This event counts number of memory accesses triggered by a demand data translation table walk and performing a Read of a translation table entry. Memory accesses are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD.\nNote that this event counts accesses triggered by software preloads, but not accesses triggered by hardware prefetchers." + }, + { + "ArchStdEvent": "ITLB_STEP", + "PublicDescription": "This event counts number of memory accesses triggered by an instruction translation table walk and performing a Read of a translation table entry. Memory accesses are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD." + }, + { + "ArchStdEvent": "DTLB_WALK_LARGE", + "PublicDescription": "This event counts number of demand data translation table walks caused by a miss in the L2 TLB and yielding a large page. The set of large pages is defined as all pages with a final size higher than or equal to 2MB. Translation table walks that end up taking a translation fault are not counted, as the page size would be undefined in that case. If DTLB_WALK_BLOCK is implemented, then it is an alias for this event in this family.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event counts walks triggered by software preloads, but not walks triggered by hardware prefetchers, and that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "ITLB_WALK_LARGE", + "PublicDescription": "This event counts number of instruction translation table walks caused by a miss in the L2 TLB and yielding a large page. The set of large pages is defined as all pages with a final size higher than or equal to 2MB. Translation table walks that end up taking a translation fault are not counted, as the page size would be undefined in that case. In this family, this is equal to ITLB_WALK_BLOCK event.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "DTLB_WALK_SMALL", + "PublicDescription": "This event counts number of data translation table walks caused by a miss in the L2 TLB and yielding a small page. The set of small pages is defined as all pages with a final size lower than 2MB. Translation table walks that end up taking a translation fault are not counted, as the page size would be undefined in that case. If DTLB_WALK_PAGE event is implemented, then it is an alias for this event in this family. Note that partial translations that cause a translation table walk are also counted.\nAlso note that this event counts walks triggered by software preloads, but not walks triggered by hardware prefetchers, and that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "ITLB_WALK_SMALL", + "PublicDescription": "This event counts number of instruction translation table walks caused by a miss in the L2 TLB and yielding a small page. The set of small pages is defined as all pages with a final size lower than 2MB. Translation table walks that end up taking a translation fault are not counted, as the page size would be undefined in that case. In this family, this is equal to ITLB_WALK_PAGE event.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "DTLB_WALK_RW", + "PublicDescription": "This event counts number of demand data translation table walks caused by a miss in the L2 TLB and performing at least one memory access. Translation table walks are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "ITLB_WALK_RD", + "PublicDescription": "This event counts number of demand instruction translation table walks caused by a miss in the L2 TLB and performing at least one memory access. Translation table walks are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "DTLB_WALK_PRFM", + "PublicDescription": "This event counts number of software prefetches or preloads generated data translation table walks caused by a miss in the L2 TLB and performing at least one memory access. Translation table walks are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event does not count walks triggered by TLB maintenance operations." + }, + { + "ArchStdEvent": "ITLB_WALK_PRFM", + "PublicDescription": "This event counts number of software prefetches or preloads generated instruction translation table walks caused by a miss in the L2 TLB and performing at least one memory access. Translation table walks are counted even if the translation ended up taking a translation fault for reasons different than EPD, E0PD and NFD.\nNote that partial translations that cause a translation table walk are also counted.\nAlso note that this event does not count walks triggered by TLB maintenance operations." + }, + { + "EventCode": "0x010e", + "EventName": "L1D_TLB_REFILL_RD_PF", + "PublicDescription": "L1 Data TLB refill, Read, prefetch." + }, + { + "EventCode": "0x010f", + "EventName": "L2TLB_PF_REFILL", + "PublicDescription": "L2 Data TLB refill, Read, prefetch.\nThis event counts MMU refills due to internal PFStream requests." + }, + { + "EventCode": "0x0223", + "EventName": "L1I_TLB_REFILL_RD", + "PublicDescription": "L1 Instruction TLB refills due to Demand miss." + }, + { + "EventCode": "0x0224", + "EventName": "L1I_TLB_REFILL_PRFM", + "PublicDescription": "L1 Instruction TLB refills due to Software prefetch miss." + } +] diff --git a/tools/perf/pmu-events/arch/common/common/metrics.json b/tools/perf/pmu-events/arch/common/common/metrics.json index 0d010b3ebc6d..cefc8bfe7830 100644 --- a/tools/perf/pmu-events/arch/common/common/metrics.json +++ b/tools/perf/pmu-events/arch/common/common/metrics.json @@ -46,14 +46,14 @@ }, { "BriefDescription": "Max front or backend stalls per instruction", - "MetricExpr": "max(stalled\\-cycles\\-frontend, stalled\\-cycles\\-backend) / instructions", + "MetricExpr": "(max(stalled\\-cycles\\-frontend, stalled\\-cycles\\-backend) / instructions) if (has_event(stalled\\-cycles\\-frontend) & has_event(stalled\\-cycles\\-backend)) else ((stalled\\-cycles\\-frontend / instructions) if has_event(stalled\\-cycles\\-frontend) else ((stalled\\-cycles\\-backend / instructions) if has_event(stalled\\-cycles\\-backend) else 0))", "MetricGroup": "Default", "MetricName": "stalled_cycles_per_instruction", "DefaultShowEvents": "1" }, { "BriefDescription": "Frontend stalls per cycle", - "MetricExpr": "stalled\\-cycles\\-frontend / cpu\\-cycles", + "MetricExpr": "(stalled\\-cycles\\-frontend / cpu\\-cycles) if has_event(stalled\\-cycles\\-frontend) else 0", "MetricGroup": "Default", "MetricName": "frontend_cycles_idle", "MetricThreshold": "frontend_cycles_idle > 0.1", @@ -61,7 +61,7 @@ }, { "BriefDescription": "Backend stalls per cycle", - "MetricExpr": "stalled\\-cycles\\-backend / cpu\\-cycles", + "MetricExpr": "(stalled\\-cycles\\-backend / cpu\\-cycles) if has_event(stalled\\-cycles\\-backend) else 0", "MetricGroup": "Default", "MetricName": "backend_cycles_idle", "MetricThreshold": "backend_cycles_idle > 0.2", diff --git a/tools/perf/pmu-events/arch/x86/alderlake/cache.json b/tools/perf/pmu-events/arch/x86/alderlake/cache.json index be15a7f83717..5d0d824f3e7e 100644 --- a/tools/perf/pmu-events/arch/x86/alderlake/cache.json +++ b/tools/perf/pmu-events/arch/x86/alderlake/cache.json @@ -876,105 +876,97 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 128 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_128", "MSRIndex": "0x3F6", "MSRValue": "0x80", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 128 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 16 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_16", "MSRIndex": "0x3F6", "MSRValue": "0x10", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 16 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 256 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_256", "MSRIndex": "0x3F6", "MSRValue": "0x100", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 256 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 32 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_32", "MSRIndex": "0x3F6", "MSRValue": "0x20", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 32 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 4 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_4", "MSRIndex": "0x3F6", "MSRValue": "0x4", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 4 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 512 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_512", "MSRIndex": "0x3F6", "MSRValue": "0x200", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 512 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 64 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_64", "MSRIndex": "0x3F6", "MSRValue": "0x40", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 64 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 8 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_8", "MSRIndex": "0x3F6", "MSRValue": "0x8", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 8 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" @@ -1030,12 +1022,11 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of stores uops retired. Counts with or without PEBS enabled.", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.STORE_LATENCY", - "PublicDescription": "Counts the number of stores uops retired. Counts with or without PEBS enabled. If PEBS is enabled and a PEBS record is generated, will populate PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x6", "Unit": "cpu_atom" diff --git a/tools/perf/pmu-events/arch/x86/alderlake/frontend.json b/tools/perf/pmu-events/arch/x86/alderlake/frontend.json index ff3b30c2619a..11fc853f2d0b 100644 --- a/tools/perf/pmu-events/arch/x86/alderlake/frontend.json +++ b/tools/perf/pmu-events/arch/x86/alderlake/frontend.json @@ -327,6 +327,24 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "ICACHE_TAG.STALLS_INUSE", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10", + "Unit": "cpu_core" + }, + { + "BriefDescription": "ICACHE_TAG.STALLS_ISB", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8", + "Unit": "cpu_core" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3", diff --git a/tools/perf/pmu-events/arch/x86/alderlake/pipeline.json b/tools/perf/pmu-events/arch/x86/alderlake/pipeline.json index 57a8c78cdc49..80cad3c49d20 100644 --- a/tools/perf/pmu-events/arch/x86/alderlake/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/alderlake/pipeline.json @@ -244,6 +244,15 @@ "UMask": "0xfb", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of near indirect JMP branch instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.INDIRECT_JMP", + "SampleAfterValue": "200003", + "UMask": "0xef", + "Unit": "cpu_atom" + }, { "BriefDescription": "This event is deprecated. Refer to new event BR_INST_RETIRED.INDIRECT_CALL", "Counter": "0,1,2,3,4,5", @@ -464,6 +473,15 @@ "UMask": "0x2", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of mispredicted near indirect JMP branch instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xc5", + "EventName": "BR_MISP_RETIRED.INDIRECT_JMP", + "SampleAfterValue": "200003", + "UMask": "0xef", + "Unit": "cpu_atom" + }, { "BriefDescription": "This event is deprecated. Refer to new event BR_MISP_RETIRED.INDIRECT_CALL", "Counter": "0,1,2,3,4,5", @@ -573,7 +591,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "PublicDescription": "Counts the number of core cycles while the core is not in a halt state. The core enters the halt state when it is running the HLT instruction. The core frequency may change from time to time. For this reason this event may have a changing ratio with regards to time. This event uses fixed counter 1.", @@ -582,7 +600,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles.", + "BriefDescription": "Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD_P]", "Counter": "0,1,2,3,4,5", "EventCode": "0x3c", "EventName": "CPU_CLK_UNHALTED.CORE_P", @@ -651,7 +669,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Counts the number of unhalted reference clock cycles at TSC frequency. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles at TSC frequency.", "Counter": "Fixed counter 2", "EventName": "CPU_CLK_UNHALTED.REF_TSC", "PublicDescription": "Counts the number of reference cycles that the core is not in a halt state. The core enters the halt state when it is running the HLT instruction. This event is not affected by core frequency changes and increments at a fixed frequency that is also used for the Time Stamp Counter (TSC). This event uses fixed counter 2.", @@ -689,7 +707,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "PublicDescription": "Counts the number of core cycles while the core is not in a halt state. The core enters the halt state when it is running the HLT instruction. The core frequency may change from time to time. For this reason this event may have a changing ratio with regards to time. This event uses fixed counter 1.", @@ -707,7 +725,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles.", + "BriefDescription": "Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE_P]", "Counter": "0,1,2,3,4,5", "EventCode": "0x3c", "EventName": "CPU_CLK_UNHALTED.THREAD_P", @@ -875,7 +893,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Counts the total number of instructions retired. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the total number of instructions retired.", "Counter": "Fixed counter 0", "EventName": "INST_RETIRED.ANY", "PublicDescription": "Counts the total number of instructions that retired. For instructions that consist of multiple uops, this event counts the retirement of the last uop of the instruction. This event continues counting during hardware interrupts, traps, and inside interrupt handlers. This event uses fixed counter 0. Available PDIST counters: 32", @@ -1273,6 +1291,42 @@ "UMask": "0x20", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "SampleAfterValue": "1000003", + "UMask": "0xff", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of LFENCE instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.LFENCE", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of accesses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_ACCESS", + "SampleAfterValue": "1000003", + "UMask": "0x10", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of misses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x11", + "Unit": "cpu_atom" + }, { "BriefDescription": "Cycles stalled due to no store buffers available. (not including draining form sync).", "Counter": "0,1,2,3,4,5,6,7", diff --git a/tools/perf/pmu-events/arch/x86/alderlaken/cache.json b/tools/perf/pmu-events/arch/x86/alderlaken/cache.json index 76a841675337..1f97a4dc6fb1 100644 --- a/tools/perf/pmu-events/arch/x86/alderlaken/cache.json +++ b/tools/perf/pmu-events/arch/x86/alderlaken/cache.json @@ -246,98 +246,90 @@ "UMask": "0x82" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 128 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_128", "MSRIndex": "0x3F6", "MSRValue": "0x80", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 128 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 16 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_16", "MSRIndex": "0x3F6", "MSRValue": "0x10", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 16 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 256 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_256", "MSRIndex": "0x3F6", "MSRValue": "0x100", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 256 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 32 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_32", "MSRIndex": "0x3F6", "MSRValue": "0x20", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 32 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 4 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_4", "MSRIndex": "0x3F6", "MSRValue": "0x4", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 4 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 512 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_512", "MSRIndex": "0x3F6", "MSRValue": "0x200", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 512 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 64 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_64", "MSRIndex": "0x3F6", "MSRValue": "0x40", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 64 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 8 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_8", "MSRIndex": "0x3F6", "MSRValue": "0x8", - "PublicDescription": "Counts the number of tagged loads with an instruction latency that exceeds or equals the threshold of 8 cycles as defined in MEC_CR_PEBS_LD_LAT_THRESHOLD (3F6H). Only counts with PEBS enabled. If a PEBS record is generated, will populate the PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x5" }, @@ -387,12 +379,11 @@ "UMask": "0x12" }, { - "BriefDescription": "Counts the number of stores uops retired. Counts with or without PEBS enabled.", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.STORE_LATENCY", - "PublicDescription": "Counts the number of stores uops retired. Counts with or without PEBS enabled. If PEBS is enabled and a PEBS record is generated, will populate PEBS Latency and PEBS Data Source fields accordingly.", "SampleAfterValue": "1000003", "UMask": "0x6" }, diff --git a/tools/perf/pmu-events/arch/x86/alderlaken/pipeline.json b/tools/perf/pmu-events/arch/x86/alderlaken/pipeline.json index d650cbd48c1f..a13851071624 100644 --- a/tools/perf/pmu-events/arch/x86/alderlaken/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/alderlaken/pipeline.json @@ -108,6 +108,14 @@ "SampleAfterValue": "200003", "UMask": "0xfb" }, + { + "BriefDescription": "Counts the number of near indirect JMP branch instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.INDIRECT_JMP", + "SampleAfterValue": "200003", + "UMask": "0xef" + }, { "BriefDescription": "This event is deprecated. Refer to new event BR_INST_RETIRED.INDIRECT_CALL", "Counter": "0,1,2,3,4,5", @@ -225,6 +233,14 @@ "SampleAfterValue": "200003", "UMask": "0xfb" }, + { + "BriefDescription": "Counts the number of mispredicted near indirect JMP branch instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xc5", + "EventName": "BR_MISP_RETIRED.INDIRECT_JMP", + "SampleAfterValue": "200003", + "UMask": "0xef" + }, { "BriefDescription": "This event is deprecated. Refer to new event BR_MISP_RETIRED.INDIRECT_CALL", "Counter": "0,1,2,3,4,5", @@ -278,7 +294,7 @@ "UMask": "0xfe" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "PublicDescription": "Counts the number of core cycles while the core is not in a halt state. The core enters the halt state when it is running the HLT instruction. The core frequency may change from time to time. For this reason this event may have a changing ratio with regards to time. This event uses fixed counter 1.", @@ -286,7 +302,7 @@ "UMask": "0x2" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles.", + "BriefDescription": "Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD_P]", "Counter": "0,1,2,3,4,5", "EventCode": "0x3c", "EventName": "CPU_CLK_UNHALTED.CORE_P", @@ -303,7 +319,7 @@ "UMask": "0x1" }, { - "BriefDescription": "Counts the number of unhalted reference clock cycles at TSC frequency. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles at TSC frequency.", "Counter": "Fixed counter 2", "EventName": "CPU_CLK_UNHALTED.REF_TSC", "PublicDescription": "Counts the number of reference cycles that the core is not in a halt state. The core enters the halt state when it is running the HLT instruction. This event is not affected by core frequency changes and increments at a fixed frequency that is also used for the Time Stamp Counter (TSC). This event uses fixed counter 2.", @@ -320,7 +336,7 @@ "UMask": "0x1" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "PublicDescription": "Counts the number of core cycles while the core is not in a halt state. The core enters the halt state when it is running the HLT instruction. The core frequency may change from time to time. For this reason this event may have a changing ratio with regards to time. This event uses fixed counter 1.", @@ -328,7 +344,7 @@ "UMask": "0x2" }, { - "BriefDescription": "Counts the number of unhalted core clock cycles.", + "BriefDescription": "Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE_P]", "Counter": "0,1,2,3,4,5", "EventCode": "0x3c", "EventName": "CPU_CLK_UNHALTED.THREAD_P", @@ -336,7 +352,7 @@ "SampleAfterValue": "2000003" }, { - "BriefDescription": "Counts the total number of instructions retired. (Fixed event)", + "BriefDescription": "Fixed Counter: Counts the total number of instructions retired.", "Counter": "Fixed counter 0", "EventName": "INST_RETIRED.ANY", "PublicDescription": "Counts the total number of instructions that retired. For instructions that consist of multiple uops, this event counts the retirement of the last uop of the instruction. This event continues counting during hardware interrupts, traps, and inside interrupt handlers. This event uses fixed counter 0. Available PDIST counters: 32", @@ -426,6 +442,38 @@ "SampleAfterValue": "1000003", "UMask": "0x1" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "SampleAfterValue": "1000003", + "UMask": "0xff" + }, + { + "BriefDescription": "Counts the number of LFENCE instructions retired.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.LFENCE", + "SampleAfterValue": "1000003", + "UMask": "0x2" + }, + { + "BriefDescription": "Counts the number of accesses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_ACCESS", + "SampleAfterValue": "1000003", + "UMask": "0x10" + }, + { + "BriefDescription": "Counts the number of misses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x11" + }, { "BriefDescription": "Counts the number of issue slots in a UMWAIT or TPAUSE instruction where no uop issues due to the instruction putting the CPU into the C0.1 activity state. For Tremont, UMWAIT and TPAUSE will only put the CPU into C0.1 activity state (not C0.2 activity state)", "Counter": "0,1,2,3,4,5", diff --git a/tools/perf/pmu-events/arch/x86/arrowlake/cache.json b/tools/perf/pmu-events/arch/x86/arrowlake/cache.json index fba4a0672f6c..4c3aa1fab5a8 100644 --- a/tools/perf/pmu-events/arch/x86/arrowlake/cache.json +++ b/tools/perf/pmu-events/arch/x86/arrowlake/cache.json @@ -628,6 +628,15 @@ "UMask": "0x7f", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an instruction cache or TLB miss.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x35", + "EventName": "MEM_BOUND_STALLS_IFETCH.ALL", + "SampleAfterValue": "1000003", + "UMask": "0x7f", + "Unit": "cpu_lowpower" + }, { "BriefDescription": "Counts the number of cycles the core is stalled due to an instruction cache or TLB miss which hit in the L2 cache.", "Counter": "0,1,2,3,4,5,6,7", @@ -731,6 +740,24 @@ "UMask": "0x6", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles that the core is stalled due to a demand load miss which hit in the LLC, no snoop was required, and the LLC provided data", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_HIT_NOSNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which hit in the LLC, a snoop was required, the snoop misses or the snoop hits but no fwd. LLC provides the data", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_HIT_SNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which missed all the local caches.", "Counter": "0,1,2,3,4,5,6,7", @@ -749,6 +776,24 @@ "UMask": "0x78", "Unit": "cpu_lowpower" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which missed all the caches. DRAM, MMIO or other LOCAL memory type provides the data", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_MISS_LOCALMEM", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled to a demand load miss and the data was provided from an unknown source. If the core has access to an L3 cache, an LLC miss refers to an L3 cache miss, otherwise it is an L2 cache miss.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_MISS_LOCALMEM", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_lowpower" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled to a store buffer full condition", "Counter": "0,1,2,3,4,5,6,7", @@ -1081,6 +1126,15 @@ "UMask": "0x20", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of retired load ops with an unknown source", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd4", + "EventName": "MEM_LOAD_UOPS_MISC_RETIRED.LOCAL_DRAM", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of load ops retired that miss the L3 cache and hit in DRAM", "Counter": "0,1,2,3,4,5,6,7", @@ -1181,6 +1235,15 @@ "UMask": "0x1c", "Unit": "cpu_lowpower" }, + { + "BriefDescription": "Counts the number of load ops retired that hit in the L3 cache in which no snoop was required", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd1", + "EventName": "MEM_LOAD_UOPS_RETIRED.L3_HIT_NO_SNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of loads that hit in a write combining buffer (WCB), excluding the first load that caused the WCB to allocate.", "Counter": "0,1,2,3,4,5,6,7", @@ -1331,7 +1394,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 1024. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1343,7 +1406,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1355,7 +1418,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1367,7 +1430,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1379,7 +1442,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1391,7 +1454,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 2048. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1403,7 +1466,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1415,7 +1478,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1427,7 +1490,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1439,7 +1502,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1451,7 +1514,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1463,7 +1526,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1475,7 +1538,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1487,7 +1550,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1499,7 +1562,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1511,7 +1574,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1523,7 +1586,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1535,7 +1598,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1707,7 +1770,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5,6,7", "Data_LA": "1", "EventCode": "0xd0", @@ -1717,7 +1780,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5,6,7", "Data_LA": "1", "EventCode": "0xd0", diff --git a/tools/perf/pmu-events/arch/x86/arrowlake/frontend.json b/tools/perf/pmu-events/arch/x86/arrowlake/frontend.json index a15de050a76c..21f00eafa98a 100644 --- a/tools/perf/pmu-events/arch/x86/arrowlake/frontend.json +++ b/tools/perf/pmu-events/arch/x86/arrowlake/frontend.json @@ -627,6 +627,24 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Cycles where a code fetch is stalled due to L1 instruction cache In use-full", + "Counter": "0,1,2,3,4,5,6,7,8,9", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10", + "Unit": "cpu_core" + }, + { + "BriefDescription": "Cycles where a code fetch is stalled due to L1 instruction cache ISB-full", + "Counter": "0,1,2,3,4,5,6,7,8,9", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8", + "Unit": "cpu_core" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3,4,5,6,7,8,9", diff --git a/tools/perf/pmu-events/arch/x86/arrowlake/pipeline.json b/tools/perf/pmu-events/arch/x86/arrowlake/pipeline.json index 805616052925..fb973c75be57 100644 --- a/tools/perf/pmu-events/arch/x86/arrowlake/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/arrowlake/pipeline.json @@ -822,7 +822,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles.", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -839,7 +839,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -909,7 +909,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles.", "Counter": "Fixed counter 2", "EventName": "CPU_CLK_UNHALTED.REF_TSC", "SampleAfterValue": "2000003", @@ -947,7 +947,7 @@ "Unit": "cpu_lowpower" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles.", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "SampleAfterValue": "2000003", @@ -964,7 +964,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "SampleAfterValue": "2000003", @@ -1134,10 +1134,10 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of instructions retired", + "BriefDescription": "Fixed Counter: Counts the number of instructions retired.", "Counter": "Fixed counter 0", "EventName": "INST_RETIRED.ANY", - "PublicDescription": "Fixed Counter: Counts the number of instructions retired Available PDIST counters: 32", + "PublicDescription": "Fixed Counter: Counts the number of instructions retired. Available PDIST counters: 32", "SampleAfterValue": "2000003", "UMask": "0x1", "Unit": "cpu_lowpower" @@ -1607,6 +1607,14 @@ "SampleAfterValue": "20003", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the total number of machine clears for any reason including, but not limited to, memory ordering, memory disambiguation, SMC, and FP assist.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc3", + "EventName": "MACHINE_CLEARS.ANY", + "SampleAfterValue": "20003", + "Unit": "cpu_lowpower" + }, { "BriefDescription": "Counts the number of machine clears that flush the pipeline and restart the machine without the use of microcode.", "Counter": "0,1,2,3,4,5,6,7", @@ -1813,6 +1821,15 @@ "UMask": "0xff", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "SampleAfterValue": "1000003", + "UMask": "0xff", + "Unit": "cpu_lowpower" + }, { "BriefDescription": "Counts the number of LFENCE instructions retired.", "Counter": "0,1,2,3,4,5,6,7", @@ -1822,6 +1839,15 @@ "UMask": "0x2", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of LFENCE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.LFENCE", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_lowpower" + }, { "BriefDescription": "Counts the number of RDPMC, RDTSC, and RDTSCP instructions retired.", "Counter": "0,1,2,3,4,5,6,7", diff --git a/tools/perf/pmu-events/arch/x86/emeraldrapids/cache.json b/tools/perf/pmu-events/arch/x86/emeraldrapids/cache.json index 26568e4b77f7..b2f8947f6741 100644 --- a/tools/perf/pmu-events/arch/x86/emeraldrapids/cache.json +++ b/tools/perf/pmu-events/arch/x86/emeraldrapids/cache.json @@ -514,7 +514,7 @@ "EventCode": "0xd3", "EventName": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM", "PublicDescription": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM Available PDIST counters: 0", - "SampleAfterValue": "1000003", + "SampleAfterValue": "100007", "UMask": "0x2" }, { @@ -534,7 +534,7 @@ "EventCode": "0xd3", "EventName": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM", "PublicDescription": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM Available PDIST counters: 0", - "SampleAfterValue": "1000003", + "SampleAfterValue": "100007", "UMask": "0x4" }, { diff --git a/tools/perf/pmu-events/arch/x86/emeraldrapids/frontend.json b/tools/perf/pmu-events/arch/x86/emeraldrapids/frontend.json index 793c486ffabe..e51f5e85ffd1 100644 --- a/tools/perf/pmu-events/arch/x86/emeraldrapids/frontend.json +++ b/tools/perf/pmu-events/arch/x86/emeraldrapids/frontend.json @@ -271,6 +271,22 @@ "SampleAfterValue": "200003", "UMask": "0x4" }, + { + "BriefDescription": "ICACHE_TAG.STALLS_INUSE", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10" + }, + { + "BriefDescription": "ICACHE_TAG.STALLS_ISB", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3", diff --git a/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-cache.json b/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-cache.json index 92cf47967f0b..3c8dcd9cff7c 100644 --- a/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-cache.json +++ b/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-cache.json @@ -3501,7 +3501,7 @@ "EventName": "UNC_CHA_SNOOP_RESP.RSPIFWD", "Experimental": "1", "PerPkg": "1", - "PublicDescription": "Counts when a a transaction with the opcode type RspIFwd Snoop Response was received which indicates a remote caching agent forwarded the data and the requesting agent is able to acquire the data in E (Exclusive) or M (modified) states. This is commonly returned with RFO (the Read for Ownership issued before a write) transactions. The snoop could have either been to a cacheline in the M,E,F (Modified, Exclusive or Forward) states.", + "PublicDescription": "Counts when a transaction with the opcode type RspIFwd Snoop Response was received which indicates a remote caching agent forwarded the data and the requesting agent is able to acquire the data in E (Exclusive) or M (modified) states. This is commonly returned with RFO (the Read for Ownership issued before a write) transactions. The snoop could have either been to a cacheline in the M,E,F (Modified, Exclusive or Forward) states.", "UMask": "0x4", "Unit": "CHA" }, @@ -3523,7 +3523,7 @@ "EventName": "UNC_CHA_SNOOP_RESP.RSPSFWD", "Experimental": "1", "PerPkg": "1", - "PublicDescription": "Counts when a a transaction with the opcode type RspSFwd Snoop Response was received which indicates a remote caching agent forwarded the data but held on to its current copy. This is common for data and code reads that hit in a remote socket in E (Exclusive) or F (Forward) state.", + "PublicDescription": "Counts when a transaction with the opcode type RspSFwd Snoop Response was received which indicates a remote caching agent forwarded the data but held on to its current copy. This is common for data and code reads that hit in a remote socket in E (Exclusive) or F (Forward) state.", "UMask": "0x8", "Unit": "CHA" }, diff --git a/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-io.json b/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-io.json index d4cf2199d46b..ddb0f65307f4 100644 --- a/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-io.json +++ b/tools/perf/pmu-events/arch/x86/emeraldrapids/uncore-io.json @@ -223,6 +223,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", + "PortMask": "0xff", "UMask": "0xff", "Unit": "IIO" }, @@ -234,7 +235,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x01", "PublicDescription": "x16 card plugged in to stack, Or x8 card plugged in to Lane 0/1, Or x4 card is plugged in to slot 0", "UMask": "0x1", "Unit": "IIO" @@ -247,7 +248,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x02", "PublicDescription": "x4 card is plugged in to slot 1", "UMask": "0x2", "Unit": "IIO" @@ -260,7 +261,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x04", "PublicDescription": "x8 card plugged in to Lane 2/3, Or x4 card is plugged in to slot 1", "UMask": "0x4", "Unit": "IIO" @@ -273,7 +274,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x08", "PublicDescription": "x4 card is plugged in to slot 3", "UMask": "0x8", "Unit": "IIO" @@ -286,7 +287,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x10", "PublicDescription": "x16 card plugged in to stack, Or x8 card plugged in to Lane 0/1, Or x4 card is plugged in to slot 0", "UMask": "0x10", "Unit": "IIO" @@ -299,7 +300,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x20", "PublicDescription": "x4 card is plugged in to slot 1", "UMask": "0x20", "Unit": "IIO" @@ -312,7 +313,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x40", "PublicDescription": "x8 card plugged in to Lane 2/3, Or x4 card is plugged in to slot 1", "UMask": "0x40", "Unit": "IIO" @@ -325,7 +326,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x80", "PublicDescription": "x4 card is plugged in to slot 3", "UMask": "0x80", "Unit": "IIO" diff --git a/tools/perf/pmu-events/arch/x86/grandridge/cache.json b/tools/perf/pmu-events/arch/x86/grandridge/cache.json index 9abddb06a837..0aa921ba89b4 100644 --- a/tools/perf/pmu-events/arch/x86/grandridge/cache.json +++ b/tools/perf/pmu-events/arch/x86/grandridge/cache.json @@ -285,8 +285,8 @@ "UMask": "0x82" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 1024. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_1024", @@ -296,8 +296,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_128", @@ -307,8 +307,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_16", @@ -318,8 +318,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 2048. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_2048", @@ -329,8 +329,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_256", @@ -340,8 +340,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_32", @@ -351,8 +351,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_4", @@ -362,8 +362,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_512", @@ -373,8 +373,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_64", @@ -384,8 +384,8 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", - "Counter": "0,1,2,3,4,5,6,7", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8. Only counts with PEBS enabled.", + "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_8", @@ -458,7 +458,7 @@ "UMask": "0x12" }, { - "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5,6,7", "Data_LA": "1", "EventCode": "0xd0", diff --git a/tools/perf/pmu-events/arch/x86/grandridge/pipeline.json b/tools/perf/pmu-events/arch/x86/grandridge/pipeline.json index f56d8d816e53..20986b987e18 100644 --- a/tools/perf/pmu-events/arch/x86/grandridge/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/grandridge/pipeline.json @@ -178,7 +178,7 @@ "UMask": "0xf7" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -192,7 +192,7 @@ "SampleAfterValue": "2000003" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles.", "Counter": "Fixed counter 2", "EventName": "CPU_CLK_UNHALTED.REF_TSC", "SampleAfterValue": "2000003", @@ -208,7 +208,7 @@ "UMask": "0x1" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "SampleAfterValue": "2000003", @@ -222,10 +222,10 @@ "SampleAfterValue": "2000003" }, { - "BriefDescription": "Fixed Counter: Counts the number of instructions retired", + "BriefDescription": "Fixed Counter: Counts the number of instructions retired.", "Counter": "Fixed counter 0", "EventName": "INST_RETIRED.ANY", - "PublicDescription": "Fixed Counter: Counts the number of instructions retired Available PDIST counters: 32", + "PublicDescription": "Fixed Counter: Counts the number of instructions retired. Available PDIST counters: 32", "SampleAfterValue": "2000003", "UMask": "0x1" }, @@ -301,6 +301,38 @@ "SampleAfterValue": "1000003", "UMask": "0x1" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "SampleAfterValue": "1000003", + "UMask": "0xff" + }, + { + "BriefDescription": "Counts the number of LFENCE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.LFENCE", + "SampleAfterValue": "1000003", + "UMask": "0x2" + }, + { + "BriefDescription": "Counts the number of accesses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_ACCESS", + "SampleAfterValue": "1000003", + "UMask": "0x10" + }, + { + "BriefDescription": "Counts the number of misses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x11" + }, { "BriefDescription": "Counts the number of issue slots in a UMWAIT or TPAUSE instruction where no uop issues due to the instruction putting the CPU into the C0.1 activity state.", "Counter": "0,1,2,3,4,5,6,7", diff --git a/tools/perf/pmu-events/arch/x86/graniterapids/frontend.json b/tools/perf/pmu-events/arch/x86/graniterapids/frontend.json index d580d305c926..1fdeaebb739f 100644 --- a/tools/perf/pmu-events/arch/x86/graniterapids/frontend.json +++ b/tools/perf/pmu-events/arch/x86/graniterapids/frontend.json @@ -325,6 +325,22 @@ "SampleAfterValue": "200003", "UMask": "0x4" }, + { + "BriefDescription": "ICACHE_TAG.STALLS_INUSE", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10" + }, + { + "BriefDescription": "ICACHE_TAG.STALLS_ISB", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3", diff --git a/tools/perf/pmu-events/arch/x86/graniterapids/gnr-metrics.json b/tools/perf/pmu-events/arch/x86/graniterapids/gnr-metrics.json index cc3c834ca286..299631fb8d53 100644 --- a/tools/perf/pmu-events/arch/x86/graniterapids/gnr-metrics.json +++ b/tools/perf/pmu-events/arch/x86/graniterapids/gnr-metrics.json @@ -143,6 +143,12 @@ "MetricName": "io_full_write_l3_miss", "ScaleUnit": "100%" }, + { + "BriefDescription": "The number of times per second that ownership of a cacheline was stolen from the integrated IO controller before it was able to write back the modified line", + "MetricExpr": "(UNC_I_MISC1.LOST_FWD + UNC_I_MISC1.SEC_RCVD_INVLD) / duration_time", + "MetricName": "io_lost_fwd", + "ScaleUnit": "1per_sec" + }, { "BriefDescription": "Message Signaled Interrupts (MSI) per second sent by the integrated I/O traffic controller (IIO) to System Configuration Controller (Ubox)", "MetricExpr": "UNC_IIO_NUM_REQ_OF_CPU_BY_TGT.UBOX_POSTED / duration_time", @@ -294,6 +300,27 @@ "MetricName": "memory_bandwidth_write", "ScaleUnit": "1MB/s" }, + { + "BriefDescription": "All reads to the local sub-numa cluster cache as a percentage of total memory read accesses", + "MetricExpr": "(L2_LINES_IN.ALL - (OCR.READS_TO_CORE.SNC_CACHE.HITM + OCR.READS_TO_CORE.SNC_CACHE.HIT_WITH_FWD + OCR.READS_TO_CORE.REMOTE_CACHE.SNOOP_FWD + OCR.READS_TO_CORE.REMOTE_MEMORY + OCR.READS_TO_CORE.L3_MISS_LOCAL)) / L2_LINES_IN.ALL", + "MetricName": "numa_percent_all_reads_to_local_cluster_cache", + "PublicDescription": "All reads to the local sub-numa cluster cache as a percentage of total memory read accesses. Includes demand and prefetch requests for data reads, code reads, read for ownerships (RFO), does not include LLC prefetches", + "ScaleUnit": "100%" + }, + { + "BriefDescription": "All reads to the local sub-numa cluster memory as a percentage of total memory read accesses", + "MetricExpr": "OCR.READS_TO_CORE.L3_MISS_LOCAL / L2_LINES_IN.ALL", + "MetricName": "numa_percent_all_reads_to_local_cluster_memory", + "PublicDescription": "All reads to the local sub-numa cluster memory as a percentage of total memory read accesses. Includes demand and prefetch requests for data reads, code reads, read for ownerships (RFO), does not include LLC prefetches", + "ScaleUnit": "100%" + }, + { + "BriefDescription": "All reads to a remote sub-numa cluster cache as a percentage of total memory read accesses", + "MetricExpr": "(OCR.READS_TO_CORE.SNC_CACHE.HIT_WITH_FWD + OCR.READS_TO_CORE.SNC_CACHE.HITM) / L2_LINES_IN.ALL", + "MetricName": "numa_percent_all_reads_to_remote_cluster_cache", + "PublicDescription": "All reads to a remote sub-numa cluster cache as a percentage of total memory read accesses. Includes demand and prefetch requests for data reads, code reads, read for ownerships (RFO), does not include LLC prefetches", + "ScaleUnit": "100%" + }, { "BriefDescription": "Memory read that miss the last level cache (LLC) addressed to local DRAM as a percentage of total memory read accesses, does not include LLC prefetches", "MetricExpr": "(UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL + UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL) / (UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL + UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL + UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE + UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE)", diff --git a/tools/perf/pmu-events/arch/x86/lunarlake/cache.json b/tools/perf/pmu-events/arch/x86/lunarlake/cache.json index 3d2616be8ec1..2db3e8a51fbd 100644 --- a/tools/perf/pmu-events/arch/x86/lunarlake/cache.json +++ b/tools/perf/pmu-events/arch/x86/lunarlake/cache.json @@ -550,6 +550,24 @@ "UMask": "0x7e", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an icache or itlb miss which missed all the caches.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x35", + "EventName": "MEM_BOUND_STALLS_IFETCH.LLC_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x78", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an icache or itlb miss which missed all the caches. Local DRAM, MMIO or other local memory type provides the data.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x35", + "EventName": "MEM_BOUND_STALLS_IFETCH.LLC_MISS_LOCALMEM", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an L1 demand load miss.", "Counter": "0,1,2,3,4,5,6,7", @@ -1088,7 +1106,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1100,7 +1118,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1112,7 +1130,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1124,7 +1142,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1136,7 +1154,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1148,7 +1166,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1160,7 +1178,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1172,7 +1190,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1274,7 +1292,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5,6,7", "Data_LA": "1", "EventCode": "0xd0", diff --git a/tools/perf/pmu-events/arch/x86/lunarlake/frontend.json b/tools/perf/pmu-events/arch/x86/lunarlake/frontend.json index b21d602e9f1a..798eebf77436 100644 --- a/tools/perf/pmu-events/arch/x86/lunarlake/frontend.json +++ b/tools/perf/pmu-events/arch/x86/lunarlake/frontend.json @@ -424,6 +424,15 @@ "UMask": "0x1", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of instructions retired that were tagged because empty issue slots were seen before the uop due to Instruction L1 cache miss, that missed in the L2 cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc9", + "EventName": "FRONTEND_RETIRED_SOURCE.ICACHE_L2_MISS", + "SampleAfterValue": "1000003", + "UMask": "0xe", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of instructions retired that were tagged because empty issue slots were seen before the uop due to ITLB miss that hit in the second level TLB.", "Counter": "0,1,2,3,4,5,6,7", @@ -500,6 +509,24 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Cycles where a code fetch is stalled due to L1 instruction cache In use-full", + "Counter": "0,1,2,3,4,5,6,7,8,9", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10", + "Unit": "cpu_core" + }, + { + "BriefDescription": "Cycles where a code fetch is stalled due to L1 instruction cache ISB-full", + "Counter": "0,1,2,3,4,5,6,7,8,9", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8", + "Unit": "cpu_core" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3,4,5,6,7,8,9", diff --git a/tools/perf/pmu-events/arch/x86/lunarlake/pipeline.json b/tools/perf/pmu-events/arch/x86/lunarlake/pipeline.json index 97797f7b072e..d98723b3cd78 100644 --- a/tools/perf/pmu-events/arch/x86/lunarlake/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/lunarlake/pipeline.json @@ -634,7 +634,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles.", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -725,7 +725,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles.", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "SampleAfterValue": "2000003", @@ -1530,8 +1530,9 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of accesses to KeyLocker cache.", + "BriefDescription": "This event is deprecated.", "Counter": "0,1,2,3,4,5,6,7", + "Deprecated": "1", "EventCode": "0xe1", "EventName": "MISC_RETIRED2.KEYLOCKER_ACCESS", "SampleAfterValue": "1000003", @@ -1539,8 +1540,9 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of misses to KeyLocker cache.", + "BriefDescription": "This event is deprecated.", "Counter": "0,1,2,3,4,5,6,7", + "Deprecated": "1", "EventCode": "0xe1", "EventName": "MISC_RETIRED2.KEYLOCKER_MISS", "SampleAfterValue": "1000003", diff --git a/tools/perf/pmu-events/arch/x86/mapfile.csv b/tools/perf/pmu-events/arch/x86/mapfile.csv index 149bbe7abaf5..8a9e1735e21e 100644 --- a/tools/perf/pmu-events/arch/x86/mapfile.csv +++ b/tools/perf/pmu-events/arch/x86/mapfile.csv @@ -1,7 +1,7 @@ Family-model,Version,Filename,EventType -GenuineIntel-6-(97|9A|B7|BA|BF),v1.35,alderlake,core -GenuineIntel-6-BE,v1.35,alderlaken,core -GenuineIntel-6-C[56],v1.14,arrowlake,core +GenuineIntel-6-(97|9A|B7|BA|BF),v1.37,alderlake,core +GenuineIntel-6-BE,v1.37,alderlaken,core +GenuineIntel-6-C[56],v1.16,arrowlake,core GenuineIntel-6-(1C|26|27|35|36),v5,bonnell,core GenuineIntel-6-(3D|47),v30,broadwell,core GenuineIntel-6-56,v12,broadwellde,core @@ -9,11 +9,11 @@ GenuineIntel-6-4F,v23,broadwellx,core GenuineIntel-6-55-[56789ABCDEF],v1.25,cascadelakex,core GenuineIntel-6-DD,v1.00,clearwaterforest,core GenuineIntel-6-9[6C],v1.05,elkhartlake,core -GenuineIntel-6-CF,v1.20,emeraldrapids,core +GenuineIntel-6-CF,v1.21,emeraldrapids,core GenuineIntel-6-5[CF],v13,goldmont,core GenuineIntel-6-7A,v1.01,goldmontplus,core -GenuineIntel-6-B6,v1.10,grandridge,core -GenuineIntel-6-A[DE],v1.16,graniterapids,core +GenuineIntel-6-B6,v1.11,grandridge,core +GenuineIntel-6-A[DE],v1.17,graniterapids,core GenuineIntel-6-(3C|45|46),v36,haswell,core GenuineIntel-6-3F,v29,haswellx,core GenuineIntel-6-7[DE],v1.24,icelake,core @@ -22,15 +22,15 @@ GenuineIntel-6-3A,v24,ivybridge,core GenuineIntel-6-3E,v24,ivytown,core GenuineIntel-6-2D,v24,jaketown,core GenuineIntel-6-(57|85),v16,knightslanding,core -GenuineIntel-6-BD,v1.19,lunarlake,core -GenuineIntel-6-(AA|AC|B5),v1.18,meteorlake,core +GenuineIntel-6-BD,v1.21,lunarlake,core +GenuineIntel-6-(AA|AC|B5),v1.20,meteorlake,core GenuineIntel-6-1[AEF],v4,nehalemep,core GenuineIntel-6-2E,v4,nehalemex,core -GenuineIntel-6-CC,v1.02,pantherlake,core +GenuineIntel-6-CC,v1.04,pantherlake,core GenuineIntel-6-A7,v1.04,rocketlake,core GenuineIntel-6-2A,v19,sandybridge,core -GenuineIntel-6-8F,v1.35,sapphirerapids,core -GenuineIntel-6-AF,v1.13,sierraforest,core +GenuineIntel-6-8F,v1.36,sapphirerapids,core +GenuineIntel-6-AF,v1.15,sierraforest,core GenuineIntel-6-(37|4A|4C|4D|5A),v15,silvermont,core GenuineIntel-6-(4E|5E|8E|9E|A5|A6),v59,skylake,core GenuineIntel-6-55-[01234],v1.37,skylakex,core diff --git a/tools/perf/pmu-events/arch/x86/meteorlake/cache.json b/tools/perf/pmu-events/arch/x86/meteorlake/cache.json index d3fc04b2ffbd..4c1220c19456 100644 --- a/tools/perf/pmu-events/arch/x86/meteorlake/cache.json +++ b/tools/perf/pmu-events/arch/x86/meteorlake/cache.json @@ -513,6 +513,15 @@ "UMask": "0x6", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an ICACHE or ITLB miss which hit in the LLC, no snoop was required. LLC provides the data. If the core has access to an L3 cache, an LLC hit refers to an L3 cache hit, otherwise it counts zeros.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x35", + "EventName": "MEM_BOUND_STALLS_IFETCH.LLC_HIT_NOSNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an ICACHE or ITLB miss which missed all the caches. If the core has access to an L3 cache, an LLC miss refers to an L3 cache miss, otherwise it is an L2 cache miss.", "Counter": "0,1,2,3,4,5,6,7", @@ -522,6 +531,15 @@ "UMask": "0x78", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an ICACHE or ITLB miss which missed all the caches. DRAM, MMIO or other LOCAL memory type provides the data. If the core has access to an L3 cache, an LLC miss refers to an L3 cache miss, otherwise it is an L2 cache miss.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x35", + "EventName": "MEM_BOUND_STALLS_IFETCH.LLC_MISS_LOCALMEM", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to an L1 demand load miss.", "Counter": "0,1,2,3,4,5,6,7", @@ -559,6 +577,24 @@ "UMask": "0x6", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which hit in the LLC, no snoop was required. LLC provides the data. If the core has access to an L3 cache, an LLC hit refers to an L3 cache hit, otherwise it counts zeros.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_HIT_NOSNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which hit in the LLC, a snoop was required, the snoop misses or the snoop hits but NO_FWD. LLC provides the data. If the core has access to an L3 cache, an LLC hit refers to an L3 cache hit, otherwise it counts zeros.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_HIT_SNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which missed all the local caches. If the core has access to an L3 cache, an LLC miss refers to an L3 cache miss, otherwise it is an L2 cache miss.", "Counter": "0,1,2,3,4,5,6,7", @@ -568,6 +604,15 @@ "UMask": "0x78", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled to a demand load miss and the data was provided from an unknown source. If the core has access to an L3 cache, an LLC miss refers to an L3 cache miss, otherwise it is an L2 cache miss.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_MISS_LOCALMEM", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled to a store buffer full condition", "Counter": "0,1,2,3,4,5,6,7", @@ -969,7 +1014,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 1024. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -981,7 +1026,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -993,7 +1038,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1005,7 +1050,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 2048. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1017,7 +1062,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1029,7 +1074,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1041,7 +1086,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1053,7 +1098,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1065,7 +1110,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1077,7 +1122,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -1159,7 +1204,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5,6,7", "Data_LA": "1", "EventCode": "0xd0", diff --git a/tools/perf/pmu-events/arch/x86/meteorlake/frontend.json b/tools/perf/pmu-events/arch/x86/meteorlake/frontend.json index 6484834b1127..dcf8c8e720f3 100644 --- a/tools/perf/pmu-events/arch/x86/meteorlake/frontend.json +++ b/tools/perf/pmu-events/arch/x86/meteorlake/frontend.json @@ -430,6 +430,24 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "ICACHE_TAG.STALLS_INUSE", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10", + "Unit": "cpu_core" + }, + { + "BriefDescription": "ICACHE_TAG.STALLS_ISB", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8", + "Unit": "cpu_core" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3", diff --git a/tools/perf/pmu-events/arch/x86/meteorlake/pipeline.json b/tools/perf/pmu-events/arch/x86/meteorlake/pipeline.json index bfdaabe9377d..7662846745bd 100644 --- a/tools/perf/pmu-events/arch/x86/meteorlake/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/meteorlake/pipeline.json @@ -517,7 +517,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -583,7 +583,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles.", "Counter": "Fixed counter 2", "EventName": "CPU_CLK_UNHALTED.REF_TSC", "SampleAfterValue": "2000003", @@ -620,7 +620,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "SampleAfterValue": "2000003", @@ -804,10 +804,10 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of instructions retired", + "BriefDescription": "Fixed Counter: Counts the number of instructions retired.", "Counter": "Fixed counter 0", "EventName": "INST_RETIRED.ANY", - "PublicDescription": "Fixed Counter: Counts the number of instructions retired Available PDIST counters: 32", + "PublicDescription": "Fixed Counter: Counts the number of instructions retired. Available PDIST counters: 32", "SampleAfterValue": "2000003", "UMask": "0x1", "Unit": "cpu_atom" @@ -1207,6 +1207,42 @@ "UMask": "0x20", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "SampleAfterValue": "1000003", + "UMask": "0xff", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of LFENCE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.LFENCE", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of accesses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_ACCESS", + "SampleAfterValue": "1000003", + "UMask": "0x10", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of misses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x11", + "Unit": "cpu_atom" + }, { "BriefDescription": "Cycles stalled due to no store buffers available. (not including draining form sync).", "Counter": "0,1,2,3,4,5,6,7", diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/cache.json b/tools/perf/pmu-events/arch/x86/pantherlake/cache.json index 91f5ab908926..e5323093eec0 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/cache.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/cache.json @@ -149,6 +149,60 @@ "UMask": "0xff", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Demand Code Read requests. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.DEMAND_CODE_RD", + "SampleAfterValue": "1000003", + "UMask": "0xc4", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Demand Code Read requests that resulted in a Miss. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.DEMAND_CODE_RD_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x44", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Demand Data Read requests. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.DEMAND_DATA_RD", + "SampleAfterValue": "1000003", + "UMask": "0xc1", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Demand Data Read requests that resulted in a Miss. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.DEMAND_DATA_RD_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x41", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Demand RFO requests. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.DEMAND_RFO", + "SampleAfterValue": "1000003", + "UMask": "0xc2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Demand RFO requests that resulted in a Miss. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.DEMAND_RFO_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x42", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of L2 cache accesses from front door requests that resulted in a Hit. Does not include rejects or recycles, per core event.", "Counter": "0,1,2,3,4,5,6,7", @@ -158,6 +212,24 @@ "UMask": "0x1bf", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door Hardware Prefetch requests. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.HWPF", + "SampleAfterValue": "1000003", + "UMask": "0xc8", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of L2 cache accesses from front door requests that resulted in a Miss. Does not include rejects or recycles, per core event.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x24", + "EventName": "L2_REQUEST.MISS", + "SampleAfterValue": "1000003", + "UMask": "0x17f", + "Unit": "cpu_atom" + }, { "BriefDescription": "Read requests with true-miss in L2 cache [This event is alias to L2_RQSTS.MISS]", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -365,6 +437,24 @@ "UMask": "0x6", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which hit in the LLC, no snoop was required. LLC provided data.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_HIT_NOSNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which hit in the LLC, a snoop was required, the snoop misses or the snoop hits but no fwd. LLC provides the data.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x34", + "EventName": "MEM_BOUND_STALLS_LOAD.LLC_HIT_SNOOP", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of unhalted cycles when the core is stalled due to a demand load miss which missed all the local caches.", "Counter": "0,1,2,3,4,5,6,7", @@ -716,6 +806,16 @@ "UMask": "0x20", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the total number of load ops retired that miss the L3 cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd3", + "EventName": "MEM_LOAD_UOPS_L3_MISS_RETIRED.ALL", + "PublicDescription": "Counts the total number of load ops retired that miss the L3 cache. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0xff", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of load ops retired that miss the L3 cache and hit in DRAM", "Counter": "0,1,2,3,4,5,6,7", @@ -746,6 +846,26 @@ "UMask": "0x8", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of load ops retired that hit in the L3 cache in which a snoop was required and no data was forwarded.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd4", + "EventName": "MEM_LOAD_UOPS_MISC_RETIRED.L3_HIT_SNOOP_NO_FWD", + "PublicDescription": "Counts the number of load ops retired that hit in the L3 cache in which a snoop was required and no data was forwarded. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x20", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of load ops retired that hit in the L3 cache in which a snoop was required and non-modified data was forwarded.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd4", + "EventName": "MEM_LOAD_UOPS_MISC_RETIRED.L3_HIT_SNOOP_WITH_FWD", + "PublicDescription": "Counts the number of load ops retired that hit in the L3 cache in which a snoop was required and non-modified data was forwarded. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x10", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of load ops retired that hit the L1 data cache.", "Counter": "0,1,2,3,4,5,6,7", @@ -796,6 +916,26 @@ "UMask": "0x1c", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of load ops retired that hit in the L3 cache in which no snoop was required.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd1", + "EventName": "MEM_LOAD_UOPS_RETIRED.L3_HIT_NO_SNOOP", + "PublicDescription": "Counts the number of load ops retired that hit in the L3 cache in which no snoop was required. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of load ops retired that hit in the L3 cache in which a snoop was required and it hit and forwarded data, it hit and did not forward data, or it hit and the forwarded data was modified.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xd1", + "EventName": "MEM_LOAD_UOPS_RETIRED.L3_HIT_SNOOP_HIT", + "PublicDescription": "Counts the number of load ops retired that hit in the L3 cache in which a snoop was required and it hit and forwarded data, it hit and did not forward data, or it hit and the forwarded data was modified. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x10", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of cycles that uops are blocked for any of the following reasons: load buffer, store buffer or RSV full.", "Counter": "0,1,2,3,4,5,6,7", @@ -880,13 +1020,14 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 1024.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_1024", "MSRIndex": "0x3F6", "MSRValue": "0x400", - "PublicDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled. Available PDIST counters: 0,1", + "PublicDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 1024. Available PDIST counters: 0,1", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" @@ -894,6 +1035,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_128", "MSRIndex": "0x3F6", @@ -906,6 +1048,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_16", "MSRIndex": "0x3F6", @@ -916,13 +1059,14 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 2048.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_2048", "MSRIndex": "0x3F6", "MSRValue": "0x800", - "PublicDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled. Available PDIST counters: 0,1", + "PublicDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 2048. Available PDIST counters: 0,1", "SampleAfterValue": "1000003", "UMask": "0x5", "Unit": "cpu_atom" @@ -930,6 +1074,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_256", "MSRIndex": "0x3F6", @@ -942,6 +1087,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_32", "MSRIndex": "0x3F6", @@ -954,6 +1100,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_4", "MSRIndex": "0x3F6", @@ -966,6 +1113,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_512", "MSRIndex": "0x3F6", @@ -978,6 +1126,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_64", "MSRIndex": "0x3F6", @@ -990,6 +1139,7 @@ { "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.LOAD_LATENCY_GT_8", "MSRIndex": "0x3F6", @@ -1072,6 +1222,7 @@ { "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", "Counter": "0,1,2,3,4,5,6,7", + "Data_LA": "1", "EventCode": "0xd0", "EventName": "MEM_UOPS_RETIRED.STORE_LATENCY", "PublicDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES Available PDIST counters: 0,1", diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/floating-point.json b/tools/perf/pmu-events/arch/x86/pantherlake/floating-point.json index e306a45b22ee..77f6c9028d93 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/floating-point.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/floating-point.json @@ -1,4 +1,14 @@ [ + { + "BriefDescription": "Counts the number of cycles when any of the floating point dividers are active.", + "Counter": "0,1,2,3,4,5,6,7", + "CounterMask": "1", + "EventCode": "0xcd", + "EventName": "ARITH.FPDIV_ACTIVE", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Cycles when floating-point divide unit is busy executing divide or square root operations.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -10,6 +20,24 @@ "UMask": "0x1", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of floating point dividers per cycle in the loop stage.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xcd", + "EventName": "ARITH.FPDIV_OCCUPANCY", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of floating point divider uops executed per cycle.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xcd", + "EventName": "ARITH.FPDIV_UOPS", + "SampleAfterValue": "1000003", + "UMask": "0x8", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts all microcode FP assists.", "Counter": "0,1,2,3,4,5,6,7,8,9", diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/frontend.json b/tools/perf/pmu-events/arch/x86/pantherlake/frontend.json index d36faa683d3f..5e69b81742f5 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/frontend.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/frontend.json @@ -422,6 +422,24 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Cycles where a code fetch is stalled due to L1 instruction cache In use-full", + "Counter": "0,1,2,3,4,5,6,7,8,9", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10", + "Unit": "cpu_core" + }, + { + "BriefDescription": "Cycles where a code fetch is stalled due to L1 instruction cache ISB-full", + "Counter": "0,1,2,3,4,5,6,7,8,9", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8", + "Unit": "cpu_core" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -561,5 +579,23 @@ "SampleAfterValue": "1000003", "UMask": "0x1", "Unit": "cpu_core" + }, + { + "BriefDescription": "Counts the number of cycles that the micro-sequencer is busy.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe7", + "EventName": "MS_DECODED.MS_BUSY", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of times entered into a ucode flow in the FEC. Includes inserted flows due to front-end detected faults or assists.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe7", + "EventName": "MS_DECODED.MS_ENTRY", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" } ] diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/memory.json b/tools/perf/pmu-events/arch/x86/pantherlake/memory.json index 3d31e620383d..4248cc101391 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/memory.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/memory.json @@ -8,6 +8,24 @@ "UMask": "0xf4", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of cycles that the head (oldest load) of the load buffer is stalled due to a DL1 miss.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x05", + "EventName": "LD_HEAD.L1_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of cycles that the head (oldest load) of the load buffer and retirement are both stalled due to a DL1 miss.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x05", + "EventName": "LD_HEAD.L1_MISS_AT_RET", + "SampleAfterValue": "1000003", + "UMask": "0x81", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of cycles that the head (oldest load) of the load buffer is stalled due to request buffers full or lock in progress.", "Counter": "0,1,2,3,4,5,6,7", @@ -17,6 +35,15 @@ "UMask": "0x2", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of cycles that the head (oldest load) of the load buffer and retirement are both stalled due to request buffers full or lock in progress.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x05", + "EventName": "LD_HEAD.WCB_FULL_AT_RET", + "SampleAfterValue": "1000003", + "UMask": "0x82", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of machine clears due to memory ordering caused by a snoop from an external agent. Does not count internally generated machine clears such as those due to memory disambiguation.", "Counter": "0,1,2,3,4,5,6,7", diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/other.json b/tools/perf/pmu-events/arch/x86/pantherlake/other.json index d49651d4f112..915c52f5abd1 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/other.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/other.json @@ -30,6 +30,16 @@ "UMask": "0x1", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the total number of BTCLEARS.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe8", + "EventName": "PREDICTION.BTCLEAR", + "PublicDescription": "Counts the total number of BTCLEARS which occurs when the Branch Target Buffer (BTB) predicts a taken branch.", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" + }, { "BriefDescription": "Cycles the uncore cannot take further requests", "Counter": "0,1,2,3,4,5,6,7,8,9", diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/pipeline.json b/tools/perf/pmu-events/arch/x86/pantherlake/pipeline.json index fb87d30c403d..86009237df2f 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/pipeline.json @@ -1,4 +1,14 @@ [ + { + "BriefDescription": "Counts the number of cycles when any of the floating point or integer dividers are active.", + "Counter": "0,1,2,3,4,5,6,7", + "CounterMask": "1", + "EventCode": "0xcd", + "EventName": "ARITH.DIV_ACTIVE", + "SampleAfterValue": "1000003", + "UMask": "0x3", + "Unit": "cpu_atom" + }, { "BriefDescription": "Cycles when divide unit is busy executing divide or square root operations.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -10,6 +20,16 @@ "UMask": "0x9", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of cycles when any of the integer dividers are active.", + "Counter": "0,1,2,3,4,5,6,7", + "CounterMask": "1", + "EventCode": "0xcd", + "EventName": "ARITH.IDIV_ACTIVE", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" + }, { "BriefDescription": "Cycles when integer divide unit is busy executing divide or square root operations.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -21,6 +41,24 @@ "UMask": "0x8", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts number of active integer dividers per cycle.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xcd", + "EventName": "ARITH.IDIV_OCCUPANCY", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of integer divider uops executed per cycle.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xcd", + "EventName": "ARITH.IDIV_UOPS", + "SampleAfterValue": "1000003", + "UMask": "0x4", + "Unit": "cpu_atom" + }, { "BriefDescription": "Number of occurrences where a microcode assist is invoked by hardware.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -58,6 +96,38 @@ "SampleAfterValue": "400009", "Unit": "cpu_core" }, + { + "BriefDescription": "This event is deprecated. [This event is alias to BR_INST_RETIRED.NEAR_INDIRECT]", + "Counter": "0,1,2,3,4,5,6,7", + "Deprecated": "1", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.ALL_NEAR_IND", + "PublicDescription": "This event is deprecated. [This event is alias to BR_INST_RETIRED.NEAR_INDIRECT] Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "This event is deprecated. [This event is alias to BR_INST_RETIRED.NEAR_INDIRECT_OR_RETURN]", + "Counter": "0,1,2,3,4,5,6,7", + "Deprecated": "1", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.ALL_NEAR_IND_OR_RET", + "PublicDescription": "This event is deprecated. [This event is alias to BR_INST_RETIRED.NEAR_INDIRECT_OR_RETURN] Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x58", + "Unit": "cpu_atom" + }, + { + "BriefDescription": "Counts the number of conditional branch instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.COND", + "PublicDescription": "Counts the number of conditional branch instructions retired. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x7", + "Unit": "cpu_atom" + }, { "BriefDescription": "Conditional branch instructions retired.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -88,6 +158,16 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of taken conditional branch instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.COND_TAKEN", + "PublicDescription": "Counts the number of taken conditional branch instructions retired. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x3", + "Unit": "cpu_atom" + }, { "BriefDescription": "Taken conditional branch instructions retired.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -98,6 +178,16 @@ "UMask": "0x3", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of taken backward conditional branch instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.COND_TAKEN_BWD", + "PublicDescription": "Counts the number of taken backward conditional branch instructions retired. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" + }, { "BriefDescription": "Taken backward conditional branch instructions retired.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -108,6 +198,16 @@ "UMask": "0x1", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of taken forward conditional branch instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.COND_TAKEN_FWD", + "PublicDescription": "Counts the number of taken forward conditional branch instructions retired. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Taken forward conditional branch instructions retired.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -178,6 +278,16 @@ "UMask": "0x80", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of near indirect JMP and near indirect CALL branch instructions retired. [This event is alias to BR_INST_RETIRED.ALL_NEAR_IND]", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.NEAR_INDIRECT", + "PublicDescription": "Counts the number of near indirect JMP and near indirect CALL branch instructions retired. [This event is alias to BR_INST_RETIRED.ALL_NEAR_IND] Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x50", + "Unit": "cpu_atom" + }, { "BriefDescription": "Indirect near branch instructions retired (excluding returns) [This event is alias to BR_INST_RETIRED.INDIRECT]", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -208,6 +318,16 @@ "UMask": "0x40", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of near indirect JMP, near indirect CALL, and RET branch instructions retired. [This event is alias to BR_INST_RETIRED.ALL_NEAR_IND_OR_RET]", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc4", + "EventName": "BR_INST_RETIRED.NEAR_INDIRECT_OR_RETURN", + "PublicDescription": "Counts the number of near indirect JMP, near indirect CALL, and RET branch instructions retired. [This event is alias to BR_INST_RETIRED.ALL_NEAR_IND_OR_RET] Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x58", + "Unit": "cpu_atom" + }, { "BriefDescription": "This event is deprecated. [This event is alias to BR_INST_RETIRED.NEAR_INDIRECT_CALL]", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -283,7 +403,7 @@ "Unit": "cpu_atom" }, { - "BriefDescription": "Taken branch instructions retired.", + "BriefDescription": "Near Taken branch instructions retired.", "Counter": "0,1,2,3,4,5,6,7,8,9", "EventCode": "0xc4", "EventName": "BR_INST_RETIRED.NEAR_TAKEN", @@ -755,7 +875,7 @@ "Unit": "cpu_core" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles.", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -1549,6 +1669,16 @@ "UMask": "0x1", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "PublicDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0xff", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of LFENCE instructions retired.", "Counter": "0,1,2,3,4,5,6,7", @@ -1620,6 +1750,15 @@ "UMask": "0x4", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number issue slots not consumed due to a color request for an FCW or MXCSR control register when all 4 colors (copies) are already in use.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x75", + "EventName": "SERIALIZATION.COLOR_STALLS", + "SampleAfterValue": "1000003", + "UMask": "0x8", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of issue slots where no uop could issue due to an IQ scoreboard that stalls allocation until a specified older uop retires or (in the case of jump scoreboard) executes. Commonly executed instructions with IQ scoreboards include LFENCE and MFENCE.", "Counter": "0,1,2,3,4,5,6,7", @@ -1732,6 +1871,15 @@ "UMask": "0x8", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the total number of issue slots that were not consumed by the backend because allocation is stalled due to a machine clear (nuke) of any kind including memory ordering and memory disambiguation.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x73", + "EventName": "TOPDOWN_BAD_SPECULATION.MACHINE_CLEARS", + "SampleAfterValue": "1000003", + "UMask": "0x3", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of issue slots every cycle that were not consumed by the backend due to Branch Mispredict", "Counter": "0,1,2,3,4,5,6,7", @@ -1795,6 +1943,15 @@ "UMask": "0x2", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of issue slots every cycle that were not consumed by the backend due to ROB full", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x74", + "EventName": "TOPDOWN_BE_BOUND.REORDER_BUFFER", + "SampleAfterValue": "1000003", + "UMask": "0x40", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of issue slots every cycle that were not consumed by the backend due to iq/jeu scoreboards or ms scb", "Counter": "0,1,2,3,4,5,6,7", @@ -2076,6 +2233,15 @@ "UMask": "0x10", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of uops issued by the front end every cycle.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x0e", + "EventName": "UOPS_ISSUED.ANY", + "PublicDescription": "Counts the number of uops issued by the front end every cycle. When 4-uops are requested and only 2-uops are delivered, the event counts 2. Uops_issued correlates to the number of ROB entries. If uop takes 2 ROB slots it counts as 2 uops_issued.", + "SampleAfterValue": "1000003", + "Unit": "cpu_atom" + }, { "BriefDescription": "Uops that RAT issues to RS", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -2107,6 +2273,16 @@ "UMask": "0x2", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of uops retired that are the last uop of a macro-instruction.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc2", + "EventName": "UOPS_RETIRED.EOM", + "PublicDescription": "Counts the number of uops retired that are the last uop of a macro-instruction. EOM uops indicate the 'end of a macro-instruction' and play a crucial role in the processor's control flow and recovery mechanisms.", + "SampleAfterValue": "1000003", + "UMask": "0x1", + "Unit": "cpu_atom" + }, { "BriefDescription": "Retired uops except the last uop of each instruction.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -2127,6 +2303,16 @@ "UMask": "0x80", "Unit": "cpu_atom" }, + { + "BriefDescription": "Counts the number of uops retired that originated from a loop stream detector.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc2", + "EventName": "UOPS_RETIRED.LSD", + "PublicDescription": "Counts the number of uops retired that originated from a loop stream detector. Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x20", + "Unit": "cpu_atom" + }, { "BriefDescription": "Counts the number of uops that are from the complex flows issued by the micro-sequencer (MS). This includes uops from flows due to complex instructions, faults, assists, and inserted flows.", "Counter": "0,1,2,3,4,5,6,7", @@ -2161,6 +2347,16 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "UOPS_RETIRED.NANO_CODE", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xc2", + "EventName": "UOPS_RETIRED.NANO_CODE", + "PublicDescription": "UOPS_RETIRED.NANO_CODE Available PDIST counters: 0,1", + "SampleAfterValue": "1000003", + "UMask": "0x8", + "Unit": "cpu_atom" + }, { "BriefDescription": "This event counts a subset of the Topdown Slots event that are utilized by operations that eventually get retired (committed) by the processor pipeline. Usually, this event positively correlates with higher performance for example, as measured by the instructions-per-cycle metric.", "Counter": "0,1,2,3,4,5,6,7,8,9", diff --git a/tools/perf/pmu-events/arch/x86/pantherlake/virtual-memory.json b/tools/perf/pmu-events/arch/x86/pantherlake/virtual-memory.json index 8d56c16b2a39..8f3dd36707dc 100644 --- a/tools/perf/pmu-events/arch/x86/pantherlake/virtual-memory.json +++ b/tools/perf/pmu-events/arch/x86/pantherlake/virtual-memory.json @@ -78,6 +78,16 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of page walks completed due to load DTLB misses to a 4K page.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x08", + "EventName": "DTLB_LOAD_MISSES.WALK_COMPLETED_4K", + "PublicDescription": "Counts the number of page walks completed due to loads (including SW prefetches) whose address translations missed in all Translation Lookaside Buffer (TLB) levels and were mapped to 4K pages. Includes page walks that page fault.", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Page walks completed due to a demand data load to a 4K page.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -178,6 +188,16 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of page walks completed due to store DTLB misses to a 4K page.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x49", + "EventName": "DTLB_STORE_MISSES.WALK_COMPLETED_4K", + "PublicDescription": "Counts the number of page walks completed due to stores whose address translations missed in all Translation Lookaside Buffer (TLB) levels and were mapped to 4K pages. Includes page walks that page fault.", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Page walks completed due to a demand data store to a 4K page.", "Counter": "0,1,2,3,4,5,6,7,8,9", @@ -267,6 +287,16 @@ "UMask": "0x4", "Unit": "cpu_core" }, + { + "BriefDescription": "Counts the number of page walks completed due to instruction fetch misses to a 4K page.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0x85", + "EventName": "ITLB_MISSES.WALK_COMPLETED_4K", + "PublicDescription": "Counts the number of page walks completed due to instruction fetches whose address translations missed in all Translation Lookaside Buffer (TLB) levels and were mapped to 4K pages. Includes page walks that page fault.", + "SampleAfterValue": "1000003", + "UMask": "0x2", + "Unit": "cpu_atom" + }, { "BriefDescription": "Code miss in all TLB levels causes a page walk that completes. (4K)", "Counter": "0,1,2,3,4,5,6,7,8,9", diff --git a/tools/perf/pmu-events/arch/x86/sapphirerapids/cache.json b/tools/perf/pmu-events/arch/x86/sapphirerapids/cache.json index c66324d41a89..373b26c84448 100644 --- a/tools/perf/pmu-events/arch/x86/sapphirerapids/cache.json +++ b/tools/perf/pmu-events/arch/x86/sapphirerapids/cache.json @@ -514,7 +514,7 @@ "EventCode": "0xd3", "EventName": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM", "PublicDescription": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM Available PDIST counters: 0", - "SampleAfterValue": "1000003", + "SampleAfterValue": "100007", "UMask": "0x2" }, { @@ -534,7 +534,7 @@ "EventCode": "0xd3", "EventName": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM", "PublicDescription": "MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM Available PDIST counters: 0", - "SampleAfterValue": "1000003", + "SampleAfterValue": "100007", "UMask": "0x4" }, { diff --git a/tools/perf/pmu-events/arch/x86/sapphirerapids/frontend.json b/tools/perf/pmu-events/arch/x86/sapphirerapids/frontend.json index 793c486ffabe..e51f5e85ffd1 100644 --- a/tools/perf/pmu-events/arch/x86/sapphirerapids/frontend.json +++ b/tools/perf/pmu-events/arch/x86/sapphirerapids/frontend.json @@ -271,6 +271,22 @@ "SampleAfterValue": "200003", "UMask": "0x4" }, + { + "BriefDescription": "ICACHE_TAG.STALLS_INUSE", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_INUSE", + "SampleAfterValue": "200003", + "UMask": "0x10" + }, + { + "BriefDescription": "ICACHE_TAG.STALLS_ISB", + "Counter": "0,1,2,3", + "EventCode": "0x83", + "EventName": "ICACHE_TAG.STALLS_ISB", + "SampleAfterValue": "200003", + "UMask": "0x8" + }, { "BriefDescription": "Cycles Decode Stream Buffer (DSB) is delivering any Uop", "Counter": "0,1,2,3", diff --git a/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-cache.json b/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-cache.json index 1bdda3c3ccbf..59f6fd2c7a8f 100644 --- a/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-cache.json +++ b/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-cache.json @@ -3501,7 +3501,7 @@ "EventName": "UNC_CHA_SNOOP_RESP.RSPIFWD", "Experimental": "1", "PerPkg": "1", - "PublicDescription": "Counts when a a transaction with the opcode type RspIFwd Snoop Response was received which indicates a remote caching agent forwarded the data and the requesting agent is able to acquire the data in E (Exclusive) or M (modified) states. This is commonly returned with RFO (the Read for Ownership issued before a write) transactions. The snoop could have either been to a cacheline in the M,E,F (Modified, Exclusive or Forward) states.", + "PublicDescription": "Counts when a transaction with the opcode type RspIFwd Snoop Response was received which indicates a remote caching agent forwarded the data and the requesting agent is able to acquire the data in E (Exclusive) or M (modified) states. This is commonly returned with RFO (the Read for Ownership issued before a write) transactions. The snoop could have either been to a cacheline in the M,E,F (Modified, Exclusive or Forward) states.", "UMask": "0x4", "Unit": "CHA" }, @@ -3523,7 +3523,7 @@ "EventName": "UNC_CHA_SNOOP_RESP.RSPSFWD", "Experimental": "1", "PerPkg": "1", - "PublicDescription": "Counts when a a transaction with the opcode type RspSFwd Snoop Response was received which indicates a remote caching agent forwarded the data but held on to its current copy. This is common for data and code reads that hit in a remote socket in E (Exclusive) or F (Forward) state.", + "PublicDescription": "Counts when a transaction with the opcode type RspSFwd Snoop Response was received which indicates a remote caching agent forwarded the data but held on to its current copy. This is common for data and code reads that hit in a remote socket in E (Exclusive) or F (Forward) state.", "UMask": "0x8", "Unit": "CHA" }, diff --git a/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-io.json b/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-io.json index dac7e6c50f31..45675a1099e2 100644 --- a/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-io.json +++ b/tools/perf/pmu-events/arch/x86/sapphirerapids/uncore-io.json @@ -303,6 +303,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", + "PortMask": "0xff", "UMask": "0xff", "Unit": "IIO" }, @@ -314,7 +315,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x01", "PublicDescription": "x16 card plugged in to stack, Or x8 card plugged in to Lane 0/1, Or x4 card is plugged in to slot 0", "UMask": "0x1", "Unit": "IIO" @@ -327,7 +328,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x02", "PublicDescription": "x4 card is plugged in to slot 1", "UMask": "0x2", "Unit": "IIO" @@ -340,7 +341,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x04", "PublicDescription": "x8 card plugged in to Lane 2/3, Or x4 card is plugged in to slot 1", "UMask": "0x4", "Unit": "IIO" @@ -353,7 +354,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x08", "PublicDescription": "x4 card is plugged in to slot 3", "UMask": "0x8", "Unit": "IIO" @@ -366,7 +367,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x10", "PublicDescription": "x16 card plugged in to stack, Or x8 card plugged in to Lane 0/1, Or x4 card is plugged in to slot 0", "UMask": "0x10", "Unit": "IIO" @@ -379,7 +380,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x20", "PublicDescription": "x4 card is plugged in to slot 1", "UMask": "0x20", "Unit": "IIO" @@ -392,7 +393,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x40", "PublicDescription": "x8 card plugged in to Lane 2/3, Or x4 card is plugged in to slot 1", "UMask": "0x40", "Unit": "IIO" @@ -405,7 +406,7 @@ "Experimental": "1", "FCMask": "0x07", "PerPkg": "1", - "PortMask": "0x0000", + "PortMask": "0x80", "PublicDescription": "x4 card is plugged in to slot 3", "UMask": "0x80", "Unit": "IIO" diff --git a/tools/perf/pmu-events/arch/x86/sierraforest/cache.json b/tools/perf/pmu-events/arch/x86/sierraforest/cache.json index de0e7661a52d..168f43557a0e 100644 --- a/tools/perf/pmu-events/arch/x86/sierraforest/cache.json +++ b/tools/perf/pmu-events/arch/x86/sierraforest/cache.json @@ -326,7 +326,7 @@ "UMask": "0x82" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 1024. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -337,7 +337,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 128. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -348,7 +348,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 16. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -359,7 +359,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 2048. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -370,7 +370,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 256. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -381,7 +381,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 32. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -392,7 +392,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 4. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -403,7 +403,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 512. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -414,7 +414,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 64. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -425,7 +425,7 @@ "UMask": "0x5" }, { - "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold defined in MEC_CR_PEBS_LD_LAT_THRESHOLD - Only counts with PEBS enabled.", + "BriefDescription": "Counts the number of tagged load uops retired that exceed the latency threshold of 8. Only counts with PEBS enabled.", "Counter": "0,1", "Data_LA": "1", "EventCode": "0xd0", @@ -499,7 +499,7 @@ "UMask": "0x12" }, { - "BriefDescription": "Counts the number of stores uops retired same as MEM_UOPS_RETIRED.ALL_STORES", + "BriefDescription": "Counts the number of stores uops retired.", "Counter": "0,1,2,3,4,5,6,7", "Data_LA": "1", "EventCode": "0xd0", diff --git a/tools/perf/pmu-events/arch/x86/sierraforest/pipeline.json b/tools/perf/pmu-events/arch/x86/sierraforest/pipeline.json index 70af13143024..cf67ff6135e0 100644 --- a/tools/perf/pmu-events/arch/x86/sierraforest/pipeline.json +++ b/tools/perf/pmu-events/arch/x86/sierraforest/pipeline.json @@ -186,7 +186,7 @@ "UMask": "0xf7" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.THREAD]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.CORE", "SampleAfterValue": "2000003", @@ -200,7 +200,7 @@ "SampleAfterValue": "2000003" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted reference clock cycles.", "Counter": "Fixed counter 2", "EventName": "CPU_CLK_UNHALTED.REF_TSC", "SampleAfterValue": "2000003", @@ -216,7 +216,7 @@ "UMask": "0x1" }, { - "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles", + "BriefDescription": "Fixed Counter: Counts the number of unhalted core clock cycles. [This event is alias to CPU_CLK_UNHALTED.CORE]", "Counter": "Fixed counter 1", "EventName": "CPU_CLK_UNHALTED.THREAD", "SampleAfterValue": "2000003", @@ -230,10 +230,10 @@ "SampleAfterValue": "2000003" }, { - "BriefDescription": "Fixed Counter: Counts the number of instructions retired", + "BriefDescription": "Fixed Counter: Counts the number of instructions retired.", "Counter": "Fixed counter 0", "EventName": "INST_RETIRED.ANY", - "PublicDescription": "Fixed Counter: Counts the number of instructions retired Available PDIST counters: 32", + "PublicDescription": "Fixed Counter: Counts the number of instructions retired. Available PDIST counters: 32", "SampleAfterValue": "2000003", "UMask": "0x1" }, @@ -309,6 +309,38 @@ "SampleAfterValue": "1000003", "UMask": "0x1" }, + { + "BriefDescription": "Counts the number of CLFLUSH, CLWB, and CLDEMOTE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.CL_INST", + "SampleAfterValue": "1000003", + "UMask": "0xff" + }, + { + "BriefDescription": "Counts the number of LFENCE instructions retired.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe0", + "EventName": "MISC_RETIRED1.LFENCE", + "SampleAfterValue": "1000003", + "UMask": "0x2" + }, + { + "BriefDescription": "Counts the number of accesses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_ACCESS", + "SampleAfterValue": "1000003", + "UMask": "0x10" + }, + { + "BriefDescription": "Counts the number of misses to KeyLocker cache.", + "Counter": "0,1,2,3,4,5,6,7", + "EventCode": "0xe1", + "EventName": "MISC_RETIRED2.KEYLOCKER_MISS", + "SampleAfterValue": "1000003", + "UMask": "0x11" + }, { "BriefDescription": "Counts the number of issue slots in a UMWAIT or TPAUSE instruction where no uop issues due to the instruction putting the CPU into the C0.1 activity state.", "Counter": "0,1,2,3,4,5,6,7", diff --git a/tools/perf/pmu-events/empty-pmu-events.c b/tools/perf/pmu-events/empty-pmu-events.c index 76c395cf513c..a92dd0424f79 100644 --- a/tools/perf/pmu-events/empty-pmu-events.c +++ b/tools/perf/pmu-events/empty-pmu-events.c @@ -1310,33 +1310,33 @@ static const char *const big_c_string = /* offset=128375 */ "migrations_per_second\000Default\000software@cpu\\-migrations\\,name\\=cpu\\-migrations@ * 1e9 / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Process migrations to a new CPU per CPU second\000\0001migrations/sec\000\000\000\000011" /* offset=128635 */ "page_faults_per_second\000Default\000software@page\\-faults\\,name\\=page\\-faults@ * 1e9 / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Page faults per CPU second\000\0001faults/sec\000\000\000\000011" /* offset=128866 */ "insn_per_cycle\000Default\000instructions / cpu\\-cycles\000insn_per_cycle < 1\000Instructions Per Cycle\000\0001instructions\000\000\000\000001" -/* offset=128979 */ "stalled_cycles_per_instruction\000Default\000max(stalled\\-cycles\\-frontend, stalled\\-cycles\\-backend) / instructions\000\000Max front or backend stalls per instruction\000\000\000\000\000\000001" -/* offset=129143 */ "frontend_cycles_idle\000Default\000stalled\\-cycles\\-frontend / cpu\\-cycles\000frontend_cycles_idle > 0.1\000Frontend stalls per cycle\000\000\000\000\000\000001" -/* offset=129273 */ "backend_cycles_idle\000Default\000stalled\\-cycles\\-backend / cpu\\-cycles\000backend_cycles_idle > 0.2\000Backend stalls per cycle\000\000\000\000\000\000001" -/* offset=129399 */ "cycles_frequency\000Default\000cpu\\-cycles / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Cycles per CPU second\000\0001GHz\000\000\000\000011" -/* offset=129575 */ "branch_frequency\000Default\000branches / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Branches per CPU second\000\0001000M/sec\000\000\000\000011" -/* offset=129755 */ "branch_miss_rate\000Default\000branch\\-misses / branches\000branch_miss_rate > 0.05\000Branch miss rate\000\000100%\000\000\000\000001" -/* offset=129859 */ "l1d_miss_rate\000Default2\000L1\\-dcache\\-load\\-misses / L1\\-dcache\\-loads\000l1d_miss_rate > 0.05\000L1D miss rate\000\000100%\000\000\000\000001" -/* offset=129975 */ "llc_miss_rate\000Default2\000LLC\\-load\\-misses / LLC\\-loads\000llc_miss_rate > 0.05\000LLC miss rate\000\000100%\000\000\000\000001" -/* offset=130076 */ "l1i_miss_rate\000Default3\000L1\\-icache\\-load\\-misses / L1\\-icache\\-loads\000l1i_miss_rate > 0.05\000L1I miss rate\000\000100%\000\000\000\000001" -/* offset=130191 */ "dtlb_miss_rate\000Default3\000dTLB\\-load\\-misses / dTLB\\-loads\000dtlb_miss_rate > 0.05\000dTLB miss rate\000\000100%\000\000\000\000001" -/* offset=130297 */ "itlb_miss_rate\000Default3\000iTLB\\-load\\-misses / iTLB\\-loads\000itlb_miss_rate > 0.05\000iTLB miss rate\000\000100%\000\000\000\000001" -/* offset=130403 */ "l1_prefetch_miss_rate\000Default4\000L1\\-dcache\\-prefetch\\-misses / L1\\-dcache\\-prefetches\000l1_prefetch_miss_rate > 0.05\000L1 prefetch miss rate\000\000100%\000\000\000\000001" -/* offset=130551 */ "CPI\000\0001 / IPC\000\000\000\000\000\000\000\000000" -/* offset=130574 */ "IPC\000group1\000inst_retired.any / cpu_clk_unhalted.thread\000\000\000\000\000\000\000\000000" -/* offset=130638 */ "Frontend_Bound_SMT\000\000idq_uops_not_delivered.core / (4 * (cpu_clk_unhalted.thread / 2 * (1 + cpu_clk_unhalted.one_thread_active / cpu_clk_unhalted.ref_xclk)))\000\000\000\000\000\000\000\000000" -/* offset=130805 */ "dcache_miss_cpi\000\000l1d\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000" -/* offset=130870 */ "icache_miss_cycles\000\000l1i\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000" -/* offset=130938 */ "cache_miss_cycles\000group1\000dcache_miss_cpi + icache_miss_cycles\000\000\000\000\000\000\000\000000" -/* offset=131010 */ "DCache_L2_All_Hits\000\000l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hit\000\000\000\000\000\000\000\000000" -/* offset=131105 */ "DCache_L2_All_Miss\000\000max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) + l2_rqsts.pf_miss + l2_rqsts.rfo_miss\000\000\000\000\000\000\000\000000" -/* offset=131240 */ "DCache_L2_All\000\000DCache_L2_All_Hits + DCache_L2_All_Miss\000\000\000\000\000\000\000\000000" -/* offset=131305 */ "DCache_L2_Hits\000\000d_ratio(DCache_L2_All_Hits, DCache_L2_All)\000\000\000\000\000\000\000\000000" -/* offset=131374 */ "DCache_L2_Misses\000\000d_ratio(DCache_L2_All_Miss, DCache_L2_All)\000\000\000\000\000\000\000\000000" -/* offset=131445 */ "M1\000\000ipc + M2\000\000\000\000\000\000\000\000000" -/* offset=131468 */ "M2\000\000ipc + M1\000\000\000\000\000\000\000\000000" -/* offset=131491 */ "M3\000\0001 / M3\000\000\000\000\000\000\000\000000" -/* offset=131512 */ "L1D_Cache_Fill_BW\000\00064 * l1d.replacement / 1e9 / duration_time\000\000\000\000\000\000\000\000000" +/* offset=128979 */ "stalled_cycles_per_instruction\000Default\000(max(stalled\\-cycles\\-frontend, stalled\\-cycles\\-backend) / instructions if has_event(stalled\\-cycles\\-frontend) & has_event(stalled\\-cycles\\-backend) else (stalled\\-cycles\\-frontend / instructions if has_event(stalled\\-cycles\\-frontend) else (stalled\\-cycles\\-backend / instructions if has_event(stalled\\-cycles\\-backend) else 0)))\000\000Max front or backend stalls per instruction\000\000\000\000\000\000001" +/* offset=129404 */ "frontend_cycles_idle\000Default\000(stalled\\-cycles\\-frontend / cpu\\-cycles if has_event(stalled\\-cycles\\-frontend) else 0)\000frontend_cycles_idle > 0.1\000Frontend stalls per cycle\000\000\000\000\000\000001" +/* offset=129583 */ "backend_cycles_idle\000Default\000(stalled\\-cycles\\-backend / cpu\\-cycles if has_event(stalled\\-cycles\\-backend) else 0)\000backend_cycles_idle > 0.2\000Backend stalls per cycle\000\000\000\000\000\000001" +/* offset=129757 */ "cycles_frequency\000Default\000cpu\\-cycles / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Cycles per CPU second\000\0001GHz\000\000\000\000011" +/* offset=129933 */ "branch_frequency\000Default\000branches / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Branches per CPU second\000\0001000M/sec\000\000\000\000011" +/* offset=130113 */ "branch_miss_rate\000Default\000branch\\-misses / branches\000branch_miss_rate > 0.05\000Branch miss rate\000\000100%\000\000\000\000001" +/* offset=130217 */ "l1d_miss_rate\000Default2\000L1\\-dcache\\-load\\-misses / L1\\-dcache\\-loads\000l1d_miss_rate > 0.05\000L1D miss rate\000\000100%\000\000\000\000001" +/* offset=130333 */ "llc_miss_rate\000Default2\000LLC\\-load\\-misses / LLC\\-loads\000llc_miss_rate > 0.05\000LLC miss rate\000\000100%\000\000\000\000001" +/* offset=130434 */ "l1i_miss_rate\000Default3\000L1\\-icache\\-load\\-misses / L1\\-icache\\-loads\000l1i_miss_rate > 0.05\000L1I miss rate\000\000100%\000\000\000\000001" +/* offset=130549 */ "dtlb_miss_rate\000Default3\000dTLB\\-load\\-misses / dTLB\\-loads\000dtlb_miss_rate > 0.05\000dTLB miss rate\000\000100%\000\000\000\000001" +/* offset=130655 */ "itlb_miss_rate\000Default3\000iTLB\\-load\\-misses / iTLB\\-loads\000itlb_miss_rate > 0.05\000iTLB miss rate\000\000100%\000\000\000\000001" +/* offset=130761 */ "l1_prefetch_miss_rate\000Default4\000L1\\-dcache\\-prefetch\\-misses / L1\\-dcache\\-prefetches\000l1_prefetch_miss_rate > 0.05\000L1 prefetch miss rate\000\000100%\000\000\000\000001" +/* offset=130909 */ "CPI\000\0001 / IPC\000\000\000\000\000\000\000\000000" +/* offset=130932 */ "IPC\000group1\000inst_retired.any / cpu_clk_unhalted.thread\000\000\000\000\000\000\000\000000" +/* offset=130996 */ "Frontend_Bound_SMT\000\000idq_uops_not_delivered.core / (4 * (cpu_clk_unhalted.thread / 2 * (1 + cpu_clk_unhalted.one_thread_active / cpu_clk_unhalted.ref_xclk)))\000\000\000\000\000\000\000\000000" +/* offset=131163 */ "dcache_miss_cpi\000\000l1d\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000" +/* offset=131228 */ "icache_miss_cycles\000\000l1i\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000" +/* offset=131296 */ "cache_miss_cycles\000group1\000dcache_miss_cpi + icache_miss_cycles\000\000\000\000\000\000\000\000000" +/* offset=131368 */ "DCache_L2_All_Hits\000\000l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hit\000\000\000\000\000\000\000\000000" +/* offset=131463 */ "DCache_L2_All_Miss\000\000max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) + l2_rqsts.pf_miss + l2_rqsts.rfo_miss\000\000\000\000\000\000\000\000000" +/* offset=131598 */ "DCache_L2_All\000\000DCache_L2_All_Hits + DCache_L2_All_Miss\000\000\000\000\000\000\000\000000" +/* offset=131663 */ "DCache_L2_Hits\000\000d_ratio(DCache_L2_All_Hits, DCache_L2_All)\000\000\000\000\000\000\000\000000" +/* offset=131732 */ "DCache_L2_Misses\000\000d_ratio(DCache_L2_All_Miss, DCache_L2_All)\000\000\000\000\000\000\000\000000" +/* offset=131803 */ "M1\000\000ipc + M2\000\000\000\000\000\000\000\000000" +/* offset=131826 */ "M2\000\000ipc + M1\000\000\000\000\000\000\000\000000" +/* offset=131849 */ "M3\000\0001 / M3\000\000\000\000\000\000\000\000000" +/* offset=131870 */ "L1D_Cache_Fill_BW\000\00064 * l1d.replacement / 1e9 / duration_time\000\000\000\000\000\000\000\000000" ; static const struct compact_pmu_event pmu_events__common_default_core[] = { @@ -2626,22 +2626,22 @@ static const struct pmu_table_entry pmu_events__common[] = { static const struct compact_pmu_event pmu_metrics__common_default_core[] = { { 127956 }, /* CPUs_utilized\000Default\000(software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@) / (duration_time * 1e9)\000\000Average CPU utilization\000\0001CPUs\000\000\000\000011 */ -{ 129273 }, /* backend_cycles_idle\000Default\000stalled\\-cycles\\-backend / cpu\\-cycles\000backend_cycles_idle > 0.2\000Backend stalls per cycle\000\000\000\000\000\000001 */ -{ 129575 }, /* branch_frequency\000Default\000branches / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Branches per CPU second\000\0001000M/sec\000\000\000\000011 */ -{ 129755 }, /* branch_miss_rate\000Default\000branch\\-misses / branches\000branch_miss_rate > 0.05\000Branch miss rate\000\000100%\000\000\000\000001 */ +{ 129583 }, /* backend_cycles_idle\000Default\000(stalled\\-cycles\\-backend / cpu\\-cycles if has_event(stalled\\-cycles\\-backend) else 0)\000backend_cycles_idle > 0.2\000Backend stalls per cycle\000\000\000\000\000\000001 */ +{ 129933 }, /* branch_frequency\000Default\000branches / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Branches per CPU second\000\0001000M/sec\000\000\000\000011 */ +{ 130113 }, /* branch_miss_rate\000Default\000branch\\-misses / branches\000branch_miss_rate > 0.05\000Branch miss rate\000\000100%\000\000\000\000001 */ { 128142 }, /* cs_per_second\000Default\000software@context\\-switches\\,name\\=context\\-switches@ * 1e9 / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Context switches per CPU second\000\0001cs/sec\000\000\000\000011 */ -{ 129399 }, /* cycles_frequency\000Default\000cpu\\-cycles / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Cycles per CPU second\000\0001GHz\000\000\000\000011 */ -{ 130191 }, /* dtlb_miss_rate\000Default3\000dTLB\\-load\\-misses / dTLB\\-loads\000dtlb_miss_rate > 0.05\000dTLB miss rate\000\000100%\000\000\000\000001 */ -{ 129143 }, /* frontend_cycles_idle\000Default\000stalled\\-cycles\\-frontend / cpu\\-cycles\000frontend_cycles_idle > 0.1\000Frontend stalls per cycle\000\000\000\000\000\000001 */ +{ 129757 }, /* cycles_frequency\000Default\000cpu\\-cycles / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Cycles per CPU second\000\0001GHz\000\000\000\000011 */ +{ 130549 }, /* dtlb_miss_rate\000Default3\000dTLB\\-load\\-misses / dTLB\\-loads\000dtlb_miss_rate > 0.05\000dTLB miss rate\000\000100%\000\000\000\000001 */ +{ 129404 }, /* frontend_cycles_idle\000Default\000(stalled\\-cycles\\-frontend / cpu\\-cycles if has_event(stalled\\-cycles\\-frontend) else 0)\000frontend_cycles_idle > 0.1\000Frontend stalls per cycle\000\000\000\000\000\000001 */ { 128866 }, /* insn_per_cycle\000Default\000instructions / cpu\\-cycles\000insn_per_cycle < 1\000Instructions Per Cycle\000\0001instructions\000\000\000\000001 */ -{ 130297 }, /* itlb_miss_rate\000Default3\000iTLB\\-load\\-misses / iTLB\\-loads\000itlb_miss_rate > 0.05\000iTLB miss rate\000\000100%\000\000\000\000001 */ -{ 130403 }, /* l1_prefetch_miss_rate\000Default4\000L1\\-dcache\\-prefetch\\-misses / L1\\-dcache\\-prefetches\000l1_prefetch_miss_rate > 0.05\000L1 prefetch miss rate\000\000100%\000\000\000\000001 */ -{ 129859 }, /* l1d_miss_rate\000Default2\000L1\\-dcache\\-load\\-misses / L1\\-dcache\\-loads\000l1d_miss_rate > 0.05\000L1D miss rate\000\000100%\000\000\000\000001 */ -{ 130076 }, /* l1i_miss_rate\000Default3\000L1\\-icache\\-load\\-misses / L1\\-icache\\-loads\000l1i_miss_rate > 0.05\000L1I miss rate\000\000100%\000\000\000\000001 */ -{ 129975 }, /* llc_miss_rate\000Default2\000LLC\\-load\\-misses / LLC\\-loads\000llc_miss_rate > 0.05\000LLC miss rate\000\000100%\000\000\000\000001 */ +{ 130655 }, /* itlb_miss_rate\000Default3\000iTLB\\-load\\-misses / iTLB\\-loads\000itlb_miss_rate > 0.05\000iTLB miss rate\000\000100%\000\000\000\000001 */ +{ 130761 }, /* l1_prefetch_miss_rate\000Default4\000L1\\-dcache\\-prefetch\\-misses / L1\\-dcache\\-prefetches\000l1_prefetch_miss_rate > 0.05\000L1 prefetch miss rate\000\000100%\000\000\000\000001 */ +{ 130217 }, /* l1d_miss_rate\000Default2\000L1\\-dcache\\-load\\-misses / L1\\-dcache\\-loads\000l1d_miss_rate > 0.05\000L1D miss rate\000\000100%\000\000\000\000001 */ +{ 130434 }, /* l1i_miss_rate\000Default3\000L1\\-icache\\-load\\-misses / L1\\-icache\\-loads\000l1i_miss_rate > 0.05\000L1I miss rate\000\000100%\000\000\000\000001 */ +{ 130333 }, /* llc_miss_rate\000Default2\000LLC\\-load\\-misses / LLC\\-loads\000llc_miss_rate > 0.05\000LLC miss rate\000\000100%\000\000\000\000001 */ { 128375 }, /* migrations_per_second\000Default\000software@cpu\\-migrations\\,name\\=cpu\\-migrations@ * 1e9 / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Process migrations to a new CPU per CPU second\000\0001migrations/sec\000\000\000\000011 */ { 128635 }, /* page_faults_per_second\000Default\000software@page\\-faults\\,name\\=page\\-faults@ * 1e9 / (software@cpu\\-clock\\,name\\=cpu\\-clock@ if #target_cpu else software@task\\-clock\\,name\\=task\\-clock@)\000\000Page faults per CPU second\000\0001faults/sec\000\000\000\000011 */ -{ 128979 }, /* stalled_cycles_per_instruction\000Default\000max(stalled\\-cycles\\-frontend, stalled\\-cycles\\-backend) / instructions\000\000Max front or backend stalls per instruction\000\000\000\000\000\000001 */ +{ 128979 }, /* stalled_cycles_per_instruction\000Default\000(max(stalled\\-cycles\\-frontend, stalled\\-cycles\\-backend) / instructions if has_event(stalled\\-cycles\\-frontend) & has_event(stalled\\-cycles\\-backend) else (stalled\\-cycles\\-frontend / instructions if has_event(stalled\\-cycles\\-frontend) else (stalled\\-cycles\\-backend / instructions if has_event(stalled\\-cycles\\-backend) else 0)))\000\000Max front or backend stalls per instruction\000\000\000\000\000\000001 */ }; @@ -2714,21 +2714,21 @@ static const struct pmu_table_entry pmu_events__test_soc_cpu[] = { }; static const struct compact_pmu_event pmu_metrics__test_soc_cpu_default_core[] = { -{ 130551 }, /* CPI\000\0001 / IPC\000\000\000\000\000\000\000\000000 */ -{ 131240 }, /* DCache_L2_All\000\000DCache_L2_All_Hits + DCache_L2_All_Miss\000\000\000\000\000\000\000\000000 */ -{ 131010 }, /* DCache_L2_All_Hits\000\000l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hit\000\000\000\000\000\000\000\000000 */ -{ 131105 }, /* DCache_L2_All_Miss\000\000max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) + l2_rqsts.pf_miss + l2_rqsts.rfo_miss\000\000\000\000\000\000\000\000000 */ -{ 131305 }, /* DCache_L2_Hits\000\000d_ratio(DCache_L2_All_Hits, DCache_L2_All)\000\000\000\000\000\000\000\000000 */ -{ 131374 }, /* DCache_L2_Misses\000\000d_ratio(DCache_L2_All_Miss, DCache_L2_All)\000\000\000\000\000\000\000\000000 */ -{ 130638 }, /* Frontend_Bound_SMT\000\000idq_uops_not_delivered.core / (4 * (cpu_clk_unhalted.thread / 2 * (1 + cpu_clk_unhalted.one_thread_active / cpu_clk_unhalted.ref_xclk)))\000\000\000\000\000\000\000\000000 */ -{ 130574 }, /* IPC\000group1\000inst_retired.any / cpu_clk_unhalted.thread\000\000\000\000\000\000\000\000000 */ -{ 131512 }, /* L1D_Cache_Fill_BW\000\00064 * l1d.replacement / 1e9 / duration_time\000\000\000\000\000\000\000\000000 */ -{ 131445 }, /* M1\000\000ipc + M2\000\000\000\000\000\000\000\000000 */ -{ 131468 }, /* M2\000\000ipc + M1\000\000\000\000\000\000\000\000000 */ -{ 131491 }, /* M3\000\0001 / M3\000\000\000\000\000\000\000\000000 */ -{ 130938 }, /* cache_miss_cycles\000group1\000dcache_miss_cpi + icache_miss_cycles\000\000\000\000\000\000\000\000000 */ -{ 130805 }, /* dcache_miss_cpi\000\000l1d\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000 */ -{ 130870 }, /* icache_miss_cycles\000\000l1i\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000 */ +{ 130909 }, /* CPI\000\0001 / IPC\000\000\000\000\000\000\000\000000 */ +{ 131598 }, /* DCache_L2_All\000\000DCache_L2_All_Hits + DCache_L2_All_Miss\000\000\000\000\000\000\000\000000 */ +{ 131368 }, /* DCache_L2_All_Hits\000\000l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hit\000\000\000\000\000\000\000\000000 */ +{ 131463 }, /* DCache_L2_All_Miss\000\000max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) + l2_rqsts.pf_miss + l2_rqsts.rfo_miss\000\000\000\000\000\000\000\000000 */ +{ 131663 }, /* DCache_L2_Hits\000\000d_ratio(DCache_L2_All_Hits, DCache_L2_All)\000\000\000\000\000\000\000\000000 */ +{ 131732 }, /* DCache_L2_Misses\000\000d_ratio(DCache_L2_All_Miss, DCache_L2_All)\000\000\000\000\000\000\000\000000 */ +{ 130996 }, /* Frontend_Bound_SMT\000\000idq_uops_not_delivered.core / (4 * (cpu_clk_unhalted.thread / 2 * (1 + cpu_clk_unhalted.one_thread_active / cpu_clk_unhalted.ref_xclk)))\000\000\000\000\000\000\000\000000 */ +{ 130932 }, /* IPC\000group1\000inst_retired.any / cpu_clk_unhalted.thread\000\000\000\000\000\000\000\000000 */ +{ 131870 }, /* L1D_Cache_Fill_BW\000\00064 * l1d.replacement / 1e9 / duration_time\000\000\000\000\000\000\000\000000 */ +{ 131803 }, /* M1\000\000ipc + M2\000\000\000\000\000\000\000\000000 */ +{ 131826 }, /* M2\000\000ipc + M1\000\000\000\000\000\000\000\000000 */ +{ 131849 }, /* M3\000\0001 / M3\000\000\000\000\000\000\000\000000 */ +{ 131296 }, /* cache_miss_cycles\000group1\000dcache_miss_cpi + icache_miss_cycles\000\000\000\000\000\000\000\000000 */ +{ 131163 }, /* dcache_miss_cpi\000\000l1d\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000 */ +{ 131228 }, /* icache_miss_cycles\000\000l1i\\-loads\\-misses / inst_retired.any\000\000\000\000\000\000\000\000000 */ }; diff --git a/tools/perf/tests/bitmap.c b/tools/perf/tests/bitmap.c index 98956e0e0765..e7adf60be721 100644 --- a/tools/perf/tests/bitmap.c +++ b/tools/perf/tests/bitmap.c @@ -16,7 +16,7 @@ static unsigned long *get_bitmap(const char *str, int nbits) bm = bitmap_zalloc(nbits); if (map && bm) { - int i; + unsigned int i; struct perf_cpu cpu; perf_cpu_map__for_each_cpu(cpu, i, map) diff --git a/tools/perf/tests/bp_signal.c b/tools/perf/tests/bp_signal.c index 3faeb5b6fe0b..f580ba7486b1 100644 --- a/tools/perf/tests/bp_signal.c +++ b/tools/perf/tests/bp_signal.c @@ -36,7 +36,7 @@ static int fd3; static int overflows; static int overflows_2; -volatile long the_var; +static volatile long the_var; /* diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index 5927d1ea20e2..47043a3a2fb4 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/tools/perf/tests/cpumap.c b/tools/perf/tests/cpumap.c index 2354246afc5a..b051dce2cd86 100644 --- a/tools/perf/tests/cpumap.c +++ b/tools/perf/tests/cpumap.c @@ -156,7 +156,8 @@ static int test__cpu_map_print(struct test_suite *test __maybe_unused, int subte return 0; } -static int __test__cpu_map_merge(const char *lhs, const char *rhs, int nr, const char *expected) +static int __test__cpu_map_merge(const char *lhs, const char *rhs, unsigned int nr, + const char *expected) { struct perf_cpu_map *a = perf_cpu_map__new(lhs); struct perf_cpu_map *b = perf_cpu_map__new(rhs); @@ -204,7 +205,8 @@ static int test__cpu_map_merge(struct test_suite *test __maybe_unused, return ret; } -static int __test__cpu_map_intersect(const char *lhs, const char *rhs, int nr, const char *expected) +static int __test__cpu_map_intersect(const char *lhs, const char *rhs, unsigned int nr, + const char *expected) { struct perf_cpu_map *a = perf_cpu_map__new(lhs); struct perf_cpu_map *b = perf_cpu_map__new(rhs); diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index a1fff4203b75..46bc3f597260 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -58,7 +58,7 @@ struct test_data_offset { int size; }; -struct test_data_offset offsets[] = { +static struct test_data_offset offsets[] = { /* Fill first cache page. */ { .offset = 10, diff --git a/tools/perf/tests/event_update.c b/tools/perf/tests/event_update.c index cb9e6de2e033..facc65e29f20 100644 --- a/tools/perf/tests/event_update.c +++ b/tools/perf/tests/event_update.c @@ -8,6 +8,7 @@ #include "header.h" #include "machine.h" #include "util/synthetic-events.h" +#include "target.h" #include "tool.h" #include "tests.h" #include "debug.h" @@ -81,7 +82,8 @@ static int test__event_update(struct test_suite *test __maybe_unused, int subtes { struct evsel *evsel; struct event_name tmp; - struct evlist *evlist = evlist__new_default(); + struct target target = {}; + struct evlist *evlist = evlist__new_default(&target, /*sample_callchains=*/false); TEST_ASSERT_VAL("failed to get evlist", evlist); diff --git a/tools/perf/tests/expand-cgroup.c b/tools/perf/tests/expand-cgroup.c index c7b32a220ca1..dd547f2f77cc 100644 --- a/tools/perf/tests/expand-cgroup.c +++ b/tools/perf/tests/expand-cgroup.c @@ -8,6 +8,7 @@ #include "parse-events.h" #include "pmu-events/pmu-events.h" #include "pfm.h" +#include "target.h" #include #include #include @@ -99,7 +100,8 @@ out: for (i = 0; i < nr_events; i++) static int expand_default_events(void) { int ret; - struct evlist *evlist = evlist__new_default(); + struct target target = {}; + struct evlist *evlist = evlist__new_default(&target, /*sample_callchains=*/false); TEST_ASSERT_VAL("failed to get evlist", evlist); diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c index 3eb9ef8d7ec6..606aa926a8fc 100644 --- a/tools/perf/tests/hists_cumulate.c +++ b/tools/perf/tests/hists_cumulate.c @@ -81,7 +81,7 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) { struct addr_location al; struct evsel *evsel = hists_to_evsel(hists); - struct perf_sample sample = { .period = 1000, }; + struct perf_sample sample = { .evsel = evsel, .period = 1000, }; size_t i; addr_location__init(&al); diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c index 1cebd20cc91c..cc6b26e373d1 100644 --- a/tools/perf/tests/hists_filter.c +++ b/tools/perf/tests/hists_filter.c @@ -70,6 +70,7 @@ static int add_hist_entries(struct evlist *evlist, }; struct hists *hists = evsel__hists(evsel); + sample.evsel = evsel; /* make sure it has no filter at first */ hists->thread_filter = NULL; hists->dso_filter = NULL; diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c index ee5ec8bda60e..7818950d786e 100644 --- a/tools/perf/tests/hists_output.c +++ b/tools/perf/tests/hists_output.c @@ -51,7 +51,7 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) { struct addr_location al; struct evsel *evsel = hists_to_evsel(hists); - struct perf_sample sample = { .period = 100, }; + struct perf_sample sample = { .evsel = evsel, .period = 100, }; size_t i; addr_location__init(&al); diff --git a/tools/perf/tests/mem2node.c b/tools/perf/tests/mem2node.c index a0e88c496107..7ce1ad7b6ce5 100644 --- a/tools/perf/tests/mem2node.c +++ b/tools/perf/tests/mem2node.c @@ -30,7 +30,7 @@ static unsigned long *get_bitmap(const char *str, int nbits) if (map && bm) { struct perf_cpu cpu; - int i; + unsigned int i; perf_cpu_map__for_each_cpu(cpu, i, map) __set_bit(cpu.cpu, bm); diff --git a/tools/perf/tests/openat-syscall-all-cpus.c b/tools/perf/tests/openat-syscall-all-cpus.c index 3644d6f52c07..0be43f8db3bd 100644 --- a/tools/perf/tests/openat-syscall-all-cpus.c +++ b/tools/perf/tests/openat-syscall-all-cpus.c @@ -22,7 +22,8 @@ static int test__openat_syscall_event_on_all_cpus(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - int err = TEST_FAIL, fd, idx; + int err = TEST_FAIL, fd; + unsigned int idx; struct perf_cpu cpu; struct perf_cpu_map *cpus; struct evsel *evsel; diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index 1d3cc224fbc2..05c3e899b425 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -1796,31 +1796,38 @@ static bool test__acr_valid(void) static int test__ratio_to_prev(struct evlist *evlist) { - struct evsel *evsel; + struct evsel *evsel, *leader; TEST_ASSERT_VAL("wrong number of entries", 2 * perf_pmus__num_core_pmus() == evlist->core.nr_entries); - evlist__for_each_entry(evlist, evsel) { - if (!perf_pmu__has_format(evsel->pmu, "acr_mask")) - return TEST_OK; - - if (evsel == evlist__first(evlist)) { - TEST_ASSERT_VAL("wrong config2", 0 == evsel->core.attr.config2); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_EVSEL("unexpected event", - evsel__match(evsel, HARDWARE, HW_CPU_CYCLES), - evsel); - } else { - TEST_ASSERT_VAL("wrong config2", 0 == evsel->core.attr.config2); - TEST_ASSERT_VAL("wrong leader", !evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 0); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_EVSEL("unexpected event", - evsel__match(evsel, HARDWARE, HW_INSTRUCTIONS), - evsel); + evlist__for_each_entry(evlist, evsel) { + if (evsel != evsel__leader(evsel) || + !perf_pmu__has_format(evsel->pmu, "acr_mask")) { + continue; } + leader = evsel; + /* cycles */ + TEST_ASSERT_VAL("wrong config2", 0 == leader->core.attr.config2); + TEST_ASSERT_VAL("wrong core.nr_members", leader->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(leader) == 0); + TEST_ASSERT_EVSEL("unexpected event", + evsel__match(leader, HARDWARE, HW_CPU_CYCLES), + leader); + /* + * The period value gets configured within evlist__config, + * while this test executes only parse events method. + */ + TEST_ASSERT_VAL("wrong period", 0 == leader->core.attr.sample_period); + + /* instructions/period=200000,ratio-to-prev=2.0/ */ + evsel = evsel__next(evsel); + TEST_ASSERT_VAL("wrong config2", 0 == evsel->core.attr.config2); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 0); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_EVSEL("unexpected event", + evsel__match(evsel, HARDWARE, HW_INSTRUCTIONS), + evsel); /* * The period value gets configured within evlist__config, * while this test executes only parse events method. diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c index efbd9cd60c63..ad44cc68820b 100644 --- a/tools/perf/tests/perf-record.c +++ b/tools/perf/tests/perf-record.c @@ -84,8 +84,11 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest CPU_ZERO_S(cpu_mask_size, cpu_mask); perf_sample__init(&sample, /*all=*/false); - if (evlist == NULL) /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */ - evlist = evlist__new_default(); + if (evlist == NULL) { /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */ + struct target target = {}; + + evlist = evlist__new_default(&target, /*sample_callchains=*/false); + } if (evlist == NULL) { pr_debug("Not enough memory to create evlist\n"); @@ -297,6 +300,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest } perf_mmap__consume(&md->core); + perf_sample__exit(&sample); } perf_mmap__read_done(&md->core); } diff --git a/tools/perf/tests/shell/data_type_profiling.sh b/tools/perf/tests/shell/data_type_profiling.sh index 2a7f8f7c42d0..eca694600a04 100755 --- a/tools/perf/tests/shell/data_type_profiling.sh +++ b/tools/perf/tests/shell/data_type_profiling.sh @@ -8,13 +8,17 @@ set -e # data type profiling manifestation # Values in testtypes and testprogs should match -testtypes=("# data-type: struct Buf" "# data-type: struct _buf") +testtypes=("# data-type: struct Buf" "# data-type: struct buf") testprogs=("perf test -w code_with_type" "perf test -w datasym") err=0 perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX) +# Check for support of perf mem before trap handler +perf mem record -o /dev/null -- true 2>&1 | \ + grep -q "failed: no PMU supports the memory events" && exit 2 + cleanup() { rm -rf "${perfdata}" "${perfout}" rm -rf "${perfdata}".old diff --git a/tools/perf/tests/shell/kwork.sh b/tools/perf/tests/shell/kwork.sh new file mode 100755 index 000000000000..42bfd9382816 --- /dev/null +++ b/tools/perf/tests/shell/kwork.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# perf kwork tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +# Root permissions required for tracing events. +if [ "$(id -u)" != 0 ]; then + echo "[Skip] No root permission" + exit 2 +fi + +err=0 +perfdata=$(mktemp /tmp/__perf_test_kwork.perf.data.XXXXX) + +cleanup() { + rm -f "${perfdata}" + rm -f "${perfdata}".old + + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_kwork_record() { + echo "Kwork record" + perf kwork record -o "${perfdata}" -- sleep 1 + echo "Kwork record [Success]" +} + +test_kwork_report() { + echo "Kwork report" + if ! perf kwork report -i "${perfdata}" | grep -q "Kwork Name"; then + echo "Kwork report [Failed missing output]" + err=1 + fi + echo "Kwork report [Success]" +} + +test_kwork_latency() { + echo "Kwork latency" + if ! perf kwork latency -i "${perfdata}" | grep -q "Avg delay"; then + echo "Kwork latency [Failed missing output]" + err=1 + fi + echo "Kwork latency [Success]" +} + +test_kwork_timehist() { + echo "Kwork timehist" + if ! perf kwork timehist -i "${perfdata}" | grep -q "Kwork name"; then + echo "Kwork timehist [Failed missing output]" + err=1 + fi + echo "Kwork timehist [Success]" +} + +test_kwork_top() { + echo "Kwork top" + if ! perf kwork top -i "${perfdata}" | grep -q "COMMAND"; then + echo "Kwork top [Failed missing output]" + err=1 + fi + echo "Kwork top [Success]" +} + +test_kwork_record +test_kwork_report +test_kwork_latency +test_kwork_timehist +test_kwork_top + +cleanup +exit $err diff --git a/tools/perf/tests/shell/perf_sched_stats.sh b/tools/perf/tests/shell/perf_sched_stats.sh index 2b1410b050d0..f13eb0a75b76 100755 --- a/tools/perf/tests/shell/perf_sched_stats.sh +++ b/tools/perf/tests/shell/perf_sched_stats.sh @@ -4,10 +4,34 @@ set -e +if [ "$(id -u)" != 0 ]; then + echo "[Skip] No root permission" + exit 2 +fi + +perfdata=$(mktemp /tmp/__perf_test_sched_stats.perf.data.XXXXX) +perfdata2=$(mktemp /tmp/__perf_test_sched_stats.perf.data.XXXXX) + +cleanup() { + rm -f "${perfdata}" + rm -f "${perfdata}".old + rm -f "${perfdata2}" + rm -f "${perfdata2}".old + + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + err=0 test_perf_sched_stats_record() { echo "Basic perf sched stats record test" - if ! perf sched stats record true 2>&1 | \ + if ! perf sched stats record -o "${perfdata}" true 2>&1 | \ grep -E -q "[ perf sched stats: Wrote samples to perf.data ]" then echo "Basic perf sched stats record test [Failed]" @@ -19,15 +43,13 @@ test_perf_sched_stats_record() { test_perf_sched_stats_report() { echo "Basic perf sched stats report test" - perf sched stats record true > /dev/null - if ! perf sched stats report 2>&1 | grep -E -q "Description" + perf sched stats record -o "${perfdata}" true > /dev/null + if ! perf sched stats report -i "${perfdata}" 2>&1 | grep -E -q "Description" then echo "Basic perf sched stats report test [Failed]" err=1 - rm perf.data return fi - rm perf.data echo "Basic perf sched stats report test [Success]" } @@ -44,16 +66,14 @@ test_perf_sched_stats_live() { test_perf_sched_stats_diff() { echo "Basic perf sched stats diff test" - perf sched stats record true > /dev/null - perf sched stats record true > /dev/null - if ! perf sched stats diff > /dev/null + perf sched stats record -o "${perfdata}" true > /dev/null + perf sched stats record -o "${perfdata2}" true > /dev/null + if ! perf sched stats diff "${perfdata}" "${perfdata2}" > /dev/null then echo "Basic perf sched stats diff test [Failed]" err=1 - rm perf.data.old perf.data return fi - rm perf.data.old perf.data echo "Basic perf sched stats diff test [Success]" } @@ -61,4 +81,6 @@ test_perf_sched_stats_record test_perf_sched_stats_report test_perf_sched_stats_live test_perf_sched_stats_diff + +cleanup exit $err diff --git a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh index ab99bef556bf..eca629ee83f0 100755 --- a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh +++ b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh @@ -22,9 +22,9 @@ event_pattern='probe_libc:inet_pton(_[[:digit:]]+)?' add_libc_inet_pton_event() { - event_name=$(perf probe -f -x $libc -a inet_pton 2>&1 | tail -n +2 | head -n -5 | \ + event_name=$(perf probe -f -x $libc -a inet_pton 2>&1 | \ awk -v ep="$event_pattern" -v l="$libc" '$0 ~ ep && $0 ~ \ - ("\\(on inet_pton in " l "\\)") {print $1}') + ("\\(on inet_pton in " l "\\)") {print $1}' | head -n 1) if [ $? -ne 0 ] || [ -z "$event_name" ] ; then printf "FAIL: could not add event\n" @@ -40,12 +40,12 @@ trace_libc_inet_pton_backtrace() { echo ".*inet_pton\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected case "$(uname -m)" in s390x) - eventattr='call-graph=dwarf,max-stack=4' + eventattr='call-graph=dwarf,max-stack=8' echo "((__GI_)?getaddrinfo|text_to_binary_address)\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected echo "(gaih_inet|main)\+0x[[:xdigit:]]+[[:space:]]\(inlined|.*/bin/ping.*\)$" >> $expected ;; *) - eventattr='max-stack=4' + eventattr='call-graph=dwarf,max-stack=8' echo ".*(\+0x[[:xdigit:]]+|\[unknown\])[[:space:]]\(.*/bin/ping.*\)$" >> $expected ;; esac diff --git a/tools/perf/tests/shell/stat_bpf_counters.sh b/tools/perf/tests/shell/stat_bpf_counters.sh index f43e28a136d3..35463358b273 100755 --- a/tools/perf/tests/shell/stat_bpf_counters.sh +++ b/tools/perf/tests/shell/stat_bpf_counters.sh @@ -41,8 +41,14 @@ check_counts() test_bpf_counters() { printf "Testing --bpf-counters " - base_instructions=$(perf stat --no-big-num -e instructions -- $workload 2>&1 | awk '/instructions/ {print $1}') - bpf_instructions=$(perf stat --no-big-num --bpf-counters -e instructions -- $workload 2>&1 | awk '/instructions/ {print $1}') + base_instructions=$(perf stat --no-big-num -e instructions -- $workload 2>&1 | \ + awk -v i=0 -v c=0 '/instructions/ { \ + if ($1 != " 0) printf "%.0f", c; else print "&1 | \ + awk -v i=0 -v c=0 '/instructions/ { \ + if ($1 != " 0) printf "%.0f", c; else print "&1) - base_instructions=$(echo "$stat_output"| awk '/base_instructions/ {print $1}') - bpf_instructions=$(echo "$stat_output"| awk '/bpf_instructions/ {print $1}') + base_instructions=$(echo "$stat_output"| \ + awk -v i=0 -v c=0 '/base_instructions/ { \ + if ($1 != " 0) printf "%.0f", c; else print " 0) printf "%.0f", c; else print " "$TMPDIR/record.txt" 2>&1 perf script -i "$TMPDIR/perf.data" --fields brstacksym > "$TMPDIR/perf.script" @@ -73,59 +79,88 @@ test_user_branches() { perf script -i "$TMPDIR/perf.data" --fields brstack | \ tr ' ' '\n' > "$TMPDIR/perf.script" - # There should be no kernel addresses with the u option, in either - # source or target addresses. - if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then - echo "ERROR: Kernel address found in user mode" + # There should be no kernel addresses in the target with the u option. + local regex="0x[89a-f][0-9a-f]{15}" + if has_kaslr_bug; then + # If the system has a kaslr bug that may leak kernel addresses + # in the source of something like an ERET/SYSRET. Make the regex + # more specific and just check the target address is in user + # code. + regex="^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/" + fi + if grep -q -E -m1 "$regex" $TMPDIR/perf.script; then + echo "Testing user branch stack sampling [Failed kernel address found in user mode]" err=1 fi # some branch types are still not being tested: # IND COND_CALL COND_RET SYSRET SERROR NO_TX + if [ $err -eq 0 ]; then + echo "Testing user branch stack sampling [Passed]" + err=$start_err + else + echo "Testing user branch stack sampling [Failed]" + fi } test_trap_eret_branches() { echo "Testing trap & eret branches" - if ! is_arm64; then - echo "skip: not arm64" - else - perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \ - perf test -w traploop 1000 - perf script -i $TMPDIR/perf.data --fields brstacksym | \ - tr ' ' '\n' > $TMPDIR/perf.script - # BRBINF.TYPE == TRAP are mapped to PERF_BR_IRQ by the BRBE driver - check_branches "^trap_bench\+[^ ]+/[^ ]/IRQ/" - check_branches "^[^ ]+/trap_bench\+[^ ]+/ERET/" + if ! is_arm64; then + echo "Testing trap & eret branches [Skipped not arm64]" + return + fi + start_err=$err + err=0 + perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \ + perf test -w traploop 1000 > "$TMPDIR/record.txt" 2>&1 + perf script -i $TMPDIR/perf.data --fields brstacksym | \ + tr ' ' '\n' > $TMPDIR/perf.script + + # BRBINF.TYPE == TRAP are mapped to PERF_BR_IRQ by the BRBE driver + check_branches "^trap_bench\+[^ ]+/[^ ]/IRQ/" + check_branches "^[^ ]+/trap_bench\+[^ ]+/ERET/" + if [ $err -eq 0 ]; then + echo "Testing trap & eret branches [Passed]" + err=$start_err + else + echo "Testing trap & eret branches [Failed]" fi } test_kernel_branches() { - echo "Testing that k option only includes kernel source addresses" + echo "Testing kernel branch sampling" - if ! perf record --branch-filter any,k -o- -- true > /dev/null; then - echo "skip: not enough privileges" + if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then + echo "Testing that k option [Skipped not enough privileges]" + return + fi + start_err=$err + err=0 + perf record -o $TMPDIR/perf.data --branch-filter any,k -- \ + perf bench syscall basic --loop 1000 > "$TMPDIR/record.txt" 2>&1 + perf script -i $TMPDIR/perf.data --fields brstack | \ + tr ' ' '\n' > $TMPDIR/perf.script + + # Example of branch entries: + # "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..." + # Source addresses come first in user or kernel code. Next is the target + # address that must be in the kernel. + + # Look for source addresses with top bit set + if ! grep -q -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then + echo "Testing kernel branch sampling [Failed kernel branches missing]" + err=1 + fi + # Look for no target addresses without top bit set + if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[0-7][0-9a-f]{1,15}/" $TMPDIR/perf.script; then + echo "Testing kernel branch sampling [Failed user branches found]" + err=1 + fi + if [ $err -eq 0 ]; then + echo "Testing kernel branch sampling [Passed]" + err=$start_err else - perf record -o $TMPDIR/perf.data --branch-filter any,k -- \ - perf bench syscall basic --loop 1000 - perf script -i $TMPDIR/perf.data --fields brstack | \ - tr ' ' '\n' > $TMPDIR/perf.script - - # Example of branch entries: - # "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..." - # Source addresses come first and target address can be either - # userspace or kernel even with k option, as long as the source - # is in kernel. - - #Look for source addresses with top bit set - if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then - echo "ERROR: Kernel branches missing" - err=1 - fi - # Look for no source addresses without top bit set - if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then - echo "ERROR: User branches found with kernel filter" - err=1 - fi + echo "Testing kernel branch sampling [Failed]" fi } @@ -136,14 +171,15 @@ test_filter() { test_filter_expect=$2 echo "Testing branch stack filtering permutation ($test_filter_filter,$test_filter_expect)" - perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 + perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- \ + ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 perf script -i "$TMPDIR/perf.data" --fields brstack > "$TMPDIR/perf.script" # fail if we find any branch type that doesn't match any of the expected ones # also consider UNKNOWN branch types (-) if [ ! -s "$TMPDIR/perf.script" ] then - echo "Empty script output" + echo "Testing branch stack filtering [Failed empty script output]" err=1 return fi @@ -154,26 +190,36 @@ test_filter() { > "$TMPDIR/perf.script-filtered" || true if [ -s "$TMPDIR/perf.script-filtered" ] then - echo "Unexpected branch filter in script output" + echo "Testing branch stack filtering [Failed unexpected branch filter]" cat "$TMPDIR/perf.script" err=1 return fi + echo "Testing branch stack filtering [Passed]" } test_syscall() { echo "Testing syscalls" # skip if perf doesn't have enough privileges - if ! perf record --branch-filter any,k -o- -- true > /dev/null; then - echo "skip: not enough privileges" - else - perf record -o $TMPDIR/perf.data --branch-filter \ - any_call,save_type,u,k -c 10000 -- \ - perf bench syscall basic --loop 1000 - perf script -i $TMPDIR/perf.data --fields brstacksym | \ - tr ' ' '\n' > $TMPDIR/perf.script + if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then + echo "Testing syscalls [Skipped: not enough privileges]" + return + fi + start_err=$err + err=0 + perf record -o $TMPDIR/perf.data --branch-filter \ + any_call,save_type,u,k -c 10007 -- \ + perf bench syscall basic --loop 8000 > "$TMPDIR/record.txt" 2>&1 + perf script -i $TMPDIR/perf.data --fields brstacksym | \ + tr ' ' '\n' > $TMPDIR/perf.script - check_branches "getppid[^ ]*/SYSCALL/" + check_branches "getppid[^ ]*/SYSCALL/" + + if [ $err -eq 0 ]; then + echo "Testing syscalls [Passed]" + err=$start_err + else + echo "Testing syscalls [Failed]" fi } set -e diff --git a/tools/perf/tests/shell/test_task_analyzer.sh b/tools/perf/tests/shell/test_task_analyzer.sh index e194fcf61df3..0314412e63b4 100755 --- a/tools/perf/tests/shell/test_task_analyzer.sh +++ b/tools/perf/tests/shell/test_task_analyzer.sh @@ -3,6 +3,11 @@ # SPDX-License-Identifier: GPL-2.0 tmpdir=$(mktemp -d /tmp/perf-script-task-analyzer-XXXXX) +# TODO: perf script report only supports input from the CWD perf.data file, make +# it support input from any file. +perfdata="perf.data" +csv="$tmpdir/csv" +csvsummary="$tmpdir/csvsummary" err=0 # set PERF_EXEC_PATH to find scripts in the source directory @@ -15,11 +20,10 @@ fi export ASAN_OPTIONS=detect_leaks=0 cleanup() { - rm -f perf.data - rm -f perf.data.old - rm -f csv - rm -f csvsummary + rm -f "${perfdata}" + rm -f "${perfdata}".old rm -rf "$tmpdir" + trap - exit term int } @@ -61,10 +65,10 @@ skip_no_probe_record_support() { prepare_perf_data() { # 1s should be sufficient to catch at least some switches - perf record -e sched:sched_switch -a -- sleep 1 > /dev/null 2>&1 + perf record -e sched:sched_switch -a -o "${perfdata}" -- sleep 1 > /dev/null 2>&1 # check if perf data file got created in above step. - if [ ! -e "perf.data" ]; then - printf "FAIL: perf record failed to create \"perf.data\" \n" + if [ ! -e "${perfdata}" ]; then + printf "FAIL: perf record failed to create \"${perfdata}\" \n" return 1 fi } @@ -130,28 +134,28 @@ test_extended_times_summary_ns() { } test_csv() { - perf script report task-analyzer --csv csv > /dev/null - check_exec_0 "perf script report task-analyzer --csv csv" - find_str_or_fail "Comm;" csv "${FUNCNAME[0]}" + perf script report task-analyzer --csv "${csv}" > /dev/null + check_exec_0 "perf script report task-analyzer --csv ${csv}" + find_str_or_fail "Comm;" "${csv}" "${FUNCNAME[0]}" } test_csv_extended_times() { - perf script report task-analyzer --csv csv --extended-times > /dev/null - check_exec_0 "perf script report task-analyzer --csv csv --extended-times" - find_str_or_fail "Out-Out;" csv "${FUNCNAME[0]}" + perf script report task-analyzer --csv "${csv}" --extended-times > /dev/null + check_exec_0 "perf script report task-analyzer --csv ${csv} --extended-times" + find_str_or_fail "Out-Out;" "${csv}" "${FUNCNAME[0]}" } test_csvsummary() { - perf script report task-analyzer --csv-summary csvsummary > /dev/null - check_exec_0 "perf script report task-analyzer --csv-summary csvsummary" - find_str_or_fail "Comm;" csvsummary "${FUNCNAME[0]}" + perf script report task-analyzer --csv-summary "${csvsummary}" > /dev/null + check_exec_0 "perf script report task-analyzer --csv-summary ${csvsummary}" + find_str_or_fail "Comm;" "${csvsummary}" "${FUNCNAME[0]}" } test_csvsummary_extended() { - perf script report task-analyzer --csv-summary csvsummary --summary-extended \ + perf script report task-analyzer --csv-summary "${csvsummary}" --summary-extended \ >/dev/null - check_exec_0 "perf script report task-analyzer --csv-summary csvsummary --summary-extended" - find_str_or_fail "Out-Out;" csvsummary "${FUNCNAME[0]}" + check_exec_0 "perf script report task-analyzer --csv-summary ${csvsummary} --summary-extended" + find_str_or_fail "Out-Out;" "${csvsummary}" "${FUNCNAME[0]}" } skip_no_probe_record_support diff --git a/tools/perf/tests/shell/trace_btf_general.sh b/tools/perf/tests/shell/trace_btf_general.sh index ef2da806be6b..7a94a5743924 100755 --- a/tools/perf/tests/shell/trace_btf_general.sh +++ b/tools/perf/tests/shell/trace_btf_general.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf trace BTF general tests +# perf trace BTF general tests (exclusive) # SPDX-License-Identifier: GPL-2.0 err=0 diff --git a/tools/perf/tests/switch-tracking.c b/tools/perf/tests/switch-tracking.c index 15791fcb76b2..72a8289e846d 100644 --- a/tools/perf/tests/switch-tracking.c +++ b/tools/perf/tests/switch-tracking.c @@ -239,11 +239,13 @@ static int add_event(struct evlist *evlist, struct list_head *events, if (!sample.time) { pr_debug("event with no time\n"); + perf_sample__exit(&sample); return -1; } node->event_time = sample.time; + perf_sample__exit(&sample); return 0; } diff --git a/tools/perf/tests/thread-map.c b/tools/perf/tests/thread-map.c index 54209592168d..877868107455 100644 --- a/tools/perf/tests/thread-map.c +++ b/tools/perf/tests/thread-map.c @@ -9,7 +9,6 @@ #include "debug.h" #include "event.h" #include "util/synthetic-events.h" -#include #include #include diff --git a/tools/perf/tests/topology.c b/tools/perf/tests/topology.c index ec01150d208d..f54502ebef4b 100644 --- a/tools/perf/tests/topology.c +++ b/tools/perf/tests/topology.c @@ -9,6 +9,7 @@ #include "evlist.h" #include "debug.h" #include "pmus.h" +#include "target.h" #include #define TEMPL "/tmp/perf-test-XXXXXX" @@ -37,11 +38,12 @@ static int session_write_header(char *path) .path = path, .mode = PERF_DATA_MODE_WRITE, }; + struct target target = {}; session = perf_session__new(&data, NULL); TEST_ASSERT_VAL("can't get session", !IS_ERR(session)); - session->evlist = evlist__new_default(); + session->evlist = evlist__new_default(&target, /*sample_callchains=*/false); TEST_ASSERT_VAL("can't get evlist", session->evlist); session->evlist->session = session; @@ -52,7 +54,8 @@ static int session_write_header(char *path) session->header.data_size += DATA_SIZE; TEST_ASSERT_VAL("failed to write header", - !perf_session__write_header(session, session->evlist, data.file.fd, true)); + !perf_session__write_header(session, session->evlist, + perf_data__fd(&data), true)); evlist__delete(session->evlist); perf_session__delete(session); @@ -67,7 +70,7 @@ static int check_cpu_topology(char *path, struct perf_cpu_map *map) .path = path, .mode = PERF_DATA_MODE_READ, }; - int i; + unsigned int i; struct aggr_cpu_id id; struct perf_cpu cpu; struct perf_env *env; @@ -114,7 +117,7 @@ static int check_cpu_topology(char *path, struct perf_cpu_map *map) TEST_ASSERT_VAL("Session header CPU map not set", env->cpu); - for (i = 0; i < env->nr_cpus_avail; i++) { + for (i = 0; i < (unsigned int)env->nr_cpus_avail; i++) { cpu.cpu = i; if (!perf_cpu_map__has(map, cpu)) continue; diff --git a/tools/perf/tests/workloads/datasym.c b/tools/perf/tests/workloads/datasym.c index 1d0b7d64e1ba..19242c7255c0 100644 --- a/tools/perf/tests/workloads/datasym.c +++ b/tools/perf/tests/workloads/datasym.c @@ -4,14 +4,14 @@ #include #include "../tests.h" -typedef struct _buf { +struct buf { char data1; char reserved[55]; char data2; -} buf __attribute__((aligned(64))); +} __attribute__((aligned(64))); /* volatile to try to avoid the compiler seeing reserved as unused. */ -static volatile buf workload_datasym_buf1 = { +static volatile struct buf workload_datasym_buf1 = { /* to have this in the data section */ .reserved[0] = 1, }; diff --git a/tools/perf/tests/wp.c b/tools/perf/tests/wp.c index 6c178985e37f..69b31f00eed0 100644 --- a/tools/perf/tests/wp.c +++ b/tools/perf/tests/wp.c @@ -22,11 +22,11 @@ do { \ #ifdef __i386__ /* Only breakpoint length less-than 8 has hardware support on i386. */ -volatile u32 data1; +static volatile u32 data1; #else -volatile u64 data1; +static volatile u64 data1; #endif -volatile u8 data2[3]; +static volatile u8 data2[3]; #ifndef __s390x__ static int wp_read(int fd, long long *count, int size) diff --git a/tools/perf/trace/beauty/drm_ioctl.sh b/tools/perf/trace/beauty/drm_ioctl.sh index 9aa94fd523a9..f2f1a257bac8 100755 --- a/tools/perf/trace/beauty/drm_ioctl.sh +++ b/tools/perf/trace/beauty/drm_ioctl.sh @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: LGPL-2.1 -[ $# -eq 1 ] && header_dir=$1 || header_dir=tools/include/uapi/drm/ +[ $# -eq 1 ] && header_dir=$1 || header_dir=tools/perf/trace/beauty/include/uapi/drm/ printf "#ifndef DRM_COMMAND_BASE\n" grep "#define DRM_COMMAND_BASE" $header_dir/drm.h diff --git a/tools/perf/trace/beauty/fadvise.sh b/tools/perf/trace/beauty/fadvise.sh index 4d3dd6e56ded..e9857112fa51 100755 --- a/tools/perf/trace/beauty/fadvise.sh +++ b/tools/perf/trace/beauty/fadvise.sh @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: LGPL-2.1 -[ $# -eq 1 ] && header_dir=$1 || header_dir=tools/include/uapi/linux/ +[ $# -eq 1 ] && header_dir=$1 || header_dir=tools/perf/trace/beauty/include/uapi/linux/ printf "static const char *fadvise_advices[] = {\n" regex='^[[:space:]]*#[[:space:]]*define[[:space:]]+POSIX_FADV_(\w+)[[:space:]]+([[:digit:]]+)[[:space:]]+.*' diff --git a/tools/include/uapi/drm/drm.h b/tools/perf/trace/beauty/include/uapi/drm/drm.h similarity index 100% rename from tools/include/uapi/drm/drm.h rename to tools/perf/trace/beauty/include/uapi/drm/drm.h diff --git a/tools/include/uapi/drm/i915_drm.h b/tools/perf/trace/beauty/include/uapi/drm/i915_drm.h similarity index 100% rename from tools/include/uapi/drm/i915_drm.h rename to tools/perf/trace/beauty/include/uapi/drm/i915_drm.h diff --git a/tools/include/uapi/linux/fadvise.h b/tools/perf/trace/beauty/include/uapi/linux/fadvise.h similarity index 100% rename from tools/include/uapi/linux/fadvise.h rename to tools/perf/trace/beauty/include/uapi/linux/fadvise.h diff --git a/tools/perf/util/Build b/tools/perf/util/Build index bcccad7487a9..70cc91d00804 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -149,7 +149,6 @@ endif perf-util-y += cs-etm-base.o perf-util-y += parse-branch-options.o -perf-util-y += dump-insn.o perf-util-y += parse-regs-options.o perf-util-y += parse-sublevel-options.o perf-util-y += term.o @@ -171,7 +170,6 @@ perf-util-y += mutex.o perf-util-y += sharded_mutex.o perf-util-y += intel-tpebs.o -perf-util-$(CONFIG_LIBBPF) += bpf_map.o perf-util-$(CONFIG_PERF_BPF_SKEL) += bpf_counter.o perf-util-$(CONFIG_PERF_BPF_SKEL) += bpf_counter_cgroup.o perf-util-$(CONFIG_PERF_BPF_SKEL) += bpf_ftrace.o diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c index 31c0391fffa3..4b0d349ed334 100644 --- a/tools/perf/util/addr2line.c +++ b/tools/perf/util/addr2line.c @@ -18,9 +18,6 @@ #define MAX_INLINE_NEST 1024 -/* If addr2line doesn't return data for 5 seconds then timeout. */ -int addr2line_timeout_ms = 5 * 1000; - static int filename_split(char *filename, unsigned int *line_nr) { char *sep; @@ -123,7 +120,7 @@ static enum cmd_a2l_style cmd_addr2line_configure(struct child_process *a2l, con lines = 3; pr_debug3("Detected binutils addr2line style\n"); } else { - if (!symbol_conf.disable_add2line_warn) { + if (!symbol_conf.addr2line_disable_warn) { char *output = NULL; size_t output_len; @@ -310,7 +307,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, } if (a2l == NULL) { - if (!symbol_conf.disable_add2line_warn) + if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); goto out; } @@ -330,16 +327,16 @@ int cmd__addr2line(const char *dso_name, u64 addr, len = snprintf(buf, sizeof(buf), "%016"PRIx64"\n,\n", addr); written = len > 0 ? write(a2l->in, buf, len) : -1; if (written != len) { - if (!symbol_conf.disable_add2line_warn) + if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not send request\n", __func__, dso_name); goto out; } io__init(&io, a2l->out, buf, sizeof(buf)); - io.timeout_ms = addr2line_timeout_ms; + io.timeout_ms = symbol_conf.addr2line_timeout_ms; switch (read_addr2line_record(&io, cmd_a2l_style, dso_name, addr, /*first=*/true, &record_function, &record_filename, &record_line_nr)) { case -1: - if (!symbol_conf.disable_add2line_warn) + if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not read first record\n", __func__, dso_name); goto out; case 0: @@ -355,7 +352,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, /*addr=*/1, /*first=*/true, NULL, NULL, NULL)) { case -1: - if (!symbol_conf.disable_add2line_warn) + if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not read sentinel record\n", __func__, dso_name); break; @@ -363,7 +360,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, /* The sentinel as expected. */ break; default: - if (!symbol_conf.disable_add2line_warn) + if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: unexpected record instead of sentinel", __func__, dso_name); break; diff --git a/tools/perf/util/addr2line.h b/tools/perf/util/addr2line.h index d35a47ba8dab..75989a92f16b 100644 --- a/tools/perf/util/addr2line.h +++ b/tools/perf/util/addr2line.h @@ -8,8 +8,6 @@ struct dso; struct inline_node; struct symbol; -extern int addr2line_timeout_ms; - int cmd__addr2line(const char *dso_name, u64 addr, char **file, unsigned int *line_nr, struct dso *dso, diff --git a/tools/perf/util/affinity.c b/tools/perf/util/affinity.c index 4fe851334296..6c64b5f69a4e 100644 --- a/tools/perf/util/affinity.c +++ b/tools/perf/util/affinity.c @@ -90,7 +90,7 @@ void cpu_map__set_affinity(const struct perf_cpu_map *cpumap) int cpu_set_size = get_cpu_set_size(); unsigned long *cpuset = bitmap_zalloc(cpu_set_size * 8); struct perf_cpu cpu; - int idx; + unsigned int idx; if (!cpuset) return; diff --git a/tools/perf/util/annotate-arch/annotate-loongarch.c b/tools/perf/util/annotate-arch/annotate-loongarch.c index 950f34e59e5c..c2addca77320 100644 --- a/tools/perf/util/annotate-arch/annotate-loongarch.c +++ b/tools/perf/util/annotate-arch/annotate-loongarch.c @@ -110,6 +110,7 @@ static int loongarch_jump__parse(const struct arch *arch, struct ins_operands *o } static const struct ins_ops loongarch_jump_ops = { + .free = jump__delete, .parse = loongarch_jump__parse, .scnprintf = jump__scnprintf, .is_jump = true, diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c index eb9a649ca656..7e6136536393 100644 --- a/tools/perf/util/annotate-arch/annotate-x86.c +++ b/tools/perf/util/annotate-arch/annotate-x86.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include #include +#include #include #include #include "../annotate-data.h" @@ -204,6 +205,15 @@ static int x86__cpuid_parse(struct arch *arch, const char *cpuid) } #ifdef HAVE_LIBDW_SUPPORT +static void invalidate_reg_state(struct type_state_reg *reg) +{ + reg->kind = TSR_KIND_INVALID; + reg->ok = false; + reg->lifetime_active = false; + reg->lifetime_end = 0; + reg->copied_from = -1; +} + static void update_insn_state_x86(struct type_state *state, struct data_loc_info *dloc, Dwarf_Die *cu_die, struct disasm_line *dl) @@ -222,24 +232,40 @@ static void update_insn_state_x86(struct type_state *state, if (ins__is_call(&dl->ins)) { struct symbol *func = dl->ops.target.sym; + const char *call_name; + u64 call_addr; - if (func == NULL) - return; + /* Try to resolve the call target name */ + if (func) + call_name = func->name; + else + call_name = dl->ops.target.name; /* __fentry__ will preserve all registers */ - if (!strcmp(func->name, "__fentry__")) + if (call_name && !strcmp(call_name, "__fentry__")) return; - pr_debug_dtp("call [%x] %s\n", insn_offset, func->name); + if (call_name) + pr_debug_dtp("call [%x] %s\n", insn_offset, call_name); + else + pr_debug_dtp("call [%x] \n", insn_offset); - /* Otherwise invalidate caller-saved registers after call */ + /* Invalidate caller-saved registers after call */ + call_addr = map__rip_2objdump(dloc->ms->map, + dloc->ms->sym->start + dl->al.offset); for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) { - if (state->regs[i].caller_saved) - state->regs[i].ok = false; + struct type_state_reg *reg = &state->regs[i]; + + if (!reg->caller_saved) + continue; + /* Keep register valid within DWARF location lifetime */ + if (reg->lifetime_active && call_addr < reg->lifetime_end) + continue; + invalidate_reg_state(reg); } /* Update register with the return type (if any) */ - if (die_find_func_rettype(cu_die, func->name, &type_die)) { + if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) { tsr = &state->regs[state->ret_reg]; tsr->type = type_die; tsr->kind = TSR_KIND_TYPE; @@ -265,6 +291,8 @@ static void update_insn_state_x86(struct type_state *state, tsr = &state->regs[dst->reg1]; tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; if (src->imm) imm_value = src->offset; @@ -330,6 +358,8 @@ static void update_insn_state_x86(struct type_state *state, tsr = &state->regs[dst->reg1]; tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; if (src->imm) imm_value = src->offset; @@ -364,8 +394,7 @@ static void update_insn_state_x86(struct type_state *state, src_tsr = state->regs[sreg]; tsr = &state->regs[dst->reg1]; - tsr->copied_from = -1; - tsr->ok = false; + invalidate_reg_state(tsr); /* Case 1: Based on stack pointer or frame pointer */ if (sreg == fbreg || sreg == state->stack_reg) { @@ -433,8 +462,7 @@ static void update_insn_state_x86(struct type_state *state, !strncmp(dl->ins.name, "inc", 3) || !strncmp(dl->ins.name, "dec", 3)) { pr_debug_dtp("%s [%x] invalidate reg%d\n", dl->ins.name, insn_offset, dst->reg1); - state->regs[dst->reg1].ok = false; - state->regs[dst->reg1].copied_from = -1; + invalidate_reg_state(&state->regs[dst->reg1]); return; } @@ -446,6 +474,8 @@ static void update_insn_state_x86(struct type_state *state, state->regs[dst->reg1].kind = TSR_KIND_CONST; state->regs[dst->reg1].imm_value = 0; state->regs[dst->reg1].ok = true; + state->regs[dst->reg1].lifetime_active = false; + state->regs[dst->reg1].lifetime_end = 0; state->regs[dst->reg1].copied_from = -1; return; } @@ -496,7 +526,7 @@ static void update_insn_state_x86(struct type_state *state, if (!get_global_var_type(cu_die, dloc, ip, var_addr, &offset, &type_die) || !die_get_member_type(&type_die, offset, &type_die)) { - tsr->ok = false; + invalidate_reg_state(tsr); return; } @@ -524,7 +554,7 @@ static void update_insn_state_x86(struct type_state *state, if (!has_reg_type(state, src->reg1) || !state->regs[src->reg1].ok) { - tsr->ok = false; + invalidate_reg_state(tsr); return; } @@ -532,6 +562,8 @@ static void update_insn_state_x86(struct type_state *state, tsr->kind = state->regs[src->reg1].kind; tsr->imm_value = state->regs[src->reg1].imm_value; tsr->offset = state->regs[src->reg1].offset; + tsr->lifetime_active = state->regs[src->reg1].lifetime_active; + tsr->lifetime_end = state->regs[src->reg1].lifetime_end; tsr->ok = true; /* To copy back the variable type later (hopefully) */ @@ -560,7 +592,7 @@ retry: stack = find_stack_state(state, offset); if (stack == NULL) { - tsr->ok = false; + invalidate_reg_state(tsr); return; } else if (!stack->compound) { tsr->type = stack->type; @@ -575,7 +607,7 @@ retry: tsr->offset = 0; tsr->ok = true; } else { - tsr->ok = false; + invalidate_reg_state(tsr); return; } @@ -628,7 +660,7 @@ retry: if (!get_global_var_type(cu_die, dloc, ip, addr, &offset, &type_die) || !die_get_member_type(&type_die, offset, &type_die)) { - tsr->ok = false; + invalidate_reg_state(tsr); return; } @@ -679,7 +711,7 @@ retry: } pr_debug_type_name(&tsr->type, tsr->kind); } else { - tsr->ok = false; + invalidate_reg_state(tsr); } } /* And then dereference the calculated pointer if it has one */ @@ -721,7 +753,7 @@ retry: } } - tsr->ok = false; + invalidate_reg_state(tsr); } } /* Case 3. register to memory transfers */ diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c index 44fbd41e3845..1eff0a27237d 100644 --- a/tools/perf/util/annotate-data.c +++ b/tools/perf/util/annotate-data.c @@ -455,13 +455,6 @@ static const char *match_result_str(enum type_match_result tmr) } } -static bool is_pointer_type(Dwarf_Die *type_die) -{ - int tag = dwarf_tag(type_die); - - return tag == DW_TAG_pointer_type || tag == DW_TAG_array_type; -} - static bool is_compound_type(Dwarf_Die *type_die) { int tag = dwarf_tag(type_die); @@ -474,19 +467,24 @@ static bool is_better_type(Dwarf_Die *type_a, Dwarf_Die *type_b) { Dwarf_Word size_a, size_b; Dwarf_Die die_a, die_b; + Dwarf_Die ptr_a, ptr_b; + Dwarf_Die *ptr_type_a, *ptr_type_b; + + ptr_type_a = die_get_pointer_type(type_a, &ptr_a); + ptr_type_b = die_get_pointer_type(type_b, &ptr_b); /* pointer type is preferred */ - if (is_pointer_type(type_a) != is_pointer_type(type_b)) - return is_pointer_type(type_b); + if ((ptr_type_a != NULL) != (ptr_type_b != NULL)) + return ptr_type_b != NULL; - if (is_pointer_type(type_b)) { + if (ptr_type_b) { /* * We want to compare the target type, but 'void *' can fail to * get the target type. */ - if (die_get_real_type(type_a, &die_a) == NULL) + if (die_get_real_type(ptr_type_a, &die_a) == NULL) return true; - if (die_get_real_type(type_b, &die_b) == NULL) + if (die_get_real_type(ptr_type_b, &die_b) == NULL) return false; type_a = &die_a; @@ -539,7 +537,7 @@ static enum type_match_result check_variable(struct data_loc_info *dloc, * and local variables are accessed directly without a pointer. */ if (needs_pointer) { - if (!is_pointer_type(type_die) || + if (die_get_pointer_type(type_die, type_die) == NULL || __die_get_real_type(type_die, type_die) == NULL) return PERF_TMR_NO_POINTER; } @@ -776,12 +774,7 @@ static void global_var__collect(struct data_loc_info *dloc) if (!dwarf_offdie(dwarf, pos->die_off, &type_die)) continue; - if (!get_global_var_info(dloc, pos->addr, &var_name, - &var_offset)) - continue; - - if (var_offset != 0) - continue; + get_global_var_info(dloc, pos->addr, &var_name, &var_offset); global_var__add(dloc, pos->addr, var_name, &type_die); } @@ -816,9 +809,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc, } /* Try to get the variable by address first */ - if (die_find_variable_by_addr(cu_die, var_addr, &var_die, &offset) && - check_variable(dloc, &var_die, type_die, DWARF_REG_PC, offset, - /*is_fbreg=*/false) == PERF_TMR_OK) { + if (die_find_variable_by_addr(cu_die, var_addr, &var_die, type_die, + &offset)) { var_name = dwarf_diename(&var_die); *var_offset = offset; goto ok; @@ -848,6 +840,18 @@ static bool die_is_same(Dwarf_Die *die_a, Dwarf_Die *die_b) return (die_a->cu == die_b->cu) && (die_a->addr == die_b->addr); } +static void tsr_set_lifetime(struct type_state_reg *tsr, + const struct die_var_type *var) +{ + if (var && var->has_range && var->end > var->addr) { + tsr->lifetime_active = true; + tsr->lifetime_end = var->end; + } else { + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + } +} + /** * update_var_state - Update type state using given variables * @state: type state table @@ -873,19 +877,29 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo } for (var = var_types; var != NULL; var = var->next) { - if (var->addr != addr) - continue; + /* Check if addr falls within the variable's valid range */ + if (var->has_range) { + if (addr < var->addr || (var->end && addr >= var->end)) + continue; + } else { + if (addr != var->addr) + continue; + } /* Get the type DIE using the offset */ if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die)) continue; if (var->reg == DWARF_REG_FB || var->reg == fbreg || var->reg == state->stack_reg) { + Dwarf_Die ptr_die; + Dwarf_Die *ptr_type; int offset = var->offset; struct type_state_stack *stack; + ptr_type = die_get_pointer_type(&mem_die, &ptr_die); + /* If the reg location holds the pointer value, dereference the type */ - if (!var->is_reg_var_addr && is_pointer_type(&mem_die) && - __die_get_real_type(&mem_die, &mem_die) == NULL) + if (!var->is_reg_var_addr && ptr_type && + __die_get_real_type(ptr_type, &mem_die) == NULL) continue; if (var->reg != DWARF_REG_FB) @@ -927,6 +941,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo reg->type = mem_die; reg->kind = TSR_KIND_POINTER; reg->ok = true; + tsr_set_lifetime(reg, var); pr_debug_dtp("var [%"PRIx64"] reg%d addr offset %x", insn_offset, var->reg, var->offset); @@ -943,6 +958,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo reg->type = mem_die; reg->kind = TSR_KIND_TYPE; reg->ok = true; + tsr_set_lifetime(reg, var); pr_debug_dtp("var [%"PRIx64"] reg%d offset %x", insn_offset, var->reg, var->offset); @@ -1110,7 +1126,9 @@ again: goto check_non_register; if (state->regs[reg].kind == TSR_KIND_TYPE) { + Dwarf_Die ptr_die; Dwarf_Die sized_type; + Dwarf_Die *ptr_type; struct strbuf sb; strbuf_init(&sb, 32); @@ -1122,7 +1140,8 @@ again: * Normal registers should hold a pointer (or array) to * dereference a memory location. */ - if (!is_pointer_type(&state->regs[reg].type)) { + ptr_type = die_get_pointer_type(&state->regs[reg].type, &ptr_die); + if (!ptr_type) { if (dloc->op->offset < 0 && reg != state->stack_reg) goto check_kernel; @@ -1130,7 +1149,7 @@ again: } /* Remove the pointer and get the target type */ - if (__die_get_real_type(&state->regs[reg].type, type_die) == NULL) + if (__die_get_real_type(ptr_type, type_die) == NULL) return PERF_TMR_NO_POINTER; dloc->type_offset = dloc->op->offset + state->regs[reg].offset; @@ -1230,6 +1249,11 @@ again: return PERF_TMR_BAIL_OUT; } + if (state->regs[reg].kind == TSR_KIND_CONST && + dso__kernel(map__dso(dloc->ms->map))) { + if (dloc->op->offset < 0 && reg != state->stack_reg && reg != dloc->fbreg) + goto check_kernel; + } check_non_register: if (reg == dloc->fbreg || reg == state->stack_reg) { struct type_state_stack *stack; @@ -1601,12 +1625,13 @@ retry: if (reg == DWARF_REG_PC) { if (!die_find_variable_by_addr(&scopes[i], dloc->var_addr, - &var_die, &type_offset)) + &var_die, &mem_die, + &type_offset)) continue; } else { /* Look up variables/parameters in this scope */ if (!die_find_variable_by_reg(&scopes[i], pc, reg, - &type_offset, is_fbreg, &var_die)) + &mem_die, &type_offset, is_fbreg, &var_die)) continue; } @@ -1614,26 +1639,22 @@ retry: dwarf_diename(&var_die), (long)dwarf_dieoffset(&var_die), i+1, nr_scopes, (long)dwarf_dieoffset(&scopes[i])); - /* Found a variable, see if it's correct */ - result = check_variable(dloc, &var_die, &mem_die, reg, type_offset, is_fbreg); - if (result == PERF_TMR_OK) { - if (reg == DWARF_REG_PC) { - pr_debug_dtp("addr=%#"PRIx64" type_offset=%#x\n", - dloc->var_addr, type_offset); - } else if (reg == DWARF_REG_FB || is_fbreg) { - pr_debug_dtp("stack_offset=%#x type_offset=%#x\n", - fb_offset, type_offset); - } else { - pr_debug_dtp("type_offset=%#x\n", type_offset); - } - - if (!found || is_better_type(type_die, &mem_die)) { - *type_die = mem_die; - dloc->type_offset = type_offset; - found = true; - } + if (reg == DWARF_REG_PC) { + pr_debug_dtp("addr=%#"PRIx64" type_offset=%#x\n", + dloc->var_addr, type_offset); + } else if (reg == DWARF_REG_FB || is_fbreg) { + pr_debug_dtp("stack_offset=%#x type_offset=%#x\n", + fb_offset, type_offset); } else { - pr_debug_dtp("failed: %s\n", match_result_str(result)); + pr_debug_dtp("type_offset=%#x\n", type_offset); + } + + if (!found || dloc->type_offset < type_offset || + (dloc->type_offset == type_offset && + !is_better_type(&mem_die, type_die))) { + *type_die = mem_die; + dloc->type_offset = type_offset; + found = true; } pr_debug_location(&var_die, pc, reg); diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h index 9b222869e42d..c26130744260 100644 --- a/tools/perf/util/annotate-data.h +++ b/tools/perf/util/annotate-data.h @@ -182,6 +182,9 @@ struct type_state_reg { s32 offset; bool ok; bool caller_saved; + /* DWARF location range tracking for register lifetime */ + bool lifetime_active; + u64 lifetime_end; u8 kind; u8 copied_from; }; diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index 63f0ee9d4c03..e745f3034a0e 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -8,7 +8,6 @@ #include #include -#include #include #include "util.h" // hex_width() #include "ui/ui.h" @@ -1245,7 +1244,7 @@ int hist_entry__annotate_printf(struct hist_entry *he, struct evsel *evsel) if (opts->full_path) d_filename = filename; else - d_filename = basename(filename); + d_filename = perf_basename(filename); if (evsel__is_group_event(evsel)) { evsel__group_desc(evsel, buf, sizeof(buf)); diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c index 70dd9bee47c7..e5835042acdf 100644 --- a/tools/perf/util/arm-spe.c +++ b/tools/perf/util/arm-spe.c @@ -353,12 +353,26 @@ static struct simd_flags arm_spe__synth_simd_flags(const struct arm_spe_record * if (record->op & ARM_SPE_OP_SVE) simd_flags.arch |= SIMD_OP_FLAGS_ARCH_SVE; + else if (record->op & ARM_SPE_OP_SME) + simd_flags.arch |= SIMD_OP_FLAGS_ARCH_SME; + else if (record->op & (ARM_SPE_OP_ASE | ARM_SPE_OP_SIMD_FP)) + simd_flags.arch |= SIMD_OP_FLAGS_ARCH_ASE; - if (record->type & ARM_SPE_SVE_PARTIAL_PRED) - simd_flags.pred |= SIMD_OP_FLAGS_PRED_PARTIAL; - - if (record->type & ARM_SPE_SVE_EMPTY_PRED) - simd_flags.pred |= SIMD_OP_FLAGS_PRED_EMPTY; + if (record->op & ARM_SPE_OP_SVE) { + if (!(record->op & ARM_SPE_OP_PRED)) + simd_flags.pred = SIMD_OP_FLAGS_PRED_DISABLED; + else if (record->type & ARM_SPE_SVE_PARTIAL_PRED) + simd_flags.pred = SIMD_OP_FLAGS_PRED_PARTIAL; + else if (record->type & ARM_SPE_SVE_EMPTY_PRED) + simd_flags.pred = SIMD_OP_FLAGS_PRED_EMPTY; + else + simd_flags.pred = SIMD_OP_FLAGS_PRED_FULL; + } else { + if (record->type & ARM_SPE_SVE_PARTIAL_PRED) + simd_flags.pred = SIMD_OP_FLAGS_PRED_PARTIAL; + else if (record->type & ARM_SPE_SVE_EMPTY_PRED) + simd_flags.pred = SIMD_OP_FLAGS_PRED_EMPTY; + } return simd_flags; } diff --git a/tools/perf/util/block-info.c b/tools/perf/util/block-info.c index 649392bee7ed..8d3a9a661f26 100644 --- a/tools/perf/util/block-info.c +++ b/tools/perf/util/block-info.c @@ -303,7 +303,7 @@ static int block_range_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, char buf[128]; char *start_line, *end_line; - symbol_conf.disable_add2line_warn = true; + symbol_conf.addr2line_disable_warn = true; start_line = map__srcline(he->ms.map, bi->sym->start + bi->start, he->ms.sym); diff --git a/tools/perf/util/block-range.c b/tools/perf/util/block-range.c index 15c42196c24c..7c559fcfd7e0 100644 --- a/tools/perf/util/block-range.c +++ b/tools/perf/util/block-range.c @@ -4,7 +4,7 @@ #include #include -struct { +static struct { struct rb_root root; u64 blocks; } block_ranges; diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c index 67e7786bb878..a27945c279ef 100644 --- a/tools/perf/util/bpf-event.c +++ b/tools/perf/util/bpf-event.c @@ -349,7 +349,7 @@ static struct bpf_metadata *bpf_metadata_alloc(__u32 nr_prog_tags, if (!metadata) return NULL; - metadata->prog_names = zalloc(nr_prog_tags * sizeof(char *)); + metadata->prog_names = calloc(nr_prog_tags, sizeof(char *)); if (!metadata->prog_names) { bpf_metadata_free(metadata); return NULL; diff --git a/tools/perf/util/bpf_counter.c b/tools/perf/util/bpf_counter.c index a5882b582205..34b6b0da18b7 100644 --- a/tools/perf/util/bpf_counter.c +++ b/tools/perf/util/bpf_counter.c @@ -294,7 +294,8 @@ static int bpf_program_profiler__read(struct evsel *evsel) struct perf_counts_values *counts; int reading_map_fd; __u32 key = 0; - int err, idx, bpf_cpu; + int err, bpf_cpu; + unsigned int idx; if (list_empty(&evsel->bpf_counter_list)) return -EAGAIN; @@ -318,11 +319,12 @@ static int bpf_program_profiler__read(struct evsel *evsel) } for (bpf_cpu = 0; bpf_cpu < num_cpu_bpf; bpf_cpu++) { - idx = perf_cpu_map__idx(evsel__cpus(evsel), - (struct perf_cpu){.cpu = bpf_cpu}); - if (idx == -1) + int i = perf_cpu_map__idx(evsel__cpus(evsel), + (struct perf_cpu){.cpu = bpf_cpu}); + + if (i == -1) continue; - counts = perf_counts(evsel->counts, idx, 0); + counts = perf_counts(evsel->counts, i, 0); counts->val += values[bpf_cpu].counter; counts->ena += values[bpf_cpu].enabled; counts->run += values[bpf_cpu].running; @@ -351,7 +353,7 @@ static int bpf_program_profiler__install_pe(struct evsel *evsel, int cpu_map_idx return 0; } -struct bpf_counter_ops bpf_program_profiler_ops = { +static struct bpf_counter_ops bpf_program_profiler_ops = { .load = bpf_program_profiler__load, .enable = bpf_program_profiler__enable, .disable = bpf_program_profiler__disable, @@ -668,7 +670,7 @@ static int bperf__install_pe(struct evsel *evsel, int cpu_map_idx, int fd) static int bperf_sync_counters(struct evsel *evsel) { struct perf_cpu cpu; - int idx; + unsigned int idx; perf_cpu_map__for_each_cpu(cpu, idx, evsel->core.cpus) bperf_trigger_reading(evsel->bperf_leader_prog_fd, cpu.cpu); @@ -695,13 +697,11 @@ static int bperf__read(struct evsel *evsel) struct bpf_perf_event_value values[num_cpu_bpf]; struct perf_counts_values *counts; int reading_map_fd, err = 0; - __u32 i; - int j; bperf_sync_counters(evsel); reading_map_fd = bpf_map__fd(skel->maps.accum_readings); - for (i = 0; i < filter_entry_cnt; i++) { + for (__u32 i = 0; i < filter_entry_cnt; i++) { struct perf_cpu entry; __u32 cpu; @@ -709,9 +709,10 @@ static int bperf__read(struct evsel *evsel) if (err) goto out; switch (evsel->follower_skel->bss->type) { - case BPERF_FILTER_GLOBAL: - assert(i == 0); + case BPERF_FILTER_GLOBAL: { + unsigned int j; + assert(i == 0); perf_cpu_map__for_each_cpu(entry, j, evsel__cpus(evsel)) { counts = perf_counts(evsel->counts, j, 0); counts->val = values[entry.cpu].counter; @@ -719,6 +720,7 @@ static int bperf__read(struct evsel *evsel) counts->run = values[entry.cpu].running; } break; + } case BPERF_FILTER_CPU: cpu = perf_cpu_map__cpu(evsel__cpus(evsel), i).cpu; assert(cpu >= 0); @@ -831,7 +833,7 @@ static int bperf__destroy(struct evsel *evsel) * the leader prog. */ -struct bpf_counter_ops bperf_ops = { +static struct bpf_counter_ops bperf_ops = { .load = bperf__load, .enable = bperf__enable, .disable = bperf__disable, diff --git a/tools/perf/util/bpf_counter_cgroup.c b/tools/perf/util/bpf_counter_cgroup.c index 17d7196c6589..519fee3dc3d0 100644 --- a/tools/perf/util/bpf_counter_cgroup.c +++ b/tools/perf/util/bpf_counter_cgroup.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -98,7 +97,7 @@ static int bperf_load_program(struct evlist *evlist) struct bpf_link *link; struct evsel *evsel; struct cgroup *cgrp, *leader_cgrp; - int i, j; + unsigned int i; struct perf_cpu cpu; int total_cpus = cpu__max_cpu().cpu; int map_fd, prog_fd, err; @@ -146,6 +145,8 @@ static int bperf_load_program(struct evlist *evlist) evlist__for_each_entry(evlist, evsel) { if (cgrp == NULL || evsel->cgrp == leader_cgrp) { + unsigned int j; + leader_cgrp = evsel->cgrp; evsel->cgrp = NULL; @@ -234,7 +235,7 @@ static int bperf_cgrp__install_pe(struct evsel *evsel __maybe_unused, static int bperf_cgrp__sync_counters(struct evlist *evlist) { struct perf_cpu cpu; - int idx; + unsigned int idx; int prog_fd = bpf_program__fd(skel->progs.trigger_read); perf_cpu_map__for_each_cpu(cpu, idx, evlist->core.all_cpus) @@ -286,7 +287,7 @@ static int bperf_cgrp__read(struct evsel *evsel) evlist__for_each_entry(evlist, evsel) { __u32 idx = evsel->core.idx; - int i; + unsigned int i; struct perf_cpu cpu; err = bpf_map_lookup_elem(reading_map_fd, &idx, values); diff --git a/tools/perf/util/bpf_kwork.c b/tools/perf/util/bpf_kwork.c index 5cff755c71fa..d3a2e548f2b6 100644 --- a/tools/perf/util/bpf_kwork.c +++ b/tools/perf/util/bpf_kwork.c @@ -148,7 +148,8 @@ static bool valid_kwork_class_type(enum kwork_class_type type) static int setup_filters(struct perf_kwork *kwork) { if (kwork->cpu_list != NULL) { - int idx, nr_cpus; + unsigned int idx; + int nr_cpus; struct perf_cpu_map *map; struct perf_cpu cpu; int fd = bpf_map__fd(skel->maps.perf_kwork_cpu_filter); diff --git a/tools/perf/util/bpf_kwork_top.c b/tools/perf/util/bpf_kwork_top.c index b6f187dd9136..189a29d2bc96 100644 --- a/tools/perf/util/bpf_kwork_top.c +++ b/tools/perf/util/bpf_kwork_top.c @@ -123,7 +123,8 @@ static bool valid_kwork_class_type(enum kwork_class_type type) static int setup_filters(struct perf_kwork *kwork) { if (kwork->cpu_list) { - int idx, nr_cpus, fd; + unsigned int idx; + int nr_cpus, fd; struct perf_cpu_map *map; struct perf_cpu cpu; diff --git a/tools/perf/util/bpf_map.c b/tools/perf/util/bpf_map.c deleted file mode 100644 index 442f91b4e8e1..000000000000 --- a/tools/perf/util/bpf_map.c +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) - -#include "util/bpf_map.h" -#include -#include -#include -#include -#include -#include -#include -#include - -static bool bpf_map__is_per_cpu(enum bpf_map_type type) -{ - return type == BPF_MAP_TYPE_PERCPU_HASH || - type == BPF_MAP_TYPE_PERCPU_ARRAY || - type == BPF_MAP_TYPE_LRU_PERCPU_HASH || - type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE; -} - -static void *bpf_map__alloc_value(const struct bpf_map *map) -{ - if (bpf_map__is_per_cpu(bpf_map__type(map))) - return malloc(round_up(bpf_map__value_size(map), 8) * - sysconf(_SC_NPROCESSORS_CONF)); - - return malloc(bpf_map__value_size(map)); -} - -int bpf_map__fprintf(struct bpf_map *map, FILE *fp) -{ - void *prev_key = NULL, *key, *value; - int fd = bpf_map__fd(map), err; - int printed = 0; - - if (fd < 0) - return fd; - - err = -ENOMEM; - key = malloc(bpf_map__key_size(map)); - if (key == NULL) - goto out; - - value = bpf_map__alloc_value(map); - if (value == NULL) - goto out_free_key; - - while ((err = bpf_map_get_next_key(fd, prev_key, key) == 0)) { - int intkey = *(int *)key; - - if (!bpf_map_lookup_elem(fd, key, value)) { - bool boolval = *(bool *)value; - if (boolval) - printed += fprintf(fp, "[%d] = %d,\n", intkey, boolval); - } else { - printed += fprintf(fp, "[%d] = ERROR,\n", intkey); - } - - prev_key = key; - } - - if (err == ENOENT) - err = printed; - - free(value); -out_free_key: - free(key); -out: - return err; -} diff --git a/tools/perf/util/bpf_map.h b/tools/perf/util/bpf_map.h deleted file mode 100644 index c2f7c13cba23..000000000000 --- a/tools/perf/util/bpf_map.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) -#ifndef __PERF_BPF_MAP_H -#define __PERF_BPF_MAP_H 1 - -#include -struct bpf_map; - -#ifdef HAVE_LIBBPF_SUPPORT - -int bpf_map__fprintf(struct bpf_map *map, FILE *fp); - -#else - -#include - -static inline int bpf_map__fprintf(struct bpf_map *map __maybe_unused, FILE *fp __maybe_unused) -{ - return 0; -} - -#endif // HAVE_LIBBPF_SUPPORT - -#endif // __PERF_BPF_MAP_H diff --git a/tools/perf/util/bpf_off_cpu.c b/tools/perf/util/bpf_off_cpu.c index 88e0660c4bff..a3b699a5322f 100644 --- a/tools/perf/util/bpf_off_cpu.c +++ b/tools/perf/util/bpf_off_cpu.c @@ -39,7 +39,7 @@ union off_cpu_data { u64 array[1024 / sizeof(u64)]; }; -u64 off_cpu_raw[MAX_STACKS + 5]; +static u64 off_cpu_raw[MAX_STACKS + 5]; static int off_cpu_config(struct evlist *evlist) { @@ -67,7 +67,7 @@ static void off_cpu_start(void *arg) struct evlist *evlist = arg; struct evsel *evsel; struct perf_cpu pcpu; - int i; + unsigned int i; /* update task filter for the given workload */ if (skel->rodata->has_task && skel->rodata->uses_tgid && diff --git a/tools/perf/util/bpf_skel/syscall_summary.bpf.c b/tools/perf/util/bpf_skel/syscall_summary.bpf.c index 1bcd066a5199..4172f3c9fc48 100644 --- a/tools/perf/util/bpf_skel/syscall_summary.bpf.c +++ b/tools/perf/util/bpf_skel/syscall_summary.bpf.c @@ -118,13 +118,11 @@ int sys_enter(u64 *ctx) return 0; } -SEC("tp_btf/sys_exit") -int sys_exit(u64 *ctx) +static int do_exit(long ret) { int tid; int key = 0; u64 cgroup = 0; - long ret = ctx[1]; /* return value of the syscall */ struct syscall_trace *st; s64 delta; @@ -150,4 +148,18 @@ int sys_exit(u64 *ctx) return 0; } +SEC("tp_btf/sys_exit") +int sys_exit(u64 *ctx) +{ + long ret = ctx[1]; /* return value of the syscall */ + + return do_exit(ret); +} + +SEC("tp_btf/sched_process_exit") +int process_exit(u64 *ctx) +{ + return do_exit(0); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/perf/util/bpf_trace_augment.c b/tools/perf/util/bpf_trace_augment.c index 56ed17534caa..9e706f0fa53d 100644 --- a/tools/perf/util/bpf_trace_augment.c +++ b/tools/perf/util/bpf_trace_augment.c @@ -60,7 +60,7 @@ int augmented_syscalls__create_bpf_output(struct evlist *evlist) void augmented_syscalls__setup_bpf_output(void) { struct perf_cpu cpu; - int i; + unsigned int i; if (bpf_output == NULL) return; diff --git a/tools/perf/util/branch.h b/tools/perf/util/branch.h index 7429530fa774..a1d4736497c4 100644 --- a/tools/perf/util/branch.h +++ b/tools/perf/util/branch.h @@ -66,6 +66,9 @@ static inline struct branch_entry *perf_sample__branch_entries(struct perf_sampl { u64 *entry = (u64 *)sample->branch_stack; + if (entry == NULL) + return NULL; + entry++; if (sample->no_hw_idx) return (struct branch_entry *)entry; diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 8ff0898799ee..f031cbbeeba8 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -30,6 +30,7 @@ #include "map.h" #include "callchain.h" #include "branch.h" +#include "record.h" #include "symbol.h" #include "thread.h" #include "util.h" @@ -170,7 +171,7 @@ static int get_stack_size(const char *str, unsigned long *_size) static int __parse_callchain_report_opt(const char *arg, bool allow_record_opt) { - char *tok; + char *tok, *arg_copy; char *endptr, *saveptr = NULL; bool minpcnt_set = false; bool record_opt_set = false; @@ -182,12 +183,17 @@ __parse_callchain_report_opt(const char *arg, bool allow_record_opt) if (!arg) return 0; - while ((tok = strtok_r((char *)arg, ",", &saveptr)) != NULL) { + arg_copy = strdup(arg); + if (!arg_copy) + return -ENOMEM; + + tok = strtok_r(arg_copy, ",", &saveptr); + while (tok) { if (!strncmp(tok, "none", strlen(tok))) { callchain_param.mode = CHAIN_NONE; callchain_param.enabled = false; symbol_conf.use_callchain = false; - return 0; + goto out; } if (!parse_callchain_mode(tok) || @@ -214,30 +220,35 @@ try_numbers: unsigned long size = 0; if (get_stack_size(tok, &size) < 0) - return -1; + goto err_out; callchain_param.dump_size = size; try_stack_size = false; } else if (!minpcnt_set) { /* try to get the min percent */ callchain_param.min_percent = strtod(tok, &endptr); if (tok == endptr) - return -1; + goto err_out; minpcnt_set = true; } else { /* try print limit at last */ callchain_param.print_limit = strtoul(tok, &endptr, 0); if (tok == endptr) - return -1; + goto err_out; } next: - arg = NULL; + tok = strtok_r(NULL, ",", &saveptr); } if (callchain_register_param(&callchain_param) < 0) { pr_err("Can't register callchain params\n"); - return -1; + goto err_out; } +out: + free(arg_copy); return 0; +err_out: + free(arg_copy); + return -1; } int parse_callchain_report_opt(const char *arg) @@ -257,14 +268,12 @@ int parse_callchain_record(const char *arg, struct callchain_param *param) int ret = -1; /* We need buffer that we know we can write to. */ - buf = malloc(strlen(arg) + 1); + buf = strdup(arg); if (!buf) return -ENOMEM; - strcpy(buf, arg); - - tok = strtok_r((char *)buf, ",", &saveptr); - name = tok ? : (char *)buf; + tok = strtok_r(buf, ",", &saveptr); + name = tok ? : buf; do { /* Framepointer style */ @@ -328,6 +337,44 @@ int parse_callchain_record(const char *arg, struct callchain_param *param) return ret; } +static void callchain_debug(const struct callchain_param *callchain) +{ + static const char *str[CALLCHAIN_MAX] = { "NONE", "FP", "DWARF", "LBR" }; + + pr_debug("callchain: type %s\n", str[callchain->record_mode]); + + if (callchain->record_mode == CALLCHAIN_DWARF) + pr_debug("callchain: stack dump size %d\n", + callchain->dump_size); +} + +int record_opts__parse_callchain(struct record_opts *record, + struct callchain_param *callchain, + const char *arg, bool unset) +{ + int ret; + + callchain->enabled = !unset; + + /* --no-call-graph */ + if (unset) { + callchain->record_mode = CALLCHAIN_NONE; + pr_debug("callchain: disabled\n"); + return 0; + } + + ret = parse_callchain_record_opt(arg, callchain); + if (!ret) { + /* Enable data address sampling for DWARF unwind. */ + if (callchain->record_mode == CALLCHAIN_DWARF && + !record->record_data_mmap_set) + record->record_data_mmap = true; + callchain_debug(callchain); + } + + return ret; +} + int perf_callchain_config(const char *var, const char *value) { char *endptr; @@ -1854,16 +1901,19 @@ int sample__merge_deferred_callchain(struct perf_sample *sample_orig, u64 nr_deferred = sample_callchain->callchain->nr; struct ip_callchain *callchain; + if (sample_orig->merged_callchain) { + /* Already merged. */ + return -EINVAL; + } + if (sample_orig->callchain->nr < 2) { sample_orig->deferred_callchain = false; return -EINVAL; } callchain = calloc(1 + nr_orig + nr_deferred, sizeof(u64)); - if (callchain == NULL) { - sample_orig->deferred_callchain = false; + if (callchain == NULL) return -ENOMEM; - } callchain->nr = nr_orig + nr_deferred; /* copy original including PERF_CONTEXT_USER_DEFERRED (but the cookie) */ @@ -1872,6 +1922,7 @@ int sample__merge_deferred_callchain(struct perf_sample *sample_orig, memcpy(&callchain->ips[nr_orig], sample_callchain->callchain->ips, nr_deferred * sizeof(u64)); + sample_orig->merged_callchain = true; sample_orig->callchain = callchain; return 0; } diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index df54ddb8c0cb..06d463ccc7a0 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -9,11 +9,13 @@ struct addr_location; struct evsel; +struct hist_entry; +struct hists; struct ip_callchain; struct map; struct perf_sample; +struct record_opts; struct thread; -struct hists; #define HELP_PAD "\t\t\t\t" @@ -237,14 +239,6 @@ struct callchain_cursor *get_tls_callchain_cursor(void); int callchain_cursor__copy(struct callchain_cursor *dst, struct callchain_cursor *src); -struct option; -struct hist_entry; - -int record_parse_callchain_opt(const struct option *opt, const char *arg, int unset); -int record_callchain_opt(const struct option *opt, const char *arg, int unset); - -struct record_opts; - int record_opts__parse_callchain(struct record_opts *record, struct callchain_param *callchain, const char *arg, bool unset); diff --git a/tools/perf/util/cgroup.c b/tools/perf/util/cgroup.c index 040eb75f0804..1b5664d1481f 100644 --- a/tools/perf/util/cgroup.c +++ b/tools/perf/util/cgroup.c @@ -417,7 +417,6 @@ static bool has_pattern_string(const char *str) int evlist__expand_cgroup(struct evlist *evlist, const char *str, bool open_cgroup) { struct evlist *orig_list, *tmp_list; - struct evsel *pos, *evsel, *leader; struct rblist orig_metric_events; struct cgroup *cgrp = NULL; struct cgroup_name *cn; @@ -452,6 +451,7 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str, bool open_cgro goto out_err; list_for_each_entry(cn, &cgroup_list, list) { + struct evsel *pos; char *name; if (!cn->used) @@ -467,21 +467,37 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str, bool open_cgro if (cgrp == NULL) continue; - leader = NULL; + /* copy the list and set to the new cgroup. */ evlist__for_each_entry(orig_list, pos) { - evsel = evsel__clone(/*dest=*/NULL, pos); + struct evsel *evsel = evsel__clone(/*dest=*/NULL, pos); + if (evsel == NULL) goto out_err; + /* stash the copy during the copying. */ + pos->priv = evsel; cgroup__put(evsel->cgrp); evsel->cgrp = cgroup__get(cgrp); - if (evsel__is_group_leader(pos)) - leader = evsel; - evsel__set_leader(evsel, leader); - evlist__add(tmp_list, evsel); } + /* update leader information using stashed pointer to copy. */ + evlist__for_each_entry(orig_list, pos) { + struct evsel *evsel = pos->priv; + + if (evsel__leader(pos)) + evsel__set_leader(evsel, evsel__leader(pos)->priv); + + if (pos->metric_leader) + evsel->metric_leader = pos->metric_leader->priv; + + if (pos->first_wildcard_match) + evsel->first_wildcard_match = pos->first_wildcard_match->priv; + } + /* the stashed copy is no longer used. */ + evlist__for_each_entry(orig_list, pos) + pos->priv = NULL; + /* cgroup__new() has a refcount, release it here */ cgroup__put(cgrp); nr_cgroups++; diff --git a/tools/perf/util/clockid.h b/tools/perf/util/clockid.h index 9b49b4711c76..33dbd8673c1c 100644 --- a/tools/perf/util/clockid.h +++ b/tools/perf/util/clockid.h @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0 */ - #ifndef __PERF_CLOCKID_H #define __PERF_CLOCKID_H +#include + struct option; int parse_clockid(const struct option *opt, const char *str, int unset); diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index 0452fbc6c085..087002fb1b9b 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -19,7 +19,6 @@ #include "util/hist.h" /* perf_hist_config */ #include "util/stat.h" /* perf_stat__set_big_num */ #include "util/evsel.h" /* evsel__hw_names, evsel__use_bpf_counters */ -#include "util/addr2line.h" /* addr2line_timeout_ms */ #include "srcline.h" #include "build-id.h" #include "debug.h" @@ -459,7 +458,10 @@ static int perf_default_core_config(const char *var, const char *value) proc_map_timeout = strtoul(value, NULL, 10); if (!strcmp(var, "core.addr2line-timeout")) - addr2line_timeout_ms = strtoul(value, NULL, 10); + symbol_conf.addr2line_timeout_ms = strtoul(value, NULL, 10); + + if (!strcmp(var, "core.addr2line-disable-warn")) + symbol_conf.addr2line_disable_warn = perf_config_bool(var, value); /* Add other config variables here. */ return 0; diff --git a/tools/perf/util/cpu-set-sched.h b/tools/perf/util/cpu-set-sched.h deleted file mode 100644 index 8cf4e40d322a..000000000000 --- a/tools/perf/util/cpu-set-sched.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1 -// Definitions taken from glibc for use with older systems, same licensing. -#ifndef _CPU_SET_SCHED_PERF_H -#define _CPU_SET_SCHED_PERF_H - -#include -#include - -#ifndef CPU_EQUAL -#ifndef __CPU_EQUAL_S -#if __GNUC_PREREQ (2, 91) -# define __CPU_EQUAL_S(setsize, cpusetp1, cpusetp2) \ - (__builtin_memcmp (cpusetp1, cpusetp2, setsize) == 0) -#else -# define __CPU_EQUAL_S(setsize, cpusetp1, cpusetp2) \ - (__extension__ \ - ({ const __cpu_mask *__arr1 = (cpusetp1)->__bits; \ - const __cpu_mask *__arr2 = (cpusetp2)->__bits; \ - size_t __imax = (setsize) / sizeof (__cpu_mask); \ - size_t __i; \ - for (__i = 0; __i < __imax; ++__i) \ - if (__arr1[__i] != __arr2[__i]) \ - break; \ - __i == __imax; })) -#endif -#endif // __CPU_EQUAL_S - -#define CPU_EQUAL(cpusetp1, cpusetp2) \ - __CPU_EQUAL_S (sizeof (cpu_set_t), cpusetp1, cpusetp2) -#endif // CPU_EQUAL - -#ifndef CPU_OR -#ifndef __CPU_OP_S -#define __CPU_OP_S(setsize, destset, srcset1, srcset2, op) \ - (__extension__ \ - ({ cpu_set_t *__dest = (destset); \ - const __cpu_mask *__arr1 = (srcset1)->__bits; \ - const __cpu_mask *__arr2 = (srcset2)->__bits; \ - size_t __imax = (setsize) / sizeof (__cpu_mask); \ - size_t __i; \ - for (__i = 0; __i < __imax; ++__i) \ - ((__cpu_mask *) __dest->__bits)[__i] = __arr1[__i] op __arr2[__i]; \ - __dest; })) -#endif // __CPU_OP_S - -#define CPU_OR(destset, srcset1, srcset2) \ - __CPU_OP_S (sizeof (cpu_set_t), destset, srcset1, srcset2, |) -#endif // CPU_OR - -#endif // _CPU_SET_SCHED_PERF_H diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index a80845038a5e..11922e1ded84 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c @@ -254,7 +254,7 @@ struct cpu_aggr_map *cpu_aggr_map__new(const struct perf_cpu_map *cpus, aggr_cpu_id_get_t get_id, void *data, bool needs_sort) { - int idx; + unsigned int idx; struct perf_cpu cpu; struct cpu_aggr_map *c = cpu_aggr_map__empty_new(perf_cpu_map__nr(cpus)); @@ -280,7 +280,7 @@ struct cpu_aggr_map *cpu_aggr_map__new(const struct perf_cpu_map *cpus, } } /* Trim. */ - if (c->nr != perf_cpu_map__nr(cpus)) { + if (c->nr != (int)perf_cpu_map__nr(cpus)) { struct cpu_aggr_map *trimmed_c = realloc(c, sizeof(struct cpu_aggr_map) + sizeof(struct aggr_cpu_id) * c->nr); @@ -631,9 +631,9 @@ size_t cpu_map__snprint(struct perf_cpu_map *map, char *buf, size_t size) #define COMMA first ? "" : "," - for (i = 0; i < perf_cpu_map__nr(map) + 1; i++) { + for (i = 0; i < (int)perf_cpu_map__nr(map) + 1; i++) { struct perf_cpu cpu = { .cpu = INT16_MAX }; - bool last = i == perf_cpu_map__nr(map); + bool last = i == (int)perf_cpu_map__nr(map); if (!last) cpu = perf_cpu_map__cpu(map, i); @@ -679,7 +679,7 @@ static char hex_char(unsigned char val) size_t cpu_map__snprint_mask(struct perf_cpu_map *map, char *buf, size_t size) { - int idx; + unsigned int idx; char *ptr = buf; unsigned char *bitmap; struct perf_cpu c, last_cpu = perf_cpu_map__max(map); diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c index 8bbeb2dc76fd..e0091804fe98 100644 --- a/tools/perf/util/cputopo.c +++ b/tools/perf/util/cputopo.c @@ -191,7 +191,7 @@ bool cpu_topology__core_wide(const struct cpu_topology *topology, const char *core_cpu_list = topology->core_cpus_list[i]; struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list); struct perf_cpu cpu; - int idx; + unsigned int idx; bool has_first, first = true; perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) { diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c index 212f17a3dc72..dee3020ceaa9 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c @@ -22,12 +22,15 @@ /* use raw logging */ #ifdef CS_DEBUG_RAW #define CS_LOG_RAW_FRAMES +#define CS_PKT_MON 1 #ifdef CS_RAW_PACKED #define CS_RAW_DEBUG_FLAGS (OCSD_DFRMTR_UNPACKED_RAW_OUT | \ OCSD_DFRMTR_PACKED_RAW_OUT) #else #define CS_RAW_DEBUG_FLAGS (OCSD_DFRMTR_UNPACKED_RAW_OUT) #endif +#else +#define CS_PKT_MON 0 #endif /* @@ -237,46 +240,24 @@ cs_etm_decoder__init_def_logger_printing(struct cs_etm_decoder_params *d_params, (void *)decoder, cs_etm_decoder__print_str_cb); if (ret != 0) - ret = -1; - - return 0; -} + return -1; #ifdef CS_LOG_RAW_FRAMES -static void -cs_etm_decoder__init_raw_frame_logging(struct cs_etm_decoder_params *d_params, - struct cs_etm_decoder *decoder) -{ - /* Only log these during a --dump operation */ - if (d_params->operation == CS_ETM_OPERATION_PRINT) { - /* set up a library default logger to process the - * raw frame printer we add later - */ - ocsd_def_errlog_init(OCSD_ERR_SEV_ERROR, 1); - - /* no stdout / err / file output */ - ocsd_def_errlog_config_output(C_API_MSGLOGOUT_FLG_NONE, NULL); - - /* set the string CB for the default logger, - * passes strings to perf print logger. - */ - ocsd_def_errlog_set_strprint_cb(decoder->dcd_tree, - (void *)decoder, - cs_etm_decoder__print_str_cb); - + /* + * Only log raw frames if --dump operation and hardware is actually + * generating formatted CoreSight trace frames + */ + if ((d_params->operation == CS_ETM_OPERATION_PRINT) && + (d_params->formatted == true)) { /* use the built in library printer for the raw frames */ - ocsd_dt_set_raw_frame_printer(decoder->dcd_tree, - CS_RAW_DEBUG_FLAGS); + ret = ocsd_dt_set_raw_frame_printer(decoder->dcd_tree, + CS_RAW_DEBUG_FLAGS); + if (ret != 0) + return -1; } -} -#else -static void -cs_etm_decoder__init_raw_frame_logging( - struct cs_etm_decoder_params *d_params __maybe_unused, - struct cs_etm_decoder *decoder __maybe_unused) -{ -} #endif + return 0; +} static ocsd_datapath_resp_t cs_etm_decoder__do_soft_timestamp(struct cs_etm_queue *etmq, @@ -686,7 +667,7 @@ cs_etm_decoder__create_etm_decoder(struct cs_etm_decoder_params *d_params, trace_config, &csid)) return -1; - if (ocsd_dt_set_pkt_protocol_printer(decoder->dcd_tree, csid, 0)) + if (ocsd_dt_set_pkt_protocol_printer(decoder->dcd_tree, csid, CS_PKT_MON)) return -1; return 0; @@ -738,9 +719,6 @@ cs_etm_decoder__new(int decoders, struct cs_etm_decoder_params *d_params, if (ret != 0) goto err_free_decoder; - /* init raw frame logging if required */ - cs_etm_decoder__init_raw_frame_logging(d_params, decoder); - for (i = 0; i < decoders; i++) { ret = cs_etm_decoder__create_etm_decoder(d_params, &t_params[i], diff --git a/tools/perf/util/data-convert-bt.c b/tools/perf/util/data-convert-bt.c index ba1c8e48d495..3b8f2df823a9 100644 --- a/tools/perf/util/data-convert-bt.c +++ b/tools/perf/util/data-convert-bt.c @@ -1181,6 +1181,10 @@ static int add_event(struct ctf_writer *cw, struct evsel *evsel) const char *name = evsel__name(evsel); int ret; + if (evsel->priv) { + pr_err("Error: attempt to add already added event %s\n", name); + return -1; + } pr("Adding event '%s' (type %d)\n", name, evsel->core.attr.type); event_class = bt_ctf_event_class_create(name); @@ -1223,13 +1227,28 @@ err: return -1; } -static int setup_events(struct ctf_writer *cw, struct perf_session *session) +enum setup_events_type { + SETUP_EVENTS_ALL, + SETUP_EVENTS_NOT_TRACEPOINT, + SETUP_EVENTS_TRACEPOINT_ONLY, +}; + +static int setup_events(struct ctf_writer *cw, struct perf_session *session, + enum setup_events_type type) { struct evlist *evlist = session->evlist; struct evsel *evsel; int ret; evlist__for_each_entry(evlist, evsel) { + bool is_tracepoint = evsel->core.attr.type == PERF_TYPE_TRACEPOINT; + + if (is_tracepoint && type == SETUP_EVENTS_NOT_TRACEPOINT) + continue; + + if (!is_tracepoint && type == SETUP_EVENTS_TRACEPOINT_ONLY) + continue; + ret = add_event(cw, evsel); if (ret) return ret; @@ -1360,7 +1379,7 @@ static int setup_streams(struct ctf_writer *cw, struct perf_session *session) */ ncpus = env->nr_cpus_avail ?: MAX_CPUS; - stream = zalloc(sizeof(*stream) * ncpus); + stream = calloc(ncpus, sizeof(*stream)); if (!stream) { pr_err("Failed to allocate streams.\n"); return -ENOMEM; @@ -1412,15 +1431,24 @@ static int process_feature_event(const struct perf_tool *tool, struct convert *c = container_of(tool, struct convert, tool); struct ctf_writer *cw = &c->writer; struct perf_record_header_feature *fe = &event->feat; + int ret = perf_event__process_feature(tool, session, event); - if (event->feat.feat_id < HEADER_LAST_FEATURE) { - int ret = perf_event__process_feature(session, event); - - if (ret) - return ret; - } + if (ret) + return ret; switch (fe->feat_id) { + case HEADER_EVENT_DESC: + /* + * In non-pipe mode (not here) the evsels combine the desc with + * the perf_event_attr when it is parsed. In pipe mode the + * perf_event_attr events appear first and then the event desc + * feature events that set the names appear after. Once we have + * the full evsel data we can generate the babeltrace + * events. For tracepoint events we still don't have the tracing + * data and so need to wait until the tracing data event to add + * those events to babeltrace. + */ + return setup_events(cw, session, SETUP_EVENTS_NOT_TRACEPOINT); case HEADER_HOSTNAME: if (session->header.env.hostname) { return bt_ctf_writer_add_environment_field(cw->writer, "host", @@ -1451,6 +1479,26 @@ static int process_feature_event(const struct perf_tool *tool, return 0; } +static int process_tracing_data(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + struct convert *c = container_of(tool, struct convert, tool); + struct ctf_writer *cw = &c->writer; + int ret; + + ret = perf_event__process_tracing_data(tool, session, event); + if (ret < 0) + return ret; + + /* + * Now the attr was set up by the attr event, the name by the feature + * event desc event and the tracepoint data set up above, the tracepoint + * babeltrace events can be added. + */ + return setup_events(cw, session, SETUP_EVENTS_TRACEPOINT_ONLY); +} + static int ctf_writer__setup_clock(struct ctf_writer *cw, struct perf_session *session, bool tod) @@ -1680,9 +1728,10 @@ int bt_convert__perf2ctf(const char *input, const char *path, c.tool.exit = perf_event__process_exit; c.tool.fork = perf_event__process_fork; c.tool.lost = perf_event__process_lost; - c.tool.tracing_data = perf_event__process_tracing_data; + c.tool.tracing_data = process_tracing_data; c.tool.build_id = perf_event__process_build_id; c.tool.namespaces = perf_event__process_namespaces; + c.tool.finished_round = perf_event__process_finished_round; c.tool.attr = perf_event__process_attr; c.tool.feature = process_feature_event; c.tool.ordering_requires_timestamps = true; @@ -1727,8 +1776,11 @@ int bt_convert__perf2ctf(const char *input, const char *path, if (ctf_writer__setup_env(cw, session)) goto free_writer; - /* CTF events setup */ - if (setup_events(cw, session)) + /* + * CTF events setup. Note, in pipe mode no events exist yet (they come + * in via header feature events) and so this does nothing. + */ + if (setup_events(cw, session, SETUP_EVENTS_ALL)) goto free_writer; if (opts->all && setup_non_sample_events(cw, session)) diff --git a/tools/perf/util/data-convert-json.c b/tools/perf/util/data-convert-json.c index 6a626322476a..d526c91312ed 100644 --- a/tools/perf/util/data-convert-json.c +++ b/tools/perf/util/data-convert-json.c @@ -326,17 +326,7 @@ static void output_headers(struct perf_session *session, struct convert_json *c) output_json_format(out, false, 2, "]"); } -static int process_feature_event(const struct perf_tool *tool __maybe_unused, - struct perf_session *session, - union perf_event *event) -{ - if (event->feat.feat_id < HEADER_LAST_FEATURE) - return perf_event__process_feature(session, event); - - return 0; -} - -int bt_convert__perf2json(const char *input_name, const char *output_name, +int bt_convert__perf2json(const char *_input_name, const char *output_name, struct perf_data_convert_opts *opts __maybe_unused) { struct perf_session *session; @@ -352,7 +342,7 @@ int bt_convert__perf2json(const char *input_name, const char *output_name, }; struct perf_data data = { .mode = PERF_DATA_MODE_READ, - .path = input_name, + .path = _input_name, .force = opts->force, }; @@ -375,7 +365,7 @@ int bt_convert__perf2json(const char *input_name, const char *output_name, c.tool.auxtrace = perf_event__process_auxtrace; c.tool.event_update = perf_event__process_event_update; c.tool.attr = perf_event__process_attr; - c.tool.feature = process_feature_event; + c.tool.feature = perf_event__process_feature; c.tool.ordering_requires_timestamps = true; if (opts->all) { diff --git a/tools/perf/util/data.c b/tools/perf/util/data.c index 90df41da1a32..94dc534a7386 100644 --- a/tools/perf/util/data.c +++ b/tools/perf/util/data.c @@ -20,18 +20,33 @@ #include "rlimit.h" #include +static void perf_data_file__close(struct perf_data_file *file) +{ + if (file->use_stdio) { + if (file->fptr) { + fclose(file->fptr); + file->fptr = NULL; + } + } else { + close(file->fd); + file->fd = -1; + } + zfree(&file->path); +} + static void close_dir(struct perf_data_file *files, int nr) { - while (--nr >= 0) { - close(files[nr].fd); - zfree(&files[nr].path); - } + while (--nr >= 0) + perf_data_file__close(&files[nr]); + free(files); } void perf_data__close_dir(struct perf_data *data) { close_dir(data->dir.files, data->dir.nr); + data->dir.files = NULL; + data->dir.nr = 0; } int perf_data__create_dir(struct perf_data *data, int nr) @@ -43,7 +58,7 @@ int perf_data__create_dir(struct perf_data *data, int nr) if (WARN_ON(!data->is_dir)) return -EINVAL; - files = zalloc(nr * sizeof(*files)); + files = calloc(nr, sizeof(*files)); if (!files) return -ENOMEM; @@ -132,16 +147,21 @@ int perf_data__open_dir(struct perf_data *data) files = file; file = &files[nr++]; - file->path = strdup(path); + *file = (struct perf_data_file){ + .path = strdup(path), + .fd = -1, + .size = st.st_size, + .use_stdio = false, + }; if (!file->path) goto out_err; ret = open(file->path, O_RDONLY); - if (ret < 0) + if (ret < 0) { + ret = -errno; goto out_err; - + } file->fd = ret; - file->size = st.st_size; } closedir(dir); @@ -174,7 +194,7 @@ static bool check_pipe(struct perf_data *data) } if (is_pipe) { - if (data->use_stdio) { + if (data->file.use_stdio) { const char *mode; mode = perf_data__is_read(data) ? "r" : "w"; @@ -182,7 +202,7 @@ static bool check_pipe(struct perf_data *data) if (data->file.fptr == NULL) { data->file.fd = fd; - data->use_stdio = false; + data->file.use_stdio = false; } /* @@ -344,7 +364,7 @@ int perf_data__open(struct perf_data *data) return 0; /* currently it allows stdio for pipe only */ - data->use_stdio = false; + data->file.use_stdio = false; if (!data->path) data->path = "perf.data"; @@ -364,41 +384,57 @@ void perf_data__close(struct perf_data *data) if (perf_data__is_dir(data)) perf_data__close_dir(data); - zfree(&data->file.path); + perf_data_file__close(&data->file); +} - if (data->use_stdio) - fclose(data->file.fptr); - else - close(data->file.fd); +static ssize_t perf_data_file__read(struct perf_data_file *file, void *buf, size_t size) +{ + if (file->use_stdio) { + if (fread(buf, size, 1, file->fptr) == 1) + return size; + return feof(file->fptr) ? 0 : -1; + } + return readn(file->fd, buf, size); } ssize_t perf_data__read(struct perf_data *data, void *buf, size_t size) { - if (data->use_stdio) { - if (fread(buf, size, 1, data->file.fptr) == 1) - return size; - return feof(data->file.fptr) ? 0 : -1; - } - return readn(data->file.fd, buf, size); + return perf_data_file__read(&data->file, buf, size); } ssize_t perf_data_file__write(struct perf_data_file *file, void *buf, size_t size) { + if (file->use_stdio) { + if (fwrite(buf, size, /*nmemb=*/1, file->fptr) == 1) + return size; + return -1; + } return writen(file->fd, buf, size); } ssize_t perf_data__write(struct perf_data *data, void *buf, size_t size) { - if (data->use_stdio) { - if (fwrite(buf, size, 1, data->file.fptr) == 1) - return size; - return -1; - } return perf_data_file__write(&data->file, buf, size); } +off_t perf_data_file__seek(struct perf_data_file *file, off_t offset, int whence) +{ + if (file->use_stdio) { + off_t res = fseeko(file->fptr, offset, whence); + + return res < 0 ? -1 : ftello(file->fptr); + } + return lseek(file->fd, offset, whence); +} + +off_t perf_data__seek(struct perf_data *data, off_t offset, int whence) +{ + /* Note, a pipe fd will fail with -1 with errno of ESPIPE. */ + return perf_data_file__seek(&data->file, offset, whence); +} + int perf_data__switch(struct perf_data *data, const char *postfix, size_t pos, bool at_exit, @@ -420,19 +456,18 @@ int perf_data__switch(struct perf_data *data, pr_warning("Failed to rename %s to %s\n", data->path, *new_filepath); if (!at_exit) { - close(data->file.fd); + perf_data_file__close(&data->file); ret = perf_data__open(data); if (ret < 0) goto out; - if (lseek(data->file.fd, pos, SEEK_SET) == (off_t)-1) { + if (perf_data__seek(data, pos, SEEK_SET) == (off_t)-1) { ret = -errno; - pr_debug("Failed to lseek to %zu: %m\n", - pos); + pr_debug("Failed to seek to %zu: %m", pos); goto out; } } - ret = data->file.fd; + ret = perf_data__fd(data); out: return ret; } diff --git a/tools/perf/util/data.h b/tools/perf/util/data.h index 1438e32e0451..8299fb5fa7da 100644 --- a/tools/perf/util/data.h +++ b/tools/perf/util/data.h @@ -17,32 +17,70 @@ enum perf_dir_version { PERF_DIR_VERSION = 1, }; +/** + * struct perf_data_file: A wrapper around a file used for perf.data reading or writing. Generally + * part of struct perf_data. + */ struct perf_data_file { + /** + * @path: Path of file. Generally a copy of perf_data.path but for a + * directory it is the file within the directory. + */ char *path; union { + /** @fd: File descriptor for read/writes. Valid if use_stdio is false. */ int fd; + /** + * @fptr: Stdio FILE. Valid if use_stdio is true, currently just + * pipes in perf inject. + */ FILE *fptr; }; + /** @size: Size of file when opened. */ unsigned long size; + /** @use_stdio: Use buffered stdio operations. */ + bool use_stdio; }; +/** + * struct perf_data: A wrapper around a file used for perf.data reading or writing. + */ struct perf_data { + /** @path: Path to open and of the file. NULL implies 'perf.data' will be used. */ const char *path; + /** @file: Underlying file to be used. */ struct perf_data_file file; + /** @is_pipe: Underlying file is a pipe. */ bool is_pipe; + /** @is_dir: Underlying file is a directory. */ bool is_dir; + /** @force: Ignore opening a file creating created by a different user. */ bool force; - bool use_stdio; + /** @in_place_update: A file opened for reading but will be written to. */ bool in_place_update; + /** @mode: Read or write mode. */ enum perf_data_mode mode; struct { + /** @version: perf_dir_version. */ u64 version; + /** @files: perf data files for the directory. */ struct perf_data_file *files; + /** @nr: Number of perf data files for the directory. */ int nr; } dir; }; +static inline int perf_data_file__fd(struct perf_data_file *file) +{ + return file->use_stdio ? fileno(file->fptr) : file->fd; +} + +ssize_t perf_data_file__write(struct perf_data_file *file, + void *buf, size_t size); +off_t perf_data_file__seek(struct perf_data_file *file, off_t offset, int whence); + + static inline bool perf_data__is_read(struct perf_data *data) { return data->mode == PERF_DATA_MODE_READ; @@ -70,10 +108,7 @@ static inline bool perf_data__is_single_file(struct perf_data *data) static inline int perf_data__fd(struct perf_data *data) { - if (data->use_stdio) - return fileno(data->file.fptr); - - return data->file.fd; + return perf_data_file__fd(&data->file); } int perf_data__open(struct perf_data *data); @@ -81,8 +116,7 @@ void perf_data__close(struct perf_data *data); ssize_t perf_data__read(struct perf_data *data, void *buf, size_t size); ssize_t perf_data__write(struct perf_data *data, void *buf, size_t size); -ssize_t perf_data_file__write(struct perf_data_file *file, - void *buf, size_t size); +off_t perf_data__seek(struct perf_data *data, off_t offset, int whence); /* * If at_exit is set, only rename current perf.data to * perf.data., continue write on original data. @@ -99,8 +133,10 @@ int perf_data__open_dir(struct perf_data *data); void perf_data__close_dir(struct perf_data *data); unsigned long perf_data__size(struct perf_data *data); int perf_data__make_kcore_dir(struct perf_data *data, char *buf, size_t buf_sz); -bool has_kcore_dir(const char *path); char *perf_data__kallsyms_name(struct perf_data *data); char *perf_data__guest_kallsyms_name(struct perf_data *data, pid_t machine_pid); + +bool has_kcore_dir(const char *path); bool is_perf_data(const char *path); + #endif /* __PERF_DATA_H */ diff --git a/tools/perf/util/db-export.c b/tools/perf/util/db-export.c index ae9a9065aab7..cc2bb1af4243 100644 --- a/tools/perf/util/db-export.c +++ b/tools/perf/util/db-export.c @@ -19,7 +19,6 @@ #include "callchain.h" #include "call-path.h" #include "db-export.h" -#include int db_export__init(struct db_export *dbe) { diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 1dfa4d0eec4d..6b5ffe81f141 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -48,7 +48,7 @@ int debug_ordered_events; static int redirect_to_stderr; int debug_data_convert; static FILE *_debug_file; -bool debug_display_time; +static bool debug_display_time; int debug_type_profile; FILE *debug_file(void) diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c index 4a559b3e8cdc..0e35c13abd04 100644 --- a/tools/perf/util/debuginfo.c +++ b/tools/perf/util/debuginfo.c @@ -88,18 +88,17 @@ static struct debuginfo *__debuginfo__new(const char *path) return dbg; } -enum dso_binary_type distro_dwarf_types[] = { - DSO_BINARY_TYPE__FEDORA_DEBUGINFO, - DSO_BINARY_TYPE__UBUNTU_DEBUGINFO, - DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, - DSO_BINARY_TYPE__BUILDID_DEBUGINFO, - DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO, - DSO_BINARY_TYPE__NOT_FOUND, -}; - struct debuginfo *debuginfo__new(const char *path) { - enum dso_binary_type *type; + static const enum dso_binary_type distro_dwarf_types[] = { + DSO_BINARY_TYPE__FEDORA_DEBUGINFO, + DSO_BINARY_TYPE__UBUNTU_DEBUGINFO, + DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, + DSO_BINARY_TYPE__BUILDID_DEBUGINFO, + DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO, + DSO_BINARY_TYPE__NOT_FOUND, + }; + const enum dso_binary_type *type; char buf[PATH_MAX], nil = '\0'; struct dso *dso; struct debuginfo *dinfo = NULL; diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c index 9e0420e14be1..59ba88e1f744 100644 --- a/tools/perf/util/disasm.c +++ b/tools/perf/util/disasm.c @@ -13,6 +13,7 @@ #include #include +#include #include #include "annotate.h" @@ -451,7 +452,7 @@ int jump__scnprintf(const struct ins *ins, char *bf, size_t size, ops->target.offset); } -static void jump__delete(struct ins_operands *ops __maybe_unused) +void jump__delete(struct ins_operands *ops __maybe_unused) { /* * The ops->jump.raw_comment and ops->jump.raw_func_start belong to the @@ -908,13 +909,14 @@ static void annotation_line__init(struct annotation_line *al, al->offset = args->offset; al->line = strdup(args->line); al->line_nr = args->line_nr; - al->fileloc = args->fileloc; + al->fileloc = args->fileloc ? strdup(args->fileloc) : NULL; al->data_nr = nr; } static void annotation_line__exit(struct annotation_line *al) { zfree_srcline(&al->path); + zfree(&al->fileloc); zfree(&al->line); zfree(&al->cycles); zfree(&al->br_cntr); @@ -950,7 +952,7 @@ struct disasm_line *disasm_line__new(struct annotate_args *args) annotation_line__init(&dl->al, args, nr); if (dl->al.line == NULL) - goto out_delete; + goto out_free_line; if (args->offset != -1) { if (arch__is_powerpc(args->arch)) { @@ -965,8 +967,7 @@ struct disasm_line *disasm_line__new(struct annotate_args *args) return dl; out_free_line: - zfree(&dl->al.line); -out_delete: + annotation_line__exit(&dl->al); free(dl); return NULL; } diff --git a/tools/perf/util/disasm.h b/tools/perf/util/disasm.h index a6e478caf61a..25756e3f47e4 100644 --- a/tools/perf/util/disasm.h +++ b/tools/perf/util/disasm.h @@ -161,6 +161,8 @@ int jump__scnprintf(const struct ins *ins, char *bf, size_t size, int mov__scnprintf(const struct ins *ins, char *bf, size_t size, struct ins_operands *ops, int max_ins_name); +void jump__delete(struct ins_operands *ops); + int symbol__disassemble(struct symbol *sym, struct annotate_args *args); char *expand_tabs(char *line, char **storage, size_t *storage_len); diff --git a/tools/perf/util/drm_pmu.c b/tools/perf/util/drm_pmu.c index b48a375e4584..b8badae7015c 100644 --- a/tools/perf/util/drm_pmu.c +++ b/tools/perf/util/drm_pmu.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -129,11 +130,6 @@ static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t li } -static bool starts_with(const char *str, const char *prefix) -{ - return !strncmp(prefix, str, strlen(prefix)); -} - static int add_event(struct drm_pmu_event **events, int *num_events, const char *line, enum drm_pmu_unit unit, const char *desc) { @@ -174,7 +170,7 @@ static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name) } while (io__getline(&io, &line, &line_len) > 0) { - if (starts_with(line, "drm-driver:")) { + if (strstarts(line, "drm-driver:")) { drm = add_drm_pmu(pmus, line, line_len); if (!drm) break; @@ -184,59 +180,59 @@ static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name) * Note the string matching below is alphabetical, with more * specific matches appearing before less specific. */ - if (starts_with(line, "drm-active-")) { + if (strstarts(line, "drm-active-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, "Total memory active in one or more engines"); continue; } - if (starts_with(line, "drm-cycles-")) { + if (strstarts(line, "drm-cycles-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_CYCLES, "Busy cycles"); continue; } - if (starts_with(line, "drm-engine-capacity-")) { + if (strstarts(line, "drm-engine-capacity-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_CAPACITY, "Engine capacity"); continue; } - if (starts_with(line, "drm-engine-")) { + if (strstarts(line, "drm-engine-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_NS, "Utilization in ns"); continue; } - if (starts_with(line, "drm-maxfreq-")) { + if (strstarts(line, "drm-maxfreq-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_HZ, "Maximum frequency"); continue; } - if (starts_with(line, "drm-purgeable-")) { + if (strstarts(line, "drm-purgeable-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, "Size of resident and purgeable memory buffers"); continue; } - if (starts_with(line, "drm-resident-")) { + if (strstarts(line, "drm-resident-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, "Size of resident memory buffers"); continue; } - if (starts_with(line, "drm-shared-")) { + if (strstarts(line, "drm-shared-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, "Size of shared memory buffers"); continue; } - if (starts_with(line, "drm-total-cycles-")) { + if (strstarts(line, "drm-total-cycles-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, "Total busy cycles"); continue; } - if (starts_with(line, "drm-total-")) { + if (strstarts(line, "drm-total-")) { add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, "Size of shared and private memory"); continue; } - if (verbose > 1 && starts_with(line, "drm-") && - !starts_with(line, "drm-client-id:") && - !starts_with(line, "drm-pdev:")) + if (verbose > 1 && strstarts(line, "drm-") && + !strstarts(line, "drm-client-id:") && + !strstarts(line, "drm-pdev:")) pr_debug("Unhandled DRM PMU fdinfo line match '%s'\n", line); } if (drm) { @@ -261,7 +257,7 @@ bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name) { struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); - if (!starts_with(name, "drm-")) + if (!strstarts(name, "drm-")) return false; for (int i = 0; i < drm->num_events; i++) { diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c index 0a7645c7fae7..e927e707abac 100644 --- a/tools/perf/util/dsos.c +++ b/tools/perf/util/dsos.c @@ -6,7 +6,6 @@ #include "vdso.h" #include "namespaces.h" #include -#include #include #include #include // filename__read_build_id @@ -196,6 +195,9 @@ static struct dso *__dsos__find_by_longname_id(struct dsos *dsos, int __dsos__add(struct dsos *dsos, struct dso *dso) { + if (!dso) + return -EINVAL; + if (dsos->cnt == dsos->allocated) { unsigned int to_allocate = 2; struct dso **temp; @@ -294,34 +296,21 @@ struct dso *dsos__find(struct dsos *dsos, const char *name, bool cmp_short) static void dso__set_basename(struct dso *dso) { - char *base, *lname; + bool allocated = false; + const char *base; int tid; if (perf_pid_map_tid(dso__long_name(dso), &tid)) { - if (asprintf(&base, "[JIT] tid %d", tid) < 0) + char *jitname; + + if (asprintf(&jitname, "[JIT] tid %d", tid) < 0) return; + allocated = true; + base = jitname; } else { - /* - * basename() may modify path buffer, so we must pass - * a copy. - */ - lname = strdup(dso__long_name(dso)); - if (!lname) - return; - - /* - * basename() may return a pointer to internal - * storage which is reused in subsequent calls - * so copy the result. - */ - base = strdup(basename(lname)); - - free(lname); - - if (!base) - return; + base = perf_basename(dso__long_name(dso)); } - dso__set_short_name(dso, base, true); + dso__set_short_name(dso, base, allocated); } static struct dso *__dsos__addnew_id(struct dsos *dsos, const char *name, const struct dso_id *id) diff --git a/tools/perf/util/dump-insn.c b/tools/perf/util/dump-insn.c deleted file mode 100644 index c1cc0ade48d0..000000000000 --- a/tools/perf/util/dump-insn.c +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include -#include "dump-insn.h" - -/* Fallback code */ - -__weak -const char *dump_insn(struct perf_insn *x __maybe_unused, - u64 ip __maybe_unused, u8 *inbuf __maybe_unused, - int inlen __maybe_unused, int *lenp) -{ - if (lenp) - *lenp = 0; - return "?"; -} - -__weak -int arch_is_uncond_branch(const unsigned char *buf __maybe_unused, - size_t len __maybe_unused, - int x86_64 __maybe_unused) -{ - return 0; -} diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index 9267af204c7d..92db2fccc788 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -303,6 +303,33 @@ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) return vr_die; } +/** + * die_get_pointer_type - Get a pointer/array type die + * @type_die: a DIE of a type + * @die_mem: where to store a type DIE + * + * Get a pointer/array type DIE from @type_die. If the type is a typedef or + * qualifier (const, volatile, etc.), follow the chain to find the underlying + * pointer type. + */ +Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem) +{ + int tag; + + do { + tag = dwarf_tag(type_die); + if (tag == DW_TAG_pointer_type || tag == DW_TAG_array_type) + return type_die; + if (tag != DW_TAG_typedef && tag != DW_TAG_const_type && + tag != DW_TAG_restrict_type && tag != DW_TAG_volatile_type && + tag != DW_TAG_shared_type) + return NULL; + type_die = die_get_type(type_die, die_mem); + } while (type_die); + + return NULL; +} + /* Get attribute and translate it as a udata */ static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name, Dwarf_Word *result) @@ -1378,6 +1405,8 @@ struct find_var_data { Dwarf_Addr addr; /* Target register */ unsigned reg; + /* Access data type */ + Dwarf_Die type; /* Access offset, set for global data */ int offset; /* True if the current register is the frame base */ @@ -1390,29 +1419,31 @@ struct find_var_data { static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data, s64 addr_offset, s64 addr_type, bool is_pointer) { - Dwarf_Die type_die; Dwarf_Word size; + Dwarf_Die ptr_die; + Dwarf_Die *ptr_type; s64 offset = addr_offset - addr_type; + if (offset < 0) + return false; + + if (__die_get_real_type(die_mem, &data->type) == NULL) + return false; + + ptr_type = die_get_pointer_type(&data->type, &ptr_die); + if (is_pointer && ptr_type) { + /* Get the target type of the pointer */ + if (__die_get_real_type(ptr_type, &data->type) == NULL) + return false; + } + if (offset == 0) { /* Update offset relative to the start of the variable */ data->offset = 0; return true; } - if (offset < 0) - return false; - - if (die_get_real_type(die_mem, &type_die) == NULL) - return false; - - if (is_pointer && dwarf_tag(&type_die) == DW_TAG_pointer_type) { - /* Get the target type of the pointer */ - if (die_get_real_type(&type_die, &type_die) == NULL) - return false; - } - - if (dwarf_aggregate_size(&type_die, &size) < 0) + if (dwarf_aggregate_size(&data->type, &size) < 0) return false; if ((u64)offset >= size) @@ -1529,7 +1560,7 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg) * when the variable is in the stack. */ Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg, - int *poffset, bool is_fbreg, + Dwarf_Die *type_die, int *poffset, bool is_fbreg, Dwarf_Die *die_mem) { struct find_var_data data = { @@ -1541,8 +1572,10 @@ Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg, Dwarf_Die *result; result = die_find_child(sc_die, __die_find_var_reg_cb, &data, die_mem); - if (result) + if (result) { *poffset = data.offset; + *type_die = data.type; + } return result; } @@ -1586,7 +1619,8 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg) * This is usually for global variables. */ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr, - Dwarf_Die *die_mem, int *offset) + Dwarf_Die *die_mem, Dwarf_Die *type_die, + int *offset) { struct find_var_data data = { .addr = addr, @@ -1594,8 +1628,10 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr, Dwarf_Die *result; result = die_find_child(sc_die, __die_find_var_addr_cb, &data, die_mem); - if (result) + if (result) { *offset = data.offset; + *type_die = data.type; + } return result; } @@ -1605,10 +1641,11 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg) Dwarf_Die type_die; int tag = dwarf_tag(die_mem); Dwarf_Attribute attr; - Dwarf_Addr base, start, end; + Dwarf_Addr base, start, end = 0; Dwarf_Op *ops; size_t nops; struct die_var_type *vt; + ptrdiff_t off; if (tag != DW_TAG_variable && tag != DW_TAG_formal_parameter) return DIE_FIND_CB_SIBLING; @@ -1616,39 +1653,40 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg) if (dwarf_attr(die_mem, DW_AT_location, &attr) == NULL) return DIE_FIND_CB_SIBLING; - /* - * Only collect the first location as it can reconstruct the - * remaining state by following the instructions. - * start = 0 means it covers the whole range. - */ - if (dwarf_getlocations(&attr, 0, &base, &start, &end, &ops, &nops) <= 0) - return DIE_FIND_CB_SIBLING; - - if (!check_allowed_ops(ops, nops)) - return DIE_FIND_CB_SIBLING; - if (__die_get_real_type(die_mem, &type_die) == NULL) return DIE_FIND_CB_SIBLING; - vt = malloc(sizeof(*vt)); - if (vt == NULL) - return DIE_FIND_CB_END; + /* + * Collect all location entries as variables may have different + * locations across different address ranges. + */ + off = 0; + while ((off = dwarf_getlocations(&attr, off, &base, &start, &end, &ops, &nops)) > 0) { + if (!check_allowed_ops(ops, nops)) + continue; - /* Usually a register holds the value of a variable */ - vt->is_reg_var_addr = false; + vt = malloc(sizeof(*vt)); + if (vt == NULL) + return DIE_FIND_CB_END; - if (((ops->atom >= DW_OP_breg0 && ops->atom <= DW_OP_breg31) || - ops->atom == DW_OP_bregx || ops->atom == DW_OP_fbreg) && - !is_breg_access_indirect(ops, nops)) - /* The register contains an address of the variable. */ - vt->is_reg_var_addr = true; + /* Usually a register holds the value of a variable */ + vt->is_reg_var_addr = false; - vt->die_off = dwarf_dieoffset(&type_die); - vt->addr = start; - vt->reg = reg_from_dwarf_op(ops); - vt->offset = offset_from_dwarf_op(ops); - vt->next = *var_types; - *var_types = vt; + if (((ops->atom >= DW_OP_breg0 && ops->atom <= DW_OP_breg31) || + ops->atom == DW_OP_bregx || ops->atom == DW_OP_fbreg) && + !is_breg_access_indirect(ops, nops)) + /* The register contains an address of the variable. */ + vt->is_reg_var_addr = true; + + vt->die_off = dwarf_dieoffset(&type_die); + vt->addr = start; + vt->end = end; + vt->has_range = (end != 0 || start != 0); + vt->reg = reg_from_dwarf_op(ops); + vt->offset = offset_from_dwarf_op(ops); + vt->next = *var_types; + *var_types = vt; + } return DIE_FIND_CB_SIBLING; } @@ -1707,6 +1745,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg) vt->die_off = dwarf_dieoffset(&type_die); vt->addr = ops->number; + vt->end = 0; + vt->has_range = false; vt->reg = -1; vt->offset = 0; vt->next = *var_types; @@ -2091,13 +2131,28 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset, tag = dwarf_tag(&mb_type); - if (tag == DW_TAG_structure_type || tag == DW_TAG_union_type) { + if (tag == DW_TAG_structure_type || tag == DW_TAG_union_type || + tag == DW_TAG_array_type) { Dwarf_Word loc; /* Update offset for the start of the member struct */ if (die_get_data_member_location(member, &loc) == 0) offset -= loc; } + + /* Handle array types: resolve to the element type by one level */ + if (tag == DW_TAG_array_type) { + Dwarf_Word size; + + if (die_get_real_type(&mb_type, &mb_type) == NULL) + return NULL; + + if (dwarf_aggregate_size(&mb_type, &size) < 0) + return NULL; + + offset = offset % size; + tag = dwarf_tag(&mb_type); + } } *die_mem = mb_type; return die_mem; diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h index cd481ec9c5a1..a79968a2e573 100644 --- a/tools/perf/util/dwarf-aux.h +++ b/tools/perf/util/dwarf-aux.h @@ -60,6 +60,8 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem); Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem); /* Get a type die, but skip qualifiers and typedef */ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem); +/* Get a pointer/array type, following typedefs/qualifiers */ +Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem); /* Check whether the DIE is signed or not */ bool die_is_signed_type(Dwarf_Die *tp_die); @@ -146,10 +148,12 @@ struct die_var_type { struct die_var_type *next; u64 die_off; u64 addr; + u64 end; /* end address of location range */ int reg; int offset; /* Whether the register holds a address to the type */ bool is_reg_var_addr; + bool has_range; /* whether end is valid */ }; /* Return type info of a member at offset */ @@ -163,12 +167,13 @@ int die_get_var_range(Dwarf_Die *sp_die, Dwarf_Die *vr_die, struct strbuf *buf); /* Find a variable saved in the 'reg' at given address */ Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg, - int *poffset, bool is_fbreg, + Dwarf_Die *type_die, int *poffset, bool is_fbreg, Dwarf_Die *die_mem); /* Find a (global) variable located in the 'addr' */ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr, - Dwarf_Die *die_mem, int *offset); + Dwarf_Die *die_mem, Dwarf_Die *type_die, + int *offset); /* Save all variables and parameters in this scope */ void die_collect_vars(Dwarf_Die *sc_die, struct die_var_type **var_types); diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c index 93d475a80f14..1e54e2c86360 100644 --- a/tools/perf/util/env.c +++ b/tools/perf/util/env.c @@ -718,7 +718,7 @@ int perf_env__numa_node(struct perf_env *env, struct perf_cpu cpu) for (i = 0; i < env->nr_numa_nodes; i++) { struct perf_cpu tmp; - int j; + unsigned int j; nn = &env->numa_nodes[i]; perf_cpu_map__for_each_cpu(tmp, j, nn->map) diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h index a4501cbca375..c7052ac1f856 100644 --- a/tools/perf/util/env.h +++ b/tools/perf/util/env.h @@ -112,6 +112,7 @@ struct perf_env { struct cpu_cache_level *caches; struct cpu_domain_map **cpu_domain; int caches_cnt; + unsigned int cln_size; u32 comp_ratio; u32 comp_ver; u32 comp_type; diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index bc045fddf7d5..66f4843bb235 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -12,7 +12,6 @@ #include #include /* To get things like MAP_HUGETLB even on older libc headers */ #include -#include #include "cpumap.h" #include "dso.h" #include "event.h" diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index 591bdf0b3e2a..ee971d15b3c6 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -13,6 +13,7 @@ #include "util/mmap.h" #include "thread_map.h" #include "target.h" +#include "dwarf-regs.h" #include "evlist.h" #include "evsel.h" #include "record.h" @@ -98,38 +99,47 @@ struct evlist *evlist__new(void) return evlist; } -struct evlist *evlist__new_default(void) +struct evlist *evlist__new_default(const struct target *target, bool sample_callchains) { struct evlist *evlist = evlist__new(); bool can_profile_kernel; struct perf_pmu *pmu = NULL; + struct evsel *evsel; + char buf[256]; + int err; if (!evlist) return NULL; can_profile_kernel = perf_event_paranoid_check(1); - while ((pmu = perf_pmus__scan_core(pmu)) != NULL) { - char buf[256]; - int err; - - snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name, + if (EM_HOST == EM_S390 && sample_callchains) { + snprintf(buf, sizeof(buf), "software/%s/%s", + target__has_cpu(target) ? "cpu-clock" : "task-clock", can_profile_kernel ? "P" : "Pu"); err = parse_event(evlist, buf); - if (err) { - evlist__delete(evlist); - return NULL; + if (err) + goto out_err; + } else { + while ((pmu = perf_pmus__scan_core(pmu)) != NULL) { + snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name, + can_profile_kernel ? "P" : "Pu"); + err = parse_event(evlist, buf); + if (err) + goto out_err; } } + /* If there is only 1 event a sample identifier isn't necessary. */ if (evlist->core.nr_entries > 1) { - struct evsel *evsel; - evlist__for_each_entry(evlist, evsel) evsel__set_sample_id(evsel, /*can_sample_identifier=*/false); } return evlist; +out_err: + evlist__delete(evlist); + return NULL; } struct evlist *evlist__new_dummy(void) @@ -815,9 +825,8 @@ static struct mmap *evlist__alloc_mmap(struct evlist *evlist, bool overwrite) { int i; - struct mmap *map; + struct mmap *map = calloc(evlist->core.nr_mmaps, sizeof(struct mmap)); - map = zalloc(evlist->core.nr_mmaps * sizeof(struct mmap)); if (!map) return NULL; @@ -1622,8 +1631,11 @@ int evlist__parse_sample(struct evlist *evlist, union perf_event *event, struct struct evsel *evsel = evlist__event2evsel(evlist, event); int ret; - if (!evsel) + if (!evsel) { + /* Ensure the sample is okay for perf_sample__exit. */ + perf_sample__init(sample, /*all=*/false); return -EFAULT; + } ret = evsel__parse_sample(evsel, event, sample); if (ret) return ret; diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index d17c3b57a409..e507f5f20ef6 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -104,7 +104,7 @@ struct evsel_str_handler { }; struct evlist *evlist__new(void); -struct evlist *evlist__new_default(void); +struct evlist *evlist__new_default(const struct target *target, bool sample_callchains); struct evlist *evlist__new_dummy(void); void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus, struct perf_thread_map *threads); diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index f59228c1a39e..2ee87fd84d3e 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -926,7 +926,8 @@ const char *evsel__name(struct evsel *evsel) break; case PERF_TYPE_TRACEPOINT: - scnprintf(bf, sizeof(bf), "%s", "unknown tracepoint"); + scnprintf(bf, sizeof(bf), "unknown tracepoint id=%#"PRIx64, + evsel->core.attr.config); break; case PERF_TYPE_BREAKPOINT: @@ -938,8 +939,8 @@ const char *evsel__name(struct evsel *evsel) break; default: - scnprintf(bf, sizeof(bf), "unknown attr type: %d", - evsel->core.attr.type); + scnprintf(bf, sizeof(bf), "unknown event PMU=%d config=%#"PRIx64, + evsel->core.attr.type, evsel->core.attr.config); break; } @@ -1015,12 +1016,17 @@ uint16_t evsel__e_machine(struct evsel *evsel, uint32_t *e_flags) return perf_session__e_machine(session, e_flags); } -static void __evsel__config_callchain(struct evsel *evsel, struct record_opts *opts, - struct callchain_param *param) +static void __evsel__config_callchain(struct evsel *evsel, const struct record_opts *opts, + const struct callchain_param *param) { bool function = evsel__is_function_event(evsel); struct perf_event_attr *attr = &evsel->core.attr; + if (EM_HOST == EM_S390 && param->record_mode == CALLCHAIN_FP) { + pr_warning_once( + "Framepointer unwinding lacks kernel support. Use '--call-graph dwarf'\n"); + } + evsel__set_sample_bit(evsel, CALLCHAIN); attr->sample_max_stack = param->max_stack; @@ -1080,14 +1086,14 @@ static void __evsel__config_callchain(struct evsel *evsel, struct record_opts *o attr->defer_callchain = 1; } -void evsel__config_callchain(struct evsel *evsel, struct record_opts *opts, - struct callchain_param *param) +void evsel__config_callchain(struct evsel *evsel, const struct record_opts *opts, + const struct callchain_param *param) { if (param->enabled) return __evsel__config_callchain(evsel, opts, param); } -static void evsel__reset_callgraph(struct evsel *evsel, struct callchain_param *param) +static void evsel__reset_callgraph(struct evsel *evsel, const struct callchain_param *param) { struct perf_event_attr *attr = &evsel->core.attr; @@ -1106,7 +1112,7 @@ static void evsel__reset_callgraph(struct evsel *evsel, struct callchain_param * static void evsel__apply_ratio_to_prev(struct evsel *evsel, struct perf_event_attr *attr, - struct record_opts *opts, + const struct record_opts *opts, const char *buf) { struct perf_event_attr *prev_attr = NULL; @@ -1170,7 +1176,7 @@ static void evsel__apply_ratio_to_prev(struct evsel *evsel, } static void evsel__apply_config_terms(struct evsel *evsel, - struct record_opts *opts, bool track) + const struct record_opts *opts, bool track) { struct evsel_config_term *term; struct list_head *config_terms = &evsel->config_terms; @@ -1445,7 +1451,7 @@ void __weak arch_evsel__apply_ratio_to_prev(struct evsel *evsel __maybe_unused, { } -static void evsel__set_default_freq_period(struct record_opts *opts, +static void evsel__set_default_freq_period(const struct record_opts *opts, struct perf_event_attr *attr) { if (opts->freq) { @@ -1490,8 +1496,8 @@ bool evsel__is_offcpu_event(struct evsel *evsel) * enable/disable events specifically, as there's no * initial traced exec call. */ -void evsel__config(struct evsel *evsel, struct record_opts *opts, - struct callchain_param *callchain) +void evsel__config(struct evsel *evsel, const struct record_opts *opts, + const struct callchain_param *callchain) { struct evsel *leader = evsel__leader(evsel); struct perf_event_attr *attr = &evsel->core.attr; @@ -3067,7 +3073,7 @@ static inline bool overflow(const void *endp, u16 max_size, const void *offset, #define OVERFLOW_CHECK(offset, size, max_size) \ do { \ if (overflow(endp, (max_size), (offset), (size))) \ - return -EFAULT; \ + goto out_efault; \ } while (0) #define OVERFLOW_CHECK_u64(offset) \ @@ -3199,6 +3205,8 @@ static int __set_offcpu_sample(struct perf_sample *data) data->cgroup = *array; return 0; +out_efault: + return -EFAULT; } int evsel__parse_sample(struct evsel *evsel, union perf_event *event, @@ -3217,7 +3225,8 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, */ union u64_swap u; - memset(data, 0, sizeof(*data)); + perf_sample__init(data, /*all=*/true); + data->evsel = evsel; data->cpu = data->pid = data->tid = -1; data->stream_id = data->id = data->time = -1ULL; data->period = evsel->core.attr.sample_period; @@ -3231,25 +3240,26 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, data->callchain = (struct ip_callchain *)&event->callchain_deferred.nr; if (data->callchain->nr > max_callchain_nr) - return -EFAULT; + goto out_efault; data->deferred_cookie = event->callchain_deferred.cookie; if (evsel->core.attr.sample_id_all) perf_evsel__parse_id_sample(evsel, event, data); + return 0; } if (event->header.type != PERF_RECORD_SAMPLE) { - if (!evsel->core.attr.sample_id_all) - return 0; - return perf_evsel__parse_id_sample(evsel, event, data); + if (evsel->core.attr.sample_id_all) + perf_evsel__parse_id_sample(evsel, event, data); + return 0; } array = event->sample.array; if (perf_event__check_size(event, evsel->sample_size)) - return -EFAULT; + goto out_efault; if (type & PERF_SAMPLE_IDENTIFIER) { data->id = *array; @@ -3342,7 +3352,7 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, sizeof(struct sample_read_value); if (data->read.group.nr > max_group_nr) - return -EFAULT; + goto out_efault; sz = data->read.group.nr * sample_read_value_size(read_format); OVERFLOW_CHECK(array, sz, max_size); @@ -3370,7 +3380,7 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, data->callchain = (struct ip_callchain *)array++; callchain_nr = data->callchain->nr; if (callchain_nr > max_callchain_nr) - return -EFAULT; + goto out_efault; sz = callchain_nr * sizeof(u64); /* * Save the cookie for the deferred user callchain. The last 2 @@ -3428,7 +3438,7 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, data->branch_stack = (struct branch_stack *)array++; if (data->branch_stack->nr > max_branch_nr) - return -EFAULT; + goto out_efault; sz = data->branch_stack->nr * sizeof(struct branch_entry); if (evsel__has_branch_hw_idx(evsel)) { @@ -3505,7 +3515,7 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, data->user_stack.size = *array++; if (WARN_ONCE(data->user_stack.size > sz, "user stack dump failure\n")) - return -EFAULT; + goto out_efault; } } @@ -3582,10 +3592,15 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event, array = (void *)array + sz; } - if (evsel__is_offcpu_event(evsel)) - return __set_offcpu_sample(data); + if (evsel__is_offcpu_event(evsel)) { + if (__set_offcpu_sample(data)) + goto out_efault; + } return 0; +out_efault: + perf_sample__exit(data); + return -EFAULT; } int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event, @@ -3785,25 +3800,42 @@ bool evsel__fallback(struct evsel *evsel, struct target *target, int err, { int paranoid; - if ((err == ENOENT || err == ENXIO || err == ENODEV) && - evsel->core.attr.type == PERF_TYPE_HARDWARE && - evsel->core.attr.config == PERF_COUNT_HW_CPU_CYCLES) { + if ((err == ENODEV || err == ENOENT || err == ENXIO) && + evsel__match(evsel, HARDWARE, HW_CPU_CYCLES)) { /* - * If it's cycles then fall back to hrtimer based cpu-clock sw - * counter, which is always available even if no PMU support. - * - * PPC returns ENXIO until 2.6.37 (behavior changed with commit - * b0a873e). + * If it's the legacy hardware cycles event fails then fall back + * to hrtimer based cpu-clock sw counter, which is always + * available even if no PMU support. PPC returned ENXIO rather + * than ENODEV or ENOENT until 2.6.37. */ - evsel->core.attr.type = PERF_TYPE_SOFTWARE; + evsel->pmu = perf_pmus__find_by_type(PERF_TYPE_SOFTWARE); + assert(evsel->pmu); /* software is a "well-known" and can't fail PMU type. */ + + /* Configure the event. */ + evsel->core.attr.type = PERF_TYPE_SOFTWARE; evsel->core.attr.config = target__has_cpu(target) ? PERF_COUNT_SW_CPU_CLOCK : PERF_COUNT_SW_TASK_CLOCK; - scnprintf(msg, msgsize, - "The cycles event is not supported, trying to fall back to %s", - target__has_cpu(target) ? "cpu-clock" : "task-clock"); + evsel->core.is_pmu_core = false; + /* Remove excludes for new event. */ + if (evsel->fallenback_eacces) { + evsel->core.attr.exclude_kernel = 0; + evsel->core.attr.exclude_hv = 0; + evsel->fallenback_eacces = false; + } + if (evsel->fallenback_eopnotsupp) { + evsel->core.attr.exclude_guest = 0; + evsel->fallenback_eopnotsupp = false; + } + + /* Name is recomputed by evsel__name. */ zfree(&evsel->name); + + /* Log message. */ + scnprintf(msg, msgsize, + "The cycles event is not supported, trying to fall back to %s", + evsel__name(evsel)); return true; } else if (err == EACCES && !evsel->core.attr.exclude_kernel && (paranoid = perf_event_paranoid()) > 1) { @@ -3830,7 +3862,7 @@ bool evsel__fallback(struct evsel *evsel, struct target *target, int err, " samples", paranoid); evsel->core.attr.exclude_kernel = 1; evsel->core.attr.exclude_hv = 1; - + evsel->fallenback_eacces = true; return true; } else if (err == EOPNOTSUPP && !evsel->core.attr.exclude_guest && !evsel->exclude_GH) { @@ -3851,7 +3883,7 @@ bool evsel__fallback(struct evsel *evsel, struct target *target, int err, /* Apple M1 requires exclude_guest */ scnprintf(msg, msgsize, "Trying to fall back to excluding guest samples"); evsel->core.attr.exclude_guest = 1; - + evsel->fallenback_eopnotsupp = true; return true; } no_fallback: diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index a3d754c029a0..339b5c08a33d 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -124,6 +124,8 @@ struct evsel { bool default_metricgroup; /* A member of the Default metricgroup */ bool default_show_events; /* If a default group member, show the event */ bool needs_uniquify; + bool fallenback_eacces; + bool fallenback_eopnotsupp; struct hashmap *per_pkg_mask; int err; int script_output_type; @@ -285,10 +287,10 @@ void evsel__set_priv_destructor(void (*destructor)(void *priv)); struct callchain_param; -void evsel__config(struct evsel *evsel, struct record_opts *opts, - struct callchain_param *callchain); -void evsel__config_callchain(struct evsel *evsel, struct record_opts *opts, - struct callchain_param *callchain); +void evsel__config(struct evsel *evsel, const struct record_opts *opts, + const struct callchain_param *callchain); +void evsel__config_callchain(struct evsel *evsel, const struct record_opts *opts, + const struct callchain_param *callchain); int __evsel__sample_size(u64 sample_type); void evsel__calc_id_pos(struct evsel *evsel); diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c index 465fe2e9bbbe..644769e92708 100644 --- a/tools/perf/util/expr.c +++ b/tools/perf/util/expr.c @@ -376,7 +376,8 @@ int expr__find_ids(const char *expr, const char *one, if (one) expr__del_id(ctx, one); - return ret; + /* A positive value means syntax error, convert to -EINVAL */ + return ret > 0 ? -EINVAL : ret; } double expr_id_data__value(const struct expr_id_data *data) @@ -406,9 +407,9 @@ double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx &count)) result = count; else - pr_err("Failure to read '%s'", literal); + pr_err("Failure to read '%s'\n", literal); } else { - pr_err("Unrecognized literal '%s'", literal); + pr_err("Unrecognized literal '%s'\n", literal); } pr_debug2("literal: %s = %f\n", literal, result); diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 9142a8ba4019..f30e48eb3fc3 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -54,6 +54,7 @@ #include "bpf-event.h" #include "bpf-utils.h" #include "clockid.h" +#include "cacheline.h" #include #include @@ -62,6 +63,15 @@ #include #endif +#define MAX_BPF_DATA_LEN (256 * 1024 * 1024) +#define MAX_BPF_PROGS 131072 +#define MAX_CACHE_ENTRIES 32768 +#define MAX_GROUP_DESC 32768 +#define MAX_NUMA_NODES 4096 +#define MAX_PMU_CAPS 512 +#define MAX_PMU_MAPPINGS 4096 +#define MAX_SCHED_DOMAINS 64 + /* * magic2 = "PERFILE2" * must be a numerical value to let the endianness @@ -306,16 +316,19 @@ static int do_read_bitmap(struct feat_fd *ff, unsigned long **pset, u64 *psize) return 0; } -#ifdef HAVE_LIBTRACEEVENT static int write_tracing_data(struct feat_fd *ff, - struct evlist *evlist) + struct evlist *evlist __maybe_unused) { if (WARN(ff->buf, "Error: calling %s in pipe-mode.\n", __func__)) return -1; +#ifdef HAVE_LIBTRACEEVENT return read_tracing_data(ff->fd, &evlist->core.entries); -} +#else + pr_err("ERROR: Trying to write tracing data without libtraceevent support.\n"); + return -1; #endif +} static int write_build_id(struct feat_fd *ff, struct evlist *evlist __maybe_unused) @@ -1026,10 +1039,10 @@ static int write_dir_format(struct feat_fd *ff, return do_write(ff, &data->dir.version, sizeof(data->dir.version)); } -#ifdef HAVE_LIBBPF_SUPPORT -static int write_bpf_prog_info(struct feat_fd *ff, +static int write_bpf_prog_info(struct feat_fd *ff __maybe_unused, struct evlist *evlist __maybe_unused) { +#ifdef HAVE_LIBBPF_SUPPORT struct perf_env *env = &ff->ph->env; struct rb_root *root; struct rb_node *next; @@ -1067,11 +1080,16 @@ static int write_bpf_prog_info(struct feat_fd *ff, out: up_read(&env->bpf_progs.lock); return ret; +#else + pr_err("ERROR: Trying to write bpf_prog_info without libbpf support.\n"); + return -1; +#endif // HAVE_LIBBPF_SUPPORT } -static int write_bpf_btf(struct feat_fd *ff, +static int write_bpf_btf(struct feat_fd *ff __maybe_unused, struct evlist *evlist __maybe_unused) { +#ifdef HAVE_LIBBPF_SUPPORT struct perf_env *env = &ff->ph->env; struct rb_root *root; struct rb_node *next; @@ -1100,8 +1118,11 @@ static int write_bpf_btf(struct feat_fd *ff, out: up_read(&env->bpf_progs.lock); return ret; -} +#else + pr_err("ERROR: Trying to write btf data without libbpf support.\n"); + return -1; #endif // HAVE_LIBBPF_SUPPORT +} static int cpu_cache_level__sort(const void *a, const void *b) { @@ -1304,6 +1325,19 @@ out: return ret; } +static int write_cln_size(struct feat_fd *ff, + struct evlist *evlist __maybe_unused) +{ + int cln_size = cacheline_size(); + + if (!cln_size) + cln_size = DEFAULT_CACHELINE_SIZE; + + ff->ph->env.cln_size = cln_size; + + return do_write(ff, &cln_size, sizeof(cln_size)); +} + static int write_stat(struct feat_fd *ff __maybe_unused, struct evlist *evlist __maybe_unused) { @@ -1980,9 +2014,9 @@ static void print_dir_format(struct feat_fd *ff, FILE *fp) fprintf(fp, "# directory data version : %"PRIu64"\n", data->dir.version); } -#ifdef HAVE_LIBBPF_SUPPORT -static void print_bpf_prog_info(struct feat_fd *ff, FILE *fp) +static void print_bpf_prog_info(struct feat_fd *ff __maybe_unused, FILE *fp) { +#ifdef HAVE_LIBBPF_SUPPORT struct perf_env *env = &ff->ph->env; struct rb_root *root; struct rb_node *next; @@ -1993,7 +2027,7 @@ static void print_bpf_prog_info(struct feat_fd *ff, FILE *fp) next = rb_first(root); if (!next) - printf("# bpf_prog_info empty\n"); + fprintf(fp, "# bpf_prog_info empty\n"); while (next) { struct bpf_prog_info_node *node; @@ -2006,10 +2040,14 @@ static void print_bpf_prog_info(struct feat_fd *ff, FILE *fp) } up_read(&env->bpf_progs.lock); +#else + fprintf(fp, "# bpf_prog_info missing, no libbpf support\n"); +#endif // HAVE_LIBBPF_SUPPORT } -static void print_bpf_btf(struct feat_fd *ff, FILE *fp) +static void print_bpf_btf(struct feat_fd *ff __maybe_unused, FILE *fp) { +#ifdef HAVE_LIBBPF_SUPPORT struct perf_env *env = &ff->ph->env; struct rb_root *root; struct rb_node *next; @@ -2031,8 +2069,10 @@ static void print_bpf_btf(struct feat_fd *ff, FILE *fp) } up_read(&env->bpf_progs.lock); -} +#else + fprintf(fp, "# bpf btf data missing, no libbpf support\n"); #endif // HAVE_LIBBPF_SUPPORT +} static void free_event_desc(struct evsel *events) { @@ -2261,6 +2301,11 @@ static void print_cache(struct feat_fd *ff, FILE *fp __maybe_unused) } } +static void print_cln_size(struct feat_fd *ff, FILE *fp) +{ + fprintf(fp, "# cacheline size: %u\n", ff->ph->env.cln_size); +} + static void print_compressed(struct feat_fd *ff, FILE *fp) { fprintf(fp, "# compressed : %s, level = %d, ratio = %d\n", @@ -2545,6 +2590,11 @@ static int perf_header__read_build_ids_abi_quirk(struct perf_header *header, perf_event_header__bswap(&old_bev.header); len = old_bev.header.size - sizeof(old_bev); + if (len < 0 || len >= PATH_MAX) { + pr_warning("invalid build_id filename length %zd\n", len); + return -1; + } + if (readn(input, filename, len) != len) return -1; @@ -2587,6 +2637,11 @@ static int perf_header__read_build_ids(struct perf_header *header, perf_event_header__bswap(&bev.header); len = bev.header.size - sizeof(bev); + if (len < 0 || len >= PATH_MAX) { + pr_warning("invalid build_id filename length %zd\n", len); + goto out; + } + if (readn(input, filename, len) != len) goto out; /* @@ -2644,14 +2699,17 @@ static int process_e_machine(struct feat_fd *ff, void *data __maybe_unused) return do_read_u32(ff, &ff->ph->env.e_flags); } -#ifdef HAVE_LIBTRACEEVENT -static int process_tracing_data(struct feat_fd *ff, void *data) +static int process_tracing_data(struct feat_fd *ff __maybe_unused, void *data __maybe_unused) { +#ifdef HAVE_LIBTRACEEVENT ssize_t ret = trace_report(ff->fd, data, false); return ret < 0 ? -1 : 0; -} +#else + pr_err("ERROR: Trying to read tracing data without libtraceevent support.\n"); + return -1; #endif +} static int process_build_id(struct feat_fd *ff, void *data __maybe_unused) { @@ -2673,6 +2731,13 @@ static int process_nrcpus(struct feat_fd *ff, void *data __maybe_unused) ret = do_read_u32(ff, &nr_cpus_online); if (ret) return ret; + + if (nr_cpus_online > nr_cpus_avail) { + pr_err("Invalid HEADER_NRCPUS: nr_cpus_online (%u) > nr_cpus_avail (%u)\n", + nr_cpus_online, nr_cpus_avail); + return -1; + } + env->nr_cpus_avail = (int)nr_cpus_avail; env->nr_cpus_online = (int)nr_cpus_online; return 0; @@ -2746,6 +2811,12 @@ process_event_desc(struct feat_fd *ff, void *data __maybe_unused) return 0; } +/* + * Some arbitrary max for the number of command line arguments, + * Wildcards can expand and end up with tons of command line args. + */ +#define MAX_CMDLINE_NR 1048576 + static int process_cmdline(struct feat_fd *ff, void *data __maybe_unused) { struct perf_env *env = &ff->ph->env; @@ -2755,13 +2826,16 @@ static int process_cmdline(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &nr)) return -1; + if (nr > MAX_CMDLINE_NR) + return -1; + env->nr_cmdline = nr; cmdline = zalloc(ff->size + nr + 1); if (!cmdline) return -1; - argv = zalloc(sizeof(char *) * (nr + 1)); + argv = calloc(nr + 1, sizeof(char *)); if (!argv) goto error; @@ -2794,6 +2868,11 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused) int cpu_nr = env->nr_cpus_avail; u64 size = 0; + if (cpu_nr == 0) { + pr_err("Invalid HEADER_CPU_TOPOLOGY: missing HEADER_NRCPUS\n"); + return -1; + } + env->cpu = calloc(cpu_nr, sizeof(*env->cpu)); if (!env->cpu) return -1; @@ -2801,6 +2880,12 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &nr)) goto free_cpu; + if (nr > (u32)cpu_nr) { + pr_err("Invalid HEADER_CPU_TOPOLOGY: nr_sibling_cores (%u) > nr_cpus_avail (%d)\n", + nr, cpu_nr); + goto free_cpu; + } + env->nr_sibling_cores = nr; size += sizeof(u32); if (strbuf_init(&sb, 128) < 0) @@ -2820,7 +2905,13 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused) env->sibling_cores = strbuf_detach(&sb, NULL); if (do_read_u32(ff, &nr)) - return -1; + goto free_cpu; + + if (nr > (u32)cpu_nr) { + pr_err("Invalid HEADER_CPU_TOPOLOGY: nr_sibling_threads (%u) > nr_cpus_avail (%d)\n", + nr, cpu_nr); + goto free_cpu; + } env->nr_sibling_threads = nr; size += sizeof(u32); @@ -2869,7 +2960,13 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused) return 0; if (do_read_u32(ff, &nr)) - return -1; + goto free_cpu; + + if (nr > (u32)cpu_nr) { + pr_err("Invalid HEADER_CPU_TOPOLOGY: nr_sibling_dies (%u) > nr_cpus_avail (%d)\n", + nr, cpu_nr); + goto free_cpu; + } env->nr_sibling_dies = nr; size += sizeof(u32); @@ -2915,7 +3012,19 @@ static int process_numa_topology(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &nr)) return -1; - nodes = zalloc(sizeof(*nodes) * nr); + if (nr > MAX_NUMA_NODES) { + pr_err("Invalid HEADER_NUMA_TOPOLOGY: nr_nodes (%u) > %u\n", + nr, MAX_NUMA_NODES); + return -1; + } + + if (ff->size < sizeof(u32) + nr * (sizeof(u32) + 2 * sizeof(u64))) { + pr_err("Invalid HEADER_NUMA_TOPOLOGY: section too small (%zu) for %u nodes\n", + ff->size, nr); + return -1; + } + + nodes = calloc(nr, sizeof(*nodes)); if (!nodes) return -ENOMEM; @@ -2966,6 +3075,18 @@ static int process_pmu_mappings(struct feat_fd *ff, void *data __maybe_unused) return 0; } + if (pmu_num > MAX_PMU_MAPPINGS) { + pr_err("Invalid HEADER_PMU_MAPPINGS: pmu_num (%u) > %u\n", + pmu_num, MAX_PMU_MAPPINGS); + return -1; + } + + if (ff->size < sizeof(u32) + pmu_num * 2 * sizeof(u32)) { + pr_err("Invalid HEADER_PMU_MAPPINGS: section too small (%zu) for %u PMUs\n", + ff->size, pmu_num); + return -1; + } + env->nr_pmu_mappings = pmu_num; if (strbuf_init(&sb, 128) < 0) return -1; @@ -3016,12 +3137,25 @@ static int process_group_desc(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &nr_groups)) return -1; - env->nr_groups = nr_groups; if (!nr_groups) { pr_debug("group desc not available\n"); return 0; } + if (nr_groups > MAX_GROUP_DESC) { + pr_err("Invalid HEADER_GROUP_DESC: nr_groups (%u) > %u\n", + nr_groups, MAX_GROUP_DESC); + return -1; + } + + if (ff->size < sizeof(u32) + nr_groups * 3 * sizeof(u32)) { + pr_err("Invalid HEADER_GROUP_DESC: section too small (%zu) for %u groups\n", + ff->size, nr_groups); + return -1; + } + + env->nr_groups = nr_groups; + desc = calloc(nr_groups, sizeof(*desc)); if (!desc) return -1; @@ -3113,7 +3247,19 @@ static int process_cache(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &cnt)) return -1; - caches = zalloc(sizeof(*caches) * cnt); + if (cnt > MAX_CACHE_ENTRIES) { + pr_err("Invalid HEADER_CACHE: cnt (%u) > %u\n", + cnt, MAX_CACHE_ENTRIES); + return -1; + } + + if (ff->size < 2 * sizeof(u32) + cnt * 7 * sizeof(u32)) { + pr_err("Invalid HEADER_CACHE: section too small (%zu) for %u entries\n", + ff->size, cnt); + return -1; + } + + caches = calloc(cnt, sizeof(*caches)); if (!caches) return -1; @@ -3154,6 +3300,16 @@ out_free_caches: return -1; } +static int process_cln_size(struct feat_fd *ff, void *data __maybe_unused) +{ + struct perf_env *env = &ff->ph->env; + + if (do_read_u32(ff, &env->cln_size)) + return -1; + + return 0; +} + static int process_sample_time(struct feat_fd *ff, void *data __maybe_unused) { struct perf_session *session; @@ -3195,7 +3351,19 @@ static int process_mem_topology(struct feat_fd *ff, if (do_read_u64(ff, &nr)) return -1; - nodes = zalloc(sizeof(*nodes) * nr); + if (nr > MAX_NUMA_NODES) { + pr_err("Invalid HEADER_MEM_TOPOLOGY: nr_nodes (%llu) > %u\n", + (unsigned long long)nr, MAX_NUMA_NODES); + return -1; + } + + if (ff->size < 3 * sizeof(u64) + nr * 2 * sizeof(u64)) { + pr_err("Invalid HEADER_MEM_TOPOLOGY: section too small (%zu) for %llu nodes\n", + ff->size, (unsigned long long)nr); + return -1; + } + + nodes = calloc(nr, sizeof(*nodes)); if (!nodes) return -1; @@ -3285,7 +3453,19 @@ static int process_hybrid_topology(struct feat_fd *ff, if (do_read_u32(ff, &nr)) return -1; - nodes = zalloc(sizeof(*nodes) * nr); + if (nr > MAX_PMU_MAPPINGS) { + pr_err("Invalid HEADER_HYBRID_TOPOLOGY: nr_nodes (%u) > %u\n", + nr, MAX_PMU_MAPPINGS); + return -1; + } + + if (ff->size < sizeof(u32) + nr * 2 * sizeof(u32)) { + pr_err("Invalid HEADER_HYBRID_TOPOLOGY: section too small (%zu) for %u nodes\n", + ff->size, nr); + return -1; + } + + nodes = calloc(nr, sizeof(*nodes)); if (!nodes) return -ENOMEM; @@ -3330,9 +3510,9 @@ static int process_dir_format(struct feat_fd *ff, return do_read_u64(ff, &data->dir.version); } -#ifdef HAVE_LIBBPF_SUPPORT -static int process_bpf_prog_info(struct feat_fd *ff, void *data __maybe_unused) +static int process_bpf_prog_info(struct feat_fd *ff __maybe_unused, void *data __maybe_unused) { +#ifdef HAVE_LIBBPF_SUPPORT struct bpf_prog_info_node *info_node; struct perf_env *env = &ff->ph->env; struct perf_bpil *info_linear; @@ -3347,6 +3527,18 @@ static int process_bpf_prog_info(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &count)) return -1; + if (count > MAX_BPF_PROGS) { + pr_err("Invalid HEADER_BPF_PROG_INFO: count (%u) > %u\n", + count, MAX_BPF_PROGS); + return -1; + } + + if (ff->size < sizeof(u32) + count * (2 * sizeof(u32) + sizeof(u64))) { + pr_err("Invalid HEADER_BPF_PROG_INFO: section too small (%zu) for %u entries\n", + ff->size, count); + return -1; + } + down_write(&env->bpf_progs.lock); for (i = 0; i < count; ++i) { @@ -3364,6 +3556,12 @@ static int process_bpf_prog_info(struct feat_fd *ff, void *data __maybe_unused) goto out; } + if (data_len > MAX_BPF_DATA_LEN) { + pr_warning("Invalid HEADER_BPF_PROG_INFO: data_len (%u) too large\n", + data_len); + goto out; + } + info_linear = malloc(sizeof(struct perf_bpil) + data_len); if (!info_linear) @@ -3402,10 +3600,15 @@ out: free(info_node); up_write(&env->bpf_progs.lock); return err; +#else + pr_err("ERROR: Trying to read bpf_prog_info without libbpf support.\n"); + return -1; +#endif // HAVE_LIBBPF_SUPPORT } -static int process_bpf_btf(struct feat_fd *ff, void *data __maybe_unused) +static int process_bpf_btf(struct feat_fd *ff __maybe_unused, void *data __maybe_unused) { +#ifdef HAVE_LIBBPF_SUPPORT struct perf_env *env = &ff->ph->env; struct btf_node *node = NULL; u32 count, i; @@ -3419,6 +3622,17 @@ static int process_bpf_btf(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &count)) return -1; + if (count > MAX_BPF_PROGS) { + pr_err("bpf btf count %u too large (max %u)\n", count, MAX_BPF_PROGS); + return -1; + } + + if (ff->size < sizeof(u32) + count * 2 * sizeof(u32)) { + pr_err("Invalid HEADER_BPF_BTF: section too small (%zu) for %u entries\n", + ff->size, count); + return -1; + } + down_write(&env->bpf_progs.lock); for (i = 0; i < count; ++i) { @@ -3429,6 +3643,12 @@ static int process_bpf_btf(struct feat_fd *ff, void *data __maybe_unused) if (do_read_u32(ff, &data_size)) goto out; + if (data_size > MAX_BPF_DATA_LEN) { + pr_err("bpf btf data size %u too large (max %u)\n", + data_size, MAX_BPF_DATA_LEN); + goto out; + } + node = malloc(sizeof(struct btf_node) + data_size); if (!node) goto out; @@ -3449,8 +3669,11 @@ out: up_write(&env->bpf_progs.lock); free(node); return err; -} +#else + pr_err("ERROR: Trying to read btf data without libbpf support.\n"); + return -1; #endif // HAVE_LIBBPF_SUPPORT +} static int process_compressed(struct feat_fd *ff, void *data __maybe_unused) @@ -3492,7 +3715,13 @@ static int __process_pmu_caps(struct feat_fd *ff, int *nr_caps, if (!nr_pmu_caps) return 0; - *caps = zalloc(sizeof(char *) * nr_pmu_caps); + if (nr_pmu_caps > MAX_PMU_CAPS) { + pr_err("Invalid pmu caps: nr_pmu_caps (%u) > %u\n", + nr_pmu_caps, MAX_PMU_CAPS); + return -1; + } + + *caps = calloc(nr_pmu_caps, sizeof(char *)); if (!*caps) return -1; @@ -3569,7 +3798,19 @@ static int process_pmu_caps(struct feat_fd *ff, void *data __maybe_unused) return 0; } - pmu_caps = zalloc(sizeof(*pmu_caps) * nr_pmu); + if (nr_pmu > MAX_PMU_MAPPINGS) { + pr_err("Invalid HEADER_PMU_CAPS: nr_pmu (%u) > %u\n", + nr_pmu, MAX_PMU_MAPPINGS); + return -1; + } + + if (ff->size < sizeof(u32) + nr_pmu * sizeof(u32)) { + pr_err("Invalid HEADER_PMU_CAPS: section too small (%zu) for %u PMUs\n", + ff->size, nr_pmu); + return -1; + } + + pmu_caps = calloc(nr_pmu, sizeof(*pmu_caps)); if (!pmu_caps) return -ENOMEM; @@ -3622,7 +3863,18 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused nra = env->nr_cpus_avail; nr = env->nr_cpus_online; - cd_map = zalloc(sizeof(*cd_map) * nra); + if (nra == 0 || nr == 0) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: missing HEADER_NRCPUS\n"); + return -1; + } + + if (ff->size < 2 * sizeof(u32) + nr * 2 * sizeof(u32)) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: section too small (%zu) for %u CPUs\n", + (size_t)ff->size, nr); + return -1; + } + + cd_map = calloc(nra, sizeof(*cd_map)); if (!cd_map) return -1; @@ -3638,12 +3890,34 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused if (ret) return ret; + /* + * Sanity check: real systems have at most ~10 sched domain levels + * (SMT, CLS, MC, PKG + NUMA hops). Reject obviously bogus values + * from malformed perf.data files before they cause excessive + * allocation in the per-CPU loop. + */ + if (max_sched_domains > MAX_SCHED_DOMAINS) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: max_sched_domains %u > %u\n", + max_sched_domains, MAX_SCHED_DOMAINS); + return -1; + } + env->max_sched_domains = max_sched_domains; for (i = 0; i < nr; i++) { if (do_read_u32(ff, &cpu)) return -1; + if (cpu >= nra) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: cpu %d >= nr_cpus_avail (%d)\n", cpu, nra); + return -1; + } + + if (cd_map[cpu]) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: duplicate cpu %u\n", cpu); + return -1; + } + cd_map[cpu] = zalloc(sizeof(*cd_map[cpu])); if (!cd_map[cpu]) return -1; @@ -3653,9 +3927,15 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused if (do_read_u32(ff, &nr_domains)) return -1; + if (nr_domains > max_sched_domains) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: nr_domains %u > max_sched_domains (%u)\n", + nr_domains, max_sched_domains); + return -1; + } + cd_map[cpu]->nr_domains = nr_domains; - cd_map[cpu]->domains = zalloc(sizeof(*d_info) * max_sched_domains); + cd_map[cpu]->domains = calloc(max_sched_domains, sizeof(*d_info)); if (!cd_map[cpu]->domains) return -1; @@ -3663,11 +3943,23 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused if (do_read_u32(ff, &domain)) return -1; + if (domain >= max_sched_domains) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: domain %d >= max_sched_domains (%d)\n", + domain, max_sched_domains); + return -1; + } + d_info = zalloc(sizeof(*d_info)); if (!d_info) return -1; - assert(cd_map[cpu]->domains[domain] == NULL); + if (cd_map[cpu]->domains[domain]) { + pr_err("Invalid HEADER_CPU_DOMAIN_INFO: duplicate domain %u for cpu %u\n", + domain, cpu); + free(d_info); + return -1; + } + cd_map[cpu]->domains[domain] = d_info; d_info->domain = domain; @@ -3726,9 +4018,7 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE]; const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = { -#ifdef HAVE_LIBTRACEEVENT FEAT_OPN(TRACING_DATA, tracing_data, false), -#endif FEAT_OPN(BUILD_ID, build_id, false), FEAT_OPR(HOSTNAME, hostname, false), FEAT_OPR(OSRELEASE, osrelease, false), @@ -3752,10 +4042,8 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = { FEAT_OPR(MEM_TOPOLOGY, mem_topology, true), FEAT_OPR(CLOCKID, clockid, false), FEAT_OPN(DIR_FORMAT, dir_format, false), -#ifdef HAVE_LIBBPF_SUPPORT FEAT_OPR(BPF_PROG_INFO, bpf_prog_info, false), FEAT_OPR(BPF_BTF, bpf_btf, false), -#endif FEAT_OPR(COMPRESSED, compressed, false), FEAT_OPR(CPU_PMU_CAPS, cpu_pmu_caps, false), FEAT_OPR(CLOCK_DATA, clock_data, false), @@ -3763,6 +4051,7 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = { FEAT_OPR(PMU_CAPS, pmu_caps, false), FEAT_OPR(CPU_DOMAIN_INFO, cpu_domain_info, true), FEAT_OPR(E_MACHINE, e_machine, false), + FEAT_OPR(CLN_SIZE, cln_size, false), }; struct header_print_data { @@ -3770,6 +4059,13 @@ struct header_print_data { bool full; /* extended list of headers */ }; +const char *header_feat__name(unsigned int id) +{ + if (id < HEADER_LAST_FEATURE) + return feat_ops[id].name ?: "INVALID"; + return "INVALID"; +} + static int perf_file_section__fprintf_info(struct perf_file_section *section, struct perf_header *ph, int feat, int fd, void *data) @@ -3778,11 +4074,11 @@ static int perf_file_section__fprintf_info(struct perf_file_section *section, struct feat_fd ff; if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) { - pr_debug("Failed to lseek to %" PRIu64 " offset for feature " - "%d, continuing...\n", section->offset, feat); + pr_debug("Failed to lseek to %" PRIu64 " offset for feature %s (%d), continuing...\n", + section->offset, header_feat__name(feat), feat); return 0; } - if (feat >= HEADER_LAST_FEATURE) { + if (feat >= ph->last_feat) { pr_warning("unknown feature %d\n", feat); return 0; } @@ -3834,7 +4130,7 @@ int perf_header__fprintf_info(struct perf_session *session, FILE *fp, bool full) return 0; fprintf(fp, "# missing features: "); - for_each_clear_bit(bit, header->adds_features, HEADER_LAST_FEATURE) { + for_each_clear_bit(bit, header->adds_features, header->last_feat) { if (bit) fprintf(fp, "%s ", feat_ops[bit].name); } @@ -4164,7 +4460,7 @@ int perf_header__process_sections(struct perf_header *header, int fd, if (err < 0) goto out_free; - for_each_set_bit(feat, header->adds_features, HEADER_LAST_FEATURE) { + for_each_set_bit(feat, header->adds_features, header->last_feat) { err = process(sec++, header, feat, fd, data); if (err < 0) goto out_free; @@ -4379,6 +4675,7 @@ int perf_file_header__read(struct perf_file_header *header, ph->data_offset = header->data.offset; ph->data_size = header->data.size; ph->feat_offset = header->data.offset + header->data.size; + ph->last_feat = HEADER_LAST_FEATURE; return 0; } @@ -4394,8 +4691,8 @@ static int perf_file_section__process(struct perf_file_section *section, }; if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) { - pr_debug("Failed to lseek to %" PRIu64 " offset for feature " - "%d, continuing...\n", section->offset, feat); + pr_debug("Failed to lseek to %" PRIu64 " offset for feature %s (%d), continuing...\n", + section->offset, header_feat__name(feat), feat); return 0; } @@ -4428,6 +4725,8 @@ static int perf_file_header__read_pipe(struct perf_pipe_file_header *header, if (ph->needs_swap) header->size = bswap_64(header->size); + /* The last feature is written out as a 0 sized event and will update this value. */ + ph->last_feat = 0; return 0; } @@ -4660,31 +4959,68 @@ out_delete_evlist: return -ENOMEM; } -int perf_event__process_feature(struct perf_session *session, +int perf_event__process_feature(const struct perf_tool *tool __maybe_unused, + struct perf_session *session, union perf_event *event) { struct feat_fd ff = { .fd = 0 }; struct perf_record_header_feature *fe = (struct perf_record_header_feature *)event; + struct perf_header *header = &session->header; int type = fe->header.type; - u64 feat = fe->feat_id; + int feat = (int)fe->feat_id; int ret = 0; bool print = dump_trace; + bool last_feature_mark = false; if (type < 0 || type >= PERF_RECORD_HEADER_MAX) { pr_warning("invalid record type %d in pipe-mode\n", type); return 0; } - if (feat == HEADER_RESERVED || feat >= HEADER_LAST_FEATURE) { - pr_warning("invalid record type %d in pipe-mode\n", type); + if (feat == HEADER_RESERVED) { + pr_warning("invalid reserved record type in pipe-mode\n"); + return -1; + } + if (feat < 0 || feat == INT_MAX) { + pr_warning("invalid value for feature type %x\n", feat); + return -1; + } + if (feat >= header->last_feat) { + if (event->header.size == sizeof(*fe)) { + /* + * Either an unexpected zero size feature or the + * HEADER_LAST_FEATURE mark. + */ + if (feat > header->last_feat) + header->last_feat = min(feat, HEADER_LAST_FEATURE); + last_feature_mark = true; + } else { + /* + * A feature but beyond what is known as in + * bounds. Assume the last feature is 1 beyond this + * feature. + */ + session->header.last_feat = min(feat + 1, HEADER_LAST_FEATURE); + } + } + if (feat >= HEADER_LAST_FEATURE) { + if (!last_feature_mark) { + pr_warning("unknown feature %d for data file version (%s) in this version of perf (%s)\n", + feat, header->env.version, perf_version_string); + } + return 0; + } + if (event->header.size < sizeof(*fe)) { + pr_warning("feature header size too small\n"); return -1; } - ff.buf = (void *)fe->data; ff.size = event->header.size - sizeof(*fe); - ff.ph = &session->header; + ff.ph = header; if (feat_ops[feat].process && feat_ops[feat].process(&ff, NULL)) { - ret = -1; + // Processing failed, ignore when this is the last feature mark. + if (!last_feature_mark) + ret = -1; goto out; } diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index cc40ac796f52..86b1a72026d3 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -55,6 +55,7 @@ enum { HEADER_PMU_CAPS, HEADER_CPU_DOMAIN_INFO, HEADER_E_MACHINE, + HEADER_CLN_SIZE, HEADER_LAST_FEATURE, HEADER_FEAT_BITS = 256, }; @@ -109,6 +110,7 @@ struct perf_header { u64 data_size; u64 feat_offset; DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS); + int last_feat; struct perf_env env; }; @@ -132,6 +134,8 @@ struct perf_header_feature_ops { extern const char perf_version_string[]; +const char *header_feat__name(unsigned int id); + int perf_session__read_header(struct perf_session *session); int perf_session__write_header(struct perf_session *session, struct evlist *evlist, @@ -170,7 +174,8 @@ int perf_header__process_sections(struct perf_header *header, int fd, int perf_header__fprintf_info(struct perf_session *s, FILE *fp, bool full); -int perf_event__process_feature(struct perf_session *session, +int perf_event__process_feature(const struct perf_tool *tool, + struct perf_session *session, union perf_event *event); int perf_event__process_attr(const struct perf_tool *tool, union perf_event *event, struct evlist **pevlist); @@ -202,6 +207,8 @@ int write_padded(struct feat_fd *fd, const void *bf, int build_caches_for_cpu(u32 cpu, struct cpu_cache_level caches[], u32 *cntp); +#define DEFAULT_CACHELINE_SIZE 64 + /* * arch specific callback */ diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 7ffaa3d9851b..747fdc455c80 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -110,6 +110,9 @@ void hists__calc_col_len(struct hists *hists, struct hist_entry *h) len = thread__comm_len(h->thread); if (hists__new_col_len(hists, HISTC_COMM, len)) hists__set_col_len(hists, HISTC_THREAD, len + 8); + if (hists->hpp_list->comm_nodigit) + hists__new_col_len(hists, HISTC_COMM_NODIGIT, + (u16) sort__comm_nodigit_len(h)); if (h->ms.map) { len = dso__name_len(map__dso(h->ms.map)); @@ -1148,7 +1151,7 @@ iter_prepare_cumulative_entry(struct hist_entry_iter *iter, * cumulated only one time to prevent entries more than 100% * overhead. */ - he_cache = malloc(sizeof(*he_cache) * (cursor->nr + 1)); + he_cache = calloc(cursor->nr + 1, sizeof(*he_cache)); if (he_cache == NULL) return -ENOMEM; diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 1d5ea632ca4e..d97a4efb9250 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -44,6 +44,7 @@ enum hist_column { HISTC_THREAD, HISTC_TGID, HISTC_COMM, + HISTC_COMM_NODIGIT, HISTC_CGROUP_ID, HISTC_CGROUP, HISTC_PARENT, @@ -522,6 +523,7 @@ struct perf_hpp_list { int socket; int thread; int comm; + int comm_nodigit; }; extern struct perf_hpp_list perf_hpp_list; diff --git a/tools/perf/util/intel-tpebs.c b/tools/perf/util/intel-tpebs.c index 3c958d738ca6..8b615dc94e9e 100644 --- a/tools/perf/util/intel-tpebs.c +++ b/tools/perf/util/intel-tpebs.c @@ -22,7 +22,6 @@ #include "tool.h" #include "cpumap.h" #include "metricgroup.h" -#include "stat.h" #include #include #include @@ -217,15 +216,6 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused, return 0; } -static int process_feature_event(const struct perf_tool *tool __maybe_unused, - struct perf_session *session, - union perf_event *event) -{ - if (event->feat.feat_id < HEADER_LAST_FEATURE) - return perf_event__process_feature(session, event); - return 0; -} - static void *__sample_reader(void *arg __maybe_unused) { struct perf_session *session; @@ -238,7 +228,7 @@ static void *__sample_reader(void *arg __maybe_unused) perf_tool__init(&tool, /*ordered_events=*/false); tool.sample = process_sample_event; - tool.feature = process_feature_event; + tool.feature = perf_event__process_feature; tool.attr = perf_event__process_attr; session = perf_session__new(&data, &tool); diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c index 63ea3fb53e77..c1c12308cc12 100644 --- a/tools/perf/util/libbfd.c +++ b/tools/perf/util/libbfd.c @@ -233,7 +233,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, } if (a2l == NULL) { - if (!symbol_conf.disable_add2line_warn) + if (!symbol_conf.addr2line_disable_warn) pr_warning("addr2line_init failed for %s\n", dso_name); return 0; } diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c index 0d126d233c01..a0deb742a733 100644 --- a/tools/perf/util/llvm.c +++ b/tools/perf/util/llvm.c @@ -153,11 +153,17 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym, /*get_op_info=*/NULL, symbol_lookup_callback); } else { char triplet[64]; + const char *features = NULL; scnprintf(triplet, sizeof(triplet), "%s-linux-gnu", args->arch->name); - disasm = LLVMCreateDisasm(triplet, &storage, /*tag_type=*/0, - /*get_op_info=*/NULL, symbol_lookup_callback); + if (args->arch->id.e_machine == EM_AARCH64) + features = "+all"; + disasm = LLVMCreateDisasmCPUFeatures(triplet, /*cpu=*/"", + features, &storage, + /*tag_type=*/0, + /*get_op_info=*/NULL, + symbol_lookup_callback); } if (disasm == NULL) diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c index 4092211cff62..81a97ac34077 100644 --- a/tools/perf/util/maps.c +++ b/tools/perf/util/maps.c @@ -844,7 +844,6 @@ static int __maps__insert_sorted(struct maps *maps, unsigned int first_after_ind static int __maps__fixup_overlap_and_insert(struct maps *maps, struct map *new) { int err = 0; - FILE *fp = debug_file(); unsigned int i, ni = INT_MAX; // Some gcc complain, but depends on maps_by_name... if (!maps__maps_by_address_sorted(maps)) @@ -872,8 +871,8 @@ static int __maps__fixup_overlap_and_insert(struct maps *maps, struct map *new) dso__name(map__dso(new))); } else if (verbose >= 2) { pr_debug("overlapping maps:\n"); - map__fprintf(new, fp); - map__fprintf(pos, fp); + map__fprintf(new, debug_file()); + map__fprintf(pos, debug_file()); } if (maps_by_name) @@ -894,7 +893,7 @@ static int __maps__fixup_overlap_and_insert(struct maps *maps, struct map *new) map__set_end(before, map__start(new)); if (verbose >= 2 && !use_browser) - map__fprintf(before, fp); + map__fprintf(before, debug_file()); } if (map__end(new) < map__end(pos)) { /* The new map isn't as long as the existing map. */ @@ -912,7 +911,7 @@ static int __maps__fixup_overlap_and_insert(struct maps *maps, struct map *new) map__map_ip(after, map__end(new))); if (verbose >= 2 && !use_browser) - map__fprintf(after, fp); + map__fprintf(after, debug_file()); } /* * If adding one entry, for `before` or `after`, we can replace @@ -956,6 +955,7 @@ static int __maps__fixup_overlap_and_insert(struct maps *maps, struct map *new) if (maps_by_name) { map__put(maps_by_name[ni]); maps_by_name[ni] = map__get(new); + maps__set_maps_by_name_sorted(maps, false); } err = __maps__insert_sorted(maps, i + 1, after, NULL); @@ -982,6 +982,7 @@ static int __maps__fixup_overlap_and_insert(struct maps *maps, struct map *new) if (maps_by_name) { map__put(maps_by_name[ni]); maps_by_name[ni] = map__get(new); + maps__set_maps_by_name_sorted(maps, false); } check_invariants(maps); @@ -1080,16 +1081,9 @@ int maps__copy_from(struct maps *dest, struct maps *parent) map__put(new); } maps__set_maps_by_address_sorted(dest, maps__maps_by_address_sorted(parent)); - if (!err) { - RC_CHK_ACCESS(dest)->last_search_by_name_idx = - RC_CHK_ACCESS(parent)->last_search_by_name_idx; - maps__set_maps_by_name_sorted(dest, - dest_maps_by_name && - maps__maps_by_name_sorted(parent)); - } else { - RC_CHK_ACCESS(dest)->last_search_by_name_idx = 0; - maps__set_maps_by_name_sorted(dest, false); - } + RC_CHK_ACCESS(dest)->last_search_by_name_idx = 0; + /* Values were copied into the name array in address order. */ + maps__set_maps_by_name_sorted(dest, false); } else { /* Unexpected copying to a maps containing entries. */ for (unsigned int i = 0; !err && i < n; i++) { diff --git a/tools/perf/util/mem2node.c b/tools/perf/util/mem2node.c index 03a7d7b27737..51a2292cbf7e 100644 --- a/tools/perf/util/mem2node.c +++ b/tools/perf/util/mem2node.c @@ -59,7 +59,7 @@ int mem2node__init(struct mem2node *map, struct perf_env *env) max += bitmap_weight(n->set, n->size); } - entries = zalloc(sizeof(*entries) * max); + entries = calloc(max, sizeof(*entries)); if (!entries) return -ENOMEM; diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c index 7e39d469111b..4db9578efd81 100644 --- a/tools/perf/util/metricgroup.c +++ b/tools/perf/util/metricgroup.c @@ -387,8 +387,13 @@ static bool match_pm_metric_or_groups(const struct pmu_metric *pm, const char *p const char *metric_or_groups) { const char *pm_pmu = pm->pmu ?: "cpu"; + struct perf_pmu *perf_pmu = NULL; - if (strcmp(pmu, "all") && strcmp(pm_pmu, pmu)) + if (pm->pmu) + perf_pmu = perf_pmus__find(pm->pmu); + + if (strcmp(pmu, "all") && strcmp(pm_pmu, pmu) && + (perf_pmu && !perf_pmu__name_wildcard_match(perf_pmu, pmu))) return false; return match_metric_or_groups(pm->metric_group, metric_or_groups) || @@ -909,10 +914,9 @@ static int __add_metric(struct list_head *metric_list, expr = metric_no_threshold ? pm->metric_name : pm->metric_threshold; visited_node.name = "__threshold__"; } - if (expr__find_ids(expr, NULL, root_metric->pctx) < 0) { - /* Broken metric. */ - ret = -EINVAL; - } + + ret = expr__find_ids(expr, NULL, root_metric->pctx); + if (!ret) { /* Resolve referenced metrics. */ struct perf_pmu *pmu; @@ -1096,7 +1100,7 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con */ ret = metricgroup__for_each_metric(table, metricgroup__add_metric_callback, &data); if (!ret && !data.has_match) - ret = -EINVAL; + ret = -ENOENT; /* * add to metric_list so that they can be released @@ -1147,6 +1151,8 @@ static int metricgroup__add_metric_list(const char *pmu, const char *list, user_requested_cpu_list, system_wide, metric_list, table); if (ret == -EINVAL) + pr_err("Fail to parse metric or group `%s'\n", metric_name); + else if (ret == -ENOENT) pr_err("Cannot find metric or group `%s'\n", metric_name); if (ret) @@ -1259,7 +1265,8 @@ err_out: static int parse_ids(bool metric_no_merge, bool fake_pmu, struct expr_parse_ctx *ids, const char *modifier, bool group_events, const bool tool_events[TOOL_PMU__EVENT_MAX], - struct evlist **out_evlist) + struct evlist **out_evlist, + const char *filter_pmu) { struct parse_events_error parse_error; struct evlist *parsed_evlist; @@ -1313,7 +1320,7 @@ static int parse_ids(bool metric_no_merge, bool fake_pmu, } pr_debug("Parsing metric events '%s'\n", events.buf); parse_events_error__init(&parse_error); - ret = __parse_events(parsed_evlist, events.buf, /*pmu_filter=*/NULL, + ret = __parse_events(parsed_evlist, events.buf, filter_pmu, &parse_error, fake_pmu, /*warn_if_reordered=*/false, /*fake_tp=*/false); if (ret) { @@ -1416,7 +1423,8 @@ static int parse_groups(struct evlist *perf_evlist, /*modifier=*/NULL, /*group_events=*/false, tool_events, - &combined_evlist); + &combined_evlist, + (pmu && strcmp(pmu, "all") == 0) ? NULL : pmu); } if (combined) expr__ctx_free(combined); @@ -1471,7 +1479,8 @@ static int parse_groups(struct evlist *perf_evlist, } if (!metric_evlist) { ret = parse_ids(metric_no_merge, fake_pmu, m->pctx, m->modifier, - m->group_events, tool_events, &m->evlist); + m->group_events, tool_events, &m->evlist, + (pmu && strcmp(pmu, "all") == 0) ? NULL : pmu); if (ret) goto out; diff --git a/tools/perf/util/ordered-events.c b/tools/perf/util/ordered-events.c index 8c62611f10aa..a5857f9f5af2 100644 --- a/tools/perf/util/ordered-events.c +++ b/tools/perf/util/ordered-events.c @@ -243,7 +243,7 @@ static int do_flush(struct ordered_events *oe, bool show_progress) if (iter->timestamp > limit) break; ret = oe->deliver(oe, iter); - if (ret) + if (ret < 0) return ret; ordered_events__delete(oe, iter); diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 7b4629625b1e..1497e1f2a08c 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -429,7 +429,7 @@ bool parse_events__filter_pmu(const struct parse_events_state *parse_state, if (parse_state->pmu_filter == NULL) return false; - return strcmp(parse_state->pmu_filter, pmu->name) != 0; + return perf_pmu__wildcard_match(pmu, parse_state->pmu_filter) == 0; } static int parse_events_add_pmu(struct parse_events_state *parse_state, diff --git a/tools/perf/util/perf-regs-arch/perf_regs_s390.c b/tools/perf/util/perf-regs-arch/perf_regs_s390.c index c61df24edf0f..19f219225183 100644 --- a/tools/perf/util/perf-regs-arch/perf_regs_s390.c +++ b/tools/perf/util/perf-regs-arch/perf_regs_s390.c @@ -1,7 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 +#include +#include #include "../perf_regs.h" #include "../../arch/s390/include/perf_regs.h" +#include "debug.h" + +#include +#include uint64_t __perf_reg_mask_s390(bool intr __maybe_unused) { @@ -95,3 +101,75 @@ uint64_t __perf_reg_sp_s390(void) { return PERF_REG_S390_R15; } + +/* %rXX */ +#define SDT_OP_REGEX1 "^(%r([0-9]|1[0-5]))$" +/* +-###(%rXX) */ +#define SDT_OP_REGEX2 "^([+-]?[0-9]+\\(%r([0-9]|1[0-5])\\))$" +static regex_t sdt_op_regex1, sdt_op_regex2; + +static int sdt_init_op_regex(void) +{ + static int initialized; + int ret = 0; + + if (initialized) + return 0; + + ret = regcomp(&sdt_op_regex1, SDT_OP_REGEX1, REG_EXTENDED); + if (ret) + goto error; + initialized = 1; + + ret = regcomp(&sdt_op_regex2, SDT_OP_REGEX2, REG_EXTENDED); + if (ret) + goto free_regex1; + initialized = 2; + + return 0; + +free_regex1: + regfree(&sdt_op_regex1); +error: + pr_debug4("Regex compilation error, initialized %d\n", initialized); + initialized = 0; + return ret; +} + +/* + * Parse OP and convert it into uprobe format, which is, +/-NUM(%gprREG). + * Possible variants of OP are: + * Format Example + * ------------------------- + * NUM(%rREG) 48(%r1) + * -NUM(%rREG) -48(%r1) + * +NUM(%rREG) +48(%r1) + * %rREG %r1 + */ +int __perf_sdt_arg_parse_op_s390(char *old_op, char **new_op) +{ + int ret, new_len; + regmatch_t rm[6]; + + *new_op = NULL; + ret = sdt_init_op_regex(); + if (ret) + return -EINVAL; + + if (!regexec(&sdt_op_regex1, old_op, ARRAY_SIZE(rm), rm, 0) || + !regexec(&sdt_op_regex2, old_op, ARRAY_SIZE(rm), rm, 0)) { + new_len = 1; /* NULL byte */ + new_len += (int)(rm[1].rm_eo - rm[1].rm_so); + *new_op = zalloc(new_len); + if (!*new_op) + return -ENOMEM; + + scnprintf(*new_op, new_len, "%.*s", + (int)(rm[1].rm_eo - rm[1].rm_so), old_op + rm[1].rm_so); + } else { + pr_debug4("Skipping unsupported SDT argument: %s\n", old_op); + return SDT_ARG_SKIP; + } + + return SDT_ARG_VALID; +} diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index 5b8f34beb24e..f52b0e1f7fc7 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c @@ -23,6 +23,9 @@ int perf_sdt_arg_parse_op(uint16_t e_machine, char *old_op, char **new_op) case EM_X86_64: ret = __perf_sdt_arg_parse_op_x86(old_op, new_op); break; + case EM_S390: + ret = __perf_sdt_arg_parse_op_s390(old_op, new_op); + break; default: pr_debug("Unknown ELF machine %d, standard arguments parse will be skipped.\n", e_machine); diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index 7c04700bf837..573f0d1dfe04 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h @@ -62,6 +62,7 @@ uint64_t __perf_reg_mask_s390(bool intr); const char *__perf_reg_name_s390(int id); uint64_t __perf_reg_ip_s390(void); uint64_t __perf_reg_sp_s390(void); +int __perf_sdt_arg_parse_op_s390(char *old_op, char **new_op); int __perf_sdt_arg_parse_op_x86(char *old_op, char **new_op); uint64_t __perf_reg_mask_x86(bool intr); diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c index 98be2eb8f1f0..9a2023ceeefd 100644 --- a/tools/perf/util/pmus.c +++ b/tools/perf/util/pmus.c @@ -621,7 +621,7 @@ void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *p while ((pmu = scan_fn(pmu)) != NULL) len += perf_pmu__num_events(pmu); - aliases = zalloc(sizeof(struct sevent) * len); + aliases = calloc(len, sizeof(struct sevent)); if (!aliases) { pr_err("FATAL: not enough memory to print PMU events\n"); return; diff --git a/tools/perf/util/powerpc-vpadtl.c b/tools/perf/util/powerpc-vpadtl.c index d1c3396f182f..710f3093f3f9 100644 --- a/tools/perf/util/powerpc-vpadtl.c +++ b/tools/perf/util/powerpc-vpadtl.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include "color.h" @@ -182,7 +183,9 @@ static int powerpc_vpadtl_sample(struct powerpc_vpadtl_entry *record, { struct perf_sample sample; union perf_event event; + int ret; + perf_sample__init(&sample, /*all=*/true); sample.ip = be64_to_cpu(record->srr0); sample.period = 1; sample.cpu = cpu; @@ -198,12 +201,12 @@ static int powerpc_vpadtl_sample(struct powerpc_vpadtl_entry *record, event.sample.header.misc = sample.cpumode; event.sample.header.size = sizeof(struct perf_event_header); - if (perf_session__deliver_synth_event(vpa->session, &event, &sample)) { + ret = perf_session__deliver_synth_event(vpa->session, &event, &sample); + if (ret) pr_debug("Failed to create sample for dtl entry\n"); - return -1; - } - return 0; + perf_sample__exit(&sample); + return ret; } static int powerpc_vpadtl_get_buffer(struct powerpc_vpadtl_queue *vpaq) diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 710e4620923e..34b4badd2c14 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -229,7 +228,7 @@ static int convert_exec_to_group(const char *exec, char **result) if (!exec_copy) return -ENOMEM; - ptr1 = basename(exec_copy); + ptr1 = (char *)perf_basename(exec_copy); if (!ptr1) { ret = -EINVAL; goto out; @@ -1850,7 +1849,7 @@ int parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev) /* Copy arguments and ensure return probe has no C argument */ pev->nargs = argc - 1; - pev->args = zalloc(sizeof(struct perf_probe_arg) * pev->nargs); + pev->args = calloc(pev->nargs, sizeof(struct perf_probe_arg)); if (pev->args == NULL) { ret = -ENOMEM; goto out; @@ -2000,7 +1999,7 @@ int parse_probe_trace_command(const char *cmd, struct probe_trace_event *tev) } tev->nargs = argc - 2; - tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); + tev->args = calloc(tev->nargs, sizeof(struct probe_trace_arg)); if (tev->args == NULL) { ret = -ENOMEM; goto out; @@ -2373,7 +2372,7 @@ static int convert_to_perf_probe_event(struct probe_trace_event *tev, /* Convert trace_arg to probe_arg */ pev->nargs = tev->nargs; - pev->args = zalloc(sizeof(struct perf_probe_arg) * pev->nargs); + pev->args = calloc(pev->nargs, sizeof(struct perf_probe_arg)); if (pev->args == NULL) return -ENOMEM; for (i = 0; i < tev->nargs && ret >= 0; i++) { @@ -2480,7 +2479,7 @@ int perf_probe_event__copy(struct perf_probe_event *dst, if (perf_probe_point__copy(&dst->point, &src->point) < 0) goto out_err; - dst->args = zalloc(sizeof(struct perf_probe_arg) * src->nargs); + dst->args = calloc(src->nargs, sizeof(struct perf_probe_arg)); if (!dst->args) goto out_err; dst->nargs = src->nargs; @@ -3179,7 +3178,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev, } /* Setup result trace-probe-events */ - *tevs = zalloc(sizeof(*tev) * num_matched_functions); + *tevs = calloc(num_matched_functions, sizeof(*tev)); if (!*tevs) { ret = -ENOMEM; goto out; @@ -3251,8 +3250,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev, tev->uprobes = pev->uprobes; tev->nargs = pev->nargs; if (tev->nargs) { - tev->args = zalloc(sizeof(struct probe_trace_arg) * - tev->nargs); + tev->args = calloc(tev->nargs, sizeof(struct probe_trace_arg)); if (tev->args == NULL) goto nomem_out; } @@ -3363,7 +3361,7 @@ static int try_to_find_absolute_address(struct perf_probe_event *pev, } tev->nargs = pev->nargs; - tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); + tev->args = calloc(tev->nargs, sizeof(struct probe_trace_arg)); if (!tev->args) goto errout; @@ -3549,7 +3547,7 @@ static int find_probe_trace_events_from_cache(struct perf_probe_event *pev, goto out; } - *tevs = zalloc(ret * sizeof(*tev)); + *tevs = calloc(ret, sizeof(*tev)); if (!*tevs) { ret = -ENOMEM; goto out; diff --git a/tools/perf/util/probe-file.c b/tools/perf/util/probe-file.c index f78c3bc3d601..4032572cbf55 100644 --- a/tools/perf/util/probe-file.c +++ b/tools/perf/util/probe-file.c @@ -414,7 +414,7 @@ int probe_cache_entry__get_event(struct probe_cache_entry *entry, if (ret > probe_conf.max_probes) return -E2BIG; - *tevs = zalloc(ret * sizeof(*tev)); + *tevs = calloc(ret, sizeof(*tev)); if (!*tevs) return -ENOMEM; diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 5ffd97ee4898..64328abeef8b 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -1305,7 +1305,7 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf) tev->point.offset); /* Expand special probe argument if exist */ - args = zalloc(sizeof(struct perf_probe_arg) * MAX_PROBE_ARGS); + args = calloc(MAX_PROBE_ARGS, sizeof(struct perf_probe_arg)); if (args == NULL) { ret = -ENOMEM; goto end; @@ -1316,7 +1316,7 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf) goto end; tev->nargs = ret; - tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); + tev->args = calloc(tev->nargs, sizeof(struct probe_trace_arg)); if (tev->args == NULL) { ret = -ENOMEM; goto end; @@ -1393,7 +1393,7 @@ int debuginfo__find_trace_events(struct debuginfo *dbg, int ret, i; /* Allocate result tevs array */ - *tevs = zalloc(sizeof(struct probe_trace_event) * tf.max_tevs); + *tevs = calloc(tf.max_tevs, sizeof(struct probe_trace_event)); if (*tevs == NULL) return -ENOMEM; @@ -1566,7 +1566,7 @@ int debuginfo__find_available_vars_at(struct debuginfo *dbg, int ret; /* Allocate result vls array */ - *vls = zalloc(sizeof(struct variable_list) * af.max_vls); + *vls = calloc(af.max_vls, sizeof(struct variable_list)); if (*vls == NULL) return -ENOMEM; diff --git a/tools/perf/util/sample.c b/tools/perf/util/sample.c index 8f82aaf1aab6..cf73329326d7 100644 --- a/tools/perf/util/sample.c +++ b/tools/perf/util/sample.c @@ -19,15 +19,22 @@ void perf_sample__init(struct perf_sample *sample, bool all) if (all) { memset(sample, 0, sizeof(*sample)); } else { + sample->evsel = NULL; sample->user_regs = NULL; sample->intr_regs = NULL; + sample->merged_callchain = false; + sample->callchain = NULL; } } void perf_sample__exit(struct perf_sample *sample) { - free(sample->user_regs); - free(sample->intr_regs); + zfree(&sample->user_regs); + zfree(&sample->intr_regs); + if (sample->merged_callchain) { + zfree(&sample->callchain); + sample->merged_callchain = false; + } } struct regs_dump *perf_sample__user_regs(struct perf_sample *sample) diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h index 3cce8dd202aa..e556c9b656ea 100644 --- a/tools/perf/util/sample.h +++ b/tools/perf/util/sample.h @@ -5,6 +5,7 @@ #include #include +struct evsel; struct machine; struct thread; @@ -70,58 +71,177 @@ struct aux_sample { }; struct simd_flags { - u8 arch:1, /* architecture (isa) */ - pred:2; /* predication */ + u8 arch: 2, /* architecture (isa) */ + pred: 3, /* predication */ + resv: 3; /* reserved */ }; /* simd architecture flags */ -#define SIMD_OP_FLAGS_ARCH_SVE 0x01 /* ARM SVE */ +enum simd_op_flags { + SIMD_OP_FLAGS_ARCH_NONE = 0x0, /* No SIMD operation */ + SIMD_OP_FLAGS_ARCH_SVE, /* Arm SVE */ + SIMD_OP_FLAGS_ARCH_SME, /* Arm SME */ + SIMD_OP_FLAGS_ARCH_ASE, /* Arm Advanced SIMD */ +}; /* simd predicate flags */ -#define SIMD_OP_FLAGS_PRED_PARTIAL 0x01 /* partial predicate */ -#define SIMD_OP_FLAGS_PRED_EMPTY 0x02 /* empty predicate */ +enum simd_pred_flags { + SIMD_OP_FLAGS_PRED_NONE = 0x0, /* Not available */ + SIMD_OP_FLAGS_PRED_PARTIAL, /* partial predicate */ + SIMD_OP_FLAGS_PRED_EMPTY, /* empty predicate */ + SIMD_OP_FLAGS_PRED_FULL, /* full predicate */ + SIMD_OP_FLAGS_PRED_DISABLED, /* disabled predicate */ +}; +/** + * struct perf_sample + * + * A sample is generally filled in by evlist__parse_sample/evsel__parse_sample + * which fills in the variables from a "union perf_event *event" which is data + * from a perf ring buffer or perf.data file. The "event" sample is variable in + * length as determined by the perf_event_attr (in the evsel) and details within + * the sample event itself. A struct perf_sample avoids needing to care about + * the variable length nature of the original event. + * + * To avoid being excessively large parts of the struct perf_sample are pointers + * into the original sample event. In general the lifetime of a struct + * perf_sample needs to be less than the "union perf_event *event" it was + * derived from. + * + * The struct regs_dump user_regs and intr_regs are lazily allocated again for + * size reasons, due to them holding a cache of looked up registers. The + * function pair of perf_sample__init and perf_sample__exit correctly initialize + * and clean up these values. + */ struct perf_sample { + /** @evsel: Backward reference to the evsel used when constructing the sample. */ + struct evsel *evsel; + /** @ip: The sample event PERF_SAMPLE_IP value. */ u64 ip; - u32 pid, tid; + /** @pid: The sample event PERF_SAMPLE_TID pid value. */ + u32 pid; + /** @tid: The sample event PERF_SAMPLE_TID tid value. */ + u32 tid; + /** @time: The sample event PERF_SAMPLE_TIME value. */ u64 time; + /** @addr: The sample event PERF_SAMPLE_ADDR value. */ u64 addr; + /** @id: The sample event PERF_SAMPLE_ID or PERF_SAMPLE_IDENTIFIER value. */ u64 id; + /** @stream_id: The sample event PERF_SAMPLE_STREAM_ID value. */ u64 stream_id; + /** @period: The sample event PERF_SAMPLE_PERIOD value. */ u64 period; + /** @weight: Data determined by PERF_SAMPLE_WEIGHT or PERF_SAMPLE_WEIGHT_STRUCT. */ u64 weight; + /** @transaction: The sample event PERF_SAMPLE_TRANSACTION value. */ u64 transaction; + /** @insn_cnt: Filled in and used by intel-pt. */ u64 insn_cnt; + /** @cyc_cnt: Filled in and used by intel-pt. */ u64 cyc_cnt; + /** @cpu: The sample event PERF_SAMPLE_CPU value. */ u32 cpu; + /** + * @raw_size: The size in bytes of raw data from PERF_SAMPLE_RAW. For + * alignment reasons this should always be sizeof(u32) + * followed by a multiple of sizeof(u64). + */ u32 raw_size; + /** @data_src: The sample event PERF_SAMPLE_DATA_SRC value. */ u64 data_src; + /** @phys_addr: The sample event PERF_SAMPLE_PHYS_ADDR value. */ u64 phys_addr; + /** @data_page_size: The sample event PERF_SAMPLE_DATA_PAGE_SIZE value. */ u64 data_page_size; + /** @code_page_size: The sample event PERF_SAMPLE_CODE_PAGE_SIZE value. */ u64 code_page_size; + /** @cgroup: The sample event PERF_SAMPLE_CGROUP value. */ u64 cgroup; + /** @flags: Extra flag data from auxiliary events like intel-pt. */ u32 flags; + /** @machine_pid: The guest machine pid derived from the sample id. */ u32 machine_pid; + /** @vcpu: The guest machine vcpu derived from the sample id. */ u32 vcpu; + /** + * @insn_len: Instruction length from auxiliary events like + * intel-pt. The instruction itself is held in insn. + */ u16 insn_len; - u8 cpumode; + /** @misc: The entire struct perf_event_header misc variable. */ u16 misc; + /** + * @ins_lat: Instruction latency information from weight2 in + * PERF_SAMPLE_WEIGHT_STRUCT or auxiliary events like + * intel-pt. + */ u16 ins_lat; - /** @weight3: On x86 holds retire_lat, on powerpc holds p_stage_cyc. */ + /** + * @weight3: From PERF_SAMPLE_WEIGHT_STRUCT. On x86 holds retire_lat, on + * powerpc holds p_stage_cyc. + */ u16 weight3; - bool no_hw_idx; /* No hw_idx collected in branch_stack */ - bool deferred_callchain; /* Has deferred user callchains */ + /** + * @cpumode: The cpumode from struct perf_event_header misc variable + * masked with CPUMODE_MASK. Gives user, kernel and hypervisor + * information. + */ + u8 cpumode; + /** + * @no_hw_idx: For PERF_SAMPLE_BRANCH_STACK, true when + * PERF_SAMPLE_BRANCH_HW_INDEX isn't set. + */ + bool no_hw_idx; + /** + * @deferred_callchain: When processing PERF_SAMPLE_CALLCHAIN a deferred + * user callchain marker was encountered. + */ + bool deferred_callchain; + /** + * @merged_callchain: A synthesized merged callchain that is allocated + * and needs freeing. + */ + bool merged_callchain; + /** + * @deferred_cookie: Identifier of the deferred callchain in the later + * PERF_RECORD_CALLCHAIN_DEFERRED event. + */ u64 deferred_cookie; + /** @insn: A copy of the sampled instruction filled in by perf_sample__fetch_insn. */ char insn[MAX_INSN]; + /** @raw_data: Pointer into the original event for PERF_SAMPLE_RAW data. */ void *raw_data; + /** + * @callchain: Pointer into the original event for PERF_SAMPLE_CALLCHAIN + * data. For deferred callchains this may be a copy that + * needs freeing, see sample__merge_deferred_callchain. + */ struct ip_callchain *callchain; + /** @branch_stack: Pointer into the original event for PERF_SAMPLE_BRANCH_STACK data. */ struct branch_stack *branch_stack; + /** + * @branch_stack_cntr: Pointer into the original event for + * PERF_SAMPLE_BRANCH_COUNTERS data. + */ u64 *branch_stack_cntr; + /** @user_regs: Values and pointers into the sample for PERF_SAMPLE_REGS_USER. */ struct regs_dump *user_regs; + /** @intr_regs: Values and pointers into the sample for PERF_SAMPLE_REGS_INTR. */ struct regs_dump *intr_regs; + /** @user_stack: Size and pointer into the sample for PERF_SAMPLE_STACK_USER. */ struct stack_dump user_stack; + /** + * @read: The sample event PERF_SAMPLE_READ counter values. The valid + * values depend on the attr.read_format PERF_FORMAT_ values. + */ struct sample_read read; + /** + * @aux_sample: Similar to raw data but with a 64-bit size and + * alignment, PERF_SAMPLE_AUX data. + */ struct aux_sample aux_sample; + /** @simd_flags: SIMD flag information from ARM SPE auxiliary events. */ struct simd_flags simd_flags; }; diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index 2b0df7bd9a46..5a30caaec73e 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -1701,7 +1701,7 @@ static void python_process_stat(struct perf_stat_config *config, struct perf_cpu_map *cpus = counter->core.cpus; for (int thread = 0; thread < perf_thread_map__nr(threads); thread++) { - int idx; + unsigned int idx; struct perf_cpu cpu; perf_cpu_map__for_each_cpu(cpu, idx, cpus) { diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 4b465abfa36c..fe0de2a0277f 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -131,10 +131,17 @@ static int ordered_events__deliver_event(struct ordered_events *oe, { struct perf_session *session = container_of(oe, struct perf_session, ordered_events); + int ret = perf_session__deliver_event(session, event->event, + session->tool, event->file_offset, + event->file_path); - return perf_session__deliver_event(session, event->event, - session->tool, event->file_offset, - event->file_path); + if (ret) { + pr_err("%#" PRIx64 " [%#x]: ordered event processing failed (%d) for event of type: %s (%d)\n", + event->file_offset, event->event->header.size, ret, + perf_event__name(event->event->header.type), + event->event->header.type); + } + return ret; } struct perf_session *__perf_session__new(struct perf_data *data, @@ -1257,8 +1264,9 @@ static int deliver_sample_value(struct evlist *evlist, bool per_thread) { struct perf_sample_id *sid = evlist__id2sid(evlist, v->id); - struct evsel *evsel; + struct evsel *saved_evsel = sample->evsel; u64 *storage = NULL; + int ret; if (sid) { storage = perf_sample_id__get_period_storage(sid, sample->tid, per_thread); @@ -1282,8 +1290,10 @@ static int deliver_sample_value(struct evlist *evlist, if (!sample->period) return 0; - evsel = container_of(sid->evsel, struct evsel, core); - return tool->sample(tool, event, sample, evsel, machine); + sample->evsel = container_of(sid->evsel, struct evsel, core); + ret = tool->sample(tool, event, sample, sample->evsel, machine); + sample->evsel = saved_evsel; + return ret; } static int deliver_sample_group(struct evlist *evlist, @@ -1355,39 +1365,44 @@ static int evlist__deliver_deferred_callchain(struct evlist *evlist, struct machine *machine) { struct deferred_event *de, *tmp; - struct evsel *evsel; int ret = 0; if (!tool->merge_deferred_callchains) { - evsel = evlist__id2evsel(evlist, sample->id); - return tool->callchain_deferred(tool, event, sample, - evsel, machine); + struct evsel *saved_evsel = sample->evsel; + + sample->evsel = evlist__id2evsel(evlist, sample->id); + ret = tool->callchain_deferred(tool, event, sample, + sample->evsel, machine); + sample->evsel = saved_evsel; + return ret; } list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) { struct perf_sample orig_sample; + perf_sample__init(&orig_sample, /*all=*/false); ret = evlist__parse_sample(evlist, de->event, &orig_sample); if (ret < 0) { pr_err("failed to parse original sample\n"); + perf_sample__exit(&orig_sample); break; } - if (sample->tid != orig_sample.tid) + if (sample->tid != orig_sample.tid) { + perf_sample__exit(&orig_sample); continue; + } if (event->callchain_deferred.cookie == orig_sample.deferred_cookie) sample__merge_deferred_callchain(&orig_sample, sample); else orig_sample.deferred_callchain = false; - evsel = evlist__id2evsel(evlist, orig_sample.id); + orig_sample.evsel = evlist__id2evsel(evlist, orig_sample.id); ret = evlist__deliver_sample(evlist, tool, de->event, - &orig_sample, evsel, machine); - - if (orig_sample.deferred_callchain) - free(orig_sample.callchain); + &orig_sample, orig_sample.evsel, machine); + perf_sample__exit(&orig_sample); list_del(&de->list); free(de->event); free(de); @@ -1408,22 +1423,24 @@ static int session__flush_deferred_samples(struct perf_session *session, struct evlist *evlist = session->evlist; struct machine *machine = &session->machines.host; struct deferred_event *de, *tmp; - struct evsel *evsel; int ret = 0; list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) { struct perf_sample sample; + perf_sample__init(&sample, /*all=*/false); ret = evlist__parse_sample(evlist, de->event, &sample); if (ret < 0) { pr_err("failed to parse original sample\n"); + perf_sample__exit(&sample); break; } - evsel = evlist__id2evsel(evlist, sample.id); + sample.evsel = evlist__id2evsel(evlist, sample.id); ret = evlist__deliver_sample(evlist, tool, de->event, - &sample, evsel, machine); + &sample, sample.evsel, machine); + perf_sample__exit(&sample); list_del(&de->list); free(de->event); free(de); @@ -1446,8 +1463,12 @@ static int machines__deliver_event(struct machines *machines, dump_event(evlist, event, file_offset, sample, file_path); - evsel = evlist__id2evsel(evlist, sample->id); + if (!sample->evsel) + sample->evsel = evlist__id2evsel(evlist, sample->id); + else + assert(sample->evsel == evlist__id2evsel(evlist, sample->id)); + evsel = sample->evsel; machine = machines__find_for_cpumode(machines, event, sample); switch (event->header.type) { @@ -2110,8 +2131,10 @@ more: } if ((skip = perf_session__process_event(session, event, head, "pipe")) < 0) { - pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n", - head, event->header.size, event->header.type); + pr_err("%#" PRIx64 " [%#x]: piped event processing failed for event of type: %s (%d)\n", + head, event->header.size, + perf_event__name(event->header.type), + event->header.type); err = -EINVAL; goto out_err; } @@ -2225,8 +2248,10 @@ static int __perf_session__process_decomp_events(struct perf_session *session) if (size < sizeof(struct perf_event_header) || (skip = perf_session__process_event(session, event, decomp->file_pos, decomp->file_path)) < 0) { - pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n", - decomp->file_pos + decomp->head, event->header.size, event->header.type); + pr_err("%#" PRIx64 " [%#x]: decompress event processing failed for event of type: %s (%d)\n", + decomp->file_pos + decomp->head, event->header.size, + perf_event__name(event->header.type), + event->header.type); return -EINVAL; } @@ -2382,8 +2407,9 @@ reader__read_event(struct reader *rd, struct perf_session *session, if (size < sizeof(struct perf_event_header) || (skip = rd->process(session, event, rd->file_pos, rd->path)) < 0) { errno = -skip; - pr_err("%#" PRIx64 " [%#x]: failed to process type: %d [%m]\n", + pr_err("%#" PRIx64 " [%#x]: processing failed for event of type: %s (%d) [%m]\n", rd->file_offset + rd->head, event->header.size, + perf_event__name(event->header.type), event->header.type); err = skip; goto out; @@ -2533,7 +2559,7 @@ static int __perf_session__process_dir_events(struct perf_session *session) nr_readers++; } - rd = zalloc(nr_readers * sizeof(struct reader)); + rd = calloc(nr_readers, sizeof(struct reader)); if (!rd) return -ENOMEM; @@ -2557,7 +2583,7 @@ static int __perf_session__process_dir_events(struct perf_session *session) if (!data->dir.files[i].size) continue; rd[readers] = (struct reader) { - .fd = data->dir.files[i].fd, + .fd = perf_data_file__fd(&data->dir.files[i]), .path = data->dir.files[i].path, .data_size = data->dir.files[i].size, .data_offset = 0, @@ -2766,7 +2792,8 @@ struct evsel *perf_session__find_first_evtype(struct perf_session *session, int perf_session__cpu_bitmap(struct perf_session *session, const char *cpu_list, unsigned long *cpu_bitmap) { - int i, err = -1; + unsigned int i; + int err = -1; struct perf_cpu_map *map; int nr_cpus = min(perf_session__env(session)->nr_cpus_avail, MAX_NR_CPUS); struct perf_cpu cpu; diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 42d5cd7ef4e2..0020089cb13c 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include #include @@ -30,6 +31,7 @@ #include "time-utils.h" #include "cgroup.h" #include "machine.h" +#include "session.h" #include "trace-event.h" #include #include @@ -42,11 +44,11 @@ regex_t parent_regex; const char default_parent_pattern[] = "^sys_|^do_page_fault"; const char *parent_pattern = default_parent_pattern; const char *default_sort_order = "comm,dso,symbol"; -const char default_branch_sort_order[] = "comm,dso_from,symbol_from,symbol_to,cycles"; +static const char default_branch_sort_order[] = "comm,dso_from,symbol_from,symbol_to,cycles"; const char default_mem_sort_order[] = "local_weight,mem,sym,dso,symbol_daddr,dso_daddr,snoop,tlb,locked,blocked,local_ins_lat,local_p_stage_cyc"; -const char default_top_sort_order[] = "dso,symbol"; -const char default_diff_sort_order[] = "dso,symbol"; -const char default_tracepoint_sort_order[] = "trace"; +static const char default_top_sort_order[] = "dso,symbol"; +static const char default_diff_sort_order[] = "dso,symbol"; +static const char default_tracepoint_sort_order[] = "trace"; const char *sort_order; const char *field_order; regex_t ignore_callees_regex; @@ -171,7 +173,7 @@ static int hist_entry__tgid_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%7d:%-*.*s", tgid, width, width, comm ?: ""); } -struct sort_entry sort_tgid = { +static struct sort_entry sort_tgid = { .se_header = " Tgid:Command", .se_cmp = sort__tgid_cmp, .se_snprintf = hist_entry__tgid_snprintf, @@ -193,8 +195,12 @@ static const char *hist_entry__get_simd_name(struct simd_flags *simd_flags) { u64 arch = simd_flags->arch; - if (arch & SIMD_OP_FLAGS_ARCH_SVE) + if (arch == SIMD_OP_FLAGS_ARCH_SVE) return "SVE"; + else if (arch == SIMD_OP_FLAGS_ARCH_SME) + return "SME"; + else if (arch == SIMD_OP_FLAGS_ARCH_ASE) + return "ASE"; else return "n/a"; } @@ -203,21 +209,26 @@ static int hist_entry__simd_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width __maybe_unused) { const char *name; + const char *pred_str = "."; if (!he->simd_flags.arch) return repsep_snprintf(bf, size, ""); name = hist_entry__get_simd_name(&he->simd_flags); - if (he->simd_flags.pred & SIMD_OP_FLAGS_PRED_EMPTY) - return repsep_snprintf(bf, size, "[e] %s", name); - else if (he->simd_flags.pred & SIMD_OP_FLAGS_PRED_PARTIAL) - return repsep_snprintf(bf, size, "[p] %s", name); + if (he->simd_flags.pred == SIMD_OP_FLAGS_PRED_EMPTY) + pred_str = "e"; + else if (he->simd_flags.pred == SIMD_OP_FLAGS_PRED_PARTIAL) + pred_str = "p"; + else if (he->simd_flags.pred == SIMD_OP_FLAGS_PRED_DISABLED) + pred_str = "d"; + else if (he->simd_flags.pred == SIMD_OP_FLAGS_PRED_FULL) + pred_str = "f"; - return repsep_snprintf(bf, size, "[.] %s", name); + return repsep_snprintf(bf, size, "[%s] %s", pred_str, name); } -struct sort_entry sort_simd = { +static struct sort_entry sort_simd = { .se_header = "Simd ", .se_cmp = sort__simd_cmp, .se_snprintf = hist_entry__simd_snprintf, @@ -265,6 +276,115 @@ struct sort_entry sort_comm = { .se_width_idx = HISTC_COMM, }; +/* --sort comm_nodigit */ + +size_t sort__comm_nodigit_len(struct hist_entry *entry) +{ + const char *comm = comm__str(entry->comm); + size_t index, len_nodigit = 0; + bool in_number = false; + + if (!comm) + return 0; + + for (index = 0; comm[index]; index++) { + if (!isdigit((unsigned char)comm[index])) { + in_number = false; + len_nodigit++; + } else if (!in_number) { + in_number = true; + len_nodigit += 3; /* */ + } + } + + return len_nodigit; +} + +static int64_t strcmp_nodigit(const char *left, const char *right) +{ + for (;;) { + while (*left && isdigit((unsigned char)*left)) + left++; + while (*right && isdigit((unsigned char)*right)) + right++; + if (*left == *right && !*left) { + return 0; + } else if (*left == *right) { + left++; + right++; + } else { + return (int64_t)((unsigned char)*left - (unsigned char)*right); + } + } +} + +static int64_t +sort__comm_nodigit_cmp(struct hist_entry *left, struct hist_entry *right) +{ + return strcmp_nodigit(comm__str(right->comm), comm__str(left->comm)); +} + +static int64_t +sort__comm_nodigit_collapse(struct hist_entry *left, struct hist_entry *right) +{ + return strcmp_nodigit(comm__str(right->comm), comm__str(left->comm)); +} + +static int64_t +sort__comm_nodigit_sort(struct hist_entry *left, struct hist_entry *right) +{ + return strcmp_nodigit(comm__str(right->comm), comm__str(left->comm)); +} + +static int hist_entry__comm_nodigit_snprintf(struct hist_entry *he, char *bf, + size_t size, unsigned int width) +{ + int ret = 0; + unsigned int print_len, printed = 0, start = 0, end = 0; + bool in_digit; + const char *comm = comm__str(he->comm), *print; + + while (printed < width && printed < size && comm[start]) { + in_digit = !!isdigit((unsigned char)comm[start]); + end = start + 1; + while (comm[end] && !!isdigit((unsigned char)comm[end]) == in_digit) + end++; + if (in_digit) { + print_len = 3; /* */ + print = ""; + } else { + print_len = end - start; + print = &comm[start]; + } + print_len = min(print_len, width - printed); + ret = repsep_snprintf(bf + printed, size - printed, "%-.*s", + print_len, print); + if (ret < 0) + return ret; + start = end; + printed += ret; + } + /* Pad to width if necessary */ + if (printed < width && printed < size) { + ret = repsep_snprintf(bf + printed, size - printed, "%-*.*s", + width - printed, width - printed, ""); + if (ret < 0) + return ret; + printed += ret; + } + return printed; +} + +struct sort_entry sort_comm_nodigit = { + .se_header = "CommandNoDigit", + .se_cmp = sort__comm_nodigit_cmp, + .se_collapse = sort__comm_nodigit_collapse, + .se_sort = sort__comm_nodigit_sort, + .se_snprintf = hist_entry__comm_nodigit_snprintf, + .se_filter = hist_entry__thread_filter, + .se_width_idx = HISTC_COMM_NODIGIT, +}; + /* --sort dso */ static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r) @@ -494,7 +614,7 @@ hist_entry__symoff_snprintf(struct hist_entry *he, char *bf, size_t size, unsign return repsep_snprintf(bf, size, "[%c] %s+0x%llx", he->level, sym->name, he->ip - sym->start); } -struct sort_entry sort_sym_offset = { +static struct sort_entry sort_sym_offset = { .se_header = "Symbol Offset", .se_cmp = sort__symoff_cmp, .se_sort = sort__symoff_sort, @@ -605,7 +725,7 @@ static int hist_entry__srcline_from_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*.*s", width, width, he->branch_info->srcline_from); } -struct sort_entry sort_srcline_from = { +static struct sort_entry sort_srcline_from = { .se_header = "From Source:Line", .se_cmp = sort__srcline_from_cmp, .se_collapse = sort__srcline_from_collapse, @@ -653,7 +773,7 @@ static int hist_entry__srcline_to_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*.*s", width, width, he->branch_info->srcline_to); } -struct sort_entry sort_srcline_to = { +static struct sort_entry sort_srcline_to = { .se_header = "To Source:Line", .se_cmp = sort__srcline_to_cmp, .se_collapse = sort__srcline_to_collapse, @@ -689,7 +809,7 @@ static int hist_entry__sym_ipc_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*s", width, tmp); } -struct sort_entry sort_sym_ipc = { +static struct sort_entry sort_sym_ipc = { .se_header = "IPC [IPC Coverage]", .se_cmp = sort__sym_cmp, .se_snprintf = hist_entry__sym_ipc_snprintf, @@ -707,7 +827,7 @@ static int hist_entry__sym_ipc_null_snprintf(struct hist_entry *he return repsep_snprintf(bf, size, "%-*s", width, tmp); } -struct sort_entry sort_sym_ipc_null = { +static struct sort_entry sort_sym_ipc_null = { .se_header = "IPC [IPC Coverage]", .se_cmp = sort__sym_cmp, .se_snprintf = hist_entry__sym_ipc_null_snprintf, @@ -740,7 +860,7 @@ static int hist_entry__callchain_branch_predicted_snprintf( return repsep_snprintf(bf, size, "%-*.*s", width, width, str); } -struct sort_entry sort_callchain_branch_predicted = { +static struct sort_entry sort_callchain_branch_predicted = { .se_header = "Predicted", .se_cmp = sort__callchain_branch_predicted_cmp, .se_snprintf = hist_entry__callchain_branch_predicted_snprintf, @@ -770,7 +890,7 @@ static int hist_entry__callchain_branch_abort_snprintf(struct hist_entry *he, return repsep_snprintf(bf, size, "%-*.*s", width, width, str); } -struct sort_entry sort_callchain_branch_abort = { +static struct sort_entry sort_callchain_branch_abort = { .se_header = "Abort", .se_cmp = sort__callchain_branch_abort_cmp, .se_snprintf = hist_entry__callchain_branch_abort_snprintf, @@ -803,7 +923,7 @@ static int hist_entry__callchain_branch_cycles_snprintf(struct hist_entry *he, return repsep_snprintf(bf, size, "%-*.*s", width, width, str); } -struct sort_entry sort_callchain_branch_cycles = { +static struct sort_entry sort_callchain_branch_cycles = { .se_header = "Cycles", .se_cmp = sort__callchain_branch_cycles_cmp, .se_snprintf = hist_entry__callchain_branch_cycles_snprintf, @@ -870,7 +990,7 @@ static int hist_entry__srcfile_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-.*s", width, he->srcfile); } -struct sort_entry sort_srcfile = { +static struct sort_entry sort_srcfile = { .se_header = "Source File", .se_cmp = sort__srcfile_cmp, .se_collapse = sort__srcfile_collapse, @@ -922,7 +1042,7 @@ static int hist_entry__cpu_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%*.*d", width, width, he->cpu); } -struct sort_entry sort_cpu = { +static struct sort_entry sort_cpu = { .se_header = "CPU", .se_cmp = sort__cpu_cmp, .se_snprintf = hist_entry__cpu_snprintf, @@ -953,7 +1073,7 @@ static int hist_entry__parallelism_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%*d", width, he->parallelism); } -struct sort_entry sort_parallelism = { +static struct sort_entry sort_parallelism = { .se_header = "Parallelism", .se_cmp = sort__parallelism_cmp, .se_filter = hist_entry__parallelism_filter, @@ -994,7 +1114,7 @@ static int hist_entry__cgroup_id_snprintf(struct hist_entry *he, he->cgroup_id.ino); } -struct sort_entry sort_cgroup_id = { +static struct sort_entry sort_cgroup_id = { .se_header = "cgroup id (dev/inode)", .se_cmp = sort__cgroup_id_cmp, .se_snprintf = hist_entry__cgroup_id_snprintf, @@ -1027,7 +1147,7 @@ static int hist_entry__cgroup_snprintf(struct hist_entry *he, return repsep_snprintf(bf, size, "%s", cgrp_name); } -struct sort_entry sort_cgroup = { +static struct sort_entry sort_cgroup = { .se_header = "Cgroup", .se_cmp = sort__cgroup_cmp, .se_snprintf = hist_entry__cgroup_snprintf, @@ -1058,7 +1178,7 @@ static int hist_entry__socket_filter(struct hist_entry *he, int type, const void return sk >= 0 && he->socket != sk; } -struct sort_entry sort_socket = { +static struct sort_entry sort_socket = { .se_header = "Socket", .se_cmp = sort__socket_cmp, .se_snprintf = hist_entry__socket_snprintf, @@ -1089,7 +1209,7 @@ static int hist_entry__time_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-.*s", width, he_time); } -struct sort_entry sort_time = { +static struct sort_entry sort_time = { .se_header = "Time", .se_cmp = sort__time_cmp, .se_snprintf = hist_entry__time_snprintf, @@ -1158,7 +1278,7 @@ static int hist_entry__trace_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-.*s", width, he->trace_output); } -struct sort_entry sort_trace = { +static struct sort_entry sort_trace = { .se_header = "Trace output", .se_cmp = sort__trace_cmp, .se_snprintf = hist_entry__trace_snprintf, @@ -1453,7 +1573,7 @@ sort__addr_to_cmp(struct hist_entry *left, struct hist_entry *right) return _sort__addr_cmp(to_l->addr, to_r->addr); } -struct sort_entry sort_addr_from = { +static struct sort_entry sort_addr_from = { .se_header = "Source Address", .se_cmp = sort__addr_from_cmp, .se_snprintf = hist_entry__addr_from_snprintf, @@ -1461,7 +1581,7 @@ struct sort_entry sort_addr_from = { .se_width_idx = HISTC_ADDR_FROM, }; -struct sort_entry sort_addr_to = { +static struct sort_entry sort_addr_to = { .se_header = "Target Address", .se_cmp = sort__addr_to_cmp, .se_snprintf = hist_entry__addr_to_snprintf, @@ -1518,7 +1638,7 @@ static int hist_entry__cycles_snprintf(struct hist_entry *he, char *bf, he->branch_info->flags.cycles); } -struct sort_entry sort_cycles = { +static struct sort_entry sort_cycles = { .se_header = "Basic Block Cycles", .se_cmp = sort__cycles_cmp, .se_snprintf = hist_entry__cycles_snprintf, @@ -1808,7 +1928,7 @@ static int hist_entry__dcacheline_snprintf(struct hist_entry *he, char *bf, return _hist_entry__sym_snprintf(ms, addr, level, bf, size, width); } -struct sort_entry sort_mispredict = { +static struct sort_entry sort_mispredict = { .se_header = "Branch Mispredicted", .se_cmp = sort__mispredict_cmp, .se_snprintf = hist_entry__mispredict_snprintf, @@ -1827,7 +1947,7 @@ static int hist_entry__local_weight_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*llu", width, he->weight); } -struct sort_entry sort_local_weight = { +static struct sort_entry sort_local_weight = { .se_header = "Local Weight", .se_cmp = sort__weight_cmp, .se_snprintf = hist_entry__local_weight_snprintf, @@ -1841,7 +1961,7 @@ static int hist_entry__global_weight_snprintf(struct hist_entry *he, char *bf, he->weight * he->stat.nr_events); } -struct sort_entry sort_global_weight = { +static struct sort_entry sort_global_weight = { .se_header = "Weight", .se_cmp = sort__weight_cmp, .se_snprintf = hist_entry__global_weight_snprintf, @@ -1860,7 +1980,7 @@ static int hist_entry__local_ins_lat_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*u", width, he->ins_lat); } -struct sort_entry sort_local_ins_lat = { +static struct sort_entry sort_local_ins_lat = { .se_header = "Local INSTR Latency", .se_cmp = sort__ins_lat_cmp, .se_snprintf = hist_entry__local_ins_lat_snprintf, @@ -1874,7 +1994,7 @@ static int hist_entry__global_ins_lat_snprintf(struct hist_entry *he, char *bf, he->ins_lat * he->stat.nr_events); } -struct sort_entry sort_global_ins_lat = { +static struct sort_entry sort_global_ins_lat = { .se_header = "INSTR Latency", .se_cmp = sort__ins_lat_cmp, .se_snprintf = hist_entry__global_ins_lat_snprintf, @@ -1900,70 +2020,70 @@ static int hist_entry__p_stage_cyc_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*u", width, he->weight3); } -struct sort_entry sort_local_p_stage_cyc = { +static struct sort_entry sort_local_p_stage_cyc = { .se_header = "Local Pipeline Stage Cycle", .se_cmp = sort__p_stage_cyc_cmp, .se_snprintf = hist_entry__p_stage_cyc_snprintf, .se_width_idx = HISTC_LOCAL_P_STAGE_CYC, }; -struct sort_entry sort_global_p_stage_cyc = { +static struct sort_entry sort_global_p_stage_cyc = { .se_header = "Pipeline Stage Cycle", .se_cmp = sort__p_stage_cyc_cmp, .se_snprintf = hist_entry__global_p_stage_cyc_snprintf, .se_width_idx = HISTC_GLOBAL_P_STAGE_CYC, }; -struct sort_entry sort_mem_daddr_sym = { +static struct sort_entry sort_mem_daddr_sym = { .se_header = "Data Symbol", .se_cmp = sort__daddr_cmp, .se_snprintf = hist_entry__daddr_snprintf, .se_width_idx = HISTC_MEM_DADDR_SYMBOL, }; -struct sort_entry sort_mem_iaddr_sym = { +static struct sort_entry sort_mem_iaddr_sym = { .se_header = "Code Symbol", .se_cmp = sort__iaddr_cmp, .se_snprintf = hist_entry__iaddr_snprintf, .se_width_idx = HISTC_MEM_IADDR_SYMBOL, }; -struct sort_entry sort_mem_daddr_dso = { +static struct sort_entry sort_mem_daddr_dso = { .se_header = "Data Object", .se_cmp = sort__dso_daddr_cmp, .se_snprintf = hist_entry__dso_daddr_snprintf, .se_width_idx = HISTC_MEM_DADDR_DSO, }; -struct sort_entry sort_mem_locked = { +static struct sort_entry sort_mem_locked = { .se_header = "Locked", .se_cmp = sort__locked_cmp, .se_snprintf = hist_entry__locked_snprintf, .se_width_idx = HISTC_MEM_LOCKED, }; -struct sort_entry sort_mem_tlb = { +static struct sort_entry sort_mem_tlb = { .se_header = "TLB access", .se_cmp = sort__tlb_cmp, .se_snprintf = hist_entry__tlb_snprintf, .se_width_idx = HISTC_MEM_TLB, }; -struct sort_entry sort_mem_lvl = { +static struct sort_entry sort_mem_lvl = { .se_header = "Memory access", .se_cmp = sort__lvl_cmp, .se_snprintf = hist_entry__lvl_snprintf, .se_width_idx = HISTC_MEM_LVL, }; -struct sort_entry sort_mem_snoop = { +static struct sort_entry sort_mem_snoop = { .se_header = "Snoop", .se_cmp = sort__snoop_cmp, .se_snprintf = hist_entry__snoop_snprintf, .se_width_idx = HISTC_MEM_SNOOP, }; -struct sort_entry sort_mem_dcacheline = { +static struct sort_entry sort_mem_dcacheline = { .se_header = "Data Cacheline", .se_cmp = sort__dcacheline_cmp, .se_snprintf = hist_entry__dcacheline_snprintf, @@ -1998,7 +2118,7 @@ static int hist_entry__blocked_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%.*s", width, out); } -struct sort_entry sort_mem_blocked = { +static struct sort_entry sort_mem_blocked = { .se_header = "Blocked", .se_cmp = sort__blocked_cmp, .se_snprintf = hist_entry__blocked_snprintf, @@ -2039,7 +2159,7 @@ static int hist_entry__phys_daddr_snprintf(struct hist_entry *he, char *bf, return width; } -struct sort_entry sort_mem_phys_daddr = { +static struct sort_entry sort_mem_phys_daddr = { .se_header = "Data Physical Address", .se_cmp = sort__phys_daddr_cmp, .se_snprintf = hist_entry__phys_daddr_snprintf, @@ -2068,7 +2188,7 @@ static int hist_entry__data_page_size_snprintf(struct hist_entry *he, char *bf, get_page_size_name(mem_info__daddr(he->mem_info)->data_page_size, str)); } -struct sort_entry sort_mem_data_page_size = { +static struct sort_entry sort_mem_data_page_size = { .se_header = "Data Page Size", .se_cmp = sort__data_page_size_cmp, .se_snprintf = hist_entry__data_page_size_snprintf, @@ -2093,7 +2213,7 @@ static int hist_entry__code_page_size_snprintf(struct hist_entry *he, char *bf, get_page_size_name(he->code_page_size, str)); } -struct sort_entry sort_code_page_size = { +static struct sort_entry sort_code_page_size = { .se_header = "Code Page Size", .se_cmp = sort__code_page_size_cmp, .se_snprintf = hist_entry__code_page_size_snprintf, @@ -2125,7 +2245,7 @@ static int hist_entry__abort_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*s", width, out); } -struct sort_entry sort_abort = { +static struct sort_entry sort_abort = { .se_header = "Transaction abort", .se_cmp = sort__abort_cmp, .se_snprintf = hist_entry__abort_snprintf, @@ -2157,7 +2277,7 @@ static int hist_entry__in_tx_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*s", width, out); } -struct sort_entry sort_in_tx = { +static struct sort_entry sort_in_tx = { .se_header = "Branch in transaction", .se_cmp = sort__in_tx_cmp, .se_snprintf = hist_entry__in_tx_snprintf, @@ -2229,7 +2349,7 @@ static int hist_entry__transaction_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*s", width, buf); } -struct sort_entry sort_transaction = { +static struct sort_entry sort_transaction = { .se_header = "Transaction ", .se_cmp = sort__transaction_cmp, .se_snprintf = hist_entry__transaction_snprintf, @@ -2268,7 +2388,7 @@ static int hist_entry__sym_size_snprintf(struct hist_entry *he, char *bf, return _hist_entry__sym_size_snprintf(he->ms.sym, bf, size, width); } -struct sort_entry sort_sym_size = { +static struct sort_entry sort_sym_size = { .se_header = "Symbol size", .se_cmp = sort__sym_size_cmp, .se_snprintf = hist_entry__sym_size_snprintf, @@ -2307,7 +2427,7 @@ static int hist_entry__dso_size_snprintf(struct hist_entry *he, char *bf, return _hist_entry__dso_size_snprintf(he->ms.map, bf, size, width); } -struct sort_entry sort_dso_size = { +static struct sort_entry sort_dso_size = { .se_header = "DSO size", .se_cmp = sort__dso_size_cmp, .se_snprintf = hist_entry__dso_size_snprintf, @@ -2344,7 +2464,7 @@ static int hist_entry__addr_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-#*llx", width, ip); } -struct sort_entry sort_addr = { +static struct sort_entry sort_addr = { .se_header = "Address", .se_cmp = sort__addr_cmp, .se_snprintf = hist_entry__addr_snprintf, @@ -2462,7 +2582,7 @@ static int hist_entry__typeoff_snprintf(struct hist_entry *he, char *bf, he->mem_type_off, buf); } -struct sort_entry sort_type_offset = { +static struct sort_entry sort_type_offset = { .se_header = "Data Type Offset", .se_cmp = sort__type_cmp, .se_collapse = sort__typeoff_sort, @@ -2474,7 +2594,26 @@ struct sort_entry sort_type_offset = { /* --sort typecln */ -#define DEFAULT_CACHELINE_SIZE 64 +static int +hist_entry__cln_size(struct hist_entry *he) +{ + int ret = 0; + + if (he && he->hists) { + struct evsel *evsel = hists_to_evsel(he->hists); + + if (evsel) { + struct perf_session *session = evsel__session(evsel); + + ret = session->header.env.cln_size; + } + } + + if (ret < 1) + ret = DEFAULT_CACHELINE_SIZE; // avoid div/0 later + + return ret; +} static int64_t sort__typecln_sort(struct hist_entry *left, struct hist_entry *right) @@ -2482,11 +2621,9 @@ sort__typecln_sort(struct hist_entry *left, struct hist_entry *right) struct annotated_data_type *left_type = left->mem_type; struct annotated_data_type *right_type = right->mem_type; int64_t left_cln, right_cln; + int64_t cln_size_left = hist_entry__cln_size(left); + int64_t cln_size_right = hist_entry__cln_size(right); int64_t ret; - int cln_size = cacheline_size(); - - if (cln_size == 0) - cln_size = DEFAULT_CACHELINE_SIZE; if (!left_type) { sort__type_init(left); @@ -2502,8 +2639,8 @@ sort__typecln_sort(struct hist_entry *left, struct hist_entry *right) if (ret) return ret; - left_cln = left->mem_type_off / cln_size; - right_cln = right->mem_type_off / cln_size; + left_cln = left->mem_type_off / cln_size_left; + right_cln = right->mem_type_off / cln_size_right; return left_cln - right_cln; } @@ -2511,16 +2648,13 @@ static int hist_entry__typecln_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width __maybe_unused) { struct annotated_data_type *he_type = he->mem_type; - int cln_size = cacheline_size(); - - if (cln_size == 0) - cln_size = DEFAULT_CACHELINE_SIZE; + int cln_size = hist_entry__cln_size(he); return repsep_snprintf(bf, size, "%s: cache-line %d", he_type->self.type_name, he->mem_type_off / cln_size); } -struct sort_entry sort_type_cacheline = { +static struct sort_entry sort_type_cacheline = { .se_header = "Data Type Cacheline", .se_cmp = sort__type_cmp, .se_collapse = sort__typecln_sort, @@ -2583,6 +2717,7 @@ static struct sort_dimension common_sort_dimensions[] = { DIM(SORT_PID, "pid", sort_thread), DIM(SORT_TGID, "tgid", sort_tgid), DIM(SORT_COMM, "comm", sort_comm), + DIM(SORT_COMM_NODIGIT, "comm_nodigit", sort_comm_nodigit), DIM(SORT_DSO, "dso", sort_dso), DIM(SORT_SYM, "symbol", sort_sym), DIM(SORT_PARENT, "parent", sort_parent), @@ -3579,6 +3714,8 @@ static int __sort_dimension__update(struct sort_dimension *sd, list->thread = 1; } else if (sd->entry == &sort_comm) { list->comm = 1; + } else if (sd->entry == &sort_comm_nodigit) { + list->comm_nodigit = list->comm = 1; } else if (sd->entry == &sort_type_offset) { symbol_conf.annotate_data_member = true; } else if (sd->entry == &sort_sym_from || sd->entry == &sort_sym_to) { @@ -4040,6 +4177,7 @@ static bool get_elide(int idx, FILE *output) case HISTC_DSO: return __get_elide(symbol_conf.dso_list, "dso", output); case HISTC_COMM: + case HISTC_COMM_NODIGIT: return __get_elide(symbol_conf.comm_list, "comm", output); default: break; diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index d7787958e06b..c962e77e4b93 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -43,6 +43,7 @@ enum sort_type { /* common sort keys */ SORT_PID, SORT_COMM, + SORT_COMM_NODIGIT, SORT_DSO, SORT_SYM, SORT_PARENT, @@ -158,4 +159,5 @@ sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right); int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r); char *hist_entry__srcline(struct hist_entry *he); +size_t sort__comm_nodigit_len(struct hist_entry *entry); #endif /* __PERF_SORT_H */ diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index 9be42f398440..db164d258163 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -8,10 +8,12 @@ #include "symbol.h" #include "libdw.h" #include "debug.h" +#include "util.h" #include #include #include +#include bool srcline_full_filename; @@ -73,14 +75,6 @@ int inline_list__append_tail(struct symbol *symbol, char *srcline, struct inline return 0; } -/* basename version that takes a const input string */ -static const char *gnu_basename(const char *path) -{ - const char *base = strrchr(path, '/'); - - return base ? base + 1 : path; -} - char *srcline_from_fileline(const char *file, unsigned int line) { char *srcline; @@ -89,7 +83,7 @@ char *srcline_from_fileline(const char *file, unsigned int line) return NULL; if (!srcline_full_filename) - file = gnu_basename(file); + file = perf_basename(file); if (asprintf(&srcline, "%s:%u", file, line) < 0) return NULL; diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c index dc2b66855f6c..993f4c4b8f44 100644 --- a/tools/perf/util/stat-display.c +++ b/tools/perf/util/stat-display.c @@ -897,7 +897,7 @@ static bool should_skip_zero_counter(struct perf_stat_config *config, const struct aggr_cpu_id *id) { struct perf_cpu cpu; - int idx; + unsigned int idx; /* * Skip unsupported default events when not verbose. (default events @@ -1125,7 +1125,7 @@ static void print_no_aggr_metric(struct perf_stat_config *config, struct evlist *evlist, struct outstate *os) { - int all_idx; + unsigned int all_idx; struct perf_cpu cpu; perf_cpu_map__for_each_cpu(cpu, all_idx, evlist->core.user_requested_cpus) { diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c index 59d2cd4f2188..bc2d44df7baf 100644 --- a/tools/perf/util/stat-shadow.c +++ b/tools/perf/util/stat-shadow.c @@ -13,7 +13,6 @@ #include "metricgroup.h" #include "cgroup.h" #include "units.h" -#include #include "iostat.h" #include "util/hashmap.h" #include "tool_pmu.h" diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c index 976a06e63252..14d169e22e8f 100644 --- a/tools/perf/util/stat.c +++ b/tools/perf/util/stat.c @@ -246,9 +246,11 @@ void evlist__reset_prev_raw_counts(struct evlist *evlist) static void evsel__copy_prev_raw_counts(struct evsel *evsel) { - int idx, nthreads = perf_thread_map__nr(evsel->core.threads); + int nthreads = perf_thread_map__nr(evsel->core.threads); for (int thread = 0; thread < nthreads; thread++) { + unsigned int idx; + perf_cpu_map__for_each_idx(idx, evsel__cpus(evsel)) { *perf_counts(evsel->counts, idx, thread) = *perf_counts(evsel->prev_raw_counts, idx, thread); @@ -580,7 +582,7 @@ static void evsel__update_percore_stats(struct evsel *evsel, struct aggr_cpu_id struct perf_counts_values counts = { 0, }; struct aggr_cpu_id id; struct perf_cpu cpu; - int idx; + unsigned int idx; /* collect per-core counts */ perf_cpu_map__for_each_cpu(cpu, idx, evsel->core.cpus) { @@ -617,7 +619,7 @@ static void evsel__process_percore(struct evsel *evsel) struct perf_stat_evsel *ps = evsel->stats; struct aggr_cpu_id core_id; struct perf_cpu cpu; - int idx; + unsigned int idx; if (!evsel->percore) return; diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c index b1d259f590e9..e360e7736c7b 100644 --- a/tools/perf/util/svghelper.c +++ b/tools/perf/util/svghelper.c @@ -726,7 +726,8 @@ static void scan_core_topology(int *map, struct topology *t, int nr_cpus) static int str_to_bitmap(char *s, cpumask_t *b, int nr_cpus) { - int idx, ret = 0; + unsigned int idx; + int ret = 0; struct perf_cpu_map *map; struct perf_cpu cpu; diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 76912c62b6a0..7afa8a117139 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -372,10 +372,8 @@ static bool get_plt_sizes(struct dso *dso, GElf_Ehdr *ehdr, GElf_Shdr *shdr_plt, *plt_entry_size = 12; return true; case EM_AARCH64: - *plt_header_size = 32; - *plt_entry_size = 16; - return true; case EM_LOONGARCH: + case EM_RISCV: *plt_header_size = 32; *plt_entry_size = 16; return true; @@ -1054,15 +1052,15 @@ void symsrc__destroy(struct symsrc *ss) close(ss->fd); } -bool elf__needs_adjust_symbols(GElf_Ehdr ehdr) +static bool elf__needs_adjust_symbols(const GElf_Ehdr *ehdr) { /* * Usually vmlinux is an ELF file with type ET_EXEC for most * architectures; except Arm64 kernel is linked with option * '-share', so need to check type ET_DYN. */ - return ehdr.e_type == ET_EXEC || ehdr.e_type == ET_REL || - ehdr.e_type == ET_DYN; + return ehdr->e_type == ET_EXEC || ehdr->e_type == ET_REL || + ehdr->e_type == ET_DYN; } static Elf *read_gnu_debugdata(struct dso *dso, Elf *elf, const char *name, int *fd_ret) @@ -1235,7 +1233,7 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name, if (dso__kernel(dso) == DSO_SPACE__USER) ss->adjust_symbols = true; else - ss->adjust_symbols = elf__needs_adjust_symbols(ehdr); + ss->adjust_symbols = elf__needs_adjust_symbols(&ehdr); ss->name = strdup(name); if (!ss->name) { @@ -1356,8 +1354,12 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map, char dso_name[PATH_MAX]; /* Adjust symbol to map to file offset */ - if (adjust_kernel_syms) - sym->st_value -= shdr->sh_addr - shdr->sh_offset; + if (adjust_kernel_syms) { + if (dso__rel(dso)) + sym->st_value += shdr->sh_offset; + else + sym->st_value -= shdr->sh_addr - shdr->sh_offset; + } if (strcmp(section_name, (dso__short_name(curr_dso) + dso__short_name_len(dso))) == 0) return 0; diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 8662001e1e25..fcaeeddbbb6b 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -26,7 +26,6 @@ #include "demangle-rust-v0.h" #include "dso.h" #include "util.h" // lsdir() -#include "debug.h" #include "event.h" #include "machine.h" #include "map.h" @@ -66,9 +65,11 @@ struct symbol_conf symbol_conf = { .time_quantum = 100 * NSEC_PER_MSEC, /* 100ms */ .show_hist_headers = true, .symfs = "", + .symfs_layout_flat = false, .event_group = true, .inline_name = true, .res_sample = 0, + .addr2line_timeout_ms = 5 * 1000, }; struct map_list_node { @@ -2363,7 +2364,8 @@ static int setup_parallelism_bitmap(void) { struct perf_cpu_map *map; struct perf_cpu cpu; - int i, err = -1; + unsigned int i; + int err = -1; if (symbol_conf.parallelism_list_str == NULL) return 0; @@ -2491,16 +2493,42 @@ int symbol__config_symfs(const struct option *opt __maybe_unused, const char *dir, int unset __maybe_unused) { char *bf = NULL; + const char *layout_str; + char *dir_copy; int ret; - symbol_conf.symfs = strdup(dir); - if (symbol_conf.symfs == NULL) - return -ENOMEM; + layout_str = strrchr(dir, ','); + if (layout_str) { + size_t dir_len = layout_str - dir; + + dir_copy = strndup(dir, dir_len); + if (dir_copy == NULL) + return -ENOMEM; + + symbol_conf.symfs = dir_copy; + + layout_str++; + if (!strcmp(layout_str, "flat")) + symbol_conf.symfs_layout_flat = true; + else if (!strcmp(layout_str, "hierarchy")) + symbol_conf.symfs_layout_flat = false; + else { + pr_err("Invalid layout: '%s', use 'hierarchy' or 'flat'\n", + layout_str); + free(dir_copy); + return -EINVAL; + } + } else { + symbol_conf.symfs = strdup(dir); + if (symbol_conf.symfs == NULL) + return -ENOMEM; + symbol_conf.symfs_layout_flat = false; + } /* skip the locally configured cache if a symfs is given, and * config buildid dir to symfs/.debug */ - ret = asprintf(&bf, "%s/%s", dir, ".debug"); + ret = asprintf(&bf, "%s/%s", symbol_conf.symfs, ".debug"); if (ret < 0) return -ENOMEM; diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 3fb5d146d9b1..bd6eb90c8668 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -9,10 +9,12 @@ #include #include #include +#include #include "addr_location.h" #include "path.h" #include "symbol_conf.h" #include "spark.h" +#include "util.h" #ifdef HAVE_LIBELF_SUPPORT #include @@ -96,6 +98,9 @@ struct intlist; static inline int __symbol__join_symfs(char *bf, size_t size, const char *path) { + if (symbol_conf.symfs_layout_flat) + return path__join(bf, size, symbol_conf.symfs, perf_basename(path)); + return path__join(bf, size, symbol_conf.symfs, path); } @@ -169,6 +174,11 @@ size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp); size_t symbol__fprintf(struct symbol *sym, FILE *fp); bool symbol__restricted_filename(const char *filename, const char *restricted_filename); + +#define SYMFS_HELP "setup root directory which contains debug files:\n" \ + "\t\t\t\t" "directory:\tLook for files with symbols relative to this directory.\n" \ + "\t\t\t\t" "layout: \tLayout of files, 'hierarchy' matches full path (default), 'flat' only matches base name.\n" + int symbol__config_symfs(const struct option *opt __maybe_unused, const char *dir, int unset __maybe_unused); @@ -217,7 +227,6 @@ int setup_intlist(struct intlist **list, const char *list_str, const char *list_name); #ifdef HAVE_LIBELF_SUPPORT -bool elf__needs_adjust_symbols(GElf_Ehdr ehdr); void arch__sym_update(struct symbol *s, GElf_Sym *sym); #endif diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h index 71bb17372a6c..6cd454d7c98e 100644 --- a/tools/perf/util/symbol_conf.h +++ b/tools/perf/util/symbol_conf.h @@ -51,7 +51,7 @@ struct symbol_conf { report_block, report_individual_block, inline_name, - disable_add2line_warn, + addr2line_disable_warn, no_buildid_mmap2, guest_code, lazy_load_kernel_maps, @@ -80,6 +80,7 @@ struct symbol_conf { *bt_stop_list_str; const char *addr2line_path; enum a2l_style addr2line_style[MAX_A2L_STYLE]; + int addr2line_timeout_ms; unsigned long time_quantum; struct strlist *dso_list, *comm_list, @@ -93,6 +94,7 @@ struct symbol_conf { *tid_list, *addr_list; const char *symfs; + bool symfs_layout_flat; int res_sample; int pad_output_len_dso; int group_sort_idx; diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c index ddf1cbda1902..85bee747f4cd 100644 --- a/tools/perf/util/synthetic-events.c +++ b/tools/perf/util/synthetic-events.c @@ -1266,7 +1266,7 @@ static void synthesize_cpus(struct synthesize_cpu_map_data *data) static void synthesize_mask(struct synthesize_cpu_map_data *data) { - int idx; + unsigned int idx; struct perf_cpu cpu; /* Due to padding, the 4bytes per entry mask variant is always smaller. */ diff --git a/tools/perf/util/target.h b/tools/perf/util/target.h index 84ebb9c940c6..bc2bff9c6842 100644 --- a/tools/perf/util/target.h +++ b/tools/perf/util/target.h @@ -49,22 +49,22 @@ uid_t parse_uid(const char *str); int target__strerror(struct target *target, int errnum, char *buf, size_t buflen); -static inline bool target__has_task(struct target *target) +static inline bool target__has_task(const struct target *target) { return target->tid || target->pid; } -static inline bool target__has_cpu(struct target *target) +static inline bool target__has_cpu(const struct target *target) { return target->system_wide || target->cpu_list; } -static inline bool target__none(struct target *target) +static inline bool target__none(const struct target *target) { return !target__has_task(target) && !target__has_cpu(target); } -static inline bool target__enable_on_exec(struct target *target) +static inline bool target__enable_on_exec(const struct target *target) { /* * Normally enable_on_exec should be set if: @@ -75,12 +75,12 @@ static inline bool target__enable_on_exec(struct target *target) return target__none(target) && !target->initial_delay; } -static inline bool target__has_per_thread(struct target *target) +static inline bool target__has_per_thread(const struct target *target) { return target->system_wide && target->per_thread; } -static inline bool target__uses_dummy_map(struct target *target) +static inline bool target__uses_dummy_map(const struct target *target) { bool use_dummy = false; diff --git a/tools/perf/util/unwind-libunwind-local.c b/tools/perf/util/unwind-libunwind-local.c index 5b39ce21e333..87d496e9dfa6 100644 --- a/tools/perf/util/unwind-libunwind-local.c +++ b/tools/perf/util/unwind-libunwind-local.c @@ -25,7 +25,6 @@ #include #include #include -#include #ifndef REMOTE_UNWIND_LIBUNWIND #include #include diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 8b893de35f77..25849434f0a4 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -77,8 +77,6 @@ bool sysctl__nmi_watchdog_enabled(void) return nmi_watchdog; } -bool test_attr__enabled; - bool exclude_GH_default; bool perf_host = true; @@ -547,3 +545,11 @@ int scandirat(int dirfd, const char *dirp, return err; } #endif + +/* basename version that takes a const input string */ +const char *perf_basename(const char *path) +{ + const char *base = strrchr(path, '/'); + + return base ? base + 1 : path; +} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 394dbfa944ac..87a0818a8c76 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -30,7 +30,6 @@ extern bool perf_guest; /* General helper functions */ void usage(const char *err) __noreturn; -void die(const char *err, ...) __noreturn __printf(1, 2); struct dirent; struct strlist; @@ -87,6 +86,8 @@ struct perf_debuginfod { }; void perf_debuginfod_setup(struct perf_debuginfod *di); +const char *perf_basename(const char *path); + char *filename_with_chroot(int pid, const char *filename); int do_realloc_array_as_needed(void **arr, size_t *arr_sz, size_t x, diff --git a/tools/perf/util/values.c b/tools/perf/util/values.c index ec72d29f3d58..6eaddfcf833e 100644 --- a/tools/perf/util/values.c +++ b/tools/perf/util/values.c @@ -13,9 +13,9 @@ int perf_read_values_init(struct perf_read_values *values) { values->threads_max = 16; - values->pid = malloc(values->threads_max * sizeof(*values->pid)); - values->tid = malloc(values->threads_max * sizeof(*values->tid)); - values->value = zalloc(values->threads_max * sizeof(*values->value)); + values->pid = calloc(values->threads_max, sizeof(*values->pid)); + values->tid = calloc(values->threads_max, sizeof(*values->tid)); + values->value = calloc(values->threads_max, sizeof(*values->value)); if (!values->pid || !values->tid || !values->value) { pr_debug("failed to allocate read_values threads arrays"); goto out_free_pid; @@ -96,7 +96,7 @@ static int perf_read_values__findnew_thread(struct perf_read_values *values, i = values->threads; - values->value[i] = zalloc(values->counters_max * sizeof(**values->value)); + values->value[i] = calloc(values->counters_max, sizeof(**values->value)); if (!values->value[i]) { pr_debug("failed to allocate read_values counters array"); return -ENOMEM; diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild index 53d84a6874b7..2be1df80fcc9 100644 --- a/tools/testing/cxl/Kbuild +++ b/tools/testing/cxl/Kbuild @@ -11,8 +11,12 @@ ldflags-y += --wrap=devm_cxl_endpoint_decoders_setup ldflags-y += --wrap=hmat_get_extended_linear_cache_size ldflags-y += --wrap=devm_cxl_add_dport_by_dev ldflags-y += --wrap=devm_cxl_switch_port_decoders_setup +ldflags-y += --wrap=walk_hmem_resources +ldflags-y += --wrap=region_intersects +ldflags-y += --wrap=region_intersects_soft_reserve DRIVERS := ../../../drivers +DAX_HMEM_SRC := $(DRIVERS)/dax/hmem CXL_SRC := $(DRIVERS)/cxl CXL_CORE_SRC := $(DRIVERS)/cxl/core ccflags-y := -I$(srctree)/drivers/cxl/ @@ -59,7 +63,7 @@ cxl_core-y += $(CXL_CORE_SRC)/hdm.o cxl_core-y += $(CXL_CORE_SRC)/pmu.o cxl_core-y += $(CXL_CORE_SRC)/cdat.o cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o -cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o +cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o $(CXL_CORE_SRC)/region_pmem.o $(CXL_CORE_SRC)/region_dax.o cxl_core-$(CONFIG_CXL_MCE) += $(CXL_CORE_SRC)/mce.o cxl_core-$(CONFIG_CXL_FEATURES) += $(CXL_CORE_SRC)/features.o cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += $(CXL_CORE_SRC)/edac.o @@ -70,6 +74,9 @@ cxl_core-y += config_check.o cxl_core-y += cxl_core_test.o cxl_core-y += cxl_core_exports.o +obj-m += dax_hmem.o +dax_hmem-y := $(DAX_HMEM_SRC)/hmem.o + KBUILD_CFLAGS := $(filter-out -Wmissing-prototypes -Wmissing-declarations, $(KBUILD_CFLAGS)) obj-m += test/ diff --git a/tools/testing/cxl/test/Kbuild b/tools/testing/cxl/test/Kbuild index af50972c8b6d..c168e3c998a7 100644 --- a/tools/testing/cxl/test/Kbuild +++ b/tools/testing/cxl/test/Kbuild @@ -7,6 +7,7 @@ obj-m += cxl_mock_mem.o obj-m += cxl_translate.o cxl_test-y := cxl.o +cxl_test-y += hmem_test.o cxl_mock-y := mock.o cxl_mock_mem-y := mem.o diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c index 81e2aef3627a..418669927fb0 100644 --- a/tools/testing/cxl/test/cxl.c +++ b/tools/testing/cxl/test/cxl.c @@ -16,6 +16,7 @@ static int interleave_arithmetic; static bool extended_linear_cache; +static bool fail_autoassemble; #define FAKE_QTG_ID 42 @@ -51,6 +52,31 @@ struct platform_device *cxl_mem_single[NR_MEM_SINGLE]; static struct platform_device *cxl_rch[NR_CXL_RCH]; static struct platform_device *cxl_rcd[NR_CXL_RCH]; +/* + * Decoder registry + * + * Record decoder programming so that the topology can be reconstructed + * after cxl_acpi unbind/bind. This allows a user-created region config + * to be replayed as if firmware had provided the region at enumeration + * time. + * + * Entries are keyed by a stable port identity (port->uport_dev) combined + * with the decoder id. Decoder state is saved at initialization and + * updated on commit and reset. + * + * On re-enumeration mock_init_hdm_decoder() consults this registry to + * restore enabled decoders. Disabled decoders are reinitialized to a + * clean default state rather than replaying stale programming. + */ +static DEFINE_XARRAY(decoder_registry); + +/* + * When set, decoder reset will not update the registry. This allows + * region destroy operations to reset live decoders without erasing + * the saved programming needed for replay after re-enumeration. + */ +static bool decoder_reset_preserve_registry; + static inline bool is_multi_bridge(struct device *dev) { int i; @@ -704,6 +730,194 @@ static int map_targets(struct device *dev, void *data) return 0; } +/* + * Build a stable registry key from the decoder's upstream port identity + * and decoder id. + * + * Decoder objects and cxl_port objects are reallocated on each enumeration, + * so their addresses cannot be used directly as replay keys. However, + * port->uport_dev is stable for a given topology across cxl_acpi unbind/bind + * in cxl_test, so use that as the port identity and pack the local decoder + * id into the low bits. + * + * The key is formed as: + * ((unsigned long)port->uport_dev << 4) | cxld->id + * + * The low bits hold the decoder id (which must fit in 4 bits) while + * the remaining bits identify the upstream port. This key is only used + * within cxl_test to locate saved decoder state during replay. + */ +static unsigned long cxld_registry_index(struct cxl_decoder *cxld) +{ + struct cxl_port *port = to_cxl_port(cxld->dev.parent); + + dev_WARN_ONCE(&port->dev, cxld->id >= 16, + "decoder id:%d out of range\n", cxld->id); + return (((unsigned long)port->uport_dev) << 4) | cxld->id; +} + +struct cxl_test_decoder { + union { + struct cxl_switch_decoder cxlsd; + struct cxl_endpoint_decoder cxled; + }; + struct range dpa_range; +}; + +static struct cxl_test_decoder *cxld_registry_find(struct cxl_decoder *cxld) +{ + return xa_load(&decoder_registry, cxld_registry_index(cxld)); +} + +#define dbg_cxld(port, msg, cxld) \ + do { \ + struct cxl_decoder *___d = (cxld); \ + dev_dbg((port)->uport_dev, \ + "decoder%d: %s range: %#llx-%#llx iw: %d ig: %d flags: %#lx\n", \ + ___d->id, msg, ___d->hpa_range.start, \ + ___d->hpa_range.end + 1, ___d->interleave_ways, \ + ___d->interleave_granularity, ___d->flags); \ + } while (0) + +static int mock_decoder_commit(struct cxl_decoder *cxld); +static void mock_decoder_reset(struct cxl_decoder *cxld); +static void init_disabled_mock_decoder(struct cxl_decoder *cxld); + +static void cxld_copy(struct cxl_decoder *a, struct cxl_decoder *b) +{ + a->id = b->id; + a->hpa_range = b->hpa_range; + a->interleave_ways = b->interleave_ways; + a->interleave_granularity = b->interleave_granularity; + a->target_type = b->target_type; + a->flags = b->flags; + a->commit = mock_decoder_commit; + a->reset = mock_decoder_reset; +} + +/* + * Restore decoder programming saved in the registry. + * + * Only decoders that were saved enabled are restored. Disabled decoders + * are left in their default inactive state so that stale programming is + * not resurrected after topology replay. + * + * For endpoint decoders this also restores the DPA reservation needed + * to reconstruct committed mappings. + */ +static int cxld_registry_restore(struct cxl_decoder *cxld, + struct cxl_test_decoder *td) +{ + struct cxl_port *port = to_cxl_port(cxld->dev.parent); + int rc; + + if (is_switch_decoder(&cxld->dev)) { + struct cxl_switch_decoder *cxlsd = + to_cxl_switch_decoder(&cxld->dev); + + if (!(td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE)) + return 0; + + dbg_cxld(port, "restore", &td->cxlsd.cxld); + cxld_copy(cxld, &td->cxlsd.cxld); + WARN_ON(cxlsd->nr_targets != td->cxlsd.nr_targets); + + /* Restore saved target intent; live dport binding happens later */ + for (int i = 0; i < cxlsd->nr_targets; i++) { + cxlsd->target[i] = NULL; + cxld->target_map[i] = td->cxlsd.cxld.target_map[i]; + } + + port->commit_end = cxld->id; + + } else { + struct cxl_endpoint_decoder *cxled = + to_cxl_endpoint_decoder(&cxld->dev); + + if (!(td->cxled.cxld.flags & CXL_DECODER_F_ENABLE)) + return 0; + + dbg_cxld(port, "restore", &td->cxled.cxld); + cxld_copy(cxld, &td->cxled.cxld); + cxled->state = td->cxled.state; + cxled->skip = td->cxled.skip; + if (range_len(&td->dpa_range)) { + rc = devm_cxl_dpa_reserve(cxled, td->dpa_range.start, + range_len(&td->dpa_range), + td->cxled.skip); + if (rc) { + init_disabled_mock_decoder(cxld); + return rc; + } + } + port->commit_end = cxld->id; + } + + return 0; +} + +static void __cxld_registry_save(struct cxl_test_decoder *td, + struct cxl_decoder *cxld) +{ + if (is_switch_decoder(&cxld->dev)) { + struct cxl_switch_decoder *cxlsd = + to_cxl_switch_decoder(&cxld->dev); + + cxld_copy(&td->cxlsd.cxld, cxld); + td->cxlsd.nr_targets = cxlsd->nr_targets; + + /* Save target port_id as a stable identify for the dport */ + for (int i = 0; i < cxlsd->nr_targets; i++) { + struct cxl_dport *dport; + + if (!cxlsd->target[i]) + continue; + + dport = cxlsd->target[i]; + td->cxlsd.cxld.target_map[i] = dport->port_id; + } + } else { + struct cxl_endpoint_decoder *cxled = + to_cxl_endpoint_decoder(&cxld->dev); + + cxld_copy(&td->cxled.cxld, cxld); + td->cxled.state = cxled->state; + td->cxled.skip = cxled->skip; + + if (!(cxld->flags & CXL_DECODER_F_ENABLE)) { + td->dpa_range.start = 0; + td->dpa_range.end = -1; + } else if (cxled->dpa_res) { + td->dpa_range.start = cxled->dpa_res->start; + td->dpa_range.end = cxled->dpa_res->end; + } else { + td->dpa_range.start = 0; + td->dpa_range.end = -1; + } + } +} + +static void cxld_registry_save(struct cxl_test_decoder *td, + struct cxl_decoder *cxld) +{ + struct cxl_port *port = to_cxl_port(cxld->dev.parent); + + dbg_cxld(port, "save", cxld); + __cxld_registry_save(td, cxld); +} + +static void cxld_registry_update(struct cxl_decoder *cxld) +{ + struct cxl_test_decoder *td = cxld_registry_find(cxld); + struct cxl_port *port = to_cxl_port(cxld->dev.parent); + + if (WARN_ON_ONCE(!td)) + return; + + dbg_cxld(port, "update", cxld); + __cxld_registry_save(td, cxld); +} + static int mock_decoder_commit(struct cxl_decoder *cxld) { struct cxl_port *port = to_cxl_port(cxld->dev.parent); @@ -723,6 +937,13 @@ static int mock_decoder_commit(struct cxl_decoder *cxld) port->commit_end++; cxld->flags |= CXL_DECODER_F_ENABLE; + if (is_endpoint_decoder(&cxld->dev)) { + struct cxl_endpoint_decoder *cxled = + to_cxl_endpoint_decoder(&cxld->dev); + + cxled->state = CXL_DECODER_STATE_AUTO; + } + cxld_registry_update(cxld); return 0; } @@ -743,6 +964,65 @@ static void mock_decoder_reset(struct cxl_decoder *cxld) "%s: out of order reset, expected decoder%d.%d\n", dev_name(&cxld->dev), port->id, port->commit_end); cxld->flags &= ~CXL_DECODER_F_ENABLE; + + if (is_endpoint_decoder(&cxld->dev)) { + struct cxl_endpoint_decoder *cxled = + to_cxl_endpoint_decoder(&cxld->dev); + + cxled->state = CXL_DECODER_STATE_MANUAL; + cxled->skip = 0; + } + if (decoder_reset_preserve_registry) + dev_dbg(port->uport_dev, "decoder%d: skip registry update\n", + cxld->id); + else + cxld_registry_update(cxld); +} + +static struct cxl_test_decoder *cxld_registry_new(struct cxl_decoder *cxld) +{ + struct cxl_test_decoder *td __free(kfree) = + kzalloc(sizeof(*td), GFP_KERNEL); + unsigned long key = cxld_registry_index(cxld); + + if (!td) + return NULL; + + if (xa_insert(&decoder_registry, key, td, GFP_KERNEL)) { + WARN_ON(1); + return NULL; + } + + cxld_registry_save(td, cxld); + return no_free_ptr(td); +} + +static void init_disabled_mock_decoder(struct cxl_decoder *cxld) +{ + cxld->hpa_range.start = 0; + cxld->hpa_range.end = -1; + cxld->interleave_ways = 1; + cxld->interleave_granularity = 0; + cxld->target_type = CXL_DECODER_HOSTONLYMEM; + cxld->flags = 0; + cxld->commit = mock_decoder_commit; + cxld->reset = mock_decoder_reset; + + if (is_switch_decoder(&cxld->dev)) { + struct cxl_switch_decoder *cxlsd = + to_cxl_switch_decoder(&cxld->dev); + + for (int i = 0; i < cxlsd->nr_targets; i++) { + cxlsd->target[i] = NULL; + cxld->target_map[i] = 0; + } + } else { + struct cxl_endpoint_decoder *cxled = + to_cxl_endpoint_decoder(&cxld->dev); + + cxled->state = CXL_DECODER_STATE_MANUAL; + cxled->skip = 0; + } } static void default_mock_decoder(struct cxl_decoder *cxld) @@ -757,6 +1037,8 @@ static void default_mock_decoder(struct cxl_decoder *cxld) cxld->target_type = CXL_DECODER_HOSTONLYMEM; cxld->commit = mock_decoder_commit; cxld->reset = mock_decoder_reset; + + WARN_ON_ONCE(!cxld_registry_new(cxld)); } static int first_decoder(struct device *dev, const void *data) @@ -771,13 +1053,29 @@ static int first_decoder(struct device *dev, const void *data) return 0; } -static void mock_init_hdm_decoder(struct cxl_decoder *cxld) +/* + * Initialize a decoder during HDM enumeration. + * + * If a saved registry entry exists: + * - enabled decoders are restored from the saved programming + * - disabled decoders are initialized in a clean disabled state + * + * If no registry entry exists the decoder follows the normal mock + * initialization path, including the special auto-region setup for + * the first endpoints under host-bridge0. + * + * Returns true if decoder state was restored from the registry. In + * that case the saved decode configuration (including target mapping) + * has already been applied and the map_targets() is skipped. + */ +static bool mock_init_hdm_decoder(struct cxl_decoder *cxld) { struct acpi_cedt_cfmws *window = mock_cfmws[0]; struct platform_device *pdev = NULL; struct cxl_endpoint_decoder *cxled; struct cxl_switch_decoder *cxlsd; struct cxl_port *port, *iter; + struct cxl_test_decoder *td; struct cxl_memdev *cxlmd; struct cxl_dport *dport; struct device *dev; @@ -804,6 +1102,24 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) port = NULL; } while (port); port = cxled_to_port(cxled); + } else { + port = to_cxl_port(cxld->dev.parent); + } + + td = cxld_registry_find(cxld); + if (td) { + bool enabled; + + if (is_switch_decoder(&cxld->dev)) + enabled = td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE; + else + enabled = td->cxled.cxld.flags & CXL_DECODER_F_ENABLE; + + if (enabled) + return !cxld_registry_restore(cxld, td); + + init_disabled_mock_decoder(cxld); + return false; } /* @@ -814,9 +1130,16 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) * * See 'cxl list -BMPu -m cxl_mem.0,cxl_mem.4' */ - if (!hb0 || pdev->id % 4 || pdev->id > 4 || cxld->id > 0) { + if (!is_endpoint_decoder(&cxld->dev) || !hb0 || pdev->id % 4 || + pdev->id > 4 || cxld->id > 0) { default_mock_decoder(cxld); - return; + return false; + } + + /* Simulate missing cxl_mem.4 configuration */ + if (hb0 && pdev->id == 4 && cxld->id == 0 && fail_autoassemble) { + default_mock_decoder(cxld); + return false; } base = window->base_hpa; @@ -838,6 +1161,7 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) cxld->commit = mock_decoder_commit; cxld->reset = mock_decoder_reset; + WARN_ON_ONCE(!cxld_registry_new(cxld)); /* * Now that endpoint decoder is set up, walk up the hierarchy * and setup the switch and root port decoders targeting @cxlmd. @@ -859,14 +1183,14 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) /* put cxl_mem.4 second in the decode order */ if (pdev->id == 4) { cxlsd->target[1] = dport; - cxld->target_map[1] = dport->port_id; + cxlsd->cxld.target_map[1] = dport->port_id; } else { cxlsd->target[0] = dport; - cxld->target_map[0] = dport->port_id; + cxlsd->cxld.target_map[0] = dport->port_id; } } else { cxlsd->target[0] = dport; - cxld->target_map[0] = dport->port_id; + cxlsd->cxld.target_map[0] = dport->port_id; } cxld = &cxlsd->cxld; cxld->target_type = CXL_DECODER_HOSTONLYMEM; @@ -885,8 +1209,14 @@ static void mock_init_hdm_decoder(struct cxl_decoder *cxld) .start = base, .end = base + mock_auto_region_size - 1, }; + cxld->commit = mock_decoder_commit; + cxld->reset = mock_decoder_reset; + + cxld_registry_update(cxld); put_device(dev); } + + return false; } static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, @@ -895,6 +1225,7 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, struct cxl_port *port = cxlhdm->port; struct cxl_port *parent_port = to_cxl_port(port->dev.parent); int target_count, i; + bool restored; if (is_cxl_endpoint(port)) target_count = 0; @@ -934,10 +1265,8 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, } ctx.target_map = cxld->target_map; - - mock_init_hdm_decoder(cxld); - - if (target_count) { + restored = mock_init_hdm_decoder(cxld); + if (target_count && !restored) { rc = device_for_each_child(port->uport_dev, &ctx, map_targets); if (rc) { @@ -1114,6 +1443,53 @@ static void mock_cxl_endpoint_parse_cdat(struct cxl_port *port) cxl_endpoint_get_perf_coordinates(port, ep_c); } +/* + * Simulate that the first half of mock CXL Window 0 is "Soft Reserve" capacity + */ +static int mock_walk_hmem_resources(struct device *host, walk_hmem_fn fn) +{ + struct acpi_cedt_cfmws *cfmws = mock_cfmws[0]; + struct resource window = + DEFINE_RES_MEM(cfmws->base_hpa, cfmws->window_size / 2); + + dev_dbg(host, "walk cxl_test resource: %pr\n", &window); + return fn(host, 0, &window); +} + +/* + * This should only be called by the dax_hmem case, treat mismatches (negative + * result) as "fallback to base region_intersects()". Simulate that the first + * half of mock CXL Window 0 is IORES_DESC_CXL capacity. + */ +static int mock_region_intersects(resource_size_t start, size_t size, + unsigned long flags, unsigned long desc) +{ + struct resource res = DEFINE_RES_MEM(start, size); + struct acpi_cedt_cfmws *cfmws = mock_cfmws[0]; + struct resource window = + DEFINE_RES_MEM(cfmws->base_hpa, cfmws->window_size / 2); + + if (resource_overlaps(&res, &window)) + return REGION_INTERSECTS; + pr_debug("warning: no cxl_test CXL intersection for %pr\n", &res); + return -1; +} + + +static int +mock_region_intersects_soft_reserve(resource_size_t start, size_t size) +{ + struct resource res = DEFINE_RES_MEM(start, size); + struct acpi_cedt_cfmws *cfmws = mock_cfmws[0]; + struct resource window = + DEFINE_RES_MEM(cfmws->base_hpa, cfmws->window_size / 2); + + if (resource_overlaps(&res, &window)) + return REGION_INTERSECTS; + pr_debug("warning: no cxl_test soft reserve intersection for %pr\n", &res); + return -1; +} + static struct cxl_mock_ops cxl_mock_ops = { .is_mock_adev = is_mock_adev, .is_mock_bridge = is_mock_bridge, @@ -1129,6 +1505,9 @@ static struct cxl_mock_ops cxl_mock_ops = { .devm_cxl_add_dport_by_dev = mock_cxl_add_dport_by_dev, .hmat_get_extended_linear_cache_size = mock_hmat_get_extended_linear_cache_size, + .walk_hmem_resources = mock_walk_hmem_resources, + .region_intersects = mock_region_intersects, + .region_intersects_soft_reserve = mock_region_intersects_soft_reserve, .list = LIST_HEAD_INIT(cxl_mock_ops.list), }; @@ -1415,6 +1794,33 @@ err_mem: return rc; } +static ssize_t +decoder_reset_preserve_registry_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", decoder_reset_preserve_registry); +} + +static ssize_t +decoder_reset_preserve_registry_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + + rc = kstrtobool(buf, &decoder_reset_preserve_registry); + if (rc) + return rc; + return count; +} + +static DEVICE_ATTR_RW(decoder_reset_preserve_registry); + +static struct attribute *cxl_acpi_attrs[] = { + &dev_attr_decoder_reset_preserve_registry.attr, NULL +}; +ATTRIBUTE_GROUPS(cxl_acpi); + static __init int cxl_test_init(void) { int rc, i; @@ -1545,6 +1951,7 @@ static __init int cxl_test_init(void) mock_companion(&acpi0017_mock, &cxl_acpi->dev); acpi0017_mock.dev.bus = &platform_bus_type; + cxl_acpi->dev.groups = cxl_acpi_groups; rc = platform_device_add(cxl_acpi); if (rc) @@ -1554,8 +1961,14 @@ static __init int cxl_test_init(void) if (rc) goto err_root; + rc = hmem_test_init(); + if (rc) + goto err_mem; + return 0; +err_mem: + cxl_mem_exit(); err_root: platform_device_put(cxl_acpi); err_rch: @@ -1589,10 +2002,22 @@ err_gen_pool_create: return rc; } +static void free_decoder_registry(void) +{ + unsigned long index; + void *entry; + + xa_for_each(&decoder_registry, index, entry) { + xa_erase(&decoder_registry, index); + kfree(entry); + } +} + static __exit void cxl_test_exit(void) { int i; + hmem_test_exit(); cxl_mem_exit(); platform_device_unregister(cxl_acpi); cxl_rch_topo_exit(); @@ -1614,12 +2039,16 @@ static __exit void cxl_test_exit(void) depopulate_all_mock_resources(); gen_pool_destroy(cxl_mock_pool); unregister_cxl_mock_ops(&cxl_mock_ops); + free_decoder_registry(); + xa_destroy(&decoder_registry); } module_param(interleave_arithmetic, int, 0444); MODULE_PARM_DESC(interleave_arithmetic, "Modulo:0, XOR:1"); module_param(extended_linear_cache, bool, 0444); MODULE_PARM_DESC(extended_linear_cache, "Enable extended linear cache support"); +module_param(fail_autoassemble, bool, 0444); +MODULE_PARM_DESC(fail_autoassemble, "Simulate missing member of an auto-region"); module_init(cxl_test_init); module_exit(cxl_test_exit); MODULE_LICENSE("GPL v2"); diff --git a/tools/testing/cxl/test/hmem_test.c b/tools/testing/cxl/test/hmem_test.c new file mode 100644 index 000000000000..3a1a089e1721 --- /dev/null +++ b/tools/testing/cxl/test/hmem_test.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2026 Intel Corporation */ +#include +#include +#include "../../../drivers/dax/bus.h" + +static bool hmem_test; + +static void hmem_test_work(struct work_struct *work) +{ +} + +static void hmem_test_release(struct device *dev) +{ + struct hmem_platform_device *hpdev = + container_of(dev, typeof(*hpdev), pdev.dev); + + memset(hpdev, 0, sizeof(*hpdev)); +} + +static struct hmem_platform_device hmem_test_device = { + .pdev = { + .name = "hmem_platform", + .id = 1, + .dev = { + .release = hmem_test_release, + }, + }, + .work = __WORK_INITIALIZER(hmem_test_device.work, hmem_test_work), +}; + +int hmem_test_init(void) +{ + if (!hmem_test) + return 0; + + return platform_device_register(&hmem_test_device.pdev); +} + +void hmem_test_exit(void) +{ + if (hmem_test) + platform_device_unregister(&hmem_test_device.pdev); +} + +module_param(hmem_test, bool, 0444); +MODULE_PARM_DESC(hmem_test, "Enable/disable the dax_hmem test platform device"); diff --git a/tools/testing/cxl/test/mem.c b/tools/testing/cxl/test/mem.c index cb87e8c0e63c..271c7ad8cc32 100644 --- a/tools/testing/cxl/test/mem.c +++ b/tools/testing/cxl/test/mem.c @@ -1695,6 +1695,9 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) struct cxl_dpa_info range_info = { 0 }; int rc; + /* Increase async probe race window */ + usleep_range(500*1000, 1000*1000); + mdata = devm_kzalloc(dev, sizeof(*mdata), GFP_KERNEL); if (!mdata) return -ENOMEM; @@ -1716,7 +1719,7 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) if (rc) return rc; - mds = cxl_memdev_state_create(dev); + mds = cxl_memdev_state_create(dev, pdev->id + 1, 0); if (IS_ERR(mds)) return PTR_ERR(mds); @@ -1732,7 +1735,6 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) mds->event.buf = (struct cxl_get_event_payload *) mdata->event_buf; INIT_DELAYED_WORK(&mds->security.poll_dwork, cxl_mockmem_sanitize_work); - cxlds->serial = pdev->id + 1; if (is_rcd(pdev)) cxlds->rcd = true; diff --git a/tools/testing/cxl/test/mock.c b/tools/testing/cxl/test/mock.c index b8fcb50c1027..6454b868b122 100644 --- a/tools/testing/cxl/test/mock.c +++ b/tools/testing/cxl/test/mock.c @@ -251,6 +251,56 @@ struct cxl_dport *__wrap_devm_cxl_add_dport_by_dev(struct cxl_port *port, } EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_add_dport_by_dev, "CXL"); +int __wrap_region_intersects(resource_size_t start, size_t size, + unsigned long flags, unsigned long desc) +{ + int rc = -1; + int index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops) + rc = ops->region_intersects(start, size, flags, desc); + if (rc < 0) + rc = region_intersects(start, size, flags, desc); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_GPL(__wrap_region_intersects); + +int __wrap_region_intersects_soft_reserve(resource_size_t start, size_t size) +{ + int rc = -1; + int index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops) + rc = ops->region_intersects_soft_reserve(start, size); + if (rc < 0) + rc = region_intersects_soft_reserve(start, size); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_GPL(__wrap_region_intersects_soft_reserve); + +int __wrap_walk_hmem_resources(struct device *host, walk_hmem_fn fn) +{ + int index, rc = 0; + bool is_mock = strcmp(dev_name(host), "hmem_platform.1") == 0; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (is_mock) { + if (ops) + rc = ops->walk_hmem_resources(host, fn); + } else { + rc = walk_hmem_resources(host, fn); + } + put_cxl_mock_ops(index); + return rc; +} +EXPORT_SYMBOL_GPL(__wrap_walk_hmem_resources); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("cxl_test: emulation module"); MODULE_IMPORT_NS("ACPI"); diff --git a/tools/testing/cxl/test/mock.h b/tools/testing/cxl/test/mock.h index 2684b89c8aa2..4f57dc80ae7d 100644 --- a/tools/testing/cxl/test/mock.h +++ b/tools/testing/cxl/test/mock.h @@ -2,6 +2,7 @@ #include #include +#include #include struct cxl_mock_ops { @@ -27,8 +28,15 @@ struct cxl_mock_ops { int (*hmat_get_extended_linear_cache_size)(struct resource *backing_res, int nid, resource_size_t *cache_size); + int (*walk_hmem_resources)(struct device *host, walk_hmem_fn fn); + int (*region_intersects)(resource_size_t start, size_t size, + unsigned long flags, unsigned long desc); + int (*region_intersects_soft_reserve)(resource_size_t start, + size_t size); }; +int hmem_test_init(void); +void hmem_test_exit(void); void register_cxl_mock_ops(struct cxl_mock_ops *ops); void unregister_cxl_mock_ops(struct cxl_mock_ops *ops); struct cxl_mock_ops *get_cxl_mock_ops(int *index); diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h index 009b97bbdd22..b6b1d147fd75 100644 --- a/tools/testing/memblock/internal.h +++ b/tools/testing/memblock/internal.h @@ -11,9 +11,22 @@ static int memblock_debug = 1; #define pr_warn_ratelimited(fmt, ...) printf(fmt, ##__VA_ARGS__) +#define K(x) ((x) << (PAGE_SHIFT-10)) + bool mirrored_kernelcore = false; struct page {}; +static inline void *page_address(struct page *page) +{ + BUG(); + return page; +} + +static inline struct page *virt_to_page(void *virt) +{ + BUG(); + return virt; +} void memblock_free_pages(unsigned long pfn, unsigned int order) { @@ -23,10 +36,34 @@ static inline void accept_memory(phys_addr_t start, unsigned long size) { } -static inline unsigned long free_reserved_area(void *start, void *end, - int poison, const char *s) +unsigned long free_reserved_area(void *start, void *end, int poison, const char *s); +void free_reserved_page(struct page *page); + +static inline bool deferred_pages_enabled(void) { - return 0; + return false; } +#define for_each_valid_pfn(pfn, start_pfn, end_pfn) \ + for ((pfn) = (start_pfn); (pfn) < (end_pfn); (pfn)++) + +static inline void *kasan_reset_tag(const void *addr) +{ + return (void *)addr; +} + +static inline bool __is_kernel(unsigned long addr) +{ + return false; +} + +#define for_each_valid_pfn(pfn, start_pfn, end_pfn) \ + for ((pfn) = (start_pfn); (pfn) < (end_pfn); (pfn)++) + +static inline void init_deferred_page(unsigned long pfn, int nid) +{ +} + +#define __SetPageReserved(p) ((void)(p)) + #endif diff --git a/tools/testing/memblock/linux/string_helpers.h b/tools/testing/memblock/linux/string_helpers.h new file mode 100644 index 000000000000..dbf015cfff31 --- /dev/null +++ b/tools/testing/memblock/linux/string_helpers.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_STRING_HELPERS_H_ +#define _LINUX_STRING_HELPERS_H_ + +/* + * Header stub to avoid test build breakage; we don't need to + * actually implement string_get_size() as it's not used in the tests. + */ + +#endif diff --git a/tools/testing/memblock/mmzone.c b/tools/testing/memblock/mmzone.c index d3d58851864e..e719450f81cb 100644 --- a/tools/testing/memblock/mmzone.c +++ b/tools/testing/memblock/mmzone.c @@ -11,10 +11,6 @@ struct pglist_data *next_online_pgdat(struct pglist_data *pgdat) return NULL; } -void reserve_bootmem_region(phys_addr_t start, phys_addr_t end, int nid) -{ -} - void atomic_long_set(atomic_long_t *v, long i) { } diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 78e60040811e..6ef6872adbc3 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -751,6 +751,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \ btf_helpers.c \ cap_helpers.c \ unpriv_helpers.c \ + sysctl_helpers.c \ netlink_helpers.c \ jit_disasm_helpers.c \ io_helpers.c \ diff --git a/tools/testing/selftests/bpf/prog_tests/snprintf.c b/tools/testing/selftests/bpf/prog_tests/snprintf.c index 594441acb707..4e4a82d54f79 100644 --- a/tools/testing/selftests/bpf/prog_tests/snprintf.c +++ b/tools/testing/selftests/bpf/prog_tests/snprintf.c @@ -114,7 +114,8 @@ static void test_snprintf_negative(void) ASSERT_ERR(load_single_snprintf("%--------"), "invalid specifier 5"); ASSERT_ERR(load_single_snprintf("%lc"), "invalid specifier 6"); ASSERT_ERR(load_single_snprintf("%llc"), "invalid specifier 7"); - ASSERT_ERR(load_single_snprintf("\x80"), "non ascii character"); + ASSERT_OK(load_single_snprintf("\x80"), "non ascii plain text"); + ASSERT_ERR(load_single_snprintf("%\x80"), "non ascii in specifier"); ASSERT_ERR(load_single_snprintf("\x1"), "non printable character"); ASSERT_ERR(load_single_snprintf("%p%"), "invalid specifier 8"); ASSERT_ERR(load_single_snprintf("%s%"), "invalid specifier 9"); diff --git a/tools/testing/selftests/bpf/prog_tests/task_local_data.h b/tools/testing/selftests/bpf/prog_tests/task_local_data.h index 1e5c67c78ffb..8ae4fb2027f7 100644 --- a/tools/testing/selftests/bpf/prog_tests/task_local_data.h +++ b/tools/testing/selftests/bpf/prog_tests/task_local_data.h @@ -99,14 +99,20 @@ struct tld_meta_u { struct tld_metadata metadata[]; }; +/* + * The unused field ensures map_val.start > 0. On the BPF side, __tld_fetch_key() + * calculates off by summing map_val.start and tld_key_t.off and treats off == 0 + * as key not cached. + */ struct tld_data_u { - __u64 start; /* offset of tld_data_u->data in a page */ + __u64 unused; char data[] __attribute__((aligned(8))); }; struct tld_map_value { void *data; struct tld_meta_u *meta; + __u16 start; /* offset of tld_data_u->data in a page */ }; struct tld_meta_u * _Atomic tld_meta_p __attribute__((weak)); @@ -182,7 +188,7 @@ static int __tld_init_data_p(int map_fd) * is a page in BTF. */ map_val.data = (void *)(TLD_PAGE_MASK & (intptr_t)data); - data->start = (~TLD_PAGE_MASK & (intptr_t)data) + sizeof(struct tld_data_u); + map_val.start = (~TLD_PAGE_MASK & (intptr_t)data) + sizeof(struct tld_data_u); map_val.meta = tld_meta_p; err = bpf_map_update_elem(map_fd, &tid_fd, &map_val, 0); @@ -241,7 +247,8 @@ retry: * TLD_DYN_DATA_SIZE is allocated for tld_create_key() */ if (dyn_data) { - if (off + TLD_ROUND_UP(size, 8) > tld_meta_p->size) + if (off + TLD_ROUND_UP(size, 8) > tld_meta_p->size || + tld_meta_p->size > TLD_PAGE_SIZE - sizeof(struct tld_data_u)) return (tld_key_t){-E2BIG}; } else { if (off + TLD_ROUND_UP(size, 8) > TLD_PAGE_SIZE - sizeof(struct tld_data_u)) diff --git a/tools/testing/selftests/bpf/prog_tests/test_task_local_data.c b/tools/testing/selftests/bpf/prog_tests/test_task_local_data.c index e219ff506b56..6a5806b36113 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_task_local_data.c +++ b/tools/testing/selftests/bpf/prog_tests/test_task_local_data.c @@ -3,8 +3,14 @@ #include #include +/* + * Only a page is pinned to kernel, so the maximum amount of dynamic data + * allowed is page_size - sizeof(struct tld_data_u) - static TLD fields. + */ +#define TLD_DYN_DATA_SIZE_MAX (getpagesize() - sizeof(struct tld_data_u) - 8) + #define TLD_FREE_DATA_ON_THREAD_EXIT -#define TLD_DYN_DATA_SIZE (getpagesize() - 8) +#define TLD_DYN_DATA_SIZE TLD_DYN_DATA_SIZE_MAX #include "task_local_data.h" struct test_tld_struct { @@ -24,12 +30,12 @@ TLD_DEFINE_KEY(value0_key, "value0", sizeof(int)); * sequentially. Users of task local data library should not touch * library internal. */ -static void reset_tld(void) +static void reset_tld(__u16 dyn_data_size) { if (tld_meta_p) { /* Remove TLDs created by tld_create_key() */ tld_meta_p->cnt = 1; - tld_meta_p->size = TLD_DYN_DATA_SIZE; + tld_meta_p->size = dyn_data_size + 8; memset(&tld_meta_p->metadata[1], 0, (TLD_MAX_DATA_CNT - 1) * sizeof(struct tld_metadata)); } @@ -127,7 +133,7 @@ static void test_task_local_data_basic(void) tld_key_t key; int i, err; - reset_tld(); + reset_tld(TLD_DYN_DATA_SIZE_MAX); ASSERT_OK(pthread_mutex_init(&global_mutex, NULL), "pthread_mutex_init"); @@ -147,11 +153,13 @@ static void test_task_local_data_basic(void) /* * Shouldn't be able to store data exceed a page. Create a TLD just big - * enough to exceed a page. TLDs already created are int value0, int - * value1, and struct test_tld_struct value2. + * enough to exceed a page. Data already contains struct tld_data_u, + * value0 and value1 of int type, and value 2 of struct test_tld_struct. */ - key = tld_create_key("value_not_exist", - TLD_PAGE_SIZE - 2 * sizeof(int) - sizeof(struct test_tld_struct) + 1); + key = tld_create_key("value_not_exist", TLD_PAGE_SIZE + 1 - + sizeof(struct tld_data_u) - + TLD_ROUND_UP(sizeof(int), 8) * 2 - + TLD_ROUND_UP(sizeof(struct test_tld_struct), 8)); ASSERT_EQ(tld_key_err_or_zero(key), -E2BIG, "tld_create_key"); key = tld_create_key("value2", sizeof(struct test_tld_struct)); @@ -239,7 +247,7 @@ static void test_task_local_data_race(void) tld_keys[0] = value0_key; for (j = 0; j < 100; j++) { - reset_tld(); + reset_tld(TLD_DYN_DATA_SIZE_MAX); for (i = 0; i < TEST_RACE_THREAD_NUM; i++) { /* @@ -288,10 +296,80 @@ out: test_task_local_data__destroy(skel); } +static void test_task_local_data_dyn_size(__u16 dyn_data_size) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct test_task_local_data *skel; + int max_keys, i, err, fd, *data; + char name[TLD_NAME_LEN]; + tld_key_t key; + + reset_tld(dyn_data_size); + + skel = test_task_local_data__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + return; + + tld_keys = calloc(TLD_MAX_DATA_CNT, sizeof(tld_key_t)); + if (!ASSERT_OK_PTR(tld_keys, "calloc tld_keys")) + goto out; + + fd = bpf_map__fd(skel->maps.tld_data_map); + + /* Create as many int-sized TLDs as the dynamic data size allows */ + max_keys = dyn_data_size / TLD_ROUND_UP(sizeof(int), 8); + for (i = 0; i < max_keys; i++) { + snprintf(name, TLD_NAME_LEN, "value_%d", i); + tld_keys[i] = tld_create_key(name, sizeof(int)); + if (!ASSERT_FALSE(tld_key_is_err(tld_keys[i]), "tld_create_key")) + goto out; + + data = tld_get_data(fd, tld_keys[i]); + if (!ASSERT_OK_PTR(data, "tld_get_data")) + goto out; + *data = i; + } + + /* The next key should fail with E2BIG */ + key = tld_create_key("overflow", sizeof(int)); + ASSERT_EQ(tld_key_err_or_zero(key), -E2BIG, "tld_create_key overflow"); + + /* Verify data for value_i do not overlap */ + for (i = 0; i < max_keys; i++) { + data = tld_get_data(fd, tld_keys[i]); + if (!ASSERT_OK_PTR(data, "tld_get_data")) + goto out; + + ASSERT_EQ(*data, i, "tld_get_data value_i"); + } + + /* Verify BPF side can still read the static key */ + data = tld_get_data(fd, value0_key); + if (!ASSERT_OK_PTR(data, "tld_get_data value0")) + goto out; + *data = 0xdeadbeef; + + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.task_main), &opts); + ASSERT_OK(err, "run task_main"); + ASSERT_EQ(skel->bss->test_value0, 0xdeadbeef, "tld_get_data value0"); + +out: + if (tld_keys) { + free(tld_keys); + tld_keys = NULL; + } + tld_free(); + test_task_local_data__destroy(skel); +} + void test_task_local_data(void) { if (test__start_subtest("task_local_data_basic")) test_task_local_data_basic(); if (test__start_subtest("task_local_data_race")) test_task_local_data_race(); + if (test__start_subtest("task_local_data_dyn_size_small")) + test_task_local_data_dyn_size(64); + if (test__start_subtest("task_local_data_dyn_size_zero")) + test_task_local_data_dyn_size(0); } diff --git a/tools/testing/selftests/bpf/prog_tests/token.c b/tools/testing/selftests/bpf/prog_tests/token.c index b81dde283052..f2f5d36ae00a 100644 --- a/tools/testing/selftests/bpf/prog_tests/token.c +++ b/tools/testing/selftests/bpf/prog_tests/token.c @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ #define _GNU_SOURCE -#include #include -#include "cap_helpers.h" #include #include #include @@ -15,9 +13,17 @@ #include #include #include + +#include "bpf_util.h" +#include "cap_helpers.h" +#include "sysctl_helpers.h" +#include "test_progs.h" +#include "trace_helpers.h" + #include "priv_map.skel.h" #include "priv_prog.skel.h" #include "dummy_st_ops_success.skel.h" +#include "token_kallsyms.skel.h" #include "token_lsm.skel.h" #include "priv_freplace_prog.skel.h" @@ -1045,6 +1051,58 @@ err_out: return -EINVAL; } +static bool kallsyms_has_bpf_func(struct ksyms *ksyms, const char *func_name) +{ + char name[256]; + int i; + + for (i = 0; i < ksyms->sym_cnt; i++) { + if (sscanf(ksyms->syms[i].name, "bpf_prog_%*[^_]_%255s", name) == 1 && + strcmp(name, func_name) == 0) + return true; + } + return false; +} + +static int userns_obj_priv_prog_kallsyms(int mnt_fd, struct token_lsm *lsm_skel) +{ + const char *func_names[] = { "xdp_main", "token_ksym_subprog" }; + LIBBPF_OPTS(bpf_object_open_opts, opts); + struct token_kallsyms *skel; + struct ksyms *ksyms = NULL; + char buf[256]; + int i, err; + + snprintf(buf, sizeof(buf), "/proc/self/fd/%d", mnt_fd); + opts.bpf_token_path = buf; + skel = token_kallsyms__open_opts(&opts); + if (!ASSERT_OK_PTR(skel, "token_kallsyms__open_opts")) + return -EINVAL; + + err = token_kallsyms__load(skel); + if (!ASSERT_OK(err, "token_kallsyms__load")) + goto cleanup; + + ksyms = load_kallsyms_local(); + if (!ASSERT_OK_PTR(ksyms, "load_kallsyms_local")) { + err = -EINVAL; + goto cleanup; + } + + for (i = 0; i < ARRAY_SIZE(func_names); i++) { + if (!ASSERT_TRUE(kallsyms_has_bpf_func(ksyms, func_names[i]), + func_names[i])) { + err = -EINVAL; + break; + } + } + +cleanup: + free_kallsyms_local(ksyms); + token_kallsyms__destroy(skel); + return err; +} + #define bit(n) (1ULL << (n)) static int userns_bpf_token_info(int mnt_fd, struct token_lsm *lsm_skel) @@ -1082,7 +1140,7 @@ cleanup: return err; } -void test_token(void) +void serial_test_token(void) { if (test__start_subtest("map_token")) { struct bpffs_opts opts = { @@ -1194,4 +1252,26 @@ void test_token(void) subtest_userns(&opts, userns_bpf_token_info); } + if (test__start_subtest("obj_priv_prog_kallsyms")) { + char perf_paranoid_orig[32] = {}; + char kptr_restrict_orig[32] = {}; + struct bpffs_opts opts = { + .cmds = bit(BPF_BTF_LOAD) | bit(BPF_PROG_LOAD), + .progs = bit(BPF_PROG_TYPE_XDP), + .attachs = ~0ULL, + }; + + if (sysctl_set_or_fail("/proc/sys/kernel/perf_event_paranoid", perf_paranoid_orig, "0")) + goto cleanup; + if (sysctl_set_or_fail("/proc/sys/kernel/kptr_restrict", kptr_restrict_orig, "0")) + goto cleanup; + + subtest_userns(&opts, userns_obj_priv_prog_kallsyms); + +cleanup: + if (perf_paranoid_orig[0]) + sysctl_set_or_fail("/proc/sys/kernel/perf_event_paranoid", NULL, perf_paranoid_orig); + if (kptr_restrict_orig[0]) + sysctl_set_or_fail("/proc/sys/kernel/kptr_restrict", NULL, kptr_restrict_orig); + } } diff --git a/tools/testing/selftests/bpf/prog_tests/trace_printk.c b/tools/testing/selftests/bpf/prog_tests/trace_printk.c index e56e88596d64..a5a8104c1ddd 100644 --- a/tools/testing/selftests/bpf/prog_tests/trace_printk.c +++ b/tools/testing/selftests/bpf/prog_tests/trace_printk.c @@ -6,18 +6,21 @@ #include "trace_printk.lskel.h" #define SEARCHMSG "testing,testing" +#define SEARCHMSG_UTF8 "中文,测试" static void trace_pipe_cb(const char *str, void *data) { if (strstr(str, SEARCHMSG) != NULL) - (*(int *)data)++; + ((int *)data)[0]++; + if (strstr(str, SEARCHMSG_UTF8)) + ((int *)data)[1]++; } void serial_test_trace_printk(void) { struct trace_printk_lskel__bss *bss; struct trace_printk_lskel *skel; - int err = 0, found = 0; + int err = 0, found[2] = {}; skel = trace_printk_lskel__open(); if (!ASSERT_OK_PTR(skel, "trace_printk__open")) @@ -46,11 +49,24 @@ void serial_test_trace_printk(void) if (!ASSERT_GT(bss->trace_printk_ret, 0, "bss->trace_printk_ret")) goto cleanup; - /* verify our search string is in the trace buffer */ - ASSERT_OK(read_trace_pipe_iter(trace_pipe_cb, &found, 1000), - "read_trace_pipe_iter"); + if (!ASSERT_GT(bss->trace_printk_utf8_ran, 0, "bss->trace_printk_utf8_ran")) + goto cleanup; - if (!ASSERT_EQ(found, bss->trace_printk_ran, "found")) + if (!ASSERT_GT(bss->trace_printk_utf8_ret, 0, "bss->trace_printk_utf8_ret")) + goto cleanup; + + if (!ASSERT_LT(bss->trace_printk_invalid_spec_ret, 0, + "bss->trace_printk_invalid_spec_ret")) + goto cleanup; + + /* verify our search strings are in the trace buffer */ + ASSERT_OK(read_trace_pipe_iter(trace_pipe_cb, found, 1000), + "read_trace_pipe_iter"); + + if (!ASSERT_EQ(found[0], bss->trace_printk_ran, "found")) + goto cleanup; + + if (!ASSERT_EQ(found[1], bss->trace_printk_utf8_ran, "found_utf8")) goto cleanup; cleanup: diff --git a/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c b/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c index 472f4f9fa95f..64404602b9ab 100644 --- a/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c +++ b/tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c @@ -8,6 +8,7 @@ #include "cap_helpers.h" #include "bpf_util.h" +#include "sysctl_helpers.h" /* Using CAP_LAST_CAP is risky here, since it can get pulled in from * an old /usr/include/linux/capability.h and be < CAP_BPF; as a result @@ -36,26 +37,6 @@ static void process_perfbuf(void *ctx, int cpu, void *data, __u32 len) got_perfbuf_val = *(__u32 *)data; } -static int sysctl_set(const char *sysctl_path, char *old_val, const char *new_val) -{ - int ret = 0; - FILE *fp; - - fp = fopen(sysctl_path, "r+"); - if (!fp) - return -errno; - if (old_val && fscanf(fp, "%s", old_val) <= 0) { - ret = -ENOENT; - } else if (!old_val || strcmp(old_val, new_val) != 0) { - fseek(fp, 0, SEEK_SET); - if (fprintf(fp, "%s", new_val) < 0) - ret = -errno; - } - fclose(fp); - - return ret; -} - static void test_unpriv_bpf_disabled_positive(struct test_unpriv_bpf_disabled *skel, __u32 prog_id, int prog_fd, int perf_fd, char **map_paths, int *map_fds) diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_unix.c b/tools/testing/selftests/bpf/progs/bpf_iter_unix.c index fea275df9e22..a2652c8c3616 100644 --- a/tools/testing/selftests/bpf/progs/bpf_iter_unix.c +++ b/tools/testing/selftests/bpf/progs/bpf_iter_unix.c @@ -7,6 +7,13 @@ char _license[] SEC("license") = "GPL"; +SEC(".maps") struct { + __uint(type, BPF_MAP_TYPE_SOCKMAP); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u64); +} sockmap; + static long sock_i_ino(const struct sock *sk) { const struct socket *sk_socket = sk->sk_socket; @@ -76,5 +83,8 @@ int dump_unix(struct bpf_iter__unix *ctx) BPF_SEQ_PRINTF(seq, "\n"); + /* Test for deadlock. */ + bpf_map_update_elem(&sockmap, &(int){0}, sk, 0); + return 0; } diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c index 6443b320c732..ee053b24e6ca 100644 --- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c @@ -385,4 +385,19 @@ int kptr_xchg_possibly_null(struct __sk_buff *ctx) return 0; } +SEC("?tc") +__failure __msg("invalid kptr access, R") +int reject_scalar_store_to_kptr(struct __sk_buff *ctx) +{ + struct map_value *v; + int key = 0; + + v = bpf_map_lookup_elem(&array_map, &key); + if (!v) + return 0; + + *(volatile u64 *)&v->unref_ptr = 0xBADC0DE; + return 0; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h index 1f396711f487..0df8a12fd61e 100644 --- a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h +++ b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h @@ -86,13 +86,14 @@ struct tld_meta_u { }; struct tld_data_u { - __u64 start; /* offset of tld_data_u->data in a page */ + __u64 unused; char data[__PAGE_SIZE - sizeof(__u64)] __attribute__((aligned(8))); }; struct tld_map_value { struct tld_data_u __uptr *data; struct tld_meta_u __uptr *meta; + __u16 start; /* offset of tld_data_u->data in a page */ }; typedef struct tld_uptr_dummy { @@ -176,7 +177,7 @@ static int __tld_fetch_key(struct tld_object *tld_obj, const char *name, int i_s if (!tld_obj->data_map || !tld_obj->data_map->data || !tld_obj->data_map->meta) return 0; - start = tld_obj->data_map->data->start; + start = tld_obj->data_map->start; cnt = tld_obj->data_map->meta->cnt; metadata = tld_obj->data_map->meta->metadata; diff --git a/tools/testing/selftests/bpf/progs/timer_start_deadlock.c b/tools/testing/selftests/bpf/progs/timer_start_deadlock.c index 019518ee18cd..afabd15bdac4 100644 --- a/tools/testing/selftests/bpf/progs/timer_start_deadlock.c +++ b/tools/testing/selftests/bpf/progs/timer_start_deadlock.c @@ -27,13 +27,13 @@ static int timer_cb(void *map, int *key, struct elem *value) return 0; } -SEC("tp_btf/hrtimer_cancel") -int BPF_PROG(tp_hrtimer_cancel, struct hrtimer *hrtimer) +SEC("tp_btf/hrtimer_start") +int BPF_PROG(tp_hrtimer_start, struct hrtimer *hrtimer, enum hrtimer_mode mode, bool was_armed) { struct bpf_timer *timer; int key = 0; - if (!in_timer_start) + if (!in_timer_start || !was_armed) return 0; tp_called = 1; @@ -60,7 +60,7 @@ int start_timer(void *ctx) /* * call hrtimer_start() twice, so that 2nd call does - * remove_hrtimer() and trace_hrtimer_cancel() tracepoint. + * trace_hrtimer_start(was_armed=1) tracepoint. */ in_timer_start = 1; bpf_timer_start(timer, 1000000000, 0); diff --git a/tools/testing/selftests/bpf/progs/token_kallsyms.c b/tools/testing/selftests/bpf/progs/token_kallsyms.c new file mode 100644 index 000000000000..c9f9344f3eb2 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/token_kallsyms.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include "vmlinux.h" +#include + +char _license[] SEC("license") = "GPL"; + +__weak +int token_ksym_subprog(void) +{ + return 0; +} + +SEC("xdp") +int xdp_main(struct xdp_md *xdp) +{ + return token_ksym_subprog(); +} diff --git a/tools/testing/selftests/bpf/progs/trace_printk.c b/tools/testing/selftests/bpf/progs/trace_printk.c index 6695478c2b25..f4c538ec3ebd 100644 --- a/tools/testing/selftests/bpf/progs/trace_printk.c +++ b/tools/testing/selftests/bpf/progs/trace_printk.c @@ -10,13 +10,23 @@ char _license[] SEC("license") = "GPL"; int trace_printk_ret = 0; int trace_printk_ran = 0; +int trace_printk_invalid_spec_ret = 0; +int trace_printk_utf8_ret = 0; +int trace_printk_utf8_ran = 0; const char fmt[] = "Testing,testing %d\n"; +static const char utf8_fmt[] = "中文,测试 %d\n"; +/* Non-ASCII bytes after '%' must still be rejected. */ +static const char invalid_spec_fmt[] = "%\x80\n"; SEC("fentry/" SYS_PREFIX "sys_nanosleep") int sys_enter(void *ctx) { trace_printk_ret = bpf_trace_printk(fmt, sizeof(fmt), ++trace_printk_ran); + trace_printk_utf8_ret = bpf_trace_printk(utf8_fmt, sizeof(utf8_fmt), + ++trace_printk_utf8_ran); + trace_printk_invalid_spec_ret = bpf_trace_printk(invalid_spec_fmt, + sizeof(invalid_spec_fmt)); return 0; } diff --git a/tools/testing/selftests/bpf/progs/verifier_live_stack.c b/tools/testing/selftests/bpf/progs/verifier_live_stack.c index b7a9fa10e84d..401152b2b64f 100644 --- a/tools/testing/selftests/bpf/progs/verifier_live_stack.c +++ b/tools/testing/selftests/bpf/progs/verifier_live_stack.c @@ -2647,3 +2647,196 @@ __naked void spill_join_with_imprecise_off(void) "exit;" ::: __clobber_all); } + +/* + * Same as spill_join_with_multi_off but the write is BPF_ST (store + * immediate) instead of BPF_STX. BPF_ST goes through + * clear_stack_for_all_offs() rather than spill_to_stack(), and that + * path also needs to join instead of overwriting. + * + * fp-8 = &fp-24 + * fp-16 = &fp-32 + * r1 = fp-8 or fp-16 (two offsets from branch) + * *(u64 *)(r1 + 0) = 0 -- BPF_ST with immediate + * r0 = *(u64 *)(r10 - 16) -- fill from fp-16 + * r0 = *(u64 *)(r0 + 0) -- deref: should produce use + */ +SEC("socket") +__log_level(2) +__failure +__msg("15: (7a) *(u64 *)(r1 +0) = 0 fp-8: fp0-24 -> fp0-24|fp0+0 fp-16: fp0-32 -> fp0-32|fp0+0") +__msg("17: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-32") +__naked void st_imm_join_with_multi_off(void) +{ + asm volatile ( + "*(u64 *)(r10 - 24) = 0;" + "*(u64 *)(r10 - 32) = 0;" + "r1 = r10;" + "r1 += -24;" + "*(u64 *)(r10 - 8) = r1;" + "r1 = r10;" + "r1 += -32;" + "*(u64 *)(r10 - 16) = r1;" + /* create r1 with two candidate offsets: fp-8 or fp-16 */ + "call %[bpf_get_prandom_u32];" + "if r0 == 0 goto 1f;" + "r1 = r10;" + "r1 += -8;" + "goto 2f;" +"1:" + "r1 = r10;" + "r1 += -16;" +"2:" + /* BPF_ST: store immediate through multi-offset r1 */ + "*(u64 *)(r1 + 0) = 0;" + /* read back fp-16 and deref */ + "r0 = *(u64 *)(r10 - 16);" + "r0 = *(u64 *)(r0 + 0);" + "r0 = 0;" + "exit;" + :: __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * Check that BPF_ST with a known offset fully overwrites stack slot + * from the arg tracking point of view. + */ +SEC("socket") +__log_level(2) +__success +__msg("5: (7a) *(u64 *)(r1 +0) = 0 fp-8: fp0-16 -> _{{$}}") +__naked void st_imm_join_with_single_off(void) +{ + asm volatile ( + "r2 = r10;" + "r2 += -16;" + "*(u64 *)(r10 - 8) = r2;" + "r1 = r10;" + "r1 += -8;" + "*(u64 *)(r1 + 0) = 0;" + "r0 = 0;" + "exit;" + ::: __clobber_all); +} + +/* + * Same as spill_join_with_imprecise_off but the write is BPF_ST. + * Use "r2 = -8; r1 += r2" to make arg tracking lose offset + * precision while the main verifier keeps r1 as fixed-offset. + * + * fp-8 = &fp-24 + * fp-16 = &fp-32 + * r1 = fp-8 (imprecise to arg tracking) + * *(u64 *)(r1 + 0) = 0 -- BPF_ST with immediate + * r0 = *(u64 *)(r10 - 16) -- fill from fp-16 + * r0 = *(u64 *)(r0 + 0) -- deref: should produce use + */ +SEC("socket") +__log_level(2) +__success +__msg("13: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-32") +__naked void st_imm_join_with_imprecise_off(void) +{ + asm volatile ( + "*(u64 *)(r10 - 24) = 0;" + "*(u64 *)(r10 - 32) = 0;" + "r1 = r10;" + "r1 += -24;" + "*(u64 *)(r10 - 8) = r1;" + "r1 = r10;" + "r1 += -32;" + "*(u64 *)(r10 - 16) = r1;" + /* r1 = fp-8 but arg tracking sees off_cnt == 0 */ + "r1 = r10;" + "r2 = -8;" + "r1 += r2;" + /* store immediate through imprecise r1 */ + "*(u64 *)(r1 + 0) = 0;" + /* read back fp-16 */ + "r0 = *(u64 *)(r10 - 16);" + /* deref: should produce use */ + "r0 = *(u64 *)(r0 + 0);" + "r0 = 0;" + "exit;" + ::: __clobber_all); +} + +/* + * Test that spilling through an ARG_IMPRECISE pointer joins with + * existing at_stack values. Subprog receives r1 = fp0-24 and + * r2 = map_value, creates an ARG_IMPRECISE pointer by joining caller + * and callee FP on two branches. + * + * Setup: callee spills &fp1-16 to fp1-8 (precise, tracked). + * Then writes map_value through ARG_IMPRECISE r1 — on path A + * this hits fp1-8, on path B it hits caller stack. + * Since spill_to_stack is skipped for ARG_IMPRECISE dst, + * fp1-8 tracking isn't joined with none. + * + * Expected after the imprecise write: + * - arg tracking should show fp1-8 = fp1-16|fp1+0 (joined with none) + * - read from fp1-8 and deref should produce use for fp1-16 + * - write through it should NOT produce def for fp1-16 + */ +SEC("socket") +__log_level(2) +__success +__msg("26: (79) r0 = *(u64 *)(r10 -8) // r1=IMP3 r6=fp0-24 r7=fp1-16 fp-8=fp1-16|fp1+0") +__naked void imprecise_dst_spill_join(void) +{ + asm volatile ( + "*(u64 *)(r10 - 24) = 0;" + /* map lookup for a valid non-FP pointer */ + "*(u32 *)(r10 - 32) = 0;" + "r1 = %[map] ll;" + "r2 = r10;" + "r2 += -32;" + "call %[bpf_map_lookup_elem];" + "if r0 == 0 goto 1f;" + /* r1 = &caller_fp-24, r2 = map_value */ + "r1 = r10;" + "r1 += -24;" + "r2 = r0;" + "call imprecise_dst_spill_join_sub;" +"1:" + "r0 = 0;" + "exit;" + :: __imm_addr(map), + __imm(bpf_map_lookup_elem) + : __clobber_all); +} + +static __used __naked void imprecise_dst_spill_join_sub(void) +{ + asm volatile ( + /* r6 = &caller_fp-24 (frame=0), r8 = map_value */ + "r6 = r1;" + "r8 = r2;" + /* spill &fp1-16 to fp1-8: at_stack[0] = fp1-16 */ + "*(u64 *)(r10 - 16) = 0;" + "r7 = r10;" + "r7 += -16;" + "*(u64 *)(r10 - 8) = r7;" + /* branch to create ARG_IMPRECISE pointer */ + "call %[bpf_get_prandom_u32];" + /* path B: r1 = caller fp-24 (frame=0) */ + "r1 = r6;" + "if r0 == 0 goto 1f;" + /* path A: r1 = callee fp-8 (frame=1) */ + "r1 = r10;" + "r1 += -8;" +"1:" + /* r1 = ARG_IMPRECISE{mask=BIT(0)|BIT(1)}. + * Write map_value (non-FP) through r1. On path A this overwrites fp1-8. + * Should join at_stack[0] with none: fp1-16|fp1+0. + */ + "*(u64 *)(r1 + 0) = r8;" + /* read fp1-8: should be fp1-16|fp1+0 (joined) */ + "r0 = *(u64 *)(r10 - 8);" + "*(u64 *)(r0 + 0) = 42;" + "r0 = 0;" + "exit;" + :: __imm(bpf_get_prandom_u32) + : __clobber_all); +} diff --git a/tools/testing/selftests/bpf/sysctl_helpers.c b/tools/testing/selftests/bpf/sysctl_helpers.c new file mode 100644 index 000000000000..e2bd824f12d5 --- /dev/null +++ b/tools/testing/selftests/bpf/sysctl_helpers.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "sysctl_helpers.h" +#include "test_progs.h" + +int sysctl_set(const char *sysctl_path, char *old_val, const char *new_val) +{ + int ret = 0; + FILE *fp; + + fp = fopen(sysctl_path, "r+"); + if (!fp) + return -errno; + if (old_val && fscanf(fp, "%s", old_val) <= 0) { + ret = -ENOENT; + } else if (!old_val || strcmp(old_val, new_val) != 0) { + fseek(fp, 0, SEEK_SET); + if (fprintf(fp, "%s", new_val) < 0) + ret = -errno; + } + fclose(fp); + + return ret; +} + +int sysctl_set_or_fail(const char *sysctl_path, char *old_val, const char *new_val) +{ + int err; + + err = sysctl_set(sysctl_path, old_val, new_val); + if (err) + PRINT_FAIL("failed to set %s to %s: %s\n", sysctl_path, new_val, strerror(-err)); + return err; +} diff --git a/tools/testing/selftests/bpf/sysctl_helpers.h b/tools/testing/selftests/bpf/sysctl_helpers.h new file mode 100644 index 000000000000..35e37bfe1b3b --- /dev/null +++ b/tools/testing/selftests/bpf/sysctl_helpers.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SYSCTL_HELPERS_H +#define __SYSCTL_HELPERS_H + +int sysctl_set(const char *sysctl_path, char *old_val, const char *new_val); +int sysctl_set_or_fail(const char *sysctl_path, char *old_val, const char *new_val); + +#endif