diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-04 20:07:20 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-04 20:07:20 -0700 |
commit | 06dd3dfeea60e2a6457a6aedf97afc8e6d2ba497 (patch) | |
tree | 1d8b9efbd7cd3dbb5d7b7663d7fd2de61b26f453 | |
parent | 38047d5c269bbdedf900fc86954913f3dffa01f1 (diff) | |
parent | 86f690e8bfd124c38940e7ad58875ef383003348 (diff) |
Merge tag 'char-misc-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc updates from Greg KH:
"Here is the big set of char/misc driver patches for 4.17-rc1.
There are a lot of little things in here, nothing huge, but all
important to the different hardware types involved:
- thunderbolt driver updates
- parport updates (people still care...)
- nvmem driver updates
- mei updates (as always)
- hwtracing driver updates
- hyperv driver updates
- extcon driver updates
- ... and a handful of even smaller driver subsystem and individual
driver updates
All of these have been in linux-next with no reported issues"
* tag 'char-misc-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (149 commits)
hwtracing: Add HW tracing support menu
intel_th: Add ACPI glue layer
intel_th: Allow forcing host mode through drvdata
intel_th: Pick up irq number from resources
intel_th: Don't touch switch routing in host mode
intel_th: Use correct method of finding hub
intel_th: Add SPDX GPL-2.0 header to replace GPLv2 boilerplate
stm class: Make dummy's master/channel ranges configurable
stm class: Add SPDX GPL-2.0 header to replace GPLv2 boilerplate
MAINTAINERS: Bestow upon myself the care for drivers/hwtracing
hv: add SPDX license id to Kconfig
hv: add SPDX license to trace
Drivers: hv: vmbus: do not mark HV_PCIE as perf_device
Drivers: hv: vmbus: respect what we get from hv_get_synint_state()
/dev/mem: Avoid overwriting "err" in read_mem()
eeprom: at24: use SPDX identifier instead of GPL boiler-plate
eeprom: at24: simplify the i2c functionality checking
eeprom: at24: fix a line break
eeprom: at24: tweak newlines
eeprom: at24: refactor at24_probe()
...
141 files changed, 3377 insertions, 1207 deletions
diff --git a/Documentation/ABI/stable/sysfs-bus-vmbus b/Documentation/ABI/stable/sysfs-bus-vmbus index e46be65d0e1d..0c9d9dcd2151 100644 --- a/Documentation/ABI/stable/sysfs-bus-vmbus +++ b/Documentation/ABI/stable/sysfs-bus-vmbus @@ -132,3 +132,10 @@ KernelVersion: 4.16 Contact: Stephen Hemminger <sthemmin@microsoft.com> Description: Monitor bit associated with channel Users: Debugging tools and userspace drivers + +What: /sys/bus/vmbus/devices/vmbus_*/channels/NN/ring +Date: January. 2018 +KernelVersion: 4.16 +Contact: Stephen Hemminger <sthemmin@microsoft.com> +Description: Binary file created by uio_hv_generic for ring buffer +Users: Userspace drivers diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 93798c02e28b..151584a1f950 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -1,3 +1,26 @@ +What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl +Date: Jun 2018 +KernelVersion: 4.17 +Contact: thunderbolt-software@lists.01.org +Description: Holds a comma separated list of device unique_ids that + are allowed to be connected automatically during system + startup (e.g boot devices). The list always contains + maximum supported number of unique_ids where unused + entries are empty. This allows the userspace software + to determine how many entries the controller supports. + If there are multiple controllers, each controller has + its own ACL list and size may be different between the + controllers. + + System BIOS may have an option "Preboot ACL" or similar + that needs to be selected before this list is taken into + consideration. + + Software always updates a full list in each write. + + If a device is authorized automatically during boot its + boot attribute is set to 1. + What: /sys/bus/thunderbolt/devices/.../domainX/security Date: Sep 2017 KernelVersion: 4.13 @@ -12,6 +35,9 @@ Description: This attribute holds current Thunderbolt security level minimum. User needs to authorize each device. dponly: Automatically tunnel Display port (and USB). No PCIe tunnels are created. + usbonly: Automatically tunnel USB controller of the + connected Thunderbolt dock (and Display Port). All + PCIe links downstream of the dock are removed. What: /sys/bus/thunderbolt/devices/.../authorized Date: Sep 2017 @@ -38,6 +64,13 @@ Description: This attribute is used to authorize Thunderbolt devices the device did not contain a key at all, and EKEYREJECTED if the challenge response did not match. +What: /sys/bus/thunderbolt/devices/.../boot +Date: Jun 2018 +KernelVersion: 4.17 +Contact: thunderbolt-software@lists.01.org +Description: This attribute contains 1 if Thunderbolt device was already + authorized on boot and 0 otherwise. + What: /sys/bus/thunderbolt/devices/.../key Date: Sep 2017 KernelVersion: 4.13 diff --git a/Documentation/ABI/testing/sysfs-class-mei b/Documentation/ABI/testing/sysfs-class-mei index 5096a82f4cde..81ff6abf9673 100644 --- a/Documentation/ABI/testing/sysfs-class-mei +++ b/Documentation/ABI/testing/sysfs-class-mei @@ -45,3 +45,12 @@ Contact: Tomas Winkler <tomas.winkler@intel.com> Description: Display the driver HBM protocol version. The HBM protocol version supported by the driver. + +What: /sys/class/mei/meiN/tx_queue_limit +Date: Jan 2018 +KernelVersion: 4.16 +Contact: Tomas Winkler <tomas.winkler@intel.com> +Description: Configure tx queue limit + + Set maximal number of pending writes + per opened session. diff --git a/Documentation/ABI/testing/sysfs-driver-fsi-master-gpio b/Documentation/ABI/testing/sysfs-driver-fsi-master-gpio new file mode 100644 index 000000000000..1f29c8843cfd --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-fsi-master-gpio @@ -0,0 +1,10 @@ +What: /sys/bus/platform/devices/[..]/fsi-master-gpio/external_mode +Date: Feb 2018 +KernelVersion: 4.17 +Contact: jk@ozlabs.org +Description: + Controls access arbitration for GPIO-based FSI master. A + value of 0 (the default) sets normal mode, where the + driver performs FSI bus transactions, 1 sets external mode, + where the FSI bus is driven externally (for example, by + a debug device). diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 9948ec36a204..35fccba6a9a6 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -21,11 +21,11 @@ vulnerable to DMA attacks. Security levels and how to use them ----------------------------------- Starting with Intel Falcon Ridge Thunderbolt controller there are 4 -security levels available. The reason for these is the fact that the -connected devices can be DMA masters and thus read contents of the host -memory without CPU and OS knowing about it. There are ways to prevent -this by setting up an IOMMU but it is not always available for various -reasons. +security levels available. Intel Titan Ridge added one more security level +(usbonly). The reason for these is the fact that the connected devices can +be DMA masters and thus read contents of the host memory without CPU and OS +knowing about it. There are ways to prevent this by setting up an IOMMU but +it is not always available for various reasons. The security levels are as follows: @@ -52,6 +52,11 @@ The security levels are as follows: USB. No PCIe tunneling is done. In BIOS settings this is typically called *Display Port Only*. + usbonly + The firmware automatically creates tunnels for the USB controller and + Display Port in a dock. All PCIe links downstream of the dock are + removed. + The current security level can be read from ``/sys/bus/thunderbolt/devices/domainX/security`` where ``domainX`` is the Thunderbolt domain the host controller manages. There is typically diff --git a/Documentation/devicetree/bindings/connector/samsung,usb-connector-11pin.txt b/Documentation/devicetree/bindings/connector/samsung,usb-connector-11pin.txt new file mode 100644 index 000000000000..22256e295a7a --- /dev/null +++ b/Documentation/devicetree/bindings/connector/samsung,usb-connector-11pin.txt @@ -0,0 +1,49 @@ +Samsung micro-USB 11-pin connector +================================== + +Samsung micro-USB 11-pin connector is an extension of micro-USB connector. +It is present in multiple Samsung mobile devices. +It has additional pins to route MHL traffic simultanously with USB. + +The bindings are superset of usb-connector bindings for micro-USB connector[1]. + +Required properties: +- compatible: must be: "samsung,usb-connector-11pin", "usb-b-connector", +- type: must be "micro". + +Required nodes: +- any data bus to the connector should be modeled using the OF graph bindings + specified in bindings/graph.txt, unless the bus is between parent node and + the connector. Since single connector can have multpile data buses every bus + has assigned OF graph port number as follows: + 0: High Speed (HS), + 3: Mobile High-Definition Link (MHL), specific to 11-pin Samsung micro-USB. + +[1]: bindings/connector/usb-connector.txt + +Example +------- + +Micro-USB connector with HS lines routed via controller (MUIC) and MHL lines +connected to HDMI-MHL bridge (sii8620): + +muic-max77843@66 { + ... + usb_con: connector { + compatible = "samsung,usb-connector-11pin", "usb-b-connector"; + label = "micro-USB"; + type = "micro"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@3 { + reg = <3>; + usb_con_mhl: endpoint { + remote-endpoint = <&sii8620_mhl>; + }; + }; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/connector/usb-connector.txt b/Documentation/devicetree/bindings/connector/usb-connector.txt new file mode 100644 index 000000000000..e1463f14af38 --- /dev/null +++ b/Documentation/devicetree/bindings/connector/usb-connector.txt @@ -0,0 +1,75 @@ +USB Connector +============= + +USB connector node represents physical USB connector. It should be +a child of USB interface controller. + +Required properties: +- compatible: describes type of the connector, must be one of: + "usb-a-connector", + "usb-b-connector", + "usb-c-connector". + +Optional properties: +- label: symbolic name for the connector, +- type: size of the connector, should be specified in case of USB-A, USB-B + non-fullsize connectors: "mini", "micro". + +Required nodes: +- any data bus to the connector should be modeled using the OF graph bindings + specified in bindings/graph.txt, unless the bus is between parent node and + the connector. Since single connector can have multpile data buses every bus + has assigned OF graph port number as follows: + 0: High Speed (HS), present in all connectors, + 1: Super Speed (SS), present in SS capable connectors, + 2: Sideband use (SBU), present in USB-C. + +Examples +-------- + +1. Micro-USB connector with HS lines routed via controller (MUIC): + +muic-max77843@66 { + ... + usb_con: connector { + compatible = "usb-b-connector"; + label = "micro-USB"; + type = "micro"; + }; +}; + +2. USB-C connector attached to CC controller (s2mm005), HS lines routed +to companion PMIC (max77865), SS lines to USB3 PHY and SBU to DisplayPort. +DisplayPort video lines are routed to the connector via SS mux in USB3 PHY. + +ccic: s2mm005@33 { + ... + usb_con: connector { + compatible = "usb-c-connector"; + label = "USB-C"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_con_hs: endpoint { + remote-endpoint = <&max77865_usbc_hs>; + }; + }; + port@1 { + reg = <1>; + usb_con_ss: endpoint { + remote-endpoint = <&usbdrd_phy_ss>; + }; + }; + port@2 { + reg = <2>; + usb_con_sbu: endpoint { + remote-endpoint = <&dp_aux>; + }; + }; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/fsi/fsi.txt b/Documentation/devicetree/bindings/fsi/fsi.txt new file mode 100644 index 000000000000..ab516c673a4b --- /dev/null +++ b/Documentation/devicetree/bindings/fsi/fsi.txt @@ -0,0 +1,151 @@ +FSI bus & engine generic device tree bindings +============================================= + +The FSI bus is probe-able, so the OS is able to enumerate FSI slaves, and +engines within those slaves. However, we have a facility to match devicetree +nodes to probed engines. This allows for fsi engines to expose non-probeable +busses, which are then exposed by the device tree. For example, an FSI engine +that is an I2C master - the I2C bus can be described by the device tree under +the engine's device tree node. + +FSI masters may require their own DT nodes (to describe the master HW itself); +that requirement is defined by the master's implementation, and is described by +the fsi-master-* binding specifications. + +Under the masters' nodes, we can describe the bus topology using nodes to +represent the FSI slaves and their slave engines. As a basic outline: + + fsi-master { + /* top-level of FSI bus topology, bound to an FSI master driver and + * exposes an FSI bus */ + + fsi-slave@<link,id> { + /* this node defines the FSI slave device, and is handled + * entirely with FSI core code */ + + fsi-slave-engine@<addr> { + /* this node defines the engine endpoint & address range, which + * is bound to the relevant fsi device driver */ + ... + }; + + fsi-slave-engine@<addr> { + ... + }; + + }; + }; + +Note that since the bus is probe-able, some (or all) of the topology may +not be described; this binding only provides an optional facility for +adding subordinate device tree nodes as children of FSI engines. + +FSI masters +----------- + +FSI master nodes declare themselves as such with the "fsi-master" compatible +value. It's likely that an implementation-specific compatible value will +be needed as well, for example: + + compatible = "fsi-master-gpio", "fsi-master"; + +Since the master nodes describe the top-level of the FSI topology, they also +need to declare the FSI-standard addressing scheme. This requires two cells for +addresses (link index and slave ID), and no size: + + #address-cells = <2>; + #size-cells = <0>; + +An optional boolean property can be added to indicate that a particular master +should not scan for connected devices at initialization time. This is +necessary in cases where a scan could cause arbitration issues with other +masters that may be present on the bus. + + no-scan-on-init; + +FSI slaves +---------- + +Slaves are identified by a (link-index, slave-id) pair, so require two cells +for an address identifier. Since these are not a range, no size cells are +required. For an example, a slave on link 1, with ID 2, could be represented +as: + + cfam@1,2 { + reg = <1 2>; + [...]; + } + +Each slave provides an address-space, under which the engines are accessible. +That address space has a maximum of 23 bits, so we use one cell to represent +addresses and sizes in the slave address space: + + #address-cells = <1>; + #size-cells = <1>; + + +FSI engines (devices) +--------------------- + +Engines are identified by their address under the slaves' address spaces. We +use a single cell for address and size. Engine nodes represent the endpoint +FSI device, and are passed to those FSI device drivers' ->probe() functions. + +For example, for a slave using a single 0x400-byte page starting at address +0xc00: + + engine@c00 { + reg = <0xc00 0x400>; + }; + + +Full example +------------ + +Here's an example that illustrates: + - an FSI master + - connected to an FSI slave + - that contains an engine that is an I2C master + - connected to an I2C EEPROM + +The FSI master may be connected to additional slaves, and slaves may have +additional engines, but they don't necessarily need to be describe in the +device tree if no extra platform information is required. + + /* The GPIO-based FSI master node, describing the top level of the + * FSI bus + */ + gpio-fsi { + compatible = "fsi-master-gpio", "fsi-master"; + #address-cells = <2>; + #size-cells = <0>; + + /* A FSI slave (aka. CFAM) at link 0, ID 0. */ + cfam@0,0 { + reg = <0 0>; + #address-cells = <1>; + #size-cells = <1>; + + /* FSI engine at 0xc00, using a single page. In this example, + * it's an I2C master controller, so subnodes describe the + * I2C bus. + */ + i2c-controller@c00 { + reg = <0xc00 0x400>; + + /* Engine-specific data. In this case, we're describing an + * I2C bus, so we're conforming to the generic I2C binding + */ + compatible = "some-vendor,fsi-i2c-controller"; + #address-cells = <1>; + #size-cells = <1>; + + /* I2C endpoint device: an Atmel EEPROM */ + eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + pagesize = <64>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt index 514d82ced95b..69aadee00d5f 100644 --- a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt +++ b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt @@ -109,9 +109,50 @@ lpc: lpc@1e789000 { }; }; +BMC Node Children +================== + + Host Node Children ================== +LPC Host Interface Controller +------------------- + +The LPC Host Interface Controller manages functions exposed to the host such as +LPC firmware hub cycles, configuration of the LPC-to-AHB mapping, UART +management and bus snoop configuration. + +Required properties: + +- compatible: One of: + "aspeed,ast2400-lpc-ctrl"; + "aspeed,ast2500-lpc-ctrl"; + +- reg: contains offset/length values of the host interface controller + memory regions + +- clocks: contains a phandle to the syscon node describing the clocks. + There should then be one cell representing the clock to use + +- memory-region: A phandle to a reserved_memory region to be used for the LPC + to AHB mapping + +- flash: A phandle to the SPI flash controller containing the flash to + be exposed over the LPC to AHB mapping + +Example: + +lpc-host@80 { + lpc_ctrl: lpc-ctrl@0 { + compatible = "aspeed,ast2500-lpc-ctrl"; + reg = <0x0 0x80>; + clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + memory-region = <&flash_memory>; + flash = <&spi>; + }; +}; + LPC Host Controller ------------------- diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt index f162c72b4e36..729f6747813b 100644 --- a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt +++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt @@ -11,17 +11,32 @@ Required properties: "fsl,imx6ul-ocotp" (i.MX6UL), "fsl,imx7d-ocotp" (i.MX7D/S), followed by "syscon". +- #address-cells : Should be 1 +- #size-cells : Should be 1 - reg: Should contain the register base and length. - clocks: Should contain a phandle pointing to the gated peripheral clock. Optional properties: - read-only: disable write access -Example: +Optional Child nodes: + +- Data cells of ocotp: + Detailed bindings are described in bindings/nvmem/nvmem.txt +Example: ocotp: ocotp@21bc000 { - compatible = "fsl,imx6q-ocotp", "syscon"; + #address-cells = <1>; + #size-cells = <1>; + compatible = "fsl,imx6sx-ocotp", "syscon"; reg = <0x021bc000 0x4000>; - clocks = <&clks IMX6QDL_CLK_IIM>; - read-only; + clocks = <&clks IMX6SX_CLK_OCOTP>; + + tempmon_calib: calib@38 { + reg = <0x38 4>; + }; + + tempmon_temp_grade: temp-grade@20 { + reg = <0x20 4>; + }; }; diff --git a/Documentation/devicetree/bindings/nvmem/snvs-lpgpr.txt b/Documentation/devicetree/bindings/nvmem/snvs-lpgpr.txt index 20bc49b49799..3cb170896658 100644 --- a/Documentation/devicetree/bindings/nvmem/snvs-lpgpr.txt +++ b/Documentation/devicetree/bindings/nvmem/snvs-lpgpr.txt @@ -1,5 +1,5 @@ Device tree bindings for Low Power General Purpose Register found in i.MX6Q/D -Secure Non-Volatile Storage. +and i.MX7 Secure Non-Volatile Storage. This DT node should be represented as a sub-node of a "syscon", "simple-mfd" node. @@ -8,6 +8,7 @@ Required properties: - compatible: should be one of the fallowing variants: "fsl,imx6q-snvs-lpgpr" for Freescale i.MX6Q/D/DL/S "fsl,imx6ul-snvs-lpgpr" for Freescale i.MX6UL + "fsl,imx7d-snvs-lpgpr" for Freescale i.MX7D/S Example: snvs: snvs@020cc000 { diff --git a/Documentation/driver-api/uio-howto.rst b/Documentation/driver-api/uio-howto.rst index 693e3bd84e79..92056c20e070 100644 --- a/Documentation/driver-api/uio-howto.rst +++ b/Documentation/driver-api/uio-howto.rst @@ -709,6 +709,11 @@ The vmbus device regions are mapped into uio device resources: 3) Network receive buffer region 4) Network send buffer region +If a subchannel is created by a request to host, then the uio_hv_generic +device driver will create a sysfs binary file for the per-channel ring buffer. +For example: + /sys/bus/vmbus/devices/3811fe4d-0fa0-4b62-981a-74fc1084c757/channels/21/ring + Further information =================== diff --git a/MAINTAINERS b/MAINTAINERS index 30bc6e417a03..89fd8040e295 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6212,6 +6212,11 @@ F: Documentation/hw_random.txt F: drivers/char/hw_random/ F: include/linux/hw_random.h +HARDWARE TRACING FACILITIES +M: Alexander Shishkin <alexander.shishkin@linux.intel.com> +S: Maintained +F: drivers/hwtracing/ + HARDWARE SPINLOCK CORE M: Ohad Ben-Cohen <ohad@wizery.com> M: Bjorn Andersson <bjorn.andersson@linaro.org> @@ -8140,7 +8145,7 @@ F: drivers/*/*/*pasemi* LINUX KERNEL DUMP TEST MODULE (LKDTM) M: Kees Cook <keescook@chromium.org> S: Maintained -F: drivers/misc/lkdtm* +F: drivers/misc/lkdtm/* LINUX KERNEL MEMORY CONSISTENCY MODEL (LKMM) M: Alan Stern <stern@rowland.harvard.edu> diff --git a/arch/x86/entry/entry_32.S b/arch/x86/entry/entry_32.S index 6ad064c8cf35..bef8e2b202a8 100644 --- a/arch/x86/entry/entry_32.S +++ b/arch/x86/entry/entry_32.S @@ -902,6 +902,9 @@ BUILD_INTERRUPT3(hyperv_callback_vector, HYPERVISOR_CALLBACK_VECTOR, BUILD_INTERRUPT3(hyperv_reenlightenment_vector, HYPERV_REENLIGHTENMENT_VECTOR, hyperv_reenlightenment_intr) +BUILD_INTERRUPT3(hv_stimer0_callback_vector, HYPERV_STIMER0_VECTOR, + hv_stimer0_vector_handler) + #endif /* CONFIG_HYPERV */ ENTRY(page_fault) diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S index 936e19642eab..b0a4649e55ce 100644 --- a/arch/x86/entry/entry_64.S +++ b/arch/x86/entry/entry_64.S @@ -1140,6 +1140,9 @@ apicinterrupt3 HYPERVISOR_CALLBACK_VECTOR \ apicinterrupt3 HYPERV_REENLIGHTENMENT_VECTOR \ hyperv_reenlightenment_vector hyperv_reenlightenment_intr + +apicinterrupt3 HYPERV_STIMER0_VECTOR \ + hv_stimer0_callback_vector hv_stimer0_vector_handler #endif /* CONFIG_HYPERV */ idtentry debug do_debug has_error_code=0 paranoid=1 shift_ist=DEBUG_STACK diff --git a/arch/x86/include/asm/hardirq.h b/arch/x86/include/asm/hardirq.h index 7c341a74ec8c..5ea2afd4c871 100644 --- a/arch/x86/include/asm/hardirq.h +++ b/arch/x86/include/asm/hardirq.h @@ -40,6 +40,7 @@ typedef struct { #endif #if IS_ENABLED(CONFIG_HYPERV) unsigned int irq_hv_reenlightenment_count; + unsigned int hyperv_stimer0_count; #endif } ____cacheline_aligned irq_cpustat_t; diff --git a/arch/x86/include/asm/irq_vectors.h b/arch/x86/include/asm/irq_vectors.h index e71c1120426b..404c5fdff859 100644 --- a/arch/x86/include/asm/irq_vectors.h +++ b/arch/x86/include/asm/irq_vectors.h @@ -106,9 +106,10 @@ #if IS_ENABLED(CONFIG_HYPERV) #define HYPERV_REENLIGHTENMENT_VECTOR 0xee +#define HYPERV_STIMER0_VECTOR 0xed #endif -#define LOCAL_TIMER_VECTOR 0xed +#define LOCAL_TIMER_VECTOR 0xec #define NR_VECTORS 256 diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h index 25283f7eb299..e73c4d0c06ad 100644 --- a/arch/x86/include/asm/mshyperv.h +++ b/arch/x86/include/asm/mshyperv.h @@ -173,6 +173,19 @@ void hv_remove_kexec_handler(void); void hv_setup_crash_handler(void (*handler)(struct pt_regs *regs)); void hv_remove_crash_handler(void); +/* + * Routines for stimer0 Direct Mode handling. + * On x86/x64, there are no percpu actions to take. + */ +void hv_stimer0_vector_handler(struct pt_regs *regs); +void hv_stimer0_callback_vector(void); +int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void)); +void hv_remove_stimer0_irq(int irq); + +static inline void hv_enable_stimer0_percpu_irq(int irq) {} +static inline void hv_disable_stimer0_percpu_irq(int irq) {} + + #if IS_ENABLED(CONFIG_HYPERV) extern struct clocksource *hyperv_cs; extern void *hv_hypercall_pg; diff --git a/arch/x86/include/uapi/asm/hyperv.h b/arch/x86/include/uapi/asm/hyperv.h index 099414345865..6c0c3a3b631c 100644 --- a/arch/x86/include/uapi/asm/hyperv.h +++ b/arch/x86/include/uapi/asm/hyperv.h @@ -77,6 +77,9 @@ /* Crash MSR available */ #define HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE (1 << 10) +/* stimer Direct Mode is available */ +#define HV_X64_STIMER_DIRECT_MODE_AVAILABLE (1 << 19) + /* * Feature identification: EBX indicates which flags were specified at * partition creation. The format is the same as the partition creation diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index 9340f41ce8d3..4488cf0dd499 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -37,6 +37,7 @@ EXPORT_SYMBOL_GPL(ms_hyperv); #if IS_ENABLED(CONFIG_HYPERV) static void (*vmbus_handler)(void); +static void (*hv_stimer0_handler)(void); static void (*hv_kexec_handler)(void); static void (*hv_crash_handler)(struct pt_regs *regs); @@ -69,6 +70,41 @@ void hv_remove_vmbus_irq(void) EXPORT_SYMBOL_GPL(hv_setup_vmbus_irq); EXPORT_SYMBOL_GPL(hv_remove_vmbus_irq); +/* + * Routines to do per-architecture handling of stimer0 + * interrupts when in Direct Mode + */ + +__visible void __irq_entry hv_stimer0_vector_handler(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + entering_irq(); + inc_irq_stat(hyperv_stimer0_count); + if (hv_stimer0_handler) + hv_stimer0_handler(); + ack_APIC_irq(); + + exiting_irq(); + set_irq_regs(old_regs); +} + +int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void)) +{ + *vector = HYPERV_STIMER0_VECTOR; + *irq = 0; /* Unused on x86/x64 */ + hv_stimer0_handler = handler; + return 0; +} +EXPORT_SYMBOL_GPL(hv_setup_stimer0_irq); + +void hv_remove_stimer0_irq(int irq) +{ + /* We have no way to deallocate the interrupt gate */ + hv_stimer0_handler = NULL; +} +EXPORT_SYMBOL_GPL(hv_remove_stimer0_irq); + void hv_setup_kexec_handler(void (*handler)(void)) { hv_kexec_handler = handler; @@ -257,6 +293,10 @@ static void __init ms_hyperv_init_platform(void) alloc_intr_gate(HYPERV_REENLIGHTENMENT_VECTOR, hyperv_reenlightenment_vector); + /* Setup the IDT for stimer0 */ + if (ms_hyperv.misc_features & HV_X64_STIMER_DIRECT_MODE_AVAILABLE) + alloc_intr_gate(HYPERV_STIMER0_VECTOR, + hv_stimer0_callback_vector); #endif } diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index 45fb4d2565f8..328d027d829d 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -150,6 +150,13 @@ int arch_show_interrupts(struct seq_file *p, int prec) irq_stats(j)->irq_hv_reenlightenment_count); seq_puts(p, " Hyper-V reenlightenment interrupts\n"); } + if (test_bit(HYPERV_STIMER0_VECTOR, system_vectors)) { + seq_printf(p, "%*s: ", prec, "HVS"); + for_each_online_cpu(j) + seq_printf(p, "%10u ", + irq_stats(j)->hyperv_stimer0_count); + seq_puts(p, " Hyper-V stimer0 interrupts\n"); + } #endif seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count)); #if defined(CONFIG_X86_IO_APIC) diff --git a/block/genhd.c b/block/genhd.c index 9656f9e9f99e..dc7e089373b9 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -308,19 +308,22 @@ void blkdev_show(struct seq_file *seqf, off_t offset) /** * register_blkdev - register a new block device * - * @major: the requested major device number [1..255]. If @major = 0, try to - * allocate any unused major number. + * @major: the requested major device number [1..BLKDEV_MAJOR_MAX-1]. If + * @major = 0, try to allocate any unused major number. * @name: the name of the new block device as a zero terminated string * * The @name must be unique within the system. * * The return value depends on the @major input parameter: * - * - if a major device number was requested in range [1..255] then the - * function returns zero on success, or a negative error code + * - if a major device number was requested in range [1..BLKDEV_MAJOR_MAX-1] + * then the function returns zero on success, or a negative error code * - if any unused major number was requested with @major = 0 parameter * then the return value is the allocated major number in range - * [1..255] or a negative error code otherwise + * [1..BLKDEV_MAJOR_MAX-1] or a negative error code otherwise + * + * See Documentation/admin-guide/devices.txt for the list of allocated + * major numbers. */ int register_blkdev(unsigned int major, const char *name) { @@ -347,8 +350,8 @@ int register_blkdev(unsigned int major, const char *name) } if (major >= BLKDEV_MAJOR_MAX) { - pr_err("register_blkdev: major requested (%d) is greater than the maximum (%d) for %s\n", - major, BLKDEV_MAJOR_MAX, name); + pr_err("register_blkdev: major requested (%u) is greater than the maximum (%u) for %s\n", + major, BLKDEV_MAJOR_MAX-1, name); ret = -EINVAL; goto out; @@ -375,7 +378,7 @@ int register_blkdev(unsigned int major, const char *name) ret = -EBUSY; if (ret < 0) { - printk("register_blkdev: cannot get major %d for %s\n", + printk("register_blkdev: cannot get major %u for %s\n", major, name); kfree(p); } diff --git a/drivers/Kconfig b/drivers/Kconfig index 879dc0604cba..95b9ccc08165 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -199,9 +199,7 @@ source "drivers/dax/Kconfig" source "drivers/nvmem/Kconfig" -source "drivers/hwtracing/stm/Kconfig" - -source "drivers/hwtracing/intel_th/Kconfig" +source "drivers/hwtracing/Kconfig" source "drivers/fpga/Kconfig" diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 40947a796666..e538061eadcb 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -236,7 +236,7 @@ source "drivers/char/hw_random/Kconfig" config NVRAM tristate "/dev/nvram support" - depends on ATARI || X86 || (ARM && RTC_DRV_CMOS) || GENERIC_NVRAM + depends on ATARI || X86 || GENERIC_NVRAM ---help--- If you say Y here and create a character special file /dev/nvram with major number 10 and minor number 144 using mknod ("man mknod"), diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 052011bcf100..ffeb60d3434c 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -137,7 +137,7 @@ static ssize_t read_mem(struct file *file, char __user *buf, while (count > 0) { unsigned long remaining; - int allowed; + int allowed, probe; sz = size_inside_page(p, count); @@ -160,9 +160,9 @@ static ssize_t read_mem(struct file *file, char __user *buf, if (!ptr) goto failed; - err = probe_kernel_read(bounce, ptr, sz); + probe = probe_kernel_read(bounce, ptr, sz); unxlate_dev_mem_ptr(p, ptr); - if (err) + if (probe) goto failed; remaining = copy_to_user(buf, bounce, sz); diff --git a/drivers/char/xillybus/xillybus_pcie.c b/drivers/char/xillybus/xillybus_pcie.c index dff2d1538164..05e5324f60bd 100644 --- a/drivers/char/xillybus/xillybus_pcie.c +++ b/drivers/char/xillybus/xillybus_pcie.c @@ -24,7 +24,6 @@ MODULE_LICENSE("GPL v2"); #define PCI_DEVICE_ID_XILLYBUS 0xebeb -#define PCI_VENDOR_ID_ALTERA 0x1172 #define PCI_VENDOR_ID_ACTEL 0x11aa #define PCI_VENDOR_ID_LATTICE 0x1204 diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index ab770adcca7e..13ba3a6e81d5 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -18,8 +18,6 @@ */ #include <linux/extcon-provider.h> -#include <linux/extcon/extcon-gpio.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/interrupt.h> @@ -29,14 +27,30 @@ #include <linux/slab.h> #include <linux/workqueue.h> +/** + * struct gpio_extcon_data - A simple GPIO-controlled extcon device state container. + * @edev: Extcon device. + * @irq: Interrupt line for the external connector. + * @work: Work fired by the interrupt. + * @debounce_jiffies: Number of jiffies to wait for the GPIO to stabilize, from the debounce + * value. + * @gpiod: GPIO descriptor for this external connector. + * @extcon_id: The unique id of specific external connector. + * @debounce: Debounce time for GPIO IRQ in ms. + * @irq_flags: IRQ Flags (e.g., IRQF_TRIGGER_LOW). + * @check_on_resume: Boolean describing whether to check the state of gpio + * while resuming from sleep. + */ struct gpio_extcon_data { struct extcon_dev *edev; int irq; struct delayed_work work; unsigned long debounce_jiffies; - - struct gpio_desc *id_gpiod; - struct gpio_extcon_pdata *pdata; + struct gpio_desc *gpiod; + unsigned int extcon_id; + unsigned long debounce; + unsigned long irq_flags; + bool check_on_resume; }; static void gpio_extcon_work(struct work_struct *work) @@ -46,11 +60,8 @@ static void gpio_extcon_work(struct work_struct *work) container_of(to_delayed_work(work), struct gpio_extcon_data, work); - state = gpiod_get_value_cansleep(data->id_gpiod); - if (data->pdata->gpio_active_low) - state = !state; - - extcon_set_state_sync(data->edev, data->pdata->extcon_id, state); + state = gpiod_get_value_cansleep(data->gpiod); + extcon_set_state_sync(data->edev, data->extcon_id, state); } static irqreturn_t gpio_irq_handler(int irq, void *dev_id) @@ -62,65 +73,41 @@ static irqreturn_t gpio_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } -static int gpio_extcon_init(struct device *dev, struct gpio_extcon_data *data) -{ - struct gpio_extcon_pdata *pdata = data->pdata; - int ret; - - ret = devm_gpio_request_one(dev, pdata->gpio, GPIOF_DIR_IN, - dev_name(dev)); - if (ret < 0) - return ret; - - data->id_gpiod = gpio_to_desc(pdata->gpio); - if (!data->id_gpiod) - return -EINVAL; - - if (pdata->debounce) { - ret = gpiod_set_debounce(data->id_gpiod, - pdata->debounce * 1000); - if (ret < 0) - data->debounce_jiffies = - msecs_to_jiffies(pdata->debounce); - } - - data->irq = gpiod_to_irq(data->id_gpiod); - if (data->irq < 0) - return data->irq; - - return 0; -} - static int gpio_extcon_probe(struct platform_device *pdev) { - struct gpio_extcon_pdata *pdata = dev_get_platdata(&pdev->dev); struct gpio_extcon_data *data; + struct device *dev = &pdev->dev; int ret; - if (!pdata) - return -EBUSY; - if (!pdata->irq_flags || pdata->extcon_id > EXTCON_NONE) - return -EINVAL; - - data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data), - GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->pdata = pdata; - /* Initialize the gpio */ - ret = gpio_extcon_init(&pdev->dev, data); - if (ret < 0) - return ret; + /* + * FIXME: extcon_id represents the unique identifier of external + * connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id + * is necessary to register the extcon device. But, it's not yet + * developed to get the extcon id from device-tree or others. + * On later, it have to be solved. + */ + if (!data->irq_flags || data->extcon_id > EXTCON_NONE) + return -EINVAL; + + data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN); + if (IS_ERR(data->gpiod)) + return PTR_ERR(data->gpiod); + data->irq = gpiod_to_irq(data->gpiod); + if (data->irq <= 0) + return data->irq; /* Allocate the memory of extcon devie and register extcon device */ - data->edev = devm_extcon_dev_allocate(&pdev->dev, &pdata->extcon_id); + data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id); if (IS_ERR(data->edev)) { - dev_err(&pdev->dev, "failed to allocate extcon device\n"); + dev_err(dev, "failed to allocate extcon device\n"); return -ENOMEM; } - ret = devm_extcon_dev_register(&pdev->dev, data->edev); + ret = devm_extcon_dev_register(dev, data->edev); if (ret < 0) return ret; @@ -130,8 +117,8 @@ static int gpio_extcon_probe(struct platform_device *pdev) * Request the interrupt of gpio to detect whether external connector * is attached or detached. */ - ret = devm_request_any_context_irq(&pdev->dev, data->irq, - gpio_irq_handler, pdata->irq_flags, + ret = devm_request_any_context_irq(dev, data->irq, + gpio_irq_handler, data->irq_flags, pdev->name, data); if (ret < 0) return ret; @@ -158,7 +145,7 @@ static int gpio_extcon_resume(struct device *dev) struct gpio_extcon_data *data; data = dev_get_drvdata(dev); - if (data->pdata->check_on_resume) + if (data->check_on_resume) queue_delayed_work(system_power_efficient_wq, &data->work, data->debounce_jiffies); diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 7c4bc8c44c3f..b7e9ea377d70 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -66,6 +66,8 @@ #define CHT_WC_VBUS_GPIO_CTLO 0x6e2d #define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0) +#define CHT_WC_VBUS_GPIO_CTLO_DRV_OD BIT(4) +#define CHT_WC_VBUS_GPIO_CTLO_DIR_OUT BIT(5) enum cht_wc_usb_id { USB_ID_OTG, @@ -183,14 +185,15 @@ static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, { int ret, val; - val = enable ? CHT_WC_VBUS_GPIO_CTLO_OUTPUT : 0; - /* * The 5V boost converter is enabled through a gpio on the PMIC, since * there currently is no gpio driver we access the gpio reg directly. */ - ret = regmap_update_bits(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, - CHT_WC_VBUS_GPIO_CTLO_OUTPUT, val); + val = CHT_WC_VBUS_GPIO_CTLO_DRV_OD | CHT_WC_VBUS_GPIO_CTLO_DIR_OUT; + if (enable) + val |= CHT_WC_VBUS_GPIO_CTLO_OUTPUT; + + ret = regmap_write(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, val); if (ret) dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret); } diff --git a/drivers/extcon/extcon-intel-int3496.c b/drivers/extcon/extcon-intel-int3496.c index 191e99f06a9a..acaccb128fc4 100644 --- a/drivers/extcon/extcon-intel-int3496.c +++ b/drivers/extcon/extcon-intel-int3496.c @@ -50,7 +50,11 @@ static const struct acpi_gpio_params vbus_gpios = { INT3496_GPIO_VBUS_EN, 0, fal static const struct acpi_gpio_params mux_gpios = { INT3496_GPIO_USB_MUX, 0, false }; static const struct acpi_gpio_mapping acpi_int3496_default_gpios[] = { - { "id-gpios", &id_gpios, 1 }, + /* + * Some platforms have a bug in ACPI GPIO description making IRQ + * GPIO to be output only. Ask the GPIO core to ignore this limit. + */ + { "id-gpios", &id_gpios, 1, ACPI_GPIO_QUIRK_NO_IO_RESTRICTION }, { "vbus-gpios", &vbus_gpios, 1 }, { "mux-gpios", &mux_gpios, 1 }, { }, @@ -112,9 +116,6 @@ static int int3496_probe(struct platform_device *pdev) ret = PTR_ERR(data->gpio_usb_id); dev_err(dev, "can't request USB ID GPIO: %d\n", ret); return ret; - } else if (gpiod_get_direction(data->gpio_usb_id) != GPIOF_DIR_IN) { - dev_warn(dev, FW_BUG "USB ID GPIO not in input mode, fixing\n"); - gpiod_direction_input(data->gpio_usb_id); } data->usb_id_irq = gpiod_to_irq(data->gpio_usb_id); diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index cb38c2747684..8bff5fd18185 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -1336,6 +1336,28 @@ void extcon_dev_unregister(struct extcon_dev *edev) EXPORT_SYMBOL_GPL(extcon_dev_unregister); #ifdef CONFIG_OF + +/* + * extcon_find_edev_by_node - Find the extcon device from devicetree. + * @node : OF node identifying edev + * + * Return the pointer of extcon device if success or ERR_PTR(err) if fail. + */ +struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) +{ + struct extcon_dev *edev; + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(edev, &extcon_dev_list, entry) + if (edev->dev.parent && edev->dev.parent->of_node == node) + goto out; + edev = ERR_PTR(-EPROBE_DEFER); +out: + mutex_unlock(&extcon_dev_list_lock); + + return edev; +} + /* * extcon_get_edev_by_phandle - Get the extcon device from devicetree. * @dev : the instance to the given device @@ -1363,25 +1385,27 @@ struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) return ERR_PTR(-ENODEV); } - mutex_lock(&extcon_dev_list_lock); - list_for_each_entry(edev, &extcon_dev_list, entry) { - if (edev->dev.parent && edev->dev.parent->of_node == node) { - mutex_unlock(&extcon_dev_list_lock); - of_node_put(node); - return edev; - } - } - mutex_unlock(&extcon_dev_list_lock); + edev = extcon_find_edev_by_node(node); of_node_put(node); - return ERR_PTR(-EPROBE_DEFER); + return edev; } + #else + +struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) +{ + return ERR_PTR(-ENOSYS); +} + struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) { return ERR_PTR(-ENOSYS); } + #endif /* CONFIG_OF */ + +EXPORT_SYMBOL_GPL(extcon_find_edev_by_node); EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); /** diff --git a/drivers/fpga/altera-cvp.c b/drivers/fpga/altera-cvp.c index 00e73d28077c..77b04e4b3254 100644 --- a/drivers/fpga/altera-cvp.c +++ b/drivers/fpga/altera-cvp.c @@ -384,8 +384,6 @@ static int altera_cvp_probe(struct pci_dev *pdev, const struct pci_device_id *dev_id); static void altera_cvp_remove(struct pci_dev *pdev); -#define PCI_VENDOR_ID_ALTERA 0x1172 - static struct pci_device_id altera_cvp_id_tbl[] = { { PCI_VDEVICE(ALTERA, PCI_ANY_ID) }, { } diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index 513e35173aaa..a326ed663d3c 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -4,6 +4,7 @@ menuconfig FSI tristate "FSI support" + depends on OF select CRC4 ---help--- FSI - the FRU Support Interface - is a simple bus for low-level diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index e318bf8c623c..4c03d6933646 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -18,6 +18,7 @@ #include <linux/fsi.h> #include <linux/idr.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/slab.h> #include <linux/bitops.h> @@ -142,6 +143,7 @@ static void fsi_device_release(struct device *_device) { struct fsi_device *device = to_fsi_dev(_device); + of_node_put(device->dev.of_node); kfree(device); } @@ -205,7 +207,7 @@ static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) if (rc) return rc; - dev_info(&slave->dev, "status: 0x%08x, sisc: 0x%08x\n", + dev_dbg(&slave->dev, "status: 0x%08x, sisc: 0x%08x\n", be32_to_cpu(stat), be32_to_cpu(irq)); /* clear interrupts */ @@ -334,6 +336,57 @@ extern void fsi_slave_release_range(struct fsi_slave *slave, } EXPORT_SYMBOL_GPL(fsi_slave_release_range); +static bool fsi_device_node_matches(struct device *dev, struct device_node *np, + uint32_t addr, uint32_t size) +{ + unsigned int len, na, ns; + const __be32 *prop; + uint32_t psize; + + na = of_n_addr_cells(np); + ns = of_n_size_cells(np); + + if (na != 1 || ns != 1) + return false; + + prop = of_get_property(np, "reg", &len); + if (!prop || len != 8) + return false; + + if (of_read_number(prop, 1) != addr) + return false; + + psize = of_read_number(prop + 1, 1); + if (psize != size) { + dev_warn(dev, + "node %s matches probed address, but not size (got 0x%x, expected 0x%x)", + of_node_full_name(np), psize, size); + } + + return true; +} + +/* Find a matching node for the slave engine at @address, using @size bytes + * of space. Returns NULL if not found, or a matching node with refcount + * already incremented. + */ +static struct device_node *fsi_device_find_of_node(struct fsi_device *dev) +{ + struct device_node *parent, *np; + + parent = dev_of_node(&dev->slave->dev); + if (!parent) + return NULL; + + for_each_child_of_node(parent, np) { + if (fsi_device_node_matches(&dev->dev, np, + dev->addr, dev->size)) + return np; + } + + return NULL; +} + static int fsi_slave_scan(struct fsi_slave *slave) { uint32_t engine_addr; @@ -402,6 +455,7 @@ static int fsi_slave_scan(struct fsi_slave *slave) dev_set_name(&dev->dev, "%02x:%02x:%02x:%02x", slave->master->idx, slave->link, slave->id, i - 2); + dev->dev.of_node = fsi_device_find_of_node(dev); rc = device_register(&dev->dev); if (rc) { @@ -558,9 +612,53 @@ static void fsi_slave_release(struct device *dev) { struct fsi_slave *slave = to_fsi_slave(dev); + of_node_put(dev->of_node); kfree(slave); } +static bool fsi_slave_node_matches(struct device_node *np, + int link, uint8_t id) +{ + unsigned int len, na, ns; + const __be32 *prop; + + na = of_n_addr_cells(np); + ns = of_n_size_cells(np); + + /* Ensure we have the correct format for addresses and sizes in + * reg properties + */ + if (na != 2 || ns != 0) + return false; + + prop = of_get_property(np, "reg", &len); + if (!prop || len != 8) + return false; + + return (of_read_number(prop, 1) == link) && + (of_read_number(prop + 1, 1) == id); +} + +/* Find a matching node for the slave at (link, id). Returns NULL if none + * found, or a matching node with refcount already incremented. + */ +static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, + int link, uint8_t id) +{ + struct device_node *parent, *np; + + parent = dev_of_node(&master->dev); + if (!parent) + return NULL; + + for_each_child_of_node(parent, np) { + if (fsi_slave_node_matches(np, link, id)) + return np; + } + + return NULL; +} + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { uint32_t chip_id, llmode; @@ -589,7 +687,7 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) return -EIO; } - dev_info(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n", + dev_dbg(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n", chip_id, master->idx, link, id); rc = fsi_slave_set_smode(master, link, id); @@ -623,6 +721,7 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) slave->master = master; slave->dev.parent = &master->dev; + slave->dev.of_node = fsi_slave_find_of_node(master, link, id); slave->dev.release = fsi_slave_release; slave->link = link; slave->id = id; @@ -656,10 +755,13 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) /* FSI master support */ static int fsi_check_access(uint32_t addr, size_t size) { - if (size != 1 && size != 2 && size != 4) - return -EINVAL; - - if ((addr & 0x3) != (size & 0x3)) + if (size == 4) { + if (addr & 0x3) + return -EINVAL; + } else if (size == 2) { + if (addr & 0x1) + return -EINVAL; + } else if (size != 1) return -EINVAL; return 0; @@ -762,14 +864,20 @@ static void fsi_master_unscan(struct fsi_master *master) device_for_each_child(&master->dev, NULL, fsi_master_remove_slave); } +int fsi_master_rescan(struct fsi_master *master) +{ + fsi_master_unscan(master); + return fsi_master_scan(master); +} +EXPORT_SYMBOL_GPL(fsi_master_rescan); + static ssize_t master_rescan_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct fsi_master *master = to_fsi_master(dev); int rc; - fsi_master_unscan(master); - rc = fsi_master_scan(master); + rc = fsi_master_rescan(master); if (rc < 0) return rc; @@ -793,6 +901,7 @@ static DEVICE_ATTR(break, 0200, NULL, master_break_store); int fsi_master_register(struct fsi_master *master) { int rc; + struct device_node *np; if (!master) return -EINVAL; @@ -820,7 +929,9 @@ int fsi_master_register(struct fsi_master *master) return rc; } - fsi_master_scan(master); + np = dev_of_node(&master->dev); + if (!of_property_read_bool(np, "no-scan-on-init")) + fsi_master_scan(master); return 0; } diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index ae2618768508..3f487449a277 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -9,6 +9,7 @@ #include <linux/gpio/consumer.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/spinlock.h> @@ -59,6 +60,7 @@ struct fsi_master_gpio { struct gpio_desc *gpio_trans; /* Voltage translator */ struct gpio_desc *gpio_enable; /* FSI enable */ struct gpio_desc *gpio_mux; /* Mux control */ + bool external_mode; }; #define CREATE_TRACE_POINTS @@ -411,6 +413,12 @@ static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, int rc; spin_lock_irqsave(&master->cmd_lock, flags); + + if (master->external_mode) { + spin_unlock_irqrestore(&master->cmd_lock, flags); + return -EBUSY; + } + serial_out(master, cmd); echo_delay(master); rc = poll_for_response(master, slave, resp_len, resp); @@ -461,12 +469,18 @@ static int fsi_master_gpio_term(struct fsi_master *_master, static int fsi_master_gpio_break(struct fsi_master *_master, int link) { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); + unsigned long flags; if (link != 0) return -ENODEV; trace_fsi_master_gpio_break(master); + spin_lock_irqsave(&master->cmd_lock, flags); + if (master->external_mode) { + spin_unlock_irqrestore(&master->cmd_lock, flags); + return -EBUSY; + } set_sda_output(master, 1); sda_out(master, 1); clock_toggle(master, FSI_PRE_BREAK_CLOCKS); @@ -475,6 +489,7 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) echo_delay(master); sda_out(master, 1); clock_toggle(master, FSI_POST_BREAK_CLOCKS); + spin_unlock_irqrestore(&master->cmd_lock, flags); /* Wait for logic reset to take effect */ udelay(200); @@ -494,21 +509,84 @@ static void fsi_master_gpio_init(struct fsi_master_gpio *master) clock_zeros(master, FSI_INIT_CLOCKS); } +static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) +{ + gpiod_direction_output(master->gpio_mux, 0); + gpiod_direction_output(master->gpio_trans, 0); + gpiod_direction_output(master->gpio_enable, 1); + gpiod_direction_input(master->gpio_clk); + gpiod_direction_input(master->gpio_data); +} + static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); + unsigned long flags; + int rc = -EBUSY; if (link != 0) return -ENODEV; - gpiod_set_value(master->gpio_enable, 1); - return 0; + spin_lock_irqsave(&master->cmd_lock, flags); + if (!master->external_mode) { + gpiod_set_value(master->gpio_enable, 1); + rc = 0; + } + spin_unlock_irqrestore(&master->cmd_lock, flags); + + return rc; +} + +static ssize_t external_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsi_master_gpio *master = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", + master->external_mode ? 1 : 0); } +static ssize_t external_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_master_gpio *master = dev_get_drvdata(dev); + unsigned long flags, val; + bool external_mode; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + external_mode = !!val; + + spin_lock_irqsave(&master->cmd_lock, flags); + + if (external_mode == master->external_mode) { + spin_unlock_irqrestore(&master->cmd_lock, flags); + return count; + } + + master->external_mode = external_mode; + if (master->external_mode) + fsi_master_gpio_init_external(master); + else + fsi_master_gpio_init(master); + spin_unlock_irqrestore(&master->cmd_lock, flags); + + fsi_master_rescan(&master->master); + + return count; +} + +static DEVICE_ATTR(external_mode, 0664, + external_mode_show, external_mode_store); + static int fsi_master_gpio_probe(struct platform_device *pdev) { struct fsi_master_gpio *master; struct gpio_desc *gpio; + int rc; master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); if (!master) @@ -516,6 +594,7 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->dev = &pdev->dev; master->master.dev.parent = master->dev; + master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); gpio = devm_gpiod_get(&pdev->dev, "clock", 0); if (IS_ERR(gpio)) { @@ -565,6 +644,10 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) fsi_master_gpio_init(master); + rc = device_create_file(&pdev->dev, &dev_attr_external_mode); + if (rc) + return rc; + return fsi_master_register(&master->master); } @@ -583,6 +666,8 @@ static int fsi_master_gpio_remove(struct platform_device *pdev) devm_gpiod_put(&pdev->dev, master->gpio_mux); fsi_master_unregister(&master->master); + of_node_put(master->master.dev.of_node); + return 0; } diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index 133b9bff1d65..5885fc4a1ef0 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -16,6 +16,7 @@ #include <linux/delay.h> #include <linux/fsi.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/slab.h> #include "fsi-master.h" @@ -253,7 +254,7 @@ static int hub_master_probe(struct device *dev) reg = be32_to_cpu(__reg); links = (reg >> 8) & 0xff; - dev_info(dev, "hub version %08x (%d links)\n", reg, links); + dev_dbg(dev, "hub version %08x (%d links)\n", reg, links); rc = fsi_slave_claim_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET, FSI_HUB_LINK_SIZE * links); @@ -274,6 +275,7 @@ static int hub_master_probe(struct device *dev) hub->master.dev.parent = dev; hub->master.dev.release = hub_master_release; + hub->master.dev.of_node = of_node_get(dev_of_node(dev)); hub->master.n_links = links; hub->master.read = hub_master_read; @@ -286,10 +288,19 @@ static int hub_master_probe(struct device *dev) hub_master_init(hub); rc = fsi_master_register(&hub->master); - if (!rc) - return 0; + if (rc) + goto err_release; + + /* At this point, fsi_master_register performs the device_initialize(), + * and holds the sole reference on master.dev. This means the device + * will be freed (via ->release) during any subsequent call to + * fsi_master_unregister. We add our own reference to it here, so we + * can perform cleanup (in _remove()) without it being freed before + * we're ready. + */ + get_device(&hub->master.dev); + return 0; - kfree(hub); err_release: fsi_slave_release_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET, FSI_HUB_LINK_SIZE * links); @@ -302,6 +313,14 @@ static int hub_master_remove(struct device *dev) fsi_master_unregister(&hub->master); fsi_slave_release_range(hub->upstream->slave, hub->addr, hub->size); + of_node_put(hub->master.dev.of_node); + + /* + * master.dev will likely be ->release()ed after this, which free()s + * the hub + */ + put_device(&hub->master.dev); + return 0; } diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index 12f7b119567d..ee0b46086026 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h @@ -37,7 +37,24 @@ struct fsi_master { #define dev_to_fsi_master(d) container_of(d, struct fsi_master, dev) +/** + * fsi_master registration & lifetime: the fsi_master_register() and + * fsi_master_unregister() functions will take ownership of the master, and + * ->dev in particular. The registration path performs a get_device(), which + * takes the first reference on the device. Similarly, the unregistration path + * performs a put_device(), which may well drop the last reference. + * + * This means that master implementations *may* need to hold their own + * reference (via get_device()) on master->dev. In particular, if the device's + * ->release callback frees the fsi_master, then fsi_master_unregister will + * invoke this free if no other reference is held. + * + * The same applies for the error path of fsi_master_register; if the call + * fails, dev->release will have been invoked. + */ extern int fsi_master_register(struct fsi_master *master); extern void fsi_master_unregister(struct fsi_master *master); +extern int fsi_master_rescan(struct fsi_master *master); + #endif /* DRIVERS_FSI_MASTER_H */ diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index 86789f8918a4..7ab36042a822 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -17,6 +17,7 @@ #include <linux/clk.h> #include <linux/delay.h> +#include <linux/extcon.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/interrupt.h> @@ -25,6 +26,7 @@ #include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -81,6 +83,10 @@ struct sii8620 { struct edid *edid; unsigned int gen2_write_burst:1; enum sii8620_mt_state mt_state; + struct extcon_dev *extcon; + struct notifier_block extcon_nb; + struct work_struct extcon_wq; + int cable_state; struct list_head mt_queue; struct { int r_size; @@ -2170,6 +2176,77 @@ static void sii8620_init_rcp_input_dev(struct sii8620 *ctx) ctx->rc_dev = rc_dev; } +static void sii8620_cable_out(struct sii8620 *ctx) +{ + disable_irq(to_i2c_client(ctx->dev)->irq); + sii8620_hw_off(ctx); +} + +static void sii8620_extcon_work(struct work_struct *work) +{ + struct sii8620 *ctx = + container_of(work, struct sii8620, extcon_wq); + int state = extcon_get_state(ctx->extcon, EXTCON_DISP_MHL); + + if (state == ctx->cable_state) + return; + + ctx->cable_state = state; + + if (state > 0) + sii8620_cable_in(ctx); + else + sii8620_cable_out(ctx); +} + +static int sii8620_extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct sii8620 *ctx = + container_of(self, struct sii8620, extcon_nb); + + schedule_work(&ctx->extcon_wq); + + return NOTIFY_DONE; +} + +static int sii8620_extcon_init(struct sii8620 *ctx) +{ + struct extcon_dev *edev; + struct device_node *musb, *muic; + int ret; + + /* get micro-USB connector node */ + musb = of_graph_get_remote_node(ctx->dev->of_node, 1, -1); + /* next get micro-USB Interface Controller node */ + muic = of_get_next_parent(musb); + + if (!muic) { + dev_info(ctx->dev, "no extcon found, switching to 'always on' mode\n"); + return 0; + } + + edev = extcon_find_edev_by_node(muic); + of_node_put(muic); + if (IS_ERR(edev)) { + if (PTR_ERR(edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(ctx->dev, "Invalid or missing extcon\n"); + return PTR_ERR(edev); + } + + ctx->extcon = edev; + ctx->extcon_nb.notifier_call = sii8620_extcon_notifier; + INIT_WORK(&ctx->extcon_wq, sii8620_extcon_work); + ret = extcon_register_notifier(edev, EXTCON_DISP_MHL, &ctx->extcon_nb); + if (ret) { + dev_err(ctx->dev, "failed to register notifier for MHL\n"); + return ret; + } + + return 0; +} + static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) { return container_of(bridge, struct sii8620, bridge); @@ -2302,13 +2379,20 @@ static int sii8620_probe(struct i2c_client *client, if (ret) return ret; + ret = sii8620_extcon_init(ctx); + if (ret < 0) { + dev_err(ctx->dev, "failed to initialize EXTCON\n"); + return ret; + } + i2c_set_clientdata(client, ctx); ctx->bridge.funcs = &sii8620_bridge_funcs; ctx->bridge.of_node = dev->of_node; drm_bridge_add(&ctx->bridge); - sii8620_cable_in(ctx); + if (!ctx->extcon) + sii8620_cable_in(ctx); return 0; } @@ -2317,8 +2401,15 @@ static int sii8620_remove(struct i2c_client *client) { struct sii8620 *ctx = i2c_get_clientdata(client); - disable_irq(to_i2c_client(ctx->dev)->irq); - sii8620_hw_off(ctx); + if (ctx->extcon) { + extcon_unregister_notifier(ctx->extcon, EXTCON_DISP_MHL, + &ctx->extcon_nb); + flush_work(&ctx->extcon_wq); + if (ctx->cable_state > 0) + sii8620_cable_out(ctx); + } else { + sii8620_cable_out(ctx); + } drm_bridge_remove(&ctx->bridge); return 0; diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig index 50b89ea0e60f..97954f575c3f 100644 --- a/drivers/hv/Kconfig +++ b/drivers/hv/Kconfig @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + menu "Microsoft Hyper-V guest support" config HYPERV diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile index 14c22786b519..a1eec7177c2d 100644 --- a/drivers/hv/Makefile +++ b/drivers/hv/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_HYPERV_UTILS) += hv_utils.o obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o CFLAGS_hv_trace.o = -I$(src) +CFLAGS_hv_balloon.o = -I$(src) hv_vmbus-y := vmbus_drv.o \ hv.o connection.o channel.o \ diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index c21020b69114..ecc2bd275a73 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -71,7 +71,7 @@ static const struct vmbus_device vmbus_devs[] = { /* PCIE */ { .dev_type = HV_PCIE, HV_PCIE_GUID, - .perf_device = true, + .perf_device = false, }, /* Synthetic Frame Buffer */ @@ -596,10 +596,8 @@ static int next_numa_node_id; /* * Starting with Win8, we can statically distribute the incoming * channel interrupt load by binding a channel to VCPU. - * We do this in a hierarchical fashion: - * First distribute the primary channels across available NUMA nodes - * and then distribute the subchannels amongst the CPUs in the NUMA - * node assigned to the primary channel. + * We distribute the interrupt loads to one or more NUMA nodes based on + * the channel's affinity_policy. * * For pre-win8 hosts or non-performance critical channels we assign the * first CPU in the first NUMA node. diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index fe96aab9e794..8137b3885b99 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -27,7 +27,7 @@ #include <linux/vmalloc.h> #include <linux/hyperv.h> #include <linux/version.h> -#include <linux/interrupt.h> +#include <linux/random.h> #include <linux/clockchips.h> #include <asm/hyperv.h> #include <asm/mshyperv.h> @@ -38,6 +38,17 @@ struct hv_context hv_context = { .synic_initialized = false, }; +/* + * If false, we're using the old mechanism for stimer0 interrupts + * where it sends a VMbus message when it expires. The old + * mechanism is used when running on older versions of Hyper-V + * that don't support Direct Mode. While Hyper-V provides + * four stimer's per CPU, Linux uses only stimer0. + */ +static bool direct_mode_enabled; +static int stimer0_irq; +static int stimer0_vector; + #define HV_TIMER_FREQUENCY (10 * 1000 * 1000) /* 100ns period */ #define HV_MAX_MAX_DELTA_TICKS 0xffffffff #define HV_MIN_DELTA_TICKS 1 @@ -53,6 +64,8 @@ int hv_init(void) if (!hv_context.cpu_context) return -ENOMEM; + direct_mode_enabled = ms_hyperv.misc_features & + HV_X64_STIMER_DIRECT_MODE_AVAILABLE; return 0; } @@ -91,6 +104,21 @@ int hv_post_message(union hv_connection_id connection_id, return status & 0xFFFF; } +/* + * ISR for when stimer0 is operating in Direct Mode. Direct Mode + * does not use VMbus or any VMbus messages, so process here and not + * in the VMbus driver code. + */ + +static void hv_stimer0_isr(void) +{ + struct hv_per_cpu_context *hv_cpu; + + hv_cpu = this_cpu_ptr(hv_context.cpu_context); + hv_cpu->clk_evt->event_handler(hv_cpu->clk_evt); + add_interrupt_randomness(stimer0_vector, 0); +} + static int hv_ce_set_next_event(unsigned long delta, struct clock_event_device *evt) { @@ -108,6 +136,8 @@ static int hv_ce_shutdown(struct clock_event_device *evt) { hv_init_timer(HV_X64_MSR_STIMER0_COUNT, 0); hv_init_timer_config(HV_X64_MSR_STIMER0_CONFIG, 0); + if (direct_mode_enabled) + hv_disable_stimer0_percpu_irq(stimer0_irq); return 0; } @@ -116,11 +146,26 @@ static int hv_ce_set_oneshot(struct clock_event_device *evt) { union hv_timer_config timer_cfg; + timer_cfg.as_uint64 = 0; timer_cfg.enable = 1; timer_cfg.auto_enable = 1; - timer_cfg.sintx = VMBUS_MESSAGE_SINT; + if (direct_mode_enabled) { + /* + * When it expires, the timer will directly interrupt + * on the specified hardware vector/IRQ. + */ + timer_cfg.direct_mode = 1; + timer_cfg.apic_vector = stimer0_vector; + hv_enable_stimer0_percpu_irq(stimer0_irq); + } else { + /* + * When it expires, the timer will generate a VMbus message, + * to be handled by the normal VMbus interrupt handler. + */ + timer_cfg.direct_mode = 0; + timer_cfg.sintx = VMBUS_MESSAGE_SINT; + } hv_init_timer_config(HV_X64_MSR_STIMER0_CONFIG, timer_cfg.as_uint64); - return 0; } @@ -147,7 +192,7 @@ int hv_synic_alloc(void) int cpu; hv_context.hv_numa_map = kzalloc(sizeof(struct cpumask) * nr_node_ids, - GFP_ATOMIC); + GFP_KERNEL); if (hv_context.hv_numa_map == NULL) { pr_err("Unable to allocate NUMA map\n"); goto err; @@ -191,6 +236,11 @@ int hv_synic_alloc(void) INIT_LIST_HEAD(&hv_cpu->chan_list); } + if (direct_mode_enabled && + hv_setup_stimer0_irq(&stimer0_irq, &stimer0_vector, + hv_stimer0_isr)) + goto err; + return 0; err: return -ENOMEM; @@ -217,7 +267,7 @@ void hv_synic_free(void) } /* - * hv_synic_init - Initialize the Synthethic Interrupt Controller. + * hv_synic_init - Initialize the Synthetic Interrupt Controller. * * If it is already initialized by another entity (ie x2v shim), we need to * retrieve the initialized message and event pages. Otherwise, we create and @@ -252,7 +302,6 @@ int hv_synic_init(unsigned int cpu) hv_get_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64); - shared_sint.as_uint64 = 0; shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR; shared_sint.masked = false; if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED) @@ -292,6 +341,9 @@ void hv_synic_clockevents_cleanup(void) if (!(ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE)) return; + if (direct_mode_enabled) + hv_remove_stimer0_irq(stimer0_irq); + for_each_present_cpu(cpu) { struct hv_per_cpu_context *hv_cpu = per_cpu_ptr(hv_context.cpu_context, cpu); diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index db0e6652d7ef..b3e9f13f8bc3 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -34,6 +34,9 @@ #include <linux/hyperv.h> +#define CREATE_TRACE_POINTS +#include "hv_trace_balloon.h" + /* * We begin with definitions supporting the Dynamic Memory protocol * with the host. @@ -576,11 +579,65 @@ static struct hv_dynmem_device dm_device; static void post_status(struct hv_dynmem_device *dm); #ifdef CONFIG_MEMORY_HOTPLUG +static inline bool has_pfn_is_backed(struct hv_hotadd_state *has, + unsigned long pfn) +{ + struct hv_hotadd_gap *gap; + + /* The page is not backed. */ + if ((pfn < has->covered_start_pfn) || (pfn >= has->covered_end_pfn)) + return false; + + /* Check for gaps. */ + list_for_each_entry(gap, &has->gap_list, list) { + if ((pfn >= gap->start_pfn) && (pfn < gap->end_pfn)) + return false; + } + + return true; +} + +static unsigned long hv_page_offline_check(unsigned long start_pfn, + unsigned long nr_pages) +{ + unsigned long pfn = start_pfn, count = 0; + struct hv_hotadd_state *has; + bool found; + + while (pfn < start_pfn + nr_pages) { + /* + * Search for HAS which covers the pfn and when we find one + * count how many consequitive PFNs are covered. + */ + found = false; + list_for_each_entry(has, &dm_device.ha_region_list, list) { + while ((pfn >= has->start_pfn) && + (pfn < has->end_pfn) && + (pfn < start_pfn + nr_pages)) { + found = true; + if (has_pfn_is_backed(has, pfn)) + count++; + pfn++; + } + } + + /* + * This PFN is not in any HAS (e.g. we're offlining a region + * which was present at boot), no need to account for it. Go + * to the next one. + */ + if (!found) + pfn++; + } + + return count; +} + static int hv_memory_notifier(struct notifier_block *nb, unsigned long val, void *v) { struct memory_notify *mem = (struct memory_notify *)v; - unsigned long flags; + unsigned long flags, pfn_count; switch (val) { case MEM_ONLINE: @@ -593,7 +650,19 @@ static int hv_memory_notifier(struct notifier_block *nb, unsigned long val, case MEM_OFFLINE: spin_lock_irqsave(&dm_device.ha_lock, flags); - dm_device.num_pages_onlined -= mem->nr_pages; + pfn_count = hv_page_offline_check(mem->start_pfn, + mem->nr_pages); + if (pfn_count <= dm_device.num_pages_onlined) { + dm_device.num_pages_onlined -= pfn_count; + } else { + /* + * We're offlining more pages than we managed to online. + * This is unexpected. In any case don't let + * num_pages_onlined wrap around zero. + */ + WARN_ON_ONCE(1); + dm_device.num_pages_onlined = 0; + } spin_unlock_irqrestore(&dm_device.ha_lock, flags); break; case MEM_GOING_ONLINE: @@ -612,30 +681,9 @@ static struct notifier_block hv_memory_nb = { /* Check if the particular page is backed and can be onlined and online it. */ static void hv_page_online_one(struct hv_hotadd_state *has, struct page *pg) { - unsigned long cur_start_pgp; - unsigned long cur_end_pgp; - struct hv_hotadd_gap *gap; - - cur_start_pgp = (unsigned long)pfn_to_page(has->covered_start_pfn); - cur_end_pgp = (unsigned long)pfn_to_page(has->covered_end_pfn); - - /* The page is not backed. */ - if (((unsigned long)pg < cur_start_pgp) || - ((unsigned long)pg >= cur_end_pgp)) + if (!has_pfn_is_backed(has, page_to_pfn(pg))) return; - /* Check for gaps. */ - list_for_each_entry(gap, &has->gap_list, list) { - cur_start_pgp = (unsigned long) - pfn_to_page(gap->start_pfn); - cur_end_pgp = (unsigned long) - pfn_to_page(gap->end_pfn); - if (((unsigned long)pg >= cur_start_pgp) && - ((unsigned long)pg < cur_end_pgp)) { - return; - } - } - /* This frame is currently backed; online the page. */ __online_page_set_limits(pg); __online_page_increment_counters(pg); @@ -691,7 +739,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, (HA_CHUNK << PAGE_SHIFT)); if (ret) { - pr_warn("hot_add memory failed error is %d\n", ret); + pr_err("hot_add memory failed error is %d\n", ret); if (ret == -EEXIST) { /* * This error indicates that the error @@ -726,19 +774,13 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, static void hv_online_page(struct page *pg) { struct hv_hotadd_state *has; - unsigned long cur_start_pgp; - unsigned long cur_end_pgp; unsigned long flags; + unsigned long pfn = page_to_pfn(pg); spin_lock_irqsave(&dm_device.ha_lock, flags); list_for_each_entry(has, &dm_device.ha_region_list, list) { - cur_start_pgp = (unsigned long) - pfn_to_page(has->start_pfn); - cur_end_pgp = (unsigned long)pfn_to_page(has->end_pfn); - /* The page belongs to a different HAS. */ - if (((unsigned long)pg < cur_start_pgp) || - ((unsigned long)pg >= cur_end_pgp)) + if ((pfn < has->start_pfn) || (pfn >= has->end_pfn)) continue; hv_page_online_one(has, pg); @@ -1014,7 +1056,7 @@ static void hot_add_req(struct work_struct *dummy) resp.result = 0; if (!do_hot_add || (resp.page_count == 0)) - pr_info("Memory hot add failed\n"); + pr_err("Memory hot add failed\n"); dm->state = DM_INITIALIZED; resp.hdr.trans_id = atomic_inc_return(&trans_id); @@ -1041,7 +1083,7 @@ static void process_info(struct hv_dynmem_device *dm, struct dm_info_msg *msg) break; default: - pr_info("Received Unknown type: %d\n", info_hdr->type); + pr_warn("Received Unknown type: %d\n", info_hdr->type); } } @@ -1120,6 +1162,9 @@ static void post_status(struct hv_dynmem_device *dm) dm->num_pages_added - dm->num_pages_onlined : 0) + compute_balloon_floor(); + trace_balloon_status(status.num_avail, status.num_committed, + vm_memory_committed(), dm->num_pages_ballooned, + dm->num_pages_added, dm->num_pages_onlined); /* * If our transaction ID is no longer current, just don't * send the status. This can happen if we were interrupted @@ -1290,7 +1335,7 @@ static void balloon_up(struct work_struct *dummy) /* * Free up the memory we allocatted. */ - pr_info("Balloon response failed\n"); + pr_err("Balloon response failed\n"); for (i = 0; i < bl_resp->range_count; i++) free_balloon_pages(&dm_device, @@ -1421,7 +1466,7 @@ static void cap_resp(struct hv_dynmem_device *dm, struct dm_capabilities_resp_msg *cap_resp) { if (!cap_resp->is_accepted) { - pr_info("Capabilities not accepted by host\n"); + pr_err("Capabilities not accepted by host\n"); dm->state = DM_INIT_ERROR; } complete(&dm->host_event); @@ -1508,7 +1553,7 @@ static void balloon_onchannelcallback(void *context) break; default: - pr_err("Unhandled message: type: %d\n", dm_hdr->type); + pr_warn("Unhandled message: type: %d\n", dm_hdr->type); } } diff --git a/drivers/hv/hv_trace.c b/drivers/hv/hv_trace.c index df47acd01a81..38d359cf1e70 100644 --- a/drivers/hv/hv_trace.c +++ b/drivers/hv/hv_trace.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 + #include "hyperv_vmbus.h" #define CREATE_TRACE_POINTS diff --git a/drivers/hv/hv_trace.h b/drivers/hv/hv_trace.h index d635ee95b20d..999f80a63bff 100644 --- a/drivers/hv/hv_trace.h +++ b/drivers/hv/hv_trace.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 + #undef TRACE_SYSTEM #define TRACE_SYSTEM hyperv diff --git a/drivers/hv/hv_trace_balloon.h b/drivers/hv/hv_trace_balloon.h new file mode 100644 index 000000000000..93082888aec3 --- /dev/null +++ b/drivers/hv/hv_trace_balloon.h @@ -0,0 +1,48 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM hyperv + +#if !defined(_HV_TRACE_BALLOON_H) || defined(TRACE_HEADER_MULTI_READ) +#define _HV_TRACE_BALLOON_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(balloon_status, + TP_PROTO(u64 available, u64 committed, + unsigned long vm_memory_committed, + unsigned long pages_ballooned, + unsigned long pages_added, + unsigned long pages_onlined), + TP_ARGS(available, committed, vm_memory_committed, + pages_ballooned, pages_added, pages_onlined), + TP_STRUCT__entry( + __field(u64, available) + __field(u64, committed) + __field(unsigned long, vm_memory_committed) + __field(unsigned long, pages_ballooned) + __field(unsigned long, pages_added) + __field(unsigned long, pages_onlined) + ), + TP_fast_assign( + __entry->available = available; + __entry->committed = committed; + __entry->vm_memory_committed = vm_memory_committed; + __entry->pages_ballooned = pages_ballooned; + __entry->pages_added = pages_added; + __entry->pages_onlined = pages_onlined; + ), + TP_printk("available %lld, committed %lld; vm_memory_committed %ld;" + " pages_ballooned %ld, pages_added %ld, pages_onlined %ld", + __entry->available, __entry->committed, + __entry->vm_memory_committed, __entry->pages_ballooned, + __entry->pages_added, __entry->pages_onlined + ) + ); + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE hv_trace_balloon +#endif /* _HV_TRACE_BALLOON_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h> diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 22300ec7b556..36d34fe3ccb3 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -57,7 +57,9 @@ union hv_timer_config { u64 periodic:1; u64 lazy:1; u64 auto_enable:1; - u64 reserved_z0:12; + u64 apic_vector:8; + u64 direct_mode:1; + u64 reserved_z0:3; u64 sintx:4; u64 reserved_z1:44; }; diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig new file mode 100644 index 000000000000..f68e025c5131 --- /dev/null +++ b/drivers/hwtracing/Kconfig @@ -0,0 +1,7 @@ +menu "HW tracing support" + +source "drivers/hwtracing/stm/Kconfig" + +source "drivers/hwtracing/intel_th/Kconfig" + +endmenu diff --git a/drivers/hwtracing/coresight/coresight-cpu-debug.c b/drivers/hwtracing/coresight/coresight-cpu-debug.c index 6ea62c62ff27..9cdb3fbc8c1f 100644 --- a/drivers/hwtracing/coresight/coresight-cpu-debug.c +++ b/drivers/hwtracing/coresight/coresight-cpu-debug.c @@ -315,7 +315,7 @@ static void debug_dump_regs(struct debug_drvdata *drvdata) } pc = debug_adjust_pc(drvdata); - dev_emerg(dev, " EDPCSR: [<%p>] %pS\n", (void *)pc, (void *)pc); + dev_emerg(dev, " EDPCSR: [<%px>] %pS\n", (void *)pc, (void *)pc); if (drvdata->edcidsr_present) dev_emerg(dev, " EDCIDSR: %08x\n", drvdata->edcidsr); diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c index 4e6eab53e34e..d21961710713 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c @@ -1780,7 +1780,7 @@ static ssize_t ctxid_masks_store(struct device *dev, */ for (j = 0; j < 8; j++) { if (maskbyte & 1) - config->ctxid_pid[i] &= ~(0xFF << (j * 8)); + config->ctxid_pid[i] &= ~(0xFFUL << (j * 8)); maskbyte >>= 1; } /* Select the next ctxid comparator mask value */ @@ -1963,7 +1963,7 @@ static ssize_t vmid_masks_store(struct device *dev, */ for (j = 0; j < 8; j++) { if (maskbyte & 1) - config->vmid_val[i] &= ~(0xFF << (j * 8)); + config->vmid_val[i] &= ~(0xFFUL << (j * 8)); maskbyte >>= 1; } /* Select the next vmid comparator mask value */ diff --git a/drivers/hwtracing/intel_th/Kconfig b/drivers/hwtracing/intel_th/Kconfig index 1b412f8a56b5..ca0527d588e9 100644 --- a/drivers/hwtracing/intel_th/Kconfig +++ b/drivers/hwtracing/intel_th/Kconfig @@ -25,6 +25,18 @@ config INTEL_TH_PCI Say Y here to enable PCI Intel TH support. +config INTEL_TH_ACPI + tristate "Intel(R) Trace Hub ACPI controller" + depends on ACPI + help + Intel(R) Trace Hub may exist as an ACPI device. This option enables + support glue layer for ACPI-based Intel TH. This typically implies + 'host debugger' mode, that is, the trace configuration and capture + is handled by an external debug host and corresponding controls will + not be available on the target. + + Say Y here to enable ACPI Intel TH support. + config INTEL_TH_GTH tristate "Intel(R) Trace Hub Global Trace Hub" help diff --git a/drivers/hwtracing/intel_th/Makefile b/drivers/hwtracing/intel_th/Makefile index 880c9b5e8566..d9252fa8d9ca 100644 --- a/drivers/hwtracing/intel_th/Makefile +++ b/drivers/hwtracing/intel_th/Makefile @@ -6,6 +6,9 @@ intel_th-$(CONFIG_INTEL_TH_DEBUG) += debug.o obj-$(CONFIG_INTEL_TH_PCI) += intel_th_pci.o intel_th_pci-y := pci.o +obj-$(CONFIG_INTEL_TH_ACPI) += intel_th_acpi.o +intel_th_acpi-y := acpi.o + obj-$(CONFIG_INTEL_TH_GTH) += intel_th_gth.o intel_th_gth-y := gth.o diff --git a/drivers/hwtracing/intel_th/acpi.c b/drivers/hwtracing/intel_th/acpi.c new file mode 100644 index 000000000000..87bc3744755f --- /dev/null +++ b/drivers/hwtracing/intel_th/acpi.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel(R) Trace Hub ACPI driver + * + * Copyright (C) 2017 Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> + +#include "intel_th.h" + +#define DRIVER_NAME "intel_th_acpi" + +static const struct intel_th_drvdata intel_th_acpi_pch = { + .host_mode_only = 1, +}; + +static const struct intel_th_drvdata intel_th_acpi_uncore = { + .host_mode_only = 1, +}; + +static const struct acpi_device_id intel_th_acpi_ids[] = { + { "INTC1000", (kernel_ulong_t)&intel_th_acpi_uncore }, + { "INTC1001", (kernel_ulong_t)&intel_th_acpi_pch }, + { "", 0 }, +}; + +MODULE_DEVICE_TABLE(acpi, intel_th_acpi_ids); + +static int intel_th_acpi_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + const struct acpi_device_id *id; + struct intel_th *th; + + id = acpi_match_device(intel_th_acpi_ids, &pdev->dev); + if (!id) + return -ENODEV; + + th = intel_th_alloc(&pdev->dev, (void *)id->driver_data, + pdev->resource, pdev->num_resources, -1); + if (IS_ERR(th)) + return PTR_ERR(th); + + adev->driver_data = th; + + return 0; +} + +static int intel_th_acpi_remove(struct platform_device *pdev) +{ + struct intel_th *th = platform_get_drvdata(pdev); + + intel_th_free(th); + + return 0; +} + +static struct platform_driver intel_th_acpi_driver = { + .probe = intel_th_acpi_probe, + .remove = intel_th_acpi_remove, + .driver = { + .name = DRIVER_NAME, + .acpi_match_table = intel_th_acpi_ids, + }, +}; + +module_platform_driver(intel_th_acpi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub ACPI controller driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>"); diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c index 1a023e30488c..da962aa2cef5 100644 --- a/drivers/hwtracing/intel_th/core.c +++ b/drivers/hwtracing/intel_th/core.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub driver core * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -638,7 +630,8 @@ intel_th_subdevice_alloc(struct intel_th *th, thdev->output.port = -1; thdev->output.scratchpad = subdev->scrpd; } else if (subdev->type == INTEL_TH_SWITCH) { - thdev->host_mode = host_mode; + thdev->host_mode = + INTEL_TH_CAP(th, host_mode_only) ? true : host_mode; th->hub = thdev; } @@ -737,7 +730,8 @@ static int intel_th_populate(struct intel_th *th) struct intel_th_device *thdev; /* only allow SOURCE and SWITCH devices in host mode */ - if (host_mode && subdev->type == INTEL_TH_OUTPUT) + if ((INTEL_TH_CAP(th, host_mode_only) || host_mode) && + subdev->type == INTEL_TH_OUTPUT) continue; /* @@ -813,7 +807,14 @@ intel_th_alloc(struct device *dev, struct intel_th_drvdata *drvdata, struct resource *devres, unsigned int ndevres, int irq) { struct intel_th *th; - int err; + int err, r; + + if (irq == -1) + for (r = 0; r < ndevres; r++) + if (devres[r].flags & IORESOURCE_IRQ) { + irq = devres[r].start; + break; + } th = kzalloc(sizeof(*th), GFP_KERNEL); if (!th) @@ -935,9 +936,13 @@ EXPORT_SYMBOL_GPL(intel_th_trace_disable); int intel_th_set_output(struct intel_th_device *thdev, unsigned int master) { - struct intel_th_device *hub = to_intel_th_device(thdev->dev.parent); + struct intel_th_device *hub = to_intel_th_hub(thdev); struct intel_th_driver *hubdrv = to_intel_th_driver(hub->dev.driver); + /* In host mode, this is up to the external debugger, do nothing. */ + if (hub->host_mode) + return 0; + if (!hubdrv->set_output) return -ENOTSUPP; diff --git a/drivers/hwtracing/intel_th/debug.c b/drivers/hwtracing/intel_th/debug.c index 788a1f0a97ad..ff79063118a0 100644 --- a/drivers/hwtracing/intel_th/debug.c +++ b/drivers/hwtracing/intel_th/debug.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub driver debugging * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #include <linux/types.h> diff --git a/drivers/hwtracing/intel_th/debug.h b/drivers/hwtracing/intel_th/debug.h index 88311bad3ba4..78bd7e4bf9ce 100644 --- a/drivers/hwtracing/intel_th/debug.h +++ b/drivers/hwtracing/intel_th/debug.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Intel(R) Trace Hub driver debugging * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef __INTEL_TH_DEBUG_H__ diff --git a/drivers/hwtracing/intel_th/gth.c b/drivers/hwtracing/intel_th/gth.c index 018678ec3c13..8426b7970c14 100644 --- a/drivers/hwtracing/intel_th/gth.c +++ b/drivers/hwtracing/intel_th/gth.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub Global Trace Hub * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/hwtracing/intel_th/gth.h b/drivers/hwtracing/intel_th/gth.h index f3d234251a12..6f2b0b930875 100644 --- a/drivers/hwtracing/intel_th/gth.h +++ b/drivers/hwtracing/intel_th/gth.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Intel(R) Trace Hub Global Trace Hub (GTH) data structures * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef __INTEL_TH_GTH_H__ diff --git a/drivers/hwtracing/intel_th/intel_th.h b/drivers/hwtracing/intel_th/intel_th.h index 99ad563fc40d..780206dc9012 100644 --- a/drivers/hwtracing/intel_th/intel_th.h +++ b/drivers/hwtracing/intel_th/intel_th.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Intel(R) Trace Hub data structures * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef __INTEL_TH_H__ @@ -50,9 +42,11 @@ struct intel_th_output { /** * struct intel_th_drvdata - describes hardware capabilities and quirks * @tscu_enable: device needs SW to enable time stamping unit + * @host_mode_only: device can only operate in 'host debugger' mode */ struct intel_th_drvdata { - unsigned int tscu_enable : 1; + unsigned int tscu_enable : 1, + host_mode_only : 1; }; #define INTEL_TH_CAP(_th, _cap) ((_th)->drvdata ? (_th)->drvdata->_cap : 0) diff --git a/drivers/hwtracing/intel_th/msu.c b/drivers/hwtracing/intel_th/msu.c index dfb57eaa9f22..ede388309376 100644 --- a/drivers/hwtracing/intel_th/msu.c +++ b/drivers/hwtracing/intel_th/msu.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub Memory Storage Unit * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/hwtracing/intel_th/msu.h b/drivers/hwtracing/intel_th/msu.h index 9b710e4aa98a..9cc8aced6116 100644 --- a/drivers/hwtracing/intel_th/msu.h +++ b/drivers/hwtracing/intel_th/msu.h @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub Memory Storage Unit (MSU) data structures * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef __INTEL_TH_MSU_H__ diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index c2a2ce8ee541..c2e55e5d97f6 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub pci driver * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/hwtracing/intel_th/pti.c b/drivers/hwtracing/intel_th/pti.c index e96a1fcb57b2..56694339cb06 100644 --- a/drivers/hwtracing/intel_th/pti.c +++ b/drivers/hwtracing/intel_th/pti.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub PTI output driver * * Copyright (C) 2014-2016 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/hwtracing/intel_th/pti.h b/drivers/hwtracing/intel_th/pti.h index 30827be67b4c..e9381babc84c 100644 --- a/drivers/hwtracing/intel_th/pti.h +++ b/drivers/hwtracing/intel_th/pti.h @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub PTI output data structures * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef __INTEL_TH_STH_H__ diff --git a/drivers/hwtracing/intel_th/sth.c b/drivers/hwtracing/intel_th/sth.c index b03444624648..4b7ae47789d2 100644 --- a/drivers/hwtracing/intel_th/sth.c +++ b/drivers/hwtracing/intel_th/sth.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Intel(R) Trace Hub Software Trace Hub support * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/hwtracing/intel_th/sth.h b/drivers/hwtracing/intel_th/sth.h index f1390cd4f2ed..f97fc0c51739 100644 --- a/drivers/hwtracing/intel_th/sth.h +++ b/drivers/hwtracing/intel_th/sth.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Intel(R) Trace Hub Software Trace Hub (STH) data structures * * Copyright (C) 2014-2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef __INTEL_TH_STH_H__ diff --git a/drivers/hwtracing/stm/console.c b/drivers/hwtracing/stm/console.c index c9d9a8d2ff52..a00f65e21747 100644 --- a/drivers/hwtracing/stm/console.c +++ b/drivers/hwtracing/stm/console.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Simple kernel console driver for STM devices * Copyright (c) 2014, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * STM console will send kernel messages over STM devices to a trace host. */ diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c index f129869e05a9..05386b76465e 100644 --- a/drivers/hwtracing/stm/core.c +++ b/drivers/hwtracing/stm/core.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * System Trace Module (STM) infrastructure * Copyright (c) 2014, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * STM class implements generic infrastructure for System Trace Module devices * as defined in MIPI STPv2 specification. */ diff --git a/drivers/hwtracing/stm/dummy_stm.c b/drivers/hwtracing/stm/dummy_stm.c index c5f94ca31c4d..38528ffdc0b3 100644 --- a/drivers/hwtracing/stm/dummy_stm.c +++ b/drivers/hwtracing/stm/dummy_stm.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * A dummy STM device for stm/stm_source class testing. * Copyright (c) 2014, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * STM class implements generic infrastructure for System Trace Module devices * as defined in MIPI STPv2 specification. */ @@ -20,6 +12,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/stm.h> +#include <uapi/linux/stm.h> static ssize_t notrace dummy_stm_packet(struct stm_data *stm_data, unsigned int master, @@ -52,6 +45,18 @@ static unsigned int fail_mode; module_param(fail_mode, int, 0600); +static unsigned int master_min; + +module_param(master_min, int, 0400); + +static unsigned int master_max = STP_MASTER_MAX; + +module_param(master_max, int, 0400); + +static unsigned int nr_channels = STP_CHANNEL_MAX; + +module_param(nr_channels, int, 0400); + static int dummy_stm_link(struct stm_data *data, unsigned int master, unsigned int channel) { @@ -68,14 +73,19 @@ static int dummy_stm_init(void) if (nr_dummies < 0 || nr_dummies > DUMMY_STM_MAX) return -EINVAL; + if (master_min > master_max || + master_max > STP_MASTER_MAX || + nr_channels > STP_CHANNEL_MAX) + return -EINVAL; + for (i = 0; i < nr_dummies; i++) { dummy_stm[i].name = kasprintf(GFP_KERNEL, "dummy_stm.%d", i); if (!dummy_stm[i].name) goto fail_unregister; - dummy_stm[i].sw_start = 0x0000; - dummy_stm[i].sw_end = 0xffff; - dummy_stm[i].sw_nchannels = 0xffff; + dummy_stm[i].sw_start = master_min; + dummy_stm[i].sw_end = master_max; + dummy_stm[i].sw_nchannels = nr_channels; dummy_stm[i].packet = dummy_stm_packet; dummy_stm[i].link = dummy_stm_link; diff --git a/drivers/hwtracing/stm/heartbeat.c b/drivers/hwtracing/stm/heartbeat.c index 3da7b673aab2..7db42395e131 100644 --- a/drivers/hwtracing/stm/heartbeat.c +++ b/drivers/hwtracing/stm/heartbeat.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Simple heartbeat STM source driver * Copyright (c) 2016, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * Heartbeat STM source will send repetitive messages over STM devices to a * trace host. */ diff --git a/drivers/hwtracing/stm/policy.c b/drivers/hwtracing/stm/policy.c index 33e9a1b6ea7c..3fd07e275b34 100644 --- a/drivers/hwtracing/stm/policy.c +++ b/drivers/hwtracing/stm/policy.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * System Trace Module (STM) master/channel allocation policy management * Copyright (c) 2014, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * A master/channel allocation policy allows mapping string identifiers to * master and channel ranges, where allocation can be done. */ diff --git a/drivers/hwtracing/stm/stm.h b/drivers/hwtracing/stm/stm.h index 4e8c6926260f..923571adc6f4 100644 --- a/drivers/hwtracing/stm/stm.h +++ b/drivers/hwtracing/stm/stm.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * System Trace Module (STM) infrastructure * Copyright (c) 2014, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * STM class implements generic infrastructure for System Trace Module devices * as defined in MIPI STPv2 specification. */ diff --git a/drivers/mcb/mcb-pci.c b/drivers/mcb/mcb-pci.c index af4d2f26f1c6..c2d69e33bf2b 100644 --- a/drivers/mcb/mcb-pci.c +++ b/drivers/mcb/mcb-pci.c @@ -117,6 +117,7 @@ static void mcb_pci_remove(struct pci_dev *pdev) static const struct pci_device_id mcb_pci_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_MEN, PCI_DEVICE_ID_MEN_CHAMELEON) }, + { PCI_DEVICE(PCI_VENDOR_ID_ALTERA, PCI_DEVICE_ID_MEN_CHAMELEON) }, { 0 }, }; MODULE_DEVICE_TABLE(pci, mcb_pci_tbl); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 03605f8fc0dc..5d713008749b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -75,7 +75,6 @@ config ATMEL_TCB_CLKSRC config ATMEL_TCB_CLKSRC_BLOCK int depends on ATMEL_TCB_CLKSRC - prompt "TC Block" if CPU_AT32AP700X default 0 range 0 1 help diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c3c8624f4d95..20be70c3f118 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -12,7 +12,7 @@ obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o obj-$(CONFIG_ICS932S401) += ics932s401.o -obj-$(CONFIG_LKDTM) += lkdtm.o +obj-$(CONFIG_LKDTM) += lkdtm/ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o @@ -57,21 +57,3 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_OCXL) += ocxl/ obj-$(CONFIG_MISC_RTSX) += cardreader/ - -lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o -lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o -lkdtm-$(CONFIG_LKDTM) += lkdtm_heap.o -lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o -lkdtm-$(CONFIG_LKDTM) += lkdtm_refcount.o -lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o -lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o - -KCOV_INSTRUMENT_lkdtm_rodata.o := n - -OBJCOPYFLAGS := -OBJCOPYFLAGS_lkdtm_rodata_objcopy.o := \ - --set-section-flags .text=alloc,readonly \ - --rename-section .text=.rodata -targets += lkdtm_rodata.o lkdtm_rodata_objcopy.o -$(obj)/lkdtm_rodata_objcopy.o: $(obj)/lkdtm_rodata.o FORCE - $(call if_changed,objcopy) diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c index b5439643f54b..a024f8042259 100644 --- a/drivers/misc/aspeed-lpc-ctrl.c +++ b/drivers/misc/aspeed-lpc-ctrl.c @@ -7,6 +7,7 @@ * 2 of the License, or (at your option) any later version. */ +#include <linux/clk.h> #include <linux/mfd/syscon.h> #include <linux/miscdevice.h> #include <linux/mm.h> @@ -20,12 +21,17 @@ #define DEVICE_NAME "aspeed-lpc-ctrl" +#define HICR5 0x0 +#define HICR5_ENL2H BIT(8) +#define HICR5_ENFWH BIT(10) + #define HICR7 0x8 #define HICR8 0xc struct aspeed_lpc_ctrl { struct miscdevice miscdev; struct regmap *regmap; + struct clk *clk; phys_addr_t mem_base; resource_size_t mem_size; u32 pnor_size; @@ -153,8 +159,18 @@ static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd, if (rc) return rc; - return regmap_write(lpc_ctrl->regmap, HICR8, - (~(map.size - 1)) | ((map.size >> 16) - 1)); + rc = regmap_write(lpc_ctrl->regmap, HICR8, + (~(map.size - 1)) | ((map.size >> 16) - 1)); + if (rc) + return rc; + + /* + * Enable LPC FHW cycles. This is required for the host to + * access the regions specified. + */ + return regmap_update_bits(lpc_ctrl->regmap, HICR5, + HICR5_ENFWH | HICR5_ENL2H, + HICR5_ENFWH | HICR5_ENL2H); } return -EINVAL; @@ -221,16 +237,33 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) return -ENODEV; } + lpc_ctrl->clk = devm_clk_get(dev, NULL); + if (IS_ERR(lpc_ctrl->clk)) { + dev_err(dev, "couldn't get clock\n"); + return PTR_ERR(lpc_ctrl->clk); + } + rc = clk_prepare_enable(lpc_ctrl->clk); + if (rc) { + dev_err(dev, "couldn't enable clock\n"); + return rc; + } + lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR; lpc_ctrl->miscdev.name = DEVICE_NAME; lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops; lpc_ctrl->miscdev.parent = dev; rc = misc_register(&lpc_ctrl->miscdev); - if (rc) + if (rc) { dev_err(dev, "Unable to register device\n"); - else - dev_info(dev, "Loaded at %pr\n", &resm); + goto err; + } + + dev_info(dev, "Loaded at %pr\n", &resm); + + return 0; +err: + clk_disable_unprepare(lpc_ctrl->clk); return rc; } @@ -239,6 +272,7 @@ static int aspeed_lpc_ctrl_remove(struct platform_device *pdev) struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev); misc_deregister(&lpc_ctrl->miscdev); + clk_disable_unprepare(lpc_ctrl->clk); return 0; } diff --git a/drivers/misc/cardreader/rts5260.c b/drivers/misc/cardreader/rts5260.c index 07cb93abf685..a493b01c5bc6 100644 --- a/drivers/misc/cardreader/rts5260.c +++ b/drivers/misc/cardreader/rts5260.c @@ -388,17 +388,17 @@ static void rts5260_disable_ocp(struct rtsx_pcr *pcr) OC_POWER_DOWN); } -int rts5260_get_ocpstat(struct rtsx_pcr *pcr, u8 *val) +static int rts5260_get_ocpstat(struct rtsx_pcr *pcr, u8 *val) { return rtsx_pci_read_register(pcr, REG_OCPSTAT, val); } -int rts5260_get_ocpstat2(struct rtsx_pcr *pcr, u8 *val) +static int rts5260_get_ocpstat2(struct rtsx_pcr *pcr, u8 *val) { return rtsx_pci_read_register(pcr, REG_DV3318_OCPSTAT, val); } -void rts5260_clear_ocpstat(struct rtsx_pcr *pcr) +static void rts5260_clear_ocpstat(struct rtsx_pcr *pcr) { u8 mask = 0; u8 val = 0; @@ -418,7 +418,7 @@ void rts5260_clear_ocpstat(struct rtsx_pcr *pcr) DV3318_OCP_INT_CLR | DV3318_OCP_CLR, 0); } -void rts5260_process_ocp(struct rtsx_pcr *pcr) +static void rts5260_process_ocp(struct rtsx_pcr *pcr) { if (!pcr->option.ocp_en) return; @@ -449,7 +449,7 @@ void rts5260_process_ocp(struct rtsx_pcr *pcr) } } -int rts5260_init_hw(struct rtsx_pcr *pcr) +static int rts5260_init_hw(struct rtsx_pcr *pcr) { int err; @@ -620,7 +620,7 @@ static int rts5260_extra_init_hw(struct rtsx_pcr *pcr) return 0; } -void rts5260_set_aspm(struct rtsx_pcr *pcr, bool enable) +static void rts5260_set_aspm(struct rtsx_pcr *pcr, bool enable) { struct rtsx_cr_option *option = &pcr->option; u8 val = 0; diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 01f9c4921c50..0c125f207aea 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -1,14 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * at24.c - handle most I2C EEPROMs * * Copyright (C) 2005-2007 David Brownell * Copyright (C) 2008 Wolfram Sang, Pengutronix - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ + #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> @@ -63,8 +60,6 @@ struct at24_client { }; struct at24_data { - struct at24_platform_data chip; - /* * Lock protects against activities from other Linux tasks, * but not from changes by other I2C masters. @@ -75,7 +70,10 @@ struct at24_data { unsigned int num_addresses; unsigned int offset_adj; - struct nvmem_config nvmem_config; + u32 byte_len; + u16 page_size; + u8 flags; + struct nvmem_device *nvmem; struct gpio_desc *wp_gpio; @@ -239,8 +237,6 @@ static const struct acpi_device_id at24_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, at24_acpi_ids); -/*-------------------------------------------------------------------------*/ - /* * This routine supports chips which consume multiple I2C addresses. It * computes the addressing information to be used for a given r/w request. @@ -255,7 +251,7 @@ static struct at24_client *at24_translate_offset(struct at24_data *at24, { unsigned int i; - if (at24->chip.flags & AT24_FLAG_ADDR16) { + if (at24->flags & AT24_FLAG_ADDR16) { i = *offset >> 16; *offset &= 0xffff; } else { @@ -266,6 +262,11 @@ static struct at24_client *at24_translate_offset(struct at24_data *at24, return &at24->client[i]; } +static struct device *at24_base_client_dev(struct at24_data *at24) +{ + return &at24->client[0].client->dev; +} + static size_t at24_adjust_read_count(struct at24_data *at24, unsigned int offset, size_t count) { @@ -277,8 +278,8 @@ static size_t at24_adjust_read_count(struct at24_data *at24, * the next slave address: truncate the count to the slave boundary, * so that the read never straddles slaves. */ - if (at24->chip.flags & AT24_FLAG_NO_RDROL) { - bits = (at24->chip.flags & AT24_FLAG_ADDR16) ? 16 : 8; + if (at24->flags & AT24_FLAG_NO_RDROL) { + bits = (at24->flags & AT24_FLAG_ADDR16) ? 16 : 8; remainder = BIT(bits) - offset; if (count > remainder) count = remainder; @@ -337,7 +338,7 @@ static size_t at24_adjust_write_count(struct at24_data *at24, count = at24->write_max; /* Never roll over backwards, to the start of this page */ - next_page = roundup(offset + 1, at24->chip.page_size); + next_page = roundup(offset + 1, at24->page_size); if (offset + count > next_page) count = next_page - offset; @@ -371,15 +372,18 @@ static ssize_t at24_regmap_write(struct at24_data *at24, const char *buf, static int at24_read(void *priv, unsigned int off, void *val, size_t count) { - struct at24_data *at24 = priv; - struct device *dev = &at24->client[0].client->dev; + struct at24_data *at24; + struct device *dev; char *buf = val; int ret; + at24 = priv; + dev = at24_base_client_dev(at24); + if (unlikely(!count)) return count; - if (off + count > at24->chip.byte_len) + if (off + count > at24->byte_len) return -EINVAL; ret = pm_runtime_get_sync(dev); @@ -395,17 +399,15 @@ static int at24_read(void *priv, unsigned int off, void *val, size_t count) mutex_lock(&at24->lock); while (count) { - int status; - - status = at24_regmap_read(at24, buf, off, count); - if (status < 0) { + ret = at24_regmap_read(at24, buf, off, count); + if (ret < 0) { mutex_unlock(&at24->lock); pm_runtime_put(dev); - return status; + return ret; } - buf += status; - off += status; - count -= status; + buf += ret; + off += ret; + count -= ret; } mutex_unlock(&at24->lock); @@ -417,15 +419,18 @@ static int at24_read(void *priv, unsigned int off, void *val, size_t count) static int at24_write(void *priv, unsigned int off, void *val, size_t count) { - struct at24_data *at24 = priv; - struct device *dev = &at24->client[0].client->dev; + struct at24_data *at24; + struct device *dev; char *buf = val; int ret; + at24 = priv; + dev = at24_base_client_dev(at24); + if (unlikely(!count)) return -EINVAL; - if (off + count > at24->chip.byte_len) + if (off + count > at24->byte_len) return -EINVAL; ret = pm_runtime_get_sync(dev); @@ -442,18 +447,16 @@ static int at24_write(void *priv, unsigned int off, void *val, size_t count) gpiod_set_value_cansleep(at24->wp_gpio, 0); while (count) { - int status; - - status = at24_regmap_write(at24, buf, off, count); - if (status < 0) { + ret = at24_regmap_write(at24, buf, off, count); + if (ret < 0) { gpiod_set_value_cansleep(at24->wp_gpio, 1); mutex_unlock(&at24->lock); pm_runtime_put(dev); - return status; + return ret; } - buf += status; - off += status; - count -= status; + buf += ret; + off += ret; + count -= ret; } gpiod_set_value_cansleep(at24->wp_gpio, 1); @@ -464,7 +467,8 @@ static int at24_write(void *priv, unsigned int off, void *val, size_t count) return 0; } -static void at24_get_pdata(struct device *dev, struct at24_platform_data *chip) +static void at24_properties_to_pdata(struct device *dev, + struct at24_platform_data *chip) { int err; u32 val; @@ -491,6 +495,43 @@ static void at24_get_pdata(struct device *dev, struct at24_platform_data *chip) } } +static int at24_get_pdata(struct device *dev, struct at24_platform_data *pdata) +{ + struct device_node *of_node = dev->of_node; + const struct at24_chip_data *cdata; + const struct i2c_device_id *id; + struct at24_platform_data *pd; + + pd = dev_get_platdata(dev); + if (pd) { + memcpy(pdata, pd, sizeof(*pdata)); + return 0; + } + + id = i2c_match_id(at24_ids, to_i2c_client(dev)); + + /* + * The I2C core allows OF nodes compatibles to match against the + * I2C device ID table as a fallback, so check not only if an OF + * node is present but also if it matches an OF device ID entry. + */ + if (of_node && of_match_device(at24_of_match, dev)) + cdata = of_device_get_match_data(dev); + else if (id) + cdata = (void *)&id->driver_data; + else + cdata = acpi_device_get_match_data(dev); + + if (!cdata) + return -ENODEV; + + pdata->byte_len = cdata->byte_len; + pdata->flags = cdata->flags; + at24_properties_to_pdata(dev, pdata); + + return 0; +} + static unsigned int at24_get_offset_adj(u8 flags, unsigned int byte_len) { if (flags & AT24_FLAG_MAC) { @@ -514,102 +555,83 @@ static unsigned int at24_get_offset_adj(u8 flags, unsigned int byte_len) } } -static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int at24_probe(struct i2c_client *client) { - struct at24_platform_data chip = { 0 }; - const struct at24_chip_data *cd = NULL; - bool writable; - struct at24_data *at24; - int err; - unsigned int i, num_addresses; struct regmap_config regmap_config = { }; + struct nvmem_config nvmem_config = { }; + struct at24_platform_data pdata = { }; + struct device *dev = &client->dev; + bool i2c_fn_i2c, i2c_fn_block; + unsigned int i, num_addresses; + struct at24_data *at24; + struct regmap *regmap; + size_t at24_size; + bool writable; u8 test_byte; + int err; - if (client->dev.platform_data) { - chip = *(struct at24_platform_data *)client->dev.platform_data; - } else { - /* - * The I2C core allows OF nodes compatibles to match against the - * I2C device ID table as a fallback, so check not only if an OF - * node is present but also if it matches an OF device ID entry. - */ - if (client->dev.of_node && - of_match_device(at24_of_match, &client->dev)) { - cd = of_device_get_match_data(&client->dev); - } else if (id) { - cd = (void *)id->driver_data; - } else { - const struct acpi_device_id *aid; - - aid = acpi_match_device(at24_acpi_ids, &client->dev); - if (aid) - cd = (void *)aid->driver_data; - } - if (!cd) - return -ENODEV; + i2c_fn_i2c = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + i2c_fn_block = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK); - chip.byte_len = cd->byte_len; - chip.flags = cd->flags; - at24_get_pdata(&client->dev, &chip); - } + err = at24_get_pdata(dev, &pdata); + if (err) + return err; + + if (!i2c_fn_i2c && !i2c_fn_block) + pdata.page_size = 1; - if (!is_power_of_2(chip.byte_len)) - dev_warn(&client->dev, - "byte_len looks suspicious (no power of 2)!\n"); - if (!chip.page_size) { - dev_err(&client->dev, "page_size must not be 0!\n"); + if (!pdata.page_size) { + dev_err(dev, "page_size must not be 0!\n"); return -EINVAL; } - if (!is_power_of_2(chip.page_size)) - dev_warn(&client->dev, - "page_size looks suspicious (no power of 2)!\n"); - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C) && - !i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) - chip.page_size = 1; + if (!is_power_of_2(pdata.page_size)) + dev_warn(dev, "page_size looks suspicious (no power of 2)!\n"); - if (chip.flags & AT24_FLAG_TAKE8ADDR) + if (pdata.flags & AT24_FLAG_TAKE8ADDR) num_addresses = 8; else - num_addresses = DIV_ROUND_UP(chip.byte_len, - (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256); + num_addresses = DIV_ROUND_UP(pdata.byte_len, + (pdata.flags & AT24_FLAG_ADDR16) ? 65536 : 256); + + if ((pdata.flags & AT24_FLAG_SERIAL) && (pdata.flags & AT24_FLAG_MAC)) { + dev_err(dev, + "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC."); + return -EINVAL; + } regmap_config.val_bits = 8; - regmap_config.reg_bits = (chip.flags & AT24_FLAG_ADDR16) ? 16 : 8; + regmap_config.reg_bits = (pdata.flags & AT24_FLAG_ADDR16) ? 16 : 8; + regmap_config.disable_locking = true; - at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) + - num_addresses * sizeof(struct at24_client), GFP_KERNEL); + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + at24_size = sizeof(*at24) + num_addresses * sizeof(struct at24_client); + at24 = devm_kzalloc(dev, at24_size, GFP_KERNEL); if (!at24) return -ENOMEM; mutex_init(&at24->lock); - at24->chip = chip; + at24->byte_len = pdata.byte_len; + at24->page_size = pdata.page_size; + at24->flags = pdata.flags; at24->num_addresses = num_addresses; - at24->offset_adj = at24_get_offset_adj(chip.flags, chip.byte_len); + at24->offset_adj = at24_get_offset_adj(pdata.flags, pdata.byte_len); + at24->client[0].client = client; + at24->client[0].regmap = regmap; - at24->wp_gpio = devm_gpiod_get_optional(&client->dev, - "wp", GPIOD_OUT_HIGH); + at24->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_HIGH); if (IS_ERR(at24->wp_gpio)) return PTR_ERR(at24->wp_gpio); - at24->client[0].client = client; - at24->client[0].regmap = devm_regmap_init_i2c(client, ®map_config); - if (IS_ERR(at24->client[0].regmap)) - return PTR_ERR(at24->client[0].regmap); - - if ((chip.flags & AT24_FLAG_SERIAL) && (chip.flags & AT24_FLAG_MAC)) { - dev_err(&client->dev, - "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC."); - return -EINVAL; - } - - writable = !(chip.flags & AT24_FLAG_READONLY); + writable = !(pdata.flags & AT24_FLAG_READONLY); if (writable) { at24->write_max = min_t(unsigned int, - chip.page_size, at24_io_limit); - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C) && - at24->write_max > I2C_SMBUS_BLOCK_MAX) + pdata.page_size, at24_io_limit); + if (!i2c_fn_i2c && at24->write_max > I2C_SMBUS_BLOCK_MAX) at24->write_max = I2C_SMBUS_BLOCK_MAX; } @@ -618,8 +640,8 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) at24->client[i].client = i2c_new_dummy(client->adapter, client->addr + i); if (!at24->client[i].client) { - dev_err(&client->dev, "address 0x%02x unavailable\n", - client->addr + i); + dev_err(dev, "address 0x%02x unavailable\n", + client->addr + i); err = -EADDRINUSE; goto err_clients; } @@ -635,48 +657,47 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) i2c_set_clientdata(client, at24); /* enable runtime pm */ - pm_runtime_set_active(&client->dev); - pm_runtime_enable(&client->dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); /* * Perform a one-byte test read to verify that the * chip is functional. */ err = at24_read(at24, 0, &test_byte, 1); - pm_runtime_idle(&client->dev); + pm_runtime_idle(dev); if (err) { err = -ENODEV; goto err_clients; } - at24->nvmem_config.name = dev_name(&client->dev); - at24->nvmem_config.dev = &client->dev; - at24->nvmem_config.read_only = !writable; - at24->nvmem_config.root_only = true; - at24->nvmem_config.owner = THIS_MODULE; - at24->nvmem_config.compat = true; - at24->nvmem_config.base_dev = &client->dev; - at24->nvmem_config.reg_read = at24_read; - at24->nvmem_config.reg_write = at24_write; - at24->nvmem_config.priv = at24; - at24->nvmem_config.stride = 1; - at24->nvmem_config.word_size = 1; - at24->nvmem_config.size = chip.byte_len; - - at24->nvmem = nvmem_register(&at24->nvmem_config); - + nvmem_config.name = dev_name(dev); + nvmem_config.dev = dev; + nvmem_config.read_only = !writable; + nvmem_config.root_only = true; + nvmem_config.owner = THIS_MODULE; + nvmem_config.compat = true; + nvmem_config.base_dev = dev; + nvmem_config.reg_read = at24_read; + nvmem_config.reg_write = at24_write; + nvmem_config.priv = at24; + nvmem_config.stride = 1; + nvmem_config.word_size = 1; + nvmem_config.size = pdata.byte_len; + + at24->nvmem = nvmem_register(&nvmem_config); if (IS_ERR(at24->nvmem)) { err = PTR_ERR(at24->nvmem); goto err_clients; } - dev_info(&client->dev, "%u byte %s EEPROM, %s, %u bytes/write\n", - chip.byte_len, client->name, - writable ? "writable" : "read-only", at24->write_max); + dev_info(dev, "%u byte %s EEPROM, %s, %u bytes/write\n", + pdata.byte_len, client->name, + writable ? "writable" : "read-only", at24->write_max); /* export data to kernel code */ - if (chip.setup) - chip.setup(at24->nvmem, chip.context); + if (pdata.setup) + pdata.setup(at24->nvmem, pdata.context); return 0; @@ -685,7 +706,7 @@ err_clients: if (at24->client[i].client) i2c_unregister_device(at24->client[i].client); - pm_runtime_disable(&client->dev); + pm_runtime_disable(dev); return err; } @@ -708,15 +729,13 @@ static int at24_remove(struct i2c_client *client) return 0; } -/*-------------------------------------------------------------------------*/ - static struct i2c_driver at24_driver = { .driver = { .name = "at24", .of_match_table = at24_of_match, .acpi_match_table = ACPI_PTR(at24_acpi_ids), }, - .probe = at24_probe, + .probe_new = at24_probe, .remove = at24_remove, .id_table = at24_ids, }; diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 9282ffd607ff..6a7d4a2ad514 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -102,7 +102,7 @@ static int at25_ee_read(void *priv, unsigned int offset, } spi_message_init(&m); - memset(t, 0, sizeof t); + memset(t, 0, sizeof(t)); t[0].tx_buf = command; t[0].len = at25->addrlen + 1; diff --git a/drivers/misc/lkdtm/Makefile b/drivers/misc/lkdtm/Makefile new file mode 100644 index 000000000000..3370a4138e94 --- /dev/null +++ b/drivers/misc/lkdtm/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_LKDTM) += lkdtm.o + +lkdtm-$(CONFIG_LKDTM) += core.o +lkdtm-$(CONFIG_LKDTM) += bugs.o +lkdtm-$(CONFIG_LKDTM) += heap.o +lkdtm-$(CONFIG_LKDTM) += perms.o +lkdtm-$(CONFIG_LKDTM) += refcount.o +lkdtm-$(CONFIG_LKDTM) += rodata_objcopy.o +lkdtm-$(CONFIG_LKDTM) += usercopy.o + +KCOV_INSTRUMENT_rodata.o := n + +OBJCOPYFLAGS := +OBJCOPYFLAGS_rodata_objcopy.o := \ + --set-section-flags .text=alloc,readonly \ + --rename-section .text=.rodata +targets += rodata.o rodata_objcopy.o +$(obj)/rodata_objcopy.o: $(obj)/rodata.o FORCE + $(call if_changed,objcopy) diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm/bugs.c index 7eebbdfbcacd..7eebbdfbcacd 100644 --- a/drivers/misc/lkdtm_bugs.c +++ b/drivers/misc/lkdtm/bugs.c diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm/core.c index 2154d1bfd18b..2154d1bfd18b 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm/core.c diff --git a/drivers/misc/lkdtm_heap.c b/drivers/misc/lkdtm/heap.c index 65026d7de130..65026d7de130 100644 --- a/drivers/misc/lkdtm_heap.c +++ b/drivers/misc/lkdtm/heap.c diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h index 9e513dcfd809..9e513dcfd809 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm/lkdtm.h diff --git a/drivers/misc/lkdtm_perms.c b/drivers/misc/lkdtm/perms.c index 53b85c9d16b8..53b85c9d16b8 100644 --- a/drivers/misc/lkdtm_perms.c +++ b/drivers/misc/lkdtm/perms.c diff --git a/drivers/misc/lkdtm_refcount.c b/drivers/misc/lkdtm/refcount.c index 2b99d448e7fd..0a146b32da13 100644 --- a/drivers/misc/lkdtm_refcount.c +++ b/drivers/misc/lkdtm/refcount.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * This is for all the tests related to refcount bugs (e.g. overflow, * underflow, reaching zero untested, etc). diff --git a/drivers/misc/lkdtm_rodata.c b/drivers/misc/lkdtm/rodata.c index 58d180af72cf..58d180af72cf 100644 --- a/drivers/misc/lkdtm_rodata.c +++ b/drivers/misc/lkdtm/rodata.c diff --git a/drivers/misc/lkdtm_usercopy.c b/drivers/misc/lkdtm/usercopy.c index 9725aed305bb..9725aed305bb 100644 --- a/drivers/misc/lkdtm_usercopy.c +++ b/drivers/misc/lkdtm/usercopy.c diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 772d02922529..b1133739fb4b 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -74,6 +74,23 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, goto out; } + while (cl->tx_cb_queued >= bus->tx_queue_limit) { + mutex_unlock(&bus->device_lock); + rets = wait_event_interruptible(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE || + (!mei_cl_is_connected(cl))); + mutex_lock(&bus->device_lock); + if (rets) { + if (signal_pending(current)) + rets = -EINTR; + goto out; + } + if (!mei_cl_is_connected(cl)) { + rets = -ENODEV; + goto out; + } + } + cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL); if (!cb) { rets = -ENOMEM; @@ -450,6 +467,29 @@ bool mei_cldev_enabled(struct mei_cl_device *cldev) EXPORT_SYMBOL_GPL(mei_cldev_enabled); /** + * mei_cl_bus_module_get - acquire module of the underlying + * hw driver. + * + * @cldev: mei client device + * + * Return: true on success; false if the module was removed. + */ +static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) +{ + return try_module_get(cldev->bus->dev->driver->owner); +} + +/** + * mei_cl_bus_module_put - release the underlying hw module. + * + * @cldev: mei client device + */ +static void mei_cl_bus_module_put(struct mei_cl_device *cldev) +{ + module_put(cldev->bus->dev->driver->owner); +} + +/** * mei_cldev_enable - enable me client device * create connection with me client * @@ -487,9 +527,17 @@ int mei_cldev_enable(struct mei_cl_device *cldev) goto out; } + if (!mei_cl_bus_module_get(cldev)) { + dev_err(&cldev->dev, "get hw module failed"); + ret = -ENODEV; + goto out; + } + ret = mei_cl_connect(cl, cldev->me_cl, NULL); - if (ret < 0) + if (ret < 0) { dev_err(&cldev->dev, "cannot connect\n"); + mei_cl_bus_module_put(cldev); + } out: mutex_unlock(&bus->device_lock); @@ -553,6 +601,8 @@ int mei_cldev_disable(struct mei_cl_device *cldev) dev_err(bus->dev, "Could not disconnect from the ME client\n"); out: + mei_cl_bus_module_put(cldev); + /* Flush queues and remove any pending read */ mei_cl_flush_queues(cl, NULL); mei_cl_unlink(cl); @@ -563,37 +613,6 @@ out: EXPORT_SYMBOL_GPL(mei_cldev_disable); /** - * mei_cl_bus_module_get - acquire module of the underlying - * hw module. - * - * @cl: host client - * - * Return: true on success; false if the module was removed. - */ -bool mei_cl_bus_module_get(struct mei_cl *cl) -{ - struct mei_cl_device *cldev = cl->cldev; - - if (!cldev) - return true; - - return try_module_get(cldev->bus->dev->driver->owner); -} - -/** - * mei_cl_bus_module_put - release the underlying hw module. - * - * @cl: host client - */ -void mei_cl_bus_module_put(struct mei_cl *cl) -{ - struct mei_cl_device *cldev = cl->cldev; - - if (cldev) - module_put(cldev->bus->dev->driver->owner); -} - -/** * mei_cl_device_find - find matching entry in the driver id table * * @cldev: me client device diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 7e60c1817c31..8d6197a88b54 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -350,6 +350,36 @@ void mei_io_cb_free(struct mei_cl_cb *cb) } /** + * mei_tx_cb_queue - queue tx callback + * + * Locking: called under "dev->device_lock" lock + * + * @cb: mei callback struct + * @head: an instance of list to queue on + */ +static inline void mei_tx_cb_enqueue(struct mei_cl_cb *cb, + struct list_head *head) +{ + list_add_tail(&cb->list, head); + cb->cl->tx_cb_queued++; +} + +/** + * mei_tx_cb_dequeue - dequeue tx callback + * + * Locking: called under "dev->device_lock" lock + * + * @cb: mei callback struct to dequeue and free + */ +static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb) +{ + if (!WARN_ON(cb->cl->tx_cb_queued == 0)) + cb->cl->tx_cb_queued--; + + mei_io_cb_free(cb); +} + +/** * mei_io_cb_init - allocate and initialize io callback * * @cl: mei client @@ -377,49 +407,37 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, } /** - * __mei_io_list_flush_cl - removes and frees cbs belonging to cl. + * mei_io_list_flush_cl - removes cbs belonging to the cl. * * @head: an instance of our list structure - * @cl: host client, can be NULL for flushing the whole list - * @free: whether to free the cbs + * @cl: host client */ -static void __mei_io_list_flush_cl(struct list_head *head, - const struct mei_cl *cl, bool free) +static void mei_io_list_flush_cl(struct list_head *head, + const struct mei_cl *cl) { struct mei_cl_cb *cb, *next; - /* enable removing everything if no cl is specified */ list_for_each_entry_safe(cb, next, head, list) { - if (!cl || mei_cl_cmp_id(cl, cb->cl)) { + if (mei_cl_cmp_id(cl, cb->cl)) list_del_init(&cb->list); - if (free) - mei_io_cb_free(cb); - } } } /** - * mei_io_list_flush_cl - removes list entry belonging to cl. + * mei_io_tx_list_free_cl - removes cb belonging to the cl and free them * * @head: An instance of our list structure * @cl: host client */ -static inline void mei_io_list_flush_cl(struct list_head *head, - const struct mei_cl *cl) +static void mei_io_tx_list_free_cl(struct list_head *head, + const struct mei_cl *cl) { - __mei_io_list_flush_cl(head, cl, false); -} + struct mei_cl_cb *cb, *next; -/** - * mei_io_list_free_cl - removes cb belonging to cl and free them - * - * @head: An instance of our list structure - * @cl: host client - */ -static inline void mei_io_list_free_cl(struct list_head *head, - const struct mei_cl *cl) -{ - __mei_io_list_flush_cl(head, cl, true); + list_for_each_entry_safe(cb, next, head, list) { + if (mei_cl_cmp_id(cl, cb->cl)) + mei_tx_cb_dequeue(cb); + } } /** @@ -538,8 +556,8 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) dev = cl->dev; cl_dbg(dev, cl, "remove list entry belonging to cl\n"); - mei_io_list_free_cl(&cl->dev->write_list, cl); - mei_io_list_free_cl(&cl->dev->write_waiting_list, cl); + mei_io_tx_list_free_cl(&cl->dev->write_list, cl); + mei_io_tx_list_free_cl(&cl->dev->write_waiting_list, cl); mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl); mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl); mei_io_list_free_fp(&cl->rd_pending, fp); @@ -756,8 +774,8 @@ static void mei_cl_set_disconnected(struct mei_cl *cl) return; cl->state = MEI_FILE_DISCONNECTED; - mei_io_list_free_cl(&dev->write_list, cl); - mei_io_list_free_cl(&dev->write_waiting_list, cl); + mei_io_tx_list_free_cl(&dev->write_list, cl); + mei_io_tx_list_free_cl(&dev->write_waiting_list, cl); mei_io_list_flush_cl(&dev->ctrl_rd_list, cl); mei_io_list_flush_cl(&dev->ctrl_wr_list, cl); mei_cl_wake_all(cl); @@ -765,8 +783,6 @@ static void mei_cl_set_disconnected(struct mei_cl *cl) cl->tx_flow_ctrl_creds = 0; cl->timer_count = 0; - mei_cl_bus_module_put(cl); - if (!cl->me_cl) return; @@ -1076,9 +1092,6 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, dev = cl->dev; - if (!mei_cl_bus_module_get(cl)) - return -ENODEV; - rets = mei_cl_set_connecting(cl, me_cl); if (rets) goto nortpm; @@ -1698,9 +1711,9 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) out: if (mei_hdr.msg_complete) - list_add_tail(&cb->list, &dev->write_waiting_list); + mei_tx_cb_enqueue(cb, &dev->write_waiting_list); else - list_add_tail(&cb->list, &dev->write_list); + mei_tx_cb_enqueue(cb, &dev->write_list); cb = NULL; if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { @@ -1746,7 +1759,7 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) switch (cb->fop_type) { case MEI_FOP_WRITE: - mei_io_cb_free(cb); + mei_tx_cb_dequeue(cb); cl->writing_state = MEI_WRITE_COMPLETE; if (waitqueue_active(&cl->tx_wait)) { wake_up_interruptible(&cl->tx_wait); diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index a617aa5a3ad8..c815da91089c 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -97,7 +97,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf, int pos = 0; int ret; -#define HDR " |me|host|state|rd|wr|\n" +#define HDR " |me|host|state|rd|wr|wrq\n" if (!dev) return -ENODEV; @@ -130,9 +130,10 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf, list_for_each_entry(cl, &dev->file_list, link) { pos += scnprintf(buf + pos, bufsz - pos, - "%3d|%2d|%4d|%5d|%2d|%2d|\n", + "%3d|%2d|%4d|%5d|%2d|%2d|%3u\n", i, mei_cl_me_id(cl), cl->host_client_id, cl->state, - !list_empty(&cl->rd_completed), cl->writing_state); + !list_empty(&cl->rd_completed), cl->writing_state, + cl->tx_cb_queued); i++; } out: diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index c46f6e99a55e..4888ebc076b7 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -383,6 +383,7 @@ void mei_device_init(struct mei_device *dev, INIT_LIST_HEAD(&dev->write_waiting_list); INIT_LIST_HEAD(&dev->ctrl_wr_list); INIT_LIST_HEAD(&dev->ctrl_rd_list); + dev->tx_queue_limit = MEI_TX_QUEUE_LIMIT_DEFAULT; INIT_DELAYED_WORK(&dev->timer_work, mei_timer); INIT_WORK(&dev->reset_work, mei_reset_work); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 758dc73602d5..7465f17e1559 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -291,6 +291,27 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, goto out; } + while (cl->tx_cb_queued >= dev->tx_queue_limit) { + if (file->f_flags & O_NONBLOCK) { + rets = -EAGAIN; + goto out; + } + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE || + (!mei_cl_is_connected(cl))); + mutex_lock(&dev->device_lock); + if (rets) { + if (signal_pending(current)) + rets = -EINTR; + goto out; + } + if (!mei_cl_is_connected(cl)) { + rets = -ENODEV; + goto out; + } + } + *offset = 0; cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); if (!cb) { @@ -507,7 +528,6 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) break; default: - dev_err(dev->dev, ": unsupported ioctl %d.\n", cmd); rets = -ENOIOCTLCMD; } @@ -580,6 +600,12 @@ static __poll_t mei_poll(struct file *file, poll_table *wait) mei_cl_read_start(cl, mei_cl_mtu(cl), file); } + if (req_events & (POLLOUT | POLLWRNORM)) { + poll_wait(file, &cl->tx_wait, wait); + if (cl->tx_cb_queued < dev->tx_queue_limit) + mask |= POLLOUT | POLLWRNORM; + } + out: mutex_unlock(&dev->device_lock); return mask; @@ -749,10 +775,48 @@ static ssize_t hbm_ver_drv_show(struct device *device, } static DEVICE_ATTR_RO(hbm_ver_drv); +static ssize_t tx_queue_limit_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct mei_device *dev = dev_get_drvdata(device); + u8 size = 0; + + mutex_lock(&dev->device_lock); + size = dev->tx_queue_limit; + mutex_unlock(&dev->device_lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", size); +} + +static ssize_t tx_queue_limit_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mei_device *dev = dev_get_drvdata(device); + u8 limit; + unsigned int inp; + int err; + + err = kstrtouint(buf, 10, &inp); + if (err) + return err; + if (inp > MEI_TX_QUEUE_LIMIT_MAX || inp < MEI_TX_QUEUE_LIMIT_MIN) + return -EINVAL; + limit = inp; + + mutex_lock(&dev->device_lock); + dev->tx_queue_limit = limit; + mutex_unlock(&dev->device_lock); + + return count; +} +static DEVICE_ATTR_RW(tx_queue_limit); + static struct attribute *mei_attrs[] = { &dev_attr_fw_status.attr, &dev_attr_hbm_ver.attr, &dev_attr_hbm_ver_drv.attr, + &dev_attr_tx_queue_limit.attr, NULL }; ATTRIBUTE_GROUPS(mei); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index ebcd5132e447..be9c48415da9 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -210,6 +210,7 @@ struct mei_cl_cb { * @timer_count: watchdog timer for operation completion * @notify_en: notification - enabled/disabled * @notify_ev: pending notification event + * @tx_cb_queued: number of tx callbacks in queue * @writing_state: state of the tx * @rd_pending: pending read credits * @rd_completed: completed read @@ -234,6 +235,7 @@ struct mei_cl { u8 timer_count; u8 notify_en; u8 notify_ev; + u8 tx_cb_queued; enum mei_file_transaction_states writing_state; struct list_head rd_pending; struct list_head rd_completed; @@ -241,6 +243,10 @@ struct mei_cl { struct mei_cl_device *cldev; }; +#define MEI_TX_QUEUE_LIMIT_DEFAULT 50 +#define MEI_TX_QUEUE_LIMIT_MAX 255 +#define MEI_TX_QUEUE_LIMIT_MIN 30 + /** * struct mei_hw_ops - hw specific ops * @@ -315,8 +321,6 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, bool mei_cl_bus_rx_event(struct mei_cl *cl); bool mei_cl_bus_notify_event(struct mei_cl *cl); void mei_cl_bus_remove_devices(struct mei_device *bus); -bool mei_cl_bus_module_get(struct mei_cl *cl); -void mei_cl_bus_module_put(struct mei_cl *cl); int mei_cl_bus_init(void); void mei_cl_bus_exit(void); @@ -361,6 +365,7 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @write_waiting_list : write completion list * @ctrl_wr_list : pending control write list * @ctrl_rd_list : pending control read list + * @tx_queue_limit: tx queues per client linit * * @file_list : list of opened handles * @open_handle_count: number of opened handles @@ -425,6 +430,7 @@ struct mei_device { struct list_head write_waiting_list; struct list_head ctrl_wr_list; struct list_head ctrl_rd_list; + u8 tx_queue_limit; struct list_head file_list; long open_handle_count; diff --git a/drivers/misc/mic/bus/vop_bus.c b/drivers/misc/mic/bus/vop_bus.c index fd7f2a6049f8..e5bb9c749b5d 100644 --- a/drivers/misc/mic/bus/vop_bus.c +++ b/drivers/misc/mic/bus/vop_bus.c @@ -135,7 +135,9 @@ EXPORT_SYMBOL_GPL(vop_unregister_driver); static void vop_release_dev(struct device *d) { - put_device(d); + struct vop_device *dev = dev_to_vop(d); + + kfree(dev); } struct vop_device * @@ -174,7 +176,7 @@ vop_register_device(struct device *pdev, int id, goto free_vdev; return vdev; free_vdev: - kfree(vdev); + put_device(&vdev->dev); return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(vop_register_device); diff --git a/drivers/misc/ocxl/pci.c b/drivers/misc/ocxl/pci.c index 0051d9ec76cc..21f425472a82 100644 --- a/drivers/misc/ocxl/pci.c +++ b/drivers/misc/ocxl/pci.c @@ -519,7 +519,7 @@ static struct ocxl_fn *init_function(struct pci_dev *dev) rc = device_register(&fn->dev); if (rc) { deconfigure_function(fn); - device_unregister(&fn->dev); + put_device(&fn->dev); return ERR_PTR(rc); } return fn; diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 55cf64ee7f7b..1090924efdb1 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -167,10 +167,10 @@ config MESON_MX_EFUSE config NVMEM_SNVS_LPGPR tristate "Support for Low Power General Purpose Register" - depends on SOC_IMX6 || COMPILE_TEST + depends on SOC_IMX6 || SOC_IMX7D || COMPILE_TEST help This is a driver for Low Power General Purpose Register (LPGPR) available on - i.MX6 SoCs in Secure Non-Volatile Storage (SNVS) of this chip. + i.MX6 and i.MX7 SoCs in Secure Non-Volatile Storage (SNVS) of this chip. This driver can also be built as a module. If so, the module will be called nvmem-snvs-lpgpr. diff --git a/drivers/nvmem/bcm-ocotp.c b/drivers/nvmem/bcm-ocotp.c index 5e9e324427f9..4159b3f41d79 100644 --- a/drivers/nvmem/bcm-ocotp.c +++ b/drivers/nvmem/bcm-ocotp.c @@ -262,8 +262,7 @@ static int bcm_otpc_probe(struct platform_device *pdev) else if (of_device_is_compatible(dev->of_node, "brcm,ocotp-v2")) priv->map = &otp_map_v2; else { - dev_err(&pdev->dev, - "%s otpc config map not defined\n", __func__); + dev_err(dev, "%s otpc config map not defined\n", __func__); return -EINVAL; } @@ -302,27 +301,17 @@ static int bcm_otpc_probe(struct platform_device *pdev) priv->config = &bcm_otpc_nvmem_config; - nvmem = nvmem_register(&bcm_otpc_nvmem_config); + nvmem = devm_nvmem_register(dev, &bcm_otpc_nvmem_config); if (IS_ERR(nvmem)) { dev_err(dev, "error registering nvmem config\n"); return PTR_ERR(nvmem); } - platform_set_drvdata(pdev, nvmem); - return 0; } -static int bcm_otpc_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); -} - static struct platform_driver bcm_otpc_driver = { .probe = bcm_otpc_probe, - .remove = bcm_otpc_remove, .driver = { .name = "brcm-otpc", .of_match_table = bcm_otpc_dt_ids, diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 35a3dbeea324..b05aa8e81303 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -473,9 +473,14 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->reg_read = config->reg_read; nvmem->reg_write = config->reg_write; nvmem->dev.of_node = config->dev->of_node; - dev_set_name(&nvmem->dev, "%s%d", - config->name ? : "nvmem", - config->name ? config->id : nvmem->id); + + if (config->id == -1 && config->name) { + dev_set_name(&nvmem->dev, "%s", config->name); + } else { + dev_set_name(&nvmem->dev, "%s%d", + config->name ? : "nvmem", + config->name ? config->id : nvmem->id); + } nvmem->read_only = device_property_present(config->dev, "read-only") | config->read_only; @@ -544,6 +549,65 @@ int nvmem_unregister(struct nvmem_device *nvmem) } EXPORT_SYMBOL_GPL(nvmem_unregister); +static void devm_nvmem_release(struct device *dev, void *res) +{ + WARN_ON(nvmem_unregister(*(struct nvmem_device **)res)); +} + +/** + * devm_nvmem_register() - Register a managed nvmem device for given + * nvmem_config. + * Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem + * + * @config: nvmem device configuration with which nvmem device is created. + * + * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device + * on success. + */ +struct nvmem_device *devm_nvmem_register(struct device *dev, + const struct nvmem_config *config) +{ + struct nvmem_device **ptr, *nvmem; + + ptr = devres_alloc(devm_nvmem_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + nvmem = nvmem_register(config); + + if (!IS_ERR(nvmem)) { + *ptr = nvmem; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return nvmem; +} +EXPORT_SYMBOL_GPL(devm_nvmem_register); + +static int devm_nvmem_match(struct device *dev, void *res, void *data) +{ + struct nvmem_device **r = res; + + return *r == data; +} + +/** + * devm_nvmem_unregister() - Unregister previously registered managed nvmem + * device. + * + * @nvmem: Pointer to previously registered nvmem device. + * + * Return: Will be an negative on error or a zero on success. + */ +int devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem) +{ + return devres_release(dev, devm_nvmem_release, devm_nvmem_match, nvmem); +} +EXPORT_SYMBOL(devm_nvmem_unregister); + + static struct nvmem_device *__nvmem_device_get(struct device_node *np, struct nvmem_cell **cellp, const char *cell_id) diff --git a/drivers/nvmem/imx-iim.c b/drivers/nvmem/imx-iim.c index 52cfe91d9762..6651e4cdc002 100644 --- a/drivers/nvmem/imx-iim.c +++ b/drivers/nvmem/imx-iim.c @@ -125,7 +125,7 @@ static int imx_iim_probe(struct platform_device *pdev) drvdata = of_id->data; - iim->clk = devm_clk_get(&pdev->dev, NULL); + iim->clk = devm_clk_get(dev, NULL); if (IS_ERR(iim->clk)) return PTR_ERR(iim->clk); @@ -138,25 +138,13 @@ static int imx_iim_probe(struct platform_device *pdev) cfg.size = drvdata->nregs; cfg.priv = iim; - nvmem = nvmem_register(&cfg); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + nvmem = devm_nvmem_register(dev, &cfg); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int imx_iim_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static struct platform_driver imx_iim_driver = { .probe = imx_iim_probe, - .remove = imx_iim_remove, .driver = { .name = "imx-iim", .of_match_table = imx_iim_dt_ids, diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index d7ba351a70c9..60816c856dd6 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -439,7 +439,6 @@ MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); static int imx_ocotp_probe(struct platform_device *pdev) { - const struct of_device_id *of_id; struct device *dev = &pdev->dev; struct resource *res; struct ocotp_priv *priv; @@ -460,32 +459,19 @@ static int imx_ocotp_probe(struct platform_device *pdev) if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); - of_id = of_match_device(imx_ocotp_dt_ids, dev); priv->params = of_device_get_match_data(&pdev->dev); imx_ocotp_nvmem_config.size = 4 * priv->params->nregs; imx_ocotp_nvmem_config.dev = dev; imx_ocotp_nvmem_config.priv = priv; priv->config = &imx_ocotp_nvmem_config; - nvmem = nvmem_register(&imx_ocotp_nvmem_config); + nvmem = devm_nvmem_register(dev, &imx_ocotp_nvmem_config); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int imx_ocotp_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static struct platform_driver imx_ocotp_driver = { .probe = imx_ocotp_probe, - .remove = imx_ocotp_remove, .driver = { .name = "imx_ocotp", .of_match_table = imx_ocotp_dt_ids, diff --git a/drivers/nvmem/lpc18xx_otp.c b/drivers/nvmem/lpc18xx_otp.c index 95268db155e9..549b5298ac4c 100644 --- a/drivers/nvmem/lpc18xx_otp.c +++ b/drivers/nvmem/lpc18xx_otp.c @@ -86,20 +86,9 @@ static int lpc18xx_otp_probe(struct platform_device *pdev) lpc18xx_otp_nvmem_config.dev = &pdev->dev; lpc18xx_otp_nvmem_config.priv = otp; - nvmem = nvmem_register(&lpc18xx_otp_nvmem_config); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + nvmem = devm_nvmem_register(&pdev->dev, &lpc18xx_otp_nvmem_config); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int lpc18xx_otp_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static const struct of_device_id lpc18xx_otp_dt_ids[] = { @@ -110,7 +99,6 @@ MODULE_DEVICE_TABLE(of, lpc18xx_otp_dt_ids); static struct platform_driver lpc18xx_otp_driver = { .probe = lpc18xx_otp_probe, - .remove = lpc18xx_otp_remove, .driver = { .name = "lpc18xx_otp", .of_match_table = lpc18xx_otp_dt_ids, diff --git a/drivers/nvmem/meson-efuse.c b/drivers/nvmem/meson-efuse.c index a43c68f90937..71823d1403c5 100644 --- a/drivers/nvmem/meson-efuse.c +++ b/drivers/nvmem/meson-efuse.c @@ -60,25 +60,13 @@ static int meson_efuse_probe(struct platform_device *pdev) econfig.reg_read = meson_efuse_read; econfig.size = size; - nvmem = nvmem_register(&econfig); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + nvmem = devm_nvmem_register(&pdev->dev, &econfig); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int meson_efuse_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static struct platform_driver meson_efuse_driver = { .probe = meson_efuse_probe, - .remove = meson_efuse_remove, .driver = { .name = "meson-efuse", .of_match_table = meson_efuse_match, diff --git a/drivers/nvmem/meson-mx-efuse.c b/drivers/nvmem/meson-mx-efuse.c index 41d3a3c1104e..a085563e39e3 100644 --- a/drivers/nvmem/meson-mx-efuse.c +++ b/drivers/nvmem/meson-mx-efuse.c @@ -233,25 +233,13 @@ static int meson_mx_efuse_probe(struct platform_device *pdev) return PTR_ERR(efuse->core_clk); } - efuse->nvmem = nvmem_register(&efuse->config); - if (IS_ERR(efuse->nvmem)) - return PTR_ERR(efuse->nvmem); + efuse->nvmem = devm_nvmem_register(&pdev->dev, &efuse->config); - platform_set_drvdata(pdev, efuse); - - return 0; -} - -static int meson_mx_efuse_remove(struct platform_device *pdev) -{ - struct meson_mx_efuse *efuse = platform_get_drvdata(pdev); - - return nvmem_unregister(efuse->nvmem); + return PTR_ERR_OR_ZERO(efuse->nvmem); } static struct platform_driver meson_mx_efuse_driver = { .probe = meson_mx_efuse_probe, - .remove = meson_mx_efuse_remove, .driver = { .name = "meson-mx-efuse", .of_match_table = meson_mx_efuse_match, diff --git a/drivers/nvmem/mtk-efuse.c b/drivers/nvmem/mtk-efuse.c index 9ee3479cfc7b..e66adf17a747 100644 --- a/drivers/nvmem/mtk-efuse.c +++ b/drivers/nvmem/mtk-efuse.c @@ -72,20 +72,9 @@ static int mtk_efuse_probe(struct platform_device *pdev) econfig.size = resource_size(res); econfig.priv = priv; econfig.dev = dev; - nvmem = nvmem_register(&econfig); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + nvmem = devm_nvmem_register(dev, &econfig); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int mtk_efuse_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static const struct of_device_id mtk_efuse_of_match[] = { @@ -97,7 +86,6 @@ MODULE_DEVICE_TABLE(of, mtk_efuse_of_match); static struct platform_driver mtk_efuse_driver = { .probe = mtk_efuse_probe, - .remove = mtk_efuse_remove, .driver = { .name = "mediatek,efuse", .of_match_table = mtk_efuse_of_match, diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c index cb3b48b47d64..4f650baad983 100644 --- a/drivers/nvmem/qfprom.c +++ b/drivers/nvmem/qfprom.c @@ -47,13 +47,6 @@ static int qfprom_reg_write(void *context, return 0; } -static int qfprom_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); -} - static struct nvmem_config econfig = { .name = "qfprom", .stride = 1, @@ -82,13 +75,9 @@ static int qfprom_probe(struct platform_device *pdev) econfig.dev = dev; econfig.priv = priv; - nvmem = nvmem_register(&econfig); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); - - platform_set_drvdata(pdev, nvmem); + nvmem = devm_nvmem_register(dev, &econfig); - return 0; + return PTR_ERR_OR_ZERO(nvmem); } static const struct of_device_id qfprom_of_match[] = { @@ -99,7 +88,6 @@ MODULE_DEVICE_TABLE(of, qfprom_of_match); static struct platform_driver qfprom_driver = { .probe = qfprom_probe, - .remove = qfprom_remove, .driver = { .name = "qcom,qfprom", .of_match_table = qfprom_of_match, diff --git a/drivers/nvmem/rockchip-efuse.c b/drivers/nvmem/rockchip-efuse.c index f13a8335f364..b3b0b648be62 100644 --- a/drivers/nvmem/rockchip-efuse.c +++ b/drivers/nvmem/rockchip-efuse.c @@ -259,55 +259,43 @@ static int rockchip_efuse_probe(struct platform_device *pdev) struct resource *res; struct nvmem_device *nvmem; struct rockchip_efuse_chip *efuse; - const struct of_device_id *match; + const void *data; struct device *dev = &pdev->dev; - match = of_match_device(dev->driver->of_match_table, dev); - if (!match || !match->data) { + data = of_device_get_match_data(dev); + if (!data) { dev_err(dev, "failed to get match data\n"); return -EINVAL; } - efuse = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_efuse_chip), + efuse = devm_kzalloc(dev, sizeof(struct rockchip_efuse_chip), GFP_KERNEL); if (!efuse) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - efuse->base = devm_ioremap_resource(&pdev->dev, res); + efuse->base = devm_ioremap_resource(dev, res); if (IS_ERR(efuse->base)) return PTR_ERR(efuse->base); - efuse->clk = devm_clk_get(&pdev->dev, "pclk_efuse"); + efuse->clk = devm_clk_get(dev, "pclk_efuse"); if (IS_ERR(efuse->clk)) return PTR_ERR(efuse->clk); - efuse->dev = &pdev->dev; + efuse->dev = dev; if (of_property_read_u32(dev->of_node, "rockchip,efuse-size", &econfig.size)) econfig.size = resource_size(res); - econfig.reg_read = match->data; + econfig.reg_read = data; econfig.priv = efuse; econfig.dev = efuse->dev; - nvmem = nvmem_register(&econfig); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + nvmem = devm_nvmem_register(dev, &econfig); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int rockchip_efuse_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static struct platform_driver rockchip_efuse_driver = { .probe = rockchip_efuse_probe, - .remove = rockchip_efuse_remove, .driver = { .name = "rockchip-efuse", .of_match_table = rockchip_efuse_match, diff --git a/drivers/nvmem/snvs_lpgpr.c b/drivers/nvmem/snvs_lpgpr.c index e5c2a4a17f03..c050a23a9f2b 100644 --- a/drivers/nvmem/snvs_lpgpr.c +++ b/drivers/nvmem/snvs_lpgpr.c @@ -14,15 +14,21 @@ #include <linux/regmap.h> #define IMX6Q_SNVS_HPLR 0x00 -#define IMX6Q_GPR_SL BIT(5) #define IMX6Q_SNVS_LPLR 0x34 -#define IMX6Q_GPR_HL BIT(5) #define IMX6Q_SNVS_LPGPR 0x68 +#define IMX7D_SNVS_HPLR 0x00 +#define IMX7D_SNVS_LPLR 0x34 +#define IMX7D_SNVS_LPGPR 0x90 + +#define IMX_GPR_SL BIT(5) +#define IMX_GPR_HL BIT(5) + struct snvs_lpgpr_cfg { int offset; int offset_hplr; int offset_lplr; + int size; }; struct snvs_lpgpr_priv { @@ -36,6 +42,14 @@ static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx6q = { .offset = IMX6Q_SNVS_LPGPR, .offset_hplr = IMX6Q_SNVS_HPLR, .offset_lplr = IMX6Q_SNVS_LPLR, + .size = 4, +}; + +static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx7d = { + .offset = IMX7D_SNVS_LPGPR, + .offset_hplr = IMX7D_SNVS_HPLR, + .offset_lplr = IMX7D_SNVS_LPLR, + .size = 16, }; static int snvs_lpgpr_write(void *context, unsigned int offset, void *val, @@ -50,14 +64,14 @@ static int snvs_lpgpr_write(void *context, unsigned int offset, void *val, if (ret < 0) return ret; - if (lock_reg & IMX6Q_GPR_SL) + if (lock_reg & IMX_GPR_SL) return -EPERM; ret = regmap_read(priv->regmap, dcfg->offset_lplr, &lock_reg); if (ret < 0) return ret; - if (lock_reg & IMX6Q_GPR_HL) + if (lock_reg & IMX_GPR_HL) return -EPERM; return regmap_bulk_write(priv->regmap, dcfg->offset + offset, val, @@ -110,40 +124,29 @@ static int snvs_lpgpr_probe(struct platform_device *pdev) cfg->priv = priv; cfg->name = dev_name(dev); cfg->dev = dev; - cfg->stride = 4, - cfg->word_size = 4, - cfg->size = 4, - cfg->owner = THIS_MODULE, - cfg->reg_read = snvs_lpgpr_read, - cfg->reg_write = snvs_lpgpr_write, - - nvmem = nvmem_register(cfg); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + cfg->stride = 4; + cfg->word_size = 4; + cfg->size = dcfg->size, + cfg->owner = THIS_MODULE; + cfg->reg_read = snvs_lpgpr_read; + cfg->reg_write = snvs_lpgpr_write; - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int snvs_lpgpr_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); + nvmem = devm_nvmem_register(dev, cfg); - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static const struct of_device_id snvs_lpgpr_dt_ids[] = { { .compatible = "fsl,imx6q-snvs-lpgpr", .data = &snvs_lpgpr_cfg_imx6q }, { .compatible = "fsl,imx6ul-snvs-lpgpr", .data = &snvs_lpgpr_cfg_imx6q }, + { .compatible = "fsl,imx7d-snvs-lpgpr", .data = &snvs_lpgpr_cfg_imx7d }, { }, }; MODULE_DEVICE_TABLE(of, snvs_lpgpr_dt_ids); static struct platform_driver snvs_lpgpr_driver = { .probe = snvs_lpgpr_probe, - .remove = snvs_lpgpr_remove, .driver = { .name = "snvs_lpgpr", .of_match_table = snvs_lpgpr_dt_ids, @@ -152,5 +155,5 @@ static struct platform_driver snvs_lpgpr_driver = { module_platform_driver(snvs_lpgpr_driver); MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>"); -MODULE_DESCRIPTION("Low Power General Purpose Register in i.MX6 Secure Non-Volatile Storage"); +MODULE_DESCRIPTION("Low Power General Purpose Register in i.MX6 and i.MX7 Secure Non-Volatile Storage"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c index 99bd54d85fcb..26bb637afe92 100644 --- a/drivers/nvmem/sunxi_sid.c +++ b/drivers/nvmem/sunxi_sid.c @@ -85,13 +85,14 @@ static int sunxi_sid_read(void *context, unsigned int offset, } static int sun8i_sid_register_readout(const struct sunxi_sid *sid, - const unsigned int word) + const unsigned int offset, + u32 *out) { u32 reg_val; int ret; /* Set word, lock access, and set read command */ - reg_val = (word & SUN8I_SID_OFFSET_MASK) + reg_val = (offset & SUN8I_SID_OFFSET_MASK) << SUN8I_SID_OFFSET_SHIFT; reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; writel(reg_val, sid->base + SUN8I_SID_PRCTL); @@ -101,7 +102,49 @@ static int sun8i_sid_register_readout(const struct sunxi_sid *sid, if (ret) return ret; + if (out) + *out = readl(sid->base + SUN8I_SID_RDKEY); + writel(0, sid->base + SUN8I_SID_PRCTL); + + return 0; +} + +/* + * On Allwinner H3, the value on the 0x200 offset of the SID controller seems + * to be not reliable at all. + * Read by the registers instead. + */ +static int sun8i_sid_read_byte_by_reg(const struct sunxi_sid *sid, + const unsigned int offset, + u8 *out) +{ + u32 word; + int ret; + + ret = sun8i_sid_register_readout(sid, offset & ~0x03, &word); + + if (ret) + return ret; + + *out = (word >> ((offset & 0x3) * 8)) & 0xff; + + return 0; +} + +static int sun8i_sid_read_by_reg(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct sunxi_sid *sid = context; + u8 *buf = val; + int ret; + + while (bytes--) { + ret = sun8i_sid_read_byte_by_reg(sid, offset++, buf++); + if (ret) + return ret; + } + return 0; } @@ -131,26 +174,12 @@ static int sunxi_sid_probe(struct platform_device *pdev) size = cfg->size; - if (cfg->need_register_readout) { - /* - * H3's SID controller have a bug that the value at 0x200 - * offset is not the correct value when the hardware is reseted. - * However, after doing a register-based read operation, the - * value become right. - * Do a full read operation here, but ignore its value - * (as it's more fast to read by direct MMIO value than - * with registers) - */ - for (i = 0; i < (size >> 2); i++) { - ret = sun8i_sid_register_readout(sid, i); - if (ret) - return ret; - } - } - econfig.size = size; econfig.dev = dev; - econfig.reg_read = sunxi_sid_read; + if (cfg->need_register_readout) + econfig.reg_read = sun8i_sid_read_by_reg; + else + econfig.reg_read = sunxi_sid_read; econfig.priv = sid; nvmem = nvmem_register(&econfig); if (IS_ERR(nvmem)) @@ -163,7 +192,7 @@ static int sunxi_sid_probe(struct platform_device *pdev) } for (i = 0; i < size; i++) - randomness[i] = sunxi_sid_read_byte(sid, i); + econfig.reg_read(sid, i, &randomness[i], 1); add_device_randomness(randomness, size); kfree(randomness); diff --git a/drivers/nvmem/uniphier-efuse.c b/drivers/nvmem/uniphier-efuse.c index be11880a1358..271f0b2ff86a 100644 --- a/drivers/nvmem/uniphier-efuse.c +++ b/drivers/nvmem/uniphier-efuse.c @@ -60,20 +60,9 @@ static int uniphier_efuse_probe(struct platform_device *pdev) econfig.size = resource_size(res); econfig.priv = priv; econfig.dev = dev; - nvmem = nvmem_register(&econfig); - if (IS_ERR(nvmem)) - return PTR_ERR(nvmem); + nvmem = devm_nvmem_register(dev, &econfig); - platform_set_drvdata(pdev, nvmem); - - return 0; -} - -static int uniphier_efuse_remove(struct platform_device *pdev) -{ - struct nvmem_device *nvmem = platform_get_drvdata(pdev); - - return nvmem_unregister(nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static const struct of_device_id uniphier_efuse_of_match[] = { @@ -84,7 +73,6 @@ MODULE_DEVICE_TABLE(of, uniphier_efuse_of_match); static struct platform_driver uniphier_efuse_driver = { .probe = uniphier_efuse_probe, - .remove = uniphier_efuse_remove, .driver = { .name = "uniphier-efuse", .of_match_table = uniphier_efuse_of_match, diff --git a/drivers/nvmem/vf610-ocotp.c b/drivers/nvmem/vf610-ocotp.c index 5ae9e002f195..4662309489db 100644 --- a/drivers/nvmem/vf610-ocotp.c +++ b/drivers/nvmem/vf610-ocotp.c @@ -217,21 +217,13 @@ static const struct of_device_id ocotp_of_match[] = { }; MODULE_DEVICE_TABLE(of, ocotp_of_match); -static int vf610_ocotp_remove(struct platform_device *pdev) -{ - struct vf610_ocotp *ocotp_dev = platform_get_drvdata(pdev); - - return nvmem_unregister(ocotp_dev->nvmem); -} - static int vf610_ocotp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; struct vf610_ocotp *ocotp_dev; - ocotp_dev = devm_kzalloc(&pdev->dev, - sizeof(struct vf610_ocotp), GFP_KERNEL); + ocotp_dev = devm_kzalloc(dev, sizeof(struct vf610_ocotp), GFP_KERNEL); if (!ocotp_dev) return -ENOMEM; @@ -246,26 +238,20 @@ static int vf610_ocotp_probe(struct platform_device *pdev) PTR_ERR(ocotp_dev->clk)); return PTR_ERR(ocotp_dev->clk); } + ocotp_dev->dev = dev; + ocotp_dev->timing = vf610_ocotp_calculate_timing(ocotp_dev); ocotp_config.size = resource_size(res); ocotp_config.priv = ocotp_dev; ocotp_config.dev = dev; - ocotp_dev->nvmem = nvmem_register(&ocotp_config); - if (IS_ERR(ocotp_dev->nvmem)) - return PTR_ERR(ocotp_dev->nvmem); + ocotp_dev->nvmem = devm_nvmem_register(dev, &ocotp_config); - ocotp_dev->dev = dev; - platform_set_drvdata(pdev, ocotp_dev); - - ocotp_dev->timing = vf610_ocotp_calculate_timing(ocotp_dev); - - return 0; + return PTR_ERR_OR_ZERO(ocotp_dev->nvmem); } static struct platform_driver vf610_ocotp_driver = { .probe = vf610_ocotp_probe, - .remove = vf610_ocotp_remove, .driver = { .name = "vf610-ocotp", .of_match_table = ocotp_of_match, diff --git a/drivers/parport/parport_ax88796.c b/drivers/parport/parport_ax88796.c index 2fc91edb058d..bfe97c2a8d4c 100644 --- a/drivers/parport/parport_ax88796.c +++ b/drivers/parport/parport_ax88796.c @@ -273,18 +273,16 @@ static int parport_ax88796_probe(struct platform_device *pdev) { struct device *_dev = &pdev->dev; struct ax_drvdata *dd; - struct parport *pp = NULL; + struct parport *pp; struct resource *res; unsigned long size; int spacing; int irq; int ret; - dd = kzalloc(sizeof(struct ax_drvdata), GFP_KERNEL); - if (dd == NULL) { - dev_err(_dev, "no memory for private data\n"); + dd = kzalloc(sizeof(*dd), GFP_KERNEL); + if (!dd) return -ENOMEM; - } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { diff --git a/drivers/parport/parport_pc.c b/drivers/parport/parport_pc.c index 489492b608cf..380916bff9e0 100644 --- a/drivers/parport/parport_pc.c +++ b/drivers/parport/parport_pc.c @@ -2646,6 +2646,7 @@ enum parport_pc_pci_cards { netmos_9901, netmos_9865, quatech_sppxp100, + wch_ch382l, }; @@ -2708,6 +2709,7 @@ static struct parport_pc_pci { /* netmos_9901 */ { 1, { { 0, -1 }, } }, /* netmos_9865 */ { 1, { { 0, -1 }, } }, /* quatech_sppxp100 */ { 1, { { 0, 1 }, } }, + /* wch_ch382l */ { 1, { { 2, -1 }, } }, }; static const struct pci_device_id parport_pc_pci_tbl[] = { @@ -2797,6 +2799,8 @@ static const struct pci_device_id parport_pc_pci_tbl[] = { /* Quatech SPPXP-100 Parallel port PCI ExpressCard */ { PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SPPXP_100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, quatech_sppxp100 }, + /* WCH CH382L PCI-E single parallel port card */ + { 0x1c00, 0x3050, 0x1c00, 0x3050, 0, 0, wch_ch382l }, { 0, } /* terminate list */ }; MODULE_DEVICE_TABLE(pci, parport_pc_pci_tbl); diff --git a/drivers/parport/parport_serial.c b/drivers/parport/parport_serial.c index 087e847b1da2..ae9e01ef7599 100644 --- a/drivers/parport/parport_serial.c +++ b/drivers/parport/parport_serial.c @@ -1,30 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * Support for common PCI multi-I/O cards (which is most of them) * * Copyright (C) 2001 Tim Waugh <twaugh@redhat.com> * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * * Multi-function PCI cards are supposed to present separate logical * devices on the bus. A common thing to do seems to be to just use * one logical device with lots of base address registers for both * parallel ports and serial ports. This driver is for dealing with * that. - * */ -#include <linux/types.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/pci.h> #include <linux/interrupt.h> +#include <linux/module.h> #include <linux/parport.h> #include <linux/parport_pc.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/types.h> + #include <linux/8250_pci.h> enum parport_pc_pci_cards { @@ -65,6 +59,7 @@ enum parport_pc_pci_cards { wch_ch353_1s1p, wch_ch353_2s1p, wch_ch382_2s1p, + brainboxes_5s1p, sunix_2s1p, }; @@ -153,6 +148,7 @@ static struct parport_pc_pci cards[] = { /* wch_ch353_1s1p*/ { 1, { { 1, -1}, } }, /* wch_ch353_2s1p*/ { 1, { { 2, -1}, } }, /* wch_ch382_2s1p*/ { 1, { { 2, -1}, } }, + /* brainboxes_5s1p */ { 1, { { 3, -1 }, } }, /* sunix_2s1p */ { 1, { { 3, -1 }, } }, }; @@ -258,6 +254,10 @@ static struct pci_device_id parport_serial_pci_tbl[] = { { 0x4348, 0x7053, 0x4348, 0x3253, 0, 0, wch_ch353_2s1p}, { 0x1c00, 0x3250, 0x1c00, 0x3250, 0, 0, wch_ch382_2s1p}, + /* BrainBoxes PX272/PX306 MIO card */ + { PCI_VENDOR_ID_INTASHIELD, 0x4100, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, brainboxes_5s1p }, + /* * More SUNIX variations. At least one of these has part number * '5079A but subdevice 0x102. That board reports 0x0708 as @@ -501,6 +501,12 @@ static struct pciserial_board pci_parport_serial_boards[] = { .uart_offset = 8, .first_offset = 0xC0, }, + [brainboxes_5s1p] = { + .flags = FL_BASE2, + .num_ports = 5, + .base_baud = 921600, + .uart_offset = 8, + }, [sunix_2s1p] = { .flags = FL_BASE0|FL_BASE_BARS, .num_ports = 2, @@ -524,12 +530,10 @@ static int serial_register(struct pci_dev *dev, const struct pci_device_id *id) struct serial_private *serial; board = &pci_parport_serial_boards[id->driver_data]; - if (board->num_ports == 0) return 0; serial = pciserial_init_ports(dev, board); - if (IS_ERR(serial)) return PTR_ERR(serial); @@ -558,10 +562,9 @@ static int parport_register(struct pci_dev *dev, const struct pci_device_id *id) int irq; if (priv->num_par == ARRAY_SIZE (priv->port)) { - printk (KERN_WARNING - "parport_serial: %s: only %zu parallel ports " - "supported (%d reported)\n", pci_name (dev), - ARRAY_SIZE(priv->port), card->numports); + dev_warn(&dev->dev, + "only %zu parallel ports supported (%d reported)\n", + ARRAY_SIZE(priv->port), card->numports); break; } @@ -577,12 +580,12 @@ static int parport_register(struct pci_dev *dev, const struct pci_device_id *id) irq = dev->irq; if (irq == IRQ_NONE) { dev_dbg(&dev->dev, - "PCI parallel port detected: I/O at %#lx(%#lx)\n", + "PCI parallel port detected: I/O at %#lx(%#lx)\n", io_lo, io_hi); irq = PARPORT_IRQ_NONE; } else { dev_dbg(&dev->dev, - "PCI parallel port detected: I/O at %#lx(%#lx), IRQ %d\n", + "PCI parallel port detected: I/O at %#lx(%#lx), IRQ %d\n", io_lo, io_hi, irq); } port = parport_pc_probe_port (io_lo, io_hi, irq, @@ -605,28 +608,26 @@ static int parport_serial_pci_probe(struct pci_dev *dev, struct parport_serial_private *priv; int err; - priv = kzalloc (sizeof *priv, GFP_KERNEL); + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + pci_set_drvdata (dev, priv); - err = pci_enable_device (dev); - if (err) { - kfree (priv); + err = pcim_enable_device(dev); + if (err) return err; - } - if (parport_register (dev, id)) { - kfree (priv); - return -ENODEV; - } + err = parport_register(dev, id); + if (err) + return err; - if (serial_register (dev, id)) { + err = serial_register(dev, id); + if (err) { int i; for (i = 0; i < priv->num_par; i++) parport_pc_unregister_port (priv->port[i]); - kfree (priv); - return -ENODEV; + return err; } return 0; @@ -645,78 +646,47 @@ static void parport_serial_pci_remove(struct pci_dev *dev) for (i = 0; i < priv->num_par; i++) parport_pc_unregister_port (priv->port[i]); - kfree (priv); return; } -#ifdef CONFIG_PM -static int parport_serial_pci_suspend(struct pci_dev *dev, pm_message_t state) +static int __maybe_unused parport_serial_pci_suspend(struct device *dev) { - struct parport_serial_private *priv = pci_get_drvdata(dev); + struct pci_dev *pdev = to_pci_dev(dev); + struct parport_serial_private *priv = pci_get_drvdata(pdev); if (priv->serial) pciserial_suspend_ports(priv->serial); /* FIXME: What about parport? */ - - pci_save_state(dev); - pci_set_power_state(dev, pci_choose_state(dev, state)); return 0; } -static int parport_serial_pci_resume(struct pci_dev *dev) +static int __maybe_unused parport_serial_pci_resume(struct device *dev) { - struct parport_serial_private *priv = pci_get_drvdata(dev); - int err; - - pci_set_power_state(dev, PCI_D0); - pci_restore_state(dev); - - /* - * The device may have been disabled. Re-enable it. - */ - err = pci_enable_device(dev); - if (err) { - printk(KERN_ERR "parport_serial: %s: error enabling " - "device for resume (%d)\n", pci_name(dev), err); - return err; - } + struct pci_dev *pdev = to_pci_dev(dev); + struct parport_serial_private *priv = pci_get_drvdata(pdev); if (priv->serial) pciserial_resume_ports(priv->serial); /* FIXME: What about parport? */ - return 0; } -#endif + +static SIMPLE_DEV_PM_OPS(parport_serial_pm_ops, + parport_serial_pci_suspend, parport_serial_pci_resume); static struct pci_driver parport_serial_pci_driver = { .name = "parport_serial", .id_table = parport_serial_pci_tbl, .probe = parport_serial_pci_probe, .remove = parport_serial_pci_remove, -#ifdef CONFIG_PM - .suspend = parport_serial_pci_suspend, - .resume = parport_serial_pci_resume, -#endif + .driver = { + .pm = &parport_serial_pm_ops, + }, }; - - -static int __init parport_serial_init (void) -{ - return pci_register_driver (&parport_serial_pci_driver); -} - -static void __exit parport_serial_exit (void) -{ - pci_unregister_driver (&parport_serial_pci_driver); - return; -} +module_pci_driver(parport_serial_pci_driver); MODULE_AUTHOR("Tim Waugh <twaugh@redhat.com>"); MODULE_DESCRIPTION("Driver for common parallel+serial multi-I/O PCI cards"); MODULE_LICENSE("GPL"); - -module_init(parport_serial_init); -module_exit(parport_serial_exit); diff --git a/drivers/pps/clients/pps_parport.c b/drivers/pps/clients/pps_parport.c index 83797d89c30f..4db824f88d00 100644 --- a/drivers/pps/clients/pps_parport.c +++ b/drivers/pps/clients/pps_parport.c @@ -49,6 +49,7 @@ MODULE_PARM_DESC(clear_wait, " zero turns clear edge capture off entirely"); module_param(clear_wait, uint, 0); +static DEFINE_IDA(pps_client_index); /* internal per port structure */ struct pps_client_pp { @@ -56,6 +57,7 @@ struct pps_client_pp { struct pps_device *pps; /* PPS device */ unsigned int cw; /* port clear timeout */ unsigned int cw_err; /* number of timeouts */ + int index; /* device number */ }; static inline int signal_is_set(struct parport *port) @@ -136,6 +138,8 @@ out_both: static void parport_attach(struct parport *port) { + struct pardev_cb pps_client_cb; + int index; struct pps_client_pp *device; struct pps_source_info info = { .name = KBUILD_MODNAME, @@ -154,8 +158,15 @@ static void parport_attach(struct parport *port) return; } - device->pardev = parport_register_device(port, KBUILD_MODNAME, - NULL, NULL, parport_irq, PARPORT_FLAG_EXCL, device); + index = ida_simple_get(&pps_client_index, 0, 0, GFP_KERNEL); + memset(&pps_client_cb, 0, sizeof(pps_client_cb)); + pps_client_cb.private = device; + pps_client_cb.irq_func = parport_irq; + pps_client_cb.flags = PARPORT_FLAG_EXCL; + device->pardev = parport_register_dev_model(port, + KBUILD_MODNAME, + &pps_client_cb, + index); if (!device->pardev) { pr_err("couldn't register with %s\n", port->name); goto err_free; @@ -176,6 +187,7 @@ static void parport_attach(struct parport *port) device->cw = clear_wait; port->ops->enable_irq(port); + device->index = index; pr_info("attached to %s\n", port->name); @@ -186,6 +198,7 @@ err_release_dev: err_unregister_dev: parport_unregister_device(device->pardev); err_free: + ida_simple_remove(&pps_client_index, index); kfree(device); } @@ -205,13 +218,15 @@ static void parport_detach(struct parport *port) pps_unregister_source(device->pps); parport_release(pardev); parport_unregister_device(pardev); + ida_simple_remove(&pps_client_index, device->index); kfree(device); } static struct parport_driver pps_parport_driver = { .name = KBUILD_MODNAME, - .attach = parport_attach, + .match_port = parport_attach, .detach = parport_detach, + .devmodel = true, }; /* module staff */ diff --git a/drivers/pps/generators/pps_gen_parport.c b/drivers/pps/generators/pps_gen_parport.c index 51cfde6afffd..7fd36cac063b 100644 --- a/drivers/pps/generators/pps_gen_parport.c +++ b/drivers/pps/generators/pps_gen_parport.c @@ -192,13 +192,18 @@ static inline ktime_t next_intr_time(struct pps_generator_pp *dev) static void parport_attach(struct parport *port) { + struct pardev_cb pps_cb; + if (attached) { /* we already have a port */ return; } - device.pardev = parport_register_device(port, KBUILD_MODNAME, - NULL, NULL, NULL, PARPORT_FLAG_EXCL, &device); + memset(&pps_cb, 0, sizeof(pps_cb)); + pps_cb.private = &device; + pps_cb.flags = PARPORT_FLAG_EXCL; + device.pardev = parport_register_dev_model(port, KBUILD_MODNAME, + &pps_cb, 0); if (!device.pardev) { pr_err("couldn't register with %s\n", port->name); return; @@ -236,8 +241,9 @@ static void parport_detach(struct parport *port) static struct parport_driver pps_gen_parport_driver = { .name = KBUILD_MODNAME, - .attach = parport_attach, + .match_port = parport_attach, .detach = parport_detach, + .devmodel = true, }; /* module staff */ diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c index fdfcdea25867..16590dfaafa4 100644 --- a/drivers/siox/siox-core.c +++ b/drivers/siox/siox-core.c @@ -594,7 +594,7 @@ static ssize_t device_add_store(struct device *dev, size_t inbytes = 0, outbytes = 0; u8 statustype = 0; - ret = sscanf(buf, "%20s %zu %zu %hhu", type, &inbytes, + ret = sscanf(buf, "%19s %zu %zu %hhu", type, &inbytes, &outbytes, &statustype); if (ret != 3 && ret != 4) return -EINVAL; diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c index 4988a8f4d905..7ddfc675b131 100644 --- a/drivers/slimbus/core.c +++ b/drivers/slimbus/core.c @@ -141,7 +141,7 @@ static struct slim_device *slim_alloc_device(struct slim_controller *ctrl, sbdev->e_addr = *eaddr; ret = slim_add_device(ctrl, sbdev, node); if (ret) { - kfree(sbdev); + put_device(&sbdev->dev); return NULL; } diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c index af6dde347bee..f2701194f810 100644 --- a/drivers/thunderbolt/dma_port.c +++ b/drivers/thunderbolt/dma_port.c @@ -170,24 +170,22 @@ static int dma_port_write(struct tb_ctl *ctl, const void *buffer, u64 route, static int dma_find_port(struct tb_switch *sw) { - int port, ret; - u32 type; + static const int ports[] = { 3, 5, 7 }; + int i; /* - * The DMA (NHI) port is either 3 or 5 depending on the - * controller. Try both starting from 5 which is more common. + * The DMA (NHI) port is either 3, 5 or 7 depending on the + * controller. Try all of them. */ - port = 5; - ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), port, 2, 1, - DMA_PORT_TIMEOUT); - if (!ret && (type & 0xffffff) == TB_TYPE_NHI) - return port; - - port = 3; - ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), port, 2, 1, - DMA_PORT_TIMEOUT); - if (!ret && (type & 0xffffff) == TB_TYPE_NHI) - return port; + for (i = 0; i < ARRAY_SIZE(ports); i++) { + u32 type; + int ret; + + ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), ports[i], + 2, 1, DMA_PORT_TIMEOUT); + if (!ret && (type & 0xffffff) == TB_TYPE_NHI) + return ports[i]; + } return -ENODEV; } diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 9b90115319ce..6281266b8ec0 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -117,23 +117,151 @@ static const char * const tb_security_names[] = { [TB_SECURITY_USER] = "user", [TB_SECURITY_SECURE] = "secure", [TB_SECURITY_DPONLY] = "dponly", + [TB_SECURITY_USBONLY] = "usbonly", }; +static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb *tb = container_of(dev, struct tb, dev); + uuid_t *uuids; + ssize_t ret; + int i; + + uuids = kcalloc(tb->nboot_acl, sizeof(uuid_t), GFP_KERNEL); + if (!uuids) + return -ENOMEM; + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out; + } + ret = tb->cm_ops->get_boot_acl(tb, uuids, tb->nboot_acl); + if (ret) { + mutex_unlock(&tb->lock); + goto out; + } + mutex_unlock(&tb->lock); + + for (ret = 0, i = 0; i < tb->nboot_acl; i++) { + if (!uuid_is_null(&uuids[i])) + ret += snprintf(buf + ret, PAGE_SIZE - ret, "%pUb", + &uuids[i]); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s", + i < tb->nboot_acl - 1 ? "," : "\n"); + } + +out: + kfree(uuids); + return ret; +} + +static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tb *tb = container_of(dev, struct tb, dev); + char *str, *s, *uuid_str; + ssize_t ret = 0; + uuid_t *acl; + int i = 0; + + /* + * Make sure the value is not bigger than tb->nboot_acl * UUID + * length + commas and optional "\n". Also the smallest allowable + * string is tb->nboot_acl * ",". + */ + if (count > (UUID_STRING_LEN + 1) * tb->nboot_acl + 1) + return -EINVAL; + if (count < tb->nboot_acl - 1) + return -EINVAL; + + str = kstrdup(buf, GFP_KERNEL); + if (!str) + return -ENOMEM; + + acl = kcalloc(tb->nboot_acl, sizeof(uuid_t), GFP_KERNEL); + if (!acl) { + ret = -ENOMEM; + goto err_free_str; + } + + uuid_str = strim(str); + while ((s = strsep(&uuid_str, ",")) != NULL && i < tb->nboot_acl) { + size_t len = strlen(s); + + if (len) { + if (len != UUID_STRING_LEN) { + ret = -EINVAL; + goto err_free_acl; + } + ret = uuid_parse(s, &acl[i]); + if (ret) + goto err_free_acl; + } + + i++; + } + + if (s || i < tb->nboot_acl) { + ret = -EINVAL; + goto err_free_acl; + } + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto err_free_acl; + } + ret = tb->cm_ops->set_boot_acl(tb, acl, tb->nboot_acl); + mutex_unlock(&tb->lock); + +err_free_acl: + kfree(acl); +err_free_str: + kfree(str); + + return ret ?: count; +} +static DEVICE_ATTR_RW(boot_acl); + static ssize_t security_show(struct device *dev, struct device_attribute *attr, char *buf) { struct tb *tb = container_of(dev, struct tb, dev); + const char *name = "unknown"; - return sprintf(buf, "%s\n", tb_security_names[tb->security_level]); + if (tb->security_level < ARRAY_SIZE(tb_security_names)) + name = tb_security_names[tb->security_level]; + + return sprintf(buf, "%s\n", name); } static DEVICE_ATTR_RO(security); static struct attribute *domain_attrs[] = { + &dev_attr_boot_acl.attr, &dev_attr_security.attr, NULL, }; +static umode_t domain_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct tb *tb = container_of(dev, struct tb, dev); + + if (attr == &dev_attr_boot_acl.attr) { + if (tb->nboot_acl && + tb->cm_ops->get_boot_acl && + tb->cm_ops->set_boot_acl) + return attr->mode; + return 0; + } + + return attr->mode; +} + static struct attribute_group domain_attr_group = { + .is_visible = domain_attr_is_visible, .attrs = domain_attrs, }; diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index ab02d13f40b7..2d2ceda9aa26 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -41,7 +41,8 @@ #define PHY_PORT_CS1_LINK_STATE_MASK GENMASK(29, 26) #define PHY_PORT_CS1_LINK_STATE_SHIFT 26 -#define ICM_TIMEOUT 5000 /* ms */ +#define ICM_TIMEOUT 5000 /* ms */ +#define ICM_APPROVE_TIMEOUT 10000 /* ms */ #define ICM_MAX_LINK 4 #define ICM_MAX_DEPTH 6 @@ -55,9 +56,11 @@ * @vnd_cap: Vendor defined capability where PCIe2CIO mailbox resides * (only set when @upstream_port is not %NULL) * @safe_mode: ICM is in safe mode + * @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported) * @is_supported: Checks if we can support ICM on this controller * @get_mode: Read and return the ICM firmware mode (optional) * @get_route: Find a route string for given switch + * @driver_ready: Send driver ready message to ICM * @device_connected: Handle device connected ICM message * @device_disconnected: Handle device disconnected ICM message * @xdomain_connected - Handle XDomain connected ICM message @@ -67,11 +70,15 @@ struct icm { struct mutex request_lock; struct delayed_work rescan_work; struct pci_dev *upstream_port; + size_t max_boot_acl; int vnd_cap; bool safe_mode; bool (*is_supported)(struct tb *tb); int (*get_mode)(struct tb *tb); int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route); + int (*driver_ready)(struct tb *tb, + enum tb_security_level *security_level, + size_t *nboot_acl); void (*device_connected)(struct tb *tb, const struct icm_pkg_header *hdr); void (*device_disconnected)(struct tb *tb, @@ -111,6 +118,12 @@ static inline u64 get_route(u32 route_hi, u32 route_lo) return (u64)route_hi << 32 | route_lo; } +static inline u64 get_parent_route(u64 route) +{ + int depth = tb_route_length(route); + return depth ? route & ~(0xffULL << (depth - 1) * TB_ROUTE_SHIFT) : 0; +} + static bool icm_match(const struct tb_cfg_request *req, const struct ctl_pkg *pkg) { @@ -245,6 +258,28 @@ err_free: return ret; } +static int +icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level, + size_t *nboot_acl) +{ + struct icm_fr_pkg_driver_ready_response reply; + struct icm_pkg_driver_ready request = { + .hdr.code = ICM_DRIVER_READY, + }; + int ret; + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (security_level) + *security_level = reply.security_level & ICM_FR_SLEVEL_MASK; + + return 0; +} + static int icm_fr_approve_switch(struct tb *tb, struct tb_switch *sw) { struct icm_fr_pkg_approve_device request; @@ -260,7 +295,7 @@ static int icm_fr_approve_switch(struct tb *tb, struct tb_switch *sw) memset(&reply, 0, sizeof(reply)); /* Use larger timeout as establishing tunnels can take some time */ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), - 1, 10000); + 1, ICM_APPROVE_TIMEOUT); if (ret) return ret; @@ -374,6 +409,59 @@ static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) return 0; } +static void add_switch(struct tb_switch *parent_sw, u64 route, + const uuid_t *uuid, u8 connection_id, u8 connection_key, + u8 link, u8 depth, enum tb_security_level security_level, + bool authorized, bool boot) +{ + struct tb_switch *sw; + + sw = tb_switch_alloc(parent_sw->tb, &parent_sw->dev, route); + if (!sw) + return; + + sw->uuid = kmemdup(uuid, sizeof(*uuid), GFP_KERNEL); + sw->connection_id = connection_id; + sw->connection_key = connection_key; + sw->link = link; + sw->depth = depth; + sw->authorized = authorized; + sw->security_level = security_level; + sw->boot = boot; + + /* Link the two switches now */ + tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw); + tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw); + + if (tb_switch_add(sw)) { + tb_port_at(tb_route(sw), parent_sw)->remote = NULL; + tb_switch_put(sw); + return; + } +} + +static void update_switch(struct tb_switch *parent_sw, struct tb_switch *sw, + u64 route, u8 connection_id, u8 connection_key, + u8 link, u8 depth, bool boot) +{ + /* Disconnect from parent */ + tb_port_at(tb_route(sw), parent_sw)->remote = NULL; + /* Re-connect via updated port*/ + tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw); + + /* Update with the new addressing information */ + sw->config.route_hi = upper_32_bits(route); + sw->config.route_lo = lower_32_bits(route); + sw->connection_id = connection_id; + sw->connection_key = connection_key; + sw->link = link; + sw->depth = depth; + sw->boot = boot; + + /* This switch still exists */ + sw->is_unplugged = false; +} + static void remove_switch(struct tb_switch *sw) { struct tb_switch *parent_sw; @@ -383,15 +471,52 @@ static void remove_switch(struct tb_switch *sw) tb_switch_remove(sw); } +static void add_xdomain(struct tb_switch *sw, u64 route, + const uuid_t *local_uuid, const uuid_t *remote_uuid, + u8 link, u8 depth) +{ + struct tb_xdomain *xd; + + xd = tb_xdomain_alloc(sw->tb, &sw->dev, route, local_uuid, remote_uuid); + if (!xd) + return; + + xd->link = link; + xd->depth = depth; + + tb_port_at(route, sw)->xdomain = xd; + + tb_xdomain_add(xd); +} + +static void update_xdomain(struct tb_xdomain *xd, u64 route, u8 link) +{ + xd->link = link; + xd->route = route; + xd->is_unplugged = false; +} + +static void remove_xdomain(struct tb_xdomain *xd) +{ + struct tb_switch *sw; + + sw = tb_to_switch(xd->dev.parent); + tb_port_at(xd->route, sw)->xdomain = NULL; + tb_xdomain_remove(xd); +} + static void icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) { const struct icm_fr_event_device_connected *pkg = (const struct icm_fr_event_device_connected *)hdr; + enum tb_security_level security_level; struct tb_switch *sw, *parent_sw; struct icm *icm = tb_priv(tb); bool authorized = false; + struct tb_xdomain *xd; u8 link, depth; + bool boot; u64 route; int ret; @@ -399,6 +524,15 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >> ICM_LINK_INFO_DEPTH_SHIFT; authorized = pkg->link_info & ICM_LINK_INFO_APPROVED; + security_level = (pkg->hdr.flags & ICM_FLAGS_SLEVEL_MASK) >> + ICM_FLAGS_SLEVEL_SHIFT; + boot = pkg->link_info & ICM_LINK_INFO_BOOT; + + if (pkg->link_info & ICM_LINK_INFO_REJECTED) { + tb_info(tb, "switch at %u.%u was rejected by ICM firmware because topology limit exceeded\n", + link, depth); + return; + } ret = icm->get_route(tb, link, depth, &route); if (ret) { @@ -425,16 +559,8 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) */ if (sw->depth == depth && sw_phy_port == phy_port && !!sw->authorized == authorized) { - tb_port_at(tb_route(sw), parent_sw)->remote = NULL; - tb_port_at(route, parent_sw)->remote = - tb_upstream_port(sw); - sw->config.route_hi = upper_32_bits(route); - sw->config.route_lo = lower_32_bits(route); - sw->connection_id = pkg->connection_id; - sw->connection_key = pkg->connection_key; - sw->link = link; - sw->depth = depth; - sw->is_unplugged = false; + update_switch(parent_sw, sw, route, pkg->connection_id, + pkg->connection_key, link, depth, boot); tb_switch_put(sw); return; } @@ -467,6 +593,13 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) tb_switch_put(sw); } + /* Remove existing XDomain connection if found */ + xd = tb_xdomain_find_by_link_depth(tb, link, depth); + if (xd) { + remove_xdomain(xd); + tb_xdomain_put(xd); + } + parent_sw = tb_switch_find_by_link_depth(tb, link, depth - 1); if (!parent_sw) { tb_err(tb, "failed to find parent switch for %u.%u\n", @@ -474,30 +607,10 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) return; } - sw = tb_switch_alloc(tb, &parent_sw->dev, route); - if (!sw) { - tb_switch_put(parent_sw); - return; - } - - sw->uuid = kmemdup(&pkg->ep_uuid, sizeof(pkg->ep_uuid), GFP_KERNEL); - sw->connection_id = pkg->connection_id; - sw->connection_key = pkg->connection_key; - sw->link = link; - sw->depth = depth; - sw->authorized = authorized; - sw->security_level = (pkg->hdr.flags & ICM_FLAGS_SLEVEL_MASK) >> - ICM_FLAGS_SLEVEL_SHIFT; - - /* Link the two switches now */ - tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw); - tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw); + add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id, + pkg->connection_key, link, depth, security_level, + authorized, boot); - ret = tb_switch_add(sw); - if (ret) { - tb_port_at(tb_route(sw), parent_sw)->remote = NULL; - tb_switch_put(sw); - } tb_switch_put(parent_sw); } @@ -529,15 +642,6 @@ icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) tb_switch_put(sw); } -static void remove_xdomain(struct tb_xdomain *xd) -{ - struct tb_switch *sw; - - sw = tb_to_switch(xd->dev.parent); - tb_port_at(xd->route, sw)->xdomain = NULL; - tb_xdomain_remove(xd); -} - static void icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) { @@ -577,9 +681,7 @@ icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) phy_port = phy_port_from_route(route, depth); if (xd->depth == depth && xd_phy_port == phy_port) { - xd->link = link; - xd->route = route; - xd->is_unplugged = false; + update_xdomain(xd, route, link); tb_xdomain_put(xd); return; } @@ -629,19 +731,8 @@ icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) return; } - xd = tb_xdomain_alloc(sw->tb, &sw->dev, route, - &pkg->local_uuid, &pkg->remote_uuid); - if (!xd) { - tb_switch_put(sw); - return; - } - - xd->link = link; - xd->depth = depth; - - tb_port_at(route, sw)->xdomain = xd; - - tb_xdomain_add(xd); + add_xdomain(sw, route, &pkg->local_uuid, &pkg->remote_uuid, link, + depth); tb_switch_put(sw); } @@ -664,6 +755,351 @@ icm_fr_xdomain_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) } } +static int +icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level, + size_t *nboot_acl) +{ + struct icm_tr_pkg_driver_ready_response reply; + struct icm_pkg_driver_ready request = { + .hdr.code = ICM_DRIVER_READY, + }; + int ret; + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, 20000); + if (ret) + return ret; + + if (security_level) + *security_level = reply.info & ICM_TR_INFO_SLEVEL_MASK; + if (nboot_acl) + *nboot_acl = (reply.info & ICM_TR_INFO_BOOT_ACL_MASK) >> + ICM_TR_INFO_BOOT_ACL_SHIFT; + return 0; +} + +static int icm_tr_approve_switch(struct tb *tb, struct tb_switch *sw) +{ + struct icm_tr_pkg_approve_device request; + struct icm_tr_pkg_approve_device reply; + int ret; + + memset(&request, 0, sizeof(request)); + memcpy(&request.ep_uuid, sw->uuid, sizeof(request.ep_uuid)); + request.hdr.code = ICM_APPROVE_DEVICE; + request.route_lo = sw->config.route_lo; + request.route_hi = sw->config.route_hi; + request.connection_id = sw->connection_id; + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_APPROVE_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) { + tb_warn(tb, "PCIe tunnel creation failed\n"); + return -EIO; + } + + return 0; +} + +static int icm_tr_add_switch_key(struct tb *tb, struct tb_switch *sw) +{ + struct icm_tr_pkg_add_device_key_response reply; + struct icm_tr_pkg_add_device_key request; + int ret; + + memset(&request, 0, sizeof(request)); + memcpy(&request.ep_uuid, sw->uuid, sizeof(request.ep_uuid)); + request.hdr.code = ICM_ADD_DEVICE_KEY; + request.route_lo = sw->config.route_lo; + request.route_hi = sw->config.route_hi; + request.connection_id = sw->connection_id; + memcpy(request.key, sw->key, TB_SWITCH_KEY_SIZE); + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) { + tb_warn(tb, "Adding key to switch failed\n"); + return -EIO; + } + + return 0; +} + +static int icm_tr_challenge_switch_key(struct tb *tb, struct tb_switch *sw, + const u8 *challenge, u8 *response) +{ + struct icm_tr_pkg_challenge_device_response reply; + struct icm_tr_pkg_challenge_device request; + int ret; + + memset(&request, 0, sizeof(request)); + memcpy(&request.ep_uuid, sw->uuid, sizeof(request.ep_uuid)); + request.hdr.code = ICM_CHALLENGE_DEVICE; + request.route_lo = sw->config.route_lo; + request.route_hi = sw->config.route_hi; + request.connection_id = sw->connection_id; + memcpy(request.challenge, challenge, TB_SWITCH_KEY_SIZE); + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EKEYREJECTED; + if (reply.hdr.flags & ICM_FLAGS_NO_KEY) + return -ENOKEY; + + memcpy(response, reply.response, TB_SWITCH_KEY_SIZE); + + return 0; +} + +static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) +{ + struct icm_tr_pkg_approve_xdomain_response reply; + struct icm_tr_pkg_approve_xdomain request; + int ret; + + memset(&request, 0, sizeof(request)); + request.hdr.code = ICM_APPROVE_XDOMAIN; + request.route_hi = upper_32_bits(xd->route); + request.route_lo = lower_32_bits(xd->route); + request.transmit_path = xd->transmit_path; + request.transmit_ring = xd->transmit_ring; + request.receive_path = xd->receive_path; + request.receive_ring = xd->receive_ring; + memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid)); + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EIO; + + return 0; +} + +static int icm_tr_xdomain_tear_down(struct tb *tb, struct tb_xdomain *xd, + int stage) +{ + struct icm_tr_pkg_disconnect_xdomain_response reply; + struct icm_tr_pkg_disconnect_xdomain request; + int ret; + + memset(&request, 0, sizeof(request)); + request.hdr.code = ICM_DISCONNECT_XDOMAIN; + request.stage = stage; + request.route_hi = upper_32_bits(xd->route); + request.route_lo = lower_32_bits(xd->route); + memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid)); + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EIO; + + return 0; +} + +static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) +{ + int ret; + + ret = icm_tr_xdomain_tear_down(tb, xd, 1); + if (ret) + return ret; + + usleep_range(10, 50); + return icm_tr_xdomain_tear_down(tb, xd, 2); +} + +static void +icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) +{ + const struct icm_tr_event_device_connected *pkg = + (const struct icm_tr_event_device_connected *)hdr; + enum tb_security_level security_level; + struct tb_switch *sw, *parent_sw; + struct tb_xdomain *xd; + bool authorized, boot; + u64 route; + + /* + * Currently we don't use the QoS information coming with the + * device connected message so simply just ignore that extra + * packet for now. + */ + if (pkg->hdr.packet_id) + return; + + /* + * After NVM upgrade adding root switch device fails because we + * initiated reset. During that time ICM might still send device + * connected message which we ignore here. + */ + if (!tb->root_switch) + return; + + route = get_route(pkg->route_hi, pkg->route_lo); + authorized = pkg->link_info & ICM_LINK_INFO_APPROVED; + security_level = (pkg->hdr.flags & ICM_FLAGS_SLEVEL_MASK) >> + ICM_FLAGS_SLEVEL_SHIFT; + boot = pkg->link_info & ICM_LINK_INFO_BOOT; + + if (pkg->link_info & ICM_LINK_INFO_REJECTED) { + tb_info(tb, "switch at %llx was rejected by ICM firmware because topology limit exceeded\n", + route); + return; + } + + sw = tb_switch_find_by_uuid(tb, &pkg->ep_uuid); + if (sw) { + /* Update the switch if it is still in the same place */ + if (tb_route(sw) == route && !!sw->authorized == authorized) { + parent_sw = tb_to_switch(sw->dev.parent); + update_switch(parent_sw, sw, route, pkg->connection_id, + 0, 0, 0, boot); + tb_switch_put(sw); + return; + } + + remove_switch(sw); + tb_switch_put(sw); + } + + /* Another switch with the same address */ + sw = tb_switch_find_by_route(tb, route); + if (sw) { + remove_switch(sw); + tb_switch_put(sw); + } + + /* XDomain connection with the same address */ + xd = tb_xdomain_find_by_route(tb, route); + if (xd) { + remove_xdomain(xd); + tb_xdomain_put(xd); + } + + parent_sw = tb_switch_find_by_route(tb, get_parent_route(route)); + if (!parent_sw) { + tb_err(tb, "failed to find parent switch for %llx\n", route); + return; + } + + add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id, + 0, 0, 0, security_level, authorized, boot); + + tb_switch_put(parent_sw); +} + +static void +icm_tr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) +{ + const struct icm_tr_event_device_disconnected *pkg = + (const struct icm_tr_event_device_disconnected *)hdr; + struct tb_switch *sw; + u64 route; + + route = get_route(pkg->route_hi, pkg->route_lo); + + sw = tb_switch_find_by_route(tb, route); + if (!sw) { + tb_warn(tb, "no switch exists at %llx, ignoring\n", route); + return; + } + + remove_switch(sw); + tb_switch_put(sw); +} + +static void +icm_tr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) +{ + const struct icm_tr_event_xdomain_connected *pkg = + (const struct icm_tr_event_xdomain_connected *)hdr; + struct tb_xdomain *xd; + struct tb_switch *sw; + u64 route; + + if (!tb->root_switch) + return; + + route = get_route(pkg->local_route_hi, pkg->local_route_lo); + + xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid); + if (xd) { + if (xd->route == route) { + update_xdomain(xd, route, 0); + tb_xdomain_put(xd); + return; + } + + remove_xdomain(xd); + tb_xdomain_put(xd); + } + + /* An existing xdomain with the same address */ + xd = tb_xdomain_find_by_route(tb, route); + if (xd) { + remove_xdomain(xd); + tb_xdomain_put(xd); + } + + /* + * If the user disconnected a switch during suspend and + * connected another host to the same port, remove the switch + * first. + */ + sw = get_switch_at_route(tb->root_switch, route); + if (sw) + remove_switch(sw); + + sw = tb_switch_find_by_route(tb, get_parent_route(route)); + if (!sw) { + tb_warn(tb, "no switch exists at %llx, ignoring\n", route); + return; + } + + add_xdomain(sw, route, &pkg->local_uuid, &pkg->remote_uuid, 0, 0); + tb_switch_put(sw); +} + +static void +icm_tr_xdomain_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) +{ + const struct icm_tr_event_xdomain_disconnected *pkg = + (const struct icm_tr_event_xdomain_disconnected *)hdr; + struct tb_xdomain *xd; + u64 route; + + route = get_route(pkg->route_hi, pkg->route_lo); + + xd = tb_xdomain_find_by_route(tb, route); + if (xd) { + remove_xdomain(xd); + tb_xdomain_put(xd); + } +} + static struct pci_dev *get_upstream_port(struct pci_dev *pdev) { struct pci_dev *parent; @@ -728,14 +1164,14 @@ static bool icm_ar_is_supported(struct tb *tb) static int icm_ar_get_mode(struct tb *tb) { struct tb_nhi *nhi = tb->nhi; - int retries = 5; + int retries = 60; u32 val; do { val = ioread32(nhi->iobase + REG_FW_STS); if (val & REG_FW_STS_NVM_AUTH_DONE) break; - msleep(30); + msleep(50); } while (--retries); if (!retries) { @@ -746,6 +1182,30 @@ static int icm_ar_get_mode(struct tb *tb) return nhi_mailbox_mode(nhi); } +static int +icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level, + size_t *nboot_acl) +{ + struct icm_ar_pkg_driver_ready_response reply; + struct icm_pkg_driver_ready request = { + .hdr.code = ICM_DRIVER_READY, + }; + int ret; + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (security_level) + *security_level = reply.info & ICM_AR_INFO_SLEVEL_MASK; + if (nboot_acl && (reply.info & ICM_AR_INFO_BOOT_ACL_SUPPORTED)) + *nboot_acl = (reply.info & ICM_AR_INFO_BOOT_ACL_MASK) >> + ICM_AR_INFO_BOOT_ACL_SHIFT; + return 0; +} + static int icm_ar_get_route(struct tb *tb, u8 link, u8 depth, u64 *route) { struct icm_ar_pkg_get_route_response reply; @@ -768,6 +1228,87 @@ static int icm_ar_get_route(struct tb *tb, u8 link, u8 depth, u64 *route) return 0; } +static int icm_ar_get_boot_acl(struct tb *tb, uuid_t *uuids, size_t nuuids) +{ + struct icm_ar_pkg_preboot_acl_response reply; + struct icm_ar_pkg_preboot_acl request = { + .hdr = { .code = ICM_PREBOOT_ACL }, + }; + int ret, i; + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EIO; + + for (i = 0; i < nuuids; i++) { + u32 *uuid = (u32 *)&uuids[i]; + + uuid[0] = reply.acl[i].uuid_lo; + uuid[1] = reply.acl[i].uuid_hi; + + if (uuid[0] == 0xffffffff && uuid[1] == 0xffffffff) { + /* Map empty entries to null UUID */ + uuid[0] = 0; + uuid[1] = 0; + } else { + /* Upper two DWs are always one's */ + uuid[2] = 0xffffffff; + uuid[3] = 0xffffffff; + } + } + + return ret; +} + +static int icm_ar_set_boot_acl(struct tb *tb, const uuid_t *uuids, + size_t nuuids) +{ + struct icm_ar_pkg_preboot_acl_response reply; + struct icm_ar_pkg_preboot_acl request = { + .hdr = { + .code = ICM_PREBOOT_ACL, + .flags = ICM_FLAGS_WRITE, + }, + }; + int ret, i; + + for (i = 0; i < nuuids; i++) { + const u32 *uuid = (const u32 *)&uuids[i]; + + if (uuid_is_null(&uuids[i])) { + /* + * Map null UUID to the empty (all one) entries + * for ICM. + */ + request.acl[i].uuid_lo = 0xffffffff; + request.acl[i].uuid_hi = 0xffffffff; + } else { + /* Two high DWs need to be set to all one */ + if (uuid[2] != 0xffffffff || uuid[3] != 0xffffffff) + return -EINVAL; + + request.acl[i].uuid_lo = uuid[0]; + request.acl[i].uuid_hi = uuid[1]; + } + } + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EIO; + + return 0; +} + static void icm_handle_notification(struct work_struct *work) { struct icm_notification *n = container_of(work, typeof(*n), work); @@ -814,23 +1355,18 @@ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, } static int -__icm_driver_ready(struct tb *tb, enum tb_security_level *security_level) +__icm_driver_ready(struct tb *tb, enum tb_security_level *security_level, + size_t *nboot_acl) { - struct icm_pkg_driver_ready_response reply; - struct icm_pkg_driver_ready request = { - .hdr.code = ICM_DRIVER_READY, - }; - unsigned int retries = 10; + struct icm *icm = tb_priv(tb); + unsigned int retries = 50; int ret; - memset(&reply, 0, sizeof(reply)); - ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), - 1, ICM_TIMEOUT); - if (ret) + ret = icm->driver_ready(tb, security_level, nboot_acl); + if (ret) { + tb_err(tb, "failed to send driver ready to ICM\n"); return ret; - - if (security_level) - *security_level = reply.security_level & 0xf; + } /* * Hold on here until the switch config space is accessible so @@ -848,6 +1384,7 @@ __icm_driver_ready(struct tb *tb, enum tb_security_level *security_level) msleep(50); } while (--retries); + tb_err(tb, "failed to read root switch config space, giving up\n"); return -ETIMEDOUT; } @@ -915,6 +1452,9 @@ static int icm_firmware_reset(struct tb *tb, struct tb_nhi *nhi) struct icm *icm = tb_priv(tb); u32 val; + if (!icm->upstream_port) + return -ENODEV; + /* Put ARC to wait for CIO reset event to happen */ val = ioread32(nhi->iobase + REG_FW_STS); val |= REG_FW_STS_CIO_RESET_REQ; @@ -1054,6 +1594,9 @@ static int icm_firmware_init(struct tb *tb) break; default: + if (ret < 0) + return ret; + tb_err(tb, "ICM firmware is in wrong mode: %u\n", ret); return -ENODEV; } @@ -1089,7 +1632,18 @@ static int icm_driver_ready(struct tb *tb) return 0; } - return __icm_driver_ready(tb, &tb->security_level); + ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl); + if (ret) + return ret; + + /* + * Make sure the number of supported preboot ACL matches what we + * expect or disable the whole feature. + */ + if (tb->nboot_acl > icm->max_boot_acl) + tb->nboot_acl = 0; + + return 0; } static int icm_suspend(struct tb *tb) @@ -1185,7 +1739,7 @@ static void icm_complete(struct tb *tb) * Now all existing children should be resumed, start events * from ICM to get updated status. */ - __icm_driver_ready(tb, NULL); + __icm_driver_ready(tb, NULL, NULL); /* * We do not get notifications of devices that have been @@ -1238,7 +1792,7 @@ static int icm_disconnect_pcie_paths(struct tb *tb) return nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DISCONNECT_PCIE_PATHS, 0); } -/* Falcon Ridge and Alpine Ridge */ +/* Falcon Ridge */ static const struct tb_cm_ops icm_fr_ops = { .driver_ready = icm_driver_ready, .start = icm_start, @@ -1254,6 +1808,42 @@ static const struct tb_cm_ops icm_fr_ops = { .disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths, }; +/* Alpine Ridge */ +static const struct tb_cm_ops icm_ar_ops = { + .driver_ready = icm_driver_ready, + .start = icm_start, + .stop = icm_stop, + .suspend = icm_suspend, + .complete = icm_complete, + .handle_event = icm_handle_event, + .get_boot_acl = icm_ar_get_boot_acl, + .set_boot_acl = icm_ar_set_boot_acl, + .approve_switch = icm_fr_approve_switch, + .add_switch_key = icm_fr_add_switch_key, + .challenge_switch_key = icm_fr_challenge_switch_key, + .disconnect_pcie_paths = icm_disconnect_pcie_paths, + .approve_xdomain_paths = icm_fr_approve_xdomain_paths, + .disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths, +}; + +/* Titan Ridge */ +static const struct tb_cm_ops icm_tr_ops = { + .driver_ready = icm_driver_ready, + .start = icm_start, + .stop = icm_stop, + .suspend = icm_suspend, + .complete = icm_complete, + .handle_event = icm_handle_event, + .get_boot_acl = icm_ar_get_boot_acl, + .set_boot_acl = icm_ar_set_boot_acl, + .approve_switch = icm_tr_approve_switch, + .add_switch_key = icm_tr_add_switch_key, + .challenge_switch_key = icm_tr_challenge_switch_key, + .disconnect_pcie_paths = icm_disconnect_pcie_paths, + .approve_xdomain_paths = icm_tr_approve_xdomain_paths, + .disconnect_xdomain_paths = icm_tr_disconnect_xdomain_paths, +}; + struct tb *icm_probe(struct tb_nhi *nhi) { struct icm *icm; @@ -1272,6 +1862,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: icm->is_supported = icm_fr_is_supported; icm->get_route = icm_fr_get_route; + icm->driver_ready = icm_fr_driver_ready; icm->device_connected = icm_fr_device_connected; icm->device_disconnected = icm_fr_device_disconnected; icm->xdomain_connected = icm_fr_xdomain_connected; @@ -1284,14 +1875,29 @@ struct tb *icm_probe(struct tb_nhi *nhi) case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI: + icm->max_boot_acl = ICM_AR_PREBOOT_ACL_ENTRIES; icm->is_supported = icm_ar_is_supported; icm->get_mode = icm_ar_get_mode; icm->get_route = icm_ar_get_route; + icm->driver_ready = icm_ar_driver_ready; icm->device_connected = icm_fr_device_connected; icm->device_disconnected = icm_fr_device_disconnected; icm->xdomain_connected = icm_fr_xdomain_connected; icm->xdomain_disconnected = icm_fr_xdomain_disconnected; - tb->cm_ops = &icm_fr_ops; + tb->cm_ops = &icm_ar_ops; + break; + + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI: + icm->max_boot_acl = ICM_AR_PREBOOT_ACL_ENTRIES; + icm->is_supported = icm_ar_is_supported; + icm->get_mode = icm_ar_get_mode; + icm->driver_ready = icm_tr_driver_ready; + icm->device_connected = icm_tr_device_connected; + icm->device_disconnected = icm_tr_device_disconnected; + icm->xdomain_connected = icm_tr_xdomain_connected; + icm->xdomain_disconnected = icm_tr_xdomain_disconnected; + tb->cm_ops = &icm_tr_ops; break; } diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index f45bcbc63738..f5a33e88e676 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1036,7 +1036,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) */ tb_domain_put(tb); nhi_shutdown(nhi); - return -EIO; + return res; } pci_set_drvdata(pdev, tb); @@ -1064,6 +1064,7 @@ static const struct dev_pm_ops nhi_pm_ops = { * we just disable hotplug, the * pci-tunnels stay alive. */ + .thaw_noirq = nhi_resume_noirq, .restore_noirq = nhi_resume_noirq, .suspend = nhi_suspend, .freeze = nhi_suspend, @@ -1110,6 +1111,8 @@ static struct pci_device_id nhi_ids[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, { 0,} }; diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 4476ab4cfd0c..1696a4560948 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -45,5 +45,10 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI 0x15dc #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI 0x15dd #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI 0x15de +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE 0x15e7 +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI 0x15e8 +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE 0x15ea +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI 0x15eb +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE 0x15ef #endif diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index da54ace4dd2f..25758671ddf4 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -716,6 +716,13 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) if (sw->authorized) goto unlock; + /* + * Make sure there is no PCIe rescan ongoing when a new PCIe + * tunnel is created. Otherwise the PCIe rescan code might find + * the new tunnel too early. + */ + pci_lock_rescan_remove(); + switch (val) { /* Approve switch */ case 1: @@ -735,6 +742,8 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) break; } + pci_unlock_rescan_remove(); + if (!ret) { sw->authorized = val; /* Notify status change to the userspace */ @@ -766,6 +775,15 @@ static ssize_t authorized_store(struct device *dev, } static DEVICE_ATTR_RW(authorized); +static ssize_t boot_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_switch *sw = tb_to_switch(dev); + + return sprintf(buf, "%u\n", sw->boot); +} +static DEVICE_ATTR_RO(boot); + static ssize_t device_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -942,6 +960,7 @@ static DEVICE_ATTR_RO(unique_id); static struct attribute *switch_attrs[] = { &dev_attr_authorized.attr, + &dev_attr_boot.attr, &dev_attr_device.attr, &dev_attr_device_name.attr, &dev_attr_key.attr, @@ -970,6 +989,10 @@ static umode_t switch_attr_is_visible(struct kobject *kobj, if (sw->dma_port) return attr->mode; return 0; + } else if (attr == &dev_attr_boot.attr) { + if (tb_route(sw)) + return attr->mode; + return 0; } return sw->safe_mode ? 0 : attr->mode; @@ -1028,6 +1051,9 @@ static int tb_switch_get_generation(struct tb_switch *sw) case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_BRIDGE: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE: return 3; default: @@ -1470,6 +1496,7 @@ struct tb_sw_lookup { u8 link; u8 depth; const uuid_t *uuid; + u64 route; }; static int tb_switch_match(struct device *dev, void *data) @@ -1485,6 +1512,11 @@ static int tb_switch_match(struct device *dev, void *data) if (lookup->uuid) return !memcmp(sw->uuid, lookup->uuid, sizeof(*lookup->uuid)); + if (lookup->route) { + return sw->config.route_lo == lower_32_bits(lookup->route) && + sw->config.route_hi == upper_32_bits(lookup->route); + } + /* Root switch is matched only by depth */ if (!lookup->depth) return !sw->depth; @@ -1519,7 +1551,7 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth) } /** - * tb_switch_find_by_link_depth() - Find switch by UUID + * tb_switch_find_by_uuid() - Find switch by UUID * @tb: Domain the switch belongs * @uuid: UUID to look for * @@ -1542,6 +1574,33 @@ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid) return NULL; } +/** + * tb_switch_find_by_route() - Find switch by route string + * @tb: Domain the switch belongs + * @route: Route string to look for + * + * Returned switch has reference count increased so the caller needs to + * call tb_switch_put() when done with the switch. + */ +struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route) +{ + struct tb_sw_lookup lookup; + struct device *dev; + + if (!route) + return tb_switch_get(tb->root_switch); + + memset(&lookup, 0, sizeof(lookup)); + lookup.tb = tb; + lookup.route = route; + + dev = bus_find_device(&tb_bus_type, NULL, &lookup, tb_switch_match); + if (dev) + return tb_to_switch(dev); + + return NULL; +} + void tb_switch_exit(void) { ida_destroy(&nvm_ida); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 895c57a0a090..9d9f0ca16bfb 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -66,6 +66,7 @@ struct tb_switch_nvm { * @nvm: Pointer to the NVM if the switch has one (%NULL otherwise) * @no_nvm_upgrade: Prevent NVM upgrade of this switch * @safe_mode: The switch is in safe-mode + * @boot: Whether the switch was already authorized on boot or not * @authorized: Whether the switch is authorized by user or policy * @work: Work used to automatically authorize a switch * @security_level: Switch supported security level @@ -99,6 +100,7 @@ struct tb_switch { struct tb_switch_nvm *nvm; bool no_nvm_upgrade; bool safe_mode; + bool boot; unsigned int authorized; struct work_struct work; enum tb_security_level security_level; @@ -198,6 +200,8 @@ struct tb_path { * @suspend: Connection manager specific suspend * @complete: Connection manager specific complete * @handle_event: Handle thunderbolt event + * @get_boot_acl: Get boot ACL list + * @set_boot_acl: Set boot ACL list * @approve_switch: Approve switch * @add_switch_key: Add key to switch * @challenge_switch_key: Challenge switch using key @@ -215,6 +219,8 @@ struct tb_cm_ops { void (*complete)(struct tb *tb); void (*handle_event)(struct tb *tb, enum tb_cfg_pkg_type, const void *buf, size_t size); + int (*get_boot_acl)(struct tb *tb, uuid_t *uuids, size_t nuuids); + int (*set_boot_acl)(struct tb *tb, const uuid_t *uuids, size_t nuuids); int (*approve_switch)(struct tb *tb, struct tb_switch *sw); int (*add_switch_key)(struct tb *tb, struct tb_switch *sw); int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw, @@ -386,6 +392,14 @@ struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth); struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid); +struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route); + +static inline struct tb_switch *tb_switch_get(struct tb_switch *sw) +{ + if (sw) + get_device(&sw->dev); + return sw; +} static inline void tb_switch_put(struct tb_switch *sw) { diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index b0a092baa605..bc13f8d6b804 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -102,6 +102,8 @@ enum icm_pkg_code { ICM_ADD_DEVICE_KEY = 0x6, ICM_GET_ROUTE = 0xa, ICM_APPROVE_XDOMAIN = 0x10, + ICM_DISCONNECT_XDOMAIN = 0x11, + ICM_PREBOOT_ACL = 0x18, }; enum icm_event_code { @@ -122,18 +124,23 @@ struct icm_pkg_header { #define ICM_FLAGS_NO_KEY BIT(1) #define ICM_FLAGS_SLEVEL_SHIFT 3 #define ICM_FLAGS_SLEVEL_MASK GENMASK(4, 3) +#define ICM_FLAGS_WRITE BIT(7) struct icm_pkg_driver_ready { struct icm_pkg_header hdr; }; -struct icm_pkg_driver_ready_response { +/* Falcon Ridge only messages */ + +struct icm_fr_pkg_driver_ready_response { struct icm_pkg_header hdr; u8 romver; u8 ramver; u16 security_level; }; +#define ICM_FR_SLEVEL_MASK 0xf + /* Falcon Ridge & Alpine Ridge common messages */ struct icm_fr_pkg_get_topology { @@ -176,6 +183,8 @@ struct icm_fr_event_device_connected { #define ICM_LINK_INFO_DEPTH_SHIFT 4 #define ICM_LINK_INFO_DEPTH_MASK GENMASK(7, 4) #define ICM_LINK_INFO_APPROVED BIT(8) +#define ICM_LINK_INFO_REJECTED BIT(9) +#define ICM_LINK_INFO_BOOT BIT(10) struct icm_fr_pkg_approve_device { struct icm_pkg_header hdr; @@ -270,6 +279,18 @@ struct icm_fr_pkg_approve_xdomain_response { /* Alpine Ridge only messages */ +struct icm_ar_pkg_driver_ready_response { + struct icm_pkg_header hdr; + u8 romver; + u8 ramver; + u16 info; +}; + +#define ICM_AR_INFO_SLEVEL_MASK GENMASK(3, 0) +#define ICM_AR_INFO_BOOT_ACL_SHIFT 7 +#define ICM_AR_INFO_BOOT_ACL_MASK GENMASK(11, 7) +#define ICM_AR_INFO_BOOT_ACL_SUPPORTED BIT(13) + struct icm_ar_pkg_get_route { struct icm_pkg_header hdr; u16 reserved; @@ -284,6 +305,163 @@ struct icm_ar_pkg_get_route_response { u32 route_lo; }; +struct icm_ar_boot_acl_entry { + u32 uuid_lo; + u32 uuid_hi; +}; + +#define ICM_AR_PREBOOT_ACL_ENTRIES 16 + +struct icm_ar_pkg_preboot_acl { + struct icm_pkg_header hdr; + struct icm_ar_boot_acl_entry acl[ICM_AR_PREBOOT_ACL_ENTRIES]; +}; + +struct icm_ar_pkg_preboot_acl_response { + struct icm_pkg_header hdr; + struct icm_ar_boot_acl_entry acl[ICM_AR_PREBOOT_ACL_ENTRIES]; +}; + +/* Titan Ridge messages */ + +struct icm_tr_pkg_driver_ready_response { + struct icm_pkg_header hdr; + u16 reserved1; + u16 info; + u32 nvm_version; + u16 device_id; + u16 reserved2; +}; + +#define ICM_TR_INFO_SLEVEL_MASK GENMASK(2, 0) +#define ICM_TR_INFO_BOOT_ACL_SHIFT 7 +#define ICM_TR_INFO_BOOT_ACL_MASK GENMASK(12, 7) + +struct icm_tr_event_device_connected { + struct icm_pkg_header hdr; + uuid_t ep_uuid; + u32 route_hi; + u32 route_lo; + u8 connection_id; + u8 reserved; + u16 link_info; + u32 ep_name[55]; +}; + +struct icm_tr_event_device_disconnected { + struct icm_pkg_header hdr; + u32 route_hi; + u32 route_lo; +}; + +struct icm_tr_event_xdomain_connected { + struct icm_pkg_header hdr; + u16 reserved; + u16 link_info; + uuid_t remote_uuid; + uuid_t local_uuid; + u32 local_route_hi; + u32 local_route_lo; + u32 remote_route_hi; + u32 remote_route_lo; +}; + +struct icm_tr_event_xdomain_disconnected { + struct icm_pkg_header hdr; + u32 route_hi; + u32 route_lo; + uuid_t remote_uuid; +}; + +struct icm_tr_pkg_approve_device { + struct icm_pkg_header hdr; + uuid_t ep_uuid; + u32 route_hi; + u32 route_lo; + u8 connection_id; + u8 reserved1[3]; +}; + +struct icm_tr_pkg_add_device_key { + struct icm_pkg_header hdr; + uuid_t ep_uuid; + u32 route_hi; + u32 route_lo; + u8 connection_id; + u8 reserved[3]; + u32 key[8]; +}; + +struct icm_tr_pkg_challenge_device { + struct icm_pkg_header hdr; + uuid_t ep_uuid; + u32 route_hi; + u32 route_lo; + u8 connection_id; + u8 reserved[3]; + u32 challenge[8]; +}; + +struct icm_tr_pkg_approve_xdomain { + struct icm_pkg_header hdr; + u32 route_hi; + u32 route_lo; + uuid_t remote_uuid; + u16 transmit_path; + u16 transmit_ring; + u16 receive_path; + u16 receive_ring; +}; + +struct icm_tr_pkg_disconnect_xdomain { + struct icm_pkg_header hdr; + u8 stage; + u8 reserved[3]; + u32 route_hi; + u32 route_lo; + uuid_t remote_uuid; +}; + +struct icm_tr_pkg_challenge_device_response { + struct icm_pkg_header hdr; + uuid_t ep_uuid; + u32 route_hi; + u32 route_lo; + u8 connection_id; + u8 reserved[3]; + u32 challenge[8]; + u32 response[8]; +}; + +struct icm_tr_pkg_add_device_key_response { + struct icm_pkg_header hdr; + uuid_t ep_uuid; + u32 route_hi; + u32 route_lo; + u8 connection_id; + u8 reserved[3]; +}; + +struct icm_tr_pkg_approve_xdomain_response { + struct icm_pkg_header hdr; + u32 route_hi; + u32 route_lo; + uuid_t remote_uuid; + u16 transmit_path; + u16 transmit_ring; + u16 receive_path; + u16 receive_ring; +}; + +struct icm_tr_pkg_disconnect_xdomain_response { + struct icm_pkg_header hdr; + u8 stage; + u8 reserved[3]; + u32 route_hi; + u32 route_lo; + uuid_t remote_uuid; +}; + /* XDomain messages */ struct tb_xdomain_header { diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index f25d88d4552b..8abb4e843085 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -1255,6 +1255,7 @@ struct tb_xdomain_lookup { const uuid_t *uuid; u8 link; u8 depth; + u64 route; }; static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, @@ -1275,9 +1276,13 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, if (lookup->uuid) { if (uuid_equal(xd->remote_uuid, lookup->uuid)) return xd; - } else if (lookup->link == xd->link && + } else if (lookup->link && + lookup->link == xd->link && lookup->depth == xd->depth) { return xd; + } else if (lookup->route && + lookup->route == xd->route) { + return xd; } } else if (port->remote) { xd = switch_find_xdomain(port->remote->sw, lookup); @@ -1313,12 +1318,7 @@ struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid) lookup.uuid = uuid; xd = switch_find_xdomain(tb->root_switch, &lookup); - if (xd) { - get_device(&xd->dev); - return xd; - } - - return NULL; + return tb_xdomain_get(xd); } EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid); @@ -1349,13 +1349,36 @@ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, lookup.depth = depth; xd = switch_find_xdomain(tb->root_switch, &lookup); - if (xd) { - get_device(&xd->dev); - return xd; - } + return tb_xdomain_get(xd); +} - return NULL; +/** + * tb_xdomain_find_by_route() - Find an XDomain by route string + * @tb: Domain where the XDomain belongs to + * @route: XDomain route string + * + * Finds XDomain by walking through the Thunderbolt topology below @tb. + * The returned XDomain will have its reference count increased so the + * caller needs to call tb_xdomain_put() when it is done with the + * object. + * + * This will find all XDomains including the ones that are not yet added + * to the bus (handshake is still in progress). + * + * The caller needs to hold @tb->lock. + */ +struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route) +{ + struct tb_xdomain_lookup lookup; + struct tb_xdomain *xd; + + memset(&lookup, 0, sizeof(lookup)); + lookup.route = route; + + xd = switch_find_xdomain(tb->root_switch, &lookup); + return tb_xdomain_get(xd); } +EXPORT_SYMBOL_GPL(tb_xdomain_find_by_route); bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type, const void *buf, size_t size) diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index 8ca549032c27..f695a7e8c314 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -121,6 +121,94 @@ static void hv_uio_rescind(struct vmbus_channel *channel) uio_event_notify(&pdata->info); } +/* + * Handle fault when looking for sub channel ring buffer + * Subchannel ring buffer is same as resource 0 which is main ring buffer + * This is derived from uio_vma_fault + */ +static int hv_uio_vma_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + void *ring_buffer = vma->vm_private_data; + struct page *page; + void *addr; + + addr = ring_buffer + (vmf->pgoff << PAGE_SHIFT); + page = virt_to_page(addr); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct hv_uio_vm_ops = { + .fault = hv_uio_vma_fault, +}; + +/* Sysfs API to allow mmap of the ring buffers */ +static int hv_uio_ring_mmap(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct vmbus_channel *channel + = container_of(kobj, struct vmbus_channel, kobj); + unsigned long requested_pages, actual_pages; + + if (vma->vm_end < vma->vm_start) + return -EINVAL; + + /* only allow 0 for now */ + if (vma->vm_pgoff > 0) + return -EINVAL; + + requested_pages = vma_pages(vma); + actual_pages = 2 * HV_RING_SIZE; + if (requested_pages > actual_pages) + return -EINVAL; + + vma->vm_private_data = channel->ringbuffer_pages; + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = &hv_uio_vm_ops; + return 0; +} + +static struct bin_attribute ring_buffer_bin_attr __ro_after_init = { + .attr = { + .name = "ring", + .mode = 0600, + /* size is set at init time */ + }, + .mmap = hv_uio_ring_mmap, +}; + +/* Callback from VMBUS subystem when new channel created. */ +static void +hv_uio_new_channel(struct vmbus_channel *new_sc) +{ + struct hv_device *hv_dev = new_sc->primary_channel->device_obj; + struct device *device = &hv_dev->device; + struct hv_uio_private_data *pdata = hv_get_drvdata(hv_dev); + const size_t ring_bytes = HV_RING_SIZE * PAGE_SIZE; + int ret; + + /* Create host communication ring */ + ret = vmbus_open(new_sc, ring_bytes, ring_bytes, NULL, 0, + hv_uio_channel_cb, pdata); + if (ret) { + dev_err(device, "vmbus_open subchannel failed: %d\n", ret); + return; + } + + /* Disable interrupts on sub channel */ + new_sc->inbound.ring_buffer->interrupt_mask = 1; + set_channel_read_mode(new_sc, HV_CALL_ISR); + + ret = sysfs_create_bin_file(&new_sc->kobj, &ring_buffer_bin_attr); + if (ret) { + dev_err(device, "sysfs create ring bin file failed; %d\n", ret); + vmbus_close(new_sc); + } +} + static void hv_uio_cleanup(struct hv_device *dev, struct hv_uio_private_data *pdata) { @@ -236,6 +324,7 @@ hv_uio_probe(struct hv_device *dev, } vmbus_set_chn_rescind_callback(dev->channel, hv_uio_rescind); + vmbus_set_sc_create_callback(dev->channel, hv_uio_new_channel); hv_set_drvdata(dev, pdata); diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 0c2a5a8327bd..80a778b02f28 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -706,6 +706,7 @@ static int __w1_attach_slave_device(struct w1_slave *sl) dev_err(&sl->dev, "Device registration [%s] failed. err=%d\n", dev_name(&sl->dev), err); + put_device(&sl->dev); return err; } w1_family_notify(BUS_NOTIFY_ADD_DEVICE, sl); diff --git a/fs/char_dev.c b/fs/char_dev.c index a65e4a56318c..a279c58fe360 100644 --- a/fs/char_dev.c +++ b/fs/char_dev.c @@ -67,18 +67,18 @@ static int find_dynamic_major(void) int i; struct char_device_struct *cd; - for (i = ARRAY_SIZE(chrdevs)-1; i > CHRDEV_MAJOR_DYN_END; i--) { + for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) { if (chrdevs[i] == NULL) return i; } for (i = CHRDEV_MAJOR_DYN_EXT_START; - i > CHRDEV_MAJOR_DYN_EXT_END; i--) { + i >= CHRDEV_MAJOR_DYN_EXT_END; i--) { for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next) if (cd->major == i) break; - if (cd == NULL || cd->major != i) + if (cd == NULL) return i; } @@ -121,8 +121,8 @@ __register_chrdev_region(unsigned int major, unsigned int baseminor, } if (major >= CHRDEV_MAJOR_MAX) { - pr_err("CHRDEV \"%s\" major requested (%d) is greater than the maximum (%d)\n", - name, major, CHRDEV_MAJOR_MAX); + pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n", + name, major, CHRDEV_MAJOR_MAX-1); ret = -EINVAL; goto out; } diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 6d94e82c8ad9..7f033b1ea568 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -230,6 +230,7 @@ extern void devm_extcon_unregister_notifier_all(struct device *dev, * Following APIs get the extcon_dev from devicetree or by through extcon name. */ extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name); +extern struct extcon_dev *extcon_find_edev_by_node(struct device_node *node); extern struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index); @@ -283,6 +284,11 @@ static inline struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) return ERR_PTR(-ENODEV); } +static inline struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) +{ + return ERR_PTR(-ENODEV); +} + static inline struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) { diff --git a/include/linux/extcon/extcon-gpio.h b/include/linux/extcon/extcon-gpio.h deleted file mode 100644 index 7cacafb78b09..000000000000 --- a/include/linux/extcon/extcon-gpio.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Single-state GPIO extcon driver based on extcon class - * - * Copyright (C) 2012 Samsung Electronics - * Author: MyungJoo Ham <myungjoo.ham@samsung.com> - * - * based on switch class driver - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood <lockwood@android.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ -#ifndef __EXTCON_GPIO_H__ -#define __EXTCON_GPIO_H__ __FILE__ - -#include <linux/extcon.h> - -/** - * struct gpio_extcon_pdata - A simple GPIO-controlled extcon device. - * @extcon_id: The unique id of specific external connector. - * @gpio: Corresponding GPIO. - * @gpio_active_low: Boolean describing whether gpio active state is 1 or 0 - * If true, low state of gpio means active. - * If false, high state of gpio means active. - * @debounce: Debounce time for GPIO IRQ in ms. - * @irq_flags: IRQ Flags (e.g., IRQF_TRIGGER_LOW). - * @check_on_resume: Boolean describing whether to check the state of gpio - * while resuming from sleep. - */ -struct gpio_extcon_pdata { - unsigned int extcon_id; - unsigned gpio; - bool gpio_active_low; - unsigned long debounce; - unsigned long irq_flags; - - bool check_on_resume; -}; - -#endif /* __EXTCON_GPIO_H__ */ diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 93bd6fcd6e62..2048f3c3b68a 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -844,7 +844,7 @@ struct vmbus_channel { /* * NUMA distribution policy: - * We support teo policies: + * We support two policies: * 1) Balanced: Here all performance critical channels are * distributed evenly amongst all the NUMA nodes. * This policy will be the default policy. diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h index 497706f5adca..f89598bc4e1c 100644 --- a/include/linux/nvmem-provider.h +++ b/include/linux/nvmem-provider.h @@ -22,6 +22,31 @@ typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset, typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset, void *val, size_t bytes); +/** + * struct nvmem_config - NVMEM device configuration + * + * @dev: Parent device. + * @name: Optional name. + * @id: Optional device ID used in full name. Ignored if name is NULL. + * @owner: Pointer to exporter module. Used for refcounting. + * @cells: Optional array of pre-defined NVMEM cells. + * @ncells: Number of elements in cells. + * @read_only: Device is read-only. + * @root_only: Device is accessibly to root only. + * @reg_read: Callback to read data. + * @reg_write: Callback to write data. + * @size: Device size. + * @word_size: Minimum read/write access granularity. + * @stride: Minimum read/write access stride. + * @priv: User context passed to read/write callbacks. + * + * Note: A default "nvmem<id>" name will be assigned to the device if + * no name is specified in its configuration. In such case "<id>" is + * generated with ida_simple_get() and provided id field is ignored. + * + * Note: Specifying name and setting id to -1 implies a unique device + * whose name is provided as-is (kept unaltered). + */ struct nvmem_config { struct device *dev; const char *name; @@ -47,6 +72,11 @@ struct nvmem_config { struct nvmem_device *nvmem_register(const struct nvmem_config *cfg); int nvmem_unregister(struct nvmem_device *nvmem); +struct nvmem_device *devm_nvmem_register(struct device *dev, + const struct nvmem_config *cfg); + +int devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem); + #else static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c) @@ -59,5 +89,17 @@ static inline int nvmem_unregister(struct nvmem_device *nvmem) return -ENOSYS; } +static inline struct nvmem_device * +devm_nvmem_register(struct device *dev, const struct nvmem_config *c) +{ + return nvmem_register(c); +} + +static inline int +devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem) +{ + return nvmem_unregister(nvmem); +} + #endif /* CONFIG_NVMEM */ #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 44f74b54d483..2d61d9bde83d 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -1562,6 +1562,8 @@ #define PCI_DEVICE_ID_SERVERWORKS_CSB6LPC 0x0227 #define PCI_DEVICE_ID_SERVERWORKS_HT1100LD 0x0408 +#define PCI_VENDOR_ID_ALTERA 0x1172 + #define PCI_VENDOR_ID_SBE 0x1176 #define PCI_DEVICE_ID_SBE_WANXL100 0x0301 #define PCI_DEVICE_ID_SBE_WANXL200 0x0302 diff --git a/include/linux/stm.h b/include/linux/stm.h index 210ff2292361..c6f577ab6f21 100644 --- a/include/linux/stm.h +++ b/include/linux/stm.h @@ -1,15 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* * System Trace Module (STM) infrastructure apis * Copyright (C) 2014 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. */ #ifndef _STM_H_ diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 7b69853188b1..a3ed26082bc1 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -45,12 +45,16 @@ enum tb_cfg_pkg_type { * @TB_SECURITY_USER: User approval required at minimum * @TB_SECURITY_SECURE: One time saved key required at minimum * @TB_SECURITY_DPONLY: Only tunnel Display port (and USB) + * @TB_SECURITY_USBONLY: Only tunnel USB controller of the connected + * Thunderbolt dock (and Display Port). All PCIe + * links downstream of the dock are removed. */ enum tb_security_level { TB_SECURITY_NONE, TB_SECURITY_USER, TB_SECURITY_SECURE, TB_SECURITY_DPONLY, + TB_SECURITY_USBONLY, }; /** @@ -65,6 +69,7 @@ enum tb_security_level { * @cm_ops: Connection manager specific operations vector * @index: Linux assigned domain number * @security_level: Current security level + * @nboot_acl: Number of boot ACLs the domain supports * @privdata: Private connection manager specific data */ struct tb { @@ -77,6 +82,7 @@ struct tb { const struct tb_cm_ops *cm_ops; int index; enum tb_security_level security_level; + size_t nboot_acl; unsigned long privdata[0]; }; @@ -237,6 +243,7 @@ int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path, u16 receive_ring); int tb_xdomain_disable_paths(struct tb_xdomain *xd); struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid); +struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route); static inline struct tb_xdomain * tb_xdomain_find_by_uuid_locked(struct tb *tb, const uuid_t *uuid) @@ -250,6 +257,18 @@ tb_xdomain_find_by_uuid_locked(struct tb *tb, const uuid_t *uuid) return xd; } +static inline struct tb_xdomain * +tb_xdomain_find_by_route_locked(struct tb *tb, u64 route) +{ + struct tb_xdomain *xd; + + mutex_lock(&tb->lock); + xd = tb_xdomain_find_by_route(tb, route); + mutex_unlock(&tb->lock); + + return xd; +} + static inline struct tb_xdomain *tb_xdomain_get(struct tb_xdomain *xd) { if (xd) diff --git a/include/uapi/linux/stm.h b/include/uapi/linux/stm.h index dbffdc23d804..7bac318b4440 100644 --- a/include/uapi/linux/stm.h +++ b/include/uapi/linux/stm.h @@ -3,15 +3,6 @@ * System Trace Module (STM) userspace interfaces * Copyright (c) 2014, Intel Corporation. * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * * STM class implements generic infrastructure for System Trace Module devices * as defined in MIPI STPv2 specification. */ @@ -21,6 +12,10 @@ #include <linux/types.h> +/* Maximum allowed master and channel values */ +#define STP_MASTER_MAX 0xffff +#define STP_CHANNEL_MAX 0xffff + /** * struct stp_policy_id - identification for the STP policy * @size: size of the structure including real id[] length diff --git a/lib/devres.c b/lib/devres.c index 5f2aedd58bc5..5bec1120b392 100644 --- a/lib/devres.c +++ b/lib/devres.c @@ -5,6 +5,12 @@ #include <linux/gfp.h> #include <linux/export.h> +enum devm_ioremap_type { + DEVM_IOREMAP = 0, + DEVM_IOREMAP_NC, + DEVM_IOREMAP_WC, +}; + void devm_ioremap_release(struct device *dev, void *res) { iounmap(*(void __iomem **)res); @@ -15,24 +21,28 @@ static int devm_ioremap_match(struct device *dev, void *res, void *match_data) return *(void **)res == match_data; } -/** - * devm_ioremap - Managed ioremap() - * @dev: Generic device to remap IO address for - * @offset: Resource address to map - * @size: Size of map - * - * Managed ioremap(). Map is automatically unmapped on driver detach. - */ -void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, - resource_size_t size) +static void __iomem *__devm_ioremap(struct device *dev, resource_size_t offset, + resource_size_t size, + enum devm_ioremap_type type) { - void __iomem **ptr, *addr; + void __iomem **ptr, *addr = NULL; ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return NULL; - addr = ioremap(offset, size); + switch (type) { + case DEVM_IOREMAP: + addr = ioremap(offset, size); + break; + case DEVM_IOREMAP_NC: + addr = ioremap_nocache(offset, size); + break; + case DEVM_IOREMAP_WC: + addr = ioremap_wc(offset, size); + break; + } + if (addr) { *ptr = addr; devres_add(dev, ptr); @@ -41,6 +51,20 @@ void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, return addr; } + +/** + * devm_ioremap - Managed ioremap() + * @dev: Generic device to remap IO address for + * @offset: Resource address to map + * @size: Size of map + * + * Managed ioremap(). Map is automatically unmapped on driver detach. + */ +void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, + resource_size_t size) +{ + return __devm_ioremap(dev, offset, size, DEVM_IOREMAP); +} EXPORT_SYMBOL(devm_ioremap); /** @@ -55,20 +79,7 @@ EXPORT_SYMBOL(devm_ioremap); void __iomem *devm_ioremap_nocache(struct device *dev, resource_size_t offset, resource_size_t size) { - void __iomem **ptr, *addr; - - ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return NULL; - - addr = ioremap_nocache(offset, size); - if (addr) { - *ptr = addr; - devres_add(dev, ptr); - } else - devres_free(ptr); - - return addr; + return __devm_ioremap(dev, offset, size, DEVM_IOREMAP_NC); } EXPORT_SYMBOL(devm_ioremap_nocache); @@ -83,20 +94,7 @@ EXPORT_SYMBOL(devm_ioremap_nocache); void __iomem *devm_ioremap_wc(struct device *dev, resource_size_t offset, resource_size_t size) { - void __iomem **ptr, *addr; - - ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return NULL; - - addr = ioremap_wc(offset, size); - if (addr) { - *ptr = addr; - devres_add(dev, ptr); - } else - devres_free(ptr); - - return addr; + return __devm_ioremap(dev, offset, size, DEVM_IOREMAP_WC); } EXPORT_SYMBOL(devm_ioremap_wc); diff --git a/tools/hv/hv_fcopy_daemon.c b/tools/hv/hv_fcopy_daemon.c index 457a1521f32f..d78aed86af09 100644 --- a/tools/hv/hv_fcopy_daemon.c +++ b/tools/hv/hv_fcopy_daemon.c @@ -21,15 +21,17 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <string.h> #include <errno.h> #include <linux/hyperv.h> +#include <linux/limits.h> #include <syslog.h> #include <sys/stat.h> #include <fcntl.h> #include <getopt.h> static int target_fd; -static char target_fname[W_MAX_PATH]; +static char target_fname[PATH_MAX]; static unsigned long long filesize; static int hv_start_fcopy(struct hv_start_fcopy *smsg) diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 4c99c57736ce..dbf6e8bd98ba 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -634,64 +634,6 @@ static char *kvp_if_name_to_mac(char *if_name) return mac_addr; } - -/* - * Retrieve the interface name given tha MAC address. - */ - -static char *kvp_mac_to_if_name(char *mac) -{ - DIR *dir; - struct dirent *entry; - FILE *file; - char *p, *x; - char *if_name = NULL; - char buf[256]; - char dev_id[PATH_MAX]; - unsigned int i; - - dir = opendir(KVP_NET_DIR); - if (dir == NULL) - return NULL; - - while ((entry = readdir(dir)) != NULL) { - /* - * Set the state for the next pass. - */ - snprintf(dev_id, sizeof(dev_id), "%s%s/address", KVP_NET_DIR, - entry->d_name); - - file = fopen(dev_id, "r"); - if (file == NULL) - continue; - - p = fgets(buf, sizeof(buf), file); - if (p) { - x = strchr(p, '\n'); - if (x) - *x = '\0'; - - for (i = 0; i < strlen(p); i++) - p[i] = toupper(p[i]); - - if (!strcmp(p, mac)) { - /* - * Found the MAC match; return the interface - * name. The caller will free the memory. - */ - if_name = strdup(entry->d_name); - fclose(file); - break; - } - } - fclose(file); - } - - closedir(dir); - return if_name; -} - - static void kvp_process_ipconfig_file(char *cmd, char *config_buf, unsigned int len, int element_size, int offset) @@ -997,6 +939,70 @@ getaddr_done: return error; } +/* + * Retrieve the IP given the MAC address. + */ +static int kvp_mac_to_ip(struct hv_kvp_ipaddr_value *kvp_ip_val) +{ + char *mac = (char *)kvp_ip_val->adapter_id; + DIR *dir; + struct dirent *entry; + FILE *file; + char *p, *x; + char *if_name = NULL; + char buf[256]; + char dev_id[PATH_MAX]; + unsigned int i; + int error = HV_E_FAIL; + + dir = opendir(KVP_NET_DIR); + if (dir == NULL) + return HV_E_FAIL; + + while ((entry = readdir(dir)) != NULL) { + /* + * Set the state for the next pass. + */ + snprintf(dev_id, sizeof(dev_id), "%s%s/address", KVP_NET_DIR, + entry->d_name); + + file = fopen(dev_id, "r"); + if (file == NULL) + continue; + + p = fgets(buf, sizeof(buf), file); + fclose(file); + if (!p) + continue; + + x = strchr(p, '\n'); + if (x) + *x = '\0'; + + for (i = 0; i < strlen(p); i++) + p[i] = toupper(p[i]); + + if (strcmp(p, mac)) + continue; + + /* + * Found the MAC match. + * A NIC (e.g. VF) matching the MAC, but without IP, is skipped. + */ + if_name = entry->d_name; + if (!if_name) + continue; + + error = kvp_get_ip_info(0, if_name, KVP_OP_GET_IP_INFO, + kvp_ip_val, MAX_IP_ADDR_SIZE * 2); + + if (!error && strlen((char *)kvp_ip_val->ip_addr)) + break; + } + + closedir(dir); + return error; +} static int expand_ipv6(char *addr, int type) { @@ -1472,26 +1478,12 @@ int main(int argc, char *argv[]) switch (op) { case KVP_OP_GET_IP_INFO: kvp_ip_val = &hv_msg->body.kvp_ip_val; - if_name = - kvp_mac_to_if_name((char *)kvp_ip_val->adapter_id); - if (if_name == NULL) { - /* - * We could not map the mac address to an - * interface name; return error. - */ - hv_msg->error = HV_E_FAIL; - break; - } - error = kvp_get_ip_info( - 0, if_name, KVP_OP_GET_IP_INFO, - kvp_ip_val, - (MAX_IP_ADDR_SIZE * 2)); + error = kvp_mac_to_ip(kvp_ip_val); if (error) hv_msg->error = error; - free(if_name); break; case KVP_OP_SET_IP_INFO: diff --git a/tools/hv/hv_vss_daemon.c b/tools/hv/hv_vss_daemon.c index b2b4ebffab8c..34031a297f02 100644 --- a/tools/hv/hv_vss_daemon.c +++ b/tools/hv/hv_vss_daemon.c @@ -22,6 +22,7 @@ #include <sys/poll.h> #include <sys/ioctl.h> #include <sys/stat.h> +#include <sys/sysmacros.h> #include <fcntl.h> #include <stdio.h> #include <mntent.h> |