Merge 9055c64567 ("Merge tag 'memblock-v7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock") into android-mainline
Steps on the way to v7.1-rc1 Change-Id: I467f8ab21f2970b8d4b15884a90972aa363c014b Signed-off-by: Carlos Llamas <cmllamas@google.com>
This commit is contained in:
commit
a612f4fecb
505 files changed
+49774
-4681
No files matched your search
@@ -208,6 +208,7 @@ Colin Ian King <colin.i.king@gmail.com> <colin.king@canonical.com>
|
||||
Corey Minyard <minyard@acm.org>
|
||||
Damian Hobson-Garcia <dhobsong@igel.co.jp>
|
||||
Dan Carpenter <error27@gmail.com> <dan.carpenter@oracle.com>
|
||||
Dan Williams <djbw@kernel.org> <dan.j.williams@intel.com>
|
||||
Daniel Borkmann <daniel@iogearbox.net> <danborkmann@googlemail.com>
|
||||
Daniel Borkmann <daniel@iogearbox.net> <danborkmann@iogearbox.net>
|
||||
Daniel Borkmann <daniel@iogearbox.net> <daniel.borkmann@tik.ee.ethz.ch>
|
||||
@@ -427,6 +428,7 @@ John Stultz <johnstul@us.ibm.com>
|
||||
<jon.toppins+linux@gmail.com> <jtoppins@cumulusnetworks.com>
|
||||
<jon.toppins+linux@gmail.com> <jtoppins@redhat.com>
|
||||
Jonas Gorski <jonas.gorski@gmail.com> <jogo@openwrt.org>
|
||||
Jonathan Cameron <jic23@kernel.org> <jonathan.cameron@huawei.com>
|
||||
Jordan Crouse <jordan@cosmicpenguin.net> <jcrouse@codeaurora.org>
|
||||
<josh@joshtriplett.org> <josh@freedesktop.org>
|
||||
<josh@joshtriplett.org> <josh@kernel.org>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -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 <andi.shyti@kernel.org>
|
||||
|
||||
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 <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c@c8011000 {
|
||||
compatible = "intel,ixp4xx-i2c";
|
||||
reg = <0xc8011000 0x18>;
|
||||
interrupts = <33 IRQ_TYPE_LEVEL_LOW>;
|
||||
};
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -101,7 +101,7 @@ required:
|
||||
unevaluatedProperties: false
|
||||
|
||||
allOf:
|
||||
- $ref: nand-controller.yaml
|
||||
- $ref: nand-controller-legacy.yaml
|
||||
|
||||
- if:
|
||||
properties:
|
||||
|
||||
@@ -10,22 +10,43 @@ maintainers:
|
||||
- Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <miquel.raynal@bootlin.com>
|
||||
- Richard Weinberger <richard@nod.at>
|
||||
|
||||
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
|
||||
|
||||
@@ -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(@.*)?"
|
||||
|
||||
@@ -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 <miquel.raynal@bootlin.com>
|
||||
|
||||
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
|
||||
@@ -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>;
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <miquel.raynal@bootlin.com>
|
||||
|
||||
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
|
||||
@@ -99,6 +99,7 @@ Documentation for filesystem implementations.
|
||||
isofs
|
||||
nilfs2
|
||||
nfs/index
|
||||
ntfs
|
||||
ntfs3
|
||||
ocfs2
|
||||
ocfs2-online-filecheck
|
||||
|
||||
@@ -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=<BOOL>
|
||||
show_sys_files=<BOOL> 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=<BOOL> 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=<BOOL> If nocase is specified, treat filenames
|
||||
case-insensitively.
|
||||
|
||||
disable_sparse=<BOOL> 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=<BOOL> 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=<BOOL> Make NTFS system files (e.g. $MFT, $LogFile,
|
||||
$Bitmap, $UpCase, etc.) immutable to user initiated
|
||||
modifications for extra safety. Default is off.
|
||||
|
||||
nohidden=<BOOL> Hide files and directories marked with the Windows
|
||||
"hidden" attribute. By default hidden items are
|
||||
shown.
|
||||
|
||||
hide_dot_files=<BOOL> 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=<BOOL> 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=<BOOL> Issue block device discard for clusters freed on
|
||||
file deletion/truncation to inform underlying
|
||||
storage.
|
||||
======================= ====================================================
|
||||
+22
-13
@@ -4055,7 +4055,7 @@ S: Maintained
|
||||
F: crypto/rsa*
|
||||
|
||||
ASYNCHRONOUS TRANSFERS/TRANSFORMS (IOAT) API
|
||||
R: Dan Williams <dan.j.williams@intel.com>
|
||||
R: Dan Williams <djbw@kernel.org>
|
||||
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 <dave@stgolabs.net>
|
||||
M: Jonathan Cameron <jonathan.cameron@huawei.com>
|
||||
M: Jonathan Cameron <jic23@kernel.org>
|
||||
M: Dave Jiang <dave.jiang@intel.com>
|
||||
M: Alison Schofield <alison.schofield@intel.com>
|
||||
M: Vishal Verma <vishal.l.verma@intel.com>
|
||||
M: Ira Weiny <ira.weiny@intel.com>
|
||||
M: Dan Williams <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
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 <jonathan.cameron@huawei.com>
|
||||
M: Jonathan Cameron <jic23@kernel.org>
|
||||
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 <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
M: Vishal Verma <vishal.l.verma@intel.com>
|
||||
M: Dave Jiang <dave.jiang@intel.com>
|
||||
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 <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
R: Matthew Wilcox <willy@infradead.org>
|
||||
R: Jan Kara <jack@suse.cz>
|
||||
L: linux-fsdevel@vger.kernel.org
|
||||
@@ -10597,7 +10597,7 @@ FWCTL SUBSYSTEM
|
||||
M: Dave Jiang <dave.jiang@intel.com>
|
||||
M: Jason Gunthorpe <jgg@nvidia.com>
|
||||
M: Saeed Mahameed <saeedm@nvidia.com>
|
||||
R: Jonathan Cameron <Jonathan.Cameron@huawei.com>
|
||||
R: Jonathan Cameron <jic23@kernel.org>
|
||||
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 <dave.jiang@intel.com>
|
||||
R: Dan Williams <dan.j.williams@intel.com>
|
||||
R: Dan Williams <djbw@kernel.org>
|
||||
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 <vishal.l.verma@intel.com>
|
||||
M: Dan Williams <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
M: Dave Jiang <dave.jiang@intel.com>
|
||||
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 <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
M: Vishal Verma <vishal.l.verma@intel.com>
|
||||
M: Dave Jiang <dave.jiang@intel.com>
|
||||
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 <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
M: Vishal Verma <vishal.l.verma@intel.com>
|
||||
M: Dave Jiang <dave.jiang@intel.com>
|
||||
M: Ira Weiny <ira.weiny@intel.com>
|
||||
@@ -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 <linkinjeon@kernel.org>
|
||||
M: Hyunchul Lee <hyc.lee@gmail.com>
|
||||
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 <almaz.alexandrovich@paragon-software.com>
|
||||
L: ntfs3@lists.linux.dev
|
||||
@@ -25369,7 +25378,7 @@ F: drivers/staging/
|
||||
|
||||
STANDALONE CACHE CONTROLLER DRIVERS
|
||||
M: Conor Dooley <conor@kernel.org>
|
||||
M: Jonathan Cameron <jonathan.cameron@huawei.com>
|
||||
M: Jonathan Cameron <jic23@kernel.org>
|
||||
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 <dan.j.williams@intel.com>
|
||||
M: Dan Williams <djbw@kernel.org>
|
||||
L: linux-coco@lists.linux.dev
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/configfs-tsm-report
|
||||
|
||||
+15
-26
@@ -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);
|
||||
|
||||
|
||||
+14
-35
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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");
|
||||
/*
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <asm/asm-extable.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/cpufeature.h>
|
||||
#include <asm/debug-monitors.h>
|
||||
#include <asm/insn.h>
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/filter.h>
|
||||
#include <asm/cacheflush.h>
|
||||
|
||||
/* 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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+23
-50
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include <linux/timer.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/ipmi_ssif_bmc.h>
|
||||
#if IS_ENABLED(CONFIG_SSIF_IPMI_BMC_KUNIT_TEST)
|
||||
#include <kunit/test.h>
|
||||
#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 <quan@os.amperecomputing.com>");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__ */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
+161
-318
@@ -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;
|
||||
|
||||
@@ -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 <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <cxlmem.h>
|
||||
#include <cxl.h>
|
||||
#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));
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <cxlmem.h>
|
||||
#include <cxl.h>
|
||||
#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;
|
||||
}
|
||||
@@ -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");
|
||||
+22
-101
@@ -12,6 +12,7 @@
|
||||
#include <linux/node.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/range.h>
|
||||
#include <cxl/cxl.h>
|
||||
|
||||
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);
|
||||
|
||||
+4
-86
@@ -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;
|
||||
|
||||
@@ -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__ */
|
||||
+26
-77
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
+17
-3
@@ -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);
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#ifndef __DAX_BUS_H__
|
||||
#define __DAX_BUS_H__
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/range.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
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"
|
||||
|
||||
+27
-1
@@ -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");
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/dax.h>
|
||||
#include <linux/mm.h>
|
||||
#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)
|
||||
|
||||
+102
-18
@@ -3,6 +3,7 @@
|
||||
#include <linux/memregion.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dax.h>
|
||||
#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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
+235
-79
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <linux/io.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
/*
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-mux.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
@@ -11,10 +12,16 @@
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+393
-137
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ static const struct acpi_device_id usbio_i2c_acpi_hids[] = {
|
||||
{ "INTC10B6" }, /* LNL */
|
||||
{ "INTC10D2" }, /* MTL-CVF */
|
||||
{ "INTC10E3" }, /* PTL */
|
||||
{ "INTC1118" }, /* NVL */
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <linux/platform_data/i2c-xiic.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/iopoll.h>
|
||||
@@ -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 = {
|
||||
|
||||
+4
-13
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
|
||||
*
|
||||
* Authors:
|
||||
* Serge Semin <Sergey.Semin@baikalelectronics.ru>
|
||||
*
|
||||
* Baikal-T1 Physically Mapped Internal ROM driver
|
||||
*/
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/xip.h>
|
||||
#include <linux/mux/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#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
|
||||
@@ -42,7 +42,6 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Virtual concat MTD device driver
|
||||
*
|
||||
* Copyright (C) 2018 Bernhard Frauendienst
|
||||
* Author: Bernhard Frauendienst <kernel@nospam.obeliks.de>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include "mtdcore.h"
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/concat.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
+1
-16
@@ -20,18 +20,6 @@
|
||||
|
||||
#include <asm/div64.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;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/concat.h>
|
||||
|
||||
#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)
|
||||
|
||||
Loaded 100 of 505 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user